diff options
Diffstat (limited to 'src/core')
177 files changed, 14321 insertions, 3777 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4b7395be8..b2dc71d4c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -37,6 +37,49 @@ add_library(core STATIC | |||
| 37 | debugger/gdbstub.h | 37 | debugger/gdbstub.h |
| 38 | device_memory.cpp | 38 | device_memory.cpp |
| 39 | device_memory.h | 39 | device_memory.h |
| 40 | file_sys/fssystem/fs_i_storage.h | ||
| 41 | file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp | ||
| 42 | file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h | ||
| 43 | file_sys/fssystem/fssystem_aes_ctr_storage.cpp | ||
| 44 | file_sys/fssystem/fssystem_aes_ctr_storage.h | ||
| 45 | file_sys/fssystem/fssystem_aes_xts_storage.cpp | ||
| 46 | file_sys/fssystem/fssystem_aes_xts_storage.h | ||
| 47 | file_sys/fssystem/fssystem_alignment_matching_storage.h | ||
| 48 | file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp | ||
| 49 | file_sys/fssystem/fssystem_alignment_matching_storage_impl.h | ||
| 50 | file_sys/fssystem/fssystem_bucket_tree.cpp | ||
| 51 | file_sys/fssystem/fssystem_bucket_tree.h | ||
| 52 | file_sys/fssystem/fssystem_bucket_tree_utils.h | ||
| 53 | file_sys/fssystem/fssystem_compressed_storage.h | ||
| 54 | file_sys/fssystem/fssystem_compression_common.h | ||
| 55 | file_sys/fssystem/fssystem_compression_configuration.cpp | ||
| 56 | file_sys/fssystem/fssystem_compression_configuration.h | ||
| 57 | file_sys/fssystem/fssystem_crypto_configuration.cpp | ||
| 58 | file_sys/fssystem/fssystem_crypto_configuration.h | ||
| 59 | file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp | ||
| 60 | file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h | ||
| 61 | file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp | ||
| 62 | file_sys/fssystem/fssystem_hierarchical_sha256_storage.h | ||
| 63 | file_sys/fssystem/fssystem_indirect_storage.cpp | ||
| 64 | file_sys/fssystem/fssystem_indirect_storage.h | ||
| 65 | file_sys/fssystem/fssystem_integrity_romfs_storage.cpp | ||
| 66 | file_sys/fssystem/fssystem_integrity_romfs_storage.h | ||
| 67 | file_sys/fssystem/fssystem_integrity_verification_storage.cpp | ||
| 68 | file_sys/fssystem/fssystem_integrity_verification_storage.h | ||
| 69 | file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h | ||
| 70 | file_sys/fssystem/fssystem_nca_file_system_driver.cpp | ||
| 71 | file_sys/fssystem/fssystem_nca_file_system_driver.h | ||
| 72 | file_sys/fssystem/fssystem_nca_header.cpp | ||
| 73 | file_sys/fssystem/fssystem_nca_header.h | ||
| 74 | file_sys/fssystem/fssystem_nca_reader.cpp | ||
| 75 | file_sys/fssystem/fssystem_pooled_buffer.cpp | ||
| 76 | file_sys/fssystem/fssystem_pooled_buffer.h | ||
| 77 | file_sys/fssystem/fssystem_sparse_storage.cpp | ||
| 78 | file_sys/fssystem/fssystem_sparse_storage.h | ||
| 79 | file_sys/fssystem/fssystem_switch_storage.h | ||
| 80 | file_sys/fssystem/fssystem_utility.cpp | ||
| 81 | file_sys/fssystem/fssystem_utility.h | ||
| 82 | file_sys/fssystem/fs_types.h | ||
| 40 | file_sys/bis_factory.cpp | 83 | file_sys/bis_factory.cpp |
| 41 | file_sys/bis_factory.h | 84 | file_sys/bis_factory.h |
| 42 | file_sys/card_image.cpp | 85 | file_sys/card_image.cpp |
| @@ -57,8 +100,6 @@ add_library(core STATIC | |||
| 57 | file_sys/mode.h | 100 | file_sys/mode.h |
| 58 | file_sys/nca_metadata.cpp | 101 | file_sys/nca_metadata.cpp |
| 59 | file_sys/nca_metadata.h | 102 | file_sys/nca_metadata.h |
| 60 | file_sys/nca_patch.cpp | ||
| 61 | file_sys/nca_patch.h | ||
| 62 | file_sys/partition_filesystem.cpp | 103 | file_sys/partition_filesystem.cpp |
| 63 | file_sys/partition_filesystem.h | 104 | file_sys/partition_filesystem.h |
| 64 | file_sys/patch_manager.cpp | 105 | file_sys/patch_manager.cpp |
| @@ -543,13 +584,23 @@ add_library(core STATIC | |||
| 543 | hle/service/lm/lm.h | 584 | hle/service/lm/lm.h |
| 544 | hle/service/mig/mig.cpp | 585 | hle/service/mig/mig.cpp |
| 545 | hle/service/mig/mig.h | 586 | hle/service/mig/mig.h |
| 587 | hle/service/mii/types/char_info.cpp | ||
| 588 | hle/service/mii/types/char_info.h | ||
| 589 | hle/service/mii/types/core_data.cpp | ||
| 590 | hle/service/mii/types/core_data.h | ||
| 591 | hle/service/mii/types/raw_data.cpp | ||
| 592 | hle/service/mii/types/raw_data.h | ||
| 593 | hle/service/mii/types/store_data.cpp | ||
| 594 | hle/service/mii/types/store_data.h | ||
| 595 | hle/service/mii/types/ver3_store_data.cpp | ||
| 596 | hle/service/mii/types/ver3_store_data.h | ||
| 546 | hle/service/mii/mii.cpp | 597 | hle/service/mii/mii.cpp |
| 547 | hle/service/mii/mii.h | 598 | hle/service/mii/mii.h |
| 548 | hle/service/mii/mii_manager.cpp | 599 | hle/service/mii/mii_manager.cpp |
| 549 | hle/service/mii/mii_manager.h | 600 | hle/service/mii/mii_manager.h |
| 550 | hle/service/mii/raw_data.cpp | 601 | hle/service/mii/mii_result.h |
| 551 | hle/service/mii/raw_data.h | 602 | hle/service/mii/mii_types.h |
| 552 | hle/service/mii/types.h | 603 | hle/service/mii/mii_util.h |
| 553 | hle/service/mm/mm_u.cpp | 604 | hle/service/mm/mm_u.cpp |
| 554 | hle/service/mm/mm_u.h | 605 | hle/service/mm/mm_u.h |
| 555 | hle/service/mnpp/mnpp_app.cpp | 606 | hle/service/mnpp/mnpp_app.cpp |
| @@ -576,8 +627,8 @@ add_library(core STATIC | |||
| 576 | hle/service/nfp/nfp_interface.h | 627 | hle/service/nfp/nfp_interface.h |
| 577 | hle/service/nfp/nfp_result.h | 628 | hle/service/nfp/nfp_result.h |
| 578 | hle/service/nfp/nfp_types.h | 629 | hle/service/nfp/nfp_types.h |
| 579 | hle/service/ngct/ngct.cpp | 630 | hle/service/ngc/ngc.cpp |
| 580 | hle/service/ngct/ngct.h | 631 | hle/service/ngc/ngc.h |
| 581 | hle/service/nifm/nifm.cpp | 632 | hle/service/nifm/nifm.cpp |
| 582 | hle/service/nifm/nifm.h | 633 | hle/service/nifm/nifm.h |
| 583 | hle/service/nim/nim.cpp | 634 | hle/service/nim/nim.cpp |
| @@ -813,6 +864,8 @@ add_library(core STATIC | |||
| 813 | telemetry_session.h | 864 | telemetry_session.h |
| 814 | tools/freezer.cpp | 865 | tools/freezer.cpp |
| 815 | tools/freezer.h | 866 | tools/freezer.h |
| 867 | tools/renderdoc.cpp | ||
| 868 | tools/renderdoc.h | ||
| 816 | ) | 869 | ) |
| 817 | 870 | ||
| 818 | if (MSVC) | 871 | if (MSVC) |
| @@ -828,6 +881,7 @@ else() | |||
| 828 | -Werror=conversion | 881 | -Werror=conversion |
| 829 | 882 | ||
| 830 | -Wno-sign-conversion | 883 | -Wno-sign-conversion |
| 884 | -Wno-cast-function-type | ||
| 831 | 885 | ||
| 832 | $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> | 886 | $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> |
| 833 | ) | 887 | ) |
| @@ -836,7 +890,7 @@ endif() | |||
| 836 | create_target_directory_groups(core) | 890 | create_target_directory_groups(core) |
| 837 | 891 | ||
| 838 | target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) | 892 | target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) |
| 839 | target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) | 893 | target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc) |
| 840 | if (MINGW) | 894 | if (MINGW) |
| 841 | target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) | 895 | target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) |
| 842 | endif() | 896 | endif() |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 2f67e60a9..e8300cd05 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -51,6 +51,7 @@ | |||
| 51 | #include "core/reporter.h" | 51 | #include "core/reporter.h" |
| 52 | #include "core/telemetry_session.h" | 52 | #include "core/telemetry_session.h" |
| 53 | #include "core/tools/freezer.h" | 53 | #include "core/tools/freezer.h" |
| 54 | #include "core/tools/renderdoc.h" | ||
| 54 | #include "network/network.h" | 55 | #include "network/network.h" |
| 55 | #include "video_core/host1x/host1x.h" | 56 | #include "video_core/host1x/host1x.h" |
| 56 | #include "video_core/renderer_base.h" | 57 | #include "video_core/renderer_base.h" |
| @@ -273,13 +274,18 @@ struct System::Impl { | |||
| 273 | time_manager.Initialize(); | 274 | time_manager.Initialize(); |
| 274 | 275 | ||
| 275 | is_powered_on = true; | 276 | is_powered_on = true; |
| 276 | exit_lock = false; | 277 | exit_locked = false; |
| 278 | exit_requested = false; | ||
| 277 | 279 | ||
| 278 | microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); | 280 | microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); |
| 279 | microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); | 281 | microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); |
| 280 | microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); | 282 | microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); |
| 281 | microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); | 283 | microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); |
| 282 | 284 | ||
| 285 | if (Settings::values.enable_renderdoc_hotkey) { | ||
| 286 | renderdoc_api = std::make_unique<Tools::RenderdocAPI>(); | ||
| 287 | } | ||
| 288 | |||
| 283 | LOG_DEBUG(Core, "Initialized OK"); | 289 | LOG_DEBUG(Core, "Initialized OK"); |
| 284 | 290 | ||
| 285 | return SystemResultStatus::Success; | 291 | return SystemResultStatus::Success; |
| @@ -398,12 +404,14 @@ struct System::Impl { | |||
| 398 | } | 404 | } |
| 399 | 405 | ||
| 400 | is_powered_on = false; | 406 | is_powered_on = false; |
| 401 | exit_lock = false; | 407 | exit_locked = false; |
| 408 | exit_requested = false; | ||
| 402 | 409 | ||
| 403 | if (gpu_core != nullptr) { | 410 | if (gpu_core != nullptr) { |
| 404 | gpu_core->NotifyShutdown(); | 411 | gpu_core->NotifyShutdown(); |
| 405 | } | 412 | } |
| 406 | 413 | ||
| 414 | Network::CancelPendingSocketOperations(); | ||
| 407 | kernel.SuspendApplication(true); | 415 | kernel.SuspendApplication(true); |
| 408 | if (services) { | 416 | if (services) { |
| 409 | services->KillNVNFlinger(); | 417 | services->KillNVNFlinger(); |
| @@ -425,6 +433,7 @@ struct System::Impl { | |||
| 425 | debugger.reset(); | 433 | debugger.reset(); |
| 426 | kernel.Shutdown(); | 434 | kernel.Shutdown(); |
| 427 | memory.Reset(); | 435 | memory.Reset(); |
| 436 | Network::RestartSocketOperations(); | ||
| 428 | 437 | ||
| 429 | if (auto room_member = room_network.GetRoomMember().lock()) { | 438 | if (auto room_member = room_network.GetRoomMember().lock()) { |
| 430 | Network::GameInfo game_info{}; | 439 | Network::GameInfo game_info{}; |
| @@ -507,7 +516,8 @@ struct System::Impl { | |||
| 507 | 516 | ||
| 508 | CpuManager cpu_manager; | 517 | CpuManager cpu_manager; |
| 509 | std::atomic_bool is_powered_on{}; | 518 | std::atomic_bool is_powered_on{}; |
| 510 | bool exit_lock = false; | 519 | bool exit_locked = false; |
| 520 | bool exit_requested = false; | ||
| 511 | 521 | ||
| 512 | bool nvdec_active{}; | 522 | bool nvdec_active{}; |
| 513 | 523 | ||
| @@ -516,6 +526,8 @@ struct System::Impl { | |||
| 516 | std::unique_ptr<Tools::Freezer> memory_freezer; | 526 | std::unique_ptr<Tools::Freezer> memory_freezer; |
| 517 | std::array<u8, 0x20> build_id{}; | 527 | std::array<u8, 0x20> build_id{}; |
| 518 | 528 | ||
| 529 | std::unique_ptr<Tools::RenderdocAPI> renderdoc_api; | ||
| 530 | |||
| 519 | /// Frontend applets | 531 | /// Frontend applets |
| 520 | Service::AM::Applets::AppletManager applet_manager; | 532 | Service::AM::Applets::AppletManager applet_manager; |
| 521 | 533 | ||
| @@ -559,6 +571,8 @@ struct System::Impl { | |||
| 559 | 571 | ||
| 560 | std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> | 572 | std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> |
| 561 | gpu_dirty_memory_write_manager{}; | 573 | gpu_dirty_memory_write_manager{}; |
| 574 | |||
| 575 | std::deque<std::vector<u8>> user_channel; | ||
| 562 | }; | 576 | }; |
| 563 | 577 | ||
| 564 | System::System() : impl{std::make_unique<Impl>(*this)} {} | 578 | System::System() : impl{std::make_unique<Impl>(*this)} {} |
| @@ -943,12 +957,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const { | |||
| 943 | return impl->time_manager; | 957 | return impl->time_manager; |
| 944 | } | 958 | } |
| 945 | 959 | ||
| 946 | void System::SetExitLock(bool locked) { | 960 | void System::SetExitLocked(bool locked) { |
| 947 | impl->exit_lock = locked; | 961 | impl->exit_locked = locked; |
| 962 | } | ||
| 963 | |||
| 964 | bool System::GetExitLocked() const { | ||
| 965 | return impl->exit_locked; | ||
| 948 | } | 966 | } |
| 949 | 967 | ||
| 950 | bool System::GetExitLock() const { | 968 | void System::SetExitRequested(bool requested) { |
| 951 | return impl->exit_lock; | 969 | impl->exit_requested = requested; |
| 970 | } | ||
| 971 | |||
| 972 | bool System::GetExitRequested() const { | ||
| 973 | return impl->exit_requested; | ||
| 952 | } | 974 | } |
| 953 | 975 | ||
| 954 | void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { | 976 | void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { |
| @@ -1009,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const { | |||
| 1009 | return impl->room_network; | 1031 | return impl->room_network; |
| 1010 | } | 1032 | } |
| 1011 | 1033 | ||
| 1034 | Tools::RenderdocAPI& System::GetRenderdocAPI() { | ||
| 1035 | return *impl->renderdoc_api; | ||
| 1036 | } | ||
| 1037 | |||
| 1012 | void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { | 1038 | void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { |
| 1013 | return impl->kernel.RunServer(std::move(server_manager)); | 1039 | return impl->kernel.RunServer(std::move(server_manager)); |
| 1014 | } | 1040 | } |
| @@ -1025,6 +1051,10 @@ void System::ExecuteProgram(std::size_t program_index) { | |||
| 1025 | } | 1051 | } |
| 1026 | } | 1052 | } |
| 1027 | 1053 | ||
| 1054 | std::deque<std::vector<u8>>& System::GetUserChannel() { | ||
| 1055 | return impl->user_channel; | ||
| 1056 | } | ||
| 1057 | |||
| 1028 | void System::RegisterExitCallback(ExitCallback&& callback) { | 1058 | void System::RegisterExitCallback(ExitCallback&& callback) { |
| 1029 | impl->exit_callback = std::move(callback); | 1059 | impl->exit_callback = std::move(callback); |
| 1030 | } | 1060 | } |
diff --git a/src/core/core.h b/src/core/core.h index c70ea1965..df20f26f3 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <cstddef> | 6 | #include <cstddef> |
| 7 | #include <deque> | ||
| 7 | #include <functional> | 8 | #include <functional> |
| 8 | #include <memory> | 9 | #include <memory> |
| 9 | #include <mutex> | 10 | #include <mutex> |
| @@ -101,6 +102,10 @@ namespace Network { | |||
| 101 | class RoomNetwork; | 102 | class RoomNetwork; |
| 102 | } | 103 | } |
| 103 | 104 | ||
| 105 | namespace Tools { | ||
| 106 | class RenderdocAPI; | ||
| 107 | } | ||
| 108 | |||
| 104 | namespace Core { | 109 | namespace Core { |
| 105 | 110 | ||
| 106 | class ARM_Interface; | 111 | class ARM_Interface; |
| @@ -412,8 +417,13 @@ public: | |||
| 412 | /// Gets an immutable reference to the Room Network. | 417 | /// Gets an immutable reference to the Room Network. |
| 413 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; | 418 | [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; |
| 414 | 419 | ||
| 415 | void SetExitLock(bool locked); | 420 | [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI(); |
| 416 | [[nodiscard]] bool GetExitLock() const; | 421 | |
| 422 | void SetExitLocked(bool locked); | ||
| 423 | bool GetExitLocked() const; | ||
| 424 | |||
| 425 | void SetExitRequested(bool requested); | ||
| 426 | bool GetExitRequested() const; | ||
| 417 | 427 | ||
| 418 | void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); | 428 | void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); |
| 419 | [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; | 429 | [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; |
| @@ -456,6 +466,12 @@ public: | |||
| 456 | */ | 466 | */ |
| 457 | void ExecuteProgram(std::size_t program_index); | 467 | void ExecuteProgram(std::size_t program_index); |
| 458 | 468 | ||
| 469 | /** | ||
| 470 | * Gets a reference to the user channel stack. | ||
| 471 | * It is used to transfer data between programs. | ||
| 472 | */ | ||
| 473 | [[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel(); | ||
| 474 | |||
| 459 | /// Type used for the frontend to designate a callback for System to exit the application. | 475 | /// Type used for the frontend to designate a callback for System to exit the application. |
| 460 | using ExitCallback = std::function<void()>; | 476 | using ExitCallback = std::function<void()>; |
| 461 | 477 | ||
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 4ff2c50e5..43a3c5ffd 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() { |
| @@ -658,17 +724,25 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 658 | continue; | 724 | continue; |
| 659 | } | 725 | } |
| 660 | 726 | ||
| 661 | const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); | 727 | const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16); |
| 662 | keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); | 728 | keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); |
| 663 | } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { | 729 | } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { |
| 664 | if (!ValidCryptoRevisionString(out[0], 18, 2)) { | 730 | if (!ValidCryptoRevisionString(out[0], 18, 2)) { |
| 665 | continue; | 731 | continue; |
| 666 | } | 732 | } |
| 667 | 733 | ||
| 668 | const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); | 734 | const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16); |
| 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)) { |
| @@ -676,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 676 | } | 750 | } |
| 677 | if (out[0].compare(0, kv.second.size(), kv.second) == 0) { | 751 | if (out[0].compare(0, kv.second.size(), kv.second) == 0) { |
| 678 | const auto index = | 752 | const auto index = |
| 679 | std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); | 753 | std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16); |
| 680 | const auto sub = kv.first.second; | 754 | const auto sub = kv.first.second; |
| 681 | if (sub == 0) { | 755 | if (sub == 0) { |
| 682 | s128_keys[{kv.first.first, index, 0}] = | 756 | s128_keys[{kv.first.first, index, 0}] = |
| @@ -696,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 696 | const auto& match = kak_names[j]; | 770 | const auto& match = kak_names[j]; |
| 697 | if (out[0].compare(0, std::strlen(match), match) == 0) { | 771 | if (out[0].compare(0, std::strlen(match), match) == 0) { |
| 698 | const auto index = | 772 | const auto index = |
| 699 | std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); | 773 | std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16); |
| 700 | s128_keys[{S128KeyType::KeyArea, index, j}] = | 774 | s128_keys[{S128KeyType::KeyArea, index, j}] = |
| 701 | Common::HexStringToArray<16>(out[1]); | 775 | Common::HexStringToArray<16>(out[1]); |
| 702 | } | 776 | } |
| @@ -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/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 0f839d5b4..e55831f27 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp | |||
| @@ -263,6 +263,23 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 263 | 263 | ||
| 264 | std::vector<u8> mem(size); | 264 | std::vector<u8> mem(size); |
| 265 | if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { | 265 | if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { |
| 266 | // Restore any bytes belonging to replaced instructions. | ||
| 267 | auto it = replaced_instructions.lower_bound(addr); | ||
| 268 | for (; it != replaced_instructions.end() && it->first < addr + size; it++) { | ||
| 269 | // Get the bytes of the instruction we previously replaced. | ||
| 270 | const u32 original_bytes = it->second; | ||
| 271 | |||
| 272 | // Calculate where to start writing to the output buffer. | ||
| 273 | const size_t output_offset = it->first - addr; | ||
| 274 | |||
| 275 | // Calculate how many bytes to write. | ||
| 276 | // The loop condition ensures output_offset < size. | ||
| 277 | const size_t n = std::min<size_t>(size - output_offset, sizeof(u32)); | ||
| 278 | |||
| 279 | // Write the bytes to the output buffer. | ||
| 280 | std::memcpy(mem.data() + output_offset, &original_bytes, n); | ||
| 281 | } | ||
| 282 | |||
| 266 | SendReply(Common::HexToString(mem)); | 283 | SendReply(Common::HexToString(mem)); |
| 267 | } else { | 284 | } else { |
| 268 | SendReply(GDB_STUB_REPLY_ERR); | 285 | SendReply(GDB_STUB_REPLY_ERR); |
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 5d02865f4..8b9a4fc5a 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp | |||
| @@ -31,13 +31,9 @@ XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index) | |||
| 31 | : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, | 31 | : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, |
| 32 | partitions(partition_names.size()), | 32 | partitions(partition_names.size()), |
| 33 | partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { | 33 | partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { |
| 34 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | 34 | const auto header_status = TryReadHeader(); |
| 35 | status = Loader::ResultStatus::ErrorBadXCIHeader; | 35 | if (header_status != Loader::ResultStatus::Success) { |
| 36 | return; | 36 | status = header_status; |
| 37 | } | ||
| 38 | |||
| 39 | if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||
| 40 | status = Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 41 | return; | 37 | return; |
| 42 | } | 38 | } |
| 43 | 39 | ||
| @@ -183,9 +179,9 @@ u32 XCI::GetSystemUpdateVersion() { | |||
| 183 | } | 179 | } |
| 184 | 180 | ||
| 185 | for (const auto& update_file : update->GetFiles()) { | 181 | for (const auto& update_file : update->GetFiles()) { |
| 186 | NCA nca{update_file, nullptr, 0}; | 182 | NCA nca{update_file}; |
| 187 | 183 | ||
| 188 | if (nca.GetStatus() != Loader::ResultStatus::Success) { | 184 | if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) { |
| 189 | continue; | 185 | continue; |
| 190 | } | 186 | } |
| 191 | 187 | ||
| @@ -296,7 +292,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | |||
| 296 | continue; | 292 | continue; |
| 297 | } | 293 | } |
| 298 | 294 | ||
| 299 | auto nca = std::make_shared<NCA>(partition_file, nullptr, 0); | 295 | auto nca = std::make_shared<NCA>(partition_file); |
| 300 | if (nca->IsUpdate()) { | 296 | if (nca->IsUpdate()) { |
| 301 | continue; | 297 | continue; |
| 302 | } | 298 | } |
| @@ -316,6 +312,44 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | |||
| 316 | return Loader::ResultStatus::Success; | 312 | return Loader::ResultStatus::Success; |
| 317 | } | 313 | } |
| 318 | 314 | ||
| 315 | Loader::ResultStatus XCI::TryReadHeader() { | ||
| 316 | constexpr size_t CardInitialDataRegionSize = 0x1000; | ||
| 317 | |||
| 318 | // Define the function we'll use to determine if we read a valid header. | ||
| 319 | const auto ReadCardHeader = [&]() { | ||
| 320 | // Ensure we can read the entire header. If we can't, we can't read the card image. | ||
| 321 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | ||
| 322 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 323 | } | ||
| 324 | |||
| 325 | // Ensure the header magic matches. If it doesn't, this isn't a card image header. | ||
| 326 | if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||
| 327 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 328 | } | ||
| 329 | |||
| 330 | // We read a card image header. | ||
| 331 | return Loader::ResultStatus::Success; | ||
| 332 | }; | ||
| 333 | |||
| 334 | // Try to read the header directly. | ||
| 335 | if (ReadCardHeader() == Loader::ResultStatus::Success) { | ||
| 336 | return Loader::ResultStatus::Success; | ||
| 337 | } | ||
| 338 | |||
| 339 | // Get the size of the file. | ||
| 340 | const size_t card_image_size = file->GetSize(); | ||
| 341 | |||
| 342 | // If we are large enough to have a key area, offset past the key area and retry. | ||
| 343 | if (card_image_size >= CardInitialDataRegionSize) { | ||
| 344 | file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize, | ||
| 345 | CardInitialDataRegionSize); | ||
| 346 | return ReadCardHeader(); | ||
| 347 | } | ||
| 348 | |||
| 349 | // We had no header and aren't large enough to have a key area, so this can't be parsed. | ||
| 350 | return Loader::ResultStatus::ErrorBadXCIHeader; | ||
| 351 | } | ||
| 352 | |||
| 319 | u8 XCI::GetFormatVersion() { | 353 | u8 XCI::GetFormatVersion() { |
| 320 | return GetLogoPartition() == nullptr ? 0x1 : 0x2; | 354 | return GetLogoPartition() == nullptr ? 0x1 : 0x2; |
| 321 | } | 355 | } |
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 1283f8216..9886123e7 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h | |||
| @@ -128,6 +128,7 @@ public: | |||
| 128 | 128 | ||
| 129 | private: | 129 | private: |
| 130 | Loader::ResultStatus AddNCAFromPartition(XCIPartition part); | 130 | Loader::ResultStatus AddNCAFromPartition(XCIPartition part); |
| 131 | Loader::ResultStatus TryReadHeader(); | ||
| 131 | 132 | ||
| 132 | VirtualFile file; | 133 | VirtualFile file; |
| 133 | GamecardHeader header{}; | 134 | GamecardHeader header{}; |
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 06efab46d..7d2f0abb8 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp | |||
| @@ -12,545 +12,118 @@ | |||
| 12 | #include "core/crypto/ctr_encryption_layer.h" | 12 | #include "core/crypto/ctr_encryption_layer.h" |
| 13 | #include "core/crypto/key_manager.h" | 13 | #include "core/crypto/key_manager.h" |
| 14 | #include "core/file_sys/content_archive.h" | 14 | #include "core/file_sys/content_archive.h" |
| 15 | #include "core/file_sys/nca_patch.h" | ||
| 16 | #include "core/file_sys/partition_filesystem.h" | 15 | #include "core/file_sys/partition_filesystem.h" |
| 17 | #include "core/file_sys/vfs_offset.h" | 16 | #include "core/file_sys/vfs_offset.h" |
| 18 | #include "core/loader/loader.h" | 17 | #include "core/loader/loader.h" |
| 19 | 18 | ||
| 19 | #include "core/file_sys/fssystem/fssystem_compression_configuration.h" | ||
| 20 | #include "core/file_sys/fssystem/fssystem_crypto_configuration.h" | ||
| 21 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 22 | |||
| 20 | namespace FileSys { | 23 | namespace FileSys { |
| 21 | 24 | ||
| 22 | // Media offsets in headers are stored divided by 512. Mult. by this to get real offset. | 25 | static u8 MasterKeyIdForKeyGeneration(u8 key_generation) { |
| 23 | constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; | 26 | return std::max<u8>(key_generation, 1) - 1; |
| 24 | |||
| 25 | constexpr u64 SECTION_HEADER_SIZE = 0x200; | ||
| 26 | constexpr u64 SECTION_HEADER_OFFSET = 0x400; | ||
| 27 | |||
| 28 | constexpr u32 IVFC_MAX_LEVEL = 6; | ||
| 29 | |||
| 30 | enum class NCASectionFilesystemType : u8 { | ||
| 31 | PFS0 = 0x2, | ||
| 32 | ROMFS = 0x3, | ||
| 33 | }; | ||
| 34 | |||
| 35 | struct IVFCLevel { | ||
| 36 | u64_le offset; | ||
| 37 | u64_le size; | ||
| 38 | u32_le block_size; | ||
| 39 | u32_le reserved; | ||
| 40 | }; | ||
| 41 | static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); | ||
| 42 | |||
| 43 | struct IVFCHeader { | ||
| 44 | u32_le magic; | ||
| 45 | u32_le magic_number; | ||
| 46 | INSERT_PADDING_BYTES_NOINIT(8); | ||
| 47 | std::array<IVFCLevel, 6> levels; | ||
| 48 | INSERT_PADDING_BYTES_NOINIT(64); | ||
| 49 | }; | ||
| 50 | static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); | ||
| 51 | |||
| 52 | struct NCASectionHeaderBlock { | ||
| 53 | INSERT_PADDING_BYTES_NOINIT(3); | ||
| 54 | NCASectionFilesystemType filesystem_type; | ||
| 55 | NCASectionCryptoType crypto_type; | ||
| 56 | INSERT_PADDING_BYTES_NOINIT(3); | ||
| 57 | }; | ||
| 58 | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | ||
| 59 | |||
| 60 | struct NCABucketInfo { | ||
| 61 | u64 table_offset; | ||
| 62 | u64 table_size; | ||
| 63 | std::array<u8, 0x10> table_header; | ||
| 64 | }; | ||
| 65 | static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size."); | ||
| 66 | |||
| 67 | struct NCASparseInfo { | ||
| 68 | NCABucketInfo bucket; | ||
| 69 | u64 physical_offset; | ||
| 70 | u16 generation; | ||
| 71 | INSERT_PADDING_BYTES_NOINIT(0x6); | ||
| 72 | }; | ||
| 73 | static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size."); | ||
| 74 | |||
| 75 | struct NCACompressionInfo { | ||
| 76 | NCABucketInfo bucket; | ||
| 77 | INSERT_PADDING_BYTES_NOINIT(0x8); | ||
| 78 | }; | ||
| 79 | static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size."); | ||
| 80 | |||
| 81 | struct NCASectionRaw { | ||
| 82 | NCASectionHeaderBlock header; | ||
| 83 | std::array<u8, 0x138> block_data; | ||
| 84 | std::array<u8, 0x8> section_ctr; | ||
| 85 | NCASparseInfo sparse_info; | ||
| 86 | NCACompressionInfo compression_info; | ||
| 87 | INSERT_PADDING_BYTES_NOINIT(0x60); | ||
| 88 | }; | ||
| 89 | static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); | ||
| 90 | |||
| 91 | struct PFS0Superblock { | ||
| 92 | NCASectionHeaderBlock header_block; | ||
| 93 | std::array<u8, 0x20> hash; | ||
| 94 | u32_le size; | ||
| 95 | INSERT_PADDING_BYTES_NOINIT(4); | ||
| 96 | u64_le hash_table_offset; | ||
| 97 | u64_le hash_table_size; | ||
| 98 | u64_le pfs0_header_offset; | ||
| 99 | u64_le pfs0_size; | ||
| 100 | INSERT_PADDING_BYTES_NOINIT(0x1B0); | ||
| 101 | }; | ||
| 102 | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | ||
| 103 | |||
| 104 | struct RomFSSuperblock { | ||
| 105 | NCASectionHeaderBlock header_block; | ||
| 106 | IVFCHeader ivfc; | ||
| 107 | INSERT_PADDING_BYTES_NOINIT(0x118); | ||
| 108 | }; | ||
| 109 | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); | ||
| 110 | |||
| 111 | struct BKTRHeader { | ||
| 112 | u64_le offset; | ||
| 113 | u64_le size; | ||
| 114 | u32_le magic; | ||
| 115 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 116 | u32_le number_entries; | ||
| 117 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 118 | }; | ||
| 119 | static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size."); | ||
| 120 | |||
| 121 | struct BKTRSuperblock { | ||
| 122 | NCASectionHeaderBlock header_block; | ||
| 123 | IVFCHeader ivfc; | ||
| 124 | INSERT_PADDING_BYTES_NOINIT(0x18); | ||
| 125 | BKTRHeader relocation; | ||
| 126 | BKTRHeader subsection; | ||
| 127 | INSERT_PADDING_BYTES_NOINIT(0xC0); | ||
| 128 | }; | ||
| 129 | static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size."); | ||
| 130 | |||
| 131 | union NCASectionHeader { | ||
| 132 | NCASectionRaw raw{}; | ||
| 133 | PFS0Superblock pfs0; | ||
| 134 | RomFSSuperblock romfs; | ||
| 135 | BKTRSuperblock bktr; | ||
| 136 | }; | ||
| 137 | static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | ||
| 138 | |||
| 139 | static bool IsValidNCA(const NCAHeader& header) { | ||
| 140 | // TODO(DarkLordZach): Add NCA2/NCA0 support. | ||
| 141 | return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 142 | } | 27 | } |
| 143 | 28 | ||
| 144 | NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) | 29 | NCA::NCA(VirtualFile file_, const NCA* base_nca) |
| 145 | : file(std::move(file_)), | 30 | : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { |
| 146 | bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} { | ||
| 147 | if (file == nullptr) { | 31 | if (file == nullptr) { |
| 148 | status = Loader::ResultStatus::ErrorNullFile; | 32 | status = Loader::ResultStatus::ErrorNullFile; |
| 149 | return; | 33 | return; |
| 150 | } | 34 | } |
| 151 | 35 | ||
| 152 | if (sizeof(NCAHeader) != file->ReadObject(&header)) { | 36 | reader = std::make_shared<NcaReader>(); |
| 153 | LOG_ERROR(Loader, "File reader errored out during header read."); | 37 | if (Result rc = |
| 38 | reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration()); | ||
| 39 | R_FAILED(rc)) { | ||
| 40 | if (rc != ResultInvalidNcaSignature) { | ||
| 41 | LOG_ERROR(Loader, "File reader errored out during header read: {:#x}", | ||
| 42 | rc.GetInnerValue()); | ||
| 43 | } | ||
| 154 | status = Loader::ResultStatus::ErrorBadNCAHeader; | 44 | status = Loader::ResultStatus::ErrorBadNCAHeader; |
| 155 | return; | 45 | return; |
| 156 | } | 46 | } |
| 157 | 47 | ||
| 158 | if (!HandlePotentialHeaderDecryption()) { | 48 | // Ensure we have the proper key area keys to continue. |
| 159 | return; | 49 | const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration()); |
| 160 | } | 50 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) { |
| 161 | 51 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | |
| 162 | has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; }); | ||
| 163 | |||
| 164 | const std::vector<NCASectionHeader> sections = ReadSectionHeaders(); | ||
| 165 | is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) { | ||
| 166 | return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR; | ||
| 167 | }); | ||
| 168 | |||
| 169 | if (!ReadSections(sections, bktr_base_ivfc_offset)) { | ||
| 170 | return; | 52 | return; |
| 171 | } | 53 | } |
| 172 | 54 | ||
| 173 | status = Loader::ResultStatus::Success; | 55 | RightsId rights_id{}; |
| 174 | } | 56 | reader->GetRightsId(rights_id.data(), rights_id.size()); |
| 175 | 57 | if (rights_id != RightsId{}) { | |
| 176 | NCA::~NCA() = default; | 58 | // External decryption key required; provide it here. |
| 177 | 59 | u128 rights_id_u128; | |
| 178 | bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { | 60 | std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); |
| 179 | if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { | ||
| 180 | status = Loader::ResultStatus::ErrorNCA2; | ||
| 181 | return false; | ||
| 182 | } | ||
| 183 | 61 | ||
| 184 | if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { | 62 | auto titlekey = |
| 185 | status = Loader::ResultStatus::ErrorNCA0; | 63 | keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]); |
| 186 | return false; | 64 | if (titlekey == Core::Crypto::Key128{}) { |
| 187 | } | 65 | status = Loader::ResultStatus::ErrorMissingTitlekey; |
| 188 | 66 | return; | |
| 189 | return true; | ||
| 190 | } | ||
| 191 | |||
| 192 | bool NCA::HandlePotentialHeaderDecryption() { | ||
| 193 | if (IsValidNCA(header)) { | ||
| 194 | return true; | ||
| 195 | } | ||
| 196 | |||
| 197 | if (!CheckSupportedNCA(header)) { | ||
| 198 | return false; | ||
| 199 | } | ||
| 200 | |||
| 201 | NCAHeader dec_header{}; | ||
| 202 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 203 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 204 | cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, | ||
| 205 | Core::Crypto::Op::Decrypt); | ||
| 206 | if (IsValidNCA(dec_header)) { | ||
| 207 | header = dec_header; | ||
| 208 | encrypted = true; | ||
| 209 | } else { | ||
| 210 | if (!CheckSupportedNCA(dec_header)) { | ||
| 211 | return false; | ||
| 212 | } | 67 | } |
| 213 | 68 | ||
| 214 | if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { | 69 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { |
| 215 | status = Loader::ResultStatus::ErrorIncorrectHeaderKey; | 70 | status = Loader::ResultStatus::ErrorMissingTitlekek; |
| 216 | } else { | 71 | return; |
| 217 | status = Loader::ResultStatus::ErrorMissingHeaderKey; | ||
| 218 | } | 72 | } |
| 219 | return false; | ||
| 220 | } | ||
| 221 | 73 | ||
| 222 | return true; | 74 | auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id); |
| 223 | } | 75 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); |
| 76 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), | ||
| 77 | Core::Crypto::Op::Decrypt); | ||
| 224 | 78 | ||
| 225 | std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const { | 79 | reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size()); |
| 226 | const std::ptrdiff_t number_sections = | ||
| 227 | std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) { | ||
| 228 | return entry.media_offset > 0; | ||
| 229 | }); | ||
| 230 | |||
| 231 | std::vector<NCASectionHeader> sections(number_sections); | ||
| 232 | const auto length_sections = SECTION_HEADER_SIZE * number_sections; | ||
| 233 | |||
| 234 | if (encrypted) { | ||
| 235 | auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); | ||
| 236 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 237 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 238 | cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, | ||
| 239 | Core::Crypto::Op::Decrypt); | ||
| 240 | } else { | ||
| 241 | file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); | ||
| 242 | } | ||
| 243 | |||
| 244 | return sections; | ||
| 245 | } | ||
| 246 | |||
| 247 | bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) { | ||
| 248 | for (std::size_t i = 0; i < sections.size(); ++i) { | ||
| 249 | const auto& section = sections[i]; | ||
| 250 | |||
| 251 | if (section.raw.sparse_info.bucket.table_offset != 0 && | ||
| 252 | section.raw.sparse_info.bucket.table_size != 0) { | ||
| 253 | LOG_ERROR(Loader, "Sparse NCAs are not supported."); | ||
| 254 | status = Loader::ResultStatus::ErrorSparseNCA; | ||
| 255 | return false; | ||
| 256 | } | ||
| 257 | |||
| 258 | if (section.raw.compression_info.bucket.table_offset != 0 && | ||
| 259 | section.raw.compression_info.bucket.table_size != 0) { | ||
| 260 | LOG_ERROR(Loader, "Compressed NCAs are not supported."); | ||
| 261 | status = Loader::ResultStatus::ErrorCompressedNCA; | ||
| 262 | return false; | ||
| 263 | } | ||
| 264 | |||
| 265 | if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||
| 266 | if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) { | ||
| 267 | return false; | ||
| 268 | } | ||
| 269 | } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { | ||
| 270 | if (!ReadPFS0Section(section, header.section_tables[i])) { | ||
| 271 | return false; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | return true; | ||
| 277 | } | ||
| 278 | |||
| 279 | bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, | ||
| 280 | u64 bktr_base_ivfc_offset) { | ||
| 281 | const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER; | ||
| 282 | ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | ||
| 283 | const std::size_t romfs_offset = base_offset + ivfc_offset; | ||
| 284 | const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; | ||
| 285 | auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset); | ||
| 286 | auto dec = Decrypt(section, raw, romfs_offset); | ||
| 287 | |||
| 288 | if (dec == nullptr) { | ||
| 289 | if (status != Loader::ResultStatus::Success) | ||
| 290 | return false; | ||
| 291 | if (has_rights_id) | ||
| 292 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 293 | else | ||
| 294 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 295 | return false; | ||
| 296 | } | 80 | } |
| 297 | 81 | ||
| 298 | if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { | 82 | const s32 fs_count = reader->GetFsCount(); |
| 299 | if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || | 83 | NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader); |
| 300 | section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { | 84 | std::vector<VirtualFile> filesystems(fs_count); |
| 301 | status = Loader::ResultStatus::ErrorBadBKTRHeader; | 85 | for (s32 i = 0; i < fs_count; i++) { |
| 302 | return false; | 86 | NcaFsHeaderReader header_reader; |
| 303 | } | 87 | const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i); |
| 304 | 88 | if (R_FAILED(rc)) { | |
| 305 | if (section.bktr.relocation.offset + section.bktr.relocation.size != | 89 | LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i, |
| 306 | section.bktr.subsection.offset) { | 90 | rc.GetInnerValue()); |
| 307 | status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; | 91 | status = Loader::ResultStatus::ErrorBadNCAHeader; |
| 308 | return false; | 92 | return; |
| 309 | } | ||
| 310 | |||
| 311 | const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); | ||
| 312 | if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { | ||
| 313 | status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; | ||
| 314 | return false; | ||
| 315 | } | ||
| 316 | |||
| 317 | const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | ||
| 318 | RelocationBlock relocation_block{}; | ||
| 319 | if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != | ||
| 320 | sizeof(RelocationBlock)) { | ||
| 321 | status = Loader::ResultStatus::ErrorBadRelocationBlock; | ||
| 322 | return false; | ||
| 323 | } | ||
| 324 | SubsectionBlock subsection_block{}; | ||
| 325 | if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != | ||
| 326 | sizeof(RelocationBlock)) { | ||
| 327 | status = Loader::ResultStatus::ErrorBadSubsectionBlock; | ||
| 328 | return false; | ||
| 329 | } | ||
| 330 | |||
| 331 | std::vector<RelocationBucketRaw> relocation_buckets_raw( | ||
| 332 | (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw)); | ||
| 333 | if (dec->ReadBytes(relocation_buckets_raw.data(), | ||
| 334 | section.bktr.relocation.size - sizeof(RelocationBlock), | ||
| 335 | section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) != | ||
| 336 | section.bktr.relocation.size - sizeof(RelocationBlock)) { | ||
| 337 | status = Loader::ResultStatus::ErrorBadRelocationBuckets; | ||
| 338 | return false; | ||
| 339 | } | 93 | } |
| 340 | 94 | ||
| 341 | std::vector<SubsectionBucketRaw> subsection_buckets_raw( | 95 | if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) { |
| 342 | (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw)); | 96 | files.push_back(filesystems[i]); |
| 343 | if (dec->ReadBytes(subsection_buckets_raw.data(), | 97 | romfs = files.back(); |
| 344 | section.bktr.subsection.size - sizeof(SubsectionBlock), | ||
| 345 | section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) != | ||
| 346 | section.bktr.subsection.size - sizeof(SubsectionBlock)) { | ||
| 347 | status = Loader::ResultStatus::ErrorBadSubsectionBuckets; | ||
| 348 | return false; | ||
| 349 | } | 98 | } |
| 350 | 99 | ||
| 351 | std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); | 100 | if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) { |
| 352 | std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(), | 101 | auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]); |
| 353 | &ConvertRelocationBucketRaw); | 102 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { |
| 354 | std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size()); | 103 | dirs.push_back(npfs); |
| 355 | std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(), | 104 | if (IsDirectoryExeFS(npfs)) { |
| 356 | &ConvertSubsectionBucketRaw); | 105 | exefs = dirs.back(); |
| 357 | 106 | } else if (IsDirectoryLogoPartition(npfs)) { | |
| 358 | u32 ctr_low; | 107 | logo = dirs.back(); |
| 359 | std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); | 108 | } else { |
| 360 | subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); | 109 | continue; |
| 361 | subsection_buckets.back().entries.push_back({size, {0}, 0}); | ||
| 362 | |||
| 363 | std::optional<Core::Crypto::Key128> key; | ||
| 364 | if (encrypted) { | ||
| 365 | if (has_rights_id) { | ||
| 366 | status = Loader::ResultStatus::Success; | ||
| 367 | key = GetTitlekey(); | ||
| 368 | if (!key) { | ||
| 369 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 370 | return false; | ||
| 371 | } | ||
| 372 | } else { | ||
| 373 | key = GetKeyAreaKey(NCASectionCryptoType::BKTR); | ||
| 374 | if (!key) { | ||
| 375 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 376 | return false; | ||
| 377 | } | 110 | } |
| 378 | } | 111 | } |
| 379 | } | 112 | } |
| 380 | 113 | ||
| 381 | if (bktr_base_romfs == nullptr) { | 114 | if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) { |
| 382 | status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; | 115 | is_update = true; |
| 383 | return false; | ||
| 384 | } | 116 | } |
| 385 | |||
| 386 | auto bktr = std::make_shared<BKTR>( | ||
| 387 | bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), | ||
| 388 | relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted, | ||
| 389 | encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset, | ||
| 390 | section.raw.section_ctr); | ||
| 391 | |||
| 392 | // BKTR applies to entire IVFC, so make an offset version to level 6 | ||
| 393 | files.push_back(std::make_shared<OffsetVfsFile>( | ||
| 394 | bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); | ||
| 395 | } else { | ||
| 396 | files.push_back(std::move(dec)); | ||
| 397 | } | 117 | } |
| 398 | 118 | ||
| 399 | romfs = files.back(); | 119 | if (is_update && base_nca == nullptr) { |
| 400 | return true; | 120 | status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; |
| 401 | } | ||
| 402 | |||
| 403 | bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) { | ||
| 404 | const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) + | ||
| 405 | section.pfs0.pfs0_header_offset; | ||
| 406 | const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); | ||
| 407 | |||
| 408 | auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); | ||
| 409 | if (dec != nullptr) { | ||
| 410 | auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); | ||
| 411 | |||
| 412 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { | ||
| 413 | dirs.push_back(std::move(npfs)); | ||
| 414 | if (IsDirectoryExeFS(dirs.back())) | ||
| 415 | exefs = dirs.back(); | ||
| 416 | else if (IsDirectoryLogoPartition(dirs.back())) | ||
| 417 | logo = dirs.back(); | ||
| 418 | } else { | ||
| 419 | if (has_rights_id) | ||
| 420 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 421 | else | ||
| 422 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 423 | return false; | ||
| 424 | } | ||
| 425 | } else { | 121 | } else { |
| 426 | if (status != Loader::ResultStatus::Success) | 122 | status = Loader::ResultStatus::Success; |
| 427 | return false; | ||
| 428 | if (has_rights_id) | ||
| 429 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 430 | else | ||
| 431 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 432 | return false; | ||
| 433 | } | 123 | } |
| 434 | |||
| 435 | return true; | ||
| 436 | } | ||
| 437 | |||
| 438 | u8 NCA::GetCryptoRevision() const { | ||
| 439 | u8 master_key_id = header.crypto_type; | ||
| 440 | if (header.crypto_type_2 > master_key_id) | ||
| 441 | master_key_id = header.crypto_type_2; | ||
| 442 | if (master_key_id > 0) | ||
| 443 | --master_key_id; | ||
| 444 | return master_key_id; | ||
| 445 | } | ||
| 446 | |||
| 447 | std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { | ||
| 448 | const auto master_key_id = GetCryptoRevision(); | ||
| 449 | |||
| 450 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) { | ||
| 451 | return std::nullopt; | ||
| 452 | } | ||
| 453 | |||
| 454 | std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); | ||
| 455 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 456 | keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), | ||
| 457 | Core::Crypto::Mode::ECB); | ||
| 458 | cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); | ||
| 459 | |||
| 460 | Core::Crypto::Key128 out{}; | ||
| 461 | if (type == NCASectionCryptoType::XTS) { | ||
| 462 | std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); | ||
| 463 | } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) { | ||
| 464 | std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); | ||
| 465 | } else { | ||
| 466 | LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", | ||
| 467 | type); | ||
| 468 | } | ||
| 469 | |||
| 470 | u128 out_128{}; | ||
| 471 | std::memcpy(out_128.data(), out.data(), sizeof(u128)); | ||
| 472 | LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", | ||
| 473 | master_key_id, header.key_index, out_128[1], out_128[0]); | ||
| 474 | |||
| 475 | return out; | ||
| 476 | } | 124 | } |
| 477 | 125 | ||
| 478 | std::optional<Core::Crypto::Key128> NCA::GetTitlekey() { | 126 | NCA::~NCA() = default; |
| 479 | const auto master_key_id = GetCryptoRevision(); | ||
| 480 | |||
| 481 | u128 rights_id{}; | ||
| 482 | memcpy(rights_id.data(), header.rights_id.data(), 16); | ||
| 483 | if (rights_id == u128{}) { | ||
| 484 | status = Loader::ResultStatus::ErrorInvalidRightsID; | ||
| 485 | return std::nullopt; | ||
| 486 | } | ||
| 487 | |||
| 488 | auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); | ||
| 489 | if (titlekey == Core::Crypto::Key128{}) { | ||
| 490 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 491 | return std::nullopt; | ||
| 492 | } | ||
| 493 | |||
| 494 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { | ||
| 495 | status = Loader::ResultStatus::ErrorMissingTitlekek; | ||
| 496 | return std::nullopt; | ||
| 497 | } | ||
| 498 | |||
| 499 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 500 | keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); | ||
| 501 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); | ||
| 502 | |||
| 503 | return titlekey; | ||
| 504 | } | ||
| 505 | |||
| 506 | VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) { | ||
| 507 | if (!encrypted) | ||
| 508 | return in; | ||
| 509 | |||
| 510 | switch (s_header.raw.header.crypto_type) { | ||
| 511 | case NCASectionCryptoType::NONE: | ||
| 512 | LOG_TRACE(Crypto, "called with mode=NONE"); | ||
| 513 | return in; | ||
| 514 | case NCASectionCryptoType::CTR: | ||
| 515 | // During normal BKTR decryption, this entire function is skipped. This is for the metadata, | ||
| 516 | // which uses the same CTR as usual. | ||
| 517 | case NCASectionCryptoType::BKTR: | ||
| 518 | LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); | ||
| 519 | { | ||
| 520 | std::optional<Core::Crypto::Key128> key; | ||
| 521 | if (has_rights_id) { | ||
| 522 | status = Loader::ResultStatus::Success; | ||
| 523 | key = GetTitlekey(); | ||
| 524 | if (!key) { | ||
| 525 | if (status == Loader::ResultStatus::Success) | ||
| 526 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 527 | return nullptr; | ||
| 528 | } | ||
| 529 | } else { | ||
| 530 | key = GetKeyAreaKey(NCASectionCryptoType::CTR); | ||
| 531 | if (!key) { | ||
| 532 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 533 | return nullptr; | ||
| 534 | } | ||
| 535 | } | ||
| 536 | |||
| 537 | auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key, | ||
| 538 | starting_offset); | ||
| 539 | Core::Crypto::CTREncryptionLayer::IVData iv{}; | ||
| 540 | for (std::size_t i = 0; i < 8; ++i) { | ||
| 541 | iv[i] = s_header.raw.section_ctr[8 - i - 1]; | ||
| 542 | } | ||
| 543 | out->SetIV(iv); | ||
| 544 | return std::static_pointer_cast<VfsFile>(out); | ||
| 545 | } | ||
| 546 | case NCASectionCryptoType::XTS: | ||
| 547 | // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs | ||
| 548 | default: | ||
| 549 | LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", | ||
| 550 | s_header.raw.header.crypto_type); | ||
| 551 | return nullptr; | ||
| 552 | } | ||
| 553 | } | ||
| 554 | 127 | ||
| 555 | Loader::ResultStatus NCA::GetStatus() const { | 128 | Loader::ResultStatus NCA::GetStatus() const { |
| 556 | return status; | 129 | return status; |
| @@ -579,21 +152,24 @@ VirtualDir NCA::GetParentDirectory() const { | |||
| 579 | } | 152 | } |
| 580 | 153 | ||
| 581 | NCAContentType NCA::GetType() const { | 154 | NCAContentType NCA::GetType() const { |
| 582 | return header.content_type; | 155 | return static_cast<NCAContentType>(reader->GetContentType()); |
| 583 | } | 156 | } |
| 584 | 157 | ||
| 585 | u64 NCA::GetTitleId() const { | 158 | u64 NCA::GetTitleId() const { |
| 586 | if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) | 159 | if (is_update) { |
| 587 | return header.title_id | 0x800; | 160 | return reader->GetProgramId() | 0x800; |
| 588 | return header.title_id; | 161 | } |
| 162 | return reader->GetProgramId(); | ||
| 589 | } | 163 | } |
| 590 | 164 | ||
| 591 | std::array<u8, 16> NCA::GetRightsId() const { | 165 | RightsId NCA::GetRightsId() const { |
| 592 | return header.rights_id; | 166 | RightsId result; |
| 167 | reader->GetRightsId(result.data(), result.size()); | ||
| 168 | return result; | ||
| 593 | } | 169 | } |
| 594 | 170 | ||
| 595 | u32 NCA::GetSDKVersion() const { | 171 | u32 NCA::GetSDKVersion() const { |
| 596 | return header.sdk_version; | 172 | return reader->GetSdkAddonVersion(); |
| 597 | } | 173 | } |
| 598 | 174 | ||
| 599 | bool NCA::IsUpdate() const { | 175 | bool NCA::IsUpdate() const { |
| @@ -612,10 +188,6 @@ VirtualFile NCA::GetBaseFile() const { | |||
| 612 | return file; | 188 | return file; |
| 613 | } | 189 | } |
| 614 | 190 | ||
| 615 | u64 NCA::GetBaseIVFCOffset() const { | ||
| 616 | return ivfc_offset; | ||
| 617 | } | ||
| 618 | |||
| 619 | VirtualDir NCA::GetLogoPartition() const { | 191 | VirtualDir NCA::GetLogoPartition() const { |
| 620 | return logo; | 192 | return logo; |
| 621 | } | 193 | } |
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 20f524f80..af521d453 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h | |||
| @@ -21,7 +21,7 @@ enum class ResultStatus : u16; | |||
| 21 | 21 | ||
| 22 | namespace FileSys { | 22 | namespace FileSys { |
| 23 | 23 | ||
| 24 | union NCASectionHeader; | 24 | class NcaReader; |
| 25 | 25 | ||
| 26 | /// Describes the type of content within an NCA archive. | 26 | /// Describes the type of content within an NCA archive. |
| 27 | enum class NCAContentType : u8 { | 27 | enum class NCAContentType : u8 { |
| @@ -45,41 +45,7 @@ enum class NCAContentType : u8 { | |||
| 45 | PublicData = 5, | 45 | PublicData = 5, |
| 46 | }; | 46 | }; |
| 47 | 47 | ||
| 48 | enum class NCASectionCryptoType : u8 { | 48 | using RightsId = std::array<u8, 0x10>; |
| 49 | NONE = 1, | ||
| 50 | XTS = 2, | ||
| 51 | CTR = 3, | ||
| 52 | BKTR = 4, | ||
| 53 | }; | ||
| 54 | |||
| 55 | struct NCASectionTableEntry { | ||
| 56 | u32_le media_offset; | ||
| 57 | u32_le media_end_offset; | ||
| 58 | INSERT_PADDING_BYTES(0x8); | ||
| 59 | }; | ||
| 60 | static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size."); | ||
| 61 | |||
| 62 | struct NCAHeader { | ||
| 63 | std::array<u8, 0x100> rsa_signature_1; | ||
| 64 | std::array<u8, 0x100> rsa_signature_2; | ||
| 65 | u32_le magic; | ||
| 66 | u8 is_system; | ||
| 67 | NCAContentType content_type; | ||
| 68 | u8 crypto_type; | ||
| 69 | u8 key_index; | ||
| 70 | u64_le size; | ||
| 71 | u64_le title_id; | ||
| 72 | INSERT_PADDING_BYTES(0x4); | ||
| 73 | u32_le sdk_version; | ||
| 74 | u8 crypto_type_2; | ||
| 75 | INSERT_PADDING_BYTES(15); | ||
| 76 | std::array<u8, 0x10> rights_id; | ||
| 77 | std::array<NCASectionTableEntry, 0x4> section_tables; | ||
| 78 | std::array<std::array<u8, 0x20>, 0x4> hash_tables; | ||
| 79 | std::array<u8, 0x40> key_area; | ||
| 80 | INSERT_PADDING_BYTES(0xC0); | ||
| 81 | }; | ||
| 82 | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | ||
| 83 | 49 | ||
| 84 | inline bool IsDirectoryExeFS(const VirtualDir& pfs) { | 50 | inline bool IsDirectoryExeFS(const VirtualDir& pfs) { |
| 85 | // According to switchbrew, an exefs must only contain these two files: | 51 | // According to switchbrew, an exefs must only contain these two files: |
| @@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { | |||
| 97 | // After construction, use GetStatus to determine if the file is valid and ready to be used. | 63 | // After construction, use GetStatus to determine if the file is valid and ready to be used. |
| 98 | class NCA : public ReadOnlyVfsDirectory { | 64 | class NCA : public ReadOnlyVfsDirectory { |
| 99 | public: | 65 | public: |
| 100 | explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, | 66 | explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); |
| 101 | u64 bktr_base_ivfc_offset = 0); | ||
| 102 | ~NCA() override; | 67 | ~NCA() override; |
| 103 | 68 | ||
| 104 | Loader::ResultStatus GetStatus() const; | 69 | Loader::ResultStatus GetStatus() const; |
| @@ -110,7 +75,7 @@ public: | |||
| 110 | 75 | ||
| 111 | NCAContentType GetType() const; | 76 | NCAContentType GetType() const; |
| 112 | u64 GetTitleId() const; | 77 | u64 GetTitleId() const; |
| 113 | std::array<u8, 0x10> GetRightsId() const; | 78 | RightsId GetRightsId() const; |
| 114 | u32 GetSDKVersion() const; | 79 | u32 GetSDKVersion() const; |
| 115 | bool IsUpdate() const; | 80 | bool IsUpdate() const; |
| 116 | 81 | ||
| @@ -119,26 +84,9 @@ public: | |||
| 119 | 84 | ||
| 120 | VirtualFile GetBaseFile() const; | 85 | VirtualFile GetBaseFile() const; |
| 121 | 86 | ||
| 122 | // Returns the base ivfc offset used in BKTR patching. | ||
| 123 | u64 GetBaseIVFCOffset() const; | ||
| 124 | |||
| 125 | VirtualDir GetLogoPartition() const; | 87 | VirtualDir GetLogoPartition() const; |
| 126 | 88 | ||
| 127 | private: | 89 | private: |
| 128 | bool CheckSupportedNCA(const NCAHeader& header); | ||
| 129 | bool HandlePotentialHeaderDecryption(); | ||
| 130 | |||
| 131 | std::vector<NCASectionHeader> ReadSectionHeaders() const; | ||
| 132 | bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset); | ||
| 133 | bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, | ||
| 134 | u64 bktr_base_ivfc_offset); | ||
| 135 | bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry); | ||
| 136 | |||
| 137 | u8 GetCryptoRevision() const; | ||
| 138 | std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; | ||
| 139 | std::optional<Core::Crypto::Key128> GetTitlekey(); | ||
| 140 | VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset); | ||
| 141 | |||
| 142 | std::vector<VirtualDir> dirs; | 90 | std::vector<VirtualDir> dirs; |
| 143 | std::vector<VirtualFile> files; | 91 | std::vector<VirtualFile> files; |
| 144 | 92 | ||
| @@ -146,11 +94,6 @@ private: | |||
| 146 | VirtualDir exefs = nullptr; | 94 | VirtualDir exefs = nullptr; |
| 147 | VirtualDir logo = nullptr; | 95 | VirtualDir logo = nullptr; |
| 148 | VirtualFile file; | 96 | VirtualFile file; |
| 149 | VirtualFile bktr_base_romfs; | ||
| 150 | u64 ivfc_offset = 0; | ||
| 151 | |||
| 152 | NCAHeader header{}; | ||
| 153 | bool has_rights_id{}; | ||
| 154 | 97 | ||
| 155 | Loader::ResultStatus status{}; | 98 | Loader::ResultStatus status{}; |
| 156 | 99 | ||
| @@ -158,6 +101,7 @@ private: | |||
| 158 | bool is_update = false; | 101 | bool is_update = false; |
| 159 | 102 | ||
| 160 | Core::Crypto::KeyManager& keys; | 103 | Core::Crypto::KeyManager& keys; |
| 104 | std::shared_ptr<NcaReader> reader; | ||
| 161 | }; | 105 | }; |
| 162 | 106 | ||
| 163 | } // namespace FileSys | 107 | } // namespace FileSys |
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 7cee0c7df..2f5045a67 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h | |||
| @@ -17,4 +17,74 @@ constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001}; | |||
| 17 | constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; | 17 | constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; |
| 18 | constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; | 18 | constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; |
| 19 | 19 | ||
| 20 | constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50}; | ||
| 21 | constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001}; | ||
| 22 | constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002}; | ||
| 23 | constexpr Result ResultOutOfRange{ErrorModule::FS, 3005}; | ||
| 24 | constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294}; | ||
| 25 | constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341}; | ||
| 26 | constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363}; | ||
| 27 | constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399}; | ||
| 28 | constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412}; | ||
| 29 | constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422}; | ||
| 30 | constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423}; | ||
| 31 | constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012}; | ||
| 32 | constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021}; | ||
| 33 | constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022}; | ||
| 34 | constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023}; | ||
| 35 | constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024}; | ||
| 36 | constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032}; | ||
| 37 | constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033}; | ||
| 38 | constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034}; | ||
| 39 | constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035}; | ||
| 40 | constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036}; | ||
| 41 | constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037}; | ||
| 42 | constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038}; | ||
| 43 | constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039}; | ||
| 44 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084}; | ||
| 45 | constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085}; | ||
| 46 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086}; | ||
| 47 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087}; | ||
| 48 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088}; | ||
| 49 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089}; | ||
| 50 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090}; | ||
| 51 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091}; | ||
| 52 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091}; | ||
| 53 | constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509}; | ||
| 54 | constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510}; | ||
| 55 | constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511}; | ||
| 56 | constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517}; | ||
| 57 | constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520}; | ||
| 58 | constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521}; | ||
| 59 | constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522}; | ||
| 60 | constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523}; | ||
| 61 | constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524}; | ||
| 62 | constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525}; | ||
| 63 | constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526}; | ||
| 64 | constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528}; | ||
| 65 | constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529}; | ||
| 66 | constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530}; | ||
| 67 | constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532}; | ||
| 68 | constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533}; | ||
| 69 | constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534}; | ||
| 70 | constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535}; | ||
| 71 | constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541}; | ||
| 72 | constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542}; | ||
| 73 | constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543}; | ||
| 74 | constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547}; | ||
| 75 | constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548}; | ||
| 76 | constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549}; | ||
| 77 | constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324}; | ||
| 78 | constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325}; | ||
| 79 | constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326}; | ||
| 80 | constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327}; | ||
| 81 | constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001}; | ||
| 82 | constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061}; | ||
| 83 | constexpr Result ResultInvalidSize{ErrorModule::FS, 6062}; | ||
| 84 | constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063}; | ||
| 85 | constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325}; | ||
| 86 | constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387}; | ||
| 87 | constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388}; | ||
| 88 | constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705}; | ||
| 89 | |||
| 20 | } // namespace FileSys | 90 | } // namespace FileSys |
diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h new file mode 100644 index 000000000..416dd57b8 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_i_storage.h | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/overflow.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class IStorage : public VfsFile { | ||
| 13 | public: | ||
| 14 | virtual std::string GetName() const override { | ||
| 15 | return {}; | ||
| 16 | } | ||
| 17 | |||
| 18 | virtual VirtualDir GetContainingDirectory() const override { | ||
| 19 | return {}; | ||
| 20 | } | ||
| 21 | |||
| 22 | virtual bool IsWritable() const override { | ||
| 23 | return true; | ||
| 24 | } | ||
| 25 | |||
| 26 | virtual bool IsReadable() const override { | ||
| 27 | return true; | ||
| 28 | } | ||
| 29 | |||
| 30 | virtual bool Resize(size_t size) override { | ||
| 31 | return false; | ||
| 32 | } | ||
| 33 | |||
| 34 | virtual bool Rename(std::string_view name) override { | ||
| 35 | return false; | ||
| 36 | } | ||
| 37 | |||
| 38 | static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) { | ||
| 39 | R_UNLESS(offset >= 0, ResultInvalidOffset); | ||
| 40 | R_UNLESS(size >= 0, ResultInvalidSize); | ||
| 41 | R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange); | ||
| 42 | R_UNLESS(offset + size <= total_size, ResultOutOfRange); | ||
| 43 | R_SUCCEED(); | ||
| 44 | } | ||
| 45 | }; | ||
| 46 | |||
| 47 | class IReadOnlyStorage : public IStorage { | ||
| 48 | public: | ||
| 49 | virtual bool IsWritable() const override { | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | |||
| 53 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 54 | return 0; | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fs_types.h b/src/core/file_sys/fssystem/fs_types.h new file mode 100644 index 000000000..43aeaf447 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_types.h | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | struct Int64 { | ||
| 11 | u32 low; | ||
| 12 | u32 high; | ||
| 13 | |||
| 14 | constexpr void Set(s64 v) { | ||
| 15 | this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0); | ||
| 16 | this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32); | ||
| 17 | } | ||
| 18 | |||
| 19 | constexpr s64 Get() const { | ||
| 20 | return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low)); | ||
| 21 | } | ||
| 22 | |||
| 23 | constexpr Int64& operator=(s64 v) { | ||
| 24 | this->Set(v); | ||
| 25 | return *this; | ||
| 26 | } | ||
| 27 | |||
| 28 | constexpr operator s64() const { | ||
| 29 | return this->Get(); | ||
| 30 | } | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct HashSalt { | ||
| 34 | static constexpr size_t Size = 32; | ||
| 35 | |||
| 36 | std::array<u8, Size> value; | ||
| 37 | }; | ||
| 38 | static_assert(std::is_trivial_v<HashSalt>); | ||
| 39 | static_assert(sizeof(HashSalt) == HashSalt::Size); | ||
| 40 | |||
| 41 | constexpr inline size_t IntegrityMinLayerCount = 2; | ||
| 42 | constexpr inline size_t IntegrityMaxLayerCount = 7; | ||
| 43 | constexpr inline size_t IntegrityLayerCountSave = 5; | ||
| 44 | constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4; | ||
| 45 | |||
| 46 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp new file mode 100644 index 000000000..f25c95472 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp | |||
| @@ -0,0 +1,251 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 7 | #include "core/file_sys/vfs_offset.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { | ||
| 14 | public: | ||
| 15 | virtual void Decrypt( | ||
| 16 | u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, | ||
| 17 | const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final; | ||
| 18 | }; | ||
| 19 | |||
| 20 | } // namespace | ||
| 21 | |||
| 22 | Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) { | ||
| 23 | std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>(); | ||
| 24 | R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA); | ||
| 25 | *out = std::move(decryptor); | ||
| 26 | R_SUCCEED(); | ||
| 27 | } | ||
| 28 | |||
| 29 | Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, | ||
| 30 | VirtualFile data_storage, | ||
| 31 | VirtualFile table_storage) { | ||
| 32 | // Read and verify the bucket tree header. | ||
| 33 | BucketTree::Header header; | ||
| 34 | table_storage->ReadObject(std::addressof(header), 0); | ||
| 35 | R_TRY(header.Verify()); | ||
| 36 | |||
| 37 | // Determine extents. | ||
| 38 | const auto node_storage_size = QueryNodeStorageSize(header.entry_count); | ||
| 39 | const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); | ||
| 40 | const auto node_storage_offset = QueryHeaderStorageSize(); | ||
| 41 | const auto entry_storage_offset = node_storage_offset + node_storage_size; | ||
| 42 | |||
| 43 | // Create a software decryptor. | ||
| 44 | std::unique_ptr<IDecryptor> sw_decryptor; | ||
| 45 | R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor))); | ||
| 46 | |||
| 47 | // Initialize. | ||
| 48 | R_RETURN(this->Initialize( | ||
| 49 | key, key_size, secure_value, 0, data_storage, | ||
| 50 | std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), | ||
| 51 | std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), | ||
| 52 | header.entry_count, std::move(sw_decryptor))); | ||
| 53 | } | ||
| 54 | |||
| 55 | Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, | ||
| 56 | s64 counter_offset, VirtualFile data_storage, | ||
| 57 | VirtualFile node_storage, VirtualFile entry_storage, | ||
| 58 | s32 entry_count, | ||
| 59 | std::unique_ptr<IDecryptor>&& decryptor) { | ||
| 60 | // Validate preconditions. | ||
| 61 | ASSERT(key != nullptr); | ||
| 62 | ASSERT(key_size == KeySize); | ||
| 63 | ASSERT(counter_offset >= 0); | ||
| 64 | ASSERT(decryptor != nullptr); | ||
| 65 | |||
| 66 | // Initialize the bucket tree table. | ||
| 67 | if (entry_count > 0) { | ||
| 68 | R_TRY( | ||
| 69 | m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); | ||
| 70 | } else { | ||
| 71 | m_table.Initialize(NodeSize, 0); | ||
| 72 | } | ||
| 73 | |||
| 74 | // Set members. | ||
| 75 | m_data_storage = data_storage; | ||
| 76 | std::memcpy(m_key.data(), key, key_size); | ||
| 77 | m_secure_value = secure_value; | ||
| 78 | m_counter_offset = counter_offset; | ||
| 79 | m_decryptor = std::move(decryptor); | ||
| 80 | |||
| 81 | R_SUCCEED(); | ||
| 82 | } | ||
| 83 | |||
| 84 | void AesCtrCounterExtendedStorage::Finalize() { | ||
| 85 | if (this->IsInitialized()) { | ||
| 86 | m_table.Finalize(); | ||
| 87 | m_data_storage = VirtualFile(); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, | ||
| 92 | s32 entry_count, s64 offset, s64 size) { | ||
| 93 | // Validate pre-conditions. | ||
| 94 | ASSERT(offset >= 0); | ||
| 95 | ASSERT(size >= 0); | ||
| 96 | ASSERT(this->IsInitialized()); | ||
| 97 | |||
| 98 | // Clear the out count. | ||
| 99 | R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); | ||
| 100 | *out_entry_count = 0; | ||
| 101 | |||
| 102 | // Succeed if there's no range. | ||
| 103 | R_SUCCEED_IF(size == 0); | ||
| 104 | |||
| 105 | // If we have an output array, we need it to be non-null. | ||
| 106 | R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); | ||
| 107 | |||
| 108 | // Check that our range is valid. | ||
| 109 | BucketTree::Offsets table_offsets; | ||
| 110 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 111 | |||
| 112 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 113 | |||
| 114 | // Find the offset in our tree. | ||
| 115 | BucketTree::Visitor visitor; | ||
| 116 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 117 | { | ||
| 118 | const auto entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 119 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 120 | ResultInvalidAesCtrCounterExtendedEntryOffset); | ||
| 121 | } | ||
| 122 | |||
| 123 | // Prepare to loop over entries. | ||
| 124 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 125 | s32 count = 0; | ||
| 126 | |||
| 127 | auto cur_entry = *visitor.Get<Entry>(); | ||
| 128 | while (cur_entry.GetOffset() < end_offset) { | ||
| 129 | // Try to write the entry to the out list. | ||
| 130 | if (entry_count != 0) { | ||
| 131 | if (count >= entry_count) { | ||
| 132 | break; | ||
| 133 | } | ||
| 134 | std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); | ||
| 135 | } | ||
| 136 | |||
| 137 | count++; | ||
| 138 | |||
| 139 | // Advance. | ||
| 140 | if (visitor.CanMoveNext()) { | ||
| 141 | R_TRY(visitor.MoveNext()); | ||
| 142 | cur_entry = *visitor.Get<Entry>(); | ||
| 143 | } else { | ||
| 144 | break; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | // Write the output count. | ||
| 149 | *out_entry_count = count; | ||
| 150 | R_SUCCEED(); | ||
| 151 | } | ||
| 152 | |||
| 153 | size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 154 | // Validate preconditions. | ||
| 155 | ASSERT(this->IsInitialized()); | ||
| 156 | |||
| 157 | // Allow zero size. | ||
| 158 | if (size == 0) { | ||
| 159 | return size; | ||
| 160 | } | ||
| 161 | |||
| 162 | // Validate arguments. | ||
| 163 | ASSERT(buffer != nullptr); | ||
| 164 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 165 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 166 | |||
| 167 | BucketTree::Offsets table_offsets; | ||
| 168 | ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets)))); | ||
| 169 | |||
| 170 | ASSERT(table_offsets.IsInclude(offset, size)); | ||
| 171 | |||
| 172 | // Read the data. | ||
| 173 | m_data_storage->Read(buffer, size, offset); | ||
| 174 | |||
| 175 | // Find the offset in our tree. | ||
| 176 | BucketTree::Visitor visitor; | ||
| 177 | ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset))); | ||
| 178 | { | ||
| 179 | const auto entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 180 | ASSERT(Common::IsAligned(entry_offset, BlockSize)); | ||
| 181 | ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset)); | ||
| 182 | } | ||
| 183 | |||
| 184 | // Prepare to read in chunks. | ||
| 185 | u8* cur_data = static_cast<u8*>(buffer); | ||
| 186 | auto cur_offset = offset; | ||
| 187 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 188 | |||
| 189 | while (cur_offset < end_offset) { | ||
| 190 | // Get the current entry. | ||
| 191 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 192 | |||
| 193 | // Get and validate the entry's offset. | ||
| 194 | const auto cur_entry_offset = cur_entry.GetOffset(); | ||
| 195 | ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset); | ||
| 196 | |||
| 197 | // Get and validate the next entry offset. | ||
| 198 | s64 next_entry_offset; | ||
| 199 | if (visitor.CanMoveNext()) { | ||
| 200 | ASSERT(R_SUCCEEDED(visitor.MoveNext())); | ||
| 201 | next_entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 202 | ASSERT(table_offsets.IsInclude(next_entry_offset)); | ||
| 203 | } else { | ||
| 204 | next_entry_offset = table_offsets.end_offset; | ||
| 205 | } | ||
| 206 | ASSERT(Common::IsAligned(next_entry_offset, BlockSize)); | ||
| 207 | ASSERT(cur_offset < static_cast<size_t>(next_entry_offset)); | ||
| 208 | |||
| 209 | // Get the offset of the entry in the data we read. | ||
| 210 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 211 | const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset; | ||
| 212 | ASSERT(data_size > 0); | ||
| 213 | |||
| 214 | // Determine how much is left. | ||
| 215 | const auto remaining_size = end_offset - cur_offset; | ||
| 216 | const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size)); | ||
| 217 | ASSERT(cur_size <= size); | ||
| 218 | |||
| 219 | // If necessary, perform decryption. | ||
| 220 | if (cur_entry.encryption_value == Entry::Encryption::Encrypted) { | ||
| 221 | // Make the CTR for the data we're decrypting. | ||
| 222 | const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset; | ||
| 223 | NcaAesCtrUpperIv upper_iv = { | ||
| 224 | .part = {.generation = static_cast<u32>(cur_entry.generation), | ||
| 225 | .secure_value = m_secure_value}}; | ||
| 226 | |||
| 227 | std::array<u8, IvSize> iv; | ||
| 228 | AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset); | ||
| 229 | |||
| 230 | // Decrypt. | ||
| 231 | m_decryptor->Decrypt(cur_data, cur_size, m_key, iv); | ||
| 232 | } | ||
| 233 | |||
| 234 | // Advance. | ||
| 235 | cur_data += cur_size; | ||
| 236 | cur_offset += cur_size; | ||
| 237 | } | ||
| 238 | |||
| 239 | return size; | ||
| 240 | } | ||
| 241 | |||
| 242 | void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size, | ||
| 243 | const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, | ||
| 244 | const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) { | ||
| 245 | Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher( | ||
| 246 | key, Core::Crypto::Mode::CTR); | ||
| 247 | cipher.SetIV(iv); | ||
| 248 | cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt); | ||
| 249 | } | ||
| 250 | |||
| 251 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h new file mode 100644 index 000000000..d0e9ceed0 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | using namespace Common::Literals; | ||
| 15 | |||
| 16 | class AesCtrCounterExtendedStorage : public IReadOnlyStorage { | ||
| 17 | YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage); | ||
| 18 | YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage); | ||
| 19 | |||
| 20 | public: | ||
| 21 | static constexpr size_t BlockSize = 0x10; | ||
| 22 | static constexpr size_t KeySize = 0x10; | ||
| 23 | static constexpr size_t IvSize = 0x10; | ||
| 24 | static constexpr size_t NodeSize = 16_KiB; | ||
| 25 | |||
| 26 | class IDecryptor { | ||
| 27 | public: | ||
| 28 | virtual ~IDecryptor() {} | ||
| 29 | virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key, | ||
| 30 | const std::array<u8, IvSize>& iv) = 0; | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct Entry { | ||
| 34 | enum class Encryption : u8 { | ||
| 35 | Encrypted = 0, | ||
| 36 | NotEncrypted = 1, | ||
| 37 | }; | ||
| 38 | |||
| 39 | std::array<u8, sizeof(s64)> offset; | ||
| 40 | Encryption encryption_value; | ||
| 41 | std::array<u8, 3> reserved; | ||
| 42 | s32 generation; | ||
| 43 | |||
| 44 | void SetOffset(s64 value) { | ||
| 45 | std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64)); | ||
| 46 | } | ||
| 47 | |||
| 48 | s64 GetOffset() const { | ||
| 49 | s64 value; | ||
| 50 | std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64)); | ||
| 51 | return value; | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | static_assert(sizeof(Entry) == 0x10); | ||
| 55 | static_assert(alignof(Entry) == 4); | ||
| 56 | static_assert(std::is_trivial_v<Entry>); | ||
| 57 | |||
| 58 | public: | ||
| 59 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 60 | return BucketTree::QueryHeaderStorageSize(); | ||
| 61 | } | ||
| 62 | |||
| 63 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 64 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 65 | } | ||
| 66 | |||
| 67 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 68 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 69 | } | ||
| 70 | |||
| 71 | static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out); | ||
| 72 | |||
| 73 | public: | ||
| 74 | AesCtrCounterExtendedStorage() | ||
| 75 | : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {} | ||
| 76 | virtual ~AesCtrCounterExtendedStorage() { | ||
| 77 | this->Finalize(); | ||
| 78 | } | ||
| 79 | |||
| 80 | Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset, | ||
| 81 | VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, | ||
| 82 | s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor); | ||
| 83 | void Finalize(); | ||
| 84 | |||
| 85 | bool IsInitialized() const { | ||
| 86 | return m_table.IsInitialized(); | ||
| 87 | } | ||
| 88 | |||
| 89 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 90 | |||
| 91 | virtual size_t GetSize() const override { | ||
| 92 | BucketTree::Offsets offsets; | ||
| 93 | ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets)))); | ||
| 94 | |||
| 95 | return offsets.end_offset; | ||
| 96 | } | ||
| 97 | |||
| 98 | Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, | ||
| 99 | s64 size); | ||
| 100 | |||
| 101 | private: | ||
| 102 | Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage, | ||
| 103 | VirtualFile table_storage); | ||
| 104 | |||
| 105 | private: | ||
| 106 | mutable BucketTree m_table; | ||
| 107 | VirtualFile m_data_storage; | ||
| 108 | std::array<u8, KeySize> m_key; | ||
| 109 | u32 m_secure_value; | ||
| 110 | s64 m_counter_offset; | ||
| 111 | std::unique_ptr<IDecryptor> m_decryptor; | ||
| 112 | }; | ||
| 113 | |||
| 114 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp new file mode 100644 index 000000000..b65aca18d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) { | ||
| 13 | ASSERT(dst != nullptr); | ||
| 14 | ASSERT(dst_size == IvSize); | ||
| 15 | ASSERT(offset >= 0); | ||
| 16 | |||
| 17 | const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); | ||
| 18 | |||
| 19 | *reinterpret_cast<u64_be*>(out_addr + 0) = upper; | ||
| 20 | *reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize); | ||
| 21 | } | ||
| 22 | |||
| 23 | AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, | ||
| 24 | size_t iv_size) | ||
| 25 | : m_base_storage(std::move(base)) { | ||
| 26 | ASSERT(m_base_storage != nullptr); | ||
| 27 | ASSERT(key != nullptr); | ||
| 28 | ASSERT(iv != nullptr); | ||
| 29 | ASSERT(key_size == KeySize); | ||
| 30 | ASSERT(iv_size == IvSize); | ||
| 31 | |||
| 32 | std::memcpy(m_key.data(), key, KeySize); | ||
| 33 | std::memcpy(m_iv.data(), iv, IvSize); | ||
| 34 | |||
| 35 | m_cipher.emplace(m_key, Core::Crypto::Mode::CTR); | ||
| 36 | } | ||
| 37 | |||
| 38 | size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 39 | // Allow zero-size reads. | ||
| 40 | if (size == 0) { | ||
| 41 | return size; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Ensure buffer is valid. | ||
| 45 | ASSERT(buffer != nullptr); | ||
| 46 | |||
| 47 | // We can only read at block aligned offsets. | ||
| 48 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 49 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 50 | |||
| 51 | // Read the data. | ||
| 52 | m_base_storage->Read(buffer, size, offset); | ||
| 53 | |||
| 54 | // Setup the counter. | ||
| 55 | std::array<u8, IvSize> ctr; | ||
| 56 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 57 | AddCounter(ctr.data(), IvSize, offset / BlockSize); | ||
| 58 | |||
| 59 | // Decrypt. | ||
| 60 | m_cipher->SetIV(ctr); | ||
| 61 | m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt); | ||
| 62 | |||
| 63 | return size; | ||
| 64 | } | ||
| 65 | |||
| 66 | size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) { | ||
| 67 | // Allow zero-size writes. | ||
| 68 | if (size == 0) { | ||
| 69 | return size; | ||
| 70 | } | ||
| 71 | |||
| 72 | // Ensure buffer is valid. | ||
| 73 | ASSERT(buffer != nullptr); | ||
| 74 | |||
| 75 | // We can only write at block aligned offsets. | ||
| 76 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 77 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 78 | |||
| 79 | // Get a pooled buffer. | ||
| 80 | PooledBuffer pooled_buffer; | ||
| 81 | const bool use_work_buffer = true; | ||
| 82 | if (use_work_buffer) { | ||
| 83 | pooled_buffer.Allocate(size, BlockSize); | ||
| 84 | } | ||
| 85 | |||
| 86 | // Setup the counter. | ||
| 87 | std::array<u8, IvSize> ctr; | ||
| 88 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 89 | AddCounter(ctr.data(), IvSize, offset / BlockSize); | ||
| 90 | |||
| 91 | // Loop until all data is written. | ||
| 92 | size_t remaining = size; | ||
| 93 | s64 cur_offset = 0; | ||
| 94 | while (remaining > 0) { | ||
| 95 | // Determine data we're writing and where. | ||
| 96 | const size_t write_size = | ||
| 97 | use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; | ||
| 98 | |||
| 99 | void* write_buf; | ||
| 100 | if (use_work_buffer) { | ||
| 101 | write_buf = pooled_buffer.GetBuffer(); | ||
| 102 | } else { | ||
| 103 | write_buf = const_cast<u8*>(buffer); | ||
| 104 | } | ||
| 105 | |||
| 106 | // Encrypt the data. | ||
| 107 | m_cipher->SetIV(ctr); | ||
| 108 | m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf), | ||
| 109 | Core::Crypto::Op::Encrypt); | ||
| 110 | |||
| 111 | // Write the encrypted data. | ||
| 112 | m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset); | ||
| 113 | |||
| 114 | // Advance. | ||
| 115 | cur_offset += write_size; | ||
| 116 | remaining -= write_size; | ||
| 117 | if (remaining > 0) { | ||
| 118 | AddCounter(ctr.data(), IvSize, write_size / BlockSize); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | return size; | ||
| 123 | } | ||
| 124 | |||
| 125 | size_t AesCtrStorage::GetSize() const { | ||
| 126 | return m_base_storage->GetSize(); | ||
| 127 | } | ||
| 128 | |||
| 129 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h new file mode 100644 index 000000000..339e49697 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/key_manager.h" | ||
| 10 | #include "core/file_sys/errors.h" | ||
| 11 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 12 | #include "core/file_sys/vfs.h" | ||
| 13 | |||
| 14 | namespace FileSys { | ||
| 15 | |||
| 16 | class AesCtrStorage : public IStorage { | ||
| 17 | YUZU_NON_COPYABLE(AesCtrStorage); | ||
| 18 | YUZU_NON_MOVEABLE(AesCtrStorage); | ||
| 19 | |||
| 20 | public: | ||
| 21 | static constexpr size_t BlockSize = 0x10; | ||
| 22 | static constexpr size_t KeySize = 0x10; | ||
| 23 | static constexpr size_t IvSize = 0x10; | ||
| 24 | |||
| 25 | public: | ||
| 26 | static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset); | ||
| 27 | |||
| 28 | public: | ||
| 29 | AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, | ||
| 30 | size_t iv_size); | ||
| 31 | |||
| 32 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 33 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override; | ||
| 34 | virtual size_t GetSize() const override; | ||
| 35 | |||
| 36 | private: | ||
| 37 | VirtualFile m_base_storage; | ||
| 38 | std::array<u8, KeySize> m_key; | ||
| 39 | std::array<u8, IvSize> m_iv; | ||
| 40 | mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher; | ||
| 41 | }; | ||
| 42 | |||
| 43 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp new file mode 100644 index 000000000..022424229 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) { | ||
| 14 | ASSERT(dst != nullptr); | ||
| 15 | ASSERT(dst_size == IvSize); | ||
| 16 | ASSERT(offset >= 0); | ||
| 17 | |||
| 18 | const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); | ||
| 19 | |||
| 20 | *reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size; | ||
| 21 | } | ||
| 22 | |||
| 23 | AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, | ||
| 24 | const void* iv, size_t iv_size, size_t block_size) | ||
| 25 | : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() { | ||
| 26 | ASSERT(m_base_storage != nullptr); | ||
| 27 | ASSERT(key1 != nullptr); | ||
| 28 | ASSERT(key2 != nullptr); | ||
| 29 | ASSERT(iv != nullptr); | ||
| 30 | ASSERT(key_size == KeySize); | ||
| 31 | ASSERT(iv_size == IvSize); | ||
| 32 | ASSERT(Common::IsAligned(m_block_size, AesBlockSize)); | ||
| 33 | |||
| 34 | std::memcpy(m_key.data() + 0, key1, KeySize); | ||
| 35 | std::memcpy(m_key.data() + 0x10, key2, KeySize); | ||
| 36 | std::memcpy(m_iv.data(), iv, IvSize); | ||
| 37 | |||
| 38 | m_cipher.emplace(m_key, Core::Crypto::Mode::XTS); | ||
| 39 | } | ||
| 40 | |||
| 41 | size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 42 | // Allow zero-size reads. | ||
| 43 | if (size == 0) { | ||
| 44 | return size; | ||
| 45 | } | ||
| 46 | |||
| 47 | // Ensure buffer is valid. | ||
| 48 | ASSERT(buffer != nullptr); | ||
| 49 | |||
| 50 | // We can only read at block aligned offsets. | ||
| 51 | ASSERT(Common::IsAligned(offset, AesBlockSize)); | ||
| 52 | ASSERT(Common::IsAligned(size, AesBlockSize)); | ||
| 53 | |||
| 54 | // Read the data. | ||
| 55 | m_base_storage->Read(buffer, size, offset); | ||
| 56 | |||
| 57 | // Setup the counter. | ||
| 58 | std::array<u8, IvSize> ctr; | ||
| 59 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 60 | AddCounter(ctr.data(), IvSize, offset / m_block_size); | ||
| 61 | |||
| 62 | // Handle any unaligned data before the start. | ||
| 63 | size_t processed_size = 0; | ||
| 64 | if ((offset % m_block_size) != 0) { | ||
| 65 | // Determine the size of the pre-data read. | ||
| 66 | const size_t skip_size = | ||
| 67 | static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size)); | ||
| 68 | const size_t data_size = std::min(size, m_block_size - skip_size); | ||
| 69 | |||
| 70 | // Decrypt into a pooled buffer. | ||
| 71 | { | ||
| 72 | PooledBuffer tmp_buf(m_block_size, m_block_size); | ||
| 73 | ASSERT(tmp_buf.GetSize() >= m_block_size); | ||
| 74 | |||
| 75 | std::memset(tmp_buf.GetBuffer(), 0, skip_size); | ||
| 76 | std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); | ||
| 77 | |||
| 78 | m_cipher->SetIV(ctr); | ||
| 79 | m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(), | ||
| 80 | Core::Crypto::Op::Decrypt); | ||
| 81 | |||
| 82 | std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size); | ||
| 83 | } | ||
| 84 | |||
| 85 | AddCounter(ctr.data(), IvSize, 1); | ||
| 86 | processed_size += data_size; | ||
| 87 | ASSERT(processed_size == std::min(size, m_block_size - skip_size)); | ||
| 88 | } | ||
| 89 | |||
| 90 | // Decrypt aligned chunks. | ||
| 91 | char* cur = reinterpret_cast<char*>(buffer) + processed_size; | ||
| 92 | size_t remaining = size - processed_size; | ||
| 93 | while (remaining > 0) { | ||
| 94 | const size_t cur_size = std::min(m_block_size, remaining); | ||
| 95 | |||
| 96 | m_cipher->SetIV(ctr); | ||
| 97 | m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt); | ||
| 98 | |||
| 99 | remaining -= cur_size; | ||
| 100 | cur += cur_size; | ||
| 101 | |||
| 102 | AddCounter(ctr.data(), IvSize, 1); | ||
| 103 | } | ||
| 104 | |||
| 105 | return size; | ||
| 106 | } | ||
| 107 | |||
| 108 | size_t AesXtsStorage::GetSize() const { | ||
| 109 | return m_base_storage->GetSize(); | ||
| 110 | } | ||
| 111 | |||
| 112 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h new file mode 100644 index 000000000..f342efb57 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/key_manager.h" | ||
| 10 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class AesXtsStorage : public IReadOnlyStorage { | ||
| 15 | YUZU_NON_COPYABLE(AesXtsStorage); | ||
| 16 | YUZU_NON_MOVEABLE(AesXtsStorage); | ||
| 17 | |||
| 18 | public: | ||
| 19 | static constexpr size_t AesBlockSize = 0x10; | ||
| 20 | static constexpr size_t KeySize = 0x20; | ||
| 21 | static constexpr size_t IvSize = 0x10; | ||
| 22 | |||
| 23 | public: | ||
| 24 | static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size); | ||
| 25 | |||
| 26 | public: | ||
| 27 | AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, | ||
| 28 | const void* iv, size_t iv_size, size_t block_size); | ||
| 29 | |||
| 30 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 31 | virtual size_t GetSize() const override; | ||
| 32 | |||
| 33 | private: | ||
| 34 | VirtualFile m_base_storage; | ||
| 35 | std::array<u8, KeySize> m_key; | ||
| 36 | std::array<u8, IvSize> m_iv; | ||
| 37 | const size_t m_block_size; | ||
| 38 | std::mutex m_mutex; | ||
| 39 | mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher; | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h new file mode 100644 index 000000000..f96691d03 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h | |||
| @@ -0,0 +1,146 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | template <size_t DataAlign_, size_t BufferAlign_> | ||
| 15 | class AlignmentMatchingStorage : public IStorage { | ||
| 16 | YUZU_NON_COPYABLE(AlignmentMatchingStorage); | ||
| 17 | YUZU_NON_MOVEABLE(AlignmentMatchingStorage); | ||
| 18 | |||
| 19 | public: | ||
| 20 | static constexpr size_t DataAlign = DataAlign_; | ||
| 21 | static constexpr size_t BufferAlign = BufferAlign_; | ||
| 22 | |||
| 23 | static constexpr size_t DataAlignMax = 0x200; | ||
| 24 | static_assert(DataAlign <= DataAlignMax); | ||
| 25 | static_assert(Common::IsPowerOfTwo(DataAlign)); | ||
| 26 | static_assert(Common::IsPowerOfTwo(BufferAlign)); | ||
| 27 | |||
| 28 | private: | ||
| 29 | VirtualFile m_base_storage; | ||
| 30 | s64 m_base_storage_size; | ||
| 31 | |||
| 32 | public: | ||
| 33 | explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {} | ||
| 34 | |||
| 35 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 36 | // Allocate a work buffer on stack. | ||
| 37 | alignas(DataAlignMax) std::array<char, DataAlign> work_buf; | ||
| 38 | |||
| 39 | // Succeed if zero size. | ||
| 40 | if (size == 0) { | ||
| 41 | return size; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Validate arguments. | ||
| 45 | ASSERT(buffer != nullptr); | ||
| 46 | |||
| 47 | s64 bs_size = this->GetSize(); | ||
| 48 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 49 | |||
| 50 | return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(), | ||
| 51 | DataAlign, BufferAlign, offset, buffer, size); | ||
| 52 | } | ||
| 53 | |||
| 54 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 55 | // Allocate a work buffer on stack. | ||
| 56 | alignas(DataAlignMax) std::array<char, DataAlign> work_buf; | ||
| 57 | |||
| 58 | // Succeed if zero size. | ||
| 59 | if (size == 0) { | ||
| 60 | return size; | ||
| 61 | } | ||
| 62 | |||
| 63 | // Validate arguments. | ||
| 64 | ASSERT(buffer != nullptr); | ||
| 65 | |||
| 66 | s64 bs_size = this->GetSize(); | ||
| 67 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 68 | |||
| 69 | return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(), | ||
| 70 | DataAlign, BufferAlign, offset, buffer, size); | ||
| 71 | } | ||
| 72 | |||
| 73 | virtual size_t GetSize() const override { | ||
| 74 | return m_base_storage->GetSize(); | ||
| 75 | } | ||
| 76 | }; | ||
| 77 | |||
| 78 | template <size_t BufferAlign_> | ||
| 79 | class AlignmentMatchingStoragePooledBuffer : public IStorage { | ||
| 80 | YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer); | ||
| 81 | YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer); | ||
| 82 | |||
| 83 | public: | ||
| 84 | static constexpr size_t BufferAlign = BufferAlign_; | ||
| 85 | |||
| 86 | static_assert(Common::IsPowerOfTwo(BufferAlign)); | ||
| 87 | |||
| 88 | private: | ||
| 89 | VirtualFile m_base_storage; | ||
| 90 | s64 m_base_storage_size; | ||
| 91 | size_t m_data_align; | ||
| 92 | |||
| 93 | public: | ||
| 94 | explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da) | ||
| 95 | : m_base_storage(std::move(bs)), m_data_align(da) { | ||
| 96 | ASSERT(Common::IsPowerOfTwo(da)); | ||
| 97 | } | ||
| 98 | |||
| 99 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 100 | // Succeed if zero size. | ||
| 101 | if (size == 0) { | ||
| 102 | return size; | ||
| 103 | } | ||
| 104 | |||
| 105 | // Validate arguments. | ||
| 106 | ASSERT(buffer != nullptr); | ||
| 107 | |||
| 108 | s64 bs_size = this->GetSize(); | ||
| 109 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 110 | |||
| 111 | // Allocate a pooled buffer. | ||
| 112 | PooledBuffer pooled_buffer; | ||
| 113 | pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); | ||
| 114 | |||
| 115 | return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(), | ||
| 116 | pooled_buffer.GetSize(), m_data_align, | ||
| 117 | BufferAlign, offset, buffer, size); | ||
| 118 | } | ||
| 119 | |||
| 120 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 121 | // Succeed if zero size. | ||
| 122 | if (size == 0) { | ||
| 123 | return size; | ||
| 124 | } | ||
| 125 | |||
| 126 | // Validate arguments. | ||
| 127 | ASSERT(buffer != nullptr); | ||
| 128 | |||
| 129 | s64 bs_size = this->GetSize(); | ||
| 130 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 131 | |||
| 132 | // Allocate a pooled buffer. | ||
| 133 | PooledBuffer pooled_buffer; | ||
| 134 | pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); | ||
| 135 | |||
| 136 | return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(), | ||
| 137 | pooled_buffer.GetSize(), m_data_align, | ||
| 138 | BufferAlign, offset, buffer, size); | ||
| 139 | } | ||
| 140 | |||
| 141 | virtual size_t GetSize() const override { | ||
| 142 | return m_base_storage->GetSize(); | ||
| 143 | } | ||
| 144 | }; | ||
| 145 | |||
| 146 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp new file mode 100644 index 000000000..641c888ae --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp | |||
| @@ -0,0 +1,204 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | template <typename T> | ||
| 12 | constexpr size_t GetRoundDownDifference(T x, size_t align) { | ||
| 13 | return static_cast<size_t>(x - Common::AlignDown(x, align)); | ||
| 14 | } | ||
| 15 | |||
| 16 | template <typename T> | ||
| 17 | constexpr size_t GetRoundUpDifference(T x, size_t align) { | ||
| 18 | return static_cast<size_t>(Common::AlignUp(x, align) - x); | ||
| 19 | } | ||
| 20 | |||
| 21 | template <typename T> | ||
| 22 | size_t GetRoundUpDifference(T* x, size_t align) { | ||
| 23 | return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align); | ||
| 24 | } | ||
| 25 | |||
| 26 | } // namespace | ||
| 27 | |||
| 28 | size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf, | ||
| 29 | size_t work_buf_size, size_t data_alignment, | ||
| 30 | size_t buffer_alignment, s64 offset, u8* buffer, | ||
| 31 | size_t size) { | ||
| 32 | // Check preconditions. | ||
| 33 | ASSERT(work_buf_size >= data_alignment); | ||
| 34 | |||
| 35 | // Succeed if zero size. | ||
| 36 | if (size == 0) { | ||
| 37 | return size; | ||
| 38 | } | ||
| 39 | |||
| 40 | // Validate arguments. | ||
| 41 | ASSERT(buffer != nullptr); | ||
| 42 | |||
| 43 | // Determine extents. | ||
| 44 | u8* aligned_core_buffer; | ||
| 45 | s64 core_offset; | ||
| 46 | size_t core_size; | ||
| 47 | size_t buffer_gap; | ||
| 48 | size_t offset_gap; | ||
| 49 | s64 covered_offset; | ||
| 50 | |||
| 51 | const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); | ||
| 52 | if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, | ||
| 53 | buffer_alignment)) { | ||
| 54 | aligned_core_buffer = buffer + offset_round_up_difference; | ||
| 55 | |||
| 56 | core_offset = Common::AlignUp(offset, data_alignment); | ||
| 57 | core_size = (size < offset_round_up_difference) | ||
| 58 | ? 0 | ||
| 59 | : Common::AlignDown(size - offset_round_up_difference, data_alignment); | ||
| 60 | buffer_gap = 0; | ||
| 61 | offset_gap = 0; | ||
| 62 | |||
| 63 | covered_offset = core_size > 0 ? core_offset : offset; | ||
| 64 | } else { | ||
| 65 | const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment); | ||
| 66 | |||
| 67 | aligned_core_buffer = buffer + buffer_round_up_difference; | ||
| 68 | |||
| 69 | core_offset = Common::AlignDown(offset, data_alignment); | ||
| 70 | core_size = (size < buffer_round_up_difference) | ||
| 71 | ? 0 | ||
| 72 | : Common::AlignDown(size - buffer_round_up_difference, data_alignment); | ||
| 73 | buffer_gap = buffer_round_up_difference; | ||
| 74 | offset_gap = GetRoundDownDifference(offset, data_alignment); | ||
| 75 | |||
| 76 | covered_offset = offset; | ||
| 77 | } | ||
| 78 | |||
| 79 | // Read the core portion. | ||
| 80 | if (core_size > 0) { | ||
| 81 | base_storage->Read(aligned_core_buffer, core_size, core_offset); | ||
| 82 | |||
| 83 | if (offset_gap != 0 || buffer_gap != 0) { | ||
| 84 | std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap, | ||
| 85 | core_size - offset_gap); | ||
| 86 | core_size -= offset_gap; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | // Handle the head portion. | ||
| 91 | if (offset < covered_offset) { | ||
| 92 | const s64 head_offset = Common::AlignDown(offset, data_alignment); | ||
| 93 | const size_t head_size = static_cast<size_t>(covered_offset - offset); | ||
| 94 | |||
| 95 | ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size); | ||
| 96 | |||
| 97 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 98 | std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size); | ||
| 99 | } | ||
| 100 | |||
| 101 | // Handle the tail portion. | ||
| 102 | s64 tail_offset = covered_offset + core_size; | ||
| 103 | size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); | ||
| 104 | while (remaining_tail_size > 0) { | ||
| 105 | const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); | ||
| 106 | const auto cur_size = | ||
| 107 | std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), | ||
| 108 | remaining_tail_size); | ||
| 109 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 110 | |||
| 111 | ASSERT((tail_offset - offset) + cur_size <= size); | ||
| 112 | ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment); | ||
| 113 | std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset), | ||
| 114 | work_buf + (tail_offset - aligned_tail_offset), cur_size); | ||
| 115 | |||
| 116 | remaining_tail_size -= cur_size; | ||
| 117 | tail_offset += cur_size; | ||
| 118 | } | ||
| 119 | |||
| 120 | return size; | ||
| 121 | } | ||
| 122 | |||
| 123 | size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf, | ||
| 124 | size_t work_buf_size, size_t data_alignment, | ||
| 125 | size_t buffer_alignment, s64 offset, const u8* buffer, | ||
| 126 | size_t size) { | ||
| 127 | // Check preconditions. | ||
| 128 | ASSERT(work_buf_size >= data_alignment); | ||
| 129 | |||
| 130 | // Succeed if zero size. | ||
| 131 | if (size == 0) { | ||
| 132 | return size; | ||
| 133 | } | ||
| 134 | |||
| 135 | // Validate arguments. | ||
| 136 | ASSERT(buffer != nullptr); | ||
| 137 | |||
| 138 | // Determine extents. | ||
| 139 | const u8* aligned_core_buffer; | ||
| 140 | s64 core_offset; | ||
| 141 | size_t core_size; | ||
| 142 | s64 covered_offset; | ||
| 143 | |||
| 144 | const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); | ||
| 145 | if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, | ||
| 146 | buffer_alignment)) { | ||
| 147 | aligned_core_buffer = buffer + offset_round_up_difference; | ||
| 148 | |||
| 149 | core_offset = Common::AlignUp(offset, data_alignment); | ||
| 150 | core_size = (size < offset_round_up_difference) | ||
| 151 | ? 0 | ||
| 152 | : Common::AlignDown(size - offset_round_up_difference, data_alignment); | ||
| 153 | |||
| 154 | covered_offset = core_size > 0 ? core_offset : offset; | ||
| 155 | } else { | ||
| 156 | aligned_core_buffer = nullptr; | ||
| 157 | |||
| 158 | core_offset = Common::AlignDown(offset, data_alignment); | ||
| 159 | core_size = 0; | ||
| 160 | |||
| 161 | covered_offset = offset; | ||
| 162 | } | ||
| 163 | |||
| 164 | // Write the core portion. | ||
| 165 | if (core_size > 0) { | ||
| 166 | base_storage->Write(aligned_core_buffer, core_size, core_offset); | ||
| 167 | } | ||
| 168 | |||
| 169 | // Handle the head portion. | ||
| 170 | if (offset < covered_offset) { | ||
| 171 | const s64 head_offset = Common::AlignDown(offset, data_alignment); | ||
| 172 | const size_t head_size = static_cast<size_t>(covered_offset - offset); | ||
| 173 | |||
| 174 | ASSERT((offset - head_offset) + head_size <= data_alignment); | ||
| 175 | |||
| 176 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 177 | std::memcpy(work_buf + (offset - head_offset), buffer, head_size); | ||
| 178 | base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 179 | } | ||
| 180 | |||
| 181 | // Handle the tail portion. | ||
| 182 | s64 tail_offset = covered_offset + core_size; | ||
| 183 | size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); | ||
| 184 | while (remaining_tail_size > 0) { | ||
| 185 | ASSERT(static_cast<size_t>(tail_offset - offset) < size); | ||
| 186 | |||
| 187 | const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); | ||
| 188 | const auto cur_size = | ||
| 189 | std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), | ||
| 190 | remaining_tail_size); | ||
| 191 | |||
| 192 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 193 | std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment), | ||
| 194 | buffer + (tail_offset - offset), cur_size); | ||
| 195 | base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 196 | |||
| 197 | remaining_tail_size -= cur_size; | ||
| 198 | tail_offset += cur_size; | ||
| 199 | } | ||
| 200 | |||
| 201 | return size; | ||
| 202 | } | ||
| 203 | |||
| 204 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h new file mode 100644 index 000000000..4a05b0e88 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | class AlignmentMatchingStorageImpl { | ||
| 12 | public: | ||
| 13 | static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size, | ||
| 14 | size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer, | ||
| 15 | size_t size); | ||
| 16 | static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size, | ||
| 17 | size_t data_alignment, size_t buffer_alignment, s64 offset, | ||
| 18 | const u8* buffer, size_t size); | ||
| 19 | }; | ||
| 20 | |||
| 21 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp new file mode 100644 index 000000000..af8541009 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp | |||
| @@ -0,0 +1,598 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/errors.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | using Node = impl::BucketTreeNode<const s64*>; | ||
| 14 | static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader)); | ||
| 15 | static_assert(std::is_trivial_v<Node>); | ||
| 16 | |||
| 17 | constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader); | ||
| 18 | |||
| 19 | class StorageNode { | ||
| 20 | private: | ||
| 21 | class Offset { | ||
| 22 | public: | ||
| 23 | using difference_type = s64; | ||
| 24 | |||
| 25 | private: | ||
| 26 | s64 m_offset; | ||
| 27 | s32 m_stride; | ||
| 28 | |||
| 29 | public: | ||
| 30 | constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {} | ||
| 31 | |||
| 32 | constexpr Offset& operator++() { | ||
| 33 | m_offset += m_stride; | ||
| 34 | return *this; | ||
| 35 | } | ||
| 36 | constexpr Offset operator++(int) { | ||
| 37 | Offset ret(*this); | ||
| 38 | m_offset += m_stride; | ||
| 39 | return ret; | ||
| 40 | } | ||
| 41 | |||
| 42 | constexpr Offset& operator--() { | ||
| 43 | m_offset -= m_stride; | ||
| 44 | return *this; | ||
| 45 | } | ||
| 46 | constexpr Offset operator--(int) { | ||
| 47 | Offset ret(*this); | ||
| 48 | m_offset -= m_stride; | ||
| 49 | return ret; | ||
| 50 | } | ||
| 51 | |||
| 52 | constexpr difference_type operator-(const Offset& rhs) const { | ||
| 53 | return (m_offset - rhs.m_offset) / m_stride; | ||
| 54 | } | ||
| 55 | |||
| 56 | constexpr Offset operator+(difference_type ofs) const { | ||
| 57 | return Offset(m_offset + ofs * m_stride, m_stride); | ||
| 58 | } | ||
| 59 | constexpr Offset operator-(difference_type ofs) const { | ||
| 60 | return Offset(m_offset - ofs * m_stride, m_stride); | ||
| 61 | } | ||
| 62 | |||
| 63 | constexpr Offset& operator+=(difference_type ofs) { | ||
| 64 | m_offset += ofs * m_stride; | ||
| 65 | return *this; | ||
| 66 | } | ||
| 67 | constexpr Offset& operator-=(difference_type ofs) { | ||
| 68 | m_offset -= ofs * m_stride; | ||
| 69 | return *this; | ||
| 70 | } | ||
| 71 | |||
| 72 | constexpr bool operator==(const Offset& rhs) const { | ||
| 73 | return m_offset == rhs.m_offset; | ||
| 74 | } | ||
| 75 | constexpr bool operator!=(const Offset& rhs) const { | ||
| 76 | return m_offset != rhs.m_offset; | ||
| 77 | } | ||
| 78 | |||
| 79 | constexpr s64 Get() const { | ||
| 80 | return m_offset; | ||
| 81 | } | ||
| 82 | }; | ||
| 83 | |||
| 84 | private: | ||
| 85 | const Offset m_start; | ||
| 86 | const s32 m_count; | ||
| 87 | s32 m_index; | ||
| 88 | |||
| 89 | public: | ||
| 90 | StorageNode(size_t size, s32 count) | ||
| 91 | : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {} | ||
| 92 | StorageNode(s64 ofs, size_t size, s32 count) | ||
| 93 | : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {} | ||
| 94 | |||
| 95 | s32 GetIndex() const { | ||
| 96 | return m_index; | ||
| 97 | } | ||
| 98 | |||
| 99 | void Find(const char* buffer, s64 virtual_address) { | ||
| 100 | s32 end = m_count; | ||
| 101 | auto pos = m_start; | ||
| 102 | |||
| 103 | while (end > 0) { | ||
| 104 | auto half = end / 2; | ||
| 105 | auto mid = pos + half; | ||
| 106 | |||
| 107 | s64 offset = 0; | ||
| 108 | std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64)); | ||
| 109 | |||
| 110 | if (offset <= virtual_address) { | ||
| 111 | pos = mid + 1; | ||
| 112 | end -= half + 1; | ||
| 113 | } else { | ||
| 114 | end = half; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | m_index = static_cast<s32>(pos - m_start) - 1; | ||
| 119 | } | ||
| 120 | |||
| 121 | Result Find(VirtualFile storage, s64 virtual_address) { | ||
| 122 | s32 end = m_count; | ||
| 123 | auto pos = m_start; | ||
| 124 | |||
| 125 | while (end > 0) { | ||
| 126 | auto half = end / 2; | ||
| 127 | auto mid = pos + half; | ||
| 128 | |||
| 129 | s64 offset = 0; | ||
| 130 | storage->ReadObject(std::addressof(offset), mid.Get()); | ||
| 131 | |||
| 132 | if (offset <= virtual_address) { | ||
| 133 | pos = mid + 1; | ||
| 134 | end -= half + 1; | ||
| 135 | } else { | ||
| 136 | end = half; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | m_index = static_cast<s32>(pos - m_start) - 1; | ||
| 141 | R_SUCCEED(); | ||
| 142 | } | ||
| 143 | }; | ||
| 144 | |||
| 145 | } // namespace | ||
| 146 | |||
| 147 | void BucketTree::Header::Format(s32 entry_count_) { | ||
| 148 | ASSERT(entry_count_ >= 0); | ||
| 149 | |||
| 150 | this->magic = Magic; | ||
| 151 | this->version = Version; | ||
| 152 | this->entry_count = entry_count_; | ||
| 153 | this->reserved = 0; | ||
| 154 | } | ||
| 155 | |||
| 156 | Result BucketTree::Header::Verify() const { | ||
| 157 | R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature); | ||
| 158 | R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount); | ||
| 159 | R_UNLESS(this->version <= Version, ResultUnsupportedVersion); | ||
| 160 | R_SUCCEED(); | ||
| 161 | } | ||
| 162 | |||
| 163 | Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const { | ||
| 164 | R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex); | ||
| 165 | R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize); | ||
| 166 | |||
| 167 | const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size; | ||
| 168 | R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count, | ||
| 169 | ResultInvalidBucketTreeNodeEntryCount); | ||
| 170 | R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset); | ||
| 171 | |||
| 172 | R_SUCCEED(); | ||
| 173 | } | ||
| 174 | |||
| 175 | Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, | ||
| 176 | size_t entry_size, s32 entry_count) { | ||
| 177 | // Validate preconditions. | ||
| 178 | ASSERT(entry_size >= sizeof(s64)); | ||
| 179 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 180 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 181 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 182 | ASSERT(!this->IsInitialized()); | ||
| 183 | |||
| 184 | // Ensure valid entry count. | ||
| 185 | R_UNLESS(entry_count > 0, ResultInvalidArgument); | ||
| 186 | |||
| 187 | // Allocate node. | ||
| 188 | R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed); | ||
| 189 | ON_RESULT_FAILURE { | ||
| 190 | m_node_l1.Free(node_size); | ||
| 191 | }; | ||
| 192 | |||
| 193 | // Read node. | ||
| 194 | node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size); | ||
| 195 | |||
| 196 | // Verify node. | ||
| 197 | R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64))); | ||
| 198 | |||
| 199 | // Validate offsets. | ||
| 200 | const auto offset_count = GetOffsetCount(node_size); | ||
| 201 | const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); | ||
| 202 | const auto* const node = m_node_l1.Get<Node>(); | ||
| 203 | |||
| 204 | s64 start_offset; | ||
| 205 | if (offset_count < entry_set_count && node->GetCount() < offset_count) { | ||
| 206 | start_offset = *node->GetEnd(); | ||
| 207 | } else { | ||
| 208 | start_offset = *node->GetBegin(); | ||
| 209 | } | ||
| 210 | const auto end_offset = node->GetEndOffset(); | ||
| 211 | |||
| 212 | R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), | ||
| 213 | ResultInvalidBucketTreeEntryOffset); | ||
| 214 | R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); | ||
| 215 | |||
| 216 | // Set member variables. | ||
| 217 | m_node_storage = node_storage; | ||
| 218 | m_entry_storage = entry_storage; | ||
| 219 | m_node_size = node_size; | ||
| 220 | m_entry_size = entry_size; | ||
| 221 | m_entry_count = entry_count; | ||
| 222 | m_offset_count = offset_count; | ||
| 223 | m_entry_set_count = entry_set_count; | ||
| 224 | |||
| 225 | m_offset_cache.offsets.start_offset = start_offset; | ||
| 226 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 227 | m_offset_cache.is_initialized = true; | ||
| 228 | |||
| 229 | // We succeeded. | ||
| 230 | R_SUCCEED(); | ||
| 231 | } | ||
| 232 | |||
| 233 | void BucketTree::Initialize(size_t node_size, s64 end_offset) { | ||
| 234 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 235 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 236 | ASSERT(end_offset > 0); | ||
| 237 | ASSERT(!this->IsInitialized()); | ||
| 238 | |||
| 239 | m_node_size = node_size; | ||
| 240 | |||
| 241 | m_offset_cache.offsets.start_offset = 0; | ||
| 242 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 243 | m_offset_cache.is_initialized = true; | ||
| 244 | } | ||
| 245 | |||
| 246 | void BucketTree::Finalize() { | ||
| 247 | if (this->IsInitialized()) { | ||
| 248 | m_node_storage = VirtualFile(); | ||
| 249 | m_entry_storage = VirtualFile(); | ||
| 250 | m_node_l1.Free(m_node_size); | ||
| 251 | m_node_size = 0; | ||
| 252 | m_entry_size = 0; | ||
| 253 | m_entry_count = 0; | ||
| 254 | m_offset_count = 0; | ||
| 255 | m_entry_set_count = 0; | ||
| 256 | |||
| 257 | m_offset_cache.offsets.start_offset = 0; | ||
| 258 | m_offset_cache.offsets.end_offset = 0; | ||
| 259 | m_offset_cache.is_initialized = false; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | Result BucketTree::Find(Visitor* visitor, s64 virtual_address) { | ||
| 264 | ASSERT(visitor != nullptr); | ||
| 265 | ASSERT(this->IsInitialized()); | ||
| 266 | |||
| 267 | R_UNLESS(virtual_address >= 0, ResultInvalidOffset); | ||
| 268 | R_UNLESS(!this->IsEmpty(), ResultOutOfRange); | ||
| 269 | |||
| 270 | BucketTree::Offsets offsets; | ||
| 271 | R_TRY(this->GetOffsets(std::addressof(offsets))); | ||
| 272 | |||
| 273 | R_TRY(visitor->Initialize(this, offsets)); | ||
| 274 | |||
| 275 | R_RETURN(visitor->Find(virtual_address)); | ||
| 276 | } | ||
| 277 | |||
| 278 | Result BucketTree::InvalidateCache() { | ||
| 279 | // Reset our offsets. | ||
| 280 | m_offset_cache.is_initialized = false; | ||
| 281 | |||
| 282 | R_SUCCEED(); | ||
| 283 | } | ||
| 284 | |||
| 285 | Result BucketTree::EnsureOffsetCache() { | ||
| 286 | // If we already have an offset cache, we're good. | ||
| 287 | R_SUCCEED_IF(m_offset_cache.is_initialized); | ||
| 288 | |||
| 289 | // Acquire exclusive right to edit the offset cache. | ||
| 290 | std::scoped_lock lk(m_offset_cache.mutex); | ||
| 291 | |||
| 292 | // Check again, to be sure. | ||
| 293 | R_SUCCEED_IF(m_offset_cache.is_initialized); | ||
| 294 | |||
| 295 | // Read/verify L1. | ||
| 296 | m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size); | ||
| 297 | R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64))); | ||
| 298 | |||
| 299 | // Get the node. | ||
| 300 | auto* const node = m_node_l1.Get<Node>(); | ||
| 301 | |||
| 302 | s64 start_offset; | ||
| 303 | if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) { | ||
| 304 | start_offset = *node->GetEnd(); | ||
| 305 | } else { | ||
| 306 | start_offset = *node->GetBegin(); | ||
| 307 | } | ||
| 308 | const auto end_offset = node->GetEndOffset(); | ||
| 309 | |||
| 310 | R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), | ||
| 311 | ResultInvalidBucketTreeEntryOffset); | ||
| 312 | R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); | ||
| 313 | |||
| 314 | m_offset_cache.offsets.start_offset = start_offset; | ||
| 315 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 316 | m_offset_cache.is_initialized = true; | ||
| 317 | |||
| 318 | R_SUCCEED(); | ||
| 319 | } | ||
| 320 | |||
| 321 | Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) { | ||
| 322 | ASSERT(tree != nullptr); | ||
| 323 | ASSERT(m_tree == nullptr || m_tree == tree); | ||
| 324 | |||
| 325 | if (m_entry == nullptr) { | ||
| 326 | m_entry = ::operator new(tree->m_entry_size); | ||
| 327 | R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed); | ||
| 328 | |||
| 329 | m_tree = tree; | ||
| 330 | m_offsets = offsets; | ||
| 331 | } | ||
| 332 | |||
| 333 | R_SUCCEED(); | ||
| 334 | } | ||
| 335 | |||
| 336 | Result BucketTree::Visitor::MoveNext() { | ||
| 337 | R_UNLESS(this->IsValid(), ResultOutOfRange); | ||
| 338 | |||
| 339 | // Invalidate our index, and read the header for the next index. | ||
| 340 | auto entry_index = m_entry_index + 1; | ||
| 341 | if (entry_index == m_entry_set.info.count) { | ||
| 342 | const auto entry_set_index = m_entry_set.info.index + 1; | ||
| 343 | R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange); | ||
| 344 | |||
| 345 | m_entry_index = -1; | ||
| 346 | |||
| 347 | const auto end = m_entry_set.info.end; | ||
| 348 | |||
| 349 | const auto entry_set_size = m_tree->m_node_size; | ||
| 350 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 351 | |||
| 352 | m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); | ||
| 353 | R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); | ||
| 354 | |||
| 355 | R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end, | ||
| 356 | ResultInvalidBucketTreeEntrySetOffset); | ||
| 357 | |||
| 358 | entry_index = 0; | ||
| 359 | } else { | ||
| 360 | m_entry_index = -1; | ||
| 361 | } | ||
| 362 | |||
| 363 | // Read the new entry. | ||
| 364 | const auto entry_size = m_tree->m_entry_size; | ||
| 365 | const auto entry_offset = impl::GetBucketTreeEntryOffset( | ||
| 366 | m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); | ||
| 367 | m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 368 | |||
| 369 | // Note that we changed index. | ||
| 370 | m_entry_index = entry_index; | ||
| 371 | R_SUCCEED(); | ||
| 372 | } | ||
| 373 | |||
| 374 | Result BucketTree::Visitor::MovePrevious() { | ||
| 375 | R_UNLESS(this->IsValid(), ResultOutOfRange); | ||
| 376 | |||
| 377 | // Invalidate our index, and read the header for the previous index. | ||
| 378 | auto entry_index = m_entry_index; | ||
| 379 | if (entry_index == 0) { | ||
| 380 | R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange); | ||
| 381 | |||
| 382 | m_entry_index = -1; | ||
| 383 | |||
| 384 | const auto start = m_entry_set.info.start; | ||
| 385 | |||
| 386 | const auto entry_set_size = m_tree->m_node_size; | ||
| 387 | const auto entry_set_index = m_entry_set.info.index - 1; | ||
| 388 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 389 | |||
| 390 | m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); | ||
| 391 | R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); | ||
| 392 | |||
| 393 | R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end, | ||
| 394 | ResultInvalidBucketTreeEntrySetOffset); | ||
| 395 | |||
| 396 | entry_index = m_entry_set.info.count; | ||
| 397 | } else { | ||
| 398 | m_entry_index = -1; | ||
| 399 | } | ||
| 400 | |||
| 401 | --entry_index; | ||
| 402 | |||
| 403 | // Read the new entry. | ||
| 404 | const auto entry_size = m_tree->m_entry_size; | ||
| 405 | const auto entry_offset = impl::GetBucketTreeEntryOffset( | ||
| 406 | m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); | ||
| 407 | m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 408 | |||
| 409 | // Note that we changed index. | ||
| 410 | m_entry_index = entry_index; | ||
| 411 | R_SUCCEED(); | ||
| 412 | } | ||
| 413 | |||
| 414 | Result BucketTree::Visitor::Find(s64 virtual_address) { | ||
| 415 | ASSERT(m_tree != nullptr); | ||
| 416 | |||
| 417 | // Get the node. | ||
| 418 | const auto* const node = m_tree->m_node_l1.Get<Node>(); | ||
| 419 | R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange); | ||
| 420 | |||
| 421 | // Get the entry set index. | ||
| 422 | s32 entry_set_index = -1; | ||
| 423 | if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) { | ||
| 424 | const auto start = node->GetEnd(); | ||
| 425 | const auto end = node->GetBegin() + m_tree->m_offset_count; | ||
| 426 | |||
| 427 | auto pos = std::upper_bound(start, end, virtual_address); | ||
| 428 | R_UNLESS(start < pos, ResultOutOfRange); | ||
| 429 | --pos; | ||
| 430 | |||
| 431 | entry_set_index = static_cast<s32>(pos - start); | ||
| 432 | } else { | ||
| 433 | const auto start = node->GetBegin(); | ||
| 434 | const auto end = node->GetEnd(); | ||
| 435 | |||
| 436 | auto pos = std::upper_bound(start, end, virtual_address); | ||
| 437 | R_UNLESS(start < pos, ResultOutOfRange); | ||
| 438 | --pos; | ||
| 439 | |||
| 440 | if (m_tree->IsExistL2()) { | ||
| 441 | const auto node_index = static_cast<s32>(pos - start); | ||
| 442 | R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count, | ||
| 443 | ResultInvalidBucketTreeNodeOffset); | ||
| 444 | |||
| 445 | R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index)); | ||
| 446 | } else { | ||
| 447 | entry_set_index = static_cast<s32>(pos - start); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | // Validate the entry set index. | ||
| 452 | R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count, | ||
| 453 | ResultInvalidBucketTreeNodeOffset); | ||
| 454 | |||
| 455 | // Find the entry. | ||
| 456 | R_TRY(this->FindEntry(virtual_address, entry_set_index)); | ||
| 457 | |||
| 458 | // Set count. | ||
| 459 | m_entry_set_count = m_tree->m_entry_set_count; | ||
| 460 | R_SUCCEED(); | ||
| 461 | } | ||
| 462 | |||
| 463 | Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) { | ||
| 464 | const auto node_size = m_tree->m_node_size; | ||
| 465 | |||
| 466 | PooledBuffer pool(node_size, 1); | ||
| 467 | if (node_size <= pool.GetSize()) { | ||
| 468 | R_RETURN( | ||
| 469 | this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer())); | ||
| 470 | } else { | ||
| 471 | pool.Deallocate(); | ||
| 472 | R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index)); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, | ||
| 477 | s32 node_index, char* buffer) { | ||
| 478 | // Calculate node extents. | ||
| 479 | const auto node_size = m_tree->m_node_size; | ||
| 480 | const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); | ||
| 481 | VirtualFile storage = m_tree->m_node_storage; | ||
| 482 | |||
| 483 | // Read the node. | ||
| 484 | storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset); | ||
| 485 | |||
| 486 | // Validate the header. | ||
| 487 | NodeHeader header; | ||
| 488 | std::memcpy(std::addressof(header), buffer, NodeHeaderSize); | ||
| 489 | R_TRY(header.Verify(node_index, node_size, sizeof(s64))); | ||
| 490 | |||
| 491 | // Create the node, and find. | ||
| 492 | StorageNode node(sizeof(s64), header.count); | ||
| 493 | node.Find(buffer, virtual_address); | ||
| 494 | R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset); | ||
| 495 | |||
| 496 | // Return the index. | ||
| 497 | *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); | ||
| 498 | R_SUCCEED(); | ||
| 499 | } | ||
| 500 | |||
| 501 | Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, | ||
| 502 | s32 node_index) { | ||
| 503 | // Calculate node extents. | ||
| 504 | const auto node_size = m_tree->m_node_size; | ||
| 505 | const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); | ||
| 506 | VirtualFile storage = m_tree->m_node_storage; | ||
| 507 | |||
| 508 | // Read and validate the header. | ||
| 509 | NodeHeader header; | ||
| 510 | storage->ReadObject(std::addressof(header), node_offset); | ||
| 511 | R_TRY(header.Verify(node_index, node_size, sizeof(s64))); | ||
| 512 | |||
| 513 | // Create the node, and find. | ||
| 514 | StorageNode node(node_offset, sizeof(s64), header.count); | ||
| 515 | R_TRY(node.Find(storage, virtual_address)); | ||
| 516 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 517 | |||
| 518 | // Return the index. | ||
| 519 | *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); | ||
| 520 | R_SUCCEED(); | ||
| 521 | } | ||
| 522 | |||
| 523 | Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) { | ||
| 524 | const auto entry_set_size = m_tree->m_node_size; | ||
| 525 | |||
| 526 | PooledBuffer pool(entry_set_size, 1); | ||
| 527 | if (entry_set_size <= pool.GetSize()) { | ||
| 528 | R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer())); | ||
| 529 | } else { | ||
| 530 | pool.Deallocate(); | ||
| 531 | R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index)); | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, | ||
| 536 | char* buffer) { | ||
| 537 | // Calculate entry set extents. | ||
| 538 | const auto entry_size = m_tree->m_entry_size; | ||
| 539 | const auto entry_set_size = m_tree->m_node_size; | ||
| 540 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 541 | VirtualFile storage = m_tree->m_entry_storage; | ||
| 542 | |||
| 543 | // Read the entry set. | ||
| 544 | storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset); | ||
| 545 | |||
| 546 | // Validate the entry_set. | ||
| 547 | EntrySetHeader entry_set; | ||
| 548 | std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader)); | ||
| 549 | R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); | ||
| 550 | |||
| 551 | // Create the node, and find. | ||
| 552 | StorageNode node(entry_size, entry_set.info.count); | ||
| 553 | node.Find(buffer, virtual_address); | ||
| 554 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 555 | |||
| 556 | // Copy the data into entry. | ||
| 557 | const auto entry_index = node.GetIndex(); | ||
| 558 | const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index); | ||
| 559 | std::memcpy(m_entry, buffer + entry_offset, entry_size); | ||
| 560 | |||
| 561 | // Set our entry set/index. | ||
| 562 | m_entry_set = entry_set; | ||
| 563 | m_entry_index = entry_index; | ||
| 564 | |||
| 565 | R_SUCCEED(); | ||
| 566 | } | ||
| 567 | |||
| 568 | Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) { | ||
| 569 | // Calculate entry set extents. | ||
| 570 | const auto entry_size = m_tree->m_entry_size; | ||
| 571 | const auto entry_set_size = m_tree->m_node_size; | ||
| 572 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 573 | VirtualFile storage = m_tree->m_entry_storage; | ||
| 574 | |||
| 575 | // Read and validate the entry_set. | ||
| 576 | EntrySetHeader entry_set; | ||
| 577 | storage->ReadObject(std::addressof(entry_set), entry_set_offset); | ||
| 578 | R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); | ||
| 579 | |||
| 580 | // Create the node, and find. | ||
| 581 | StorageNode node(entry_set_offset, entry_size, entry_set.info.count); | ||
| 582 | R_TRY(node.Find(storage, virtual_address)); | ||
| 583 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 584 | |||
| 585 | // Copy the data into entry. | ||
| 586 | const auto entry_index = node.GetIndex(); | ||
| 587 | const auto entry_offset = | ||
| 588 | impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index); | ||
| 589 | storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 590 | |||
| 591 | // Set our entry set/index. | ||
| 592 | m_entry_set = entry_set; | ||
| 593 | m_entry_index = entry_index; | ||
| 594 | |||
| 595 | R_SUCCEED(); | ||
| 596 | } | ||
| 597 | |||
| 598 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h new file mode 100644 index 000000000..46850cd48 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h | |||
| @@ -0,0 +1,416 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "common/alignment.h" | ||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/literals.h" | ||
| 12 | |||
| 13 | #include "core/file_sys/vfs.h" | ||
| 14 | #include "core/hle/result.h" | ||
| 15 | |||
| 16 | namespace FileSys { | ||
| 17 | |||
| 18 | using namespace Common::Literals; | ||
| 19 | |||
| 20 | class BucketTree { | ||
| 21 | YUZU_NON_COPYABLE(BucketTree); | ||
| 22 | YUZU_NON_MOVEABLE(BucketTree); | ||
| 23 | |||
| 24 | public: | ||
| 25 | static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R'); | ||
| 26 | static constexpr u32 Version = 1; | ||
| 27 | |||
| 28 | static constexpr size_t NodeSizeMin = 1_KiB; | ||
| 29 | static constexpr size_t NodeSizeMax = 512_KiB; | ||
| 30 | |||
| 31 | public: | ||
| 32 | class Visitor; | ||
| 33 | |||
| 34 | struct Header { | ||
| 35 | u32 magic; | ||
| 36 | u32 version; | ||
| 37 | s32 entry_count; | ||
| 38 | s32 reserved; | ||
| 39 | |||
| 40 | void Format(s32 entry_count); | ||
| 41 | Result Verify() const; | ||
| 42 | }; | ||
| 43 | static_assert(std::is_trivial_v<Header>); | ||
| 44 | static_assert(sizeof(Header) == 0x10); | ||
| 45 | |||
| 46 | struct NodeHeader { | ||
| 47 | s32 index; | ||
| 48 | s32 count; | ||
| 49 | s64 offset; | ||
| 50 | |||
| 51 | Result Verify(s32 node_index, size_t node_size, size_t entry_size) const; | ||
| 52 | }; | ||
| 53 | static_assert(std::is_trivial_v<NodeHeader>); | ||
| 54 | static_assert(sizeof(NodeHeader) == 0x10); | ||
| 55 | |||
| 56 | struct Offsets { | ||
| 57 | s64 start_offset; | ||
| 58 | s64 end_offset; | ||
| 59 | |||
| 60 | constexpr bool IsInclude(s64 offset) const { | ||
| 61 | return this->start_offset <= offset && offset < this->end_offset; | ||
| 62 | } | ||
| 63 | |||
| 64 | constexpr bool IsInclude(s64 offset, s64 size) const { | ||
| 65 | return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset); | ||
| 66 | } | ||
| 67 | }; | ||
| 68 | static_assert(std::is_trivial_v<Offsets>); | ||
| 69 | static_assert(sizeof(Offsets) == 0x10); | ||
| 70 | |||
| 71 | struct OffsetCache { | ||
| 72 | Offsets offsets; | ||
| 73 | std::mutex mutex; | ||
| 74 | bool is_initialized; | ||
| 75 | |||
| 76 | OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {} | ||
| 77 | }; | ||
| 78 | |||
| 79 | class ContinuousReadingInfo { | ||
| 80 | public: | ||
| 81 | constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {} | ||
| 82 | |||
| 83 | constexpr void Reset() { | ||
| 84 | m_read_size = 0; | ||
| 85 | m_skip_count = 0; | ||
| 86 | m_done = false; | ||
| 87 | } | ||
| 88 | |||
| 89 | constexpr void SetSkipCount(s32 count) { | ||
| 90 | ASSERT(count >= 0); | ||
| 91 | m_skip_count = count; | ||
| 92 | } | ||
| 93 | constexpr s32 GetSkipCount() const { | ||
| 94 | return m_skip_count; | ||
| 95 | } | ||
| 96 | constexpr bool CheckNeedScan() { | ||
| 97 | return (--m_skip_count) <= 0; | ||
| 98 | } | ||
| 99 | |||
| 100 | constexpr void Done() { | ||
| 101 | m_read_size = 0; | ||
| 102 | m_done = true; | ||
| 103 | } | ||
| 104 | constexpr bool IsDone() const { | ||
| 105 | return m_done; | ||
| 106 | } | ||
| 107 | |||
| 108 | constexpr void SetReadSize(size_t size) { | ||
| 109 | m_read_size = size; | ||
| 110 | } | ||
| 111 | constexpr size_t GetReadSize() const { | ||
| 112 | return m_read_size; | ||
| 113 | } | ||
| 114 | constexpr bool CanDo() const { | ||
| 115 | return m_read_size > 0; | ||
| 116 | } | ||
| 117 | |||
| 118 | private: | ||
| 119 | size_t m_read_size; | ||
| 120 | s32 m_skip_count; | ||
| 121 | bool m_done; | ||
| 122 | }; | ||
| 123 | |||
| 124 | private: | ||
| 125 | class NodeBuffer { | ||
| 126 | YUZU_NON_COPYABLE(NodeBuffer); | ||
| 127 | |||
| 128 | public: | ||
| 129 | NodeBuffer() : m_header() {} | ||
| 130 | |||
| 131 | ~NodeBuffer() { | ||
| 132 | ASSERT(m_header == nullptr); | ||
| 133 | } | ||
| 134 | |||
| 135 | NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) { | ||
| 136 | rhs.m_header = nullptr; | ||
| 137 | } | ||
| 138 | |||
| 139 | NodeBuffer& operator=(NodeBuffer&& rhs) { | ||
| 140 | if (this != std::addressof(rhs)) { | ||
| 141 | ASSERT(m_header == nullptr); | ||
| 142 | |||
| 143 | m_header = rhs.m_header; | ||
| 144 | |||
| 145 | rhs.m_header = nullptr; | ||
| 146 | } | ||
| 147 | return *this; | ||
| 148 | } | ||
| 149 | |||
| 150 | bool Allocate(size_t node_size) { | ||
| 151 | ASSERT(m_header == nullptr); | ||
| 152 | |||
| 153 | m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)}); | ||
| 154 | |||
| 155 | // ASSERT(Common::IsAligned(m_header, sizeof(s64))); | ||
| 156 | |||
| 157 | return m_header != nullptr; | ||
| 158 | } | ||
| 159 | |||
| 160 | void Free(size_t node_size) { | ||
| 161 | if (m_header) { | ||
| 162 | ::operator delete(m_header, std::align_val_t{sizeof(s64)}); | ||
| 163 | m_header = nullptr; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | void FillZero(size_t node_size) const { | ||
| 168 | if (m_header) { | ||
| 169 | std::memset(m_header, 0, node_size); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | NodeHeader* Get() const { | ||
| 174 | return reinterpret_cast<NodeHeader*>(m_header); | ||
| 175 | } | ||
| 176 | |||
| 177 | NodeHeader* operator->() const { | ||
| 178 | return this->Get(); | ||
| 179 | } | ||
| 180 | |||
| 181 | template <typename T> | ||
| 182 | T* Get() const { | ||
| 183 | static_assert(std::is_trivial_v<T>); | ||
| 184 | static_assert(sizeof(T) == sizeof(NodeHeader)); | ||
| 185 | return reinterpret_cast<T*>(m_header); | ||
| 186 | } | ||
| 187 | |||
| 188 | private: | ||
| 189 | void* m_header; | ||
| 190 | }; | ||
| 191 | |||
| 192 | private: | ||
| 193 | static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) { | ||
| 194 | return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size); | ||
| 195 | } | ||
| 196 | |||
| 197 | static constexpr s32 GetOffsetCount(size_t node_size) { | ||
| 198 | return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64)); | ||
| 199 | } | ||
| 200 | |||
| 201 | static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) { | ||
| 202 | const s32 entry_count_per_node = GetEntryCount(node_size, entry_size); | ||
| 203 | return Common::DivideUp(entry_count, entry_count_per_node); | ||
| 204 | } | ||
| 205 | |||
| 206 | static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) { | ||
| 207 | const s32 offset_count_per_node = GetOffsetCount(node_size); | ||
| 208 | const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); | ||
| 209 | |||
| 210 | if (entry_set_count <= offset_count_per_node) { | ||
| 211 | return 0; | ||
| 212 | } | ||
| 213 | |||
| 214 | const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node); | ||
| 215 | ASSERT(node_l2_count <= offset_count_per_node); | ||
| 216 | |||
| 217 | return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)), | ||
| 218 | offset_count_per_node); | ||
| 219 | } | ||
| 220 | |||
| 221 | public: | ||
| 222 | BucketTree() | ||
| 223 | : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(), | ||
| 224 | m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {} | ||
| 225 | ~BucketTree() { | ||
| 226 | this->Finalize(); | ||
| 227 | } | ||
| 228 | |||
| 229 | Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, | ||
| 230 | size_t entry_size, s32 entry_count); | ||
| 231 | void Initialize(size_t node_size, s64 end_offset); | ||
| 232 | void Finalize(); | ||
| 233 | |||
| 234 | bool IsInitialized() const { | ||
| 235 | return m_node_size > 0; | ||
| 236 | } | ||
| 237 | bool IsEmpty() const { | ||
| 238 | return m_entry_size == 0; | ||
| 239 | } | ||
| 240 | |||
| 241 | Result Find(Visitor* visitor, s64 virtual_address); | ||
| 242 | Result InvalidateCache(); | ||
| 243 | |||
| 244 | s32 GetEntryCount() const { | ||
| 245 | return m_entry_count; | ||
| 246 | } | ||
| 247 | |||
| 248 | Result GetOffsets(Offsets* out) { | ||
| 249 | // Ensure we have an offset cache. | ||
| 250 | R_TRY(this->EnsureOffsetCache()); | ||
| 251 | |||
| 252 | // Set the output. | ||
| 253 | *out = m_offset_cache.offsets; | ||
| 254 | R_SUCCEED(); | ||
| 255 | } | ||
| 256 | |||
| 257 | public: | ||
| 258 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 259 | return sizeof(Header); | ||
| 260 | } | ||
| 261 | |||
| 262 | static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size, | ||
| 263 | s32 entry_count) { | ||
| 264 | ASSERT(entry_size >= sizeof(s64)); | ||
| 265 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 266 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 267 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 268 | ASSERT(entry_count >= 0); | ||
| 269 | |||
| 270 | if (entry_count <= 0) { | ||
| 271 | return 0; | ||
| 272 | } | ||
| 273 | return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) * | ||
| 274 | static_cast<s64>(node_size); | ||
| 275 | } | ||
| 276 | |||
| 277 | static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size, | ||
| 278 | s32 entry_count) { | ||
| 279 | ASSERT(entry_size >= sizeof(s64)); | ||
| 280 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 281 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 282 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 283 | ASSERT(entry_count >= 0); | ||
| 284 | |||
| 285 | if (entry_count <= 0) { | ||
| 286 | return 0; | ||
| 287 | } | ||
| 288 | return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size); | ||
| 289 | } | ||
| 290 | |||
| 291 | private: | ||
| 292 | template <typename EntryType> | ||
| 293 | struct ContinuousReadingParam { | ||
| 294 | s64 offset; | ||
| 295 | size_t size; | ||
| 296 | NodeHeader entry_set; | ||
| 297 | s32 entry_index; | ||
| 298 | Offsets offsets; | ||
| 299 | EntryType entry; | ||
| 300 | }; | ||
| 301 | |||
| 302 | private: | ||
| 303 | template <typename EntryType> | ||
| 304 | Result ScanContinuousReading(ContinuousReadingInfo* out_info, | ||
| 305 | const ContinuousReadingParam<EntryType>& param) const; | ||
| 306 | |||
| 307 | bool IsExistL2() const { | ||
| 308 | return m_offset_count < m_entry_set_count; | ||
| 309 | } | ||
| 310 | bool IsExistOffsetL2OnL1() const { | ||
| 311 | return this->IsExistL2() && m_node_l1->count < m_offset_count; | ||
| 312 | } | ||
| 313 | |||
| 314 | s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const { | ||
| 315 | return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index; | ||
| 316 | } | ||
| 317 | |||
| 318 | Result EnsureOffsetCache(); | ||
| 319 | |||
| 320 | private: | ||
| 321 | mutable VirtualFile m_node_storage; | ||
| 322 | mutable VirtualFile m_entry_storage; | ||
| 323 | NodeBuffer m_node_l1; | ||
| 324 | size_t m_node_size; | ||
| 325 | size_t m_entry_size; | ||
| 326 | s32 m_entry_count; | ||
| 327 | s32 m_offset_count; | ||
| 328 | s32 m_entry_set_count; | ||
| 329 | OffsetCache m_offset_cache; | ||
| 330 | }; | ||
| 331 | |||
| 332 | class BucketTree::Visitor { | ||
| 333 | YUZU_NON_COPYABLE(Visitor); | ||
| 334 | YUZU_NON_MOVEABLE(Visitor); | ||
| 335 | |||
| 336 | public: | ||
| 337 | constexpr Visitor() | ||
| 338 | : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {} | ||
| 339 | ~Visitor() { | ||
| 340 | if (m_entry != nullptr) { | ||
| 341 | ::operator delete(m_entry, m_tree->m_entry_size); | ||
| 342 | m_tree = nullptr; | ||
| 343 | m_entry = nullptr; | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | bool IsValid() const { | ||
| 348 | return m_entry_index >= 0; | ||
| 349 | } | ||
| 350 | bool CanMoveNext() const { | ||
| 351 | return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count || | ||
| 352 | m_entry_set.info.index + 1 < m_entry_set_count); | ||
| 353 | } | ||
| 354 | bool CanMovePrevious() const { | ||
| 355 | return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0); | ||
| 356 | } | ||
| 357 | |||
| 358 | Result MoveNext(); | ||
| 359 | Result MovePrevious(); | ||
| 360 | |||
| 361 | template <typename EntryType> | ||
| 362 | Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const; | ||
| 363 | |||
| 364 | const void* Get() const { | ||
| 365 | ASSERT(this->IsValid()); | ||
| 366 | return m_entry; | ||
| 367 | } | ||
| 368 | |||
| 369 | template <typename T> | ||
| 370 | const T* Get() const { | ||
| 371 | ASSERT(this->IsValid()); | ||
| 372 | return reinterpret_cast<const T*>(m_entry); | ||
| 373 | } | ||
| 374 | |||
| 375 | const BucketTree* GetTree() const { | ||
| 376 | return m_tree; | ||
| 377 | } | ||
| 378 | |||
| 379 | private: | ||
| 380 | Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets); | ||
| 381 | |||
| 382 | Result Find(s64 virtual_address); | ||
| 383 | |||
| 384 | Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index); | ||
| 385 | Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index, | ||
| 386 | char* buffer); | ||
| 387 | Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index); | ||
| 388 | |||
| 389 | Result FindEntry(s64 virtual_address, s32 entry_set_index); | ||
| 390 | Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer); | ||
| 391 | Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index); | ||
| 392 | |||
| 393 | private: | ||
| 394 | friend class BucketTree; | ||
| 395 | |||
| 396 | union EntrySetHeader { | ||
| 397 | NodeHeader header; | ||
| 398 | struct Info { | ||
| 399 | s32 index; | ||
| 400 | s32 count; | ||
| 401 | s64 end; | ||
| 402 | s64 start; | ||
| 403 | } info; | ||
| 404 | static_assert(std::is_trivial_v<Info>); | ||
| 405 | }; | ||
| 406 | static_assert(std::is_trivial_v<EntrySetHeader>); | ||
| 407 | |||
| 408 | const BucketTree* m_tree; | ||
| 409 | BucketTree::Offsets m_offsets; | ||
| 410 | void* m_entry; | ||
| 411 | s32 m_entry_index; | ||
| 412 | s32 m_entry_set_count; | ||
| 413 | EntrySetHeader m_entry_set; | ||
| 414 | }; | ||
| 415 | |||
| 416 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h new file mode 100644 index 000000000..030b2916b --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | template <typename EntryType> | ||
| 14 | Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info, | ||
| 15 | const ContinuousReadingParam<EntryType>& param) const { | ||
| 16 | static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>); | ||
| 17 | |||
| 18 | // Validate our preconditions. | ||
| 19 | ASSERT(this->IsInitialized()); | ||
| 20 | ASSERT(out_info != nullptr); | ||
| 21 | ASSERT(m_entry_size == sizeof(EntryType)); | ||
| 22 | |||
| 23 | // Reset the output. | ||
| 24 | out_info->Reset(); | ||
| 25 | |||
| 26 | // If there's nothing to read, we're done. | ||
| 27 | R_SUCCEED_IF(param.size == 0); | ||
| 28 | |||
| 29 | // If we're reading a fragment, we're done. | ||
| 30 | R_SUCCEED_IF(param.entry.IsFragment()); | ||
| 31 | |||
| 32 | // Validate the first entry. | ||
| 33 | auto entry = param.entry; | ||
| 34 | auto cur_offset = param.offset; | ||
| 35 | R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange); | ||
| 36 | |||
| 37 | // Create a pooled buffer for our scan. | ||
| 38 | PooledBuffer pool(m_node_size, 1); | ||
| 39 | char* buffer = nullptr; | ||
| 40 | |||
| 41 | s64 entry_storage_size = m_entry_storage->GetSize(); | ||
| 42 | |||
| 43 | // Read the node. | ||
| 44 | if (m_node_size <= pool.GetSize()) { | ||
| 45 | buffer = pool.GetBuffer(); | ||
| 46 | const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size); | ||
| 47 | R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size), | ||
| 48 | ResultInvalidBucketTreeNodeEntryCount); | ||
| 49 | |||
| 50 | m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs); | ||
| 51 | } | ||
| 52 | |||
| 53 | // Calculate extents. | ||
| 54 | const auto end_offset = cur_offset + static_cast<s64>(param.size); | ||
| 55 | s64 phys_offset = entry.GetPhysicalOffset(); | ||
| 56 | |||
| 57 | // Start merge tracking. | ||
| 58 | s64 merge_size = 0; | ||
| 59 | s64 readable_size = 0; | ||
| 60 | bool merged = false; | ||
| 61 | |||
| 62 | // Iterate. | ||
| 63 | auto entry_index = param.entry_index; | ||
| 64 | for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) { | ||
| 65 | // If we're past the end, we're done. | ||
| 66 | if (end_offset <= cur_offset) { | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | |||
| 70 | // Validate the entry offset. | ||
| 71 | const auto entry_offset = entry.GetVirtualOffset(); | ||
| 72 | R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); | ||
| 73 | |||
| 74 | // Get the next entry. | ||
| 75 | EntryType next_entry = {}; | ||
| 76 | s64 next_entry_offset; | ||
| 77 | |||
| 78 | if (entry_index + 1 < entry_count) { | ||
| 79 | if (buffer != nullptr) { | ||
| 80 | const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1); | ||
| 81 | std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size); | ||
| 82 | } else { | ||
| 83 | const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size, | ||
| 84 | m_entry_size, entry_index + 1); | ||
| 85 | m_entry_storage->ReadObject(std::addressof(next_entry), ofs); | ||
| 86 | } | ||
| 87 | |||
| 88 | next_entry_offset = next_entry.GetVirtualOffset(); | ||
| 89 | R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); | ||
| 90 | } else { | ||
| 91 | next_entry_offset = param.entry_set.offset; | ||
| 92 | } | ||
| 93 | |||
| 94 | // Validate the next entry offset. | ||
| 95 | R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); | ||
| 96 | |||
| 97 | // Determine the much data there is. | ||
| 98 | const auto data_size = next_entry_offset - cur_offset; | ||
| 99 | ASSERT(data_size > 0); | ||
| 100 | |||
| 101 | // Determine how much data we should read. | ||
| 102 | const auto remaining_size = end_offset - cur_offset; | ||
| 103 | const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size)); | ||
| 104 | ASSERT(read_size <= param.size); | ||
| 105 | |||
| 106 | // Update our merge tracking. | ||
| 107 | if (entry.IsFragment()) { | ||
| 108 | // If we can't merge, stop looping. | ||
| 109 | if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) { | ||
| 110 | break; | ||
| 111 | } | ||
| 112 | |||
| 113 | // Otherwise, add the current size to the merge size. | ||
| 114 | merge_size += read_size; | ||
| 115 | } else { | ||
| 116 | // If we can't merge, stop looping. | ||
| 117 | if (phys_offset != entry.GetPhysicalOffset()) { | ||
| 118 | break; | ||
| 119 | } | ||
| 120 | |||
| 121 | // Add the size to the readable amount. | ||
| 122 | readable_size += merge_size + read_size; | ||
| 123 | ASSERT(readable_size <= static_cast<s64>(param.size)); | ||
| 124 | |||
| 125 | // Update whether we've merged. | ||
| 126 | merged |= merge_size > 0; | ||
| 127 | merge_size = 0; | ||
| 128 | } | ||
| 129 | |||
| 130 | // Advance. | ||
| 131 | cur_offset += read_size; | ||
| 132 | ASSERT(cur_offset <= end_offset); | ||
| 133 | |||
| 134 | phys_offset += next_entry_offset - entry_offset; | ||
| 135 | entry = next_entry; | ||
| 136 | } | ||
| 137 | |||
| 138 | // If we merged, set our readable size. | ||
| 139 | if (merged) { | ||
| 140 | out_info->SetReadSize(static_cast<size_t>(readable_size)); | ||
| 141 | } | ||
| 142 | out_info->SetSkipCount(entry_index - param.entry_index); | ||
| 143 | |||
| 144 | R_SUCCEED(); | ||
| 145 | } | ||
| 146 | |||
| 147 | template <typename EntryType> | ||
| 148 | Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, | ||
| 149 | size_t size) const { | ||
| 150 | static_assert(std::is_trivial_v<EntryType>); | ||
| 151 | ASSERT(this->IsValid()); | ||
| 152 | |||
| 153 | // Create our parameters. | ||
| 154 | ContinuousReadingParam<EntryType> param = { | ||
| 155 | .offset = offset, | ||
| 156 | .size = size, | ||
| 157 | .entry_set = m_entry_set.header, | ||
| 158 | .entry_index = m_entry_index, | ||
| 159 | .offsets{}, | ||
| 160 | .entry{}, | ||
| 161 | }; | ||
| 162 | std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets), | ||
| 163 | sizeof(BucketTree::Offsets)); | ||
| 164 | std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType)); | ||
| 165 | |||
| 166 | // Scan. | ||
| 167 | R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param)); | ||
| 168 | } | ||
| 169 | |||
| 170 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h new file mode 100644 index 000000000..5503613fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h | |||
| @@ -0,0 +1,110 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 7 | |||
| 8 | namespace FileSys::impl { | ||
| 9 | |||
| 10 | class SafeValue { | ||
| 11 | public: | ||
| 12 | static s64 GetInt64(const void* ptr) { | ||
| 13 | s64 value; | ||
| 14 | std::memcpy(std::addressof(value), ptr, sizeof(s64)); | ||
| 15 | return value; | ||
| 16 | } | ||
| 17 | |||
| 18 | static s64 GetInt64(const s64* ptr) { | ||
| 19 | return GetInt64(static_cast<const void*>(ptr)); | ||
| 20 | } | ||
| 21 | |||
| 22 | static s64 GetInt64(const s64& v) { | ||
| 23 | return GetInt64(std::addressof(v)); | ||
| 24 | } | ||
| 25 | |||
| 26 | static void SetInt64(void* dst, const void* src) { | ||
| 27 | std::memcpy(dst, src, sizeof(s64)); | ||
| 28 | } | ||
| 29 | |||
| 30 | static void SetInt64(void* dst, const s64* src) { | ||
| 31 | return SetInt64(dst, static_cast<const void*>(src)); | ||
| 32 | } | ||
| 33 | |||
| 34 | static void SetInt64(void* dst, const s64& v) { | ||
| 35 | return SetInt64(dst, std::addressof(v)); | ||
| 36 | } | ||
| 37 | }; | ||
| 38 | |||
| 39 | template <typename IteratorType> | ||
| 40 | struct BucketTreeNode { | ||
| 41 | using Header = BucketTree::NodeHeader; | ||
| 42 | |||
| 43 | Header header; | ||
| 44 | |||
| 45 | s32 GetCount() const { | ||
| 46 | return this->header.count; | ||
| 47 | } | ||
| 48 | |||
| 49 | void* GetArray() { | ||
| 50 | return std::addressof(this->header) + 1; | ||
| 51 | } | ||
| 52 | template <typename T> | ||
| 53 | T* GetArray() { | ||
| 54 | return reinterpret_cast<T*>(this->GetArray()); | ||
| 55 | } | ||
| 56 | const void* GetArray() const { | ||
| 57 | return std::addressof(this->header) + 1; | ||
| 58 | } | ||
| 59 | template <typename T> | ||
| 60 | const T* GetArray() const { | ||
| 61 | return reinterpret_cast<const T*>(this->GetArray()); | ||
| 62 | } | ||
| 63 | |||
| 64 | s64 GetBeginOffset() const { | ||
| 65 | return *this->GetArray<s64>(); | ||
| 66 | } | ||
| 67 | s64 GetEndOffset() const { | ||
| 68 | return this->header.offset; | ||
| 69 | } | ||
| 70 | |||
| 71 | IteratorType GetBegin() { | ||
| 72 | return IteratorType(this->GetArray<s64>()); | ||
| 73 | } | ||
| 74 | IteratorType GetEnd() { | ||
| 75 | return IteratorType(this->GetArray<s64>()) + this->header.count; | ||
| 76 | } | ||
| 77 | IteratorType GetBegin() const { | ||
| 78 | return IteratorType(this->GetArray<s64>()); | ||
| 79 | } | ||
| 80 | IteratorType GetEnd() const { | ||
| 81 | return IteratorType(this->GetArray<s64>()) + this->header.count; | ||
| 82 | } | ||
| 83 | |||
| 84 | IteratorType GetBegin(size_t entry_size) { | ||
| 85 | return IteratorType(this->GetArray(), entry_size); | ||
| 86 | } | ||
| 87 | IteratorType GetEnd(size_t entry_size) { | ||
| 88 | return IteratorType(this->GetArray(), entry_size) + this->header.count; | ||
| 89 | } | ||
| 90 | IteratorType GetBegin(size_t entry_size) const { | ||
| 91 | return IteratorType(this->GetArray(), entry_size); | ||
| 92 | } | ||
| 93 | IteratorType GetEnd(size_t entry_size) const { | ||
| 94 | return IteratorType(this->GetArray(), entry_size) + this->header.count; | ||
| 95 | } | ||
| 96 | }; | ||
| 97 | |||
| 98 | constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size, | ||
| 99 | s32 entry_index) { | ||
| 100 | return entry_set_offset + sizeof(BucketTree::NodeHeader) + | ||
| 101 | entry_index * static_cast<s64>(entry_size); | ||
| 102 | } | ||
| 103 | |||
| 104 | constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size, | ||
| 105 | size_t entry_size, s32 entry_index) { | ||
| 106 | return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size, | ||
| 107 | entry_index); | ||
| 108 | } | ||
| 109 | |||
| 110 | } // namespace FileSys::impl | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h new file mode 100644 index 000000000..33d93938e --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h | |||
| @@ -0,0 +1,963 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/literals.h" | ||
| 7 | |||
| 8 | #include "core/file_sys/errors.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 11 | #include "core/file_sys/fssystem/fssystem_compression_common.h" | ||
| 12 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 13 | #include "core/file_sys/vfs.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | using namespace Common::Literals; | ||
| 18 | |||
| 19 | class CompressedStorage : public IReadOnlyStorage { | ||
| 20 | YUZU_NON_COPYABLE(CompressedStorage); | ||
| 21 | YUZU_NON_MOVEABLE(CompressedStorage); | ||
| 22 | |||
| 23 | public: | ||
| 24 | static constexpr size_t NodeSize = 16_KiB; | ||
| 25 | |||
| 26 | struct Entry { | ||
| 27 | s64 virt_offset; | ||
| 28 | s64 phys_offset; | ||
| 29 | CompressionType compression_type; | ||
| 30 | s32 phys_size; | ||
| 31 | |||
| 32 | s64 GetPhysicalSize() const { | ||
| 33 | return this->phys_size; | ||
| 34 | } | ||
| 35 | }; | ||
| 36 | static_assert(std::is_trivial_v<Entry>); | ||
| 37 | static_assert(sizeof(Entry) == 0x18); | ||
| 38 | |||
| 39 | public: | ||
| 40 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 41 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 42 | } | ||
| 43 | |||
| 44 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 45 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 46 | } | ||
| 47 | |||
| 48 | private: | ||
| 49 | class CompressedStorageCore { | ||
| 50 | YUZU_NON_COPYABLE(CompressedStorageCore); | ||
| 51 | YUZU_NON_MOVEABLE(CompressedStorageCore); | ||
| 52 | |||
| 53 | public: | ||
| 54 | CompressedStorageCore() : m_table(), m_data_storage() {} | ||
| 55 | |||
| 56 | ~CompressedStorageCore() { | ||
| 57 | this->Finalize(); | ||
| 58 | } | ||
| 59 | |||
| 60 | public: | ||
| 61 | Result Initialize(VirtualFile data_storage, VirtualFile node_storage, | ||
| 62 | VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max, | ||
| 63 | size_t continuous_reading_size_max, | ||
| 64 | GetDecompressorFunction get_decompressor) { | ||
| 65 | // Check pre-conditions. | ||
| 66 | ASSERT(0 < block_size_max); | ||
| 67 | ASSERT(block_size_max <= continuous_reading_size_max); | ||
| 68 | ASSERT(get_decompressor != nullptr); | ||
| 69 | |||
| 70 | // Initialize our entry table. | ||
| 71 | R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), | ||
| 72 | bktr_entry_count)); | ||
| 73 | |||
| 74 | // Set our other fields. | ||
| 75 | m_block_size_max = block_size_max; | ||
| 76 | m_continuous_reading_size_max = continuous_reading_size_max; | ||
| 77 | m_data_storage = data_storage; | ||
| 78 | m_get_decompressor_function = get_decompressor; | ||
| 79 | |||
| 80 | R_SUCCEED(); | ||
| 81 | } | ||
| 82 | |||
| 83 | void Finalize() { | ||
| 84 | if (this->IsInitialized()) { | ||
| 85 | m_table.Finalize(); | ||
| 86 | m_data_storage = VirtualFile(); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | VirtualFile GetDataStorage() { | ||
| 91 | return m_data_storage; | ||
| 92 | } | ||
| 93 | |||
| 94 | Result GetDataStorageSize(s64* out) { | ||
| 95 | // Check pre-conditions. | ||
| 96 | ASSERT(out != nullptr); | ||
| 97 | |||
| 98 | // Get size. | ||
| 99 | *out = m_data_storage->GetSize(); | ||
| 100 | |||
| 101 | R_SUCCEED(); | ||
| 102 | } | ||
| 103 | |||
| 104 | BucketTree& GetEntryTable() { | ||
| 105 | return m_table; | ||
| 106 | } | ||
| 107 | |||
| 108 | Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, | ||
| 109 | s64 offset, s64 size) { | ||
| 110 | // Check pre-conditions. | ||
| 111 | ASSERT(offset >= 0); | ||
| 112 | ASSERT(size >= 0); | ||
| 113 | ASSERT(this->IsInitialized()); | ||
| 114 | |||
| 115 | // Check that we can output the count. | ||
| 116 | R_UNLESS(out_read_count != nullptr, ResultNullptrArgument); | ||
| 117 | |||
| 118 | // Check that we have anything to read at all. | ||
| 119 | R_SUCCEED_IF(size == 0); | ||
| 120 | |||
| 121 | // Check that either we have a buffer, or this is to determine how many we need. | ||
| 122 | if (max_entry_count != 0) { | ||
| 123 | R_UNLESS(out_entries != nullptr, ResultNullptrArgument); | ||
| 124 | } | ||
| 125 | |||
| 126 | // Get the table offsets. | ||
| 127 | BucketTree::Offsets table_offsets; | ||
| 128 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 129 | |||
| 130 | // Validate arguments. | ||
| 131 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 132 | |||
| 133 | // Find the offset in our tree. | ||
| 134 | BucketTree::Visitor visitor; | ||
| 135 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 136 | { | ||
| 137 | const auto entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 138 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 139 | ResultUnexpectedInCompressedStorageA); | ||
| 140 | } | ||
| 141 | |||
| 142 | // Get the entries. | ||
| 143 | const auto end_offset = offset + size; | ||
| 144 | s32 read_count = 0; | ||
| 145 | while (visitor.Get<Entry>()->virt_offset < end_offset) { | ||
| 146 | // If we should be setting the output, do so. | ||
| 147 | if (max_entry_count != 0) { | ||
| 148 | // Ensure we only read as many entries as we can. | ||
| 149 | if (read_count >= max_entry_count) { | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | |||
| 153 | // Set the current output entry. | ||
| 154 | out_entries[read_count] = *visitor.Get<Entry>(); | ||
| 155 | } | ||
| 156 | |||
| 157 | // Increase the read count. | ||
| 158 | ++read_count; | ||
| 159 | |||
| 160 | // If we're at the end, we're done. | ||
| 161 | if (!visitor.CanMoveNext()) { | ||
| 162 | break; | ||
| 163 | } | ||
| 164 | |||
| 165 | // Move to the next entry. | ||
| 166 | R_TRY(visitor.MoveNext()); | ||
| 167 | } | ||
| 168 | |||
| 169 | // Set the output read count. | ||
| 170 | *out_read_count = read_count; | ||
| 171 | R_SUCCEED(); | ||
| 172 | } | ||
| 173 | |||
| 174 | Result GetSize(s64* out) { | ||
| 175 | // Check pre-conditions. | ||
| 176 | ASSERT(out != nullptr); | ||
| 177 | |||
| 178 | // Get our table offsets. | ||
| 179 | BucketTree::Offsets offsets; | ||
| 180 | R_TRY(m_table.GetOffsets(std::addressof(offsets))); | ||
| 181 | |||
| 182 | // Set the output. | ||
| 183 | *out = offsets.end_offset; | ||
| 184 | R_SUCCEED(); | ||
| 185 | } | ||
| 186 | |||
| 187 | Result OperatePerEntry(s64 offset, s64 size, auto f) { | ||
| 188 | // Check pre-conditions. | ||
| 189 | ASSERT(offset >= 0); | ||
| 190 | ASSERT(size >= 0); | ||
| 191 | ASSERT(this->IsInitialized()); | ||
| 192 | |||
| 193 | // Succeed if there's nothing to operate on. | ||
| 194 | R_SUCCEED_IF(size == 0); | ||
| 195 | |||
| 196 | // Get the table offsets. | ||
| 197 | BucketTree::Offsets table_offsets; | ||
| 198 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 199 | |||
| 200 | // Validate arguments. | ||
| 201 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 202 | |||
| 203 | // Find the offset in our tree. | ||
| 204 | BucketTree::Visitor visitor; | ||
| 205 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 206 | { | ||
| 207 | const auto entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 208 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 209 | ResultUnexpectedInCompressedStorageA); | ||
| 210 | } | ||
| 211 | |||
| 212 | // Prepare to operate in chunks. | ||
| 213 | auto cur_offset = offset; | ||
| 214 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 215 | |||
| 216 | while (cur_offset < end_offset) { | ||
| 217 | // Get the current entry. | ||
| 218 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 219 | |||
| 220 | // Get and validate the entry's offset. | ||
| 221 | const auto cur_entry_offset = cur_entry.virt_offset; | ||
| 222 | R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA); | ||
| 223 | |||
| 224 | // Get and validate the next entry offset. | ||
| 225 | s64 next_entry_offset; | ||
| 226 | if (visitor.CanMoveNext()) { | ||
| 227 | R_TRY(visitor.MoveNext()); | ||
| 228 | next_entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 229 | R_UNLESS(table_offsets.IsInclude(next_entry_offset), | ||
| 230 | ResultUnexpectedInCompressedStorageA); | ||
| 231 | } else { | ||
| 232 | next_entry_offset = table_offsets.end_offset; | ||
| 233 | } | ||
| 234 | R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA); | ||
| 235 | |||
| 236 | // Get the offset of the entry in the data we read. | ||
| 237 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 238 | const auto data_size = (next_entry_offset - cur_entry_offset); | ||
| 239 | ASSERT(data_size > 0); | ||
| 240 | |||
| 241 | // Determine how much is left. | ||
| 242 | const auto remaining_size = end_offset - cur_offset; | ||
| 243 | const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); | ||
| 244 | ASSERT(cur_size <= size); | ||
| 245 | |||
| 246 | // Get the data storage size. | ||
| 247 | s64 storage_size = m_data_storage->GetSize(); | ||
| 248 | |||
| 249 | // Check that our read remains naively physically in bounds. | ||
| 250 | R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size, | ||
| 251 | ResultUnexpectedInCompressedStorageC); | ||
| 252 | |||
| 253 | // If we have any compression, verify that we remain physically in bounds. | ||
| 254 | if (cur_entry.compression_type != CompressionType::None) { | ||
| 255 | R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size, | ||
| 256 | ResultUnexpectedInCompressedStorageC); | ||
| 257 | } | ||
| 258 | |||
| 259 | // Check that block alignment requirements are met. | ||
| 260 | if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) { | ||
| 261 | R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment), | ||
| 262 | ResultUnexpectedInCompressedStorageA); | ||
| 263 | } | ||
| 264 | |||
| 265 | // Invoke the operator. | ||
| 266 | bool is_continuous = true; | ||
| 267 | R_TRY( | ||
| 268 | f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size)); | ||
| 269 | |||
| 270 | // If not continuous, we're done. | ||
| 271 | if (!is_continuous) { | ||
| 272 | break; | ||
| 273 | } | ||
| 274 | |||
| 275 | // Advance. | ||
| 276 | cur_offset += cur_size; | ||
| 277 | } | ||
| 278 | |||
| 279 | R_SUCCEED(); | ||
| 280 | } | ||
| 281 | |||
| 282 | public: | ||
| 283 | using ReadImplFunction = std::function<Result(void*, size_t)>; | ||
| 284 | using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>; | ||
| 285 | |||
| 286 | public: | ||
| 287 | Result Read(s64 offset, s64 size, const ReadFunction& read_func) { | ||
| 288 | // Check pre-conditions. | ||
| 289 | ASSERT(offset >= 0); | ||
| 290 | ASSERT(this->IsInitialized()); | ||
| 291 | |||
| 292 | // Succeed immediately, if we have nothing to read. | ||
| 293 | R_SUCCEED_IF(size == 0); | ||
| 294 | |||
| 295 | // Declare read lambda. | ||
| 296 | constexpr int EntriesCountMax = 0x80; | ||
| 297 | struct Entries { | ||
| 298 | CompressionType compression_type; | ||
| 299 | u32 gap_from_prev; | ||
| 300 | u32 physical_size; | ||
| 301 | u32 virtual_size; | ||
| 302 | }; | ||
| 303 | std::array<Entries, EntriesCountMax> entries; | ||
| 304 | s32 entry_count = 0; | ||
| 305 | Entry prev_entry = { | ||
| 306 | .virt_offset = -1, | ||
| 307 | .phys_offset{}, | ||
| 308 | .compression_type{}, | ||
| 309 | .phys_size{}, | ||
| 310 | }; | ||
| 311 | bool will_allocate_pooled_buffer = false; | ||
| 312 | s64 required_access_physical_offset = 0; | ||
| 313 | s64 required_access_physical_size = 0; | ||
| 314 | |||
| 315 | auto PerformRequiredRead = [&]() -> Result { | ||
| 316 | // If there are no entries, we have nothing to do. | ||
| 317 | R_SUCCEED_IF(entry_count == 0); | ||
| 318 | |||
| 319 | // Get the remaining size in a convenient form. | ||
| 320 | const size_t total_required_size = | ||
| 321 | static_cast<size_t>(required_access_physical_size); | ||
| 322 | |||
| 323 | // Perform the read based on whether we need to allocate a buffer. | ||
| 324 | if (will_allocate_pooled_buffer) { | ||
| 325 | // Allocate a pooled buffer. | ||
| 326 | PooledBuffer pooled_buffer; | ||
| 327 | if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) { | ||
| 328 | pooled_buffer.Allocate(total_required_size, m_block_size_max); | ||
| 329 | } else { | ||
| 330 | pooled_buffer.AllocateParticularlyLarge( | ||
| 331 | std::min<size_t>( | ||
| 332 | total_required_size, | ||
| 333 | PooledBuffer::GetAllocatableParticularlyLargeSizeMax()), | ||
| 334 | m_block_size_max); | ||
| 335 | } | ||
| 336 | |||
| 337 | // Read each of the entries. | ||
| 338 | for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) { | ||
| 339 | // Determine the current read size. | ||
| 340 | bool will_use_pooled_buffer = false; | ||
| 341 | const size_t cur_read_size = [&]() -> size_t { | ||
| 342 | if (const size_t target_entry_size = | ||
| 343 | static_cast<size_t>(entries[entry_idx].physical_size) + | ||
| 344 | static_cast<size_t>(entries[entry_idx].gap_from_prev); | ||
| 345 | target_entry_size <= pooled_buffer.GetSize()) { | ||
| 346 | // We'll be using the pooled buffer. | ||
| 347 | will_use_pooled_buffer = true; | ||
| 348 | |||
| 349 | // Determine how much we can read. | ||
| 350 | const size_t max_size = std::min<size_t>( | ||
| 351 | required_access_physical_size, pooled_buffer.GetSize()); | ||
| 352 | |||
| 353 | size_t read_size = 0; | ||
| 354 | for (auto n = entry_idx; n < entry_count; ++n) { | ||
| 355 | const size_t cur_entry_size = | ||
| 356 | static_cast<size_t>(entries[n].physical_size) + | ||
| 357 | static_cast<size_t>(entries[n].gap_from_prev); | ||
| 358 | if (read_size + cur_entry_size > max_size) { | ||
| 359 | break; | ||
| 360 | } | ||
| 361 | |||
| 362 | read_size += cur_entry_size; | ||
| 363 | } | ||
| 364 | |||
| 365 | return read_size; | ||
| 366 | } else { | ||
| 367 | // If we don't fit, we must be uncompressed. | ||
| 368 | ASSERT(entries[entry_idx].compression_type == | ||
| 369 | CompressionType::None); | ||
| 370 | |||
| 371 | // We can perform the whole of an uncompressed read directly. | ||
| 372 | return entries[entry_idx].virtual_size; | ||
| 373 | } | ||
| 374 | }(); | ||
| 375 | |||
| 376 | // Perform the read based on whether or not we'll use the pooled buffer. | ||
| 377 | if (will_use_pooled_buffer) { | ||
| 378 | // Read the compressed data into the pooled buffer. | ||
| 379 | auto* const buffer = pooled_buffer.GetBuffer(); | ||
| 380 | m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size, | ||
| 381 | required_access_physical_offset); | ||
| 382 | |||
| 383 | // Decompress the data. | ||
| 384 | size_t buffer_offset; | ||
| 385 | for (buffer_offset = 0; | ||
| 386 | entry_idx < entry_count && | ||
| 387 | ((static_cast<size_t>(entries[entry_idx].physical_size) + | ||
| 388 | static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 || | ||
| 389 | buffer_offset < cur_read_size); | ||
| 390 | buffer_offset += entries[entry_idx++].physical_size) { | ||
| 391 | // Advance by the relevant gap. | ||
| 392 | buffer_offset += entries[entry_idx].gap_from_prev; | ||
| 393 | |||
| 394 | const auto compression_type = entries[entry_idx].compression_type; | ||
| 395 | switch (compression_type) { | ||
| 396 | case CompressionType::None: { | ||
| 397 | // Check that we can remain within bounds. | ||
| 398 | ASSERT(buffer_offset + entries[entry_idx].virtual_size <= | ||
| 399 | cur_read_size); | ||
| 400 | |||
| 401 | // Perform no decompression. | ||
| 402 | R_TRY(read_func( | ||
| 403 | entries[entry_idx].virtual_size, | ||
| 404 | [&](void* dst, size_t dst_size) -> Result { | ||
| 405 | // Check that the size is valid. | ||
| 406 | ASSERT(dst_size == entries[entry_idx].virtual_size); | ||
| 407 | |||
| 408 | // We have no compression, so just copy the data | ||
| 409 | // out. | ||
| 410 | std::memcpy(dst, buffer + buffer_offset, | ||
| 411 | entries[entry_idx].virtual_size); | ||
| 412 | R_SUCCEED(); | ||
| 413 | })); | ||
| 414 | |||
| 415 | break; | ||
| 416 | } | ||
| 417 | case CompressionType::Zeros: { | ||
| 418 | // Check that we can remain within bounds. | ||
| 419 | ASSERT(buffer_offset <= cur_read_size); | ||
| 420 | |||
| 421 | // Zero the memory. | ||
| 422 | R_TRY(read_func( | ||
| 423 | entries[entry_idx].virtual_size, | ||
| 424 | [&](void* dst, size_t dst_size) -> Result { | ||
| 425 | // Check that the size is valid. | ||
| 426 | ASSERT(dst_size == entries[entry_idx].virtual_size); | ||
| 427 | |||
| 428 | // The data is zeroes, so zero the buffer. | ||
| 429 | std::memset(dst, 0, entries[entry_idx].virtual_size); | ||
| 430 | R_SUCCEED(); | ||
| 431 | })); | ||
| 432 | |||
| 433 | break; | ||
| 434 | } | ||
| 435 | default: { | ||
| 436 | // Check that we can remain within bounds. | ||
| 437 | ASSERT(buffer_offset + entries[entry_idx].physical_size <= | ||
| 438 | cur_read_size); | ||
| 439 | |||
| 440 | // Get the decompressor. | ||
| 441 | const auto decompressor = | ||
| 442 | this->GetDecompressor(compression_type); | ||
| 443 | R_UNLESS(decompressor != nullptr, | ||
| 444 | ResultUnexpectedInCompressedStorageB); | ||
| 445 | |||
| 446 | // Decompress the data. | ||
| 447 | R_TRY(read_func(entries[entry_idx].virtual_size, | ||
| 448 | [&](void* dst, size_t dst_size) -> Result { | ||
| 449 | // Check that the size is valid. | ||
| 450 | ASSERT(dst_size == | ||
| 451 | entries[entry_idx].virtual_size); | ||
| 452 | |||
| 453 | // Perform the decompression. | ||
| 454 | R_RETURN(decompressor( | ||
| 455 | dst, entries[entry_idx].virtual_size, | ||
| 456 | buffer + buffer_offset, | ||
| 457 | entries[entry_idx].physical_size)); | ||
| 458 | })); | ||
| 459 | |||
| 460 | break; | ||
| 461 | } | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | // Check that we processed the correct amount of data. | ||
| 466 | ASSERT(buffer_offset == cur_read_size); | ||
| 467 | } else { | ||
| 468 | // Account for the gap from the previous entry. | ||
| 469 | required_access_physical_offset += entries[entry_idx].gap_from_prev; | ||
| 470 | required_access_physical_size -= entries[entry_idx].gap_from_prev; | ||
| 471 | |||
| 472 | // We don't need the buffer (as the data is uncompressed), so just | ||
| 473 | // execute the read. | ||
| 474 | R_TRY( | ||
| 475 | read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result { | ||
| 476 | // Check that the size is valid. | ||
| 477 | ASSERT(dst_size == cur_read_size); | ||
| 478 | |||
| 479 | // Perform the read. | ||
| 480 | m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size, | ||
| 481 | required_access_physical_offset); | ||
| 482 | |||
| 483 | R_SUCCEED(); | ||
| 484 | })); | ||
| 485 | } | ||
| 486 | |||
| 487 | // Advance on. | ||
| 488 | required_access_physical_offset += cur_read_size; | ||
| 489 | required_access_physical_size -= cur_read_size; | ||
| 490 | } | ||
| 491 | |||
| 492 | // Verify that we have nothing remaining to read. | ||
| 493 | ASSERT(required_access_physical_size == 0); | ||
| 494 | |||
| 495 | R_SUCCEED(); | ||
| 496 | } else { | ||
| 497 | // We don't need a buffer, so just execute the read. | ||
| 498 | R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result { | ||
| 499 | // Check that the size is valid. | ||
| 500 | ASSERT(dst_size == total_required_size); | ||
| 501 | |||
| 502 | // Perform the read. | ||
| 503 | m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size, | ||
| 504 | required_access_physical_offset); | ||
| 505 | |||
| 506 | R_SUCCEED(); | ||
| 507 | })); | ||
| 508 | } | ||
| 509 | |||
| 510 | R_SUCCEED(); | ||
| 511 | }; | ||
| 512 | |||
| 513 | R_TRY(this->OperatePerEntry( | ||
| 514 | offset, size, | ||
| 515 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 516 | s64 data_offset, s64 read_size) -> Result { | ||
| 517 | // Determine the physical extents. | ||
| 518 | s64 physical_offset, physical_size; | ||
| 519 | if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) { | ||
| 520 | physical_offset = entry.phys_offset + data_offset; | ||
| 521 | physical_size = read_size; | ||
| 522 | } else { | ||
| 523 | physical_offset = entry.phys_offset; | ||
| 524 | physical_size = entry.GetPhysicalSize(); | ||
| 525 | } | ||
| 526 | |||
| 527 | // If we have a pending data storage operation, perform it if we have to. | ||
| 528 | const s64 required_access_physical_end = | ||
| 529 | required_access_physical_offset + required_access_physical_size; | ||
| 530 | if (required_access_physical_size > 0) { | ||
| 531 | const bool required_by_gap = | ||
| 532 | !(required_access_physical_end <= physical_offset && | ||
| 533 | physical_offset <= Common::AlignUp(required_access_physical_end, | ||
| 534 | CompressionBlockAlignment)); | ||
| 535 | const bool required_by_continuous_size = | ||
| 536 | ((physical_size + physical_offset) - required_access_physical_end) + | ||
| 537 | required_access_physical_size > | ||
| 538 | static_cast<s64>(m_continuous_reading_size_max); | ||
| 539 | const bool required_by_entry_count = entry_count == EntriesCountMax; | ||
| 540 | if (required_by_gap || required_by_continuous_size || | ||
| 541 | required_by_entry_count) { | ||
| 542 | // Check that our planned access is sane. | ||
| 543 | ASSERT(!will_allocate_pooled_buffer || | ||
| 544 | required_access_physical_size <= | ||
| 545 | static_cast<s64>(m_continuous_reading_size_max)); | ||
| 546 | |||
| 547 | // Perform the required read. | ||
| 548 | const Result rc = PerformRequiredRead(); | ||
| 549 | if (R_FAILED(rc)) { | ||
| 550 | R_THROW(rc); | ||
| 551 | } | ||
| 552 | |||
| 553 | // Reset our requirements. | ||
| 554 | prev_entry.virt_offset = -1; | ||
| 555 | required_access_physical_size = 0; | ||
| 556 | entry_count = 0; | ||
| 557 | will_allocate_pooled_buffer = false; | ||
| 558 | } | ||
| 559 | } | ||
| 560 | |||
| 561 | // Sanity check that we're within bounds on entries. | ||
| 562 | ASSERT(entry_count < EntriesCountMax); | ||
| 563 | |||
| 564 | // Determine if a buffer allocation is needed. | ||
| 565 | if (entry.compression_type != CompressionType::None || | ||
| 566 | (prev_entry.virt_offset >= 0 && | ||
| 567 | entry.virt_offset - prev_entry.virt_offset != | ||
| 568 | entry.phys_offset - prev_entry.phys_offset)) { | ||
| 569 | will_allocate_pooled_buffer = true; | ||
| 570 | } | ||
| 571 | |||
| 572 | // If we need to access the data storage, update our required access parameters. | ||
| 573 | if (CompressionTypeUtility::IsDataStorageAccessRequired( | ||
| 574 | entry.compression_type)) { | ||
| 575 | // If the data is compressed, ensure the access is sane. | ||
| 576 | if (entry.compression_type != CompressionType::None) { | ||
| 577 | R_UNLESS(data_offset == 0, ResultInvalidOffset); | ||
| 578 | R_UNLESS(virtual_data_size == read_size, ResultInvalidSize); | ||
| 579 | R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max), | ||
| 580 | ResultUnexpectedInCompressedStorageD); | ||
| 581 | } | ||
| 582 | |||
| 583 | // Update the required access parameters. | ||
| 584 | s64 gap_from_prev; | ||
| 585 | if (required_access_physical_size > 0) { | ||
| 586 | gap_from_prev = physical_offset - required_access_physical_end; | ||
| 587 | } else { | ||
| 588 | gap_from_prev = 0; | ||
| 589 | required_access_physical_offset = physical_offset; | ||
| 590 | } | ||
| 591 | required_access_physical_size += physical_size + gap_from_prev; | ||
| 592 | |||
| 593 | // Create an entry to access the data storage. | ||
| 594 | entries[entry_count++] = { | ||
| 595 | .compression_type = entry.compression_type, | ||
| 596 | .gap_from_prev = static_cast<u32>(gap_from_prev), | ||
| 597 | .physical_size = static_cast<u32>(physical_size), | ||
| 598 | .virtual_size = static_cast<u32>(read_size), | ||
| 599 | }; | ||
| 600 | } else { | ||
| 601 | // Verify that we're allowed to be operating on the non-data-storage-access | ||
| 602 | // type. | ||
| 603 | R_UNLESS(entry.compression_type == CompressionType::Zeros, | ||
| 604 | ResultUnexpectedInCompressedStorageB); | ||
| 605 | |||
| 606 | // If we have entries, create a fake entry for the zero region. | ||
| 607 | if (entry_count != 0) { | ||
| 608 | // We need to have a physical size. | ||
| 609 | R_UNLESS(entry.GetPhysicalSize() != 0, | ||
| 610 | ResultUnexpectedInCompressedStorageD); | ||
| 611 | |||
| 612 | // Create a fake entry. | ||
| 613 | entries[entry_count++] = { | ||
| 614 | .compression_type = CompressionType::Zeros, | ||
| 615 | .gap_from_prev = 0, | ||
| 616 | .physical_size = 0, | ||
| 617 | .virtual_size = static_cast<u32>(read_size), | ||
| 618 | }; | ||
| 619 | } else { | ||
| 620 | // We have no entries, so we can just perform the read. | ||
| 621 | const Result rc = | ||
| 622 | read_func(static_cast<size_t>(read_size), | ||
| 623 | [&](void* dst, size_t dst_size) -> Result { | ||
| 624 | // Check the space we should zero is correct. | ||
| 625 | ASSERT(dst_size == static_cast<size_t>(read_size)); | ||
| 626 | |||
| 627 | // Zero the memory. | ||
| 628 | std::memset(dst, 0, read_size); | ||
| 629 | R_SUCCEED(); | ||
| 630 | }); | ||
| 631 | if (R_FAILED(rc)) { | ||
| 632 | R_THROW(rc); | ||
| 633 | } | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | // Set the previous entry. | ||
| 638 | prev_entry = entry; | ||
| 639 | |||
| 640 | // We're continuous. | ||
| 641 | *out_continuous = true; | ||
| 642 | R_SUCCEED(); | ||
| 643 | })); | ||
| 644 | |||
| 645 | // If we still have a pending access, perform it. | ||
| 646 | if (required_access_physical_size != 0) { | ||
| 647 | R_TRY(PerformRequiredRead()); | ||
| 648 | } | ||
| 649 | |||
| 650 | R_SUCCEED(); | ||
| 651 | } | ||
| 652 | |||
| 653 | private: | ||
| 654 | DecompressorFunction GetDecompressor(CompressionType type) const { | ||
| 655 | // Check that we can get a decompressor for the type. | ||
| 656 | if (CompressionTypeUtility::IsUnknownType(type)) { | ||
| 657 | return nullptr; | ||
| 658 | } | ||
| 659 | |||
| 660 | // Get the decompressor. | ||
| 661 | return m_get_decompressor_function(type); | ||
| 662 | } | ||
| 663 | |||
| 664 | bool IsInitialized() const { | ||
| 665 | return m_table.IsInitialized(); | ||
| 666 | } | ||
| 667 | |||
| 668 | private: | ||
| 669 | size_t m_block_size_max; | ||
| 670 | size_t m_continuous_reading_size_max; | ||
| 671 | BucketTree m_table; | ||
| 672 | VirtualFile m_data_storage; | ||
| 673 | GetDecompressorFunction m_get_decompressor_function; | ||
| 674 | }; | ||
| 675 | |||
| 676 | class CacheManager { | ||
| 677 | YUZU_NON_COPYABLE(CacheManager); | ||
| 678 | YUZU_NON_MOVEABLE(CacheManager); | ||
| 679 | |||
| 680 | private: | ||
| 681 | struct AccessRange { | ||
| 682 | s64 virtual_offset; | ||
| 683 | s64 virtual_size; | ||
| 684 | u32 physical_size; | ||
| 685 | bool is_block_alignment_required; | ||
| 686 | |||
| 687 | s64 GetEndVirtualOffset() const { | ||
| 688 | return this->virtual_offset + this->virtual_size; | ||
| 689 | } | ||
| 690 | }; | ||
| 691 | static_assert(std::is_trivial_v<AccessRange>); | ||
| 692 | |||
| 693 | public: | ||
| 694 | CacheManager() = default; | ||
| 695 | |||
| 696 | public: | ||
| 697 | Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1, | ||
| 698 | size_t max_cache_entries) { | ||
| 699 | // Set our fields. | ||
| 700 | m_storage_size = storage_size; | ||
| 701 | |||
| 702 | R_SUCCEED(); | ||
| 703 | } | ||
| 704 | |||
| 705 | Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) { | ||
| 706 | // If we have nothing to read, succeed. | ||
| 707 | R_SUCCEED_IF(size == 0); | ||
| 708 | |||
| 709 | // Check that we have a buffer to read into. | ||
| 710 | R_UNLESS(buffer != nullptr, ResultNullptrArgument); | ||
| 711 | |||
| 712 | // Check that the read is in bounds. | ||
| 713 | R_UNLESS(offset <= m_storage_size, ResultInvalidOffset); | ||
| 714 | |||
| 715 | // Determine how much we can read. | ||
| 716 | const size_t read_size = std::min<size_t>(size, m_storage_size - offset); | ||
| 717 | |||
| 718 | // Create head/tail ranges. | ||
| 719 | AccessRange head_range = {}; | ||
| 720 | AccessRange tail_range = {}; | ||
| 721 | bool is_tail_set = false; | ||
| 722 | |||
| 723 | // Operate to determine the head range. | ||
| 724 | R_TRY(core.OperatePerEntry( | ||
| 725 | offset, 1, | ||
| 726 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 727 | s64 data_offset, s64 data_read_size) -> Result { | ||
| 728 | // Set the head range. | ||
| 729 | head_range = { | ||
| 730 | .virtual_offset = entry.virt_offset, | ||
| 731 | .virtual_size = virtual_data_size, | ||
| 732 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 733 | .is_block_alignment_required = | ||
| 734 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 735 | entry.compression_type), | ||
| 736 | }; | ||
| 737 | |||
| 738 | // If required, set the tail range. | ||
| 739 | if (static_cast<s64>(offset + read_size) <= | ||
| 740 | entry.virt_offset + virtual_data_size) { | ||
| 741 | tail_range = { | ||
| 742 | .virtual_offset = entry.virt_offset, | ||
| 743 | .virtual_size = virtual_data_size, | ||
| 744 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 745 | .is_block_alignment_required = | ||
| 746 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 747 | entry.compression_type), | ||
| 748 | }; | ||
| 749 | is_tail_set = true; | ||
| 750 | } | ||
| 751 | |||
| 752 | // We only want to determine the head range, so we're not continuous. | ||
| 753 | *out_continuous = false; | ||
| 754 | R_SUCCEED(); | ||
| 755 | })); | ||
| 756 | |||
| 757 | // If necessary, determine the tail range. | ||
| 758 | if (!is_tail_set) { | ||
| 759 | R_TRY(core.OperatePerEntry( | ||
| 760 | offset + read_size - 1, 1, | ||
| 761 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 762 | s64 data_offset, s64 data_read_size) -> Result { | ||
| 763 | // Set the tail range. | ||
| 764 | tail_range = { | ||
| 765 | .virtual_offset = entry.virt_offset, | ||
| 766 | .virtual_size = virtual_data_size, | ||
| 767 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 768 | .is_block_alignment_required = | ||
| 769 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 770 | entry.compression_type), | ||
| 771 | }; | ||
| 772 | |||
| 773 | // We only want to determine the tail range, so we're not continuous. | ||
| 774 | *out_continuous = false; | ||
| 775 | R_SUCCEED(); | ||
| 776 | })); | ||
| 777 | } | ||
| 778 | |||
| 779 | // Begin performing the accesses. | ||
| 780 | s64 cur_offset = offset; | ||
| 781 | size_t cur_size = read_size; | ||
| 782 | char* cur_dst = static_cast<char*>(buffer); | ||
| 783 | |||
| 784 | // Determine our alignment. | ||
| 785 | const bool head_unaligned = head_range.is_block_alignment_required && | ||
| 786 | (cur_offset != head_range.virtual_offset || | ||
| 787 | static_cast<s64>(cur_size) < head_range.virtual_size); | ||
| 788 | const bool tail_unaligned = [&]() -> bool { | ||
| 789 | if (tail_range.is_block_alignment_required) { | ||
| 790 | if (static_cast<s64>(cur_size + cur_offset) == | ||
| 791 | tail_range.GetEndVirtualOffset()) { | ||
| 792 | return false; | ||
| 793 | } else if (!head_unaligned) { | ||
| 794 | return true; | ||
| 795 | } else { | ||
| 796 | return head_range.GetEndVirtualOffset() < | ||
| 797 | static_cast<s64>(cur_size + cur_offset); | ||
| 798 | } | ||
| 799 | } else { | ||
| 800 | return false; | ||
| 801 | } | ||
| 802 | }(); | ||
| 803 | |||
| 804 | // Determine start/end offsets. | ||
| 805 | const s64 start_offset = | ||
| 806 | head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset; | ||
| 807 | const s64 end_offset = tail_range.is_block_alignment_required | ||
| 808 | ? tail_range.GetEndVirtualOffset() | ||
| 809 | : cur_offset + cur_size; | ||
| 810 | |||
| 811 | // Perform the read. | ||
| 812 | bool is_burst_reading = false; | ||
| 813 | R_TRY(core.Read( | ||
| 814 | start_offset, end_offset - start_offset, | ||
| 815 | [&](size_t size_buffer_required, | ||
| 816 | const CompressedStorageCore::ReadImplFunction& read_impl) -> Result { | ||
| 817 | // Determine whether we're burst reading. | ||
| 818 | const AccessRange* unaligned_range = nullptr; | ||
| 819 | if (!is_burst_reading) { | ||
| 820 | // Check whether we're using head, tail, or none as unaligned. | ||
| 821 | if (head_unaligned && head_range.virtual_offset <= cur_offset && | ||
| 822 | cur_offset < head_range.GetEndVirtualOffset()) { | ||
| 823 | unaligned_range = std::addressof(head_range); | ||
| 824 | } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset && | ||
| 825 | cur_offset < tail_range.GetEndVirtualOffset()) { | ||
| 826 | unaligned_range = std::addressof(tail_range); | ||
| 827 | } else { | ||
| 828 | is_burst_reading = true; | ||
| 829 | } | ||
| 830 | } | ||
| 831 | ASSERT((is_burst_reading ^ (unaligned_range != nullptr))); | ||
| 832 | |||
| 833 | // Perform reading by burst, or not. | ||
| 834 | if (is_burst_reading) { | ||
| 835 | // Check that the access is valid for burst reading. | ||
| 836 | ASSERT(size_buffer_required <= cur_size); | ||
| 837 | |||
| 838 | // Perform the read. | ||
| 839 | Result rc = read_impl(cur_dst, size_buffer_required); | ||
| 840 | if (R_FAILED(rc)) { | ||
| 841 | R_THROW(rc); | ||
| 842 | } | ||
| 843 | |||
| 844 | // Advance. | ||
| 845 | cur_dst += size_buffer_required; | ||
| 846 | cur_offset += size_buffer_required; | ||
| 847 | cur_size -= size_buffer_required; | ||
| 848 | |||
| 849 | // Determine whether we're going to continue burst reading. | ||
| 850 | const s64 offset_aligned = | ||
| 851 | tail_unaligned ? tail_range.virtual_offset : end_offset; | ||
| 852 | ASSERT(cur_offset <= offset_aligned); | ||
| 853 | |||
| 854 | if (offset_aligned <= cur_offset) { | ||
| 855 | is_burst_reading = false; | ||
| 856 | } | ||
| 857 | } else { | ||
| 858 | // We're not burst reading, so we have some unaligned range. | ||
| 859 | ASSERT(unaligned_range != nullptr); | ||
| 860 | |||
| 861 | // Check that the size is correct. | ||
| 862 | ASSERT(size_buffer_required == | ||
| 863 | static_cast<size_t>(unaligned_range->virtual_size)); | ||
| 864 | |||
| 865 | // Get a pooled buffer for our read. | ||
| 866 | PooledBuffer pooled_buffer; | ||
| 867 | pooled_buffer.Allocate(size_buffer_required, size_buffer_required); | ||
| 868 | |||
| 869 | // Perform read. | ||
| 870 | Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required); | ||
| 871 | if (R_FAILED(rc)) { | ||
| 872 | R_THROW(rc); | ||
| 873 | } | ||
| 874 | |||
| 875 | // Copy the data we read to the destination. | ||
| 876 | const size_t skip_size = cur_offset - unaligned_range->virtual_offset; | ||
| 877 | const size_t copy_size = std::min<size_t>( | ||
| 878 | cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset); | ||
| 879 | |||
| 880 | std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size); | ||
| 881 | |||
| 882 | // Advance. | ||
| 883 | cur_dst += copy_size; | ||
| 884 | cur_offset += copy_size; | ||
| 885 | cur_size -= copy_size; | ||
| 886 | } | ||
| 887 | |||
| 888 | R_SUCCEED(); | ||
| 889 | })); | ||
| 890 | |||
| 891 | R_SUCCEED(); | ||
| 892 | } | ||
| 893 | |||
| 894 | private: | ||
| 895 | s64 m_storage_size = 0; | ||
| 896 | }; | ||
| 897 | |||
| 898 | public: | ||
| 899 | CompressedStorage() = default; | ||
| 900 | virtual ~CompressedStorage() { | ||
| 901 | this->Finalize(); | ||
| 902 | } | ||
| 903 | |||
| 904 | Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, | ||
| 905 | s32 bktr_entry_count, size_t block_size_max, | ||
| 906 | size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor, | ||
| 907 | size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) { | ||
| 908 | // Initialize our core. | ||
| 909 | R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count, | ||
| 910 | block_size_max, continuous_reading_size_max, get_decompressor)); | ||
| 911 | |||
| 912 | // Get our core size. | ||
| 913 | s64 core_size = 0; | ||
| 914 | R_TRY(m_core.GetSize(std::addressof(core_size))); | ||
| 915 | |||
| 916 | // Initialize our cache manager. | ||
| 917 | R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries)); | ||
| 918 | |||
| 919 | R_SUCCEED(); | ||
| 920 | } | ||
| 921 | |||
| 922 | void Finalize() { | ||
| 923 | m_core.Finalize(); | ||
| 924 | } | ||
| 925 | |||
| 926 | VirtualFile GetDataStorage() { | ||
| 927 | return m_core.GetDataStorage(); | ||
| 928 | } | ||
| 929 | |||
| 930 | Result GetDataStorageSize(s64* out) { | ||
| 931 | R_RETURN(m_core.GetDataStorageSize(out)); | ||
| 932 | } | ||
| 933 | |||
| 934 | Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset, | ||
| 935 | s64 size) { | ||
| 936 | R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size)); | ||
| 937 | } | ||
| 938 | |||
| 939 | BucketTree& GetEntryTable() { | ||
| 940 | return m_core.GetEntryTable(); | ||
| 941 | } | ||
| 942 | |||
| 943 | public: | ||
| 944 | virtual size_t GetSize() const override { | ||
| 945 | s64 ret{}; | ||
| 946 | m_core.GetSize(&ret); | ||
| 947 | return ret; | ||
| 948 | } | ||
| 949 | |||
| 950 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 951 | if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) { | ||
| 952 | return size; | ||
| 953 | } else { | ||
| 954 | return 0; | ||
| 955 | } | ||
| 956 | } | ||
| 957 | |||
| 958 | private: | ||
| 959 | mutable CompressedStorageCore m_core; | ||
| 960 | mutable CacheManager m_cache_manager; | ||
| 961 | }; | ||
| 962 | |||
| 963 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_common.h b/src/core/file_sys/fssystem/fssystem_compression_common.h new file mode 100644 index 000000000..266e0a7e5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_common.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/result.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | enum class CompressionType : u8 { | ||
| 11 | None = 0, | ||
| 12 | Zeros = 1, | ||
| 13 | Two = 2, | ||
| 14 | Lz4 = 3, | ||
| 15 | Unknown = 4, | ||
| 16 | }; | ||
| 17 | |||
| 18 | using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t); | ||
| 19 | using GetDecompressorFunction = DecompressorFunction (*)(CompressionType); | ||
| 20 | |||
| 21 | constexpr s64 CompressionBlockAlignment = 0x10; | ||
| 22 | |||
| 23 | namespace CompressionTypeUtility { | ||
| 24 | |||
| 25 | constexpr bool IsBlockAlignmentRequired(CompressionType type) { | ||
| 26 | return type != CompressionType::None && type != CompressionType::Zeros; | ||
| 27 | } | ||
| 28 | |||
| 29 | constexpr bool IsDataStorageAccessRequired(CompressionType type) { | ||
| 30 | return type != CompressionType::Zeros; | ||
| 31 | } | ||
| 32 | |||
| 33 | constexpr bool IsRandomAccessible(CompressionType type) { | ||
| 34 | return type == CompressionType::None; | ||
| 35 | } | ||
| 36 | |||
| 37 | constexpr bool IsUnknownType(CompressionType type) { | ||
| 38 | return type >= CompressionType::Unknown; | ||
| 39 | } | ||
| 40 | |||
| 41 | } // namespace CompressionTypeUtility | ||
| 42 | |||
| 43 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp new file mode 100644 index 000000000..ef552cefe --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/lz4_compression.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_compression_configuration.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) { | ||
| 12 | auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size); | ||
| 13 | R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC); | ||
| 14 | R_SUCCEED(); | ||
| 15 | } | ||
| 16 | |||
| 17 | constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) { | ||
| 18 | switch (type) { | ||
| 19 | case CompressionType::Lz4: | ||
| 20 | return DecompressLz4; | ||
| 21 | default: | ||
| 22 | return nullptr; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | } // namespace | ||
| 27 | |||
| 28 | const NcaCompressionConfiguration& GetNcaCompressionConfiguration() { | ||
| 29 | static const NcaCompressionConfiguration configuration = { | ||
| 30 | .get_decompressor = GetNcaDecompressorFunction, | ||
| 31 | }; | ||
| 32 | |||
| 33 | return configuration; | ||
| 34 | } | ||
| 35 | |||
| 36 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.h b/src/core/file_sys/fssystem/fssystem_compression_configuration.h new file mode 100644 index 000000000..ec9b48e9a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | const NcaCompressionConfiguration& GetNcaCompressionConfiguration(); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp new file mode 100644 index 000000000..a4f0cde28 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/crypto/aes_util.h" | ||
| 5 | #include "core/crypto/key_manager.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_crypto_configuration.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size, | ||
| 13 | s32 key_type) { | ||
| 14 | if (key_type == static_cast<s32>(KeyType::ZeroKey)) { | ||
| 15 | std::memset(dst_key, 0, dst_key_size); | ||
| 16 | return; | ||
| 17 | } | ||
| 18 | |||
| 19 | if (key_type == static_cast<s32>(KeyType::InvalidKey) || | ||
| 20 | key_type < static_cast<s32>(KeyType::ZeroKey) || | ||
| 21 | key_type >= static_cast<s32>(KeyType::NcaExternalKey)) { | ||
| 22 | std::memset(dst_key, 0xFF, dst_key_size); | ||
| 23 | return; | ||
| 24 | } | ||
| 25 | |||
| 26 | const auto& instance = Core::Crypto::KeyManager::Instance(); | ||
| 27 | |||
| 28 | if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) || | ||
| 29 | key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) { | ||
| 30 | const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type; | ||
| 31 | const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header); | ||
| 32 | std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2)); | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | |||
| 36 | const s32 key_generation = | ||
| 37 | std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1; | ||
| 38 | const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount; | ||
| 39 | |||
| 40 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 41 | instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index), | ||
| 42 | Core::Crypto::Mode::ECB); | ||
| 43 | cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size, | ||
| 44 | reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt); | ||
| 45 | } | ||
| 46 | |||
| 47 | } // namespace | ||
| 48 | |||
| 49 | const NcaCryptoConfiguration& GetCryptoConfiguration() { | ||
| 50 | static const NcaCryptoConfiguration configuration = { | ||
| 51 | .header_1_sign_key_moduli{}, | ||
| 52 | .header_1_sign_key_public_exponent{}, | ||
| 53 | .key_area_encryption_key_source{}, | ||
| 54 | .header_encryption_key_source{}, | ||
| 55 | .header_encrypted_encryption_keys{}, | ||
| 56 | .generate_key = GenerateKey, | ||
| 57 | .verify_sign1{}, | ||
| 58 | .is_plaintext_header_available{}, | ||
| 59 | .is_available_sw_key{}, | ||
| 60 | }; | ||
| 61 | |||
| 62 | return configuration; | ||
| 63 | } | ||
| 64 | |||
| 65 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.h b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h new file mode 100644 index 000000000..7fd9c5a8d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | const NcaCryptoConfiguration& GetCryptoConfiguration(); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp new file mode 100644 index 000000000..4a75b5308 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp | |||
| @@ -0,0 +1,127 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 5 | #include "core/file_sys/vfs_offset.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage() | ||
| 10 | : m_data_size(-1) { | ||
| 11 | for (size_t i = 0; i < MaxLayers - 1; i++) { | ||
| 12 | m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>(); | ||
| 13 | } | ||
| 14 | } | ||
| 15 | |||
| 16 | Result HierarchicalIntegrityVerificationStorage::Initialize( | ||
| 17 | const HierarchicalIntegrityVerificationInformation& info, | ||
| 18 | HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries, | ||
| 19 | s8 buffer_level) { | ||
| 20 | // Validate preconditions. | ||
| 21 | ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount); | ||
| 22 | |||
| 23 | // Set member variables. | ||
| 24 | m_max_layers = info.max_layers; | ||
| 25 | |||
| 26 | // Initialize the top level verification storage. | ||
| 27 | m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage], | ||
| 28 | storage[HierarchicalStorageInformation::Layer1Storage], | ||
| 29 | static_cast<s64>(1) << info.info[0].block_order, HashSize, | ||
| 30 | false); | ||
| 31 | |||
| 32 | // Ensure we don't leak state if further initialization goes wrong. | ||
| 33 | ON_RESULT_FAILURE { | ||
| 34 | m_verify_storages[0]->Finalize(); | ||
| 35 | m_data_size = -1; | ||
| 36 | }; | ||
| 37 | |||
| 38 | // Initialize the top level buffer storage. | ||
| 39 | m_buffer_storages[0] = m_verify_storages[0]; | ||
| 40 | R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 41 | |||
| 42 | // Prepare to initialize the level storages. | ||
| 43 | s32 level = 0; | ||
| 44 | |||
| 45 | // Ensure we don't leak state if further initialization goes wrong. | ||
| 46 | ON_RESULT_FAILURE_2 { | ||
| 47 | m_verify_storages[level + 1]->Finalize(); | ||
| 48 | for (; level > 0; --level) { | ||
| 49 | m_buffer_storages[level].reset(); | ||
| 50 | m_verify_storages[level]->Finalize(); | ||
| 51 | } | ||
| 52 | }; | ||
| 53 | |||
| 54 | // Initialize the level storages. | ||
| 55 | for (; level < m_max_layers - 3; ++level) { | ||
| 56 | // Initialize the verification storage. | ||
| 57 | auto buffer_storage = | ||
| 58 | std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); | ||
| 59 | m_verify_storages[level + 1]->Initialize( | ||
| 60 | std::move(buffer_storage), storage[level + 2], | ||
| 61 | static_cast<s64>(1) << info.info[level + 1].block_order, | ||
| 62 | static_cast<s64>(1) << info.info[level].block_order, false); | ||
| 63 | |||
| 64 | // Initialize the buffer storage. | ||
| 65 | m_buffer_storages[level + 1] = m_verify_storages[level + 1]; | ||
| 66 | R_UNLESS(m_buffer_storages[level + 1] != nullptr, | ||
| 67 | ResultAllocationMemoryFailedAllocateShared); | ||
| 68 | } | ||
| 69 | |||
| 70 | // Initialize the final level storage. | ||
| 71 | { | ||
| 72 | // Initialize the verification storage. | ||
| 73 | auto buffer_storage = | ||
| 74 | std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); | ||
| 75 | m_verify_storages[level + 1]->Initialize( | ||
| 76 | std::move(buffer_storage), storage[level + 2], | ||
| 77 | static_cast<s64>(1) << info.info[level + 1].block_order, | ||
| 78 | static_cast<s64>(1) << info.info[level].block_order, true); | ||
| 79 | |||
| 80 | // Initialize the buffer storage. | ||
| 81 | m_buffer_storages[level + 1] = m_verify_storages[level + 1]; | ||
| 82 | R_UNLESS(m_buffer_storages[level + 1] != nullptr, | ||
| 83 | ResultAllocationMemoryFailedAllocateShared); | ||
| 84 | } | ||
| 85 | |||
| 86 | // Set the data size. | ||
| 87 | m_data_size = info.info[level + 1].size; | ||
| 88 | |||
| 89 | // We succeeded. | ||
| 90 | R_SUCCEED(); | ||
| 91 | } | ||
| 92 | |||
| 93 | void HierarchicalIntegrityVerificationStorage::Finalize() { | ||
| 94 | if (m_data_size >= 0) { | ||
| 95 | m_data_size = 0; | ||
| 96 | |||
| 97 | for (s32 level = m_max_layers - 2; level >= 0; --level) { | ||
| 98 | m_buffer_storages[level].reset(); | ||
| 99 | m_verify_storages[level]->Finalize(); | ||
| 100 | } | ||
| 101 | |||
| 102 | m_data_size = -1; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size, | ||
| 107 | size_t offset) const { | ||
| 108 | // Validate preconditions. | ||
| 109 | ASSERT(m_data_size >= 0); | ||
| 110 | |||
| 111 | // Succeed if zero-size. | ||
| 112 | if (size == 0) { | ||
| 113 | return size; | ||
| 114 | } | ||
| 115 | |||
| 116 | // Validate arguments. | ||
| 117 | ASSERT(buffer != nullptr); | ||
| 118 | |||
| 119 | // Read the data. | ||
| 120 | return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset); | ||
| 121 | } | ||
| 122 | |||
| 123 | size_t HierarchicalIntegrityVerificationStorage::GetSize() const { | ||
| 124 | return m_data_size; | ||
| 125 | } | ||
| 126 | |||
| 127 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h new file mode 100644 index 000000000..5cf697efe --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" | ||
| 11 | #include "core/file_sys/vfs_offset.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | struct HierarchicalIntegrityVerificationLevelInformation { | ||
| 16 | Int64 offset; | ||
| 17 | Int64 size; | ||
| 18 | s32 block_order; | ||
| 19 | std::array<u8, 4> reserved; | ||
| 20 | }; | ||
| 21 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>); | ||
| 22 | static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18); | ||
| 23 | static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4); | ||
| 24 | |||
| 25 | struct HierarchicalIntegrityVerificationInformation { | ||
| 26 | u32 max_layers; | ||
| 27 | std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info; | ||
| 28 | HashSalt seed; | ||
| 29 | |||
| 30 | s64 GetLayeredHashSize() const { | ||
| 31 | return this->info[this->max_layers - 2].offset; | ||
| 32 | } | ||
| 33 | |||
| 34 | s64 GetDataOffset() const { | ||
| 35 | return this->info[this->max_layers - 2].offset; | ||
| 36 | } | ||
| 37 | |||
| 38 | s64 GetDataSize() const { | ||
| 39 | return this->info[this->max_layers - 2].size; | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>); | ||
| 43 | |||
| 44 | struct HierarchicalIntegrityVerificationMetaInformation { | ||
| 45 | u32 magic; | ||
| 46 | u32 version; | ||
| 47 | u32 master_hash_size; | ||
| 48 | HierarchicalIntegrityVerificationInformation level_hash_info; | ||
| 49 | }; | ||
| 50 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>); | ||
| 51 | |||
| 52 | struct HierarchicalIntegrityVerificationSizeSet { | ||
| 53 | s64 control_size; | ||
| 54 | s64 master_hash_size; | ||
| 55 | std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes; | ||
| 56 | }; | ||
| 57 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>); | ||
| 58 | |||
| 59 | class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage { | ||
| 60 | YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage); | ||
| 61 | YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage); | ||
| 62 | |||
| 63 | public: | ||
| 64 | using GenerateRandomFunction = void (*)(void* dst, size_t size); | ||
| 65 | |||
| 66 | class HierarchicalStorageInformation { | ||
| 67 | public: | ||
| 68 | enum { | ||
| 69 | MasterStorage = 0, | ||
| 70 | Layer1Storage = 1, | ||
| 71 | Layer2Storage = 2, | ||
| 72 | Layer3Storage = 3, | ||
| 73 | Layer4Storage = 4, | ||
| 74 | Layer5Storage = 5, | ||
| 75 | DataStorage = 6, | ||
| 76 | }; | ||
| 77 | |||
| 78 | private: | ||
| 79 | std::array<VirtualFile, DataStorage + 1> m_storages; | ||
| 80 | |||
| 81 | public: | ||
| 82 | void SetMasterHashStorage(VirtualFile s) { | ||
| 83 | m_storages[MasterStorage] = s; | ||
| 84 | } | ||
| 85 | void SetLayer1HashStorage(VirtualFile s) { | ||
| 86 | m_storages[Layer1Storage] = s; | ||
| 87 | } | ||
| 88 | void SetLayer2HashStorage(VirtualFile s) { | ||
| 89 | m_storages[Layer2Storage] = s; | ||
| 90 | } | ||
| 91 | void SetLayer3HashStorage(VirtualFile s) { | ||
| 92 | m_storages[Layer3Storage] = s; | ||
| 93 | } | ||
| 94 | void SetLayer4HashStorage(VirtualFile s) { | ||
| 95 | m_storages[Layer4Storage] = s; | ||
| 96 | } | ||
| 97 | void SetLayer5HashStorage(VirtualFile s) { | ||
| 98 | m_storages[Layer5Storage] = s; | ||
| 99 | } | ||
| 100 | void SetDataStorage(VirtualFile s) { | ||
| 101 | m_storages[DataStorage] = s; | ||
| 102 | } | ||
| 103 | |||
| 104 | VirtualFile& operator[](s32 index) { | ||
| 105 | ASSERT(MasterStorage <= index && index <= DataStorage); | ||
| 106 | return m_storages[index]; | ||
| 107 | } | ||
| 108 | }; | ||
| 109 | |||
| 110 | public: | ||
| 111 | HierarchicalIntegrityVerificationStorage(); | ||
| 112 | virtual ~HierarchicalIntegrityVerificationStorage() override { | ||
| 113 | this->Finalize(); | ||
| 114 | } | ||
| 115 | |||
| 116 | Result Initialize(const HierarchicalIntegrityVerificationInformation& info, | ||
| 117 | HierarchicalStorageInformation storage, int max_data_cache_entries, | ||
| 118 | int max_hash_cache_entries, s8 buffer_level); | ||
| 119 | void Finalize(); | ||
| 120 | |||
| 121 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 122 | virtual size_t GetSize() const override; | ||
| 123 | |||
| 124 | bool IsInitialized() const { | ||
| 125 | return m_data_size >= 0; | ||
| 126 | } | ||
| 127 | |||
| 128 | s64 GetL1HashVerificationBlockSize() const { | ||
| 129 | return m_verify_storages[m_max_layers - 2]->GetBlockSize(); | ||
| 130 | } | ||
| 131 | |||
| 132 | VirtualFile GetL1HashStorage() { | ||
| 133 | return std::make_shared<OffsetVfsFile>( | ||
| 134 | m_buffer_storages[m_max_layers - 3], | ||
| 135 | Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0); | ||
| 136 | } | ||
| 137 | |||
| 138 | public: | ||
| 139 | static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) { | ||
| 140 | return static_cast<s8>(16 + max_layers - 2); | ||
| 141 | } | ||
| 142 | |||
| 143 | protected: | ||
| 144 | static constexpr s64 HashSize = 256 / 8; | ||
| 145 | static constexpr size_t MaxLayers = IntegrityMaxLayerCount; | ||
| 146 | |||
| 147 | private: | ||
| 148 | static GenerateRandomFunction s_generate_random; | ||
| 149 | |||
| 150 | static void SetGenerateRandomFunction(GenerateRandomFunction func) { | ||
| 151 | s_generate_random = func; | ||
| 152 | } | ||
| 153 | |||
| 154 | private: | ||
| 155 | friend struct HierarchicalIntegrityVerificationMetaInformation; | ||
| 156 | |||
| 157 | private: | ||
| 158 | std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages; | ||
| 159 | std::array<VirtualFile, MaxLayers - 1> m_buffer_storages; | ||
| 160 | s64 m_data_size; | ||
| 161 | s32 m_max_layers; | ||
| 162 | }; | ||
| 163 | |||
| 164 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp new file mode 100644 index 000000000..caea0b8f8 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/scope_exit.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | s32 Log2(s32 value) { | ||
| 13 | ASSERT(value > 0); | ||
| 14 | ASSERT(Common::IsPowerOfTwo(value)); | ||
| 15 | |||
| 16 | s32 log = 0; | ||
| 17 | while ((value >>= 1) > 0) { | ||
| 18 | ++log; | ||
| 19 | } | ||
| 20 | return log; | ||
| 21 | } | ||
| 22 | |||
| 23 | } // namespace | ||
| 24 | |||
| 25 | Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count, | ||
| 26 | size_t htbs, void* hash_buf, size_t hash_buf_size) { | ||
| 27 | // Validate preconditions. | ||
| 28 | ASSERT(layer_count == LayerCount); | ||
| 29 | ASSERT(Common::IsPowerOfTwo(htbs)); | ||
| 30 | ASSERT(hash_buf != nullptr); | ||
| 31 | |||
| 32 | // Set size tracking members. | ||
| 33 | m_hash_target_block_size = static_cast<s32>(htbs); | ||
| 34 | m_log_size_ratio = Log2(m_hash_target_block_size / HashSize); | ||
| 35 | |||
| 36 | // Get the base storage size. | ||
| 37 | m_base_storage_size = base_storages[2]->GetSize(); | ||
| 38 | { | ||
| 39 | auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; }); | ||
| 40 | R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize) | ||
| 41 | << m_log_size_ratio << m_log_size_ratio, | ||
| 42 | ResultHierarchicalSha256BaseStorageTooLarge); | ||
| 43 | size_guard.Cancel(); | ||
| 44 | } | ||
| 45 | |||
| 46 | // Set hash buffer tracking members. | ||
| 47 | m_base_storage = base_storages[2]; | ||
| 48 | m_hash_buffer = static_cast<char*>(hash_buf); | ||
| 49 | m_hash_buffer_size = hash_buf_size; | ||
| 50 | |||
| 51 | // Read the master hash. | ||
| 52 | std::array<u8, HashSize> master_hash{}; | ||
| 53 | base_storages[0]->ReadObject(std::addressof(master_hash)); | ||
| 54 | |||
| 55 | // Read and validate the data being hashed. | ||
| 56 | s64 hash_storage_size = base_storages[1]->GetSize(); | ||
| 57 | ASSERT(Common::IsAligned(hash_storage_size, HashSize)); | ||
| 58 | ASSERT(hash_storage_size <= m_hash_target_block_size); | ||
| 59 | ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size)); | ||
| 60 | |||
| 61 | base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer), | ||
| 62 | static_cast<size_t>(hash_storage_size), 0); | ||
| 63 | |||
| 64 | R_SUCCEED(); | ||
| 65 | } | ||
| 66 | |||
| 67 | size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 68 | // Succeed if zero-size. | ||
| 69 | if (size == 0) { | ||
| 70 | return size; | ||
| 71 | } | ||
| 72 | |||
| 73 | // Validate that we have a buffer to read into. | ||
| 74 | ASSERT(buffer != nullptr); | ||
| 75 | |||
| 76 | // Read the data. | ||
| 77 | return m_base_storage->Read(buffer, size, offset); | ||
| 78 | } | ||
| 79 | |||
| 80 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h new file mode 100644 index 000000000..18df400af --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "core/file_sys/errors.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/vfs.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class HierarchicalSha256Storage : public IReadOnlyStorage { | ||
| 15 | YUZU_NON_COPYABLE(HierarchicalSha256Storage); | ||
| 16 | YUZU_NON_MOVEABLE(HierarchicalSha256Storage); | ||
| 17 | |||
| 18 | public: | ||
| 19 | static constexpr s32 LayerCount = 3; | ||
| 20 | static constexpr size_t HashSize = 256 / 8; | ||
| 21 | |||
| 22 | public: | ||
| 23 | HierarchicalSha256Storage() : m_mutex() {} | ||
| 24 | |||
| 25 | Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf, | ||
| 26 | size_t hash_buf_size); | ||
| 27 | |||
| 28 | virtual size_t GetSize() const override { | ||
| 29 | return m_base_storage->GetSize(); | ||
| 30 | } | ||
| 31 | |||
| 32 | virtual size_t Read(u8* buffer, size_t length, size_t offset) const override; | ||
| 33 | |||
| 34 | private: | ||
| 35 | VirtualFile m_base_storage; | ||
| 36 | s64 m_base_storage_size; | ||
| 37 | char* m_hash_buffer; | ||
| 38 | size_t m_hash_buffer_size; | ||
| 39 | s32 m_hash_target_block_size; | ||
| 40 | s32 m_log_size_ratio; | ||
| 41 | std::mutex m_mutex; | ||
| 42 | }; | ||
| 43 | |||
| 44 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp new file mode 100644 index 000000000..7544e70b2 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp | |||
| @@ -0,0 +1,119 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/errors.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | Result IndirectStorage::Initialize(VirtualFile table_storage) { | ||
| 10 | // Read and verify the bucket tree header. | ||
| 11 | BucketTree::Header header; | ||
| 12 | table_storage->ReadObject(std::addressof(header)); | ||
| 13 | R_TRY(header.Verify()); | ||
| 14 | |||
| 15 | // Determine extents. | ||
| 16 | const auto node_storage_size = QueryNodeStorageSize(header.entry_count); | ||
| 17 | const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); | ||
| 18 | const auto node_storage_offset = QueryHeaderStorageSize(); | ||
| 19 | const auto entry_storage_offset = node_storage_offset + node_storage_size; | ||
| 20 | |||
| 21 | // Initialize. | ||
| 22 | R_RETURN(this->Initialize( | ||
| 23 | std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), | ||
| 24 | std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), | ||
| 25 | header.entry_count)); | ||
| 26 | } | ||
| 27 | |||
| 28 | void IndirectStorage::Finalize() { | ||
| 29 | if (this->IsInitialized()) { | ||
| 30 | m_table.Finalize(); | ||
| 31 | for (auto i = 0; i < StorageCount; i++) { | ||
| 32 | m_data_storage[i] = VirtualFile(); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, | ||
| 38 | s64 offset, s64 size) { | ||
| 39 | // Validate pre-conditions. | ||
| 40 | ASSERT(offset >= 0); | ||
| 41 | ASSERT(size >= 0); | ||
| 42 | ASSERT(this->IsInitialized()); | ||
| 43 | |||
| 44 | // Clear the out count. | ||
| 45 | R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); | ||
| 46 | *out_entry_count = 0; | ||
| 47 | |||
| 48 | // Succeed if there's no range. | ||
| 49 | R_SUCCEED_IF(size == 0); | ||
| 50 | |||
| 51 | // If we have an output array, we need it to be non-null. | ||
| 52 | R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); | ||
| 53 | |||
| 54 | // Check that our range is valid. | ||
| 55 | BucketTree::Offsets table_offsets; | ||
| 56 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 57 | |||
| 58 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 59 | |||
| 60 | // Find the offset in our tree. | ||
| 61 | BucketTree::Visitor visitor; | ||
| 62 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 63 | { | ||
| 64 | const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 65 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 66 | ResultInvalidIndirectEntryOffset); | ||
| 67 | } | ||
| 68 | |||
| 69 | // Prepare to loop over entries. | ||
| 70 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 71 | s32 count = 0; | ||
| 72 | |||
| 73 | auto cur_entry = *visitor.Get<Entry>(); | ||
| 74 | while (cur_entry.GetVirtualOffset() < end_offset) { | ||
| 75 | // Try to write the entry to the out list. | ||
| 76 | if (entry_count != 0) { | ||
| 77 | if (count >= entry_count) { | ||
| 78 | break; | ||
| 79 | } | ||
| 80 | std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); | ||
| 81 | } | ||
| 82 | |||
| 83 | count++; | ||
| 84 | |||
| 85 | // Advance. | ||
| 86 | if (visitor.CanMoveNext()) { | ||
| 87 | R_TRY(visitor.MoveNext()); | ||
| 88 | cur_entry = *visitor.Get<Entry>(); | ||
| 89 | } else { | ||
| 90 | break; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | // Write the output count. | ||
| 95 | *out_entry_count = count; | ||
| 96 | R_SUCCEED(); | ||
| 97 | } | ||
| 98 | |||
| 99 | size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 100 | // Validate pre-conditions. | ||
| 101 | ASSERT(this->IsInitialized()); | ||
| 102 | ASSERT(buffer != nullptr); | ||
| 103 | |||
| 104 | // Succeed if there's nothing to read. | ||
| 105 | if (size == 0) { | ||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | |||
| 109 | const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>( | ||
| 110 | offset, size, | ||
| 111 | [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { | ||
| 112 | storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), | ||
| 113 | static_cast<size_t>(cur_size), data_offset); | ||
| 114 | R_SUCCEED(); | ||
| 115 | }); | ||
| 116 | |||
| 117 | return size; | ||
| 118 | } | ||
| 119 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h new file mode 100644 index 000000000..7854335bf --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h | |||
| @@ -0,0 +1,294 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h" | ||
| 10 | #include "core/file_sys/vfs.h" | ||
| 11 | #include "core/file_sys/vfs_offset.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | class IndirectStorage : public IReadOnlyStorage { | ||
| 16 | YUZU_NON_COPYABLE(IndirectStorage); | ||
| 17 | YUZU_NON_MOVEABLE(IndirectStorage); | ||
| 18 | |||
| 19 | public: | ||
| 20 | static constexpr s32 StorageCount = 2; | ||
| 21 | static constexpr size_t NodeSize = 16_KiB; | ||
| 22 | |||
| 23 | struct Entry { | ||
| 24 | std::array<u8, sizeof(s64)> virt_offset; | ||
| 25 | std::array<u8, sizeof(s64)> phys_offset; | ||
| 26 | s32 storage_index; | ||
| 27 | |||
| 28 | void SetVirtualOffset(const s64& ofs) { | ||
| 29 | std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64)); | ||
| 30 | } | ||
| 31 | |||
| 32 | s64 GetVirtualOffset() const { | ||
| 33 | s64 offset; | ||
| 34 | std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64)); | ||
| 35 | return offset; | ||
| 36 | } | ||
| 37 | |||
| 38 | void SetPhysicalOffset(const s64& ofs) { | ||
| 39 | std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64)); | ||
| 40 | } | ||
| 41 | |||
| 42 | s64 GetPhysicalOffset() const { | ||
| 43 | s64 offset; | ||
| 44 | std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64)); | ||
| 45 | return offset; | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | static_assert(std::is_trivial_v<Entry>); | ||
| 49 | static_assert(sizeof(Entry) == 0x14); | ||
| 50 | |||
| 51 | struct EntryData { | ||
| 52 | s64 virt_offset; | ||
| 53 | s64 phys_offset; | ||
| 54 | s32 storage_index; | ||
| 55 | |||
| 56 | void Set(const Entry& entry) { | ||
| 57 | this->virt_offset = entry.GetVirtualOffset(); | ||
| 58 | this->phys_offset = entry.GetPhysicalOffset(); | ||
| 59 | this->storage_index = entry.storage_index; | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | static_assert(std::is_trivial_v<EntryData>); | ||
| 63 | |||
| 64 | public: | ||
| 65 | IndirectStorage() : m_table(), m_data_storage() {} | ||
| 66 | virtual ~IndirectStorage() { | ||
| 67 | this->Finalize(); | ||
| 68 | } | ||
| 69 | |||
| 70 | Result Initialize(VirtualFile table_storage); | ||
| 71 | void Finalize(); | ||
| 72 | |||
| 73 | bool IsInitialized() const { | ||
| 74 | return m_table.IsInitialized(); | ||
| 75 | } | ||
| 76 | |||
| 77 | Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) { | ||
| 78 | R_RETURN( | ||
| 79 | m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); | ||
| 80 | } | ||
| 81 | |||
| 82 | void SetStorage(s32 idx, VirtualFile storage) { | ||
| 83 | ASSERT(0 <= idx && idx < StorageCount); | ||
| 84 | m_data_storage[idx] = storage; | ||
| 85 | } | ||
| 86 | |||
| 87 | template <typename T> | ||
| 88 | void SetStorage(s32 idx, T storage, s64 offset, s64 size) { | ||
| 89 | ASSERT(0 <= idx && idx < StorageCount); | ||
| 90 | m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset); | ||
| 91 | } | ||
| 92 | |||
| 93 | Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, | ||
| 94 | s64 size); | ||
| 95 | |||
| 96 | virtual size_t GetSize() const override { | ||
| 97 | BucketTree::Offsets offsets{}; | ||
| 98 | m_table.GetOffsets(std::addressof(offsets)); | ||
| 99 | |||
| 100 | return offsets.end_offset; | ||
| 101 | } | ||
| 102 | |||
| 103 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 104 | |||
| 105 | public: | ||
| 106 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 107 | return BucketTree::QueryHeaderStorageSize(); | ||
| 108 | } | ||
| 109 | |||
| 110 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 111 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 112 | } | ||
| 113 | |||
| 114 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 115 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 116 | } | ||
| 117 | |||
| 118 | protected: | ||
| 119 | BucketTree& GetEntryTable() { | ||
| 120 | return m_table; | ||
| 121 | } | ||
| 122 | |||
| 123 | VirtualFile& GetDataStorage(s32 index) { | ||
| 124 | ASSERT(0 <= index && index < StorageCount); | ||
| 125 | return m_data_storage[index]; | ||
| 126 | } | ||
| 127 | |||
| 128 | template <bool ContinuousCheck, bool RangeCheck, typename F> | ||
| 129 | Result OperatePerEntry(s64 offset, s64 size, F func); | ||
| 130 | |||
| 131 | private: | ||
| 132 | struct ContinuousReadingEntry { | ||
| 133 | static constexpr size_t FragmentSizeMax = 4_KiB; | ||
| 134 | |||
| 135 | IndirectStorage::Entry entry; | ||
| 136 | |||
| 137 | s64 GetVirtualOffset() const { | ||
| 138 | return this->entry.GetVirtualOffset(); | ||
| 139 | } | ||
| 140 | |||
| 141 | s64 GetPhysicalOffset() const { | ||
| 142 | return this->entry.GetPhysicalOffset(); | ||
| 143 | } | ||
| 144 | |||
| 145 | bool IsFragment() const { | ||
| 146 | return this->entry.storage_index != 0; | ||
| 147 | } | ||
| 148 | }; | ||
| 149 | static_assert(std::is_trivial_v<ContinuousReadingEntry>); | ||
| 150 | |||
| 151 | private: | ||
| 152 | mutable BucketTree m_table; | ||
| 153 | std::array<VirtualFile, StorageCount> m_data_storage; | ||
| 154 | }; | ||
| 155 | |||
| 156 | template <bool ContinuousCheck, bool RangeCheck, typename F> | ||
| 157 | Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) { | ||
| 158 | // Validate preconditions. | ||
| 159 | ASSERT(offset >= 0); | ||
| 160 | ASSERT(size >= 0); | ||
| 161 | ASSERT(this->IsInitialized()); | ||
| 162 | |||
| 163 | // Succeed if there's nothing to operate on. | ||
| 164 | R_SUCCEED_IF(size == 0); | ||
| 165 | |||
| 166 | // Get the table offsets. | ||
| 167 | BucketTree::Offsets table_offsets; | ||
| 168 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 169 | |||
| 170 | // Validate arguments. | ||
| 171 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 172 | |||
| 173 | // Find the offset in our tree. | ||
| 174 | BucketTree::Visitor visitor; | ||
| 175 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 176 | { | ||
| 177 | const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 178 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 179 | ResultInvalidIndirectEntryOffset); | ||
| 180 | } | ||
| 181 | |||
| 182 | // Prepare to operate in chunks. | ||
| 183 | auto cur_offset = offset; | ||
| 184 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 185 | BucketTree::ContinuousReadingInfo cr_info; | ||
| 186 | |||
| 187 | while (cur_offset < end_offset) { | ||
| 188 | // Get the current entry. | ||
| 189 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 190 | |||
| 191 | // Get and validate the entry's offset. | ||
| 192 | const auto cur_entry_offset = cur_entry.GetVirtualOffset(); | ||
| 193 | R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); | ||
| 194 | |||
| 195 | // Validate the storage index. | ||
| 196 | R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount, | ||
| 197 | ResultInvalidIndirectEntryStorageIndex); | ||
| 198 | |||
| 199 | // If we need to check the continuous info, do so. | ||
| 200 | if constexpr (ContinuousCheck) { | ||
| 201 | // Scan, if we need to. | ||
| 202 | if (cr_info.CheckNeedScan()) { | ||
| 203 | R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>( | ||
| 204 | std::addressof(cr_info), cur_offset, | ||
| 205 | static_cast<size_t>(end_offset - cur_offset))); | ||
| 206 | } | ||
| 207 | |||
| 208 | // Process a base storage entry. | ||
| 209 | if (cr_info.CanDo()) { | ||
| 210 | // Ensure that we can process. | ||
| 211 | R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex); | ||
| 212 | |||
| 213 | // Ensure that we remain within range. | ||
| 214 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 215 | const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); | ||
| 216 | const auto cur_size = static_cast<s64>(cr_info.GetReadSize()); | ||
| 217 | |||
| 218 | // If we should, verify the range. | ||
| 219 | if constexpr (RangeCheck) { | ||
| 220 | // Get the current data storage's size. | ||
| 221 | s64 cur_data_storage_size = m_data_storage[0]->GetSize(); | ||
| 222 | |||
| 223 | R_UNLESS(0 <= cur_entry_phys_offset && | ||
| 224 | cur_entry_phys_offset <= cur_data_storage_size, | ||
| 225 | ResultInvalidIndirectEntryOffset); | ||
| 226 | R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= | ||
| 227 | cur_data_storage_size, | ||
| 228 | ResultInvalidIndirectStorageSize); | ||
| 229 | } | ||
| 230 | |||
| 231 | // Operate. | ||
| 232 | R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset, | ||
| 233 | cur_size)); | ||
| 234 | |||
| 235 | // Mark as done. | ||
| 236 | cr_info.Done(); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | // Get and validate the next entry offset. | ||
| 241 | s64 next_entry_offset; | ||
| 242 | if (visitor.CanMoveNext()) { | ||
| 243 | R_TRY(visitor.MoveNext()); | ||
| 244 | next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 245 | R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); | ||
| 246 | } else { | ||
| 247 | next_entry_offset = table_offsets.end_offset; | ||
| 248 | } | ||
| 249 | R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); | ||
| 250 | |||
| 251 | // Get the offset of the entry in the data we read. | ||
| 252 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 253 | const auto data_size = (next_entry_offset - cur_entry_offset); | ||
| 254 | ASSERT(data_size > 0); | ||
| 255 | |||
| 256 | // Determine how much is left. | ||
| 257 | const auto remaining_size = end_offset - cur_offset; | ||
| 258 | const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); | ||
| 259 | ASSERT(cur_size <= size); | ||
| 260 | |||
| 261 | // Operate, if we need to. | ||
| 262 | bool needs_operate; | ||
| 263 | if constexpr (!ContinuousCheck) { | ||
| 264 | needs_operate = true; | ||
| 265 | } else { | ||
| 266 | needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0; | ||
| 267 | } | ||
| 268 | |||
| 269 | if (needs_operate) { | ||
| 270 | const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); | ||
| 271 | |||
| 272 | if constexpr (RangeCheck) { | ||
| 273 | // Get the current data storage's size. | ||
| 274 | s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize(); | ||
| 275 | |||
| 276 | // Ensure that we remain within range. | ||
| 277 | R_UNLESS(0 <= cur_entry_phys_offset && | ||
| 278 | cur_entry_phys_offset <= cur_data_storage_size, | ||
| 279 | ResultIndirectStorageCorrupted); | ||
| 280 | R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size, | ||
| 281 | ResultIndirectStorageCorrupted); | ||
| 282 | } | ||
| 283 | |||
| 284 | R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset, | ||
| 285 | cur_offset, cur_size)); | ||
| 286 | } | ||
| 287 | |||
| 288 | cur_offset += cur_size; | ||
| 289 | } | ||
| 290 | |||
| 291 | R_SUCCEED(); | ||
| 292 | } | ||
| 293 | |||
| 294 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp new file mode 100644 index 000000000..2c3da230c --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | Result IntegrityRomFsStorage::Initialize( | ||
| 9 | HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, | ||
| 10 | HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, | ||
| 11 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { | ||
| 12 | // Set master hash. | ||
| 13 | m_master_hash = master_hash; | ||
| 14 | m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value); | ||
| 15 | R_UNLESS(m_master_hash_storage != nullptr, | ||
| 16 | ResultAllocationMemoryFailedInIntegrityRomFsStorageA); | ||
| 17 | |||
| 18 | // Set the master hash storage. | ||
| 19 | storage_info[0] = m_master_hash_storage; | ||
| 20 | |||
| 21 | // Initialize our integrity storage. | ||
| 22 | R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries, | ||
| 23 | max_hash_cache_entries, buffer_level)); | ||
| 24 | } | ||
| 25 | |||
| 26 | void IntegrityRomFsStorage::Finalize() { | ||
| 27 | m_integrity_storage.Finalize(); | ||
| 28 | } | ||
| 29 | |||
| 30 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h new file mode 100644 index 000000000..5f8512b2a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 8 | #include "core/file_sys/vfs_vector.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | constexpr inline size_t IntegrityLayerCountRomFs = 7; | ||
| 13 | constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB; | ||
| 14 | |||
| 15 | class IntegrityRomFsStorage : public IReadOnlyStorage { | ||
| 16 | public: | ||
| 17 | IntegrityRomFsStorage() {} | ||
| 18 | virtual ~IntegrityRomFsStorage() override { | ||
| 19 | this->Finalize(); | ||
| 20 | } | ||
| 21 | |||
| 22 | Result Initialize( | ||
| 23 | HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, | ||
| 24 | HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, | ||
| 25 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); | ||
| 26 | void Finalize(); | ||
| 27 | |||
| 28 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 29 | return m_integrity_storage.Read(buffer, size, offset); | ||
| 30 | } | ||
| 31 | |||
| 32 | virtual size_t GetSize() const override { | ||
| 33 | return m_integrity_storage.GetSize(); | ||
| 34 | } | ||
| 35 | |||
| 36 | private: | ||
| 37 | HierarchicalIntegrityVerificationStorage m_integrity_storage; | ||
| 38 | Hash m_master_hash; | ||
| 39 | std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage; | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp new file mode 100644 index 000000000..2f73abf86 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp | |||
| @@ -0,0 +1,91 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | constexpr inline u32 ILog2(u32 val) { | ||
| 10 | ASSERT(val > 0); | ||
| 11 | return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val)); | ||
| 12 | } | ||
| 13 | |||
| 14 | void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, | ||
| 15 | s64 upper_layer_verif_block_size, bool is_real_data) { | ||
| 16 | // Validate preconditions. | ||
| 17 | ASSERT(verif_block_size >= HashSize); | ||
| 18 | |||
| 19 | // Set storages. | ||
| 20 | m_hash_storage = hs; | ||
| 21 | m_data_storage = ds; | ||
| 22 | |||
| 23 | // Set verification block sizes. | ||
| 24 | m_verification_block_size = verif_block_size; | ||
| 25 | m_verification_block_order = ILog2(static_cast<u32>(verif_block_size)); | ||
| 26 | ASSERT(m_verification_block_size == 1ll << m_verification_block_order); | ||
| 27 | |||
| 28 | // Set upper layer block sizes. | ||
| 29 | upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize); | ||
| 30 | m_upper_layer_verification_block_size = upper_layer_verif_block_size; | ||
| 31 | m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size)); | ||
| 32 | ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order); | ||
| 33 | |||
| 34 | // Validate sizes. | ||
| 35 | { | ||
| 36 | s64 hash_size = m_hash_storage->GetSize(); | ||
| 37 | s64 data_size = m_data_storage->GetSize(); | ||
| 38 | ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size); | ||
| 39 | } | ||
| 40 | |||
| 41 | // Set data. | ||
| 42 | m_is_real_data = is_real_data; | ||
| 43 | } | ||
| 44 | |||
| 45 | void IntegrityVerificationStorage::Finalize() { | ||
| 46 | m_hash_storage = VirtualFile(); | ||
| 47 | m_data_storage = VirtualFile(); | ||
| 48 | } | ||
| 49 | |||
| 50 | size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 51 | // Succeed if zero size. | ||
| 52 | if (size == 0) { | ||
| 53 | return size; | ||
| 54 | } | ||
| 55 | |||
| 56 | // Validate arguments. | ||
| 57 | ASSERT(buffer != nullptr); | ||
| 58 | |||
| 59 | // Validate the offset. | ||
| 60 | s64 data_size = m_data_storage->GetSize(); | ||
| 61 | ASSERT(offset <= static_cast<size_t>(data_size)); | ||
| 62 | |||
| 63 | // Validate the access range. | ||
| 64 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange( | ||
| 65 | offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size))))); | ||
| 66 | |||
| 67 | // Determine the read extents. | ||
| 68 | size_t read_size = size; | ||
| 69 | if (static_cast<s64>(offset + read_size) > data_size) { | ||
| 70 | // Determine the padding sizes. | ||
| 71 | s64 padding_offset = data_size - offset; | ||
| 72 | size_t padding_size = static_cast<size_t>( | ||
| 73 | m_verification_block_size - (padding_offset & (m_verification_block_size - 1))); | ||
| 74 | ASSERT(static_cast<s64>(padding_size) < m_verification_block_size); | ||
| 75 | |||
| 76 | // Clear the padding. | ||
| 77 | std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size); | ||
| 78 | |||
| 79 | // Set the new in-bounds size. | ||
| 80 | read_size = static_cast<size_t>(data_size - offset); | ||
| 81 | } | ||
| 82 | |||
| 83 | // Perform the read. | ||
| 84 | return m_data_storage->Read(buffer, read_size, offset); | ||
| 85 | } | ||
| 86 | |||
| 87 | size_t IntegrityVerificationStorage::GetSize() const { | ||
| 88 | return m_data_storage->GetSize(); | ||
| 89 | } | ||
| 90 | |||
| 91 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h new file mode 100644 index 000000000..09f76799d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | class IntegrityVerificationStorage : public IReadOnlyStorage { | ||
| 14 | YUZU_NON_COPYABLE(IntegrityVerificationStorage); | ||
| 15 | YUZU_NON_MOVEABLE(IntegrityVerificationStorage); | ||
| 16 | |||
| 17 | public: | ||
| 18 | static constexpr s64 HashSize = 256 / 8; | ||
| 19 | |||
| 20 | struct BlockHash { | ||
| 21 | std::array<u8, HashSize> hash; | ||
| 22 | }; | ||
| 23 | static_assert(std::is_trivial_v<BlockHash>); | ||
| 24 | |||
| 25 | public: | ||
| 26 | IntegrityVerificationStorage() | ||
| 27 | : m_verification_block_size(0), m_verification_block_order(0), | ||
| 28 | m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {} | ||
| 29 | virtual ~IntegrityVerificationStorage() override { | ||
| 30 | this->Finalize(); | ||
| 31 | } | ||
| 32 | |||
| 33 | void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, | ||
| 34 | s64 upper_layer_verif_block_size, bool is_real_data); | ||
| 35 | void Finalize(); | ||
| 36 | |||
| 37 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 38 | virtual size_t GetSize() const override; | ||
| 39 | |||
| 40 | s64 GetBlockSize() const { | ||
| 41 | return m_verification_block_size; | ||
| 42 | } | ||
| 43 | |||
| 44 | private: | ||
| 45 | static void SetValidationBit(BlockHash* hash) { | ||
| 46 | ASSERT(hash != nullptr); | ||
| 47 | hash->hash[HashSize - 1] |= 0x80; | ||
| 48 | } | ||
| 49 | |||
| 50 | static bool IsValidationBit(const BlockHash* hash) { | ||
| 51 | ASSERT(hash != nullptr); | ||
| 52 | return (hash->hash[HashSize - 1] & 0x80) != 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | private: | ||
| 56 | VirtualFile m_hash_storage; | ||
| 57 | VirtualFile m_data_storage; | ||
| 58 | s64 m_verification_block_size; | ||
| 59 | s64 m_verification_block_order; | ||
| 60 | s64 m_upper_layer_verification_block_size; | ||
| 61 | s64 m_upper_layer_verification_block_order; | ||
| 62 | bool m_is_real_data; | ||
| 63 | }; | ||
| 64 | |||
| 65 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h new file mode 100644 index 000000000..c07a127fb --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class MemoryResourceBufferHoldStorage : public IStorage { | ||
| 11 | YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage); | ||
| 12 | YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage); | ||
| 13 | |||
| 14 | public: | ||
| 15 | MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size) | ||
| 16 | : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)), | ||
| 17 | m_buffer_size(buffer_size) {} | ||
| 18 | |||
| 19 | virtual ~MemoryResourceBufferHoldStorage() { | ||
| 20 | // If we have a buffer, deallocate it. | ||
| 21 | if (m_buffer != nullptr) { | ||
| 22 | ::operator delete(m_buffer); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | bool IsValid() const { | ||
| 27 | return m_buffer != nullptr; | ||
| 28 | } | ||
| 29 | void* GetBuffer() const { | ||
| 30 | return m_buffer; | ||
| 31 | } | ||
| 32 | |||
| 33 | public: | ||
| 34 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 35 | // Check pre-conditions. | ||
| 36 | ASSERT(m_storage != nullptr); | ||
| 37 | |||
| 38 | return m_storage->Read(buffer, size, offset); | ||
| 39 | } | ||
| 40 | |||
| 41 | virtual size_t GetSize() const override { | ||
| 42 | // Check pre-conditions. | ||
| 43 | ASSERT(m_storage != nullptr); | ||
| 44 | |||
| 45 | return m_storage->GetSize(); | ||
| 46 | } | ||
| 47 | |||
| 48 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 49 | // Check pre-conditions. | ||
| 50 | ASSERT(m_storage != nullptr); | ||
| 51 | |||
| 52 | return m_storage->Write(buffer, size, offset); | ||
| 53 | } | ||
| 54 | |||
| 55 | private: | ||
| 56 | VirtualFile m_storage; | ||
| 57 | void* m_buffer; | ||
| 58 | size_t m_buffer_size; | ||
| 59 | }; | ||
| 60 | |||
| 61 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp new file mode 100644 index 000000000..0f5432203 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp | |||
| @@ -0,0 +1,1351 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_compressed_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" | ||
| 11 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 12 | #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" | ||
| 13 | #include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" | ||
| 14 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 15 | #include "core/file_sys/fssystem/fssystem_sparse_storage.h" | ||
| 16 | #include "core/file_sys/fssystem/fssystem_switch_storage.h" | ||
| 17 | #include "core/file_sys/vfs_offset.h" | ||
| 18 | #include "core/file_sys/vfs_vector.h" | ||
| 19 | |||
| 20 | namespace FileSys { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | |||
| 24 | constexpr inline s32 IntegrityDataCacheCount = 24; | ||
| 25 | constexpr inline s32 IntegrityHashCacheCount = 8; | ||
| 26 | |||
| 27 | constexpr inline s32 IntegrityDataCacheCountForMeta = 16; | ||
| 28 | constexpr inline s32 IntegrityHashCacheCountForMeta = 2; | ||
| 29 | |||
| 30 | class SharedNcaBodyStorage : public IReadOnlyStorage { | ||
| 31 | YUZU_NON_COPYABLE(SharedNcaBodyStorage); | ||
| 32 | YUZU_NON_MOVEABLE(SharedNcaBodyStorage); | ||
| 33 | |||
| 34 | private: | ||
| 35 | VirtualFile m_storage; | ||
| 36 | std::shared_ptr<NcaReader> m_nca_reader; | ||
| 37 | |||
| 38 | public: | ||
| 39 | SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r) | ||
| 40 | : m_storage(std::move(s)), m_nca_reader(std::move(r)) {} | ||
| 41 | |||
| 42 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 43 | // Validate pre-conditions. | ||
| 44 | ASSERT(m_storage != nullptr); | ||
| 45 | |||
| 46 | // Read from the base storage. | ||
| 47 | return m_storage->Read(buffer, size, offset); | ||
| 48 | } | ||
| 49 | |||
| 50 | virtual size_t GetSize() const override { | ||
| 51 | // Validate pre-conditions. | ||
| 52 | ASSERT(m_storage != nullptr); | ||
| 53 | |||
| 54 | return m_storage->GetSize(); | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) { | ||
| 59 | return static_cast<s64>(reader.GetFsOffset(fs_index)); | ||
| 60 | } | ||
| 61 | |||
| 62 | inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) { | ||
| 63 | return static_cast<s64>(reader.GetFsEndOffset(fs_index)); | ||
| 64 | } | ||
| 65 | |||
| 66 | using Sha256DataRegion = NcaFsHeader::Region; | ||
| 67 | using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; | ||
| 68 | using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; | ||
| 69 | |||
| 70 | } // namespace | ||
| 71 | |||
| 72 | Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out, | ||
| 73 | NcaFsHeaderReader* out_header_reader, | ||
| 74 | s32 fs_index, StorageContext* ctx) { | ||
| 75 | // Open storage. | ||
| 76 | R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); | ||
| 77 | } | ||
| 78 | |||
| 79 | Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, | ||
| 80 | s32 fs_index, StorageContext* ctx) { | ||
| 81 | // Validate preconditions. | ||
| 82 | ASSERT(out != nullptr); | ||
| 83 | ASSERT(out_header_reader != nullptr); | ||
| 84 | ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); | ||
| 85 | |||
| 86 | // Validate the fs index. | ||
| 87 | R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound); | ||
| 88 | |||
| 89 | // Initialize our header reader for the fs index. | ||
| 90 | R_TRY(out_header_reader->Initialize(*m_reader, fs_index)); | ||
| 91 | |||
| 92 | // Declare the storage we're opening. | ||
| 93 | VirtualFile storage; | ||
| 94 | |||
| 95 | // Process sparse layer. | ||
| 96 | s64 fs_data_offset = 0; | ||
| 97 | if (out_header_reader->ExistsSparseLayer()) { | ||
| 98 | // Get the sparse info. | ||
| 99 | const auto& sparse_info = out_header_reader->GetSparseInfo(); | ||
| 100 | |||
| 101 | // Create based on whether we have a meta hash layer. | ||
| 102 | if (out_header_reader->ExistsSparseMetaHashLayer()) { | ||
| 103 | // Create the sparse storage with verification. | ||
| 104 | R_TRY(this->CreateSparseStorageWithVerification( | ||
| 105 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 106 | ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, | ||
| 107 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 108 | ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, | ||
| 109 | out_header_reader->GetAesCtrUpperIv(), sparse_info, | ||
| 110 | out_header_reader->GetSparseMetaDataHashDataInfo(), | ||
| 111 | out_header_reader->GetSparseMetaHashType())); | ||
| 112 | } else { | ||
| 113 | // Create the sparse storage. | ||
| 114 | R_TRY(this->CreateSparseStorage( | ||
| 115 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 116 | ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, | ||
| 117 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 118 | fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); | ||
| 119 | } | ||
| 120 | } else { | ||
| 121 | // Get the data offsets. | ||
| 122 | fs_data_offset = GetFsOffset(*m_reader, fs_index); | ||
| 123 | const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); | ||
| 124 | |||
| 125 | // Validate that we're within range. | ||
| 126 | const auto data_size = fs_end_offset - fs_data_offset; | ||
| 127 | R_UNLESS(data_size > 0, ResultInvalidNcaHeader); | ||
| 128 | |||
| 129 | // Create the body substorage. | ||
| 130 | R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); | ||
| 131 | |||
| 132 | // Potentially save the body substorage to our context. | ||
| 133 | if (ctx != nullptr) { | ||
| 134 | ctx->body_substorage = storage; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | // Process patch layer. | ||
| 139 | const auto& patch_info = out_header_reader->GetPatchInfo(); | ||
| 140 | VirtualFile patch_meta_aes_ctr_ex_meta_storage; | ||
| 141 | VirtualFile patch_meta_indirect_meta_storage; | ||
| 142 | if (out_header_reader->ExistsPatchMetaHashLayer()) { | ||
| 143 | // Check the meta hash type. | ||
| 144 | R_UNLESS(out_header_reader->GetPatchMetaHashType() == | ||
| 145 | NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, | ||
| 146 | ResultRomNcaInvalidPatchMetaDataHashType); | ||
| 147 | |||
| 148 | // Create the patch meta storage. | ||
| 149 | R_TRY(this->CreatePatchMetaStorage( | ||
| 150 | std::addressof(patch_meta_aes_ctr_ex_meta_storage), | ||
| 151 | std::addressof(patch_meta_indirect_meta_storage), | ||
| 152 | ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, | ||
| 153 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, | ||
| 154 | out_header_reader->GetPatchMetaDataHashDataInfo())); | ||
| 155 | } | ||
| 156 | |||
| 157 | if (patch_info.HasAesCtrExTable()) { | ||
| 158 | // Check the encryption type. | ||
| 159 | ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || | ||
| 160 | out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || | ||
| 161 | out_header_reader->GetEncryptionType() == | ||
| 162 | NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); | ||
| 163 | |||
| 164 | // Create the ex meta storage. | ||
| 165 | VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; | ||
| 166 | if (aes_ctr_ex_storage_meta_storage == nullptr) { | ||
| 167 | // If we don't have a meta storage, we must not have a patch meta hash layer. | ||
| 168 | ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); | ||
| 169 | |||
| 170 | R_TRY(this->CreateAesCtrExStorageMetaStorage( | ||
| 171 | std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, | ||
| 172 | out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), | ||
| 173 | patch_info)); | ||
| 174 | } | ||
| 175 | |||
| 176 | // Create the ex storage. | ||
| 177 | VirtualFile aes_ctr_ex_storage; | ||
| 178 | R_TRY(this->CreateAesCtrExStorage( | ||
| 179 | std::addressof(aes_ctr_ex_storage), | ||
| 180 | ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage), | ||
| 181 | aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 182 | patch_info)); | ||
| 183 | |||
| 184 | // Set the base storage as the ex storage. | ||
| 185 | storage = std::move(aes_ctr_ex_storage); | ||
| 186 | |||
| 187 | // Potentially save storages to our context. | ||
| 188 | if (ctx != nullptr) { | ||
| 189 | ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage; | ||
| 190 | ctx->aes_ctr_ex_storage_data_storage = storage; | ||
| 191 | ctx->fs_data_storage = storage; | ||
| 192 | } | ||
| 193 | } else { | ||
| 194 | // Create the appropriate storage for the encryption type. | ||
| 195 | switch (out_header_reader->GetEncryptionType()) { | ||
| 196 | case NcaFsHeader::EncryptionType::None: | ||
| 197 | // If there's no encryption, use the base storage we made previously. | ||
| 198 | break; | ||
| 199 | case NcaFsHeader::EncryptionType::AesXts: | ||
| 200 | R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), | ||
| 201 | fs_data_offset)); | ||
| 202 | break; | ||
| 203 | case NcaFsHeader::EncryptionType::AesCtr: | ||
| 204 | R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), | ||
| 205 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 206 | AlignmentStorageRequirement::None)); | ||
| 207 | break; | ||
| 208 | case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: { | ||
| 209 | // Create the aes ctr storage. | ||
| 210 | VirtualFile aes_ctr_storage; | ||
| 211 | R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, | ||
| 212 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 213 | AlignmentStorageRequirement::None)); | ||
| 214 | |||
| 215 | // Create region switch storage. | ||
| 216 | R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, | ||
| 217 | std::move(storage), std::move(aes_ctr_storage))); | ||
| 218 | } break; | ||
| 219 | default: | ||
| 220 | R_THROW(ResultInvalidNcaFsHeaderEncryptionType); | ||
| 221 | } | ||
| 222 | |||
| 223 | // Potentially save storages to our context. | ||
| 224 | if (ctx != nullptr) { | ||
| 225 | ctx->fs_data_storage = storage; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | // Process indirect layer. | ||
| 230 | if (patch_info.HasIndirectTable()) { | ||
| 231 | // Create the indirect meta storage. | ||
| 232 | VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage; | ||
| 233 | if (indirect_storage_meta_storage == nullptr) { | ||
| 234 | // If we don't have a meta storage, we must not have a patch meta hash layer. | ||
| 235 | ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); | ||
| 236 | |||
| 237 | R_TRY(this->CreateIndirectStorageMetaStorage( | ||
| 238 | std::addressof(indirect_storage_meta_storage), storage, patch_info)); | ||
| 239 | } | ||
| 240 | |||
| 241 | // Potentially save the indirect meta storage to our context. | ||
| 242 | if (ctx != nullptr) { | ||
| 243 | ctx->indirect_storage_meta_storage = indirect_storage_meta_storage; | ||
| 244 | } | ||
| 245 | |||
| 246 | // Get the original indirectable storage. | ||
| 247 | VirtualFile original_indirectable_storage; | ||
| 248 | if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) { | ||
| 249 | // Create a driver for the original. | ||
| 250 | NcaFileSystemDriver original_driver(m_original_reader); | ||
| 251 | |||
| 252 | // Create a header reader for the original. | ||
| 253 | NcaFsHeaderReader original_header_reader; | ||
| 254 | R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index)); | ||
| 255 | |||
| 256 | // Open original indirectable storage. | ||
| 257 | R_TRY(original_driver.OpenIndirectableStorageAsOriginal( | ||
| 258 | std::addressof(original_indirectable_storage), | ||
| 259 | std::addressof(original_header_reader), ctx)); | ||
| 260 | } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { | ||
| 261 | // Use the external original storage. | ||
| 262 | original_indirectable_storage = ctx->external_original_storage; | ||
| 263 | } else { | ||
| 264 | // Allocate a dummy memory storage as original storage. | ||
| 265 | original_indirectable_storage = std::make_shared<VectorVfsFile>(); | ||
| 266 | R_UNLESS(original_indirectable_storage != nullptr, | ||
| 267 | ResultAllocationMemoryFailedAllocateShared); | ||
| 268 | } | ||
| 269 | |||
| 270 | // Create the indirect storage. | ||
| 271 | VirtualFile indirect_storage; | ||
| 272 | R_TRY(this->CreateIndirectStorage( | ||
| 273 | std::addressof(indirect_storage), | ||
| 274 | ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage), | ||
| 275 | std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage), | ||
| 276 | patch_info)); | ||
| 277 | |||
| 278 | // Set storage as the indirect storage. | ||
| 279 | storage = std::move(indirect_storage); | ||
| 280 | } | ||
| 281 | |||
| 282 | // Check if we're sparse or requested to skip the integrity layer. | ||
| 283 | if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { | ||
| 284 | *out = std::move(storage); | ||
| 285 | R_SUCCEED(); | ||
| 286 | } | ||
| 287 | |||
| 288 | // Create the non-raw storage. | ||
| 289 | R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx)); | ||
| 290 | } | ||
| 291 | |||
| 292 | Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out, | ||
| 293 | const NcaFsHeaderReader* header_reader, | ||
| 294 | VirtualFile raw_storage, | ||
| 295 | StorageContext* ctx) { | ||
| 296 | // Initialize storage as raw storage. | ||
| 297 | VirtualFile storage = std::move(raw_storage); | ||
| 298 | |||
| 299 | // Process hash/integrity layer. | ||
| 300 | switch (header_reader->GetHashType()) { | ||
| 301 | case NcaFsHeader::HashType::HierarchicalSha256Hash: | ||
| 302 | R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), | ||
| 303 | header_reader->GetHashData().hierarchical_sha256_data)); | ||
| 304 | break; | ||
| 305 | case NcaFsHeader::HashType::HierarchicalIntegrityHash: | ||
| 306 | R_TRY(this->CreateIntegrityVerificationStorage( | ||
| 307 | std::addressof(storage), std::move(storage), | ||
| 308 | header_reader->GetHashData().integrity_meta_info)); | ||
| 309 | break; | ||
| 310 | default: | ||
| 311 | R_THROW(ResultInvalidNcaFsHeaderHashType); | ||
| 312 | } | ||
| 313 | |||
| 314 | // Process compression layer. | ||
| 315 | if (header_reader->ExistsCompressionLayer()) { | ||
| 316 | R_TRY(this->CreateCompressedStorage( | ||
| 317 | std::addressof(storage), | ||
| 318 | ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr, | ||
| 319 | ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr, | ||
| 320 | std::move(storage), header_reader->GetCompressionInfo())); | ||
| 321 | } | ||
| 322 | |||
| 323 | // Set output storage. | ||
| 324 | *out = std::move(storage); | ||
| 325 | R_SUCCEED(); | ||
| 326 | } | ||
| 327 | |||
| 328 | Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal( | ||
| 329 | VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) { | ||
| 330 | // Get the fs index. | ||
| 331 | const auto fs_index = header_reader->GetFsIndex(); | ||
| 332 | |||
| 333 | // Declare the storage we're opening. | ||
| 334 | VirtualFile storage; | ||
| 335 | |||
| 336 | // Process sparse layer. | ||
| 337 | s64 fs_data_offset = 0; | ||
| 338 | if (header_reader->ExistsSparseLayer()) { | ||
| 339 | // Get the sparse info. | ||
| 340 | const auto& sparse_info = header_reader->GetSparseInfo(); | ||
| 341 | |||
| 342 | // Create based on whether we have a meta hash layer. | ||
| 343 | if (header_reader->ExistsSparseMetaHashLayer()) { | ||
| 344 | // Create the sparse storage with verification. | ||
| 345 | R_TRY(this->CreateSparseStorageWithVerification( | ||
| 346 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 347 | ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, | ||
| 348 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 349 | ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, | ||
| 350 | header_reader->GetAesCtrUpperIv(), sparse_info, | ||
| 351 | header_reader->GetSparseMetaDataHashDataInfo(), | ||
| 352 | header_reader->GetSparseMetaHashType())); | ||
| 353 | } else { | ||
| 354 | // Create the sparse storage. | ||
| 355 | R_TRY(this->CreateSparseStorage( | ||
| 356 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 357 | ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, | ||
| 358 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 359 | fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); | ||
| 360 | } | ||
| 361 | } else { | ||
| 362 | // Get the data offsets. | ||
| 363 | fs_data_offset = GetFsOffset(*m_reader, fs_index); | ||
| 364 | const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); | ||
| 365 | |||
| 366 | // Validate that we're within range. | ||
| 367 | const auto data_size = fs_end_offset - fs_data_offset; | ||
| 368 | R_UNLESS(data_size > 0, ResultInvalidNcaHeader); | ||
| 369 | |||
| 370 | // Create the body substorage. | ||
| 371 | R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); | ||
| 372 | } | ||
| 373 | |||
| 374 | // Create the appropriate storage for the encryption type. | ||
| 375 | switch (header_reader->GetEncryptionType()) { | ||
| 376 | case NcaFsHeader::EncryptionType::None: | ||
| 377 | // If there's no encryption, use the base storage we made previously. | ||
| 378 | break; | ||
| 379 | case NcaFsHeader::EncryptionType::AesXts: | ||
| 380 | R_TRY( | ||
| 381 | this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); | ||
| 382 | break; | ||
| 383 | case NcaFsHeader::EncryptionType::AesCtr: | ||
| 384 | R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, | ||
| 385 | header_reader->GetAesCtrUpperIv(), | ||
| 386 | AlignmentStorageRequirement::CacheBlockSize)); | ||
| 387 | break; | ||
| 388 | default: | ||
| 389 | R_THROW(ResultInvalidNcaFsHeaderEncryptionType); | ||
| 390 | } | ||
| 391 | |||
| 392 | // Set output storage. | ||
| 393 | *out = std::move(storage); | ||
| 394 | R_SUCCEED(); | ||
| 395 | } | ||
| 396 | |||
| 397 | Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) { | ||
| 398 | // Create the body storage. | ||
| 399 | auto body_storage = | ||
| 400 | std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader); | ||
| 401 | R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 402 | |||
| 403 | // Get the body storage size. | ||
| 404 | s64 body_size = body_storage->GetSize(); | ||
| 405 | |||
| 406 | // Check that we're within range. | ||
| 407 | R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 408 | |||
| 409 | // Create substorage. | ||
| 410 | auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset); | ||
| 411 | R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 412 | |||
| 413 | // Set the output storage. | ||
| 414 | *out = std::move(body_substorage); | ||
| 415 | R_SUCCEED(); | ||
| 416 | } | ||
| 417 | |||
| 418 | Result NcaFileSystemDriver::CreateAesCtrStorage( | ||
| 419 | VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 420 | AlignmentStorageRequirement alignment_storage_requirement) { | ||
| 421 | // Check pre-conditions. | ||
| 422 | ASSERT(out != nullptr); | ||
| 423 | ASSERT(base_storage != nullptr); | ||
| 424 | |||
| 425 | // Create the iv. | ||
| 426 | std::array<u8, AesCtrStorage::IvSize> iv{}; | ||
| 427 | AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset); | ||
| 428 | |||
| 429 | // Create the ctr storage. | ||
| 430 | VirtualFile aes_ctr_storage; | ||
| 431 | if (m_reader->HasExternalDecryptionKey()) { | ||
| 432 | aes_ctr_storage = std::make_shared<AesCtrStorage>( | ||
| 433 | std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, | ||
| 434 | iv.data(), AesCtrStorage::IvSize); | ||
| 435 | R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 436 | } else { | ||
| 437 | // Create software decryption storage. | ||
| 438 | auto sw_storage = std::make_shared<AesCtrStorage>( | ||
| 439 | base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), | ||
| 440 | AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize); | ||
| 441 | R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 442 | |||
| 443 | aes_ctr_storage = std::move(sw_storage); | ||
| 444 | } | ||
| 445 | |||
| 446 | // Create alignment matching storage. | ||
| 447 | auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>( | ||
| 448 | std::move(aes_ctr_storage)); | ||
| 449 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 450 | |||
| 451 | // Set the out storage. | ||
| 452 | *out = std::move(aligned_storage); | ||
| 453 | R_SUCCEED(); | ||
| 454 | } | ||
| 455 | |||
| 456 | Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, | ||
| 457 | s64 offset) { | ||
| 458 | // Check pre-conditions. | ||
| 459 | ASSERT(out != nullptr); | ||
| 460 | ASSERT(base_storage != nullptr); | ||
| 461 | |||
| 462 | // Create the iv. | ||
| 463 | std::array<u8, AesXtsStorage::IvSize> iv{}; | ||
| 464 | AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize); | ||
| 465 | |||
| 466 | // Make the aes xts storage. | ||
| 467 | const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); | ||
| 468 | const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); | ||
| 469 | auto xts_storage = | ||
| 470 | std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize, | ||
| 471 | iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); | ||
| 472 | R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 473 | |||
| 474 | // Create alignment matching storage. | ||
| 475 | auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>( | ||
| 476 | std::move(xts_storage)); | ||
| 477 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 478 | |||
| 479 | // Set the out storage. | ||
| 480 | *out = std::move(xts_storage); | ||
| 481 | R_SUCCEED(); | ||
| 482 | } | ||
| 483 | |||
| 484 | Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out, | ||
| 485 | VirtualFile base_storage, s64 offset, | ||
| 486 | const NcaAesCtrUpperIv& upper_iv, | ||
| 487 | const NcaSparseInfo& sparse_info) { | ||
| 488 | // Validate preconditions. | ||
| 489 | ASSERT(out != nullptr); | ||
| 490 | ASSERT(base_storage != nullptr); | ||
| 491 | |||
| 492 | // Get the base storage size. | ||
| 493 | s64 base_size = base_storage->GetSize(); | ||
| 494 | |||
| 495 | // Get the meta extents. | ||
| 496 | const auto meta_offset = sparse_info.bucket.offset; | ||
| 497 | const auto meta_size = sparse_info.bucket.size; | ||
| 498 | R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 499 | |||
| 500 | // Create the encrypted storage. | ||
| 501 | auto enc_storage = | ||
| 502 | std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); | ||
| 503 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 504 | |||
| 505 | // Create the decrypted storage. | ||
| 506 | VirtualFile decrypted_storage; | ||
| 507 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 508 | offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), | ||
| 509 | AlignmentStorageRequirement::None)); | ||
| 510 | |||
| 511 | // Create buffered storage. | ||
| 512 | std::vector<u8> meta_data(meta_size); | ||
| 513 | decrypted_storage->Read(meta_data.data(), meta_size, 0); | ||
| 514 | |||
| 515 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 516 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 517 | |||
| 518 | // Set the output. | ||
| 519 | *out = std::move(buffered_storage); | ||
| 520 | R_SUCCEED(); | ||
| 521 | } | ||
| 522 | |||
| 523 | Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, | ||
| 524 | VirtualFile base_storage, s64 base_size, | ||
| 525 | VirtualFile meta_storage, | ||
| 526 | const NcaSparseInfo& sparse_info, | ||
| 527 | bool external_info) { | ||
| 528 | // Validate preconditions. | ||
| 529 | ASSERT(out != nullptr); | ||
| 530 | ASSERT(base_storage != nullptr); | ||
| 531 | ASSERT(meta_storage != nullptr); | ||
| 532 | |||
| 533 | // Read and verify the bucket tree header. | ||
| 534 | BucketTree::Header header; | ||
| 535 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 536 | R_TRY(header.Verify()); | ||
| 537 | |||
| 538 | // Determine storage extents. | ||
| 539 | const auto node_offset = 0; | ||
| 540 | const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); | ||
| 541 | const auto entry_offset = node_offset + node_size; | ||
| 542 | const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); | ||
| 543 | |||
| 544 | // Create the sparse storage. | ||
| 545 | auto sparse_storage = std::make_shared<SparseStorage>(); | ||
| 546 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 547 | |||
| 548 | // Sanity check that we can be doing this. | ||
| 549 | ASSERT(header.entry_count != 0); | ||
| 550 | |||
| 551 | // Initialize the sparse storage. | ||
| 552 | R_TRY(sparse_storage->Initialize( | ||
| 553 | std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset), | ||
| 554 | std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset), | ||
| 555 | header.entry_count)); | ||
| 556 | |||
| 557 | // If not external, set the data storage. | ||
| 558 | if (!external_info) { | ||
| 559 | sparse_storage->SetDataStorage( | ||
| 560 | std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0)); | ||
| 561 | } | ||
| 562 | |||
| 563 | // Set the output. | ||
| 564 | *out = std::move(sparse_storage); | ||
| 565 | R_SUCCEED(); | ||
| 566 | } | ||
| 567 | |||
| 568 | Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, | ||
| 569 | std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 570 | VirtualFile* out_meta_storage, s32 index, | ||
| 571 | const NcaAesCtrUpperIv& upper_iv, | ||
| 572 | const NcaSparseInfo& sparse_info) { | ||
| 573 | // Validate preconditions. | ||
| 574 | ASSERT(out != nullptr); | ||
| 575 | ASSERT(out_fs_data_offset != nullptr); | ||
| 576 | |||
| 577 | // Check the sparse info generation. | ||
| 578 | R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); | ||
| 579 | |||
| 580 | // Read and verify the bucket tree header. | ||
| 581 | BucketTree::Header header; | ||
| 582 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 583 | R_TRY(header.Verify()); | ||
| 584 | |||
| 585 | // Determine the storage extents. | ||
| 586 | const auto fs_offset = GetFsOffset(*m_reader, index); | ||
| 587 | const auto fs_end_offset = GetFsEndOffset(*m_reader, index); | ||
| 588 | const auto fs_size = fs_end_offset - fs_offset; | ||
| 589 | |||
| 590 | // Create the sparse storage. | ||
| 591 | std::shared_ptr<SparseStorage> sparse_storage; | ||
| 592 | if (header.entry_count != 0) { | ||
| 593 | // Create the body substorage. | ||
| 594 | VirtualFile body_substorage; | ||
| 595 | R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), | ||
| 596 | sparse_info.physical_offset, | ||
| 597 | sparse_info.GetPhysicalSize())); | ||
| 598 | |||
| 599 | // Create the meta storage. | ||
| 600 | VirtualFile meta_storage; | ||
| 601 | R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage, | ||
| 602 | sparse_info.physical_offset, upper_iv, | ||
| 603 | sparse_info)); | ||
| 604 | |||
| 605 | // Potentially set the output meta storage. | ||
| 606 | if (out_meta_storage != nullptr) { | ||
| 607 | *out_meta_storage = meta_storage; | ||
| 608 | } | ||
| 609 | |||
| 610 | // Create the sparse storage. | ||
| 611 | R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, | ||
| 612 | sparse_info.GetPhysicalSize(), std::move(meta_storage), | ||
| 613 | sparse_info, false)); | ||
| 614 | } else { | ||
| 615 | // If there are no entries, there's nothing to actually do. | ||
| 616 | sparse_storage = std::make_shared<SparseStorage>(); | ||
| 617 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 618 | |||
| 619 | sparse_storage->Initialize(fs_size); | ||
| 620 | } | ||
| 621 | |||
| 622 | // Potentially set the output sparse storage. | ||
| 623 | if (out_sparse_storage != nullptr) { | ||
| 624 | *out_sparse_storage = sparse_storage; | ||
| 625 | } | ||
| 626 | |||
| 627 | // Set the output fs data offset. | ||
| 628 | *out_fs_data_offset = fs_offset; | ||
| 629 | |||
| 630 | // Set the output storage. | ||
| 631 | *out = std::move(sparse_storage); | ||
| 632 | R_SUCCEED(); | ||
| 633 | } | ||
| 634 | |||
| 635 | Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification( | ||
| 636 | VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 637 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 638 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 639 | // Validate preconditions. | ||
| 640 | ASSERT(out != nullptr); | ||
| 641 | ASSERT(base_storage != nullptr); | ||
| 642 | |||
| 643 | // Get the base storage size. | ||
| 644 | s64 base_size = base_storage->GetSize(); | ||
| 645 | |||
| 646 | // Get the meta extents. | ||
| 647 | const auto meta_offset = sparse_info.bucket.offset; | ||
| 648 | const auto meta_size = sparse_info.bucket.size; | ||
| 649 | R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 650 | |||
| 651 | // Get the meta data hash data extents. | ||
| 652 | const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; | ||
| 653 | const s64 meta_data_hash_data_size = | ||
| 654 | Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); | ||
| 655 | R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, | ||
| 656 | ResultNcaBaseStorageOutOfRangeB); | ||
| 657 | |||
| 658 | // Check that the meta is before the hash data. | ||
| 659 | R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, | ||
| 660 | ResultRomNcaInvalidSparseMetaDataHashDataOffset); | ||
| 661 | |||
| 662 | // Check that offsets are appropriately aligned. | ||
| 663 | R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), | ||
| 664 | ResultRomNcaInvalidSparseMetaDataHashDataOffset); | ||
| 665 | R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), | ||
| 666 | ResultInvalidNcaFsHeader); | ||
| 667 | |||
| 668 | // Create the meta storage. | ||
| 669 | auto enc_storage = std::make_shared<OffsetVfsFile>( | ||
| 670 | std::move(base_storage), | ||
| 671 | meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset); | ||
| 672 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 673 | |||
| 674 | // Create the decrypted storage. | ||
| 675 | VirtualFile decrypted_storage; | ||
| 676 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 677 | offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), | ||
| 678 | AlignmentStorageRequirement::None)); | ||
| 679 | |||
| 680 | // Create the verification storage. | ||
| 681 | VirtualFile integrity_storage; | ||
| 682 | Result rc = this->CreateIntegrityVerificationStorageForMeta( | ||
| 683 | std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), | ||
| 684 | meta_offset, meta_data_hash_data_info); | ||
| 685 | if (rc == ResultInvalidNcaMetaDataHashDataSize) { | ||
| 686 | R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize); | ||
| 687 | } | ||
| 688 | if (rc == ResultInvalidNcaMetaDataHashDataHash) { | ||
| 689 | R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash); | ||
| 690 | } | ||
| 691 | R_TRY(rc); | ||
| 692 | |||
| 693 | // Create the meta storage. | ||
| 694 | auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0); | ||
| 695 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 696 | |||
| 697 | // Set the output. | ||
| 698 | *out = std::move(meta_storage); | ||
| 699 | R_SUCCEED(); | ||
| 700 | } | ||
| 701 | |||
| 702 | Result NcaFileSystemDriver::CreateSparseStorageWithVerification( | ||
| 703 | VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 704 | VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index, | ||
| 705 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 706 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info, | ||
| 707 | NcaFsHeader::MetaDataHashType meta_data_hash_type) { | ||
| 708 | // Validate preconditions. | ||
| 709 | ASSERT(out != nullptr); | ||
| 710 | ASSERT(out_fs_data_offset != nullptr); | ||
| 711 | |||
| 712 | // Check the sparse info generation. | ||
| 713 | R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); | ||
| 714 | |||
| 715 | // Read and verify the bucket tree header. | ||
| 716 | BucketTree::Header header; | ||
| 717 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 718 | R_TRY(header.Verify()); | ||
| 719 | |||
| 720 | // Determine the storage extents. | ||
| 721 | const auto fs_offset = GetFsOffset(*m_reader, index); | ||
| 722 | const auto fs_end_offset = GetFsEndOffset(*m_reader, index); | ||
| 723 | const auto fs_size = fs_end_offset - fs_offset; | ||
| 724 | |||
| 725 | // Create the sparse storage. | ||
| 726 | std::shared_ptr<SparseStorage> sparse_storage; | ||
| 727 | if (header.entry_count != 0) { | ||
| 728 | // Create the body substorage. | ||
| 729 | VirtualFile body_substorage; | ||
| 730 | R_TRY(this->CreateBodySubStorage( | ||
| 731 | std::addressof(body_substorage), sparse_info.physical_offset, | ||
| 732 | Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + | ||
| 733 | static_cast<s64>(meta_data_hash_data_info.size), | ||
| 734 | NcaHeader::CtrBlockSize))); | ||
| 735 | |||
| 736 | // Check the meta data hash type. | ||
| 737 | R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, | ||
| 738 | ResultRomNcaInvalidSparseMetaDataHashType); | ||
| 739 | |||
| 740 | // Create the meta storage. | ||
| 741 | VirtualFile meta_storage; | ||
| 742 | R_TRY(this->CreateSparseStorageMetaStorageWithVerification( | ||
| 743 | std::addressof(meta_storage), out_layer_info_storage, body_substorage, | ||
| 744 | sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info)); | ||
| 745 | |||
| 746 | // Potentially set the output meta storage. | ||
| 747 | if (out_meta_storage != nullptr) { | ||
| 748 | *out_meta_storage = meta_storage; | ||
| 749 | } | ||
| 750 | |||
| 751 | // Create the sparse storage. | ||
| 752 | R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, | ||
| 753 | sparse_info.GetPhysicalSize(), std::move(meta_storage), | ||
| 754 | sparse_info, false)); | ||
| 755 | } else { | ||
| 756 | // If there are no entries, there's nothing to actually do. | ||
| 757 | sparse_storage = std::make_shared<SparseStorage>(); | ||
| 758 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 759 | |||
| 760 | sparse_storage->Initialize(fs_size); | ||
| 761 | } | ||
| 762 | |||
| 763 | // Potentially set the output sparse storage. | ||
| 764 | if (out_sparse_storage != nullptr) { | ||
| 765 | *out_sparse_storage = sparse_storage; | ||
| 766 | } | ||
| 767 | |||
| 768 | // Set the output fs data offset. | ||
| 769 | *out_fs_data_offset = fs_offset; | ||
| 770 | |||
| 771 | // Set the output storage. | ||
| 772 | *out = std::move(sparse_storage); | ||
| 773 | R_SUCCEED(); | ||
| 774 | } | ||
| 775 | |||
| 776 | Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage( | ||
| 777 | VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 778 | NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv, | ||
| 779 | const NcaPatchInfo& patch_info) { | ||
| 780 | // Validate preconditions. | ||
| 781 | ASSERT(out != nullptr); | ||
| 782 | ASSERT(base_storage != nullptr); | ||
| 783 | ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || | ||
| 784 | encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || | ||
| 785 | encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); | ||
| 786 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 787 | |||
| 788 | // Validate patch info extents. | ||
| 789 | R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); | ||
| 790 | R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize); | ||
| 791 | R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, | ||
| 792 | ResultInvalidNcaPatchInfoAesCtrExOffset); | ||
| 793 | |||
| 794 | // Get the base storage size. | ||
| 795 | s64 base_size = base_storage->GetSize(); | ||
| 796 | |||
| 797 | // Get and validate the meta extents. | ||
| 798 | const s64 meta_offset = patch_info.aes_ctr_ex_offset; | ||
| 799 | const s64 meta_size = | ||
| 800 | Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize); | ||
| 801 | R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 802 | |||
| 803 | // Create the encrypted storage. | ||
| 804 | auto enc_storage = | ||
| 805 | std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); | ||
| 806 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 807 | |||
| 808 | // Create the decrypted storage. | ||
| 809 | VirtualFile decrypted_storage; | ||
| 810 | if (encryption_type != NcaFsHeader::EncryptionType::None) { | ||
| 811 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 812 | offset + meta_offset, upper_iv, | ||
| 813 | AlignmentStorageRequirement::None)); | ||
| 814 | } else { | ||
| 815 | // If encryption type is none, don't do any decryption. | ||
| 816 | decrypted_storage = std::move(enc_storage); | ||
| 817 | } | ||
| 818 | |||
| 819 | // Create meta storage. | ||
| 820 | auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0); | ||
| 821 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 822 | |||
| 823 | // Create buffered storage. | ||
| 824 | std::vector<u8> meta_data(meta_size); | ||
| 825 | meta_storage->Read(meta_data.data(), meta_size, 0); | ||
| 826 | |||
| 827 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 828 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 829 | |||
| 830 | // Set the output. | ||
| 831 | *out = std::move(buffered_storage); | ||
| 832 | R_SUCCEED(); | ||
| 833 | } | ||
| 834 | |||
| 835 | Result NcaFileSystemDriver::CreateAesCtrExStorage( | ||
| 836 | VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, | ||
| 837 | VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset, | ||
| 838 | const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) { | ||
| 839 | // Validate pre-conditions. | ||
| 840 | ASSERT(out != nullptr); | ||
| 841 | ASSERT(base_storage != nullptr); | ||
| 842 | ASSERT(meta_storage != nullptr); | ||
| 843 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 844 | |||
| 845 | // Read the bucket tree header. | ||
| 846 | BucketTree::Header header; | ||
| 847 | std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header)); | ||
| 848 | R_TRY(header.Verify()); | ||
| 849 | |||
| 850 | // Determine the bucket extents. | ||
| 851 | const auto entry_count = header.entry_count; | ||
| 852 | const s64 data_offset = 0; | ||
| 853 | const s64 data_size = patch_info.aes_ctr_ex_offset; | ||
| 854 | const s64 node_offset = 0; | ||
| 855 | const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); | ||
| 856 | const s64 entry_offset = node_offset + node_size; | ||
| 857 | const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); | ||
| 858 | |||
| 859 | // Create bucket storages. | ||
| 860 | auto data_storage = | ||
| 861 | std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset); | ||
| 862 | auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset); | ||
| 863 | auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset); | ||
| 864 | |||
| 865 | // Get the secure value. | ||
| 866 | const auto secure_value = upper_iv.part.secure_value; | ||
| 867 | |||
| 868 | // Create the aes ctr ex storage. | ||
| 869 | VirtualFile aes_ctr_ex_storage; | ||
| 870 | if (m_reader->HasExternalDecryptionKey()) { | ||
| 871 | // Create the decryptor. | ||
| 872 | std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor; | ||
| 873 | R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor))); | ||
| 874 | |||
| 875 | // Create the aes ctr ex storage. | ||
| 876 | auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>(); | ||
| 877 | R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 878 | |||
| 879 | // Initialize the aes ctr ex storage. | ||
| 880 | R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, | ||
| 881 | secure_value, counter_offset, data_storage, node_storage, | ||
| 882 | entry_storage, entry_count, std::move(decryptor))); | ||
| 883 | |||
| 884 | // Potentially set the output implementation storage. | ||
| 885 | if (out_ext != nullptr) { | ||
| 886 | *out_ext = impl_storage; | ||
| 887 | } | ||
| 888 | |||
| 889 | // Set the implementation storage. | ||
| 890 | aes_ctr_ex_storage = std::move(impl_storage); | ||
| 891 | } else { | ||
| 892 | // Create the software decryptor. | ||
| 893 | std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor; | ||
| 894 | R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); | ||
| 895 | |||
| 896 | // Make the software storage. | ||
| 897 | auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>(); | ||
| 898 | R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 899 | |||
| 900 | // Initialize the software storage. | ||
| 901 | R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), | ||
| 902 | AesCtrStorage::KeySize, secure_value, counter_offset, | ||
| 903 | data_storage, node_storage, entry_storage, entry_count, | ||
| 904 | std::move(sw_decryptor))); | ||
| 905 | |||
| 906 | // Potentially set the output implementation storage. | ||
| 907 | if (out_ext != nullptr) { | ||
| 908 | *out_ext = sw_storage; | ||
| 909 | } | ||
| 910 | |||
| 911 | // Set the implementation storage. | ||
| 912 | aes_ctr_ex_storage = std::move(sw_storage); | ||
| 913 | } | ||
| 914 | |||
| 915 | // Create an alignment-matching storage. | ||
| 916 | using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; | ||
| 917 | auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage)); | ||
| 918 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 919 | |||
| 920 | // Set the output. | ||
| 921 | *out = std::move(aligned_storage); | ||
| 922 | R_SUCCEED(); | ||
| 923 | } | ||
| 924 | |||
| 925 | Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out, | ||
| 926 | VirtualFile base_storage, | ||
| 927 | const NcaPatchInfo& patch_info) { | ||
| 928 | // Validate preconditions. | ||
| 929 | ASSERT(out != nullptr); | ||
| 930 | ASSERT(base_storage != nullptr); | ||
| 931 | ASSERT(patch_info.HasIndirectTable()); | ||
| 932 | |||
| 933 | // Get the base storage size. | ||
| 934 | s64 base_size = base_storage->GetSize(); | ||
| 935 | |||
| 936 | // Check that we're within range. | ||
| 937 | R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, | ||
| 938 | ResultNcaBaseStorageOutOfRangeE); | ||
| 939 | |||
| 940 | // Create the meta storage. | ||
| 941 | auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size, | ||
| 942 | patch_info.indirect_offset); | ||
| 943 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 944 | |||
| 945 | // Create buffered storage. | ||
| 946 | std::vector<u8> meta_data(patch_info.indirect_size); | ||
| 947 | meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0); | ||
| 948 | |||
| 949 | auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); | ||
| 950 | R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 951 | |||
| 952 | // Set the output. | ||
| 953 | *out = std::move(buffered_storage); | ||
| 954 | R_SUCCEED(); | ||
| 955 | } | ||
| 956 | |||
| 957 | Result NcaFileSystemDriver::CreateIndirectStorage( | ||
| 958 | VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage, | ||
| 959 | VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) { | ||
| 960 | // Validate preconditions. | ||
| 961 | ASSERT(out != nullptr); | ||
| 962 | ASSERT(base_storage != nullptr); | ||
| 963 | ASSERT(meta_storage != nullptr); | ||
| 964 | ASSERT(patch_info.HasIndirectTable()); | ||
| 965 | |||
| 966 | // Read the bucket tree header. | ||
| 967 | BucketTree::Header header; | ||
| 968 | std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header)); | ||
| 969 | R_TRY(header.Verify()); | ||
| 970 | |||
| 971 | // Determine the storage sizes. | ||
| 972 | const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); | ||
| 973 | const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); | ||
| 974 | R_UNLESS(node_size + entry_size <= patch_info.indirect_size, | ||
| 975 | ResultInvalidNcaIndirectStorageOutOfRange); | ||
| 976 | |||
| 977 | // Get the indirect data size. | ||
| 978 | const s64 indirect_data_size = patch_info.indirect_offset; | ||
| 979 | ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); | ||
| 980 | |||
| 981 | // Create the indirect data storage. | ||
| 982 | auto indirect_data_storage = | ||
| 983 | std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0); | ||
| 984 | R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 985 | |||
| 986 | // Create the indirect storage. | ||
| 987 | auto indirect_storage = std::make_shared<IndirectStorage>(); | ||
| 988 | R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 989 | |||
| 990 | // Initialize the indirect storage. | ||
| 991 | R_TRY(indirect_storage->Initialize( | ||
| 992 | std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0), | ||
| 993 | std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count)); | ||
| 994 | |||
| 995 | // Get the original data size. | ||
| 996 | s64 original_data_size = original_data_storage->GetSize(); | ||
| 997 | |||
| 998 | // Set the indirect storages. | ||
| 999 | indirect_storage->SetStorage( | ||
| 1000 | 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0)); | ||
| 1001 | indirect_storage->SetStorage( | ||
| 1002 | 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0)); | ||
| 1003 | |||
| 1004 | // If necessary, set the output indirect storage. | ||
| 1005 | if (out_ind != nullptr) { | ||
| 1006 | *out_ind = indirect_storage; | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | // Set the output. | ||
| 1010 | *out = std::move(indirect_storage); | ||
| 1011 | R_SUCCEED(); | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | Result NcaFileSystemDriver::CreatePatchMetaStorage( | ||
| 1015 | VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, | ||
| 1016 | VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 1017 | const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info, | ||
| 1018 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 1019 | // Validate preconditions. | ||
| 1020 | ASSERT(out_aes_ctr_ex_meta != nullptr); | ||
| 1021 | ASSERT(out_indirect_meta != nullptr); | ||
| 1022 | ASSERT(base_storage != nullptr); | ||
| 1023 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 1024 | ASSERT(patch_info.HasIndirectTable()); | ||
| 1025 | ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); | ||
| 1026 | |||
| 1027 | // Validate patch info extents. | ||
| 1028 | R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); | ||
| 1029 | R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize); | ||
| 1030 | R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, | ||
| 1031 | ResultInvalidNcaPatchInfoAesCtrExOffset); | ||
| 1032 | R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= | ||
| 1033 | meta_data_hash_data_info.offset, | ||
| 1034 | ResultRomNcaInvalidPatchMetaDataHashDataOffset); | ||
| 1035 | |||
| 1036 | // Get the base storage size. | ||
| 1037 | s64 base_size = base_storage->GetSize(); | ||
| 1038 | |||
| 1039 | // Check that extents remain within range. | ||
| 1040 | R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, | ||
| 1041 | ResultNcaBaseStorageOutOfRangeE); | ||
| 1042 | R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, | ||
| 1043 | ResultNcaBaseStorageOutOfRangeB); | ||
| 1044 | |||
| 1045 | // Check that metadata hash data extents remain within range. | ||
| 1046 | const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; | ||
| 1047 | const s64 meta_data_hash_data_size = | ||
| 1048 | Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); | ||
| 1049 | R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, | ||
| 1050 | ResultNcaBaseStorageOutOfRangeB); | ||
| 1051 | |||
| 1052 | // Create the encrypted storage. | ||
| 1053 | auto enc_storage = std::make_shared<OffsetVfsFile>( | ||
| 1054 | std::move(base_storage), | ||
| 1055 | meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset, | ||
| 1056 | patch_info.indirect_offset); | ||
| 1057 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1058 | |||
| 1059 | // Create the decrypted storage. | ||
| 1060 | VirtualFile decrypted_storage; | ||
| 1061 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 1062 | offset + patch_info.indirect_offset, upper_iv, | ||
| 1063 | AlignmentStorageRequirement::None)); | ||
| 1064 | |||
| 1065 | // Create the verification storage. | ||
| 1066 | VirtualFile integrity_storage; | ||
| 1067 | Result rc = this->CreateIntegrityVerificationStorageForMeta( | ||
| 1068 | std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), | ||
| 1069 | patch_info.indirect_offset, meta_data_hash_data_info); | ||
| 1070 | if (rc == ResultInvalidNcaMetaDataHashDataSize) { | ||
| 1071 | R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize); | ||
| 1072 | } | ||
| 1073 | if (rc == ResultInvalidNcaMetaDataHashDataHash) { | ||
| 1074 | R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash); | ||
| 1075 | } | ||
| 1076 | R_TRY(rc); | ||
| 1077 | |||
| 1078 | // Create the indirect meta storage. | ||
| 1079 | auto indirect_meta_storage = | ||
| 1080 | std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size, | ||
| 1081 | patch_info.indirect_offset - patch_info.indirect_offset); | ||
| 1082 | R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1083 | |||
| 1084 | // Create the aes ctr ex meta storage. | ||
| 1085 | auto aes_ctr_ex_meta_storage = | ||
| 1086 | std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size, | ||
| 1087 | patch_info.aes_ctr_ex_offset - patch_info.indirect_offset); | ||
| 1088 | R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1089 | |||
| 1090 | // Set the output. | ||
| 1091 | *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); | ||
| 1092 | *out_indirect_meta = std::move(indirect_meta_storage); | ||
| 1093 | R_SUCCEED(); | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | Result NcaFileSystemDriver::CreateSha256Storage( | ||
| 1097 | VirtualFile* out, VirtualFile base_storage, | ||
| 1098 | const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { | ||
| 1099 | // Validate preconditions. | ||
| 1100 | ASSERT(out != nullptr); | ||
| 1101 | ASSERT(base_storage != nullptr); | ||
| 1102 | |||
| 1103 | // Define storage types. | ||
| 1104 | using VerificationStorage = HierarchicalSha256Storage; | ||
| 1105 | |||
| 1106 | // Validate the hash data. | ||
| 1107 | R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size), | ||
| 1108 | ResultInvalidHierarchicalSha256BlockSize); | ||
| 1109 | R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, | ||
| 1110 | ResultInvalidHierarchicalSha256LayerCount); | ||
| 1111 | |||
| 1112 | // Get the regions. | ||
| 1113 | const auto& hash_region = hash_data.hash_layer_region[0]; | ||
| 1114 | const auto& data_region = hash_data.hash_layer_region[1]; | ||
| 1115 | |||
| 1116 | // Determine buffer sizes. | ||
| 1117 | constexpr s32 CacheBlockCount = 2; | ||
| 1118 | const auto hash_buffer_size = static_cast<size_t>(hash_region.size); | ||
| 1119 | const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; | ||
| 1120 | const auto total_buffer_size = hash_buffer_size + cache_buffer_size; | ||
| 1121 | |||
| 1122 | // Make a buffer holder storage. | ||
| 1123 | auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>( | ||
| 1124 | std::move(base_storage), total_buffer_size); | ||
| 1125 | R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1126 | R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI); | ||
| 1127 | |||
| 1128 | // Get storage size. | ||
| 1129 | s64 base_size = buffer_hold_storage->GetSize(); | ||
| 1130 | |||
| 1131 | // Check that we're within range. | ||
| 1132 | R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); | ||
| 1133 | R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); | ||
| 1134 | |||
| 1135 | // Create the master hash storage. | ||
| 1136 | auto master_hash_storage = | ||
| 1137 | std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value); | ||
| 1138 | |||
| 1139 | // Make the verification storage. | ||
| 1140 | auto verification_storage = std::make_shared<VerificationStorage>(); | ||
| 1141 | R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1142 | |||
| 1143 | // Make layer storages. | ||
| 1144 | std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{ | ||
| 1145 | std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0), | ||
| 1146 | std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset), | ||
| 1147 | std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset), | ||
| 1148 | }; | ||
| 1149 | |||
| 1150 | // Initialize the verification storage. | ||
| 1151 | R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount, | ||
| 1152 | hash_data.hash_block_size, | ||
| 1153 | buffer_hold_storage->GetBuffer(), hash_buffer_size)); | ||
| 1154 | |||
| 1155 | // Set the output. | ||
| 1156 | *out = std::move(verification_storage); | ||
| 1157 | R_SUCCEED(); | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorage( | ||
| 1161 | VirtualFile* out, VirtualFile base_storage, | ||
| 1162 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) { | ||
| 1163 | R_RETURN(this->CreateIntegrityVerificationStorageImpl( | ||
| 1164 | out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, | ||
| 1165 | HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel( | ||
| 1166 | meta_info.level_hash_info.max_layers))); | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta( | ||
| 1170 | VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 1171 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 1172 | // Validate preconditions. | ||
| 1173 | ASSERT(out != nullptr); | ||
| 1174 | |||
| 1175 | // Check the meta data hash data size. | ||
| 1176 | R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), | ||
| 1177 | ResultInvalidNcaMetaDataHashDataSize); | ||
| 1178 | |||
| 1179 | // Read the meta data hash data. | ||
| 1180 | NcaMetaDataHashData meta_data_hash_data; | ||
| 1181 | base_storage->ReadObject(std::addressof(meta_data_hash_data), | ||
| 1182 | meta_data_hash_data_info.offset - offset); | ||
| 1183 | |||
| 1184 | // Set the out layer info storage, if necessary. | ||
| 1185 | if (out_layer_info_storage != nullptr) { | ||
| 1186 | auto layer_info_storage = std::make_shared<OffsetVfsFile>( | ||
| 1187 | base_storage, | ||
| 1188 | meta_data_hash_data_info.offset + meta_data_hash_data_info.size - | ||
| 1189 | meta_data_hash_data.layer_info_offset, | ||
| 1190 | meta_data_hash_data.layer_info_offset - offset); | ||
| 1191 | R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1192 | |||
| 1193 | *out_layer_info_storage = std::move(layer_info_storage); | ||
| 1194 | } | ||
| 1195 | |||
| 1196 | // Create the meta storage. | ||
| 1197 | auto meta_storage = std::make_shared<OffsetVfsFile>( | ||
| 1198 | std::move(base_storage), meta_data_hash_data_info.offset - offset, 0); | ||
| 1199 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1200 | |||
| 1201 | // Create the integrity verification storage. | ||
| 1202 | R_RETURN(this->CreateIntegrityVerificationStorageImpl( | ||
| 1203 | out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, | ||
| 1204 | meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, | ||
| 1205 | IntegrityHashCacheCountForMeta, 0)); | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( | ||
| 1209 | VirtualFile* out, VirtualFile base_storage, | ||
| 1210 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, | ||
| 1211 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { | ||
| 1212 | // Validate preconditions. | ||
| 1213 | ASSERT(out != nullptr); | ||
| 1214 | ASSERT(base_storage != nullptr); | ||
| 1215 | ASSERT(layer_info_offset >= 0); | ||
| 1216 | |||
| 1217 | // Define storage types. | ||
| 1218 | using VerificationStorage = HierarchicalIntegrityVerificationStorage; | ||
| 1219 | using StorageInfo = VerificationStorage::HierarchicalStorageInformation; | ||
| 1220 | |||
| 1221 | // Validate the meta info. | ||
| 1222 | HierarchicalIntegrityVerificationInformation level_hash_info; | ||
| 1223 | std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), | ||
| 1224 | sizeof(level_hash_info)); | ||
| 1225 | |||
| 1226 | R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, | ||
| 1227 | ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); | ||
| 1228 | R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, | ||
| 1229 | ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); | ||
| 1230 | |||
| 1231 | // Get the base storage size. | ||
| 1232 | s64 base_storage_size = base_storage->GetSize(); | ||
| 1233 | |||
| 1234 | // Create storage info. | ||
| 1235 | StorageInfo storage_info; | ||
| 1236 | for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) { | ||
| 1237 | const auto& layer_info = level_hash_info.info[i]; | ||
| 1238 | R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, | ||
| 1239 | ResultNcaBaseStorageOutOfRangeD); | ||
| 1240 | |||
| 1241 | storage_info[i + 1] = std::make_shared<OffsetVfsFile>( | ||
| 1242 | base_storage, layer_info.size, layer_info_offset + layer_info.offset); | ||
| 1243 | } | ||
| 1244 | |||
| 1245 | // Set the last layer info. | ||
| 1246 | const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; | ||
| 1247 | const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); | ||
| 1248 | R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, | ||
| 1249 | ResultNcaBaseStorageOutOfRangeD); | ||
| 1250 | if (layer_info_offset > 0) { | ||
| 1251 | R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, | ||
| 1252 | ResultRomNcaInvalidIntegrityLayerInfoOffset); | ||
| 1253 | } | ||
| 1254 | storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>( | ||
| 1255 | std::move(base_storage), layer_info.size, last_layer_info_offset)); | ||
| 1256 | |||
| 1257 | // Make the integrity romfs storage. | ||
| 1258 | auto integrity_storage = std::make_shared<IntegrityRomFsStorage>(); | ||
| 1259 | R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1260 | |||
| 1261 | // Initialize the integrity storage. | ||
| 1262 | R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, | ||
| 1263 | max_data_cache_entries, max_hash_cache_entries, | ||
| 1264 | buffer_level)); | ||
| 1265 | |||
| 1266 | // Set the output. | ||
| 1267 | *out = std::move(integrity_storage); | ||
| 1268 | R_SUCCEED(); | ||
| 1269 | } | ||
| 1270 | |||
| 1271 | Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, | ||
| 1272 | const NcaFsHeaderReader* header_reader, | ||
| 1273 | VirtualFile inside_storage, | ||
| 1274 | VirtualFile outside_storage) { | ||
| 1275 | // Check pre-conditions. | ||
| 1276 | ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); | ||
| 1277 | |||
| 1278 | // Create the region. | ||
| 1279 | RegionSwitchStorage::Region region = {}; | ||
| 1280 | R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); | ||
| 1281 | |||
| 1282 | // Create the region switch storage. | ||
| 1283 | auto region_switch_storage = std::make_shared<RegionSwitchStorage>( | ||
| 1284 | std::move(inside_storage), std::move(outside_storage), region); | ||
| 1285 | R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1286 | |||
| 1287 | // Set the output. | ||
| 1288 | *out = std::move(region_switch_storage); | ||
| 1289 | R_SUCCEED(); | ||
| 1290 | } | ||
| 1291 | |||
| 1292 | Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, | ||
| 1293 | std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 1294 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 1295 | const NcaCompressionInfo& compression_info) { | ||
| 1296 | R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), | ||
| 1297 | compression_info, m_reader->GetDecompressor())); | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, | ||
| 1301 | std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 1302 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 1303 | const NcaCompressionInfo& compression_info, | ||
| 1304 | GetDecompressorFunction get_decompressor) { | ||
| 1305 | // Check pre-conditions. | ||
| 1306 | ASSERT(out != nullptr); | ||
| 1307 | ASSERT(base_storage != nullptr); | ||
| 1308 | ASSERT(get_decompressor != nullptr); | ||
| 1309 | |||
| 1310 | // Read and verify the bucket tree header. | ||
| 1311 | BucketTree::Header header; | ||
| 1312 | std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header)); | ||
| 1313 | R_TRY(header.Verify()); | ||
| 1314 | |||
| 1315 | // Determine the storage extents. | ||
| 1316 | const auto table_offset = compression_info.bucket.offset; | ||
| 1317 | const auto table_size = compression_info.bucket.size; | ||
| 1318 | const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count); | ||
| 1319 | const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count); | ||
| 1320 | R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize); | ||
| 1321 | |||
| 1322 | // If we should, set the output meta storage. | ||
| 1323 | if (out_meta != nullptr) { | ||
| 1324 | auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset); | ||
| 1325 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1326 | |||
| 1327 | *out_meta = std::move(meta_storage); | ||
| 1328 | } | ||
| 1329 | |||
| 1330 | // Allocate the compressed storage. | ||
| 1331 | auto compressed_storage = std::make_shared<CompressedStorage>(); | ||
| 1332 | R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1333 | |||
| 1334 | // Initialize the compressed storage. | ||
| 1335 | R_TRY(compressed_storage->Initialize( | ||
| 1336 | std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0), | ||
| 1337 | std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset), | ||
| 1338 | std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size), | ||
| 1339 | header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32)); | ||
| 1340 | |||
| 1341 | // Potentially set the output compressed storage. | ||
| 1342 | if (out_cmp) { | ||
| 1343 | *out_cmp = compressed_storage; | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | // Set the output. | ||
| 1347 | *out = std::move(compressed_storage); | ||
| 1348 | R_SUCCEED(); | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h new file mode 100644 index 000000000..5771a21fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h | |||
| @@ -0,0 +1,364 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_compression_common.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class CompressedStorage; | ||
| 13 | class AesCtrCounterExtendedStorage; | ||
| 14 | class IndirectStorage; | ||
| 15 | class SparseStorage; | ||
| 16 | |||
| 17 | struct NcaCryptoConfiguration; | ||
| 18 | |||
| 19 | using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key, | ||
| 20 | size_t src_key_size, s32 key_type); | ||
| 21 | using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data, | ||
| 22 | size_t data_size, u8 generation); | ||
| 23 | |||
| 24 | struct NcaCryptoConfiguration { | ||
| 25 | static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8; | ||
| 26 | static constexpr size_t Rsa2048KeyPublicExponentSize = 3; | ||
| 27 | static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize; | ||
| 28 | |||
| 29 | static constexpr size_t Aes128KeySize = 128 / 8; | ||
| 30 | |||
| 31 | static constexpr size_t Header1SignatureKeyGenerationMax = 1; | ||
| 32 | |||
| 33 | static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; | ||
| 34 | static constexpr s32 HeaderEncryptionKeyCount = 2; | ||
| 35 | |||
| 36 | static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF; | ||
| 37 | |||
| 38 | static constexpr size_t KeyGenerationMax = 32; | ||
| 39 | |||
| 40 | std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli; | ||
| 41 | std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent; | ||
| 42 | std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount> | ||
| 43 | key_area_encryption_key_source; | ||
| 44 | std::array<u8, Aes128KeySize> header_encryption_key_source; | ||
| 45 | std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount> | ||
| 46 | header_encrypted_encryption_keys; | ||
| 47 | KeyGenerationFunction generate_key; | ||
| 48 | VerifySign1Function verify_sign1; | ||
| 49 | bool is_plaintext_header_available; | ||
| 50 | bool is_available_sw_key; | ||
| 51 | }; | ||
| 52 | static_assert(std::is_trivial_v<NcaCryptoConfiguration>); | ||
| 53 | |||
| 54 | struct NcaCompressionConfiguration { | ||
| 55 | GetDecompressorFunction get_decompressor; | ||
| 56 | }; | ||
| 57 | static_assert(std::is_trivial_v<NcaCompressionConfiguration>); | ||
| 58 | |||
| 59 | constexpr inline s32 KeyAreaEncryptionKeyCount = | ||
| 60 | NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * | ||
| 61 | NcaCryptoConfiguration::KeyGenerationMax; | ||
| 62 | |||
| 63 | enum class KeyType : s32 { | ||
| 64 | ZeroKey = -2, | ||
| 65 | InvalidKey = -1, | ||
| 66 | NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0, | ||
| 67 | NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1, | ||
| 68 | NcaExternalKey = KeyAreaEncryptionKeyCount + 2, | ||
| 69 | SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3, | ||
| 70 | SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4, | ||
| 71 | SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5, | ||
| 72 | }; | ||
| 73 | |||
| 74 | constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { | ||
| 75 | return key_type < 0; | ||
| 76 | } | ||
| 77 | |||
| 78 | constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { | ||
| 79 | if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) { | ||
| 80 | return static_cast<s32>(KeyType::ZeroKey); | ||
| 81 | } | ||
| 82 | |||
| 83 | if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { | ||
| 84 | return static_cast<s32>(KeyType::InvalidKey); | ||
| 85 | } | ||
| 86 | |||
| 87 | return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; | ||
| 88 | } | ||
| 89 | |||
| 90 | class NcaReader { | ||
| 91 | YUZU_NON_COPYABLE(NcaReader); | ||
| 92 | YUZU_NON_MOVEABLE(NcaReader); | ||
| 93 | |||
| 94 | public: | ||
| 95 | NcaReader(); | ||
| 96 | ~NcaReader(); | ||
| 97 | |||
| 98 | Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, | ||
| 99 | const NcaCompressionConfiguration& compression_cfg); | ||
| 100 | |||
| 101 | VirtualFile GetSharedBodyStorage(); | ||
| 102 | u32 GetMagic() const; | ||
| 103 | NcaHeader::DistributionType GetDistributionType() const; | ||
| 104 | NcaHeader::ContentType GetContentType() const; | ||
| 105 | u8 GetHeaderSign1KeyGeneration() const; | ||
| 106 | u8 GetKeyGeneration() const; | ||
| 107 | u8 GetKeyIndex() const; | ||
| 108 | u64 GetContentSize() const; | ||
| 109 | u64 GetProgramId() const; | ||
| 110 | u32 GetContentIndex() const; | ||
| 111 | u32 GetSdkAddonVersion() const; | ||
| 112 | void GetRightsId(u8* dst, size_t dst_size) const; | ||
| 113 | bool HasFsInfo(s32 index) const; | ||
| 114 | s32 GetFsCount() const; | ||
| 115 | const Hash& GetFsHeaderHash(s32 index) const; | ||
| 116 | void GetFsHeaderHash(Hash* dst, s32 index) const; | ||
| 117 | void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const; | ||
| 118 | u64 GetFsOffset(s32 index) const; | ||
| 119 | u64 GetFsEndOffset(s32 index) const; | ||
| 120 | u64 GetFsSize(s32 index) const; | ||
| 121 | void GetEncryptedKey(void* dst, size_t size) const; | ||
| 122 | const void* GetDecryptionKey(s32 index) const; | ||
| 123 | bool HasValidInternalKey() const; | ||
| 124 | bool HasInternalDecryptionKeyForAesHw() const; | ||
| 125 | bool IsSoftwareAesPrioritized() const; | ||
| 126 | void PrioritizeSoftwareAes(); | ||
| 127 | bool IsAvailableSwKey() const; | ||
| 128 | bool HasExternalDecryptionKey() const; | ||
| 129 | const void* GetExternalDecryptionKey() const; | ||
| 130 | void SetExternalDecryptionKey(const void* src, size_t size); | ||
| 131 | void GetRawData(void* dst, size_t dst_size) const; | ||
| 132 | NcaHeader::EncryptionType GetEncryptionType() const; | ||
| 133 | Result ReadHeader(NcaFsHeader* dst, s32 index) const; | ||
| 134 | |||
| 135 | GetDecompressorFunction GetDecompressor() const; | ||
| 136 | |||
| 137 | bool GetHeaderSign1Valid() const; | ||
| 138 | |||
| 139 | void GetHeaderSign2(void* dst, size_t size) const; | ||
| 140 | |||
| 141 | private: | ||
| 142 | NcaHeader m_header; | ||
| 143 | std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, | ||
| 144 | NcaHeader::DecryptionKey_Count> | ||
| 145 | m_decryption_keys; | ||
| 146 | VirtualFile m_body_storage; | ||
| 147 | VirtualFile m_header_storage; | ||
| 148 | std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key; | ||
| 149 | bool m_is_software_aes_prioritized; | ||
| 150 | bool m_is_available_sw_key; | ||
| 151 | NcaHeader::EncryptionType m_header_encryption_type; | ||
| 152 | bool m_is_header_sign1_signature_valid; | ||
| 153 | GetDecompressorFunction m_get_decompressor; | ||
| 154 | }; | ||
| 155 | |||
| 156 | class NcaFsHeaderReader { | ||
| 157 | YUZU_NON_COPYABLE(NcaFsHeaderReader); | ||
| 158 | YUZU_NON_MOVEABLE(NcaFsHeaderReader); | ||
| 159 | |||
| 160 | public: | ||
| 161 | NcaFsHeaderReader() : m_fs_index(-1) { | ||
| 162 | std::memset(std::addressof(m_data), 0, sizeof(m_data)); | ||
| 163 | } | ||
| 164 | |||
| 165 | Result Initialize(const NcaReader& reader, s32 index); | ||
| 166 | bool IsInitialized() const { | ||
| 167 | return m_fs_index >= 0; | ||
| 168 | } | ||
| 169 | |||
| 170 | void GetRawData(void* dst, size_t dst_size) const; | ||
| 171 | |||
| 172 | NcaFsHeader::HashData& GetHashData(); | ||
| 173 | const NcaFsHeader::HashData& GetHashData() const; | ||
| 174 | u16 GetVersion() const; | ||
| 175 | s32 GetFsIndex() const; | ||
| 176 | NcaFsHeader::FsType GetFsType() const; | ||
| 177 | NcaFsHeader::HashType GetHashType() const; | ||
| 178 | NcaFsHeader::EncryptionType GetEncryptionType() const; | ||
| 179 | NcaPatchInfo& GetPatchInfo(); | ||
| 180 | const NcaPatchInfo& GetPatchInfo() const; | ||
| 181 | const NcaAesCtrUpperIv GetAesCtrUpperIv() const; | ||
| 182 | |||
| 183 | bool IsSkipLayerHashEncryption() const; | ||
| 184 | Result GetHashTargetOffset(s64* out) const; | ||
| 185 | |||
| 186 | bool ExistsSparseLayer() const; | ||
| 187 | NcaSparseInfo& GetSparseInfo(); | ||
| 188 | const NcaSparseInfo& GetSparseInfo() const; | ||
| 189 | |||
| 190 | bool ExistsCompressionLayer() const; | ||
| 191 | NcaCompressionInfo& GetCompressionInfo(); | ||
| 192 | const NcaCompressionInfo& GetCompressionInfo() const; | ||
| 193 | |||
| 194 | bool ExistsPatchMetaHashLayer() const; | ||
| 195 | NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo(); | ||
| 196 | const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const; | ||
| 197 | NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const; | ||
| 198 | |||
| 199 | bool ExistsSparseMetaHashLayer() const; | ||
| 200 | NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo(); | ||
| 201 | const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const; | ||
| 202 | NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const; | ||
| 203 | |||
| 204 | private: | ||
| 205 | NcaFsHeader m_data; | ||
| 206 | s32 m_fs_index; | ||
| 207 | }; | ||
| 208 | |||
| 209 | class NcaFileSystemDriver { | ||
| 210 | YUZU_NON_COPYABLE(NcaFileSystemDriver); | ||
| 211 | YUZU_NON_MOVEABLE(NcaFileSystemDriver); | ||
| 212 | |||
| 213 | public: | ||
| 214 | struct StorageContext { | ||
| 215 | bool open_raw_storage; | ||
| 216 | VirtualFile body_substorage; | ||
| 217 | std::shared_ptr<SparseStorage> current_sparse_storage; | ||
| 218 | VirtualFile sparse_storage_meta_storage; | ||
| 219 | std::shared_ptr<SparseStorage> original_sparse_storage; | ||
| 220 | void* external_current_sparse_storage; | ||
| 221 | void* external_original_sparse_storage; | ||
| 222 | VirtualFile aes_ctr_ex_storage_meta_storage; | ||
| 223 | VirtualFile aes_ctr_ex_storage_data_storage; | ||
| 224 | std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage; | ||
| 225 | VirtualFile indirect_storage_meta_storage; | ||
| 226 | std::shared_ptr<IndirectStorage> indirect_storage; | ||
| 227 | VirtualFile fs_data_storage; | ||
| 228 | VirtualFile compressed_storage_meta_storage; | ||
| 229 | std::shared_ptr<CompressedStorage> compressed_storage; | ||
| 230 | |||
| 231 | VirtualFile patch_layer_info_storage; | ||
| 232 | VirtualFile sparse_layer_info_storage; | ||
| 233 | |||
| 234 | VirtualFile external_original_storage; | ||
| 235 | }; | ||
| 236 | |||
| 237 | private: | ||
| 238 | enum class AlignmentStorageRequirement { | ||
| 239 | CacheBlockSize = 0, | ||
| 240 | None = 1, | ||
| 241 | }; | ||
| 242 | |||
| 243 | public: | ||
| 244 | static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader, | ||
| 245 | s32 fs_index); | ||
| 246 | |||
| 247 | public: | ||
| 248 | NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) { | ||
| 249 | ASSERT(m_reader != nullptr); | ||
| 250 | } | ||
| 251 | |||
| 252 | NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader, | ||
| 253 | std::shared_ptr<NcaReader> reader) | ||
| 254 | : m_original_reader(original_reader), m_reader(reader) { | ||
| 255 | ASSERT(m_reader != nullptr); | ||
| 256 | } | ||
| 257 | |||
| 258 | Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader, | ||
| 259 | s32 fs_index, StorageContext* ctx); | ||
| 260 | |||
| 261 | Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) { | ||
| 262 | // Create a storage context. | ||
| 263 | StorageContext ctx{}; | ||
| 264 | |||
| 265 | // Open the storage. | ||
| 266 | R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx))); | ||
| 267 | } | ||
| 268 | |||
| 269 | public: | ||
| 270 | Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, | ||
| 271 | VirtualFile raw_storage, StorageContext* ctx); | ||
| 272 | |||
| 273 | private: | ||
| 274 | Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index, | ||
| 275 | StorageContext* ctx); | ||
| 276 | |||
| 277 | Result OpenIndirectableStorageAsOriginal(VirtualFile* out, | ||
| 278 | const NcaFsHeaderReader* header_reader, | ||
| 279 | StorageContext* ctx); | ||
| 280 | |||
| 281 | Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size); | ||
| 282 | |||
| 283 | Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 284 | const NcaAesCtrUpperIv& upper_iv, | ||
| 285 | AlignmentStorageRequirement alignment_storage_requirement); | ||
| 286 | Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset); | ||
| 287 | |||
| 288 | Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 289 | const NcaAesCtrUpperIv& upper_iv, | ||
| 290 | const NcaSparseInfo& sparse_info); | ||
| 291 | Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage, | ||
| 292 | s64 base_size, VirtualFile meta_storage, | ||
| 293 | const NcaSparseInfo& sparse_info, bool external_info); | ||
| 294 | Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, | ||
| 295 | std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 296 | VirtualFile* out_meta_storage, s32 index, | ||
| 297 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info); | ||
| 298 | |||
| 299 | Result CreateSparseStorageMetaStorageWithVerification( | ||
| 300 | VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, | ||
| 301 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 302 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 303 | Result CreateSparseStorageWithVerification( | ||
| 304 | VirtualFile* out, s64* out_fs_data_offset, | ||
| 305 | std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage, | ||
| 306 | VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv, | ||
| 307 | const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info, | ||
| 308 | NcaFsHeader::MetaDataHashType meta_data_hash_type); | ||
| 309 | |||
| 310 | Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 311 | NcaFsHeader::EncryptionType encryption_type, | ||
| 312 | const NcaAesCtrUpperIv& upper_iv, | ||
| 313 | const NcaPatchInfo& patch_info); | ||
| 314 | Result CreateAesCtrExStorage(VirtualFile* out, | ||
| 315 | std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, | ||
| 316 | VirtualFile base_storage, VirtualFile meta_storage, | ||
| 317 | s64 counter_offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 318 | const NcaPatchInfo& patch_info); | ||
| 319 | |||
| 320 | Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, | ||
| 321 | const NcaPatchInfo& patch_info); | ||
| 322 | Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, | ||
| 323 | VirtualFile base_storage, VirtualFile original_data_storage, | ||
| 324 | VirtualFile meta_storage, const NcaPatchInfo& patch_info); | ||
| 325 | |||
| 326 | Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, | ||
| 327 | VirtualFile* out_verification, VirtualFile base_storage, | ||
| 328 | s64 offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 329 | const NcaPatchInfo& patch_info, | ||
| 330 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 331 | |||
| 332 | Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage, | ||
| 333 | const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data); | ||
| 334 | |||
| 335 | Result CreateIntegrityVerificationStorage( | ||
| 336 | VirtualFile* out, VirtualFile base_storage, | ||
| 337 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info); | ||
| 338 | Result CreateIntegrityVerificationStorageForMeta( | ||
| 339 | VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, | ||
| 340 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 341 | Result CreateIntegrityVerificationStorageImpl( | ||
| 342 | VirtualFile* out, VirtualFile base_storage, | ||
| 343 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, | ||
| 344 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); | ||
| 345 | |||
| 346 | Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, | ||
| 347 | VirtualFile inside_storage, VirtualFile outside_storage); | ||
| 348 | |||
| 349 | Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 350 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 351 | const NcaCompressionInfo& compression_info); | ||
| 352 | |||
| 353 | public: | ||
| 354 | Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 355 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 356 | const NcaCompressionInfo& compression_info, | ||
| 357 | GetDecompressorFunction get_decompressor); | ||
| 358 | |||
| 359 | private: | ||
| 360 | std::shared_ptr<NcaReader> m_original_reader; | ||
| 361 | std::shared_ptr<NcaReader> m_reader; | ||
| 362 | }; | ||
| 363 | |||
| 364 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp new file mode 100644 index 000000000..bf5742d39 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | u8 NcaHeader::GetProperKeyGeneration() const { | ||
| 9 | return std::max(this->key_generation, this->key_generation_2); | ||
| 10 | } | ||
| 11 | |||
| 12 | bool NcaPatchInfo::HasIndirectTable() const { | ||
| 13 | return this->indirect_size != 0; | ||
| 14 | } | ||
| 15 | |||
| 16 | bool NcaPatchInfo::HasAesCtrExTable() const { | ||
| 17 | return this->aes_ctr_ex_size != 0; | ||
| 18 | } | ||
| 19 | |||
| 20 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h new file mode 100644 index 000000000..a02c5d881 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.h | |||
| @@ -0,0 +1,338 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | |||
| 10 | #include "core/file_sys/errors.h" | ||
| 11 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | using namespace Common::Literals; | ||
| 16 | |||
| 17 | struct Hash { | ||
| 18 | static constexpr std::size_t Size = 256 / 8; | ||
| 19 | std::array<u8, Size> value; | ||
| 20 | }; | ||
| 21 | static_assert(sizeof(Hash) == Hash::Size); | ||
| 22 | static_assert(std::is_trivial_v<Hash>); | ||
| 23 | |||
| 24 | using NcaDigest = Hash; | ||
| 25 | |||
| 26 | struct NcaHeader { | ||
| 27 | enum class ContentType : u8 { | ||
| 28 | Program = 0, | ||
| 29 | Meta = 1, | ||
| 30 | Control = 2, | ||
| 31 | Manual = 3, | ||
| 32 | Data = 4, | ||
| 33 | PublicData = 5, | ||
| 34 | |||
| 35 | Start = Program, | ||
| 36 | End = PublicData, | ||
| 37 | }; | ||
| 38 | |||
| 39 | enum class DistributionType : u8 { | ||
| 40 | Download = 0, | ||
| 41 | GameCard = 1, | ||
| 42 | |||
| 43 | Start = Download, | ||
| 44 | End = GameCard, | ||
| 45 | }; | ||
| 46 | |||
| 47 | enum class EncryptionType : u8 { | ||
| 48 | Auto = 0, | ||
| 49 | None = 1, | ||
| 50 | }; | ||
| 51 | |||
| 52 | enum DecryptionKey { | ||
| 53 | DecryptionKey_AesXts = 0, | ||
| 54 | DecryptionKey_AesXts1 = DecryptionKey_AesXts, | ||
| 55 | DecryptionKey_AesXts2 = 1, | ||
| 56 | DecryptionKey_AesCtr = 2, | ||
| 57 | DecryptionKey_AesCtrEx = 3, | ||
| 58 | DecryptionKey_AesCtrHw = 4, | ||
| 59 | DecryptionKey_Count, | ||
| 60 | }; | ||
| 61 | |||
| 62 | struct FsInfo { | ||
| 63 | u32 start_sector; | ||
| 64 | u32 end_sector; | ||
| 65 | u32 hash_sectors; | ||
| 66 | u32 reserved; | ||
| 67 | }; | ||
| 68 | static_assert(sizeof(FsInfo) == 0x10); | ||
| 69 | static_assert(std::is_trivial_v<FsInfo>); | ||
| 70 | |||
| 71 | static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0'); | ||
| 72 | static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1'); | ||
| 73 | static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2'); | ||
| 74 | static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 75 | |||
| 76 | static constexpr u32 Magic = Magic3; | ||
| 77 | |||
| 78 | static constexpr std::size_t Size = 1_KiB; | ||
| 79 | static constexpr s32 FsCountMax = 4; | ||
| 80 | static constexpr std::size_t HeaderSignCount = 2; | ||
| 81 | static constexpr std::size_t HeaderSignSize = 0x100; | ||
| 82 | static constexpr std::size_t EncryptedKeyAreaSize = 0x100; | ||
| 83 | static constexpr std::size_t SectorSize = 0x200; | ||
| 84 | static constexpr std::size_t SectorShift = 9; | ||
| 85 | static constexpr std::size_t RightsIdSize = 0x10; | ||
| 86 | static constexpr std::size_t XtsBlockSize = 0x200; | ||
| 87 | static constexpr std::size_t CtrBlockSize = 0x10; | ||
| 88 | |||
| 89 | static_assert(SectorSize == (1 << SectorShift)); | ||
| 90 | |||
| 91 | // Data members. | ||
| 92 | std::array<u8, HeaderSignSize> header_sign_1; | ||
| 93 | std::array<u8, HeaderSignSize> header_sign_2; | ||
| 94 | u32 magic; | ||
| 95 | DistributionType distribution_type; | ||
| 96 | ContentType content_type; | ||
| 97 | u8 key_generation; | ||
| 98 | u8 key_index; | ||
| 99 | u64 content_size; | ||
| 100 | u64 program_id; | ||
| 101 | u32 content_index; | ||
| 102 | u32 sdk_addon_version; | ||
| 103 | u8 key_generation_2; | ||
| 104 | u8 header1_signature_key_generation; | ||
| 105 | std::array<u8, 2> reserved_222; | ||
| 106 | std::array<u32, 3> reserved_224; | ||
| 107 | std::array<u8, RightsIdSize> rights_id; | ||
| 108 | std::array<FsInfo, FsCountMax> fs_info; | ||
| 109 | std::array<Hash, FsCountMax> fs_header_hash; | ||
| 110 | std::array<u8, EncryptedKeyAreaSize> encrypted_key_area; | ||
| 111 | |||
| 112 | static constexpr u64 SectorToByte(u32 sector) { | ||
| 113 | return static_cast<u64>(sector) << SectorShift; | ||
| 114 | } | ||
| 115 | |||
| 116 | static constexpr u32 ByteToSector(u64 byte) { | ||
| 117 | return static_cast<u32>(byte >> SectorShift); | ||
| 118 | } | ||
| 119 | |||
| 120 | u8 GetProperKeyGeneration() const; | ||
| 121 | }; | ||
| 122 | static_assert(sizeof(NcaHeader) == NcaHeader::Size); | ||
| 123 | static_assert(std::is_trivial_v<NcaHeader>); | ||
| 124 | |||
| 125 | struct NcaBucketInfo { | ||
| 126 | static constexpr size_t HeaderSize = 0x10; | ||
| 127 | Int64 offset; | ||
| 128 | Int64 size; | ||
| 129 | std::array<u8, HeaderSize> header; | ||
| 130 | }; | ||
| 131 | static_assert(std::is_trivial_v<NcaBucketInfo>); | ||
| 132 | |||
| 133 | struct NcaPatchInfo { | ||
| 134 | static constexpr size_t Size = 0x40; | ||
| 135 | static constexpr size_t Offset = 0x100; | ||
| 136 | |||
| 137 | Int64 indirect_offset; | ||
| 138 | Int64 indirect_size; | ||
| 139 | std::array<u8, NcaBucketInfo::HeaderSize> indirect_header; | ||
| 140 | Int64 aes_ctr_ex_offset; | ||
| 141 | Int64 aes_ctr_ex_size; | ||
| 142 | std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header; | ||
| 143 | |||
| 144 | bool HasIndirectTable() const; | ||
| 145 | bool HasAesCtrExTable() const; | ||
| 146 | }; | ||
| 147 | static_assert(std::is_trivial_v<NcaPatchInfo>); | ||
| 148 | |||
| 149 | union NcaAesCtrUpperIv { | ||
| 150 | u64 value; | ||
| 151 | struct { | ||
| 152 | u32 generation; | ||
| 153 | u32 secure_value; | ||
| 154 | } part; | ||
| 155 | }; | ||
| 156 | static_assert(std::is_trivial_v<NcaAesCtrUpperIv>); | ||
| 157 | |||
| 158 | struct NcaSparseInfo { | ||
| 159 | NcaBucketInfo bucket; | ||
| 160 | Int64 physical_offset; | ||
| 161 | u16 generation; | ||
| 162 | std::array<u8, 6> reserved; | ||
| 163 | |||
| 164 | s64 GetPhysicalSize() const { | ||
| 165 | return this->bucket.offset + this->bucket.size; | ||
| 166 | } | ||
| 167 | |||
| 168 | u32 GetGeneration() const { | ||
| 169 | return static_cast<u32>(this->generation) << 16; | ||
| 170 | } | ||
| 171 | |||
| 172 | const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { | ||
| 173 | NcaAesCtrUpperIv sparse_upper_iv = upper_iv; | ||
| 174 | sparse_upper_iv.part.generation = this->GetGeneration(); | ||
| 175 | return sparse_upper_iv; | ||
| 176 | } | ||
| 177 | }; | ||
| 178 | static_assert(std::is_trivial_v<NcaSparseInfo>); | ||
| 179 | |||
| 180 | struct NcaCompressionInfo { | ||
| 181 | NcaBucketInfo bucket; | ||
| 182 | std::array<u8, 8> resreved; | ||
| 183 | }; | ||
| 184 | static_assert(std::is_trivial_v<NcaCompressionInfo>); | ||
| 185 | |||
| 186 | struct NcaMetaDataHashDataInfo { | ||
| 187 | Int64 offset; | ||
| 188 | Int64 size; | ||
| 189 | Hash hash; | ||
| 190 | }; | ||
| 191 | static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>); | ||
| 192 | |||
| 193 | struct NcaFsHeader { | ||
| 194 | static constexpr size_t Size = 0x200; | ||
| 195 | static constexpr size_t HashDataOffset = 0x8; | ||
| 196 | |||
| 197 | struct Region { | ||
| 198 | Int64 offset; | ||
| 199 | Int64 size; | ||
| 200 | }; | ||
| 201 | static_assert(std::is_trivial_v<Region>); | ||
| 202 | |||
| 203 | enum class FsType : u8 { | ||
| 204 | RomFs = 0, | ||
| 205 | PartitionFs = 1, | ||
| 206 | }; | ||
| 207 | |||
| 208 | enum class EncryptionType : u8 { | ||
| 209 | Auto = 0, | ||
| 210 | None = 1, | ||
| 211 | AesXts = 2, | ||
| 212 | AesCtr = 3, | ||
| 213 | AesCtrEx = 4, | ||
| 214 | AesCtrSkipLayerHash = 5, | ||
| 215 | AesCtrExSkipLayerHash = 6, | ||
| 216 | }; | ||
| 217 | |||
| 218 | enum class HashType : u8 { | ||
| 219 | Auto = 0, | ||
| 220 | None = 1, | ||
| 221 | HierarchicalSha256Hash = 2, | ||
| 222 | HierarchicalIntegrityHash = 3, | ||
| 223 | AutoSha3 = 4, | ||
| 224 | HierarchicalSha3256Hash = 5, | ||
| 225 | HierarchicalIntegritySha3Hash = 6, | ||
| 226 | }; | ||
| 227 | |||
| 228 | enum class MetaDataHashType : u8 { | ||
| 229 | None = 0, | ||
| 230 | HierarchicalIntegrity = 1, | ||
| 231 | }; | ||
| 232 | |||
| 233 | union HashData { | ||
| 234 | struct HierarchicalSha256Data { | ||
| 235 | static constexpr size_t HashLayerCountMax = 5; | ||
| 236 | static const size_t MasterHashOffset; | ||
| 237 | |||
| 238 | Hash fs_data_master_hash; | ||
| 239 | s32 hash_block_size; | ||
| 240 | s32 hash_layer_count; | ||
| 241 | std::array<Region, HashLayerCountMax> hash_layer_region; | ||
| 242 | } hierarchical_sha256_data; | ||
| 243 | static_assert(std::is_trivial_v<HierarchicalSha256Data>); | ||
| 244 | |||
| 245 | struct IntegrityMetaInfo { | ||
| 246 | static const size_t MasterHashOffset; | ||
| 247 | |||
| 248 | u32 magic; | ||
| 249 | u32 version; | ||
| 250 | u32 master_hash_size; | ||
| 251 | |||
| 252 | struct LevelHashInfo { | ||
| 253 | u32 max_layers; | ||
| 254 | |||
| 255 | struct HierarchicalIntegrityVerificationLevelInformation { | ||
| 256 | static constexpr size_t IntegrityMaxLayerCount = 7; | ||
| 257 | Int64 offset; | ||
| 258 | Int64 size; | ||
| 259 | s32 block_order; | ||
| 260 | std::array<u8, 4> reserved; | ||
| 261 | }; | ||
| 262 | std::array< | ||
| 263 | HierarchicalIntegrityVerificationLevelInformation, | ||
| 264 | HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1> | ||
| 265 | info; | ||
| 266 | |||
| 267 | struct SignatureSalt { | ||
| 268 | static constexpr size_t Size = 0x20; | ||
| 269 | std::array<u8, Size> value; | ||
| 270 | }; | ||
| 271 | SignatureSalt seed; | ||
| 272 | } level_hash_info; | ||
| 273 | |||
| 274 | Hash master_hash; | ||
| 275 | } integrity_meta_info; | ||
| 276 | static_assert(std::is_trivial_v<IntegrityMetaInfo>); | ||
| 277 | |||
| 278 | std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding; | ||
| 279 | }; | ||
| 280 | |||
| 281 | u16 version; | ||
| 282 | FsType fs_type; | ||
| 283 | HashType hash_type; | ||
| 284 | EncryptionType encryption_type; | ||
| 285 | MetaDataHashType meta_data_hash_type; | ||
| 286 | std::array<u8, 2> reserved; | ||
| 287 | HashData hash_data; | ||
| 288 | NcaPatchInfo patch_info; | ||
| 289 | NcaAesCtrUpperIv aes_ctr_upper_iv; | ||
| 290 | NcaSparseInfo sparse_info; | ||
| 291 | NcaCompressionInfo compression_info; | ||
| 292 | NcaMetaDataHashDataInfo meta_data_hash_data_info; | ||
| 293 | std::array<u8, 0x30> pad; | ||
| 294 | |||
| 295 | bool IsSkipLayerHashEncryption() const { | ||
| 296 | return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || | ||
| 297 | this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; | ||
| 298 | } | ||
| 299 | |||
| 300 | Result GetHashTargetOffset(s64* out) const { | ||
| 301 | switch (this->hash_type) { | ||
| 302 | case HashType::HierarchicalIntegrityHash: | ||
| 303 | case HashType::HierarchicalIntegritySha3Hash: | ||
| 304 | *out = this->hash_data.integrity_meta_info.level_hash_info | ||
| 305 | .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2] | ||
| 306 | .offset; | ||
| 307 | R_SUCCEED(); | ||
| 308 | case HashType::HierarchicalSha256Hash: | ||
| 309 | case HashType::HierarchicalSha3256Hash: | ||
| 310 | *out = | ||
| 311 | this->hash_data.hierarchical_sha256_data | ||
| 312 | .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - | ||
| 313 | 1] | ||
| 314 | .offset; | ||
| 315 | R_SUCCEED(); | ||
| 316 | default: | ||
| 317 | R_THROW(ResultInvalidNcaFsHeader); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | }; | ||
| 321 | static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); | ||
| 322 | static_assert(std::is_trivial_v<NcaFsHeader>); | ||
| 323 | static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); | ||
| 324 | |||
| 325 | inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = | ||
| 326 | offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); | ||
| 327 | inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = | ||
| 328 | offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash); | ||
| 329 | |||
| 330 | struct NcaMetaDataHashData { | ||
| 331 | s64 layer_info_offset; | ||
| 332 | NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; | ||
| 333 | }; | ||
| 334 | static_assert(sizeof(NcaMetaDataHashData) == | ||
| 335 | sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); | ||
| 336 | static_assert(std::is_trivial_v<NcaMetaDataHashData>); | ||
| 337 | |||
| 338 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp new file mode 100644 index 000000000..a3714ab37 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp | |||
| @@ -0,0 +1,531 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 6 | #include "core/file_sys/vfs_offset.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | constexpr inline u32 SdkAddonVersionMin = 0x000B0000; | ||
| 13 | constexpr inline size_t Aes128KeySize = 0x10; | ||
| 14 | constexpr const std::array<u8, Aes128KeySize> ZeroKey{}; | ||
| 15 | |||
| 16 | constexpr Result CheckNcaMagic(u32 magic) { | ||
| 17 | // Verify the magic is not a deprecated one. | ||
| 18 | R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion); | ||
| 19 | R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion); | ||
| 20 | R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion); | ||
| 21 | |||
| 22 | // Verify the magic is the current one. | ||
| 23 | R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature); | ||
| 24 | |||
| 25 | R_SUCCEED(); | ||
| 26 | } | ||
| 27 | |||
| 28 | } // namespace | ||
| 29 | |||
| 30 | NcaReader::NcaReader() | ||
| 31 | : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false), | ||
| 32 | m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), | ||
| 33 | m_get_decompressor() { | ||
| 34 | std::memset(std::addressof(m_header), 0, sizeof(m_header)); | ||
| 35 | std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys)); | ||
| 36 | std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key)); | ||
| 37 | } | ||
| 38 | |||
| 39 | NcaReader::~NcaReader() {} | ||
| 40 | |||
| 41 | Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, | ||
| 42 | const NcaCompressionConfiguration& compression_cfg) { | ||
| 43 | // Validate preconditions. | ||
| 44 | ASSERT(base_storage != nullptr); | ||
| 45 | ASSERT(m_body_storage == nullptr); | ||
| 46 | |||
| 47 | // Create the work header storage storage. | ||
| 48 | VirtualFile work_header_storage; | ||
| 49 | |||
| 50 | // We need to be able to generate keys. | ||
| 51 | R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument); | ||
| 52 | |||
| 53 | // Generate keys for header. | ||
| 54 | using AesXtsStorageForNcaHeader = AesXtsStorage; | ||
| 55 | |||
| 56 | constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount> | ||
| 57 | HeaderKeyTypeValues = { | ||
| 58 | static_cast<s32>(KeyType::NcaHeaderKey1), | ||
| 59 | static_cast<s32>(KeyType::NcaHeaderKey2), | ||
| 60 | }; | ||
| 61 | |||
| 62 | std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, | ||
| 63 | NcaCryptoConfiguration::HeaderEncryptionKeyCount> | ||
| 64 | header_decryption_keys; | ||
| 65 | for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { | ||
| 66 | crypto_cfg.generate_key(header_decryption_keys[i].data(), | ||
| 67 | AesXtsStorageForNcaHeader::KeySize, | ||
| 68 | crypto_cfg.header_encrypted_encryption_keys[i].data(), | ||
| 69 | AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]); | ||
| 70 | } | ||
| 71 | |||
| 72 | // Create the header storage. | ||
| 73 | std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {}; | ||
| 74 | work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>( | ||
| 75 | base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(), | ||
| 76 | AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize, | ||
| 77 | NcaHeader::XtsBlockSize); | ||
| 78 | |||
| 79 | // Check that we successfully created the storage. | ||
| 80 | R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); | ||
| 81 | |||
| 82 | // Read the header. | ||
| 83 | work_header_storage->ReadObject(std::addressof(m_header), 0); | ||
| 84 | |||
| 85 | // Validate the magic. | ||
| 86 | if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) { | ||
| 87 | // Try to use a plaintext header. | ||
| 88 | base_storage->ReadObject(std::addressof(m_header), 0); | ||
| 89 | R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result); | ||
| 90 | |||
| 91 | // Configure to use the plaintext header. | ||
| 92 | auto base_storage_size = base_storage->GetSize(); | ||
| 93 | work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0); | ||
| 94 | R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); | ||
| 95 | |||
| 96 | // Set encryption type as plaintext. | ||
| 97 | m_header_encryption_type = NcaHeader::EncryptionType::None; | ||
| 98 | } | ||
| 99 | |||
| 100 | // Verify the header sign1. | ||
| 101 | if (crypto_cfg.verify_sign1 != nullptr) { | ||
| 102 | const u8* sig = m_header.header_sign_1.data(); | ||
| 103 | const size_t sig_size = NcaHeader::HeaderSignSize; | ||
| 104 | const u8* msg = | ||
| 105 | static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic))); | ||
| 106 | const size_t msg_size = | ||
| 107 | NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; | ||
| 108 | |||
| 109 | m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1( | ||
| 110 | sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation); | ||
| 111 | |||
| 112 | if (!m_is_header_sign1_signature_valid) { | ||
| 113 | LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1"); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | // Validate the sdk version. | ||
| 118 | R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion); | ||
| 119 | |||
| 120 | // Validate the key index. | ||
| 121 | R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || | ||
| 122 | m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, | ||
| 123 | ResultInvalidNcaKeyIndex); | ||
| 124 | |||
| 125 | // Check if we have a rights id. | ||
| 126 | constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{}; | ||
| 127 | if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) { | ||
| 128 | // If we don't, then we don't have an external key, so we need to generate decryption keys. | ||
| 129 | crypto_cfg.generate_key( | ||
| 130 | m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize, | ||
| 131 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize, | ||
| 132 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 133 | crypto_cfg.generate_key( | ||
| 134 | m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize, | ||
| 135 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize, | ||
| 136 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 137 | crypto_cfg.generate_key( | ||
| 138 | m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize, | ||
| 139 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize, | ||
| 140 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 141 | crypto_cfg.generate_key( | ||
| 142 | m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize, | ||
| 143 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize, | ||
| 144 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 145 | |||
| 146 | // Copy the hardware speed emulation key. | ||
| 147 | std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(), | ||
| 148 | m_header.encrypted_key_area.data() + | ||
| 149 | NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize, | ||
| 150 | Aes128KeySize); | ||
| 151 | } | ||
| 152 | |||
| 153 | // Clear the external decryption key. | ||
| 154 | std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size()); | ||
| 155 | |||
| 156 | // Set software key availability. | ||
| 157 | m_is_available_sw_key = crypto_cfg.is_available_sw_key; | ||
| 158 | |||
| 159 | // Set our decompressor function getter. | ||
| 160 | m_get_decompressor = compression_cfg.get_decompressor; | ||
| 161 | |||
| 162 | // Set our storages. | ||
| 163 | m_header_storage = std::move(work_header_storage); | ||
| 164 | m_body_storage = std::move(base_storage); | ||
| 165 | |||
| 166 | R_SUCCEED(); | ||
| 167 | } | ||
| 168 | |||
| 169 | VirtualFile NcaReader::GetSharedBodyStorage() { | ||
| 170 | ASSERT(m_body_storage != nullptr); | ||
| 171 | return m_body_storage; | ||
| 172 | } | ||
| 173 | |||
| 174 | u32 NcaReader::GetMagic() const { | ||
| 175 | ASSERT(m_body_storage != nullptr); | ||
| 176 | return m_header.magic; | ||
| 177 | } | ||
| 178 | |||
| 179 | NcaHeader::DistributionType NcaReader::GetDistributionType() const { | ||
| 180 | ASSERT(m_body_storage != nullptr); | ||
| 181 | return m_header.distribution_type; | ||
| 182 | } | ||
| 183 | |||
| 184 | NcaHeader::ContentType NcaReader::GetContentType() const { | ||
| 185 | ASSERT(m_body_storage != nullptr); | ||
| 186 | return m_header.content_type; | ||
| 187 | } | ||
| 188 | |||
| 189 | u8 NcaReader::GetHeaderSign1KeyGeneration() const { | ||
| 190 | ASSERT(m_body_storage != nullptr); | ||
| 191 | return m_header.header1_signature_key_generation; | ||
| 192 | } | ||
| 193 | |||
| 194 | u8 NcaReader::GetKeyGeneration() const { | ||
| 195 | ASSERT(m_body_storage != nullptr); | ||
| 196 | return m_header.GetProperKeyGeneration(); | ||
| 197 | } | ||
| 198 | |||
| 199 | u8 NcaReader::GetKeyIndex() const { | ||
| 200 | ASSERT(m_body_storage != nullptr); | ||
| 201 | return m_header.key_index; | ||
| 202 | } | ||
| 203 | |||
| 204 | u64 NcaReader::GetContentSize() const { | ||
| 205 | ASSERT(m_body_storage != nullptr); | ||
| 206 | return m_header.content_size; | ||
| 207 | } | ||
| 208 | |||
| 209 | u64 NcaReader::GetProgramId() const { | ||
| 210 | ASSERT(m_body_storage != nullptr); | ||
| 211 | return m_header.program_id; | ||
| 212 | } | ||
| 213 | |||
| 214 | u32 NcaReader::GetContentIndex() const { | ||
| 215 | ASSERT(m_body_storage != nullptr); | ||
| 216 | return m_header.content_index; | ||
| 217 | } | ||
| 218 | |||
| 219 | u32 NcaReader::GetSdkAddonVersion() const { | ||
| 220 | ASSERT(m_body_storage != nullptr); | ||
| 221 | return m_header.sdk_addon_version; | ||
| 222 | } | ||
| 223 | |||
| 224 | void NcaReader::GetRightsId(u8* dst, size_t dst_size) const { | ||
| 225 | ASSERT(dst != nullptr); | ||
| 226 | ASSERT(dst_size >= NcaHeader::RightsIdSize); | ||
| 227 | |||
| 228 | std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize); | ||
| 229 | } | ||
| 230 | |||
| 231 | bool NcaReader::HasFsInfo(s32 index) const { | ||
| 232 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 233 | return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0; | ||
| 234 | } | ||
| 235 | |||
| 236 | s32 NcaReader::GetFsCount() const { | ||
| 237 | ASSERT(m_body_storage != nullptr); | ||
| 238 | for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { | ||
| 239 | if (!this->HasFsInfo(i)) { | ||
| 240 | return i; | ||
| 241 | } | ||
| 242 | } | ||
| 243 | return NcaHeader::FsCountMax; | ||
| 244 | } | ||
| 245 | |||
| 246 | const Hash& NcaReader::GetFsHeaderHash(s32 index) const { | ||
| 247 | ASSERT(m_body_storage != nullptr); | ||
| 248 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 249 | return m_header.fs_header_hash[index]; | ||
| 250 | } | ||
| 251 | |||
| 252 | void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const { | ||
| 253 | ASSERT(m_body_storage != nullptr); | ||
| 254 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 255 | ASSERT(dst != nullptr); | ||
| 256 | std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst)); | ||
| 257 | } | ||
| 258 | |||
| 259 | void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const { | ||
| 260 | ASSERT(m_body_storage != nullptr); | ||
| 261 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 262 | ASSERT(dst != nullptr); | ||
| 263 | std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst)); | ||
| 264 | } | ||
| 265 | |||
| 266 | u64 NcaReader::GetFsOffset(s32 index) const { | ||
| 267 | ASSERT(m_body_storage != nullptr); | ||
| 268 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 269 | return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector); | ||
| 270 | } | ||
| 271 | |||
| 272 | u64 NcaReader::GetFsEndOffset(s32 index) const { | ||
| 273 | ASSERT(m_body_storage != nullptr); | ||
| 274 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 275 | return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector); | ||
| 276 | } | ||
| 277 | |||
| 278 | u64 NcaReader::GetFsSize(s32 index) const { | ||
| 279 | ASSERT(m_body_storage != nullptr); | ||
| 280 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 281 | return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - | ||
| 282 | m_header.fs_info[index].start_sector); | ||
| 283 | } | ||
| 284 | |||
| 285 | void NcaReader::GetEncryptedKey(void* dst, size_t size) const { | ||
| 286 | ASSERT(m_body_storage != nullptr); | ||
| 287 | ASSERT(dst != nullptr); | ||
| 288 | ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); | ||
| 289 | |||
| 290 | std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize); | ||
| 291 | } | ||
| 292 | |||
| 293 | const void* NcaReader::GetDecryptionKey(s32 index) const { | ||
| 294 | ASSERT(m_body_storage != nullptr); | ||
| 295 | ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); | ||
| 296 | return m_decryption_keys[index].data(); | ||
| 297 | } | ||
| 298 | |||
| 299 | bool NcaReader::HasValidInternalKey() const { | ||
| 300 | for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { | ||
| 301 | if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize, | ||
| 302 | Aes128KeySize) != 0) { | ||
| 303 | return true; | ||
| 304 | } | ||
| 305 | } | ||
| 306 | return false; | ||
| 307 | } | ||
| 308 | |||
| 309 | bool NcaReader::HasInternalDecryptionKeyForAesHw() const { | ||
| 310 | return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), | ||
| 311 | Aes128KeySize) != 0; | ||
| 312 | } | ||
| 313 | |||
| 314 | bool NcaReader::IsSoftwareAesPrioritized() const { | ||
| 315 | return m_is_software_aes_prioritized; | ||
| 316 | } | ||
| 317 | |||
| 318 | void NcaReader::PrioritizeSoftwareAes() { | ||
| 319 | m_is_software_aes_prioritized = true; | ||
| 320 | } | ||
| 321 | |||
| 322 | bool NcaReader::IsAvailableSwKey() const { | ||
| 323 | return m_is_available_sw_key; | ||
| 324 | } | ||
| 325 | |||
| 326 | bool NcaReader::HasExternalDecryptionKey() const { | ||
| 327 | return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0; | ||
| 328 | } | ||
| 329 | |||
| 330 | const void* NcaReader::GetExternalDecryptionKey() const { | ||
| 331 | return m_external_decryption_key.data(); | ||
| 332 | } | ||
| 333 | |||
| 334 | void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) { | ||
| 335 | ASSERT(src != nullptr); | ||
| 336 | ASSERT(size == sizeof(m_external_decryption_key)); | ||
| 337 | |||
| 338 | std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key)); | ||
| 339 | } | ||
| 340 | |||
| 341 | void NcaReader::GetRawData(void* dst, size_t dst_size) const { | ||
| 342 | ASSERT(m_body_storage != nullptr); | ||
| 343 | ASSERT(dst != nullptr); | ||
| 344 | ASSERT(dst_size >= sizeof(NcaHeader)); | ||
| 345 | |||
| 346 | std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader)); | ||
| 347 | } | ||
| 348 | |||
| 349 | GetDecompressorFunction NcaReader::GetDecompressor() const { | ||
| 350 | ASSERT(m_get_decompressor != nullptr); | ||
| 351 | return m_get_decompressor; | ||
| 352 | } | ||
| 353 | |||
| 354 | NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { | ||
| 355 | return m_header_encryption_type; | ||
| 356 | } | ||
| 357 | |||
| 358 | Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const { | ||
| 359 | ASSERT(dst != nullptr); | ||
| 360 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 361 | |||
| 362 | const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; | ||
| 363 | m_header_storage->ReadObject(dst, offset); | ||
| 364 | |||
| 365 | R_SUCCEED(); | ||
| 366 | } | ||
| 367 | |||
| 368 | bool NcaReader::GetHeaderSign1Valid() const { | ||
| 369 | return m_is_header_sign1_signature_valid; | ||
| 370 | } | ||
| 371 | |||
| 372 | void NcaReader::GetHeaderSign2(void* dst, size_t size) const { | ||
| 373 | ASSERT(dst != nullptr); | ||
| 374 | ASSERT(size == NcaHeader::HeaderSignSize); | ||
| 375 | |||
| 376 | std::memcpy(dst, m_header.header_sign_2.data(), size); | ||
| 377 | } | ||
| 378 | |||
| 379 | Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) { | ||
| 380 | // Reset ourselves to uninitialized. | ||
| 381 | m_fs_index = -1; | ||
| 382 | |||
| 383 | // Read the header. | ||
| 384 | R_TRY(reader.ReadHeader(std::addressof(m_data), index)); | ||
| 385 | |||
| 386 | // Set our index. | ||
| 387 | m_fs_index = index; | ||
| 388 | R_SUCCEED(); | ||
| 389 | } | ||
| 390 | |||
| 391 | void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const { | ||
| 392 | ASSERT(this->IsInitialized()); | ||
| 393 | ASSERT(dst != nullptr); | ||
| 394 | ASSERT(dst_size >= sizeof(NcaFsHeader)); | ||
| 395 | |||
| 396 | std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader)); | ||
| 397 | } | ||
| 398 | |||
| 399 | NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() { | ||
| 400 | ASSERT(this->IsInitialized()); | ||
| 401 | return m_data.hash_data; | ||
| 402 | } | ||
| 403 | |||
| 404 | const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const { | ||
| 405 | ASSERT(this->IsInitialized()); | ||
| 406 | return m_data.hash_data; | ||
| 407 | } | ||
| 408 | |||
| 409 | u16 NcaFsHeaderReader::GetVersion() const { | ||
| 410 | ASSERT(this->IsInitialized()); | ||
| 411 | return m_data.version; | ||
| 412 | } | ||
| 413 | |||
| 414 | s32 NcaFsHeaderReader::GetFsIndex() const { | ||
| 415 | ASSERT(this->IsInitialized()); | ||
| 416 | return m_fs_index; | ||
| 417 | } | ||
| 418 | |||
| 419 | NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { | ||
| 420 | ASSERT(this->IsInitialized()); | ||
| 421 | return m_data.fs_type; | ||
| 422 | } | ||
| 423 | |||
| 424 | NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { | ||
| 425 | ASSERT(this->IsInitialized()); | ||
| 426 | return m_data.hash_type; | ||
| 427 | } | ||
| 428 | |||
| 429 | NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { | ||
| 430 | ASSERT(this->IsInitialized()); | ||
| 431 | return m_data.encryption_type; | ||
| 432 | } | ||
| 433 | |||
| 434 | NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() { | ||
| 435 | ASSERT(this->IsInitialized()); | ||
| 436 | return m_data.patch_info; | ||
| 437 | } | ||
| 438 | |||
| 439 | const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const { | ||
| 440 | ASSERT(this->IsInitialized()); | ||
| 441 | return m_data.patch_info; | ||
| 442 | } | ||
| 443 | |||
| 444 | const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { | ||
| 445 | ASSERT(this->IsInitialized()); | ||
| 446 | return m_data.aes_ctr_upper_iv; | ||
| 447 | } | ||
| 448 | |||
| 449 | bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { | ||
| 450 | ASSERT(this->IsInitialized()); | ||
| 451 | return m_data.IsSkipLayerHashEncryption(); | ||
| 452 | } | ||
| 453 | |||
| 454 | Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const { | ||
| 455 | ASSERT(out != nullptr); | ||
| 456 | ASSERT(this->IsInitialized()); | ||
| 457 | |||
| 458 | R_RETURN(m_data.GetHashTargetOffset(out)); | ||
| 459 | } | ||
| 460 | |||
| 461 | bool NcaFsHeaderReader::ExistsSparseLayer() const { | ||
| 462 | ASSERT(this->IsInitialized()); | ||
| 463 | return m_data.sparse_info.generation != 0; | ||
| 464 | } | ||
| 465 | |||
| 466 | NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() { | ||
| 467 | ASSERT(this->IsInitialized()); | ||
| 468 | return m_data.sparse_info; | ||
| 469 | } | ||
| 470 | |||
| 471 | const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const { | ||
| 472 | ASSERT(this->IsInitialized()); | ||
| 473 | return m_data.sparse_info; | ||
| 474 | } | ||
| 475 | |||
| 476 | bool NcaFsHeaderReader::ExistsCompressionLayer() const { | ||
| 477 | ASSERT(this->IsInitialized()); | ||
| 478 | return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0; | ||
| 479 | } | ||
| 480 | |||
| 481 | NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() { | ||
| 482 | ASSERT(this->IsInitialized()); | ||
| 483 | return m_data.compression_info; | ||
| 484 | } | ||
| 485 | |||
| 486 | const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const { | ||
| 487 | ASSERT(this->IsInitialized()); | ||
| 488 | return m_data.compression_info; | ||
| 489 | } | ||
| 490 | |||
| 491 | bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { | ||
| 492 | ASSERT(this->IsInitialized()); | ||
| 493 | return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); | ||
| 494 | } | ||
| 495 | |||
| 496 | NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { | ||
| 497 | ASSERT(this->IsInitialized()); | ||
| 498 | return m_data.meta_data_hash_data_info; | ||
| 499 | } | ||
| 500 | |||
| 501 | const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { | ||
| 502 | ASSERT(this->IsInitialized()); | ||
| 503 | return m_data.meta_data_hash_data_info; | ||
| 504 | } | ||
| 505 | |||
| 506 | NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { | ||
| 507 | ASSERT(this->IsInitialized()); | ||
| 508 | return m_data.meta_data_hash_type; | ||
| 509 | } | ||
| 510 | |||
| 511 | bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { | ||
| 512 | ASSERT(this->IsInitialized()); | ||
| 513 | return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); | ||
| 514 | } | ||
| 515 | |||
| 516 | NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { | ||
| 517 | ASSERT(this->IsInitialized()); | ||
| 518 | return m_data.meta_data_hash_data_info; | ||
| 519 | } | ||
| 520 | |||
| 521 | const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { | ||
| 522 | ASSERT(this->IsInitialized()); | ||
| 523 | return m_data.meta_data_hash_data_info; | ||
| 524 | } | ||
| 525 | |||
| 526 | NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { | ||
| 527 | ASSERT(this->IsInitialized()); | ||
| 528 | return m_data.meta_data_hash_type; | ||
| 529 | } | ||
| 530 | |||
| 531 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp new file mode 100644 index 000000000..bbfaab255 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | constexpr size_t HeapBlockSize = BufferPoolAlignment; | ||
| 12 | static_assert(HeapBlockSize == 4_KiB); | ||
| 13 | |||
| 14 | // A heap block is 4KiB. An order is a power of two. | ||
| 15 | // This gives blocks of the order 32KiB, 512KiB, 4MiB. | ||
| 16 | constexpr s32 HeapOrderMax = 7; | ||
| 17 | constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3; | ||
| 18 | |||
| 19 | constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax); | ||
| 20 | constexpr size_t HeapAllocatableSizeMaxForLarge = | ||
| 21 | HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge); | ||
| 22 | |||
| 23 | } // namespace | ||
| 24 | |||
| 25 | size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) { | ||
| 26 | return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; | ||
| 27 | } | ||
| 28 | |||
| 29 | void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) { | ||
| 30 | // Ensure preconditions. | ||
| 31 | ASSERT(m_buffer == nullptr); | ||
| 32 | |||
| 33 | // Check that we can allocate this size. | ||
| 34 | ASSERT(required_size <= GetAllocatableSizeMaxCore(large)); | ||
| 35 | |||
| 36 | const size_t target_size = | ||
| 37 | std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large)); | ||
| 38 | |||
| 39 | // Dummy implementation for allocate. | ||
| 40 | if (target_size > 0) { | ||
| 41 | m_buffer = | ||
| 42 | reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize})); | ||
| 43 | m_size = target_size; | ||
| 44 | |||
| 45 | // Ensure postconditions. | ||
| 46 | ASSERT(m_buffer != nullptr); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | void PooledBuffer::Shrink(size_t ideal_size) { | ||
| 51 | ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true)); | ||
| 52 | |||
| 53 | // Shrinking to zero means that we have no buffer. | ||
| 54 | if (ideal_size == 0) { | ||
| 55 | ::operator delete(m_buffer, std::align_val_t{HeapBlockSize}); | ||
| 56 | m_buffer = nullptr; | ||
| 57 | m_size = ideal_size; | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.h b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h new file mode 100644 index 000000000..9a6adbcb5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h | |||
| @@ -0,0 +1,95 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/hle/result.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | using namespace Common::Literals; | ||
| 14 | |||
| 15 | constexpr inline size_t BufferPoolAlignment = 4_KiB; | ||
| 16 | constexpr inline size_t BufferPoolWorkSize = 320; | ||
| 17 | |||
| 18 | class PooledBuffer { | ||
| 19 | YUZU_NON_COPYABLE(PooledBuffer); | ||
| 20 | |||
| 21 | public: | ||
| 22 | // Constructor/Destructor. | ||
| 23 | constexpr PooledBuffer() : m_buffer(), m_size() {} | ||
| 24 | |||
| 25 | PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() { | ||
| 26 | this->Allocate(ideal_size, required_size); | ||
| 27 | } | ||
| 28 | |||
| 29 | ~PooledBuffer() { | ||
| 30 | this->Deallocate(); | ||
| 31 | } | ||
| 32 | |||
| 33 | // Move and assignment. | ||
| 34 | explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) { | ||
| 35 | rhs.m_buffer = nullptr; | ||
| 36 | rhs.m_size = 0; | ||
| 37 | } | ||
| 38 | |||
| 39 | PooledBuffer& operator=(PooledBuffer&& rhs) { | ||
| 40 | PooledBuffer(std::move(rhs)).Swap(*this); | ||
| 41 | return *this; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Allocation API. | ||
| 45 | void Allocate(size_t ideal_size, size_t required_size) { | ||
| 46 | return this->AllocateCore(ideal_size, required_size, false); | ||
| 47 | } | ||
| 48 | |||
| 49 | void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) { | ||
| 50 | return this->AllocateCore(ideal_size, required_size, true); | ||
| 51 | } | ||
| 52 | |||
| 53 | void Shrink(size_t ideal_size); | ||
| 54 | |||
| 55 | void Deallocate() { | ||
| 56 | // Shrink the buffer to empty. | ||
| 57 | this->Shrink(0); | ||
| 58 | ASSERT(m_buffer == nullptr); | ||
| 59 | } | ||
| 60 | |||
| 61 | char* GetBuffer() const { | ||
| 62 | ASSERT(m_buffer != nullptr); | ||
| 63 | return m_buffer; | ||
| 64 | } | ||
| 65 | |||
| 66 | size_t GetSize() const { | ||
| 67 | ASSERT(m_buffer != nullptr); | ||
| 68 | return m_size; | ||
| 69 | } | ||
| 70 | |||
| 71 | public: | ||
| 72 | static size_t GetAllocatableSizeMax() { | ||
| 73 | return GetAllocatableSizeMaxCore(false); | ||
| 74 | } | ||
| 75 | static size_t GetAllocatableParticularlyLargeSizeMax() { | ||
| 76 | return GetAllocatableSizeMaxCore(true); | ||
| 77 | } | ||
| 78 | |||
| 79 | private: | ||
| 80 | static size_t GetAllocatableSizeMaxCore(bool large); | ||
| 81 | |||
| 82 | private: | ||
| 83 | void Swap(PooledBuffer& rhs) { | ||
| 84 | std::swap(m_buffer, rhs.m_buffer); | ||
| 85 | std::swap(m_size, rhs.m_size); | ||
| 86 | } | ||
| 87 | |||
| 88 | void AllocateCore(size_t ideal_size, size_t required_size, bool large); | ||
| 89 | |||
| 90 | private: | ||
| 91 | char* m_buffer; | ||
| 92 | size_t m_size; | ||
| 93 | }; | ||
| 94 | |||
| 95 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp new file mode 100644 index 000000000..8574a11dd --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_sparse_storage.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 9 | // Validate preconditions. | ||
| 10 | ASSERT(this->IsInitialized()); | ||
| 11 | ASSERT(buffer != nullptr); | ||
| 12 | |||
| 13 | // Allow zero size. | ||
| 14 | if (size == 0) { | ||
| 15 | return size; | ||
| 16 | } | ||
| 17 | |||
| 18 | SparseStorage* self = const_cast<SparseStorage*>(this); | ||
| 19 | |||
| 20 | if (self->GetEntryTable().IsEmpty()) { | ||
| 21 | BucketTree::Offsets table_offsets; | ||
| 22 | ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets)))); | ||
| 23 | ASSERT(table_offsets.IsInclude(offset, size)); | ||
| 24 | |||
| 25 | std::memset(buffer, 0, size); | ||
| 26 | } else { | ||
| 27 | self->OperatePerEntry<false, true>( | ||
| 28 | offset, size, | ||
| 29 | [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { | ||
| 30 | storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), | ||
| 31 | static_cast<size_t>(cur_size), data_offset); | ||
| 32 | R_SUCCEED(); | ||
| 33 | }); | ||
| 34 | } | ||
| 35 | |||
| 36 | return size; | ||
| 37 | } | ||
| 38 | |||
| 39 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.h b/src/core/file_sys/fssystem/fssystem_sparse_storage.h new file mode 100644 index 000000000..6c196ec61 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.h | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class SparseStorage : public IndirectStorage { | ||
| 11 | YUZU_NON_COPYABLE(SparseStorage); | ||
| 12 | YUZU_NON_MOVEABLE(SparseStorage); | ||
| 13 | |||
| 14 | private: | ||
| 15 | class ZeroStorage : public IReadOnlyStorage { | ||
| 16 | public: | ||
| 17 | ZeroStorage() {} | ||
| 18 | virtual ~ZeroStorage() {} | ||
| 19 | |||
| 20 | virtual size_t GetSize() const override { | ||
| 21 | return std::numeric_limits<size_t>::max(); | ||
| 22 | } | ||
| 23 | |||
| 24 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 25 | ASSERT(buffer != nullptr || size == 0); | ||
| 26 | |||
| 27 | if (size > 0) { | ||
| 28 | std::memset(buffer, 0, size); | ||
| 29 | } | ||
| 30 | |||
| 31 | return size; | ||
| 32 | } | ||
| 33 | }; | ||
| 34 | |||
| 35 | public: | ||
| 36 | SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {} | ||
| 37 | virtual ~SparseStorage() {} | ||
| 38 | |||
| 39 | using IndirectStorage::Initialize; | ||
| 40 | |||
| 41 | void Initialize(s64 end_offset) { | ||
| 42 | this->GetEntryTable().Initialize(NodeSize, end_offset); | ||
| 43 | this->SetZeroStorage(); | ||
| 44 | } | ||
| 45 | |||
| 46 | void SetDataStorage(VirtualFile storage) { | ||
| 47 | ASSERT(this->IsInitialized()); | ||
| 48 | |||
| 49 | this->SetStorage(0, storage); | ||
| 50 | this->SetZeroStorage(); | ||
| 51 | } | ||
| 52 | |||
| 53 | template <typename T> | ||
| 54 | void SetDataStorage(T storage, s64 offset, s64 size) { | ||
| 55 | ASSERT(this->IsInitialized()); | ||
| 56 | |||
| 57 | this->SetStorage(0, storage, offset, size); | ||
| 58 | this->SetZeroStorage(); | ||
| 59 | } | ||
| 60 | |||
| 61 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 62 | |||
| 63 | private: | ||
| 64 | void SetZeroStorage() { | ||
| 65 | return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max()); | ||
| 66 | } | ||
| 67 | |||
| 68 | private: | ||
| 69 | VirtualFile m_zero_storage; | ||
| 70 | }; | ||
| 71 | |||
| 72 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_switch_storage.h b/src/core/file_sys/fssystem/fssystem_switch_storage.h new file mode 100644 index 000000000..2b43927cb --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_switch_storage.h | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class RegionSwitchStorage : public IReadOnlyStorage { | ||
| 11 | YUZU_NON_COPYABLE(RegionSwitchStorage); | ||
| 12 | YUZU_NON_MOVEABLE(RegionSwitchStorage); | ||
| 13 | |||
| 14 | public: | ||
| 15 | struct Region { | ||
| 16 | s64 offset; | ||
| 17 | s64 size; | ||
| 18 | }; | ||
| 19 | |||
| 20 | public: | ||
| 21 | RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r) | ||
| 22 | : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), | ||
| 23 | m_region(r) {} | ||
| 24 | |||
| 25 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 26 | // Process until we're done. | ||
| 27 | size_t processed = 0; | ||
| 28 | while (processed < size) { | ||
| 29 | // Process on the appropriate storage. | ||
| 30 | s64 cur_size = 0; | ||
| 31 | if (this->CheckRegions(std::addressof(cur_size), offset + processed, | ||
| 32 | size - processed)) { | ||
| 33 | m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed); | ||
| 34 | } else { | ||
| 35 | m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed); | ||
| 36 | } | ||
| 37 | |||
| 38 | // Advance. | ||
| 39 | processed += cur_size; | ||
| 40 | } | ||
| 41 | |||
| 42 | return size; | ||
| 43 | } | ||
| 44 | |||
| 45 | virtual size_t GetSize() const override { | ||
| 46 | return m_inside_region_storage->GetSize(); | ||
| 47 | } | ||
| 48 | |||
| 49 | private: | ||
| 50 | bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const { | ||
| 51 | // Check if our region contains the access. | ||
| 52 | if (m_region.offset <= offset) { | ||
| 53 | if (offset < m_region.offset + m_region.size) { | ||
| 54 | if (m_region.offset + m_region.size <= offset + size) { | ||
| 55 | *out_current_size = m_region.offset + m_region.size - offset; | ||
| 56 | } else { | ||
| 57 | *out_current_size = size; | ||
| 58 | } | ||
| 59 | return true; | ||
| 60 | } else { | ||
| 61 | *out_current_size = size; | ||
| 62 | return false; | ||
| 63 | } | ||
| 64 | } else { | ||
| 65 | if (m_region.offset <= offset + size) { | ||
| 66 | *out_current_size = m_region.offset - offset; | ||
| 67 | } else { | ||
| 68 | *out_current_size = size; | ||
| 69 | } | ||
| 70 | return false; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | private: | ||
| 75 | VirtualFile m_inside_region_storage; | ||
| 76 | VirtualFile m_outside_region_storage; | ||
| 77 | Region m_region; | ||
| 78 | }; | ||
| 79 | |||
| 80 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_utility.cpp b/src/core/file_sys/fssystem/fssystem_utility.cpp new file mode 100644 index 000000000..ceabb8ff1 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.cpp | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | void AddCounter(void* counter_, size_t counter_size, u64 value) { | ||
| 9 | u8* counter = static_cast<u8*>(counter_); | ||
| 10 | u64 remaining = value; | ||
| 11 | u8 carry = 0; | ||
| 12 | |||
| 13 | for (size_t i = 0; i < counter_size; i++) { | ||
| 14 | auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry; | ||
| 15 | carry = static_cast<u8>(sum >> (sizeof(u8) * 8)); | ||
| 16 | auto sum8 = static_cast<u8>(sum & 0xFF); | ||
| 17 | |||
| 18 | counter[counter_size - 1 - i] = sum8; | ||
| 19 | |||
| 20 | remaining >>= (sizeof(u8) * 8); | ||
| 21 | if (carry == 0 && remaining == 0) { | ||
| 22 | break; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_utility.h b/src/core/file_sys/fssystem/fssystem_utility.h new file mode 100644 index 000000000..284b8b811 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | void AddCounter(void* counter, size_t counter_size, u64 value); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index efdf18cee..7be1322cc 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp | |||
| @@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) { | |||
| 165 | void IPSwitchCompiler::ParseFlag(const std::string& line) { | 165 | void IPSwitchCompiler::ParseFlag(const std::string& line) { |
| 166 | if (StartsWith(line, "@flag offset_shift ")) { | 166 | if (StartsWith(line, "@flag offset_shift ")) { |
| 167 | // Offset Shift Flag | 167 | // Offset Shift Flag |
| 168 | offset_shift = std::stoll(line.substr(19), nullptr, 0); | 168 | offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0); |
| 169 | } else if (StartsWith(line, "@little-endian")) { | 169 | } else if (StartsWith(line, "@little-endian")) { |
| 170 | // Set values to read as little endian | 170 | // Set values to read as little endian |
| 171 | is_little_endian = true; | 171 | is_little_endian = true; |
| @@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() { | |||
| 263 | // 11 - 8 hex digit offset + space + minimum two digit overwrite val | 263 | // 11 - 8 hex digit offset + space + minimum two digit overwrite val |
| 264 | if (patch_line.length() < 11) | 264 | if (patch_line.length() < 11) |
| 265 | break; | 265 | break; |
| 266 | auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); | 266 | auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16); |
| 267 | offset += static_cast<unsigned long>(offset_shift); | 267 | offset += static_cast<unsigned long>(offset_shift); |
| 268 | 268 | ||
| 269 | std::vector<u8> replace; | 269 | std::vector<u8> replace; |
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index 52c78020c..f4a774675 100644 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp | |||
| @@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_, | |||
| 45 | 45 | ||
| 46 | CNMT::~CNMT() = default; | 46 | CNMT::~CNMT() = default; |
| 47 | 47 | ||
| 48 | const CNMTHeader& CNMT::GetHeader() const { | ||
| 49 | return header; | ||
| 50 | } | ||
| 51 | |||
| 48 | u64 CNMT::GetTitleID() const { | 52 | u64 CNMT::GetTitleID() const { |
| 49 | return header.title_id; | 53 | return header.title_id; |
| 50 | } | 54 | } |
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index c59ece010..68e463b5f 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h | |||
| @@ -89,6 +89,7 @@ public: | |||
| 89 | std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); | 89 | std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); |
| 90 | ~CNMT(); | 90 | ~CNMT(); |
| 91 | 91 | ||
| 92 | const CNMTHeader& GetHeader() const; | ||
| 92 | u64 GetTitleID() const; | 93 | u64 GetTitleID() const; |
| 93 | u32 GetTitleVersion() const; | 94 | u32 GetTitleVersion() const; |
| 94 | TitleType GetType() const; | 95 | TitleType GetType() const; |
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp deleted file mode 100644 index 2735d053b..000000000 --- a/src/core/file_sys/nca_patch.cpp +++ /dev/null | |||
| @@ -1,217 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <array> | ||
| 6 | #include <cstddef> | ||
| 7 | #include <cstring> | ||
| 8 | |||
| 9 | #include "common/assert.h" | ||
| 10 | #include "core/crypto/aes_util.h" | ||
| 11 | #include "core/file_sys/nca_patch.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | namespace { | ||
| 15 | template <bool Subsection, typename BlockType, typename BucketType> | ||
| 16 | std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block, | ||
| 17 | const BucketType& buckets) { | ||
| 18 | if constexpr (Subsection) { | ||
| 19 | const auto& last_bucket = buckets[block.number_buckets - 1]; | ||
| 20 | if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) { | ||
| 21 | return {block.number_buckets - 1, last_bucket.number_entries}; | ||
| 22 | } | ||
| 23 | } else { | ||
| 24 | ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); | ||
| 25 | } | ||
| 26 | |||
| 27 | std::size_t bucket_id = std::count_if( | ||
| 28 | block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, | ||
| 29 | [&offset](u64 base_offset) { return base_offset <= offset; }); | ||
| 30 | |||
| 31 | const auto& bucket = buckets[bucket_id]; | ||
| 32 | |||
| 33 | if (bucket.number_entries == 1) { | ||
| 34 | return {bucket_id, 0}; | ||
| 35 | } | ||
| 36 | |||
| 37 | std::size_t low = 0; | ||
| 38 | std::size_t mid = 0; | ||
| 39 | std::size_t high = bucket.number_entries - 1; | ||
| 40 | while (low <= high) { | ||
| 41 | mid = (low + high) / 2; | ||
| 42 | if (bucket.entries[mid].address_patch > offset) { | ||
| 43 | high = mid - 1; | ||
| 44 | } else { | ||
| 45 | if (mid == bucket.number_entries - 1 || | ||
| 46 | bucket.entries[mid + 1].address_patch > offset) { | ||
| 47 | return {bucket_id, mid}; | ||
| 48 | } | ||
| 49 | |||
| 50 | low = mid + 1; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | ASSERT_MSG(false, "Offset could not be found in BKTR block."); | ||
| 54 | return {0, 0}; | ||
| 55 | } | ||
| 56 | } // Anonymous namespace | ||
| 57 | |||
| 58 | BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, | ||
| 59 | std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, | ||
| 60 | std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_, | ||
| 61 | Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_, | ||
| 62 | std::array<u8, 8> section_ctr_) | ||
| 63 | : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)), | ||
| 64 | subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)), | ||
| 65 | base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), | ||
| 66 | encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), | ||
| 67 | section_ctr(section_ctr_) { | ||
| 68 | for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) { | ||
| 69 | relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); | ||
| 70 | } | ||
| 71 | |||
| 72 | for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) { | ||
| 73 | subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, | ||
| 74 | {0}, | ||
| 75 | subsection_buckets[i + 1].entries[0].ctr}); | ||
| 76 | } | ||
| 77 | |||
| 78 | relocation_buckets.back().entries.push_back({relocation.size, 0, 0}); | ||
| 79 | } | ||
| 80 | |||
| 81 | BKTR::~BKTR() = default; | ||
| 82 | |||
| 83 | std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { | ||
| 84 | // Read out of bounds. | ||
| 85 | if (offset >= relocation.size) { | ||
| 86 | return 0; | ||
| 87 | } | ||
| 88 | |||
| 89 | const auto relocation_entry = GetRelocationEntry(offset); | ||
| 90 | const auto section_offset = | ||
| 91 | offset - relocation_entry.address_patch + relocation_entry.address_source; | ||
| 92 | const auto bktr_read = relocation_entry.from_patch; | ||
| 93 | |||
| 94 | const auto next_relocation = GetNextRelocationEntry(offset); | ||
| 95 | |||
| 96 | if (offset + length > next_relocation.address_patch) { | ||
| 97 | const u64 partition = next_relocation.address_patch - offset; | ||
| 98 | return Read(data, partition, offset) + | ||
| 99 | Read(data + partition, length - partition, offset + partition); | ||
| 100 | } | ||
| 101 | |||
| 102 | if (!bktr_read) { | ||
| 103 | ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative."); | ||
| 104 | return base_romfs->Read(data, length, section_offset - ivfc_offset); | ||
| 105 | } | ||
| 106 | |||
| 107 | if (!encrypted) { | ||
| 108 | return bktr_romfs->Read(data, length, section_offset); | ||
| 109 | } | ||
| 110 | |||
| 111 | const auto subsection_entry = GetSubsectionEntry(section_offset); | ||
| 112 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); | ||
| 113 | |||
| 114 | // Calculate AES IV | ||
| 115 | std::array<u8, 16> iv{}; | ||
| 116 | auto subsection_ctr = subsection_entry.ctr; | ||
| 117 | auto offset_iv = section_offset + base_offset; | ||
| 118 | for (std::size_t i = 0; i < section_ctr.size(); ++i) { | ||
| 119 | iv[i] = section_ctr[0x8 - i - 1]; | ||
| 120 | } | ||
| 121 | offset_iv >>= 4; | ||
| 122 | for (std::size_t i = 0; i < sizeof(u64); ++i) { | ||
| 123 | iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); | ||
| 124 | offset_iv >>= 8; | ||
| 125 | } | ||
| 126 | for (std::size_t i = 0; i < sizeof(u32); ++i) { | ||
| 127 | iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); | ||
| 128 | subsection_ctr >>= 8; | ||
| 129 | } | ||
| 130 | cipher.SetIV(iv); | ||
| 131 | |||
| 132 | const auto next_subsection = GetNextSubsectionEntry(section_offset); | ||
| 133 | |||
| 134 | if (section_offset + length > next_subsection.address_patch) { | ||
| 135 | const u64 partition = next_subsection.address_patch - section_offset; | ||
| 136 | return Read(data, partition, offset) + | ||
| 137 | Read(data + partition, length - partition, offset + partition); | ||
| 138 | } | ||
| 139 | |||
| 140 | const auto block_offset = section_offset & 0xF; | ||
| 141 | if (block_offset != 0) { | ||
| 142 | auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF); | ||
| 143 | cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt); | ||
| 144 | if (length + block_offset < 0x10) { | ||
| 145 | std::memcpy(data, block.data() + block_offset, std::min(length, block.size())); | ||
| 146 | return std::min(length, block.size()); | ||
| 147 | } | ||
| 148 | |||
| 149 | const auto read = 0x10 - block_offset; | ||
| 150 | std::memcpy(data, block.data() + block_offset, read); | ||
| 151 | return read + Read(data + read, length - read, offset + read); | ||
| 152 | } | ||
| 153 | |||
| 154 | const auto raw_read = bktr_romfs->Read(data, length, section_offset); | ||
| 155 | cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt); | ||
| 156 | return raw_read; | ||
| 157 | } | ||
| 158 | |||
| 159 | RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { | ||
| 160 | const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||
| 161 | return relocation_buckets[res.first].entries[res.second]; | ||
| 162 | } | ||
| 163 | |||
| 164 | RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const { | ||
| 165 | const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||
| 166 | const auto bucket = relocation_buckets[res.first]; | ||
| 167 | if (res.second + 1 < bucket.entries.size()) | ||
| 168 | return bucket.entries[res.second + 1]; | ||
| 169 | return relocation_buckets[res.first + 1].entries[0]; | ||
| 170 | } | ||
| 171 | |||
| 172 | SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const { | ||
| 173 | const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||
| 174 | return subsection_buckets[res.first].entries[res.second]; | ||
| 175 | } | ||
| 176 | |||
| 177 | SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const { | ||
| 178 | const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||
| 179 | const auto bucket = subsection_buckets[res.first]; | ||
| 180 | if (res.second + 1 < bucket.entries.size()) | ||
| 181 | return bucket.entries[res.second + 1]; | ||
| 182 | return subsection_buckets[res.first + 1].entries[0]; | ||
| 183 | } | ||
| 184 | |||
| 185 | std::string BKTR::GetName() const { | ||
| 186 | return base_romfs->GetName(); | ||
| 187 | } | ||
| 188 | |||
| 189 | std::size_t BKTR::GetSize() const { | ||
| 190 | return relocation.size; | ||
| 191 | } | ||
| 192 | |||
| 193 | bool BKTR::Resize(std::size_t new_size) { | ||
| 194 | return false; | ||
| 195 | } | ||
| 196 | |||
| 197 | VirtualDir BKTR::GetContainingDirectory() const { | ||
| 198 | return base_romfs->GetContainingDirectory(); | ||
| 199 | } | ||
| 200 | |||
| 201 | bool BKTR::IsWritable() const { | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | bool BKTR::IsReadable() const { | ||
| 206 | return true; | ||
| 207 | } | ||
| 208 | |||
| 209 | std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) { | ||
| 210 | return 0; | ||
| 211 | } | ||
| 212 | |||
| 213 | bool BKTR::Rename(std::string_view name) { | ||
| 214 | return base_romfs->Rename(name); | ||
| 215 | } | ||
| 216 | |||
| 217 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h deleted file mode 100644 index 595e3ef09..000000000 --- a/src/core/file_sys/nca_patch.h +++ /dev/null | |||
| @@ -1,145 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <memory> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/swap.h" | ||
| 13 | #include "core/crypto/key_manager.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | #pragma pack(push, 1) | ||
| 18 | struct RelocationEntry { | ||
| 19 | u64_le address_patch; | ||
| 20 | u64_le address_source; | ||
| 21 | u32 from_patch; | ||
| 22 | }; | ||
| 23 | #pragma pack(pop) | ||
| 24 | static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); | ||
| 25 | |||
| 26 | struct RelocationBucketRaw { | ||
| 27 | INSERT_PADDING_BYTES(4); | ||
| 28 | u32_le number_entries; | ||
| 29 | u64_le end_offset; | ||
| 30 | std::array<RelocationEntry, 0x332> relocation_entries; | ||
| 31 | INSERT_PADDING_BYTES(8); | ||
| 32 | }; | ||
| 33 | static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); | ||
| 34 | |||
| 35 | // Vector version of RelocationBucketRaw | ||
| 36 | struct RelocationBucket { | ||
| 37 | u32 number_entries; | ||
| 38 | u64 end_offset; | ||
| 39 | std::vector<RelocationEntry> entries; | ||
| 40 | }; | ||
| 41 | |||
| 42 | struct RelocationBlock { | ||
| 43 | INSERT_PADDING_BYTES(4); | ||
| 44 | u32_le number_buckets; | ||
| 45 | u64_le size; | ||
| 46 | std::array<u64, 0x7FE> base_offsets; | ||
| 47 | }; | ||
| 48 | static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); | ||
| 49 | |||
| 50 | struct SubsectionEntry { | ||
| 51 | u64_le address_patch; | ||
| 52 | INSERT_PADDING_BYTES(0x4); | ||
| 53 | u32_le ctr; | ||
| 54 | }; | ||
| 55 | static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); | ||
| 56 | |||
| 57 | struct SubsectionBucketRaw { | ||
| 58 | INSERT_PADDING_BYTES(4); | ||
| 59 | u32_le number_entries; | ||
| 60 | u64_le end_offset; | ||
| 61 | std::array<SubsectionEntry, 0x3FF> subsection_entries; | ||
| 62 | }; | ||
| 63 | static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); | ||
| 64 | |||
| 65 | // Vector version of SubsectionBucketRaw | ||
| 66 | struct SubsectionBucket { | ||
| 67 | u32 number_entries; | ||
| 68 | u64 end_offset; | ||
| 69 | std::vector<SubsectionEntry> entries; | ||
| 70 | }; | ||
| 71 | |||
| 72 | struct SubsectionBlock { | ||
| 73 | INSERT_PADDING_BYTES(4); | ||
| 74 | u32_le number_buckets; | ||
| 75 | u64_le size; | ||
| 76 | std::array<u64, 0x7FE> base_offsets; | ||
| 77 | }; | ||
| 78 | static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); | ||
| 79 | |||
| 80 | inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) { | ||
| 81 | return {raw.number_entries, | ||
| 82 | raw.end_offset, | ||
| 83 | {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}}; | ||
| 84 | } | ||
| 85 | |||
| 86 | inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) { | ||
| 87 | return {raw.number_entries, | ||
| 88 | raw.end_offset, | ||
| 89 | {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}}; | ||
| 90 | } | ||
| 91 | |||
| 92 | class BKTR : public VfsFile { | ||
| 93 | public: | ||
| 94 | BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation, | ||
| 95 | std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection, | ||
| 96 | std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted, | ||
| 97 | Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); | ||
| 98 | ~BKTR() override; | ||
| 99 | |||
| 100 | std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; | ||
| 101 | |||
| 102 | std::string GetName() const override; | ||
| 103 | |||
| 104 | std::size_t GetSize() const override; | ||
| 105 | |||
| 106 | bool Resize(std::size_t new_size) override; | ||
| 107 | |||
| 108 | VirtualDir GetContainingDirectory() const override; | ||
| 109 | |||
| 110 | bool IsWritable() const override; | ||
| 111 | |||
| 112 | bool IsReadable() const override; | ||
| 113 | |||
| 114 | std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; | ||
| 115 | |||
| 116 | bool Rename(std::string_view name) override; | ||
| 117 | |||
| 118 | private: | ||
| 119 | RelocationEntry GetRelocationEntry(u64 offset) const; | ||
| 120 | RelocationEntry GetNextRelocationEntry(u64 offset) const; | ||
| 121 | |||
| 122 | SubsectionEntry GetSubsectionEntry(u64 offset) const; | ||
| 123 | SubsectionEntry GetNextSubsectionEntry(u64 offset) const; | ||
| 124 | |||
| 125 | RelocationBlock relocation; | ||
| 126 | std::vector<RelocationBucket> relocation_buckets; | ||
| 127 | SubsectionBlock subsection; | ||
| 128 | std::vector<SubsectionBucket> subsection_buckets; | ||
| 129 | |||
| 130 | // Should be the raw base romfs, decrypted. | ||
| 131 | VirtualFile base_romfs; | ||
| 132 | // Should be the raw BKTR romfs, (located at media_offset with size media_size). | ||
| 133 | VirtualFile bktr_romfs; | ||
| 134 | |||
| 135 | bool encrypted; | ||
| 136 | Core::Crypto::Key128 key; | ||
| 137 | |||
| 138 | // Base offset into NCA, used for IV calculation. | ||
| 139 | u64 base_offset; | ||
| 140 | // Distance between IVFC start and RomFS start, used for base reads | ||
| 141 | u64 ivfc_offset; | ||
| 142 | std::array<u8, 8> section_ctr; | ||
| 143 | }; | ||
| 144 | |||
| 145 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 2ba1b34a4..8e475f25a 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -141,8 +141,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { | |||
| 141 | const auto update_tid = GetUpdateTitleID(title_id); | 141 | const auto update_tid = GetUpdateTitleID(title_id); |
| 142 | const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); | 142 | const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); |
| 143 | 143 | ||
| 144 | if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && | 144 | if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) { |
| 145 | update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { | ||
| 146 | LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", | 145 | LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", |
| 147 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); | 146 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); |
| 148 | exefs = update->GetExeFS(); | 147 | exefs = update->GetExeFS(); |
| @@ -295,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st | |||
| 295 | return out; | 294 | return out; |
| 296 | } | 295 | } |
| 297 | 296 | ||
| 298 | bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { | 297 | bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const { |
| 299 | const auto build_id_raw = Common::HexToString(build_id_); | 298 | const auto build_id_raw = Common::HexToString(build_id_); |
| 300 | const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); | 299 | const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); |
| 301 | 300 | ||
| 302 | LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); | 301 | LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); |
| 303 | 302 | ||
| 304 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); | 303 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); |
| 305 | if (load_dir == nullptr) { | 304 | if (load_dir == nullptr) { |
| @@ -353,16 +352,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 353 | const Service::FileSystem::FileSystemController& fs_controller) { | 352 | const Service::FileSystem::FileSystemController& fs_controller) { |
| 354 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); | 353 | const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); |
| 355 | const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); | 354 | const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); |
| 356 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || | 355 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data && |
| 356 | type != ContentRecordType::HtmlDocument) || | ||
| 357 | (load_dir == nullptr && sdmc_load_dir == nullptr)) { | 357 | (load_dir == nullptr && sdmc_load_dir == nullptr)) { |
| 358 | return; | 358 | return; |
| 359 | } | 359 | } |
| 360 | 360 | ||
| 361 | auto extracted = ExtractRomFS(romfs); | ||
| 362 | if (extracted == nullptr) { | ||
| 363 | return; | ||
| 364 | } | ||
| 365 | |||
| 366 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 361 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 367 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); | 362 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); |
| 368 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { | 363 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { |
| @@ -387,6 +382,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 387 | auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); | 382 | auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); |
| 388 | if (ext_dir != nullptr) | 383 | if (ext_dir != nullptr) |
| 389 | layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); | 384 | layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); |
| 385 | |||
| 386 | if (type == ContentRecordType::HtmlDocument) { | ||
| 387 | auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html"); | ||
| 388 | if (manual_dir != nullptr) | ||
| 389 | layers.push_back(std::make_shared<CachedVfsDirectory>(manual_dir)); | ||
| 390 | } | ||
| 390 | } | 391 | } |
| 391 | 392 | ||
| 392 | // When there are no layers to apply, return early as there is no need to rebuild the RomFS | 393 | // When there are no layers to apply, return early as there is no need to rebuild the RomFS |
| @@ -394,6 +395,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 394 | return; | 395 | return; |
| 395 | } | 396 | } |
| 396 | 397 | ||
| 398 | auto extracted = ExtractRomFS(romfs); | ||
| 399 | if (extracted == nullptr) { | ||
| 400 | return; | ||
| 401 | } | ||
| 402 | |||
| 397 | layers.push_back(std::move(extracted)); | 403 | layers.push_back(std::move(extracted)); |
| 398 | 404 | ||
| 399 | auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); | 405 | auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); |
| @@ -412,39 +418,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 412 | romfs = std::move(packed); | 418 | romfs = std::move(packed); |
| 413 | } | 419 | } |
| 414 | 420 | ||
| 415 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, | 421 | VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, |
| 416 | VirtualFile update_raw, bool apply_layeredfs) const { | 422 | ContentRecordType type, VirtualFile packed_update_raw, |
| 423 | bool apply_layeredfs) const { | ||
| 417 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", | 424 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", |
| 418 | title_id, static_cast<u8>(type)); | 425 | title_id, static_cast<u8>(type)); |
| 419 | |||
| 420 | if (type == ContentRecordType::Program || type == ContentRecordType::Data) { | 426 | if (type == ContentRecordType::Program || type == ContentRecordType::Data) { |
| 421 | LOG_INFO(Loader, "{}", log_string); | 427 | LOG_INFO(Loader, "{}", log_string); |
| 422 | } else { | 428 | } else { |
| 423 | LOG_DEBUG(Loader, "{}", log_string); | 429 | LOG_DEBUG(Loader, "{}", log_string); |
| 424 | } | 430 | } |
| 425 | 431 | ||
| 426 | if (romfs == nullptr) { | 432 | if (base_romfs == nullptr) { |
| 427 | return romfs; | 433 | return base_romfs; |
| 428 | } | 434 | } |
| 429 | 435 | ||
| 436 | auto romfs = base_romfs; | ||
| 437 | |||
| 430 | // Game Updates | 438 | // Game Updates |
| 431 | const auto update_tid = GetUpdateTitleID(title_id); | 439 | const auto update_tid = GetUpdateTitleID(title_id); |
| 432 | const auto update = content_provider.GetEntryRaw(update_tid, type); | 440 | const auto update_raw = content_provider.GetEntryRaw(update_tid, type); |
| 433 | 441 | ||
| 434 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 442 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 435 | const auto update_disabled = | 443 | const auto update_disabled = |
| 436 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); | 444 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); |
| 437 | 445 | ||
| 438 | if (!update_disabled && update != nullptr) { | 446 | if (!update_disabled && update_raw != nullptr && base_nca != nullptr) { |
| 439 | const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); | 447 | const auto new_nca = std::make_shared<NCA>(update_raw, base_nca); |
| 440 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && | 448 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && |
| 441 | new_nca->GetRomFS() != nullptr) { | 449 | new_nca->GetRomFS() != nullptr) { |
| 442 | LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", | 450 | LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", |
| 443 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); | 451 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); |
| 444 | romfs = new_nca->GetRomFS(); | 452 | romfs = new_nca->GetRomFS(); |
| 453 | const auto version = | ||
| 454 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)); | ||
| 445 | } | 455 | } |
| 446 | } else if (!update_disabled && update_raw != nullptr) { | 456 | } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { |
| 447 | const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); | 457 | const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca); |
| 448 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && | 458 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && |
| 449 | new_nca->GetRomFS() != nullptr) { | 459 | new_nca->GetRomFS() != nullptr) { |
| 450 | LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); | 460 | LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); |
| @@ -608,7 +618,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { | |||
| 608 | return {}; | 618 | return {}; |
| 609 | } | 619 | } |
| 610 | 620 | ||
| 611 | const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); | 621 | const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control); |
| 612 | if (romfs == nullptr) { | 622 | if (romfs == nullptr) { |
| 613 | return {}; | 623 | return {}; |
| 614 | } | 624 | } |
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 69d15e2f8..03e9c7301 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h | |||
| @@ -52,7 +52,7 @@ public: | |||
| 52 | 52 | ||
| 53 | // Checks to see if PatchNSO() will have any effect given the NSO's build ID. | 53 | // Checks to see if PatchNSO() will have any effect given the NSO's build ID. |
| 54 | // Used to prevent expensive copies in NSO loader. | 54 | // Used to prevent expensive copies in NSO loader. |
| 55 | [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; | 55 | [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const; |
| 56 | 56 | ||
| 57 | // Creates a CheatList object with all | 57 | // Creates a CheatList object with all |
| 58 | [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( | 58 | [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( |
| @@ -61,9 +61,9 @@ public: | |||
| 61 | // Currently tracked RomFS patches: | 61 | // Currently tracked RomFS patches: |
| 62 | // - Game Updates | 62 | // - Game Updates |
| 63 | // - LayeredFS | 63 | // - LayeredFS |
| 64 | [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, | 64 | [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, |
| 65 | ContentRecordType type = ContentRecordType::Program, | 65 | ContentRecordType type = ContentRecordType::Program, |
| 66 | VirtualFile update_raw = nullptr, | 66 | VirtualFile packed_update_raw = nullptr, |
| 67 | bool apply_layeredfs = true) const; | 67 | bool apply_layeredfs = true) const; |
| 68 | 68 | ||
| 69 | // Returns a vector of pairs between patch names and patch versions. | 69 | // Returns a vector of pairs between patch names and patch versions. |
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index a6960170c..04da93d5c 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "common/fs/path_util.h" | 9 | #include "common/fs/path_util.h" |
| 10 | #include "common/hex_util.h" | 10 | #include "common/hex_util.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/scope_exit.h" | ||
| 12 | #include "core/crypto/key_manager.h" | 13 | #include "core/crypto/key_manager.h" |
| 13 | #include "core/file_sys/card_image.h" | 14 | #include "core/file_sys/card_image.h" |
| 14 | #include "core/file_sys/common_funcs.h" | 15 | #include "core/file_sys/common_funcs.h" |
| @@ -416,9 +417,9 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) { | |||
| 416 | 417 | ||
| 417 | if (file == nullptr) | 418 | if (file == nullptr) |
| 418 | continue; | 419 | continue; |
| 419 | const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0); | 420 | const auto nca = std::make_shared<NCA>(parser(file, id)); |
| 420 | if (nca->GetStatus() != Loader::ResultStatus::Success || | 421 | if (nca->GetStatus() != Loader::ResultStatus::Success || |
| 421 | nca->GetType() != NCAContentType::Meta) { | 422 | nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) { |
| 422 | continue; | 423 | continue; |
| 423 | } | 424 | } |
| 424 | 425 | ||
| @@ -500,7 +501,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t | |||
| 500 | const auto raw = GetEntryRaw(title_id, type); | 501 | const auto raw = GetEntryRaw(title_id, type); |
| 501 | if (raw == nullptr) | 502 | if (raw == nullptr) |
| 502 | return nullptr; | 503 | return nullptr; |
| 503 | return std::make_unique<NCA>(raw, nullptr, 0); | 504 | return std::make_unique<NCA>(raw); |
| 504 | } | 505 | } |
| 505 | 506 | ||
| 506 | template <typename T> | 507 | template <typename T> |
| @@ -606,9 +607,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex | |||
| 606 | const auto result = RemoveExistingEntry(title_id); | 607 | const auto result = RemoveExistingEntry(title_id); |
| 607 | 608 | ||
| 608 | // Install Metadata File | 609 | // Install Metadata File |
| 609 | const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data); | 610 | const auto meta_result = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data); |
| 610 | if (res != InstallResult::Success) { | 611 | if (meta_result != InstallResult::Success) { |
| 611 | return res; | 612 | return meta_result; |
| 612 | } | 613 | } |
| 613 | 614 | ||
| 614 | // Install all the other NCAs | 615 | // Install all the other NCAs |
| @@ -621,9 +622,19 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex | |||
| 621 | if (nca == nullptr) { | 622 | if (nca == nullptr) { |
| 622 | return InstallResult::ErrorCopyFailed; | 623 | return InstallResult::ErrorCopyFailed; |
| 623 | } | 624 | } |
| 624 | const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); | 625 | if (nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && |
| 625 | if (res2 != InstallResult::Success) { | 626 | nca->GetTitleId() != title_id) { |
| 626 | return res2; | 627 | // Create fake cnmt for patch to multiprogram application |
| 628 | const auto sub_nca_result = | ||
| 629 | InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy); | ||
| 630 | if (sub_nca_result != InstallResult::Success) { | ||
| 631 | return sub_nca_result; | ||
| 632 | } | ||
| 633 | continue; | ||
| 634 | } | ||
| 635 | const auto nca_result = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); | ||
| 636 | if (nca_result != InstallResult::Success) { | ||
| 637 | return nca_result; | ||
| 627 | } | 638 | } |
| 628 | } | 639 | } |
| 629 | 640 | ||
| @@ -662,7 +673,34 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, | |||
| 662 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); | 673 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); |
| 663 | } | 674 | } |
| 664 | 675 | ||
| 676 | InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header, | ||
| 677 | const ContentRecord& base_record, | ||
| 678 | bool overwrite_if_exists, const VfsCopyFunction& copy) { | ||
| 679 | const CNMTHeader header{ | ||
| 680 | .title_id = nca.GetTitleId(), | ||
| 681 | .title_version = base_header.title_version, | ||
| 682 | .type = base_header.type, | ||
| 683 | .reserved = {}, | ||
| 684 | .table_offset = 0x10, | ||
| 685 | .number_content_entries = 1, | ||
| 686 | .number_meta_entries = 0, | ||
| 687 | .attributes = 0, | ||
| 688 | .reserved2 = {}, | ||
| 689 | .is_committed = 0, | ||
| 690 | .required_download_system_version = 0, | ||
| 691 | .reserved3 = {}, | ||
| 692 | }; | ||
| 693 | const OptionalHeader opt_header{0, 0}; | ||
| 694 | const CNMT new_cnmt(header, opt_header, {base_record}, {}); | ||
| 695 | if (!RawInstallYuzuMeta(new_cnmt)) { | ||
| 696 | return InstallResult::ErrorMetaFailed; | ||
| 697 | } | ||
| 698 | return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id); | ||
| 699 | } | ||
| 700 | |||
| 665 | bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { | 701 | bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { |
| 702 | bool removed_data = false; | ||
| 703 | |||
| 666 | const auto delete_nca = [this](const NcaID& id) { | 704 | const auto delete_nca = [this](const NcaID& id) { |
| 667 | const auto path = GetRelativePathFromNcaID(id, false, true, false); | 705 | const auto path = GetRelativePathFromNcaID(id, false, true, false); |
| 668 | 706 | ||
| @@ -706,11 +744,20 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { | |||
| 706 | const auto deleted_html = delete_nca(html_id); | 744 | const auto deleted_html = delete_nca(html_id); |
| 707 | const auto deleted_legal = delete_nca(legal_id); | 745 | const auto deleted_legal = delete_nca(legal_id); |
| 708 | 746 | ||
| 709 | return deleted_meta && (deleted_meta || deleted_program || deleted_data || | 747 | removed_data |= (deleted_meta || deleted_program || deleted_data || deleted_control || |
| 710 | deleted_control || deleted_html || deleted_legal); | 748 | deleted_html || deleted_legal); |
| 711 | } | 749 | } |
| 712 | 750 | ||
| 713 | return false; | 751 | // If patch entries for any program exist in yuzu meta, remove them |
| 752 | for (u8 i = 0; i < 0x10; i++) { | ||
| 753 | const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta"); | ||
| 754 | const auto filename = GetCNMTName(TitleType::Update, title_id + i); | ||
| 755 | if (meta_dir->GetFile(filename)) { | ||
| 756 | removed_data |= meta_dir->DeleteFile(filename); | ||
| 757 | } | ||
| 758 | } | ||
| 759 | |||
| 760 | return removed_data; | ||
| 714 | } | 761 | } |
| 715 | 762 | ||
| 716 | InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, | 763 | InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, |
| @@ -964,7 +1011,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord | |||
| 964 | const auto res = GetEntryRaw(title_id, type); | 1011 | const auto res = GetEntryRaw(title_id, type); |
| 965 | if (res == nullptr) | 1012 | if (res == nullptr) |
| 966 | return nullptr; | 1013 | return nullptr; |
| 967 | return std::make_unique<NCA>(res, nullptr, 0); | 1014 | return std::make_unique<NCA>(res); |
| 968 | } | 1015 | } |
| 969 | 1016 | ||
| 970 | std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( | 1017 | std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( |
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index bd7f53eaf..64815a845 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h | |||
| @@ -24,6 +24,7 @@ enum class NCAContentType : u8; | |||
| 24 | enum class TitleType : u8; | 24 | enum class TitleType : u8; |
| 25 | 25 | ||
| 26 | struct ContentRecord; | 26 | struct ContentRecord; |
| 27 | struct CNMTHeader; | ||
| 27 | struct MetaRecord; | 28 | struct MetaRecord; |
| 28 | class RegisteredCache; | 29 | class RegisteredCache; |
| 29 | 30 | ||
| @@ -169,6 +170,10 @@ public: | |||
| 169 | InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, | 170 | InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, |
| 170 | const VfsCopyFunction& copy = &VfsRawCopy); | 171 | const VfsCopyFunction& copy = &VfsRawCopy); |
| 171 | 172 | ||
| 173 | InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header, | ||
| 174 | const ContentRecord& base_record, bool overwrite_if_exists = false, | ||
| 175 | const VfsCopyFunction& copy = &VfsRawCopy); | ||
| 176 | |||
| 172 | // Removes an existing entry based on title id | 177 | // Removes an existing entry based on title id |
| 173 | bool RemoveExistingEntry(u64 title_id) const; | 178 | bool RemoveExistingEntry(u64 title_id) const; |
| 174 | 179 | ||
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index aa4726cfa..1bc07dae5 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp | |||
| @@ -26,13 +26,12 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi | |||
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | updatable = app_loader.IsRomFSUpdatable(); | 28 | updatable = app_loader.IsRomFSUpdatable(); |
| 29 | ivfc_offset = app_loader.ReadRomFSIVFCOffset(); | ||
| 30 | } | 29 | } |
| 31 | 30 | ||
| 32 | RomFSFactory::~RomFSFactory() = default; | 31 | RomFSFactory::~RomFSFactory() = default; |
| 33 | 32 | ||
| 34 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { | 33 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { |
| 35 | update_raw = std::move(update_raw_file); | 34 | packed_update_raw = std::move(update_raw_file); |
| 36 | } | 35 | } |
| 37 | 36 | ||
| 38 | VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { | 37 | VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { |
| @@ -40,9 +39,11 @@ VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const | |||
| 40 | return file; | 39 | return file; |
| 41 | } | 40 | } |
| 42 | 41 | ||
| 42 | const auto type = ContentRecordType::Program; | ||
| 43 | const auto nca = content_provider.GetEntry(current_process_title_id, type); | ||
| 43 | const PatchManager patch_manager{current_process_title_id, filesystem_controller, | 44 | const PatchManager patch_manager{current_process_title_id, filesystem_controller, |
| 44 | content_provider}; | 45 | content_provider}; |
| 45 | return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw); | 46 | return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw); |
| 46 | } | 47 | } |
| 47 | 48 | ||
| 48 | VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { | 49 | VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { |
| @@ -54,7 +55,7 @@ VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) | |||
| 54 | 55 | ||
| 55 | const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; | 56 | const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; |
| 56 | 57 | ||
| 57 | return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type); | 58 | return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type); |
| 58 | } | 59 | } |
| 59 | 60 | ||
| 60 | VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, | 61 | VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, |
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 7ec40d19d..e4809bc94 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h | |||
| @@ -40,21 +40,22 @@ public: | |||
| 40 | Service::FileSystem::FileSystemController& controller); | 40 | Service::FileSystem::FileSystemController& controller); |
| 41 | ~RomFSFactory(); | 41 | ~RomFSFactory(); |
| 42 | 42 | ||
| 43 | void SetPackedUpdate(VirtualFile update_raw_file); | 43 | void SetPackedUpdate(VirtualFile packed_update_raw); |
| 44 | [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; | 44 | [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; |
| 45 | [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; | 45 | [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; |
| 46 | [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, | 46 | [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, |
| 47 | ContentRecordType type) const; | 47 | ContentRecordType type) const; |
| 48 | [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; | 48 | [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; |
| 49 | |||
| 50 | private: | ||
| 51 | [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, | 49 | [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, |
| 52 | ContentRecordType type) const; | 50 | ContentRecordType type) const; |
| 53 | 51 | ||
| 52 | private: | ||
| 54 | VirtualFile file; | 53 | VirtualFile file; |
| 55 | VirtualFile update_raw; | 54 | VirtualFile packed_update_raw; |
| 55 | |||
| 56 | VirtualFile base; | ||
| 57 | |||
| 56 | bool updatable; | 58 | bool updatable; |
| 57 | u64 ivfc_offset; | ||
| 58 | 59 | ||
| 59 | ContentProvider& content_provider; | 60 | ContentProvider& content_provider; |
| 60 | Service::FileSystem::FileSystemController& filesystem_controller; | 61 | Service::FileSystem::FileSystemController& filesystem_controller; |
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index c90e6e372..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 | ||
| @@ -249,7 +220,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { | |||
| 249 | } | 220 | } |
| 250 | 221 | ||
| 251 | const auto nca = std::make_shared<NCA>(outer_file); | 222 | const auto nca = std::make_shared<NCA>(outer_file); |
| 252 | if (nca->GetStatus() != Loader::ResultStatus::Success) { | 223 | if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) { |
| 253 | program_status[nca->GetTitleId()] = nca->GetStatus(); | 224 | program_status[nca->GetTitleId()] = nca->GetStatus(); |
| 254 | continue; | 225 | continue; |
| 255 | } | 226 | } |
| @@ -280,7 +251,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { | |||
| 280 | continue; | 251 | continue; |
| 281 | } | 252 | } |
| 282 | 253 | ||
| 283 | auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); | 254 | auto next_nca = std::make_shared<NCA>(std::move(next_file)); |
| 284 | 255 | ||
| 285 | if (next_nca->GetType() == NCAContentType::Program) { | 256 | if (next_nca->GetType() == NCAContentType::Program) { |
| 286 | program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); | 257 | program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); |
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/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 3300d4f79..27755cb58 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | #include "common/assert.h" | 4 | #include "common/assert.h" |
| 5 | #include "common/logging/log.h" | 5 | #include "common/logging/log.h" |
| 6 | #include "common/settings.h" | ||
| 7 | #include "common/settings_enums.h" | ||
| 6 | #include "core/frontend/applets/controller.h" | 8 | #include "core/frontend/applets/controller.h" |
| 7 | #include "core/hid/emulated_controller.h" | 9 | #include "core/hid/emulated_controller.h" |
| 8 | #include "core/hid/hid_core.h" | 10 | #include "core/hid/hid_core.h" |
| @@ -62,7 +64,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac | |||
| 62 | controller->Connect(true); | 64 | controller->Connect(true); |
| 63 | } | 65 | } |
| 64 | } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && | 66 | } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && |
| 65 | !Settings::values.use_docked_mode.GetValue()) { | 67 | !Settings::IsDockedMode()) { |
| 66 | // We should *never* reach here under any normal circumstances. | 68 | // We should *never* reach here under any normal circumstances. |
| 67 | controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); | 69 | controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); |
| 68 | controller->Connect(true); | 70 | controller->Connect(true); |
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index b4081fc39..2590b20da 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include "common/assert.h" | 6 | #include "common/assert.h" |
| 7 | #include "common/settings.h" | 7 | #include "common/settings.h" |
| 8 | #include "common/settings_enums.h" | ||
| 8 | #include "core/frontend/framebuffer_layout.h" | 9 | #include "core/frontend/framebuffer_layout.h" |
| 9 | 10 | ||
| 10 | namespace Layout { | 11 | namespace Layout { |
| @@ -49,7 +50,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) { | |||
| 49 | } | 50 | } |
| 50 | 51 | ||
| 51 | FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) { | 52 | FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) { |
| 52 | const bool is_docked = Settings::values.use_docked_mode.GetValue(); | 53 | const bool is_docked = Settings::IsDockedMode(); |
| 53 | const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width; | 54 | const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width; |
| 54 | const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height; | 55 | const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height; |
| 55 | 56 | ||
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp index 7d6373414..cf53c04d9 100644 --- a/src/core/hid/hid_core.cpp +++ b/src/core/hid/hid_core.cpp | |||
| @@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const { | |||
| 154 | return NpadIdType::Player1; | 154 | return NpadIdType::Player1; |
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | void HIDCore::SetLastActiveController(NpadIdType npad_id) { | ||
| 158 | last_active_controller = npad_id; | ||
| 159 | } | ||
| 160 | |||
| 161 | NpadIdType HIDCore::GetLastActiveController() const { | ||
| 162 | return last_active_controller; | ||
| 163 | } | ||
| 164 | |||
| 157 | void HIDCore::EnableAllControllerConfiguration() { | 165 | void HIDCore::EnableAllControllerConfiguration() { |
| 158 | player_1->EnableConfiguration(); | 166 | player_1->EnableConfiguration(); |
| 159 | player_2->EnableConfiguration(); | 167 | player_2->EnableConfiguration(); |
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h index 5fe36551e..80abab18b 100644 --- a/src/core/hid/hid_core.h +++ b/src/core/hid/hid_core.h | |||
| @@ -48,6 +48,12 @@ public: | |||
| 48 | /// Returns the first disconnected npad id | 48 | /// Returns the first disconnected npad id |
| 49 | NpadIdType GetFirstDisconnectedNpadId() const; | 49 | NpadIdType GetFirstDisconnectedNpadId() const; |
| 50 | 50 | ||
| 51 | /// Sets the npad id of the last active controller | ||
| 52 | void SetLastActiveController(NpadIdType npad_id); | ||
| 53 | |||
| 54 | /// Returns the npad id of the last controller that pushed a button | ||
| 55 | NpadIdType GetLastActiveController() const; | ||
| 56 | |||
| 51 | /// Sets all emulated controllers into configuring mode. | 57 | /// Sets all emulated controllers into configuring mode. |
| 52 | void EnableAllControllerConfiguration(); | 58 | void EnableAllControllerConfiguration(); |
| 53 | 59 | ||
| @@ -77,6 +83,7 @@ private: | |||
| 77 | std::unique_ptr<EmulatedConsole> console; | 83 | std::unique_ptr<EmulatedConsole> console; |
| 78 | std::unique_ptr<EmulatedDevices> devices; | 84 | std::unique_ptr<EmulatedDevices> devices; |
| 79 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; | 85 | NpadStyleTag supported_style_tag{NpadStyleSet::All}; |
| 86 | NpadIdType last_active_controller{NpadIdType::Handheld}; | ||
| 80 | }; | 87 | }; |
| 81 | 88 | ||
| 82 | } // namespace Core::HID | 89 | } // namespace Core::HID |
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h index 6b35f448c..00beb40dd 100644 --- a/src/core/hid/hid_types.h +++ b/src/core/hid/hid_types.h | |||
| @@ -289,6 +289,19 @@ enum class GyroscopeZeroDriftMode : u32 { | |||
| 289 | Tight = 2, | 289 | Tight = 2, |
| 290 | }; | 290 | }; |
| 291 | 291 | ||
| 292 | // This is nn::settings::system::TouchScreenMode | ||
| 293 | enum class TouchScreenMode : u32 { | ||
| 294 | Stylus = 0, | ||
| 295 | Standard = 1, | ||
| 296 | }; | ||
| 297 | |||
| 298 | // This is nn::hid::TouchScreenModeForNx | ||
| 299 | enum class TouchScreenModeForNx : u8 { | ||
| 300 | UseSystemSetting, | ||
| 301 | Finger, | ||
| 302 | Heat2, | ||
| 303 | }; | ||
| 304 | |||
| 292 | // This is nn::hid::NpadStyleTag | 305 | // This is nn::hid::NpadStyleTag |
| 293 | struct NpadStyleTag { | 306 | struct NpadStyleTag { |
| 294 | union { | 307 | union { |
| @@ -334,6 +347,14 @@ struct TouchState { | |||
| 334 | }; | 347 | }; |
| 335 | static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); | 348 | static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); |
| 336 | 349 | ||
| 350 | // This is nn::hid::TouchScreenConfigurationForNx | ||
| 351 | struct TouchScreenConfigurationForNx { | ||
| 352 | TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting}; | ||
| 353 | INSERT_PADDING_BYTES(0xF); | ||
| 354 | }; | ||
| 355 | static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10, | ||
| 356 | "TouchScreenConfigurationForNx is an invalid size"); | ||
| 357 | |||
| 337 | struct NpadColor { | 358 | struct NpadColor { |
| 338 | u8 r{}; | 359 | u8 r{}; |
| 339 | u8 g{}; | 360 | u8 g{}; |
| @@ -662,6 +683,11 @@ struct MouseState { | |||
| 662 | }; | 683 | }; |
| 663 | static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); | 684 | static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); |
| 664 | 685 | ||
| 686 | struct UniquePadId { | ||
| 687 | u64 id; | ||
| 688 | }; | ||
| 689 | static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size"); | ||
| 690 | |||
| 665 | /// Converts a NpadIdType to an array index. | 691 | /// Converts a NpadIdType to an array index. |
| 666 | constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { | 692 | constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { |
| 667 | switch (npad_id_type) { | 693 | switch (npad_id_type) { |
diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp index 90e4e8fb0..e7da7a21d 100644 --- a/src/core/hle/kernel/k_capabilities.cpp +++ b/src/core/hle/kernel/k_capabilities.cpp | |||
| @@ -156,7 +156,6 @@ Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) { | |||
| 156 | const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize; | 156 | const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize; |
| 157 | const size_t num_pages = 1; | 157 | const size_t num_pages = 1; |
| 158 | const size_t size = num_pages * PageSize; | 158 | const size_t size = num_pages * PageSize; |
| 159 | R_UNLESS(num_pages != 0, ResultInvalidSize); | ||
| 160 | R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); | 159 | R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); |
| 161 | R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); | 160 | R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); |
| 162 | 161 | ||
diff --git a/src/core/hle/kernel/k_hardware_timer.h b/src/core/hle/kernel/k_hardware_timer.h index 00bef6ea1..27f43cd19 100644 --- a/src/core/hle/kernel/k_hardware_timer.h +++ b/src/core/hle/kernel/k_hardware_timer.h | |||
| @@ -19,13 +19,7 @@ public: | |||
| 19 | void Initialize(); | 19 | void Initialize(); |
| 20 | void Finalize(); | 20 | void Finalize(); |
| 21 | 21 | ||
| 22 | s64 GetCount() const { | 22 | s64 GetTick() const; |
| 23 | return GetTick(); | ||
| 24 | } | ||
| 25 | |||
| 26 | void RegisterTask(KTimerTask* task, s64 time_from_now) { | ||
| 27 | this->RegisterAbsoluteTask(task, GetTick() + time_from_now); | ||
| 28 | } | ||
| 29 | 23 | ||
| 30 | void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { | 24 | void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { |
| 31 | KScopedDisableDispatch dd{m_kernel}; | 25 | KScopedDisableDispatch dd{m_kernel}; |
| @@ -42,7 +36,6 @@ private: | |||
| 42 | void EnableInterrupt(s64 wakeup_time); | 36 | void EnableInterrupt(s64 wakeup_time); |
| 43 | void DisableInterrupt(); | 37 | void DisableInterrupt(); |
| 44 | bool GetInterruptEnabled(); | 38 | bool GetInterruptEnabled(); |
| 45 | s64 GetTick() const; | ||
| 46 | void DoTask(); | 39 | void DoTask(); |
| 47 | 40 | ||
| 48 | private: | 41 | private: |
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index e573e2a57..4a099286b 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp | |||
| @@ -38,7 +38,7 @@ namespace { | |||
| 38 | */ | 38 | */ |
| 39 | void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, | 39 | void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, |
| 40 | KProcessAddress stack_top) { | 40 | KProcessAddress stack_top) { |
| 41 | const KProcessAddress entry_point = owner_process.GetPageTable().GetCodeRegionStart(); | 41 | const KProcessAddress entry_point = owner_process.GetEntryPoint(); |
| 42 | ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1)); | 42 | ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1)); |
| 43 | 43 | ||
| 44 | KThread* thread = KThread::Create(system.Kernel()); | 44 | KThread* thread = KThread::Create(system.Kernel()); |
| @@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string | |||
| 96 | process->m_is_suspended = false; | 96 | process->m_is_suspended = false; |
| 97 | process->m_schedule_count = 0; | 97 | process->m_schedule_count = 0; |
| 98 | process->m_is_handle_table_initialized = false; | 98 | process->m_is_handle_table_initialized = false; |
| 99 | process->m_is_hbl = false; | ||
| 99 | 100 | ||
| 100 | // Open a reference to the resource limit. | 101 | // Open a reference to the resource limit. |
| 101 | process->m_resource_limit->Open(); | 102 | process->m_resource_limit->Open(); |
| @@ -351,12 +352,29 @@ Result KProcess::SetActivity(ProcessActivity activity) { | |||
| 351 | R_SUCCEED(); | 352 | R_SUCCEED(); |
| 352 | } | 353 | } |
| 353 | 354 | ||
| 354 | Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { | 355 | Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, |
| 356 | bool is_hbl) { | ||
| 355 | m_program_id = metadata.GetTitleID(); | 357 | m_program_id = metadata.GetTitleID(); |
| 356 | m_ideal_core = metadata.GetMainThreadCore(); | 358 | m_ideal_core = metadata.GetMainThreadCore(); |
| 357 | m_is_64bit_process = metadata.Is64BitProgram(); | 359 | m_is_64bit_process = metadata.Is64BitProgram(); |
| 358 | m_system_resource_size = metadata.GetSystemResourceSize(); | 360 | m_system_resource_size = metadata.GetSystemResourceSize(); |
| 359 | m_image_size = code_size; | 361 | m_image_size = code_size; |
| 362 | m_is_hbl = is_hbl; | ||
| 363 | |||
| 364 | if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { | ||
| 365 | // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. | ||
| 366 | // However, some (buggy) programs/libraries like skyline incorrectly depend on the | ||
| 367 | // existence of ASLR pages before the entry point, so we will adjust the load address | ||
| 368 | // to point to about 2GiB into the ASLR region. | ||
| 369 | m_code_address = 0x8000'0000; | ||
| 370 | } else { | ||
| 371 | // All other processes can be mapped at the beginning of the code region. | ||
| 372 | if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) { | ||
| 373 | m_code_address = 0x800'0000; | ||
| 374 | } else { | ||
| 375 | m_code_address = 0x20'0000; | ||
| 376 | } | ||
| 377 | } | ||
| 360 | 378 | ||
| 361 | KScopedResourceReservation memory_reservation( | 379 | KScopedResourceReservation memory_reservation( |
| 362 | m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size); | 380 | m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size); |
| @@ -368,15 +386,15 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: | |||
| 368 | // Initialize process address space | 386 | // Initialize process address space |
| 369 | if (const Result result{m_page_table.InitializeForProcess( | 387 | if (const Result result{m_page_table.InitializeForProcess( |
| 370 | metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application, | 388 | metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application, |
| 371 | 0x8000000, code_size, std::addressof(m_kernel.GetAppSystemResource()), m_resource_limit, | 389 | this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()), |
| 372 | m_kernel.System().ApplicationMemory())}; | 390 | m_resource_limit, m_kernel.System().ApplicationMemory())}; |
| 373 | result.IsError()) { | 391 | result.IsError()) { |
| 374 | R_RETURN(result); | 392 | R_RETURN(result); |
| 375 | } | 393 | } |
| 376 | 394 | ||
| 377 | // Map process code region | 395 | // Map process code region |
| 378 | if (const Result result{m_page_table.MapProcessCode(m_page_table.GetCodeRegionStart(), | 396 | if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize, |
| 379 | code_size / PageSize, KMemoryState::Code, | 397 | KMemoryState::Code, |
| 380 | KMemoryPermission::None)}; | 398 | KMemoryPermission::None)}; |
| 381 | result.IsError()) { | 399 | result.IsError()) { |
| 382 | R_RETURN(result); | 400 | R_RETURN(result); |
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index c9b37e138..146e07a57 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h | |||
| @@ -177,6 +177,10 @@ public: | |||
| 177 | return m_program_id; | 177 | return m_program_id; |
| 178 | } | 178 | } |
| 179 | 179 | ||
| 180 | KProcessAddress GetEntryPoint() const { | ||
| 181 | return m_code_address; | ||
| 182 | } | ||
| 183 | |||
| 180 | /// Gets the resource limit descriptor for this process | 184 | /// Gets the resource limit descriptor for this process |
| 181 | KResourceLimit* GetResourceLimit() const; | 185 | KResourceLimit* GetResourceLimit() const; |
| 182 | 186 | ||
| @@ -334,7 +338,8 @@ public: | |||
| 334 | * @returns ResultSuccess if all relevant metadata was able to be | 338 | * @returns ResultSuccess if all relevant metadata was able to be |
| 335 | * loaded and parsed. Otherwise, an error code is returned. | 339 | * loaded and parsed. Otherwise, an error code is returned. |
| 336 | */ | 340 | */ |
| 337 | Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); | 341 | Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, |
| 342 | bool is_hbl); | ||
| 338 | 343 | ||
| 339 | /** | 344 | /** |
| 340 | * Starts the main application thread for this process. | 345 | * Starts the main application thread for this process. |
| @@ -364,6 +369,10 @@ public: | |||
| 364 | return GetProcessId(); | 369 | return GetProcessId(); |
| 365 | } | 370 | } |
| 366 | 371 | ||
| 372 | bool IsHbl() const { | ||
| 373 | return m_is_hbl; | ||
| 374 | } | ||
| 375 | |||
| 367 | bool IsSignaled() const override; | 376 | bool IsSignaled() const override; |
| 368 | 377 | ||
| 369 | void DoWorkerTaskImpl(); | 378 | void DoWorkerTaskImpl(); |
| @@ -485,6 +494,9 @@ private: | |||
| 485 | /// Address indicating the location of the process' dedicated TLS region. | 494 | /// Address indicating the location of the process' dedicated TLS region. |
| 486 | KProcessAddress m_plr_address = 0; | 495 | KProcessAddress m_plr_address = 0; |
| 487 | 496 | ||
| 497 | /// Address indicating the location of the process's entry point. | ||
| 498 | KProcessAddress m_code_address = 0; | ||
| 499 | |||
| 488 | /// Random values for svcGetInfo RandomEntropy | 500 | /// Random values for svcGetInfo RandomEntropy |
| 489 | std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{}; | 501 | std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{}; |
| 490 | 502 | ||
| @@ -518,6 +530,7 @@ private: | |||
| 518 | bool m_is_immortal{}; | 530 | bool m_is_immortal{}; |
| 519 | bool m_is_handle_table_initialized{}; | 531 | bool m_is_handle_table_initialized{}; |
| 520 | bool m_is_initialized{}; | 532 | bool m_is_initialized{}; |
| 533 | bool m_is_hbl{}; | ||
| 521 | 534 | ||
| 522 | std::atomic<u16> m_num_running_threads{}; | 535 | std::atomic<u16> m_num_running_threads{}; |
| 523 | 536 | ||
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index fcee26a29..d8a63aaf8 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include "common/overflow.h" | 5 | #include "common/overflow.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/core_timing.h" | 7 | #include "core/core_timing.h" |
| 8 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 8 | #include "core/hle/kernel/k_resource_limit.h" | 9 | #include "core/hle/kernel/k_resource_limit.h" |
| 9 | #include "core/hle/kernel/svc_results.h" | 10 | #include "core/hle/kernel/svc_results.h" |
| 10 | 11 | ||
| @@ -15,9 +16,7 @@ KResourceLimit::KResourceLimit(KernelCore& kernel) | |||
| 15 | : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} | 16 | : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} |
| 16 | KResourceLimit::~KResourceLimit() = default; | 17 | KResourceLimit::~KResourceLimit() = default; |
| 17 | 18 | ||
| 18 | void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) { | 19 | void KResourceLimit::Initialize() {} |
| 19 | m_core_timing = core_timing; | ||
| 20 | } | ||
| 21 | 20 | ||
| 22 | void KResourceLimit::Finalize() {} | 21 | void KResourceLimit::Finalize() {} |
| 23 | 22 | ||
| @@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) { | |||
| 86 | } | 85 | } |
| 87 | 86 | ||
| 88 | bool KResourceLimit::Reserve(LimitableResource which, s64 value) { | 87 | bool KResourceLimit::Reserve(LimitableResource which, s64 value) { |
| 89 | return Reserve(which, value, m_core_timing->GetGlobalTimeNs().count() + DefaultTimeout); | 88 | return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout); |
| 90 | } | 89 | } |
| 91 | 90 | ||
| 92 | bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { | 91 | bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { |
| @@ -117,7 +116,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { | |||
| 117 | } | 116 | } |
| 118 | 117 | ||
| 119 | if (m_current_hints[index] + value <= m_limit_values[index] && | 118 | if (m_current_hints[index] + value <= m_limit_values[index] && |
| 120 | (timeout < 0 || m_core_timing->GetGlobalTimeNs().count() < timeout)) { | 119 | (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) { |
| 121 | m_waiter_count++; | 120 | m_waiter_count++; |
| 122 | m_cond_var.Wait(std::addressof(m_lock), timeout, false); | 121 | m_cond_var.Wait(std::addressof(m_lock), timeout, false); |
| 123 | m_waiter_count--; | 122 | m_waiter_count--; |
| @@ -154,7 +153,7 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) { | |||
| 154 | 153 | ||
| 155 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { | 154 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { |
| 156 | auto* resource_limit = KResourceLimit::Create(system.Kernel()); | 155 | auto* resource_limit = KResourceLimit::Create(system.Kernel()); |
| 157 | resource_limit->Initialize(std::addressof(system.CoreTiming())); | 156 | resource_limit->Initialize(); |
| 158 | 157 | ||
| 159 | // Initialize default resource limit values. | 158 | // Initialize default resource limit values. |
| 160 | // TODO(bunnei): These values are the system defaults, the limits for service processes are | 159 | // TODO(bunnei): These values are the system defaults, the limits for service processes are |
diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h index 15e69af56..b733ec8f8 100644 --- a/src/core/hle/kernel/k_resource_limit.h +++ b/src/core/hle/kernel/k_resource_limit.h | |||
| @@ -31,7 +31,7 @@ public: | |||
| 31 | explicit KResourceLimit(KernelCore& kernel); | 31 | explicit KResourceLimit(KernelCore& kernel); |
| 32 | ~KResourceLimit() override; | 32 | ~KResourceLimit() override; |
| 33 | 33 | ||
| 34 | void Initialize(const Core::Timing::CoreTiming* core_timing); | 34 | void Initialize(); |
| 35 | void Finalize() override; | 35 | void Finalize() override; |
| 36 | 36 | ||
| 37 | s64 GetLimitValue(LimitableResource which) const; | 37 | s64 GetLimitValue(LimitableResource which) const; |
| @@ -57,7 +57,6 @@ private: | |||
| 57 | mutable KLightLock m_lock; | 57 | mutable KLightLock m_lock; |
| 58 | s32 m_waiter_count{}; | 58 | s32 m_waiter_count{}; |
| 59 | KLightConditionVariable m_cond_var; | 59 | KLightConditionVariable m_cond_var; |
| 60 | const Core::Timing::CoreTiming* m_core_timing{}; | ||
| 61 | }; | 60 | }; |
| 62 | 61 | ||
| 63 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); | 62 | KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); |
diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h index c485022f5..b62415da7 100644 --- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h +++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h | |||
| @@ -28,7 +28,7 @@ public: | |||
| 28 | ~KScopedSchedulerLockAndSleep() { | 28 | ~KScopedSchedulerLockAndSleep() { |
| 29 | // Register the sleep. | 29 | // Register the sleep. |
| 30 | if (m_timeout_tick > 0) { | 30 | if (m_timeout_tick > 0) { |
| 31 | m_timer->RegisterTask(m_thread, m_timeout_tick); | 31 | m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick); |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | // Unlock the scheduler. | 34 | // Unlock the scheduler. |
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index ebe7582c6..a1134b7e2 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp | |||
| @@ -231,7 +231,7 @@ struct KernelCore::Impl { | |||
| 231 | void InitializeSystemResourceLimit(KernelCore& kernel, | 231 | void InitializeSystemResourceLimit(KernelCore& kernel, |
| 232 | const Core::Timing::CoreTiming& core_timing) { | 232 | const Core::Timing::CoreTiming& core_timing) { |
| 233 | system_resource_limit = KResourceLimit::Create(system.Kernel()); | 233 | system_resource_limit = KResourceLimit::Create(system.Kernel()); |
| 234 | system_resource_limit->Initialize(&core_timing); | 234 | system_resource_limit->Initialize(); |
| 235 | KResourceLimit::Register(kernel, system_resource_limit); | 235 | KResourceLimit::Register(kernel, system_resource_limit); |
| 236 | 236 | ||
| 237 | const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; | 237 | const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; |
diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp index 04cc5ea64..90ee43521 100644 --- a/src/core/hle/kernel/svc/svc_address_arbiter.cpp +++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 5 | #include "core/hle/kernel/k_memory_layout.h" | 6 | #include "core/hle/kernel/k_memory_layout.h" |
| 6 | #include "core/hle/kernel/k_process.h" | 7 | #include "core/hle/kernel/k_process.h" |
| 7 | #include "core/hle/kernel/kernel.h" | 8 | #include "core/hle/kernel/kernel.h" |
| @@ -52,7 +53,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ | |||
| 52 | if (timeout_ns > 0) { | 53 | if (timeout_ns > 0) { |
| 53 | const s64 offset_tick(timeout_ns); | 54 | const s64 offset_tick(timeout_ns); |
| 54 | if (offset_tick > 0) { | 55 | if (offset_tick > 0) { |
| 55 | timeout = offset_tick + 2; | 56 | timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; |
| 56 | if (timeout <= 0) { | 57 | if (timeout <= 0) { |
| 57 | timeout = std::numeric_limits<s64>::max(); | 58 | timeout = std::numeric_limits<s64>::max(); |
| 58 | } | 59 | } |
diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp index ca120d67e..bb678e6c5 100644 --- a/src/core/hle/kernel/svc/svc_condition_variable.cpp +++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 5 | #include "core/hle/kernel/k_memory_layout.h" | 6 | #include "core/hle/kernel/k_memory_layout.h" |
| 6 | #include "core/hle/kernel/k_process.h" | 7 | #include "core/hle/kernel/k_process.h" |
| 7 | #include "core/hle/kernel/kernel.h" | 8 | #include "core/hle/kernel/kernel.h" |
| @@ -25,7 +26,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u | |||
| 25 | if (timeout_ns > 0) { | 26 | if (timeout_ns > 0) { |
| 26 | const s64 offset_tick(timeout_ns); | 27 | const s64 offset_tick(timeout_ns); |
| 27 | if (offset_tick > 0) { | 28 | if (offset_tick > 0) { |
| 28 | timeout = offset_tick + 2; | 29 | timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; |
| 29 | if (timeout <= 0) { | 30 | if (timeout <= 0) { |
| 30 | timeout = std::numeric_limits<s64>::max(); | 31 | timeout = std::numeric_limits<s64>::max(); |
| 31 | } | 32 | } |
diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp index 4c14ce668..00b65429b 100644 --- a/src/core/hle/kernel/svc/svc_debug_string.cpp +++ b/src/core/hle/kernel/svc/svc_debug_string.cpp | |||
| @@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) { | |||
| 14 | 14 | ||
| 15 | std::string str(len, '\0'); | 15 | std::string str(len, '\0'); |
| 16 | GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); | 16 | GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); |
| 17 | LOG_DEBUG(Debug_Emulated, "{}", str); | 17 | LOG_INFO(Debug_Emulated, "{}", str); |
| 18 | 18 | ||
| 19 | R_SUCCEED(); | 19 | R_SUCCEED(); |
| 20 | } | 20 | } |
diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp index 580cf2f75..c581c086b 100644 --- a/src/core/hle/kernel/svc/svc_exception.cpp +++ b/src/core/hle/kernel/svc/svc_exception.cpp | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include "core/core.h" | 4 | #include "core/core.h" |
| 5 | #include "core/debugger/debugger.h" | 5 | #include "core/debugger/debugger.h" |
| 6 | #include "core/hle/kernel/k_process.h" | ||
| 6 | #include "core/hle/kernel/k_thread.h" | 7 | #include "core/hle/kernel/k_thread.h" |
| 7 | #include "core/hle/kernel/svc.h" | 8 | #include "core/hle/kernel/svc.h" |
| 8 | #include "core/hle/kernel/svc_types.h" | 9 | #include "core/hle/kernel/svc_types.h" |
| @@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { | |||
| 107 | system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); | 108 | system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); |
| 108 | } | 109 | } |
| 109 | 110 | ||
| 110 | if (system.DebuggerEnabled()) { | 111 | const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl(); |
| 112 | const bool should_break = is_hbl || !notification_only; | ||
| 113 | |||
| 114 | if (system.DebuggerEnabled() && should_break) { | ||
| 111 | auto* thread = system.Kernel().GetCurrentEmuThread(); | 115 | auto* thread = system.Kernel().GetCurrentEmuThread(); |
| 112 | system.GetDebugger().NotifyThreadStopped(thread); | 116 | system.GetDebugger().NotifyThreadStopped(thread); |
| 113 | thread->RequestSuspend(Kernel::SuspendType::Debug); | 117 | thread->RequestSuspend(Kernel::SuspendType::Debug); |
diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp index 373ae7c8d..6b5e1cb8d 100644 --- a/src/core/hle/kernel/svc/svc_ipc.cpp +++ b/src/core/hle/kernel/svc/svc_ipc.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include "common/scratch_buffer.h" | 5 | #include "common/scratch_buffer.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/hle/kernel/k_client_session.h" | 7 | #include "core/hle/kernel/k_client_session.h" |
| 8 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 8 | #include "core/hle/kernel/k_process.h" | 9 | #include "core/hle/kernel/k_process.h" |
| 9 | #include "core/hle/kernel/k_server_session.h" | 10 | #include "core/hle/kernel/k_server_session.h" |
| 10 | #include "core/hle/kernel/svc.h" | 11 | #include "core/hle/kernel/svc.h" |
| @@ -82,12 +83,29 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad | |||
| 82 | R_TRY(session->SendReply()); | 83 | R_TRY(session->SendReply()); |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 86 | // Convert the timeout from nanoseconds to ticks. | ||
| 87 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... | ||
| 88 | s64 timeout; | ||
| 89 | if (timeout_ns > 0) { | ||
| 90 | const s64 offset_tick(timeout_ns); | ||
| 91 | if (offset_tick > 0) { | ||
| 92 | timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; | ||
| 93 | if (timeout <= 0) { | ||
| 94 | timeout = std::numeric_limits<s64>::max(); | ||
| 95 | } | ||
| 96 | } else { | ||
| 97 | timeout = std::numeric_limits<s64>::max(); | ||
| 98 | } | ||
| 99 | } else { | ||
| 100 | timeout = timeout_ns; | ||
| 101 | } | ||
| 102 | |||
| 85 | // Wait for a message. | 103 | // Wait for a message. |
| 86 | while (true) { | 104 | while (true) { |
| 87 | // Wait for an object. | 105 | // Wait for an object. |
| 88 | s32 index; | 106 | s32 index; |
| 89 | Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), | 107 | Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), |
| 90 | num_handles, timeout_ns); | 108 | num_handles, timeout); |
| 91 | if (result == ResultTimedOut) { | 109 | if (result == ResultTimedOut) { |
| 92 | R_RETURN(result); | 110 | R_RETURN(result); |
| 93 | } | 111 | } |
diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp index 732bc017e..c8e820b6a 100644 --- a/src/core/hle/kernel/svc/svc_resource_limit.cpp +++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp | |||
| @@ -21,7 +21,7 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) { | |||
| 21 | SCOPE_EXIT({ resource_limit->Close(); }); | 21 | SCOPE_EXIT({ resource_limit->Close(); }); |
| 22 | 22 | ||
| 23 | // Initialize the resource limit. | 23 | // Initialize the resource limit. |
| 24 | resource_limit->Initialize(std::addressof(system.CoreTiming())); | 24 | resource_limit->Initialize(); |
| 25 | 25 | ||
| 26 | // Register the limit. | 26 | // Register the limit. |
| 27 | KResourceLimit::Register(kernel, resource_limit); | 27 | KResourceLimit::Register(kernel, resource_limit); |
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp index 366e8ed4a..8ebc1bd1c 100644 --- a/src/core/hle/kernel/svc/svc_synchronization.cpp +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include "common/scope_exit.h" | 4 | #include "common/scope_exit.h" |
| 5 | #include "common/scratch_buffer.h" | 5 | #include "common/scratch_buffer.h" |
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 7 | #include "core/hle/kernel/k_process.h" | 8 | #include "core/hle/kernel/k_process.h" |
| 8 | #include "core/hle/kernel/k_readable_event.h" | 9 | #include "core/hle/kernel/k_readable_event.h" |
| 9 | #include "core/hle/kernel/svc.h" | 10 | #include "core/hle/kernel/svc.h" |
| @@ -83,9 +84,20 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha | |||
| 83 | } | 84 | } |
| 84 | }); | 85 | }); |
| 85 | 86 | ||
| 87 | // Convert the timeout from nanoseconds to ticks. | ||
| 88 | s64 timeout; | ||
| 89 | if (timeout_ns > 0) { | ||
| 90 | u64 ticks = kernel.HardwareTimer().GetTick(); | ||
| 91 | ticks += timeout_ns; | ||
| 92 | ticks += 2; | ||
| 93 | |||
| 94 | timeout = ticks; | ||
| 95 | } else { | ||
| 96 | timeout = timeout_ns; | ||
| 97 | } | ||
| 98 | |||
| 86 | // Wait on the objects. | 99 | // Wait on the objects. |
| 87 | Result res = | 100 | Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout); |
| 88 | KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns); | ||
| 89 | 101 | ||
| 90 | R_SUCCEED_IF(res == ResultSessionClosed); | 102 | R_SUCCEED_IF(res == ResultSessionClosed); |
| 91 | R_RETURN(res); | 103 | R_RETURN(res); |
diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp index 92bcea72b..933b82e30 100644 --- a/src/core/hle/kernel/svc/svc_thread.cpp +++ b/src/core/hle/kernel/svc/svc_thread.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include "common/scope_exit.h" | 4 | #include "common/scope_exit.h" |
| 5 | #include "core/core.h" | 5 | #include "core/core.h" |
| 6 | #include "core/core_timing.h" | 6 | #include "core/core_timing.h" |
| 7 | #include "core/hle/kernel/k_hardware_timer.h" | ||
| 7 | #include "core/hle/kernel/k_process.h" | 8 | #include "core/hle/kernel/k_process.h" |
| 8 | #include "core/hle/kernel/k_scoped_resource_reservation.h" | 9 | #include "core/hle/kernel/k_scoped_resource_reservation.h" |
| 9 | #include "core/hle/kernel/k_thread.h" | 10 | #include "core/hle/kernel/k_thread.h" |
| @@ -42,9 +43,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u | |||
| 42 | R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); | 43 | R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); |
| 43 | 44 | ||
| 44 | // Reserve a new thread from the process resource limit (waiting up to 100ms). | 45 | // Reserve a new thread from the process resource limit (waiting up to 100ms). |
| 45 | KScopedResourceReservation thread_reservation( | 46 | KScopedResourceReservation thread_reservation(std::addressof(process), |
| 46 | std::addressof(process), LimitableResource::ThreadCountMax, 1, | 47 | LimitableResource::ThreadCountMax, 1, |
| 47 | system.CoreTiming().GetGlobalTimeNs().count() + 100000000); | 48 | kernel.HardwareTimer().GetTick() + 100000000); |
| 48 | R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); | 49 | R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); |
| 49 | 50 | ||
| 50 | // Create the thread. | 51 | // Create the thread. |
| @@ -102,20 +103,31 @@ void ExitThread(Core::System& system) { | |||
| 102 | } | 103 | } |
| 103 | 104 | ||
| 104 | /// Sleep the current thread | 105 | /// Sleep the current thread |
| 105 | void SleepThread(Core::System& system, s64 nanoseconds) { | 106 | void SleepThread(Core::System& system, s64 ns) { |
| 106 | auto& kernel = system.Kernel(); | 107 | auto& kernel = system.Kernel(); |
| 107 | const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); | 108 | const auto yield_type = static_cast<Svc::YieldType>(ns); |
| 108 | 109 | ||
| 109 | LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); | 110 | LOG_TRACE(Kernel_SVC, "called nanoseconds={}", ns); |
| 110 | 111 | ||
| 111 | // When the input tick is positive, sleep. | 112 | // When the input tick is positive, sleep. |
| 112 | if (nanoseconds > 0) { | 113 | if (ns > 0) { |
| 113 | // Convert the timeout from nanoseconds to ticks. | 114 | // Convert the timeout from nanoseconds to ticks. |
| 114 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... | 115 | // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... |
| 116 | s64 timeout; | ||
| 117 | |||
| 118 | const s64 offset_tick(ns); | ||
| 119 | if (offset_tick > 0) { | ||
| 120 | timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; | ||
| 121 | if (timeout <= 0) { | ||
| 122 | timeout = std::numeric_limits<s64>::max(); | ||
| 123 | } | ||
| 124 | } else { | ||
| 125 | timeout = std::numeric_limits<s64>::max(); | ||
| 126 | } | ||
| 115 | 127 | ||
| 116 | // Sleep. | 128 | // Sleep. |
| 117 | // NOTE: Nintendo does not check the result of this sleep. | 129 | // NOTE: Nintendo does not check the result of this sleep. |
| 118 | static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); | 130 | static_cast<void>(GetCurrentThread(kernel).Sleep(timeout)); |
| 119 | } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { | 131 | } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { |
| 120 | KScheduler::YieldWithoutCoreMigration(kernel); | 132 | KScheduler::YieldWithoutCoreMigration(kernel); |
| 121 | } else if (yield_type == Svc::YieldType::WithCoreMigration) { | 133 | } else if (yield_type == Svc::YieldType::WithCoreMigration) { |
| @@ -124,7 +136,6 @@ void SleepThread(Core::System& system, s64 nanoseconds) { | |||
| 124 | KScheduler::YieldToAnyThread(kernel); | 136 | KScheduler::YieldToAnyThread(kernel); |
| 125 | } else { | 137 | } else { |
| 126 | // Nintendo does nothing at all if an otherwise invalid value is passed. | 138 | // Nintendo does nothing at all if an otherwise invalid value is passed. |
| 127 | ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); | ||
| 128 | } | 139 | } |
| 129 | } | 140 | } |
| 130 | 141 | ||
diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 92a1439eb..dd0b27f47 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h | |||
| @@ -62,7 +62,7 @@ enum class ErrorModule : u32 { | |||
| 62 | XCD = 108, | 62 | XCD = 108, |
| 63 | TMP451 = 109, | 63 | TMP451 = 109, |
| 64 | NIFM = 110, | 64 | NIFM = 110, |
| 65 | Hwopus = 111, | 65 | HwOpus = 111, |
| 66 | LSM6DS3 = 112, | 66 | LSM6DS3 = 112, |
| 67 | Bluetooth = 113, | 67 | Bluetooth = 113, |
| 68 | VI = 114, | 68 | VI = 114, |
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 8d057b3a8..8ffdd19e7 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include <cinttypes> | 6 | #include <cinttypes> |
| 7 | #include <cstring> | 7 | #include <cstring> |
| 8 | #include "common/settings.h" | 8 | #include "common/settings.h" |
| 9 | #include "common/settings_enums.h" | ||
| 9 | #include "core/core.h" | 10 | #include "core/core.h" |
| 10 | #include "core/file_sys/control_metadata.h" | 11 | #include "core/file_sys/control_metadata.h" |
| 11 | #include "core/file_sys/patch_manager.h" | 12 | #include "core/file_sys/patch_manager.h" |
| @@ -45,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3}; | |||
| 45 | constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; | 46 | constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; |
| 46 | 47 | ||
| 47 | enum class LaunchParameterKind : u32 { | 48 | enum class LaunchParameterKind : u32 { |
| 48 | ApplicationSpecific = 1, | 49 | UserChannel = 1, |
| 49 | AccountPreselectedUser = 2, | 50 | AccountPreselectedUser = 2, |
| 50 | }; | 51 | }; |
| 51 | 52 | ||
| @@ -340,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) { | |||
| 340 | void ISelfController::LockExit(HLERequestContext& ctx) { | 341 | void ISelfController::LockExit(HLERequestContext& ctx) { |
| 341 | LOG_DEBUG(Service_AM, "called"); | 342 | LOG_DEBUG(Service_AM, "called"); |
| 342 | 343 | ||
| 343 | system.SetExitLock(true); | 344 | system.SetExitLocked(true); |
| 344 | 345 | ||
| 345 | IPC::ResponseBuilder rb{ctx, 2}; | 346 | IPC::ResponseBuilder rb{ctx, 2}; |
| 346 | rb.Push(ResultSuccess); | 347 | rb.Push(ResultSuccess); |
| @@ -349,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) { | |||
| 349 | void ISelfController::UnlockExit(HLERequestContext& ctx) { | 350 | void ISelfController::UnlockExit(HLERequestContext& ctx) { |
| 350 | LOG_DEBUG(Service_AM, "called"); | 351 | LOG_DEBUG(Service_AM, "called"); |
| 351 | 352 | ||
| 352 | system.SetExitLock(false); | 353 | system.SetExitLocked(false); |
| 353 | 354 | ||
| 354 | IPC::ResponseBuilder rb{ctx, 2}; | 355 | IPC::ResponseBuilder rb{ctx, 2}; |
| 355 | rb.Push(ResultSuccess); | 356 | rb.Push(ResultSuccess); |
| 357 | |||
| 358 | if (system.GetExitRequested()) { | ||
| 359 | system.Exit(); | ||
| 360 | } | ||
| 356 | } | 361 | } |
| 357 | 362 | ||
| 358 | void ISelfController::EnterFatalSection(HLERequestContext& ctx) { | 363 | void ISelfController::EnterFatalSection(HLERequestContext& ctx) { |
| @@ -833,7 +838,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(HLERequestContext& ctx) { | |||
| 833 | IPC::ResponseBuilder rb{ctx, 4}; | 838 | IPC::ResponseBuilder rb{ctx, 4}; |
| 834 | rb.Push(ResultSuccess); | 839 | rb.Push(ResultSuccess); |
| 835 | 840 | ||
| 836 | if (Settings::values.use_docked_mode.GetValue()) { | 841 | if (Settings::IsDockedMode()) { |
| 837 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); | 842 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); |
| 838 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); | 843 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); |
| 839 | } else { | 844 | } else { |
| @@ -921,7 +926,7 @@ void IStorage::Open(HLERequestContext& ctx) { | |||
| 921 | } | 926 | } |
| 922 | 927 | ||
| 923 | void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) { | 928 | void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) { |
| 924 | const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()}; | 929 | const bool use_docked_mode{Settings::IsDockedMode()}; |
| 925 | LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); | 930 | LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); |
| 926 | 931 | ||
| 927 | IPC::ResponseBuilder rb{ctx, 3}; | 932 | IPC::ResponseBuilder rb{ctx, 3}; |
| @@ -1381,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) | |||
| 1381 | {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, | 1386 | {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, |
| 1382 | {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, | 1387 | {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, |
| 1383 | {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, | 1388 | {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, |
| 1384 | {28, nullptr, "GetSaveDataSizeMax"}, | 1389 | {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"}, |
| 1385 | {29, nullptr, "GetCacheStorageMax"}, | 1390 | {29, nullptr, "GetCacheStorageMax"}, |
| 1386 | {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, | 1391 | {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, |
| 1387 | {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, | 1392 | {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, |
| @@ -1513,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { | |||
| 1513 | IPC::RequestParser rp{ctx}; | 1518 | IPC::RequestParser rp{ctx}; |
| 1514 | const auto kind = rp.PopEnum<LaunchParameterKind>(); | 1519 | const auto kind = rp.PopEnum<LaunchParameterKind>(); |
| 1515 | 1520 | ||
| 1516 | LOG_DEBUG(Service_AM, "called, kind={:08X}", kind); | 1521 | LOG_INFO(Service_AM, "called, kind={:08X}", kind); |
| 1517 | 1522 | ||
| 1518 | if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { | 1523 | if (kind == LaunchParameterKind::UserChannel) { |
| 1519 | const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) { | 1524 | auto channel = system.GetUserChannel(); |
| 1520 | return system.GetFileSystemController().GetBCATDirectory(tid); | 1525 | if (channel.empty()) { |
| 1521 | }); | 1526 | LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); |
| 1522 | const auto build_id_full = system.GetApplicationProcessBuildID(); | 1527 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1523 | u64 build_id{}; | 1528 | rb.Push(AM::ResultNoDataInChannel); |
| 1524 | std::memcpy(&build_id, build_id_full.data(), sizeof(u64)); | ||
| 1525 | |||
| 1526 | auto data = | ||
| 1527 | backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id}); | ||
| 1528 | if (data.has_value()) { | ||
| 1529 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 1530 | rb.Push(ResultSuccess); | ||
| 1531 | rb.PushIpcInterface<IStorage>(system, std::move(*data)); | ||
| 1532 | launch_popped_application_specific = true; | ||
| 1533 | return; | 1529 | return; |
| 1534 | } | 1530 | } |
| 1531 | |||
| 1532 | auto data = channel.back(); | ||
| 1533 | channel.pop_back(); | ||
| 1534 | |||
| 1535 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 1536 | rb.Push(ResultSuccess); | ||
| 1537 | rb.PushIpcInterface<IStorage>(system, std::move(data)); | ||
| 1535 | } else if (kind == LaunchParameterKind::AccountPreselectedUser && | 1538 | } else if (kind == LaunchParameterKind::AccountPreselectedUser && |
| 1536 | !launch_popped_account_preselect) { | 1539 | !launch_popped_account_preselect) { |
| 1540 | // TODO: Verify this is hw-accurate | ||
| 1537 | LaunchParameterAccountPreselectedUser params{}; | 1541 | LaunchParameterAccountPreselectedUser params{}; |
| 1538 | 1542 | ||
| 1539 | params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; | 1543 | params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; |
| @@ -1545,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { | |||
| 1545 | params.current_user = *uuid; | 1549 | params.current_user = *uuid; |
| 1546 | 1550 | ||
| 1547 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 1551 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 1548 | |||
| 1549 | rb.Push(ResultSuccess); | 1552 | rb.Push(ResultSuccess); |
| 1550 | 1553 | ||
| 1551 | std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); | 1554 | std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); |
| @@ -1553,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { | |||
| 1553 | 1556 | ||
| 1554 | rb.PushIpcInterface<IStorage>(system, std::move(buffer)); | 1557 | rb.PushIpcInterface<IStorage>(system, std::move(buffer)); |
| 1555 | launch_popped_account_preselect = true; | 1558 | launch_popped_account_preselect = true; |
| 1556 | return; | 1559 | } else { |
| 1560 | LOG_ERROR(Service_AM, "Unknown launch parameter kind."); | ||
| 1561 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1562 | rb.Push(AM::ResultNoDataInChannel); | ||
| 1557 | } | 1563 | } |
| 1558 | |||
| 1559 | LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); | ||
| 1560 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1561 | rb.Push(AM::ResultNoDataInChannel); | ||
| 1562 | } | 1564 | } |
| 1563 | 1565 | ||
| 1564 | void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { | 1566 | void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { |
| @@ -1819,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) { | |||
| 1819 | rb.PushRaw(resp); | 1821 | rb.PushRaw(resp); |
| 1820 | } | 1822 | } |
| 1821 | 1823 | ||
| 1824 | void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) { | ||
| 1825 | LOG_WARNING(Service_AM, "(STUBBED) called"); | ||
| 1826 | |||
| 1827 | constexpr u64 size_max_normal = 0xFFFFFFF; | ||
| 1828 | constexpr u64 size_max_journal = 0xFFFFFFF; | ||
| 1829 | |||
| 1830 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 1831 | rb.Push(ResultSuccess); | ||
| 1832 | rb.Push(size_max_normal); | ||
| 1833 | rb.Push(size_max_journal); | ||
| 1834 | } | ||
| 1835 | |||
| 1822 | void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { | 1836 | void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { |
| 1823 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 1837 | LOG_WARNING(Service_AM, "(STUBBED) called"); |
| 1824 | 1838 | ||
| @@ -1850,14 +1864,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) { | |||
| 1850 | } | 1864 | } |
| 1851 | 1865 | ||
| 1852 | void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { | 1866 | void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { |
| 1853 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 1867 | LOG_DEBUG(Service_AM, "called"); |
| 1868 | |||
| 1869 | system.GetUserChannel().clear(); | ||
| 1854 | 1870 | ||
| 1855 | IPC::ResponseBuilder rb{ctx, 2}; | 1871 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1856 | rb.Push(ResultSuccess); | 1872 | rb.Push(ResultSuccess); |
| 1857 | } | 1873 | } |
| 1858 | 1874 | ||
| 1859 | void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { | 1875 | void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { |
| 1860 | LOG_WARNING(Service_AM, "(STUBBED) called"); | 1876 | LOG_DEBUG(Service_AM, "called"); |
| 1877 | |||
| 1878 | IPC::RequestParser rp{ctx}; | ||
| 1879 | const auto storage = rp.PopIpcInterface<IStorage>().lock(); | ||
| 1880 | if (storage) { | ||
| 1881 | system.GetUserChannel().push_back(storage->GetData()); | ||
| 1882 | } | ||
| 1861 | 1883 | ||
| 1862 | IPC::ResponseBuilder rb{ctx, 2}; | 1884 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1863 | rb.Push(ResultSuccess); | 1885 | rb.Push(ResultSuccess); |
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index d68998f04..f86841c60 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h | |||
| @@ -316,6 +316,7 @@ private: | |||
| 316 | void ExtendSaveData(HLERequestContext& ctx); | 316 | void ExtendSaveData(HLERequestContext& ctx); |
| 317 | void GetSaveDataSize(HLERequestContext& ctx); | 317 | void GetSaveDataSize(HLERequestContext& ctx); |
| 318 | void CreateCacheStorage(HLERequestContext& ctx); | 318 | void CreateCacheStorage(HLERequestContext& ctx); |
| 319 | void GetSaveDataSizeMax(HLERequestContext& ctx); | ||
| 319 | void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); | 320 | void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); |
| 320 | void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); | 321 | void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); |
| 321 | void BeginBlockingHomeButton(HLERequestContext& ctx); | 322 | void BeginBlockingHomeButton(HLERequestContext& ctx); |
| @@ -339,7 +340,6 @@ private: | |||
| 339 | 340 | ||
| 340 | KernelHelpers::ServiceContext service_context; | 341 | KernelHelpers::ServiceContext service_context; |
| 341 | 342 | ||
| 342 | bool launch_popped_application_specific = false; | ||
| 343 | bool launch_popped_account_preselect = false; | 343 | bool launch_popped_account_preselect = false; |
| 344 | s32 previous_program_index{-1}; | 344 | s32 previous_program_index{-1}; |
| 345 | Kernel::KEvent* gpu_error_detected_event; | 345 | Kernel::KEvent* gpu_error_detected_event; |
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp index d1f652c09..350a90818 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.cpp +++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp | |||
| @@ -85,15 +85,18 @@ void MiiEdit::Execute() { | |||
| 85 | break; | 85 | break; |
| 86 | case MiiEditAppletMode::CreateMii: | 86 | case MiiEditAppletMode::CreateMii: |
| 87 | case MiiEditAppletMode::EditMii: { | 87 | case MiiEditAppletMode::EditMii: { |
| 88 | Service::Mii::MiiManager mii_manager; | 88 | Mii::CharInfo char_info{}; |
| 89 | Mii::StoreData store_data{}; | ||
| 90 | store_data.BuildBase(Mii::Gender::Male); | ||
| 91 | char_info.SetFromStoreData(store_data); | ||
| 89 | 92 | ||
| 90 | const MiiEditCharInfo char_info{ | 93 | const MiiEditCharInfo edit_char_info{ |
| 91 | .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii | 94 | .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii |
| 92 | ? applet_input_v4.char_info.mii_info | 95 | ? applet_input_v4.char_info.mii_info |
| 93 | : mii_manager.BuildDefault(0)}, | 96 | : char_info}, |
| 94 | }; | 97 | }; |
| 95 | 98 | ||
| 96 | MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info); | 99 | MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); |
| 97 | break; | 100 | break; |
| 98 | } | 101 | } |
| 99 | default: | 102 | default: |
diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h index 4705d019f..f3d764073 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit_types.h +++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h | |||
| @@ -7,7 +7,8 @@ | |||
| 7 | 7 | ||
| 8 | #include "common/common_funcs.h" | 8 | #include "common/common_funcs.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "core/hle/service/mii/types.h" | 10 | #include "common/uuid.h" |
| 11 | #include "core/hle/service/mii/types/char_info.h" | ||
| 11 | 12 | ||
| 12 | namespace Service::AM::Applets { | 13 | namespace Service::AM::Applets { |
| 13 | 14 | ||
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp index 2accf7898..1c9a1dc29 100644 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp | |||
| @@ -139,7 +139,7 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, | |||
| 139 | const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), | 139 | const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), |
| 140 | system.GetContentProvider()}; | 140 | system.GetContentProvider()}; |
| 141 | 141 | ||
| 142 | return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); | 142 | return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type); |
| 143 | } | 143 | } |
| 144 | } | 144 | } |
| 145 | 145 | ||
diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp index 227fdd0cf..4f1aa5cc2 100644 --- a/src/core/hle/service/apm/apm_controller.cpp +++ b/src/core/hle/service/apm/apm_controller.cpp | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 9 | #include "common/settings.h" | 9 | #include "common/settings.h" |
| 10 | #include "common/settings_enums.h" | ||
| 10 | #include "core/core_timing.h" | 11 | #include "core/core_timing.h" |
| 11 | #include "core/hle/service/apm/apm_controller.h" | 12 | #include "core/hle/service/apm/apm_controller.h" |
| 12 | 13 | ||
| @@ -67,8 +68,7 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { | |||
| 67 | } | 68 | } |
| 68 | 69 | ||
| 69 | PerformanceMode Controller::GetCurrentPerformanceMode() const { | 70 | PerformanceMode Controller::GetCurrentPerformanceMode() const { |
| 70 | return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost | 71 | return Settings::IsDockedMode() ? PerformanceMode::Boost : PerformanceMode::Normal; |
| 71 | : PerformanceMode::Normal; | ||
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { | 74 | PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { |
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 526a39130..56fee4591 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp | |||
| @@ -220,7 +220,7 @@ AudInU::AudInU(Core::System& system_) | |||
| 220 | AudInU::~AudInU() = default; | 220 | AudInU::~AudInU() = default; |
| 221 | 221 | ||
| 222 | void AudInU::ListAudioIns(HLERequestContext& ctx) { | 222 | void AudInU::ListAudioIns(HLERequestContext& ctx) { |
| 223 | using namespace AudioCore::AudioRenderer; | 223 | using namespace AudioCore::Renderer; |
| 224 | 224 | ||
| 225 | LOG_DEBUG(Service_Audio, "called"); | 225 | LOG_DEBUG(Service_Audio, "called"); |
| 226 | 226 | ||
| @@ -240,7 +240,7 @@ void AudInU::ListAudioIns(HLERequestContext& ctx) { | |||
| 240 | } | 240 | } |
| 241 | 241 | ||
| 242 | void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) { | 242 | void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) { |
| 243 | using namespace AudioCore::AudioRenderer; | 243 | using namespace AudioCore::Renderer; |
| 244 | 244 | ||
| 245 | LOG_DEBUG(Service_Audio, "called"); | 245 | LOG_DEBUG(Service_Audio, "called"); |
| 246 | 246 | ||
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 23f84a29f..ca683d72c 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp | |||
| @@ -228,7 +228,7 @@ AudOutU::AudOutU(Core::System& system_) | |||
| 228 | AudOutU::~AudOutU() = default; | 228 | AudOutU::~AudOutU() = default; |
| 229 | 229 | ||
| 230 | void AudOutU::ListAudioOuts(HLERequestContext& ctx) { | 230 | void AudOutU::ListAudioOuts(HLERequestContext& ctx) { |
| 231 | using namespace AudioCore::AudioRenderer; | 231 | using namespace AudioCore::Renderer; |
| 232 | 232 | ||
| 233 | std::scoped_lock l{impl->mutex}; | 233 | std::scoped_lock l{impl->mutex}; |
| 234 | 234 | ||
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index b723b65c8..2f09cade5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp | |||
| @@ -26,7 +26,7 @@ | |||
| 26 | #include "core/hle/service/ipc_helpers.h" | 26 | #include "core/hle/service/ipc_helpers.h" |
| 27 | #include "core/memory.h" | 27 | #include "core/memory.h" |
| 28 | 28 | ||
| 29 | using namespace AudioCore::AudioRenderer; | 29 | using namespace AudioCore::Renderer; |
| 30 | 30 | ||
| 31 | namespace Service::Audio { | 31 | namespace Service::Audio { |
| 32 | 32 | ||
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index d8e9c8719..3d7993a16 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h | |||
| @@ -28,7 +28,7 @@ private: | |||
| 28 | void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx); | 28 | void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx); |
| 29 | 29 | ||
| 30 | KernelHelpers::ServiceContext service_context; | 30 | KernelHelpers::ServiceContext service_context; |
| 31 | std::unique_ptr<AudioCore::AudioRenderer::Manager> impl; | 31 | std::unique_ptr<AudioCore::Renderer::Manager> impl; |
| 32 | u32 num_audio_devices{0}; | 32 | u32 num_audio_devices{0}; |
| 33 | }; | 33 | }; |
| 34 | 34 | ||
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 3d3d3d97a..c41345f7e 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h | |||
| @@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513}; | |||
| 20 | constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; | 20 | constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; |
| 21 | constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; | 21 | constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; |
| 22 | 22 | ||
| 23 | constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7}; | ||
| 24 | constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8}; | ||
| 25 | constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6}; | ||
| 26 | constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5}; | ||
| 27 | constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17}; | ||
| 28 | constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; | ||
| 29 | constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; | ||
| 30 | constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; | ||
| 31 | constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; | ||
| 32 | constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; | ||
| 33 | constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002}; | ||
| 34 | |||
| 23 | } // namespace Service::Audio | 35 | } // namespace Service::Audio |
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index fa77007f3..6a7bf9416 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp | |||
| @@ -1,371 +1,506 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <chrono> | ||
| 5 | #include <cstring> | ||
| 6 | #include <memory> | 4 | #include <memory> |
| 7 | #include <vector> | 5 | #include <vector> |
| 8 | 6 | ||
| 9 | #include <opus.h> | 7 | #include "audio_core/opus/decoder.h" |
| 10 | #include <opus_multistream.h> | 8 | #include "audio_core/opus/parameters.h" |
| 11 | |||
| 12 | #include "common/assert.h" | 9 | #include "common/assert.h" |
| 13 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 14 | #include "common/scratch_buffer.h" | 11 | #include "common/scratch_buffer.h" |
| 12 | #include "core/core.h" | ||
| 15 | #include "core/hle/service/audio/hwopus.h" | 13 | #include "core/hle/service/audio/hwopus.h" |
| 16 | #include "core/hle/service/ipc_helpers.h" | 14 | #include "core/hle/service/ipc_helpers.h" |
| 17 | 15 | ||
| 18 | namespace Service::Audio { | 16 | namespace Service::Audio { |
| 19 | namespace { | 17 | using namespace AudioCore::OpusDecoder; |
| 20 | struct OpusDeleter { | 18 | |
| 21 | void operator()(OpusMSDecoder* ptr) const { | 19 | class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> { |
| 22 | opus_multistream_decoder_destroy(ptr); | 20 | public: |
| 21 | explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus) | ||
| 22 | : ServiceFramework{system_, "IHardwareOpusDecoder"}, | ||
| 23 | impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} { | ||
| 24 | // clang-format off | ||
| 25 | static const FunctionInfo functions[] = { | ||
| 26 | {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"}, | ||
| 27 | {1, &IHardwareOpusDecoder::SetContext, "SetContext"}, | ||
| 28 | {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"}, | ||
| 29 | {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"}, | ||
| 30 | {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, | ||
| 31 | {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"}, | ||
| 32 | {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"}, | ||
| 33 | {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, | ||
| 34 | {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 35 | {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, | ||
| 36 | }; | ||
| 37 | // clang-format on | ||
| 38 | |||
| 39 | RegisterHandlers(functions); | ||
| 23 | } | 40 | } |
| 24 | }; | ||
| 25 | 41 | ||
| 26 | using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; | 42 | Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, |
| 43 | u64 transfer_memory_size) { | ||
| 44 | return impl->Initialize(params, transfer_memory, transfer_memory_size); | ||
| 45 | } | ||
| 27 | 46 | ||
| 28 | struct OpusPacketHeader { | 47 | Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, |
| 29 | // Packet size in bytes. | 48 | u64 transfer_memory_size) { |
| 30 | u32_be size; | 49 | return impl->Initialize(params, transfer_memory, transfer_memory_size); |
| 31 | // Indicates the final range of the codec's entropy coder. | 50 | } |
| 32 | u32_be final_range; | ||
| 33 | }; | ||
| 34 | static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); | ||
| 35 | 51 | ||
| 36 | class OpusDecoderState { | 52 | private: |
| 37 | public: | 53 | void DecodeInterleavedOld(HLERequestContext& ctx) { |
| 38 | /// Describes extra behavior that may be asked of the decoding context. | 54 | IPC::RequestParser rp{ctx}; |
| 39 | enum class ExtraBehavior { | ||
| 40 | /// No extra behavior. | ||
| 41 | None, | ||
| 42 | 55 | ||
| 43 | /// Resets the decoder context back to a freshly initialized state. | 56 | auto input_data{ctx.ReadBuffer(0)}; |
| 44 | ResetContext, | 57 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 45 | }; | ||
| 46 | 58 | ||
| 47 | enum class PerfTime { | 59 | u32 size{}; |
| 48 | Disabled, | 60 | u32 sample_count{}; |
| 49 | Enabled, | 61 | auto result = |
| 50 | }; | 62 | impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false); |
| 63 | |||
| 64 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); | ||
| 65 | |||
| 66 | ctx.WriteBuffer(output_data); | ||
| 51 | 67 | ||
| 52 | explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) | 68 | IPC::ResponseBuilder rb{ctx, 4}; |
| 53 | : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} | 69 | rb.Push(result); |
| 54 | 70 | rb.Push(size); | |
| 55 | // Decodes interleaved Opus packets. Optionally allows reporting time taken to | 71 | rb.Push(sample_count); |
| 56 | // perform the decoding, as well as any relevant extra behavior. | ||
| 57 | void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time, | ||
| 58 | ExtraBehavior extra_behavior) { | ||
| 59 | if (perf_time == PerfTime::Disabled) { | ||
| 60 | DecodeInterleavedHelper(ctx, nullptr, extra_behavior); | ||
| 61 | } else { | ||
| 62 | u64 performance = 0; | ||
| 63 | DecodeInterleavedHelper(ctx, &performance, extra_behavior); | ||
| 64 | } | ||
| 65 | } | 72 | } |
| 66 | 73 | ||
| 67 | private: | 74 | void SetContext(HLERequestContext& ctx) { |
| 68 | void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, | 75 | IPC::RequestParser rp{ctx}; |
| 69 | ExtraBehavior extra_behavior) { | 76 | |
| 70 | u32 consumed = 0; | 77 | LOG_DEBUG(Service_Audio, "called"); |
| 71 | u32 sample_count = 0; | 78 | |
| 72 | samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>()); | 79 | auto input_data{ctx.ReadBuffer(0)}; |
| 73 | 80 | auto result = impl->SetContext(input_data); | |
| 74 | if (extra_behavior == ExtraBehavior::ResetContext) { | 81 | |
| 75 | ResetDecoderContext(); | 82 | IPC::ResponseBuilder rb{ctx, 2}; |
| 76 | } | 83 | rb.Push(result); |
| 77 | |||
| 78 | if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { | ||
| 79 | LOG_ERROR(Audio, "Failed to decode opus data"); | ||
| 80 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 81 | // TODO(ogniK): Use correct error code | ||
| 82 | rb.Push(ResultUnknown); | ||
| 83 | return; | ||
| 84 | } | ||
| 85 | |||
| 86 | const u32 param_size = performance != nullptr ? 6 : 4; | ||
| 87 | IPC::ResponseBuilder rb{ctx, param_size}; | ||
| 88 | rb.Push(ResultSuccess); | ||
| 89 | rb.Push<u32>(consumed); | ||
| 90 | rb.Push<u32>(sample_count); | ||
| 91 | if (performance) { | ||
| 92 | rb.Push<u64>(*performance); | ||
| 93 | } | ||
| 94 | ctx.WriteBuffer(samples); | ||
| 95 | } | 84 | } |
| 96 | 85 | ||
| 97 | bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, | 86 | void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) { |
| 98 | std::span<opus_int16> output, u64* out_performance_time) const { | 87 | IPC::RequestParser rp{ctx}; |
| 99 | const auto start_time = std::chrono::steady_clock::now(); | 88 | |
| 100 | const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); | 89 | auto input_data{ctx.ReadBuffer(0)}; |
| 101 | if (sizeof(OpusPacketHeader) > input.size()) { | 90 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 102 | LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", | 91 | |
| 103 | sizeof(OpusPacketHeader), input.size()); | 92 | u32 size{}; |
| 104 | return false; | 93 | u32 sample_count{}; |
| 105 | } | 94 | auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count, |
| 106 | 95 | input_data, output_data, false); | |
| 107 | OpusPacketHeader hdr{}; | 96 | |
| 108 | std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); | 97 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); |
| 109 | if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { | 98 | |
| 110 | LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", | 99 | ctx.WriteBuffer(output_data); |
| 111 | sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); | 100 | |
| 112 | return false; | 101 | IPC::ResponseBuilder rb{ctx, 4}; |
| 113 | } | 102 | rb.Push(result); |
| 114 | 103 | rb.Push(size); | |
| 115 | const auto frame = input.data() + sizeof(OpusPacketHeader); | 104 | rb.Push(sample_count); |
| 116 | const auto decoded_sample_count = opus_packet_get_nb_samples( | ||
| 117 | frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)), | ||
| 118 | static_cast<opus_int32>(sample_rate)); | ||
| 119 | if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { | ||
| 120 | LOG_ERROR( | ||
| 121 | Audio, | ||
| 122 | "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}", | ||
| 123 | decoded_sample_count * channel_count * sizeof(u16), raw_output_sz); | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | |||
| 127 | const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); | ||
| 128 | const auto out_sample_count = | ||
| 129 | opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); | ||
| 130 | if (out_sample_count < 0) { | ||
| 131 | LOG_ERROR(Audio, | ||
| 132 | "Incorrect sample count received from opus_decode, " | ||
| 133 | "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", | ||
| 134 | out_sample_count, frame_size, static_cast<u32>(hdr.size)); | ||
| 135 | return false; | ||
| 136 | } | ||
| 137 | |||
| 138 | const auto end_time = std::chrono::steady_clock::now() - start_time; | ||
| 139 | sample_count = out_sample_count; | ||
| 140 | consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size); | ||
| 141 | if (out_performance_time != nullptr) { | ||
| 142 | *out_performance_time = | ||
| 143 | std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); | ||
| 144 | } | ||
| 145 | |||
| 146 | return true; | ||
| 147 | } | 105 | } |
| 148 | 106 | ||
| 149 | void ResetDecoderContext() { | 107 | void SetContextForMultiStream(HLERequestContext& ctx) { |
| 150 | ASSERT(decoder != nullptr); | 108 | IPC::RequestParser rp{ctx}; |
| 109 | |||
| 110 | LOG_DEBUG(Service_Audio, "called"); | ||
| 111 | |||
| 112 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 113 | auto result = impl->SetContext(input_data); | ||
| 151 | 114 | ||
| 152 | opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); | 115 | IPC::ResponseBuilder rb{ctx, 2}; |
| 116 | rb.Push(result); | ||
| 153 | } | 117 | } |
| 154 | 118 | ||
| 155 | OpusDecoderPtr decoder; | 119 | void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { |
| 156 | u32 sample_rate; | 120 | IPC::RequestParser rp{ctx}; |
| 157 | u32 channel_count; | ||
| 158 | Common::ScratchBuffer<opus_int16> samples; | ||
| 159 | }; | ||
| 160 | 121 | ||
| 161 | class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { | 122 | auto input_data{ctx.ReadBuffer(0)}; |
| 162 | public: | 123 | output_data.resize_destructive(ctx.GetWriteBufferSize()); |
| 163 | explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_) | ||
| 164 | : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ | ||
| 165 | std::move(decoder_state_)} { | ||
| 166 | // clang-format off | ||
| 167 | static const FunctionInfo functions[] = { | ||
| 168 | {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, | ||
| 169 | {1, nullptr, "SetContext"}, | ||
| 170 | {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, | ||
| 171 | {3, nullptr, "SetContextForMultiStream"}, | ||
| 172 | {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, | ||
| 173 | {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, | ||
| 174 | {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"}, | ||
| 175 | {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, | ||
| 176 | {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, | ||
| 177 | {9, nullptr, "DecodeInterleavedForMultiStream"}, | ||
| 178 | }; | ||
| 179 | // clang-format on | ||
| 180 | 124 | ||
| 181 | RegisterHandlers(functions); | 125 | u32 size{}; |
| 126 | u32 sample_count{}; | ||
| 127 | u64 time_taken{}; | ||
| 128 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 129 | output_data, false); | ||
| 130 | |||
| 131 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, | ||
| 132 | sample_count, time_taken); | ||
| 133 | |||
| 134 | ctx.WriteBuffer(output_data); | ||
| 135 | |||
| 136 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 137 | rb.Push(result); | ||
| 138 | rb.Push(size); | ||
| 139 | rb.Push(sample_count); | ||
| 140 | rb.Push(time_taken); | ||
| 182 | } | 141 | } |
| 183 | 142 | ||
| 184 | private: | 143 | void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) { |
| 185 | void DecodeInterleavedOld(HLERequestContext& ctx) { | 144 | IPC::RequestParser rp{ctx}; |
| 186 | LOG_DEBUG(Audio, "called"); | 145 | |
| 146 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 147 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 148 | |||
| 149 | u32 size{}; | ||
| 150 | u32 sample_count{}; | ||
| 151 | u64 time_taken{}; | ||
| 152 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 153 | input_data, output_data, false); | ||
| 187 | 154 | ||
| 188 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, | 155 | LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, |
| 189 | OpusDecoderState::ExtraBehavior::None); | 156 | sample_count, time_taken); |
| 157 | |||
| 158 | ctx.WriteBuffer(output_data); | ||
| 159 | |||
| 160 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 161 | rb.Push(result); | ||
| 162 | rb.Push(size); | ||
| 163 | rb.Push(sample_count); | ||
| 164 | rb.Push(time_taken); | ||
| 190 | } | 165 | } |
| 191 | 166 | ||
| 192 | void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { | 167 | void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) { |
| 193 | LOG_DEBUG(Audio, "called"); | 168 | IPC::RequestParser rp{ctx}; |
| 169 | |||
| 170 | auto reset{rp.Pop<bool>()}; | ||
| 171 | |||
| 172 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 173 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 174 | |||
| 175 | u32 size{}; | ||
| 176 | u32 sample_count{}; | ||
| 177 | u64 time_taken{}; | ||
| 178 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 179 | output_data, reset); | ||
| 180 | |||
| 181 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 182 | reset, size, sample_count, time_taken); | ||
| 183 | |||
| 184 | ctx.WriteBuffer(output_data); | ||
| 185 | |||
| 186 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 187 | rb.Push(result); | ||
| 188 | rb.Push(size); | ||
| 189 | rb.Push(sample_count); | ||
| 190 | rb.Push(time_taken); | ||
| 191 | } | ||
| 192 | |||
| 193 | void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) { | ||
| 194 | IPC::RequestParser rp{ctx}; | ||
| 195 | |||
| 196 | auto reset{rp.Pop<bool>()}; | ||
| 197 | |||
| 198 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 199 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 200 | |||
| 201 | u32 size{}; | ||
| 202 | u32 sample_count{}; | ||
| 203 | u64 time_taken{}; | ||
| 204 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 205 | input_data, output_data, reset); | ||
| 206 | |||
| 207 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 208 | reset, size, sample_count, time_taken); | ||
| 194 | 209 | ||
| 195 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, | 210 | ctx.WriteBuffer(output_data); |
| 196 | OpusDecoderState::ExtraBehavior::None); | 211 | |
| 212 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 213 | rb.Push(result); | ||
| 214 | rb.Push(size); | ||
| 215 | rb.Push(sample_count); | ||
| 216 | rb.Push(time_taken); | ||
| 197 | } | 217 | } |
| 198 | 218 | ||
| 199 | void DecodeInterleaved(HLERequestContext& ctx) { | 219 | void DecodeInterleaved(HLERequestContext& ctx) { |
| 200 | LOG_DEBUG(Audio, "called"); | 220 | IPC::RequestParser rp{ctx}; |
| 221 | |||
| 222 | auto reset{rp.Pop<bool>()}; | ||
| 223 | |||
| 224 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 225 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 226 | |||
| 227 | u32 size{}; | ||
| 228 | u32 sample_count{}; | ||
| 229 | u64 time_taken{}; | ||
| 230 | auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, | ||
| 231 | output_data, reset); | ||
| 232 | |||
| 233 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 234 | reset, size, sample_count, time_taken); | ||
| 235 | |||
| 236 | ctx.WriteBuffer(output_data); | ||
| 237 | |||
| 238 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 239 | rb.Push(result); | ||
| 240 | rb.Push(size); | ||
| 241 | rb.Push(sample_count); | ||
| 242 | rb.Push(time_taken); | ||
| 243 | } | ||
| 201 | 244 | ||
| 245 | void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { | ||
| 202 | IPC::RequestParser rp{ctx}; | 246 | IPC::RequestParser rp{ctx}; |
| 203 | const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext | ||
| 204 | : OpusDecoderState::ExtraBehavior::None; | ||
| 205 | 247 | ||
| 206 | decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); | 248 | auto reset{rp.Pop<bool>()}; |
| 249 | |||
| 250 | auto input_data{ctx.ReadBuffer(0)}; | ||
| 251 | output_data.resize_destructive(ctx.GetWriteBufferSize()); | ||
| 252 | |||
| 253 | u32 size{}; | ||
| 254 | u32 sample_count{}; | ||
| 255 | u64 time_taken{}; | ||
| 256 | auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, | ||
| 257 | input_data, output_data, reset); | ||
| 258 | |||
| 259 | LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", | ||
| 260 | reset, size, sample_count, time_taken); | ||
| 261 | |||
| 262 | ctx.WriteBuffer(output_data); | ||
| 263 | |||
| 264 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 265 | rb.Push(result); | ||
| 266 | rb.Push(size); | ||
| 267 | rb.Push(sample_count); | ||
| 268 | rb.Push(time_taken); | ||
| 207 | } | 269 | } |
| 208 | 270 | ||
| 209 | OpusDecoderState decoder_state; | 271 | std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl; |
| 272 | Common::ScratchBuffer<u8> output_data; | ||
| 210 | }; | 273 | }; |
| 211 | 274 | ||
| 212 | std::size_t WorkerBufferSize(u32 channel_count) { | 275 | void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { |
| 213 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 276 | IPC::RequestParser rp{ctx}; |
| 214 | constexpr int num_streams = 1; | ||
| 215 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | ||
| 216 | return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); | ||
| 217 | } | ||
| 218 | 277 | ||
| 219 | // Creates the mapping table that maps the input channels to the particular | 278 | auto params = rp.PopRaw<OpusParameters>(); |
| 220 | // output channels. In the stereo case, we map the left and right input channels | 279 | auto transfer_memory_size{rp.Pop<u32>()}; |
| 221 | // to the left and right output channels respectively. | 280 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; |
| 222 | // | 281 | auto transfer_memory{ |
| 223 | // However, in the monophonic case, we only map the one available channel | 282 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( |
| 224 | // to the sole output channel. We specify 255 for the would-be right channel | 283 | transfer_memory_handle)}; |
| 225 | // as this is a special value defined by Opus to indicate to the decoder to | ||
| 226 | // ignore that channel. | ||
| 227 | std::array<u8, 2> CreateMappingTable(u32 channel_count) { | ||
| 228 | if (channel_count == 2) { | ||
| 229 | return {{0, 1}}; | ||
| 230 | } | ||
| 231 | 284 | ||
| 232 | return {{0, 255}}; | 285 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", |
| 286 | params.sample_rate, params.channel_count, transfer_memory_size); | ||
| 287 | |||
| 288 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; | ||
| 289 | |||
| 290 | OpusParametersEx ex{ | ||
| 291 | .sample_rate = params.sample_rate, | ||
| 292 | .channel_count = params.channel_count, | ||
| 293 | .use_large_frame_size = false, | ||
| 294 | }; | ||
| 295 | auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); | ||
| 296 | |||
| 297 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||
| 298 | rb.Push(result); | ||
| 299 | rb.PushIpcInterface(decoder); | ||
| 233 | } | 300 | } |
| 234 | } // Anonymous namespace | ||
| 235 | 301 | ||
| 236 | void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { | 302 | void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { |
| 237 | IPC::RequestParser rp{ctx}; | 303 | IPC::RequestParser rp{ctx}; |
| 238 | const auto sample_rate = rp.Pop<u32>(); | 304 | auto params = rp.PopRaw<OpusParameters>(); |
| 239 | const auto channel_count = rp.Pop<u32>(); | ||
| 240 | 305 | ||
| 241 | LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); | 306 | u64 size{}; |
| 307 | auto result = impl.GetWorkBufferSize(params, size); | ||
| 242 | 308 | ||
| 243 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 309 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}", |
| 244 | sample_rate == 12000 || sample_rate == 8000, | 310 | params.sample_rate, params.channel_count, size); |
| 245 | "Invalid sample rate"); | 311 | |
| 246 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 312 | IPC::ResponseBuilder rb{ctx, 4}; |
| 313 | rb.Push(result); | ||
| 314 | rb.Push(size); | ||
| 315 | } | ||
| 316 | |||
| 317 | void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) { | ||
| 318 | IPC::RequestParser rp{ctx}; | ||
| 247 | 319 | ||
| 248 | const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); | 320 | auto input{ctx.ReadBuffer()}; |
| 249 | LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); | 321 | OpusMultiStreamParameters params; |
| 322 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); | ||
| 323 | |||
| 324 | auto transfer_memory_size{rp.Pop<u32>()}; | ||
| 325 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; | ||
| 326 | auto transfer_memory{ | ||
| 327 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( | ||
| 328 | transfer_memory_handle)}; | ||
| 329 | |||
| 330 | LOG_DEBUG(Service_Audio, | ||
| 331 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " | ||
| 332 | "transfer_memory_size 0x{:X}", | ||
| 333 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 334 | params.stereo_stream_count, transfer_memory_size); | ||
| 335 | |||
| 336 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; | ||
| 337 | |||
| 338 | OpusMultiStreamParametersEx ex{ | ||
| 339 | .sample_rate = params.sample_rate, | ||
| 340 | .channel_count = params.channel_count, | ||
| 341 | .total_stream_count = params.total_stream_count, | ||
| 342 | .stereo_stream_count = params.stereo_stream_count, | ||
| 343 | .use_large_frame_size = false, | ||
| 344 | .mappings{}, | ||
| 345 | }; | ||
| 346 | std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings)); | ||
| 347 | auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); | ||
| 250 | 348 | ||
| 251 | IPC::ResponseBuilder rb{ctx, 3}; | 349 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 252 | rb.Push(ResultSuccess); | 350 | rb.Push(result); |
| 253 | rb.Push<u32>(worker_buffer_sz); | 351 | rb.PushIpcInterface(decoder); |
| 254 | } | 352 | } |
| 255 | 353 | ||
| 256 | void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { | 354 | void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) { |
| 257 | GetWorkBufferSize(ctx); | 355 | IPC::RequestParser rp{ctx}; |
| 356 | |||
| 357 | auto input{ctx.ReadBuffer()}; | ||
| 358 | OpusMultiStreamParameters params; | ||
| 359 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); | ||
| 360 | |||
| 361 | u64 size{}; | ||
| 362 | auto result = impl.GetWorkBufferSizeForMultiStream(params, size); | ||
| 363 | |||
| 364 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 365 | |||
| 366 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 367 | rb.Push(result); | ||
| 368 | rb.Push(size); | ||
| 258 | } | 369 | } |
| 259 | 370 | ||
| 260 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { | 371 | void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { |
| 261 | OpusMultiStreamParametersEx param; | 372 | IPC::RequestParser rp{ctx}; |
| 262 | std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); | ||
| 263 | 373 | ||
| 264 | const auto sample_rate = param.sample_rate; | 374 | auto params = rp.PopRaw<OpusParametersEx>(); |
| 265 | const auto channel_count = param.channel_count; | 375 | auto transfer_memory_size{rp.Pop<u32>()}; |
| 266 | const auto number_streams = param.number_streams; | 376 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; |
| 267 | const auto number_stereo_streams = param.number_stereo_streams; | 377 | auto transfer_memory{ |
| 378 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( | ||
| 379 | transfer_memory_handle)}; | ||
| 268 | 380 | ||
| 269 | LOG_DEBUG( | 381 | LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", |
| 270 | Audio, | 382 | params.sample_rate, params.channel_count, transfer_memory_size); |
| 271 | "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", | ||
| 272 | sample_rate, channel_count, number_streams, number_stereo_streams); | ||
| 273 | 383 | ||
| 274 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 384 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; |
| 275 | sample_rate == 12000 || sample_rate == 8000, | ||
| 276 | "Invalid sample rate"); | ||
| 277 | 385 | ||
| 278 | const u32 worker_buffer_sz = | 386 | auto result = |
| 279 | static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); | 387 | decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); |
| 280 | 388 | ||
| 281 | IPC::ResponseBuilder rb{ctx, 3}; | 389 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 282 | rb.Push(ResultSuccess); | 390 | rb.Push(result); |
| 283 | rb.Push<u32>(worker_buffer_sz); | 391 | rb.PushIpcInterface(decoder); |
| 284 | } | 392 | } |
| 285 | 393 | ||
| 286 | void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { | 394 | void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { |
| 287 | IPC::RequestParser rp{ctx}; | 395 | IPC::RequestParser rp{ctx}; |
| 288 | const auto sample_rate = rp.Pop<u32>(); | 396 | auto params = rp.PopRaw<OpusParametersEx>(); |
| 289 | const auto channel_count = rp.Pop<u32>(); | ||
| 290 | const auto buffer_sz = rp.Pop<u32>(); | ||
| 291 | |||
| 292 | LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, | ||
| 293 | channel_count, buffer_sz); | ||
| 294 | |||
| 295 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | ||
| 296 | sample_rate == 12000 || sample_rate == 8000, | ||
| 297 | "Invalid sample rate"); | ||
| 298 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | ||
| 299 | |||
| 300 | const std::size_t worker_sz = WorkerBufferSize(channel_count); | ||
| 301 | ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); | ||
| 302 | |||
| 303 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | ||
| 304 | const auto mapping_table = CreateMappingTable(channel_count); | ||
| 305 | |||
| 306 | int error = 0; | ||
| 307 | OpusDecoderPtr decoder{ | ||
| 308 | opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, | ||
| 309 | num_stereo_streams, mapping_table.data(), &error)}; | ||
| 310 | if (error != OPUS_OK || decoder == nullptr) { | ||
| 311 | LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); | ||
| 312 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 313 | // TODO(ogniK): Use correct error code | ||
| 314 | rb.Push(ResultUnknown); | ||
| 315 | return; | ||
| 316 | } | ||
| 317 | 397 | ||
| 318 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 398 | u64 size{}; |
| 319 | rb.Push(ResultSuccess); | 399 | auto result = impl.GetWorkBufferSizeEx(params, size); |
| 320 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( | 400 | |
| 321 | system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); | 401 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); |
| 402 | |||
| 403 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 404 | rb.Push(result); | ||
| 405 | rb.Push(size); | ||
| 322 | } | 406 | } |
| 323 | 407 | ||
| 324 | void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { | 408 | void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { |
| 325 | IPC::RequestParser rp{ctx}; | 409 | IPC::RequestParser rp{ctx}; |
| 326 | const auto sample_rate = rp.Pop<u32>(); | ||
| 327 | const auto channel_count = rp.Pop<u32>(); | ||
| 328 | 410 | ||
| 329 | LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); | 411 | auto input{ctx.ReadBuffer()}; |
| 412 | OpusMultiStreamParametersEx params; | ||
| 413 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); | ||
| 330 | 414 | ||
| 331 | ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || | 415 | auto transfer_memory_size{rp.Pop<u32>()}; |
| 332 | sample_rate == 12000 || sample_rate == 8000, | 416 | auto transfer_memory_handle{ctx.GetCopyHandle(0)}; |
| 333 | "Invalid sample rate"); | 417 | auto transfer_memory{ |
| 334 | ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); | 418 | system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( |
| 419 | transfer_memory_handle)}; | ||
| 335 | 420 | ||
| 336 | const int num_stereo_streams = channel_count == 2 ? 1 : 0; | 421 | LOG_DEBUG(Service_Audio, |
| 337 | const auto mapping_table = CreateMappingTable(channel_count); | 422 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " |
| 423 | "use_large_frame_size {}" | ||
| 424 | "transfer_memory_size 0x{:X}", | ||
| 425 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 426 | params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size); | ||
| 338 | 427 | ||
| 339 | int error = 0; | 428 | auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; |
| 340 | OpusDecoderPtr decoder{ | 429 | |
| 341 | opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, | 430 | auto result = |
| 342 | num_stereo_streams, mapping_table.data(), &error)}; | 431 | decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); |
| 343 | if (error != OPUS_OK || decoder == nullptr) { | ||
| 344 | LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); | ||
| 345 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 346 | // TODO(ogniK): Use correct error code | ||
| 347 | rb.Push(ResultUnknown); | ||
| 348 | return; | ||
| 349 | } | ||
| 350 | 432 | ||
| 351 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 433 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 352 | rb.Push(ResultSuccess); | 434 | rb.Push(result); |
| 353 | rb.PushIpcInterface<IHardwareOpusDecoderManager>( | 435 | rb.PushIpcInterface(decoder); |
| 354 | system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); | 436 | } |
| 437 | |||
| 438 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { | ||
| 439 | IPC::RequestParser rp{ctx}; | ||
| 440 | |||
| 441 | auto input{ctx.ReadBuffer()}; | ||
| 442 | OpusMultiStreamParametersEx params; | ||
| 443 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); | ||
| 444 | |||
| 445 | u64 size{}; | ||
| 446 | auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size); | ||
| 447 | |||
| 448 | LOG_DEBUG(Service_Audio, | ||
| 449 | "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " | ||
| 450 | "use_large_frame_size {} -- returned size 0x{:X}", | ||
| 451 | params.sample_rate, params.channel_count, params.total_stream_count, | ||
| 452 | params.stereo_stream_count, params.use_large_frame_size, size); | ||
| 453 | |||
| 454 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 455 | rb.Push(result); | ||
| 456 | rb.Push(size); | ||
| 457 | } | ||
| 458 | |||
| 459 | void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { | ||
| 460 | IPC::RequestParser rp{ctx}; | ||
| 461 | auto params = rp.PopRaw<OpusParametersEx>(); | ||
| 462 | |||
| 463 | u64 size{}; | ||
| 464 | auto result = impl.GetWorkBufferSizeExEx(params, size); | ||
| 465 | |||
| 466 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 467 | |||
| 468 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 469 | rb.Push(result); | ||
| 470 | rb.Push(size); | ||
| 471 | } | ||
| 472 | |||
| 473 | void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) { | ||
| 474 | IPC::RequestParser rp{ctx}; | ||
| 475 | |||
| 476 | auto input{ctx.ReadBuffer()}; | ||
| 477 | OpusMultiStreamParametersEx params; | ||
| 478 | std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); | ||
| 479 | |||
| 480 | u64 size{}; | ||
| 481 | auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size); | ||
| 482 | |||
| 483 | LOG_DEBUG(Service_Audio, "size 0x{:X}", size); | ||
| 484 | |||
| 485 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 486 | rb.Push(result); | ||
| 487 | rb.Push(size); | ||
| 355 | } | 488 | } |
| 356 | 489 | ||
| 357 | HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { | 490 | HwOpus::HwOpus(Core::System& system_) |
| 491 | : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} { | ||
| 358 | static const FunctionInfo functions[] = { | 492 | static const FunctionInfo functions[] = { |
| 359 | {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, | 493 | {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, |
| 360 | {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, | 494 | {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, |
| 361 | {2, nullptr, "OpenOpusDecoderForMultiStream"}, | 495 | {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"}, |
| 362 | {3, nullptr, "GetWorkBufferSizeForMultiStream"}, | 496 | {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"}, |
| 363 | {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, | 497 | {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, |
| 364 | {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, | 498 | {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, |
| 365 | {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"}, | 499 | {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, |
| 500 | "OpenHardwareOpusDecoderForMultiStreamEx"}, | ||
| 366 | {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, | 501 | {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, |
| 367 | {8, nullptr, "GetWorkBufferSizeExEx"}, | 502 | {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, |
| 368 | {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, | 503 | {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"}, |
| 369 | }; | 504 | }; |
| 370 | RegisterHandlers(functions); | 505 | RegisterHandlers(functions); |
| 371 | } | 506 | } |
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index ece65c02c..d3960065e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include "audio_core/opus/decoder_manager.h" | ||
| 6 | #include "core/hle/service/service.h" | 7 | #include "core/hle/service/service.h" |
| 7 | 8 | ||
| 8 | namespace Core { | 9 | namespace Core { |
| @@ -11,16 +12,6 @@ class System; | |||
| 11 | 12 | ||
| 12 | namespace Service::Audio { | 13 | namespace Service::Audio { |
| 13 | 14 | ||
| 14 | struct OpusMultiStreamParametersEx { | ||
| 15 | u32 sample_rate; | ||
| 16 | u32 channel_count; | ||
| 17 | u32 number_streams; | ||
| 18 | u32 number_stereo_streams; | ||
| 19 | u32 use_large_frame_size; | ||
| 20 | u32 padding; | ||
| 21 | std::array<u32, 64> channel_mappings; | ||
| 22 | }; | ||
| 23 | |||
| 24 | class HwOpus final : public ServiceFramework<HwOpus> { | 15 | class HwOpus final : public ServiceFramework<HwOpus> { |
| 25 | public: | 16 | public: |
| 26 | explicit HwOpus(Core::System& system_); | 17 | explicit HwOpus(Core::System& system_); |
| @@ -28,10 +19,18 @@ public: | |||
| 28 | 19 | ||
| 29 | private: | 20 | private: |
| 30 | void OpenHardwareOpusDecoder(HLERequestContext& ctx); | 21 | void OpenHardwareOpusDecoder(HLERequestContext& ctx); |
| 31 | void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); | ||
| 32 | void GetWorkBufferSize(HLERequestContext& ctx); | 22 | void GetWorkBufferSize(HLERequestContext& ctx); |
| 23 | void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx); | ||
| 24 | void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx); | ||
| 25 | void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); | ||
| 33 | void GetWorkBufferSizeEx(HLERequestContext& ctx); | 26 | void GetWorkBufferSizeEx(HLERequestContext& ctx); |
| 27 | void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); | ||
| 34 | void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); | 28 | void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); |
| 29 | void GetWorkBufferSizeExEx(HLERequestContext& ctx); | ||
| 30 | void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx); | ||
| 31 | |||
| 32 | Core::System& system; | ||
| 33 | AudioCore::OpusDecoder::OpusDecoderManager impl; | ||
| 35 | }; | 34 | }; |
| 36 | 35 | ||
| 37 | } // namespace Service::Audio | 36 | } // namespace Service::Audio |
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/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index ac465d5a9..508db7360 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include <utility> | 4 | #include <utility> |
| 5 | 5 | ||
| 6 | #include "common/assert.h" | 6 | #include "common/assert.h" |
| 7 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | 8 | #include "common/fs/path_util.h" |
| 8 | #include "common/settings.h" | 9 | #include "common/settings.h" |
| 9 | #include "core/core.h" | 10 | #include "core/core.h" |
| @@ -154,10 +155,18 @@ Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, | |||
| 154 | std::string src_path(Common::FS::SanitizePath(src_path_)); | 155 | std::string src_path(Common::FS::SanitizePath(src_path_)); |
| 155 | std::string dest_path(Common::FS::SanitizePath(dest_path_)); | 156 | std::string dest_path(Common::FS::SanitizePath(dest_path_)); |
| 156 | auto src = backing->GetFileRelative(src_path); | 157 | auto src = backing->GetFileRelative(src_path); |
| 158 | auto dst = backing->GetFileRelative(dest_path); | ||
| 157 | if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { | 159 | if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { |
| 158 | // Use more-optimized vfs implementation rename. | 160 | // Use more-optimized vfs implementation rename. |
| 159 | if (src == nullptr) | 161 | if (src == nullptr) { |
| 160 | return FileSys::ERROR_PATH_NOT_FOUND; | 162 | return FileSys::ERROR_PATH_NOT_FOUND; |
| 163 | } | ||
| 164 | |||
| 165 | if (dst && Common::FS::Exists(dst->GetFullPath())) { | ||
| 166 | LOG_ERROR(Service_FS, "File at new_path={} already exists", dst->GetFullPath()); | ||
| 167 | return FileSys::ERROR_PATH_ALREADY_EXISTS; | ||
| 168 | } | ||
| 169 | |||
| 161 | if (!src->Rename(Common::FS::GetFilename(dest_path))) { | 170 | if (!src->Rename(Common::FS::GetFilename(dest_path))) { |
| 162 | // TODO(DarkLordZach): Find a better error code for this | 171 | // TODO(DarkLordZach): Find a better error code for this |
| 163 | return ResultUnknown; | 172 | return ResultUnknown; |
| @@ -373,6 +382,11 @@ FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::Stor | |||
| 373 | return romfs_factory->Open(title_id, storage_id, type); | 382 | return romfs_factory->Open(title_id, storage_id, type); |
| 374 | } | 383 | } |
| 375 | 384 | ||
| 385 | std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca( | ||
| 386 | u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { | ||
| 387 | return romfs_factory->GetEntry(title_id, storage_id, type); | ||
| 388 | } | ||
| 389 | |||
| 376 | Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, | 390 | Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, |
| 377 | FileSys::SaveDataSpaceId space, | 391 | FileSys::SaveDataSpaceId space, |
| 378 | const FileSys::SaveDataAttribute& save_struct) const { | 392 | const FileSys::SaveDataAttribute& save_struct) const { |
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index fd991f976..e7e7c4c28 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h | |||
| @@ -15,6 +15,7 @@ class System; | |||
| 15 | 15 | ||
| 16 | namespace FileSys { | 16 | namespace FileSys { |
| 17 | class BISFactory; | 17 | class BISFactory; |
| 18 | class NCA; | ||
| 18 | class RegisteredCache; | 19 | class RegisteredCache; |
| 19 | class RegisteredCacheUnion; | 20 | class RegisteredCacheUnion; |
| 20 | class PlaceholderCache; | 21 | class PlaceholderCache; |
| @@ -70,6 +71,8 @@ public: | |||
| 70 | FileSys::ContentRecordType type) const; | 71 | FileSys::ContentRecordType type) const; |
| 71 | FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, | 72 | FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, |
| 72 | FileSys::ContentRecordType type) const; | 73 | FileSys::ContentRecordType type) const; |
| 74 | std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id, | ||
| 75 | FileSys::ContentRecordType type) const; | ||
| 73 | 76 | ||
| 74 | Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, | 77 | Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, |
| 75 | const FileSys::SaveDataAttribute& save_struct) const; | 78 | const FileSys::SaveDataAttribute& save_struct) const; |
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 423a814cb..6e4d26b1e 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp | |||
| @@ -1029,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { | |||
| 1029 | 1029 | ||
| 1030 | const FileSys::PatchManager pm{title_id, fsc, content_provider}; | 1030 | const FileSys::PatchManager pm{title_id, fsc, content_provider}; |
| 1031 | 1031 | ||
| 1032 | auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data); | ||
| 1032 | auto storage = std::make_shared<IStorage>( | 1033 | auto storage = std::make_shared<IStorage>( |
| 1033 | system, pm.PatchRomFS(std::move(data), 0, FileSys::ContentRecordType::Data)); | 1034 | system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data)); |
| 1034 | 1035 | ||
| 1035 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 1036 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 1036 | rb.Push(ResultSuccess); | 1037 | rb.Push(ResultSuccess); |
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 03432f7cb..63eecd42b 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp | |||
| @@ -331,7 +331,7 @@ Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() | |||
| 331 | }; | 331 | }; |
| 332 | 332 | ||
| 333 | // Hack: There is no touch in docked but games still allow it | 333 | // Hack: There is no touch in docked but games still allow it |
| 334 | if (Settings::values.use_docked_mode.GetValue()) { | 334 | if (Settings::IsDockedMode()) { |
| 335 | gesture.points[id] = { | 335 | gesture.points[id] = { |
| 336 | .x = static_cast<s32>(active_x * Layout::ScreenDocked::Width), | 336 | .x = static_cast<s32>(active_x * Layout::ScreenDocked::Width), |
| 337 | .y = static_cast<s32>(active_y * Layout::ScreenDocked::Height), | 337 | .y = static_cast<s32>(active_y * Layout::ScreenDocked::Height), |
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 28818c813..146bb486d 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp | |||
| @@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 193 | shared_memory->system_properties.use_minus.Assign(1); | 193 | shared_memory->system_properties.use_minus.Assign(1); |
| 194 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 194 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 195 | battery_level.dual.is_charging); | 195 | battery_level.dual.is_charging); |
| 196 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; | 196 | shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController; |
| 197 | shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); | 197 | shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); |
| 198 | break; | 198 | break; |
| 199 | case Core::HID::NpadStyleIndex::Handheld: | 199 | case Core::HID::NpadStyleIndex::Handheld: |
| @@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 216 | shared_memory->system_properties.is_charging_joy_right.Assign( | 216 | shared_memory->system_properties.is_charging_joy_right.Assign( |
| 217 | battery_level.right.is_charging); | 217 | battery_level.right.is_charging); |
| 218 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; | 218 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; |
| 219 | shared_memory->applet_nfc_xcd.applet_footer.type = | 219 | shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; |
| 220 | AppletFooterUiType::HandheldJoyConLeftJoyConRight; | ||
| 221 | shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); | 220 | shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); |
| 222 | break; | 221 | break; |
| 223 | case Core::HID::NpadStyleIndex::JoyconDual: | 222 | case Core::HID::NpadStyleIndex::JoyconDual: |
| @@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 247 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; | 246 | shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; |
| 248 | 247 | ||
| 249 | if (controller.is_dual_left_connected && controller.is_dual_right_connected) { | 248 | if (controller.is_dual_left_connected && controller.is_dual_right_connected) { |
| 250 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; | 249 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDual; |
| 251 | shared_memory->fullkey_color.fullkey = body_colors.left; | 250 | shared_memory->fullkey_color.fullkey = body_colors.left; |
| 252 | shared_memory->battery_level_dual = battery_level.left.battery_level; | 251 | shared_memory->battery_level_dual = battery_level.left.battery_level; |
| 253 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 252 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 254 | battery_level.left.is_charging); | 253 | battery_level.left.is_charging); |
| 255 | } else if (controller.is_dual_left_connected) { | 254 | } else if (controller.is_dual_left_connected) { |
| 256 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; | 255 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly; |
| 257 | shared_memory->fullkey_color.fullkey = body_colors.left; | 256 | shared_memory->fullkey_color.fullkey = body_colors.left; |
| 258 | shared_memory->battery_level_dual = battery_level.left.battery_level; | 257 | shared_memory->battery_level_dual = battery_level.left.battery_level; |
| 259 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 258 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| 260 | battery_level.left.is_charging); | 259 | battery_level.left.is_charging); |
| 261 | } else { | 260 | } else { |
| 262 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; | 261 | shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly; |
| 263 | shared_memory->fullkey_color.fullkey = body_colors.right; | 262 | shared_memory->fullkey_color.fullkey = body_colors.right; |
| 264 | shared_memory->battery_level_dual = battery_level.right.battery_level; | 263 | shared_memory->battery_level_dual = battery_level.right.battery_level; |
| 265 | shared_memory->system_properties.is_charging_joy_dual.Assign( | 264 | shared_memory->system_properties.is_charging_joy_dual.Assign( |
| @@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 278 | shared_memory->system_properties.use_minus.Assign(1); | 277 | shared_memory->system_properties.use_minus.Assign(1); |
| 279 | shared_memory->system_properties.is_charging_joy_left.Assign( | 278 | shared_memory->system_properties.is_charging_joy_left.Assign( |
| 280 | battery_level.left.is_charging); | 279 | battery_level.left.is_charging); |
| 281 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; | 280 | shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal; |
| 282 | shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); | 281 | shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); |
| 283 | break; | 282 | break; |
| 284 | case Core::HID::NpadStyleIndex::JoyconRight: | 283 | case Core::HID::NpadStyleIndex::JoyconRight: |
| @@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 293 | shared_memory->system_properties.use_plus.Assign(1); | 292 | shared_memory->system_properties.use_plus.Assign(1); |
| 294 | shared_memory->system_properties.is_charging_joy_right.Assign( | 293 | shared_memory->system_properties.is_charging_joy_right.Assign( |
| 295 | battery_level.right.is_charging); | 294 | battery_level.right.is_charging); |
| 296 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; | 295 | shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal; |
| 297 | shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); | 296 | shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); |
| 298 | break; | 297 | break; |
| 299 | case Core::HID::NpadStyleIndex::GameCube: | 298 | case Core::HID::NpadStyleIndex::GameCube: |
| @@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { | |||
| 314 | case Core::HID::NpadStyleIndex::SNES: | 313 | case Core::HID::NpadStyleIndex::SNES: |
| 315 | shared_memory->style_tag.lucia.Assign(1); | 314 | shared_memory->style_tag.lucia.Assign(1); |
| 316 | shared_memory->device_type.fullkey.Assign(1); | 315 | shared_memory->device_type.fullkey.Assign(1); |
| 317 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia; | 316 | shared_memory->applet_footer_type = AppletFooterUiType::Lucia; |
| 318 | break; | 317 | break; |
| 319 | case Core::HID::NpadStyleIndex::N64: | 318 | case Core::HID::NpadStyleIndex::N64: |
| 320 | shared_memory->style_tag.lagoon.Assign(1); | 319 | shared_memory->style_tag.lagoon.Assign(1); |
| 321 | shared_memory->device_type.fullkey.Assign(1); | 320 | shared_memory->device_type.fullkey.Assign(1); |
| 322 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon; | 321 | shared_memory->applet_footer_type = AppletFooterUiType::Lagon; |
| 323 | break; | 322 | break; |
| 324 | case Core::HID::NpadStyleIndex::SegaGenesis: | 323 | case Core::HID::NpadStyleIndex::SegaGenesis: |
| 325 | shared_memory->style_tag.lager.Assign(1); | 324 | shared_memory->style_tag.lager.Assign(1); |
| @@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { | |||
| 419 | std::scoped_lock lock{mutex}; | 418 | std::scoped_lock lock{mutex}; |
| 420 | auto& controller = GetControllerFromNpadIdType(npad_id); | 419 | auto& controller = GetControllerFromNpadIdType(npad_id); |
| 421 | const auto controller_type = controller.device->GetNpadStyleIndex(); | 420 | const auto controller_type = controller.device->GetNpadStyleIndex(); |
| 421 | |||
| 422 | if (!controller.device->IsConnected() && controller.is_connected) { | ||
| 423 | DisconnectNpad(npad_id); | ||
| 424 | return; | ||
| 425 | } | ||
| 422 | if (!controller.device->IsConnected()) { | 426 | if (!controller.device->IsConnected()) { |
| 423 | return; | 427 | return; |
| 424 | } | 428 | } |
| 429 | if (controller.device->IsConnected() && !controller.is_connected) { | ||
| 430 | InitNewlyAddedController(npad_id); | ||
| 431 | } | ||
| 425 | 432 | ||
| 426 | // This function is unique to yuzu for the turbo buttons and motion to work properly | 433 | // This function is unique to yuzu for the turbo buttons and motion to work properly |
| 427 | controller.device->StatusUpdate(); | 434 | controller.device->StatusUpdate(); |
| @@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { | |||
| 468 | pad_entry.npad_buttons.l.Assign(button_state.zl); | 475 | pad_entry.npad_buttons.l.Assign(button_state.zl); |
| 469 | pad_entry.npad_buttons.r.Assign(button_state.zr); | 476 | pad_entry.npad_buttons.r.Assign(button_state.zr); |
| 470 | } | 477 | } |
| 478 | |||
| 479 | if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) { | ||
| 480 | hid_core.SetLastActiveController(npad_id); | ||
| 481 | } | ||
| 471 | } | 482 | } |
| 472 | 483 | ||
| 473 | void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { | 484 | void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { |
| @@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) { | |||
| 736 | 747 | ||
| 737 | // Once SetSupportedStyleSet is called controllers are fully initialized | 748 | // Once SetSupportedStyleSet is called controllers are fully initialized |
| 738 | is_controller_initialized = true; | 749 | is_controller_initialized = true; |
| 739 | |||
| 740 | // Connect all active controllers | ||
| 741 | for (auto& controller : controller_data) { | ||
| 742 | const auto& device = controller.device; | ||
| 743 | if (device->IsConnected()) { | ||
| 744 | AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType()); | ||
| 745 | } | ||
| 746 | } | ||
| 747 | } | 750 | } |
| 748 | 751 | ||
| 749 | Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { | 752 | Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { |
| @@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { | |||
| 1116 | .left = {}, | 1119 | .left = {}, |
| 1117 | .right = {}, | 1120 | .right = {}, |
| 1118 | }; | 1121 | }; |
| 1119 | shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None; | 1122 | shared_memory->applet_footer_type = AppletFooterUiType::None; |
| 1120 | 1123 | ||
| 1121 | controller.is_dual_left_connected = true; | 1124 | controller.is_dual_left_connected = true; |
| 1122 | controller.is_dual_right_connected = true; | 1125 | controller.is_dual_right_connected = true; |
| @@ -1508,6 +1511,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() { | |||
| 1508 | return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); | 1511 | return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); |
| 1509 | } | 1512 | } |
| 1510 | 1513 | ||
| 1514 | void Controller_NPad::ApplyNpadSystemCommonPolicy() { | ||
| 1515 | Core::HID::NpadStyleTag styletag{}; | ||
| 1516 | styletag.fullkey.Assign(1); | ||
| 1517 | styletag.handheld.Assign(1); | ||
| 1518 | styletag.joycon_dual.Assign(1); | ||
| 1519 | styletag.system_ext.Assign(1); | ||
| 1520 | styletag.system.Assign(1); | ||
| 1521 | SetSupportedStyleSet(styletag); | ||
| 1522 | |||
| 1523 | SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual); | ||
| 1524 | |||
| 1525 | supported_npad_id_types.clear(); | ||
| 1526 | supported_npad_id_types.resize(10); | ||
| 1527 | supported_npad_id_types[0] = Core::HID::NpadIdType::Player1; | ||
| 1528 | supported_npad_id_types[1] = Core::HID::NpadIdType::Player2; | ||
| 1529 | supported_npad_id_types[2] = Core::HID::NpadIdType::Player3; | ||
| 1530 | supported_npad_id_types[3] = Core::HID::NpadIdType::Player4; | ||
| 1531 | supported_npad_id_types[4] = Core::HID::NpadIdType::Player5; | ||
| 1532 | supported_npad_id_types[5] = Core::HID::NpadIdType::Player6; | ||
| 1533 | supported_npad_id_types[6] = Core::HID::NpadIdType::Player7; | ||
| 1534 | supported_npad_id_types[7] = Core::HID::NpadIdType::Player8; | ||
| 1535 | supported_npad_id_types[8] = Core::HID::NpadIdType::Other; | ||
| 1536 | supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld; | ||
| 1537 | } | ||
| 1538 | |||
| 1511 | bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { | 1539 | bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { |
| 1512 | if (controller == Core::HID::NpadStyleIndex::Handheld) { | 1540 | if (controller == Core::HID::NpadStyleIndex::Handheld) { |
| 1513 | const bool support_handheld = | 1541 | const bool support_handheld = |
| @@ -1518,7 +1546,7 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller | |||
| 1518 | return false; | 1546 | return false; |
| 1519 | } | 1547 | } |
| 1520 | // Handheld shouldn't be supported in docked mode | 1548 | // Handheld shouldn't be supported in docked mode |
| 1521 | if (Settings::values.use_docked_mode.GetValue()) { | 1549 | if (Settings::IsDockedMode()) { |
| 1522 | return false; | 1550 | return false; |
| 1523 | } | 1551 | } |
| 1524 | 1552 | ||
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 776411261..949e58a4c 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h | |||
| @@ -190,6 +190,8 @@ public: | |||
| 190 | // Specifically for cheat engine and other features. | 190 | // Specifically for cheat engine and other features. |
| 191 | Core::HID::NpadButton GetAndResetPressState(); | 191 | Core::HID::NpadButton GetAndResetPressState(); |
| 192 | 192 | ||
| 193 | void ApplyNpadSystemCommonPolicy(); | ||
| 194 | |||
| 193 | static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); | 195 | static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); |
| 194 | static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); | 196 | static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); |
| 195 | static Result VerifyValidSixAxisSensorHandle( | 197 | static Result VerifyValidSixAxisSensorHandle( |
| @@ -360,7 +362,7 @@ private: | |||
| 360 | enum class AppletFooterUiType : u8 { | 362 | enum class AppletFooterUiType : u8 { |
| 361 | None = 0, | 363 | None = 0, |
| 362 | HandheldNone = 1, | 364 | HandheldNone = 1, |
| 363 | HandheldJoyConLeftOnly = 1, | 365 | HandheldJoyConLeftOnly = 2, |
| 364 | HandheldJoyConRightOnly = 3, | 366 | HandheldJoyConRightOnly = 3, |
| 365 | HandheldJoyConLeftJoyConRight = 4, | 367 | HandheldJoyConLeftJoyConRight = 4, |
| 366 | JoyDual = 5, | 368 | JoyDual = 5, |
| @@ -382,13 +384,6 @@ private: | |||
| 382 | Lagon = 21, | 384 | Lagon = 21, |
| 383 | }; | 385 | }; |
| 384 | 386 | ||
| 385 | struct AppletFooterUi { | ||
| 386 | AppletFooterUiAttributes attributes{}; | ||
| 387 | AppletFooterUiType type{AppletFooterUiType::None}; | ||
| 388 | INSERT_PADDING_BYTES(0x5B); // Reserved | ||
| 389 | }; | ||
| 390 | static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size"); | ||
| 391 | |||
| 392 | // This is nn::hid::NpadLarkType | 387 | // This is nn::hid::NpadLarkType |
| 393 | enum class NpadLarkType : u32 { | 388 | enum class NpadLarkType : u32 { |
| 394 | Invalid, | 389 | Invalid, |
| @@ -419,13 +414,6 @@ private: | |||
| 419 | U, | 414 | U, |
| 420 | }; | 415 | }; |
| 421 | 416 | ||
| 422 | struct AppletNfcXcd { | ||
| 423 | union { | ||
| 424 | AppletFooterUi applet_footer{}; | ||
| 425 | Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo; | ||
| 426 | }; | ||
| 427 | }; | ||
| 428 | |||
| 429 | // This is nn::hid::detail::NpadInternalState | 417 | // This is nn::hid::detail::NpadInternalState |
| 430 | struct NpadInternalState { | 418 | struct NpadInternalState { |
| 431 | Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; | 419 | Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; |
| @@ -452,7 +440,9 @@ private: | |||
| 452 | Core::HID::NpadBatteryLevel battery_level_dual{}; | 440 | Core::HID::NpadBatteryLevel battery_level_dual{}; |
| 453 | Core::HID::NpadBatteryLevel battery_level_left{}; | 441 | Core::HID::NpadBatteryLevel battery_level_left{}; |
| 454 | Core::HID::NpadBatteryLevel battery_level_right{}; | 442 | Core::HID::NpadBatteryLevel battery_level_right{}; |
| 455 | AppletNfcXcd applet_nfc_xcd{}; | 443 | AppletFooterUiAttributes applet_footer_attributes{}; |
| 444 | AppletFooterUiType applet_footer_type{AppletFooterUiType::None}; | ||
| 445 | INSERT_PADDING_BYTES(0x5B); // Reserved | ||
| 456 | INSERT_PADDING_BYTES(0x20); // Unknown | 446 | INSERT_PADDING_BYTES(0x20); // Unknown |
| 457 | Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; | 447 | Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; |
| 458 | NpadLarkType lark_type_l_and_main{}; | 448 | NpadLarkType lark_type_l_and_main{}; |
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index e57a3a80e..dd00921fd 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h | |||
| @@ -16,22 +16,6 @@ class EmulatedConsole; | |||
| 16 | namespace Service::HID { | 16 | namespace Service::HID { |
| 17 | class Controller_Touchscreen final : public ControllerBase { | 17 | class Controller_Touchscreen final : public ControllerBase { |
| 18 | public: | 18 | public: |
| 19 | // This is nn::hid::TouchScreenModeForNx | ||
| 20 | enum class TouchScreenModeForNx : u8 { | ||
| 21 | UseSystemSetting, | ||
| 22 | Finger, | ||
| 23 | Heat2, | ||
| 24 | }; | ||
| 25 | |||
| 26 | // This is nn::hid::TouchScreenConfigurationForNx | ||
| 27 | struct TouchScreenConfigurationForNx { | ||
| 28 | TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting}; | ||
| 29 | INSERT_PADDING_BYTES_NOINIT(0x7); | ||
| 30 | INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved | ||
| 31 | }; | ||
| 32 | static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17, | ||
| 33 | "TouchScreenConfigurationForNx is an invalid size"); | ||
| 34 | |||
| 35 | explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); | 19 | explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); |
| 36 | ~Controller_Touchscreen() override; | 20 | ~Controller_Touchscreen() override; |
| 37 | 21 | ||
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 2bf1d8a27..4d70006c1 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp | |||
| @@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() { | |||
| 231 | return applet_resource; | 231 | return applet_resource; |
| 232 | } | 232 | } |
| 233 | 233 | ||
| 234 | Hid::Hid(Core::System& system_) | 234 | Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) |
| 235 | : ServiceFramework{system_, "hid"}, service_context{system_, service_name} { | 235 | : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{ |
| 236 | system_, | ||
| 237 | service_name} { | ||
| 236 | // clang-format off | 238 | // clang-format off |
| 237 | static const FunctionInfo functions[] = { | 239 | static const FunctionInfo functions[] = { |
| 238 | {0, &Hid::CreateAppletResource, "CreateAppletResource"}, | 240 | {0, &Hid::CreateAppletResource, "CreateAppletResource"}, |
| @@ -2368,7 +2370,7 @@ void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) { | |||
| 2368 | 2370 | ||
| 2369 | void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { | 2371 | void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { |
| 2370 | IPC::RequestParser rp{ctx}; | 2372 | IPC::RequestParser rp{ctx}; |
| 2371 | const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()}; | 2373 | const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()}; |
| 2372 | const auto applet_resource_user_id{rp.Pop<u64>()}; | 2374 | const auto applet_resource_user_id{rp.Pop<u64>()}; |
| 2373 | 2375 | ||
| 2374 | LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", | 2376 | LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", |
| @@ -2543,7 +2545,9 @@ public: | |||
| 2543 | 2545 | ||
| 2544 | class HidSys final : public ServiceFramework<HidSys> { | 2546 | class HidSys final : public ServiceFramework<HidSys> { |
| 2545 | public: | 2547 | public: |
| 2546 | explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} { | 2548 | explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) |
| 2549 | : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"}, | ||
| 2550 | applet_resource{applet_resource_} { | ||
| 2547 | // clang-format off | 2551 | // clang-format off |
| 2548 | static const FunctionInfo functions[] = { | 2552 | static const FunctionInfo functions[] = { |
| 2549 | {31, nullptr, "SendKeyboardLockKeyEvent"}, | 2553 | {31, nullptr, "SendKeyboardLockKeyEvent"}, |
| @@ -2568,7 +2572,7 @@ public: | |||
| 2568 | {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, | 2572 | {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, |
| 2569 | {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, | 2573 | {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, |
| 2570 | {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, | 2574 | {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, |
| 2571 | {306, nullptr, "GetLastActiveNpad"}, | 2575 | {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"}, |
| 2572 | {307, nullptr, "GetNpadSystemExtStyle"}, | 2576 | {307, nullptr, "GetNpadSystemExtStyle"}, |
| 2573 | {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, | 2577 | {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, |
| 2574 | {309, nullptr, "GetNpadFullKeyGripColor"}, | 2578 | {309, nullptr, "GetNpadFullKeyGripColor"}, |
| @@ -2624,7 +2628,7 @@ public: | |||
| 2624 | {700, nullptr, "ActivateUniquePad"}, | 2628 | {700, nullptr, "ActivateUniquePad"}, |
| 2625 | {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, | 2629 | {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, |
| 2626 | {703, nullptr, "GetUniquePadIds"}, | 2630 | {703, nullptr, "GetUniquePadIds"}, |
| 2627 | {751, nullptr, "AcquireJoyDetachOnBluetoothOffEventHandle"}, | 2631 | {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"}, |
| 2628 | {800, nullptr, "ListSixAxisSensorHandles"}, | 2632 | {800, nullptr, "ListSixAxisSensorHandles"}, |
| 2629 | {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, | 2633 | {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, |
| 2630 | {802, nullptr, "ResetSixAxisSensorCalibrationValues"}, | 2634 | {802, nullptr, "ResetSixAxisSensorCalibrationValues"}, |
| @@ -2650,7 +2654,7 @@ public: | |||
| 2650 | {830, nullptr, "SetNotificationLedPattern"}, | 2654 | {830, nullptr, "SetNotificationLedPattern"}, |
| 2651 | {831, nullptr, "SetNotificationLedPatternWithTimeout"}, | 2655 | {831, nullptr, "SetNotificationLedPatternWithTimeout"}, |
| 2652 | {832, nullptr, "PrepareHidsForNotificationWake"}, | 2656 | {832, nullptr, "PrepareHidsForNotificationWake"}, |
| 2653 | {850, nullptr, "IsUsbFullKeyControllerEnabled"}, | 2657 | {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"}, |
| 2654 | {851, nullptr, "EnableUsbFullKeyController"}, | 2658 | {851, nullptr, "EnableUsbFullKeyController"}, |
| 2655 | {852, nullptr, "IsUsbConnected"}, | 2659 | {852, nullptr, "IsUsbConnected"}, |
| 2656 | {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, | 2660 | {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, |
| @@ -2682,7 +2686,7 @@ public: | |||
| 2682 | {1150, nullptr, "SetTouchScreenMagnification"}, | 2686 | {1150, nullptr, "SetTouchScreenMagnification"}, |
| 2683 | {1151, nullptr, "GetTouchScreenFirmwareVersion"}, | 2687 | {1151, nullptr, "GetTouchScreenFirmwareVersion"}, |
| 2684 | {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, | 2688 | {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, |
| 2685 | {1153, nullptr, "GetTouchScreenDefaultConfiguration"}, | 2689 | {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"}, |
| 2686 | {1154, nullptr, "IsFirmwareAvailableForNotification"}, | 2690 | {1154, nullptr, "IsFirmwareAvailableForNotification"}, |
| 2687 | {1155, nullptr, "SetForceHandheldStyleVibration"}, | 2691 | {1155, nullptr, "SetForceHandheldStyleVibration"}, |
| 2688 | {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, | 2692 | {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, |
| @@ -2749,37 +2753,102 @@ public: | |||
| 2749 | // clang-format on | 2753 | // clang-format on |
| 2750 | 2754 | ||
| 2751 | RegisterHandlers(functions); | 2755 | RegisterHandlers(functions); |
| 2756 | |||
| 2757 | joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent"); | ||
| 2752 | } | 2758 | } |
| 2753 | 2759 | ||
| 2754 | private: | 2760 | private: |
| 2755 | void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { | 2761 | void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { |
| 2756 | // We already do this for homebrew so we can just stub it out | ||
| 2757 | LOG_WARNING(Service_HID, "called"); | 2762 | LOG_WARNING(Service_HID, "called"); |
| 2758 | 2763 | ||
| 2764 | GetAppletResource() | ||
| 2765 | ->GetController<Controller_NPad>(HidController::NPad) | ||
| 2766 | .ApplyNpadSystemCommonPolicy(); | ||
| 2767 | |||
| 2759 | IPC::ResponseBuilder rb{ctx, 2}; | 2768 | IPC::ResponseBuilder rb{ctx, 2}; |
| 2760 | rb.Push(ResultSuccess); | 2769 | rb.Push(ResultSuccess); |
| 2761 | } | 2770 | } |
| 2762 | 2771 | ||
| 2772 | void GetLastActiveNpad(HLERequestContext& ctx) { | ||
| 2773 | LOG_DEBUG(Service_HID, "(STUBBED) called"); | ||
| 2774 | |||
| 2775 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 2776 | rb.Push(ResultSuccess); | ||
| 2777 | rb.PushEnum(system.HIDCore().GetLastActiveController()); | ||
| 2778 | } | ||
| 2779 | |||
| 2763 | void GetUniquePadsFromNpad(HLERequestContext& ctx) { | 2780 | void GetUniquePadsFromNpad(HLERequestContext& ctx) { |
| 2764 | IPC::RequestParser rp{ctx}; | 2781 | IPC::RequestParser rp{ctx}; |
| 2765 | const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; | 2782 | const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; |
| 2766 | 2783 | ||
| 2767 | const s64 total_entries = 0; | ||
| 2768 | LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); | 2784 | LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); |
| 2769 | 2785 | ||
| 2786 | const std::vector<Core::HID::UniquePadId> unique_pads{}; | ||
| 2787 | |||
| 2788 | ctx.WriteBuffer(unique_pads); | ||
| 2789 | |||
| 2790 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 2791 | rb.Push(ResultSuccess); | ||
| 2792 | rb.Push(static_cast<u32>(unique_pads.size())); | ||
| 2793 | } | ||
| 2794 | |||
| 2795 | void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) { | ||
| 2796 | LOG_INFO(Service_AM, "called"); | ||
| 2797 | |||
| 2798 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 2799 | rb.Push(ResultSuccess); | ||
| 2800 | rb.PushCopyObjects(joy_detach_event->GetReadableEvent()); | ||
| 2801 | } | ||
| 2802 | |||
| 2803 | void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) { | ||
| 2804 | const bool is_enabled = false; | ||
| 2805 | |||
| 2806 | LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled); | ||
| 2807 | |||
| 2770 | IPC::ResponseBuilder rb{ctx, 3}; | 2808 | IPC::ResponseBuilder rb{ctx, 3}; |
| 2771 | rb.Push(ResultSuccess); | 2809 | rb.Push(ResultSuccess); |
| 2772 | rb.Push(total_entries); | 2810 | rb.Push(is_enabled); |
| 2773 | } | 2811 | } |
| 2812 | |||
| 2813 | void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) { | ||
| 2814 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2815 | |||
| 2816 | Core::HID::TouchScreenConfigurationForNx touchscreen_config{ | ||
| 2817 | .mode = Core::HID::TouchScreenModeForNx::Finger, | ||
| 2818 | }; | ||
| 2819 | |||
| 2820 | if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 && | ||
| 2821 | touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) { | ||
| 2822 | touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting; | ||
| 2823 | } | ||
| 2824 | |||
| 2825 | IPC::ResponseBuilder rb{ctx, 6}; | ||
| 2826 | rb.Push(ResultSuccess); | ||
| 2827 | rb.PushRaw(touchscreen_config); | ||
| 2828 | } | ||
| 2829 | |||
| 2830 | std::shared_ptr<IAppletResource> GetAppletResource() { | ||
| 2831 | if (applet_resource == nullptr) { | ||
| 2832 | applet_resource = std::make_shared<IAppletResource>(system, service_context); | ||
| 2833 | } | ||
| 2834 | |||
| 2835 | return applet_resource; | ||
| 2836 | } | ||
| 2837 | |||
| 2838 | Kernel::KEvent* joy_detach_event; | ||
| 2839 | KernelHelpers::ServiceContext service_context; | ||
| 2840 | std::shared_ptr<IAppletResource> applet_resource; | ||
| 2774 | }; | 2841 | }; |
| 2775 | 2842 | ||
| 2776 | void LoopProcess(Core::System& system) { | 2843 | void LoopProcess(Core::System& system) { |
| 2777 | auto server_manager = std::make_unique<ServerManager>(system); | 2844 | auto server_manager = std::make_unique<ServerManager>(system); |
| 2845 | std::shared_ptr<IAppletResource> applet_resource; | ||
| 2778 | 2846 | ||
| 2779 | server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system)); | 2847 | server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource)); |
| 2780 | server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); | 2848 | server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); |
| 2781 | server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); | 2849 | server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); |
| 2782 | server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system)); | 2850 | server_manager->RegisterNamedService("hid:sys", |
| 2851 | std::make_shared<HidSys>(system, applet_resource)); | ||
| 2783 | 2852 | ||
| 2784 | server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); | 2853 | server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); |
| 2785 | server_manager->RegisterNamedService("irs:sys", | 2854 | server_manager->RegisterNamedService("irs:sys", |
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index f247b83c2..0ca43de93 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h | |||
| @@ -95,7 +95,7 @@ private: | |||
| 95 | 95 | ||
| 96 | class Hid final : public ServiceFramework<Hid> { | 96 | class Hid final : public ServiceFramework<Hid> { |
| 97 | public: | 97 | public: |
| 98 | explicit Hid(Core::System& system_); | 98 | explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_); |
| 99 | ~Hid() override; | 99 | ~Hid() override; |
| 100 | 100 | ||
| 101 | std::shared_ptr<IAppletResource> GetAppletResource(); | 101 | std::shared_ptr<IAppletResource> GetAppletResource(); |
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 65c11a2f3..3b83c5ed7 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp | |||
| @@ -7,17 +7,16 @@ | |||
| 7 | #include "core/hle/service/ipc_helpers.h" | 7 | #include "core/hle/service/ipc_helpers.h" |
| 8 | #include "core/hle/service/mii/mii.h" | 8 | #include "core/hle/service/mii/mii.h" |
| 9 | #include "core/hle/service/mii/mii_manager.h" | 9 | #include "core/hle/service/mii/mii_manager.h" |
| 10 | #include "core/hle/service/mii/mii_result.h" | ||
| 10 | #include "core/hle/service/server_manager.h" | 11 | #include "core/hle/service/server_manager.h" |
| 11 | #include "core/hle/service/service.h" | 12 | #include "core/hle/service/service.h" |
| 12 | 13 | ||
| 13 | namespace Service::Mii { | 14 | namespace Service::Mii { |
| 14 | 15 | ||
| 15 | constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1}; | ||
| 16 | |||
| 17 | class IDatabaseService final : public ServiceFramework<IDatabaseService> { | 16 | class IDatabaseService final : public ServiceFramework<IDatabaseService> { |
| 18 | public: | 17 | public: |
| 19 | explicit IDatabaseService(Core::System& system_) | 18 | explicit IDatabaseService(Core::System& system_, bool is_system_) |
| 20 | : ServiceFramework{system_, "IDatabaseService"} { | 19 | : ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} { |
| 21 | // clang-format off | 20 | // clang-format off |
| 22 | static const FunctionInfo functions[] = { | 21 | static const FunctionInfo functions[] = { |
| 23 | {0, &IDatabaseService::IsUpdated, "IsUpdated"}, | 22 | {0, &IDatabaseService::IsUpdated, "IsUpdated"}, |
| @@ -54,34 +53,27 @@ public: | |||
| 54 | } | 53 | } |
| 55 | 54 | ||
| 56 | private: | 55 | private: |
| 57 | template <typename T> | ||
| 58 | std::vector<u8> SerializeArray(const std::vector<T>& values) { | ||
| 59 | std::vector<u8> out(values.size() * sizeof(T)); | ||
| 60 | std::size_t offset{}; | ||
| 61 | for (const auto& value : values) { | ||
| 62 | std::memcpy(out.data() + offset, &value, sizeof(T)); | ||
| 63 | offset += sizeof(T); | ||
| 64 | } | ||
| 65 | return out; | ||
| 66 | } | ||
| 67 | |||
| 68 | void IsUpdated(HLERequestContext& ctx) { | 56 | void IsUpdated(HLERequestContext& ctx) { |
| 69 | IPC::RequestParser rp{ctx}; | 57 | IPC::RequestParser rp{ctx}; |
| 70 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 58 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 71 | 59 | ||
| 72 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 60 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| 73 | 61 | ||
| 62 | const bool is_updated = manager.IsUpdated(metadata, source_flag); | ||
| 63 | |||
| 74 | IPC::ResponseBuilder rb{ctx, 3}; | 64 | IPC::ResponseBuilder rb{ctx, 3}; |
| 75 | rb.Push(ResultSuccess); | 65 | rb.Push(ResultSuccess); |
| 76 | rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter)); | 66 | rb.Push<u8>(is_updated); |
| 77 | } | 67 | } |
| 78 | 68 | ||
| 79 | void IsFullDatabase(HLERequestContext& ctx) { | 69 | void IsFullDatabase(HLERequestContext& ctx) { |
| 80 | LOG_DEBUG(Service_Mii, "called"); | 70 | LOG_DEBUG(Service_Mii, "called"); |
| 81 | 71 | ||
| 72 | const bool is_full_database = manager.IsFullDatabase(); | ||
| 73 | |||
| 82 | IPC::ResponseBuilder rb{ctx, 3}; | 74 | IPC::ResponseBuilder rb{ctx, 3}; |
| 83 | rb.Push(ResultSuccess); | 75 | rb.Push(ResultSuccess); |
| 84 | rb.Push(manager.IsFullDatabase()); | 76 | rb.Push<u8>(is_full_database); |
| 85 | } | 77 | } |
| 86 | 78 | ||
| 87 | void GetCount(HLERequestContext& ctx) { | 79 | void GetCount(HLERequestContext& ctx) { |
| @@ -90,57 +82,63 @@ private: | |||
| 90 | 82 | ||
| 91 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 83 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| 92 | 84 | ||
| 85 | const u32 mii_count = manager.GetCount(metadata, source_flag); | ||
| 86 | |||
| 93 | IPC::ResponseBuilder rb{ctx, 3}; | 87 | IPC::ResponseBuilder rb{ctx, 3}; |
| 94 | rb.Push(ResultSuccess); | 88 | rb.Push(ResultSuccess); |
| 95 | rb.Push<u32>(manager.GetCount(source_flag)); | 89 | rb.Push(mii_count); |
| 96 | } | 90 | } |
| 97 | 91 | ||
| 98 | void Get(HLERequestContext& ctx) { | 92 | void Get(HLERequestContext& ctx) { |
| 99 | IPC::RequestParser rp{ctx}; | 93 | IPC::RequestParser rp{ctx}; |
| 100 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 94 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 95 | const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; | ||
| 101 | 96 | ||
| 102 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 97 | LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size); |
| 98 | |||
| 99 | u32 mii_count{}; | ||
| 100 | std::vector<CharInfoElement> char_info_elements(output_size); | ||
| 101 | Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag); | ||
| 103 | 102 | ||
| 104 | const auto default_miis{manager.GetDefault(source_flag)}; | 103 | if (mii_count != 0) { |
| 105 | if (default_miis.size() > 0) { | 104 | ctx.WriteBuffer(char_info_elements); |
| 106 | ctx.WriteBuffer(SerializeArray(default_miis)); | ||
| 107 | } | 105 | } |
| 108 | 106 | ||
| 109 | IPC::ResponseBuilder rb{ctx, 3}; | 107 | IPC::ResponseBuilder rb{ctx, 3}; |
| 110 | rb.Push(ResultSuccess); | 108 | rb.Push(result); |
| 111 | rb.Push<u32>(static_cast<u32>(default_miis.size())); | 109 | rb.Push(mii_count); |
| 112 | } | 110 | } |
| 113 | 111 | ||
| 114 | void Get1(HLERequestContext& ctx) { | 112 | void Get1(HLERequestContext& ctx) { |
| 115 | IPC::RequestParser rp{ctx}; | 113 | IPC::RequestParser rp{ctx}; |
| 116 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 114 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 115 | const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; | ||
| 117 | 116 | ||
| 118 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 117 | LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size); |
| 119 | 118 | ||
| 120 | const auto default_miis{manager.GetDefault(source_flag)}; | 119 | u32 mii_count{}; |
| 120 | std::vector<CharInfo> char_info(output_size); | ||
| 121 | Result result = manager.Get(metadata, char_info, mii_count, source_flag); | ||
| 121 | 122 | ||
| 122 | std::vector<CharInfo> values; | 123 | if (mii_count != 0) { |
| 123 | for (const auto& element : default_miis) { | 124 | ctx.WriteBuffer(char_info); |
| 124 | values.emplace_back(element.info); | ||
| 125 | } | 125 | } |
| 126 | 126 | ||
| 127 | ctx.WriteBuffer(SerializeArray(values)); | ||
| 128 | |||
| 129 | IPC::ResponseBuilder rb{ctx, 3}; | 127 | IPC::ResponseBuilder rb{ctx, 3}; |
| 130 | rb.Push(ResultSuccess); | 128 | rb.Push(result); |
| 131 | rb.Push<u32>(static_cast<u32>(default_miis.size())); | 129 | rb.Push(mii_count); |
| 132 | } | 130 | } |
| 133 | 131 | ||
| 134 | void UpdateLatest(HLERequestContext& ctx) { | 132 | void UpdateLatest(HLERequestContext& ctx) { |
| 135 | IPC::RequestParser rp{ctx}; | 133 | IPC::RequestParser rp{ctx}; |
| 136 | const auto info{rp.PopRaw<CharInfo>()}; | 134 | const auto char_info{rp.PopRaw<CharInfo>()}; |
| 137 | const auto source_flag{rp.PopRaw<SourceFlag>()}; | 135 | const auto source_flag{rp.PopRaw<SourceFlag>()}; |
| 138 | 136 | ||
| 139 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | 137 | LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); |
| 140 | 138 | ||
| 141 | CharInfo new_char_info{}; | 139 | CharInfo new_char_info{}; |
| 142 | const auto result{manager.UpdateLatest(&new_char_info, info, source_flag)}; | 140 | const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag); |
| 143 | if (result != ResultSuccess) { | 141 | if (result.IsFailure()) { |
| 144 | IPC::ResponseBuilder rb{ctx, 2}; | 142 | IPC::ResponseBuilder rb{ctx, 2}; |
| 145 | rb.Push(result); | 143 | rb.Push(result); |
| 146 | return; | 144 | return; |
| @@ -153,7 +151,6 @@ private: | |||
| 153 | 151 | ||
| 154 | void BuildRandom(HLERequestContext& ctx) { | 152 | void BuildRandom(HLERequestContext& ctx) { |
| 155 | IPC::RequestParser rp{ctx}; | 153 | IPC::RequestParser rp{ctx}; |
| 156 | |||
| 157 | const auto age{rp.PopRaw<Age>()}; | 154 | const auto age{rp.PopRaw<Age>()}; |
| 158 | const auto gender{rp.PopRaw<Gender>()}; | 155 | const auto gender{rp.PopRaw<Gender>()}; |
| 159 | const auto race{rp.PopRaw<Race>()}; | 156 | const auto race{rp.PopRaw<Race>()}; |
| @@ -162,47 +159,48 @@ private: | |||
| 162 | 159 | ||
| 163 | if (age > Age::All) { | 160 | if (age > Age::All) { |
| 164 | IPC::ResponseBuilder rb{ctx, 2}; | 161 | IPC::ResponseBuilder rb{ctx, 2}; |
| 165 | rb.Push(ERROR_INVALID_ARGUMENT); | 162 | rb.Push(ResultInvalidArgument); |
| 166 | LOG_ERROR(Service_Mii, "invalid age={}", age); | ||
| 167 | return; | 163 | return; |
| 168 | } | 164 | } |
| 169 | 165 | ||
| 170 | if (gender > Gender::All) { | 166 | if (gender > Gender::All) { |
| 171 | IPC::ResponseBuilder rb{ctx, 2}; | 167 | IPC::ResponseBuilder rb{ctx, 2}; |
| 172 | rb.Push(ERROR_INVALID_ARGUMENT); | 168 | rb.Push(ResultInvalidArgument); |
| 173 | LOG_ERROR(Service_Mii, "invalid gender={}", gender); | ||
| 174 | return; | 169 | return; |
| 175 | } | 170 | } |
| 176 | 171 | ||
| 177 | if (race > Race::All) { | 172 | if (race > Race::All) { |
| 178 | IPC::ResponseBuilder rb{ctx, 2}; | 173 | IPC::ResponseBuilder rb{ctx, 2}; |
| 179 | rb.Push(ERROR_INVALID_ARGUMENT); | 174 | rb.Push(ResultInvalidArgument); |
| 180 | LOG_ERROR(Service_Mii, "invalid race={}", race); | ||
| 181 | return; | 175 | return; |
| 182 | } | 176 | } |
| 183 | 177 | ||
| 178 | CharInfo char_info{}; | ||
| 179 | manager.BuildRandom(char_info, age, gender, race); | ||
| 180 | |||
| 184 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 181 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 185 | rb.Push(ResultSuccess); | 182 | rb.Push(ResultSuccess); |
| 186 | rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race)); | 183 | rb.PushRaw<CharInfo>(char_info); |
| 187 | } | 184 | } |
| 188 | 185 | ||
| 189 | void BuildDefault(HLERequestContext& ctx) { | 186 | void BuildDefault(HLERequestContext& ctx) { |
| 190 | IPC::RequestParser rp{ctx}; | 187 | IPC::RequestParser rp{ctx}; |
| 191 | const auto index{rp.Pop<u32>()}; | 188 | const auto index{rp.Pop<u32>()}; |
| 192 | 189 | ||
| 193 | LOG_DEBUG(Service_Mii, "called with index={}", index); | 190 | LOG_INFO(Service_Mii, "called with index={}", index); |
| 194 | 191 | ||
| 195 | if (index > 5) { | 192 | if (index > 5) { |
| 196 | LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}", | ||
| 197 | index); | ||
| 198 | IPC::ResponseBuilder rb{ctx, 2}; | 193 | IPC::ResponseBuilder rb{ctx, 2}; |
| 199 | rb.Push(ERROR_INVALID_ARGUMENT); | 194 | rb.Push(ResultInvalidArgument); |
| 200 | return; | 195 | return; |
| 201 | } | 196 | } |
| 202 | 197 | ||
| 198 | CharInfo char_info{}; | ||
| 199 | manager.BuildDefault(char_info, index); | ||
| 200 | |||
| 203 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 201 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 204 | rb.Push(ResultSuccess); | 202 | rb.Push(ResultSuccess); |
| 205 | rb.PushRaw<CharInfo>(manager.BuildDefault(index)); | 203 | rb.PushRaw<CharInfo>(char_info); |
| 206 | } | 204 | } |
| 207 | 205 | ||
| 208 | void GetIndex(HLERequestContext& ctx) { | 206 | void GetIndex(HLERequestContext& ctx) { |
| @@ -211,19 +209,21 @@ private: | |||
| 211 | 209 | ||
| 212 | LOG_DEBUG(Service_Mii, "called"); | 210 | LOG_DEBUG(Service_Mii, "called"); |
| 213 | 211 | ||
| 214 | u32 index{}; | 212 | s32 index{}; |
| 213 | const auto result = manager.GetIndex(metadata, info, index); | ||
| 214 | |||
| 215 | IPC::ResponseBuilder rb{ctx, 3}; | 215 | IPC::ResponseBuilder rb{ctx, 3}; |
| 216 | rb.Push(manager.GetIndex(info, index)); | 216 | rb.Push(result); |
| 217 | rb.Push(index); | 217 | rb.Push(index); |
| 218 | } | 218 | } |
| 219 | 219 | ||
| 220 | void SetInterfaceVersion(HLERequestContext& ctx) { | 220 | void SetInterfaceVersion(HLERequestContext& ctx) { |
| 221 | IPC::RequestParser rp{ctx}; | 221 | IPC::RequestParser rp{ctx}; |
| 222 | current_interface_version = rp.PopRaw<u32>(); | 222 | const auto interface_version{rp.PopRaw<u32>()}; |
| 223 | 223 | ||
| 224 | LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version); | 224 | LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); |
| 225 | 225 | ||
| 226 | UNIMPLEMENTED_IF(current_interface_version != 1); | 226 | manager.SetInterfaceVersion(metadata, interface_version); |
| 227 | 227 | ||
| 228 | IPC::ResponseBuilder rb{ctx, 2}; | 228 | IPC::ResponseBuilder rb{ctx, 2}; |
| 229 | rb.Push(ResultSuccess); | 229 | rb.Push(ResultSuccess); |
| @@ -231,30 +231,27 @@ private: | |||
| 231 | 231 | ||
| 232 | void Convert(HLERequestContext& ctx) { | 232 | void Convert(HLERequestContext& ctx) { |
| 233 | IPC::RequestParser rp{ctx}; | 233 | IPC::RequestParser rp{ctx}; |
| 234 | |||
| 235 | const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; | 234 | const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; |
| 236 | 235 | ||
| 237 | LOG_INFO(Service_Mii, "called"); | 236 | LOG_INFO(Service_Mii, "called"); |
| 238 | 237 | ||
| 238 | CharInfo char_info{}; | ||
| 239 | manager.ConvertV3ToCharInfo(char_info, mii_v3); | ||
| 240 | |||
| 239 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; | 241 | IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; |
| 240 | rb.Push(ResultSuccess); | 242 | rb.Push(ResultSuccess); |
| 241 | rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3)); | 243 | rb.PushRaw<CharInfo>(char_info); |
| 242 | } | ||
| 243 | |||
| 244 | constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { | ||
| 245 | return current_interface_version >= interface_version; | ||
| 246 | } | 244 | } |
| 247 | 245 | ||
| 248 | MiiManager manager; | 246 | MiiManager manager{}; |
| 249 | 247 | DatabaseSessionMetadata metadata{}; | |
| 250 | u32 current_interface_version{}; | 248 | bool is_system{}; |
| 251 | u64 current_update_counter{}; | ||
| 252 | }; | 249 | }; |
| 253 | 250 | ||
| 254 | class MiiDBModule final : public ServiceFramework<MiiDBModule> { | 251 | class MiiDBModule final : public ServiceFramework<MiiDBModule> { |
| 255 | public: | 252 | public: |
| 256 | explicit MiiDBModule(Core::System& system_, const char* name_) | 253 | explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_) |
| 257 | : ServiceFramework{system_, name_} { | 254 | : ServiceFramework{system_, name_}, is_system{is_system_} { |
| 258 | // clang-format off | 255 | // clang-format off |
| 259 | static const FunctionInfo functions[] = { | 256 | static const FunctionInfo functions[] = { |
| 260 | {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, | 257 | {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, |
| @@ -268,10 +265,12 @@ private: | |||
| 268 | void GetDatabaseService(HLERequestContext& ctx) { | 265 | void GetDatabaseService(HLERequestContext& ctx) { |
| 269 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 266 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 270 | rb.Push(ResultSuccess); | 267 | rb.Push(ResultSuccess); |
| 271 | rb.PushIpcInterface<IDatabaseService>(system); | 268 | rb.PushIpcInterface<IDatabaseService>(system, is_system); |
| 272 | 269 | ||
| 273 | LOG_DEBUG(Service_Mii, "called"); | 270 | LOG_DEBUG(Service_Mii, "called"); |
| 274 | } | 271 | } |
| 272 | |||
| 273 | bool is_system{}; | ||
| 275 | }; | 274 | }; |
| 276 | 275 | ||
| 277 | class MiiImg final : public ServiceFramework<MiiImg> { | 276 | class MiiImg final : public ServiceFramework<MiiImg> { |
| @@ -303,8 +302,10 @@ public: | |||
| 303 | void LoopProcess(Core::System& system) { | 302 | void LoopProcess(Core::System& system) { |
| 304 | auto server_manager = std::make_unique<ServerManager>(system); | 303 | auto server_manager = std::make_unique<ServerManager>(system); |
| 305 | 304 | ||
| 306 | server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e")); | 305 | server_manager->RegisterNamedService("mii:e", |
| 307 | server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u")); | 306 | std::make_shared<MiiDBModule>(system, "mii:e", true)); |
| 307 | server_manager->RegisterNamedService("mii:u", | ||
| 308 | std::make_shared<MiiDBModule>(system, "mii:u", false)); | ||
| 308 | server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); | 309 | server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); |
| 309 | ServerManager::RunServer(std::move(server_manager)); | 310 | ServerManager::RunServer(std::move(server_manager)); |
| 310 | } | 311 | } |
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 46125d473..292d63777 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp | |||
| @@ -10,386 +10,24 @@ | |||
| 10 | 10 | ||
| 11 | #include "core/hle/service/acc/profile_manager.h" | 11 | #include "core/hle/service/acc/profile_manager.h" |
| 12 | #include "core/hle/service/mii/mii_manager.h" | 12 | #include "core/hle/service/mii/mii_manager.h" |
| 13 | #include "core/hle/service/mii/raw_data.h" | 13 | #include "core/hle/service/mii/mii_result.h" |
| 14 | #include "core/hle/service/mii/mii_util.h" | ||
| 15 | #include "core/hle/service/mii/types/core_data.h" | ||
| 16 | #include "core/hle/service/mii/types/raw_data.h" | ||
| 14 | 17 | ||
| 15 | namespace Service::Mii { | 18 | namespace Service::Mii { |
| 16 | |||
| 17 | namespace { | ||
| 18 | |||
| 19 | constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; | ||
| 20 | |||
| 21 | constexpr std::size_t BaseMiiCount{2}; | ||
| 22 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; | 19 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; |
| 23 | 20 | ||
| 24 | constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; | 21 | MiiManager::MiiManager() {} |
| 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}; | ||
| 27 | constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; | ||
| 28 | constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0}; | ||
| 29 | constexpr std::array<u8, 62> EyeRotateLookup{ | ||
| 30 | {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, | ||
| 31 | 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, | ||
| 32 | 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, | ||
| 33 | 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}}; | ||
| 34 | constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, | ||
| 35 | 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, | ||
| 36 | 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}}; | ||
| 37 | |||
| 38 | template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize> | ||
| 39 | std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) { | ||
| 40 | std::array<T, DestArraySize> out{}; | ||
| 41 | std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize)); | ||
| 42 | return out; | ||
| 43 | } | ||
| 44 | |||
| 45 | CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) { | ||
| 46 | MiiStoreBitFields bf; | ||
| 47 | std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); | ||
| 48 | |||
| 49 | return { | ||
| 50 | .uuid = data.data.uuid, | ||
| 51 | .name = ResizeArray<char16_t, 10, 11>(data.data.name), | ||
| 52 | .font_region = static_cast<u8>(bf.font_region.Value()), | ||
| 53 | .favorite_color = static_cast<u8>(bf.favorite_color.Value()), | ||
| 54 | .gender = static_cast<u8>(bf.gender.Value()), | ||
| 55 | .height = static_cast<u8>(bf.height.Value()), | ||
| 56 | .build = static_cast<u8>(bf.build.Value()), | ||
| 57 | .type = static_cast<u8>(bf.type.Value()), | ||
| 58 | .region_move = static_cast<u8>(bf.region_move.Value()), | ||
| 59 | .faceline_type = static_cast<u8>(bf.faceline_type.Value()), | ||
| 60 | .faceline_color = static_cast<u8>(bf.faceline_color.Value()), | ||
| 61 | .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()), | ||
| 62 | .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()), | ||
| 63 | .hair_type = static_cast<u8>(bf.hair_type.Value()), | ||
| 64 | .hair_color = static_cast<u8>(bf.hair_color.Value()), | ||
| 65 | .hair_flip = static_cast<u8>(bf.hair_flip.Value()), | ||
| 66 | .eye_type = static_cast<u8>(bf.eye_type.Value()), | ||
| 67 | .eye_color = static_cast<u8>(bf.eye_color.Value()), | ||
| 68 | .eye_scale = static_cast<u8>(bf.eye_scale.Value()), | ||
| 69 | .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()), | ||
| 70 | .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()), | ||
| 71 | .eye_x = static_cast<u8>(bf.eye_x.Value()), | ||
| 72 | .eye_y = static_cast<u8>(bf.eye_y.Value()), | ||
| 73 | .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()), | ||
| 74 | .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()), | ||
| 75 | .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()), | ||
| 76 | .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()), | ||
| 77 | .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()), | ||
| 78 | .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()), | ||
| 79 | .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3), | ||
| 80 | .nose_type = static_cast<u8>(bf.nose_type.Value()), | ||
| 81 | .nose_scale = static_cast<u8>(bf.nose_scale.Value()), | ||
| 82 | .nose_y = static_cast<u8>(bf.nose_y.Value()), | ||
| 83 | .mouth_type = static_cast<u8>(bf.mouth_type.Value()), | ||
| 84 | .mouth_color = static_cast<u8>(bf.mouth_color.Value()), | ||
| 85 | .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()), | ||
| 86 | .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()), | ||
| 87 | .mouth_y = static_cast<u8>(bf.mouth_y.Value()), | ||
| 88 | .beard_color = static_cast<u8>(bf.beard_color.Value()), | ||
| 89 | .beard_type = static_cast<u8>(bf.beard_type.Value()), | ||
| 90 | .mustache_type = static_cast<u8>(bf.mustache_type.Value()), | ||
| 91 | .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()), | ||
| 92 | .mustache_y = static_cast<u8>(bf.mustache_y.Value()), | ||
| 93 | .glasses_type = static_cast<u8>(bf.glasses_type.Value()), | ||
| 94 | .glasses_color = static_cast<u8>(bf.glasses_color.Value()), | ||
| 95 | .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()), | ||
| 96 | .glasses_y = static_cast<u8>(bf.glasses_y.Value()), | ||
| 97 | .mole_type = static_cast<u8>(bf.mole_type.Value()), | ||
| 98 | .mole_scale = static_cast<u8>(bf.mole_scale.Value()), | ||
| 99 | .mole_x = static_cast<u8>(bf.mole_x.Value()), | ||
| 100 | .mole_y = static_cast<u8>(bf.mole_y.Value()), | ||
| 101 | .padding = 0, | ||
| 102 | }; | ||
| 103 | } | ||
| 104 | |||
| 105 | u16 GenerateCrc16(const void* data, std::size_t size) { | ||
| 106 | s32 crc{}; | ||
| 107 | for (std::size_t i = 0; i < size; i++) { | ||
| 108 | crc ^= static_cast<const u8*>(data)[i] << 8; | ||
| 109 | for (std::size_t j = 0; j < 8; j++) { | ||
| 110 | crc <<= 1; | ||
| 111 | if ((crc & 0x10000) != 0) { | ||
| 112 | crc = (crc ^ 0x1021) & 0xFFFF; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | return Common::swap16(static_cast<u16>(crc)); | ||
| 117 | } | ||
| 118 | |||
| 119 | template <typename T> | ||
| 120 | T GetRandomValue(T min, T max) { | ||
| 121 | std::random_device device; | ||
| 122 | std::mt19937 gen(device()); | ||
| 123 | std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max)); | ||
| 124 | return static_cast<T>(distribution(gen)); | ||
| 125 | } | ||
| 126 | |||
| 127 | template <typename T> | ||
| 128 | T GetRandomValue(T max) { | ||
| 129 | return GetRandomValue<T>({}, max); | ||
| 130 | } | ||
| 131 | |||
| 132 | MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) { | ||
| 133 | MiiStoreBitFields bf{}; | ||
| 134 | |||
| 135 | if (gender == Gender::All) { | ||
| 136 | gender = GetRandomValue<Gender>(Gender::Maximum); | ||
| 137 | } | ||
| 138 | |||
| 139 | bf.gender.Assign(gender); | ||
| 140 | bf.favorite_color.Assign(GetRandomValue<u8>(11)); | ||
| 141 | bf.region_move.Assign(0); | ||
| 142 | bf.font_region.Assign(FontRegion::Standard); | ||
| 143 | bf.type.Assign(0); | ||
| 144 | bf.height.Assign(64); | ||
| 145 | bf.build.Assign(64); | ||
| 146 | |||
| 147 | if (age == Age::All) { | ||
| 148 | const auto temp{GetRandomValue<int>(10)}; | ||
| 149 | if (temp >= 8) { | ||
| 150 | age = Age::Old; | ||
| 151 | } else if (temp >= 4) { | ||
| 152 | age = Age::Normal; | ||
| 153 | } else { | ||
| 154 | age = Age::Young; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | if (race == Race::All) { | ||
| 159 | const auto temp{GetRandomValue<int>(10)}; | ||
| 160 | if (temp >= 8) { | ||
| 161 | race = Race::Black; | ||
| 162 | } else if (temp >= 4) { | ||
| 163 | race = Race::White; | ||
| 164 | } else { | ||
| 165 | race = Race::Asian; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | u32 axis_y{}; | ||
| 170 | if (gender == Gender::Female && age == Age::Young) { | ||
| 171 | axis_y = GetRandomValue<u32>(3); | ||
| 172 | } | ||
| 173 | |||
| 174 | const std::size_t index{3 * static_cast<std::size_t>(age) + | ||
| 175 | 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; | ||
| 176 | |||
| 177 | const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)}; | ||
| 178 | const auto faceline_color_info{RawData::RandomMiiFacelineColor.at( | ||
| 179 | 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; | ||
| 180 | const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; | ||
| 181 | const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; | ||
| 182 | const auto hair_type_info{RawData::RandomMiiHairType.at(index)}; | ||
| 183 | const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + | ||
| 184 | static_cast<std::size_t>(age))}; | ||
| 185 | const auto eye_type_info{RawData::RandomMiiEyeType.at(index)}; | ||
| 186 | const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; | ||
| 187 | const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; | ||
| 188 | const auto nose_type_info{RawData::RandomMiiNoseType.at(index)}; | ||
| 189 | const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)}; | ||
| 190 | const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; | ||
| 191 | |||
| 192 | bf.faceline_type.Assign( | ||
| 193 | faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]); | ||
| 194 | bf.faceline_color.Assign( | ||
| 195 | faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]); | ||
| 196 | bf.faceline_wrinkle.Assign( | ||
| 197 | faceline_wrinkle_info | ||
| 198 | .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); | ||
| 199 | bf.faceline_makeup.Assign( | ||
| 200 | faceline_makeup_info | ||
| 201 | .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); | ||
| 202 | |||
| 203 | bf.hair_type.Assign( | ||
| 204 | hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]); | ||
| 205 | bf.hair_color.Assign( | ||
| 206 | HairColorLookup[hair_color_info | ||
| 207 | .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]); | ||
| 208 | bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum)); | ||
| 209 | |||
| 210 | bf.eye_type.Assign( | ||
| 211 | eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]); | ||
| 212 | |||
| 213 | const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; | ||
| 214 | const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; | ||
| 215 | const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; | ||
| 216 | const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]}; | ||
| 217 | |||
| 218 | bf.eye_color.Assign( | ||
| 219 | EyeColorLookup[eye_color_info | ||
| 220 | .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]); | ||
| 221 | bf.eye_scale.Assign(4); | ||
| 222 | bf.eye_aspect.Assign(3); | ||
| 223 | bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate); | ||
| 224 | bf.eye_x.Assign(2); | ||
| 225 | bf.eye_y.Assign(axis_y + 12); | ||
| 226 | |||
| 227 | bf.eyebrow_type.Assign( | ||
| 228 | eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); | ||
| 229 | |||
| 230 | const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; | ||
| 231 | const auto eyebrow_y{race == Race::Asian ? 9 : 10}; | ||
| 232 | const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6}; | ||
| 233 | const auto eyebrow_rotate{ | ||
| 234 | 32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]}; | ||
| 235 | |||
| 236 | bf.eyebrow_color.Assign(bf.hair_color); | ||
| 237 | bf.eyebrow_scale.Assign(4); | ||
| 238 | bf.eyebrow_aspect.Assign(3); | ||
| 239 | bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate); | ||
| 240 | bf.eyebrow_x.Assign(2); | ||
| 241 | bf.eyebrow_y.Assign(axis_y + eyebrow_y); | ||
| 242 | |||
| 243 | const auto nose_scale{gender == Gender::Female ? 3 : 4}; | ||
| 244 | |||
| 245 | bf.nose_type.Assign( | ||
| 246 | nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]); | ||
| 247 | bf.nose_scale.Assign(nose_scale); | ||
| 248 | bf.nose_y.Assign(axis_y + 9); | ||
| 249 | |||
| 250 | const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0}; | ||
| 251 | |||
| 252 | bf.mouth_type.Assign( | ||
| 253 | mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]); | ||
| 254 | bf.mouth_color.Assign(MouthColorLookup[mouth_color]); | ||
| 255 | bf.mouth_scale.Assign(4); | ||
| 256 | bf.mouth_aspect.Assign(3); | ||
| 257 | bf.mouth_y.Assign(axis_y + 13); | ||
| 258 | |||
| 259 | bf.beard_color.Assign(bf.hair_color); | ||
| 260 | bf.mustache_scale.Assign(4); | ||
| 261 | |||
| 262 | if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) { | ||
| 263 | const auto mustache_and_beard_flag{ | ||
| 264 | GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)}; | ||
| 265 | |||
| 266 | auto beard_type{BeardType::None}; | ||
| 267 | auto mustache_type{MustacheType::None}; | ||
| 268 | |||
| 269 | if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == | ||
| 270 | BeardAndMustacheFlag::Beard) { | ||
| 271 | beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5); | ||
| 272 | } | ||
| 273 | |||
| 274 | if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == | ||
| 275 | BeardAndMustacheFlag::Mustache) { | ||
| 276 | mustache_type = | ||
| 277 | GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5); | ||
| 278 | } | ||
| 279 | |||
| 280 | bf.mustache_type.Assign(mustache_type); | ||
| 281 | bf.beard_type.Assign(beard_type); | ||
| 282 | bf.mustache_y.Assign(10); | ||
| 283 | } else { | ||
| 284 | bf.mustache_type.Assign(MustacheType::None); | ||
| 285 | bf.beard_type.Assign(BeardType::None); | ||
| 286 | bf.mustache_y.Assign(axis_y + 10); | ||
| 287 | } | ||
| 288 | |||
| 289 | const auto glasses_type_start{GetRandomValue<std::size_t>(100)}; | ||
| 290 | u8 glasses_type{}; | ||
| 291 | while (glasses_type_start < glasses_type_info.values[glasses_type]) { | ||
| 292 | if (++glasses_type >= glasses_type_info.values_count) { | ||
| 293 | ASSERT(false); | ||
| 294 | break; | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | bf.glasses_type.Assign(glasses_type); | ||
| 299 | bf.glasses_color.Assign(GlassesColorLookup[0]); | ||
| 300 | bf.glasses_scale.Assign(4); | ||
| 301 | bf.glasses_y.Assign(axis_y + 10); | ||
| 302 | |||
| 303 | bf.mole_type.Assign(0); | ||
| 304 | bf.mole_scale.Assign(4); | ||
| 305 | bf.mole_x.Assign(2); | ||
| 306 | bf.mole_y.Assign(20); | ||
| 307 | |||
| 308 | return {DefaultMiiName, bf, user_id}; | ||
| 309 | } | ||
| 310 | |||
| 311 | MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) { | ||
| 312 | MiiStoreBitFields bf{}; | ||
| 313 | |||
| 314 | bf.font_region.Assign(info.font_region); | ||
| 315 | bf.favorite_color.Assign(info.favorite_color); | ||
| 316 | bf.gender.Assign(info.gender); | ||
| 317 | bf.height.Assign(info.height); | ||
| 318 | bf.build.Assign(info.weight); | ||
| 319 | bf.type.Assign(info.type); | ||
| 320 | bf.region_move.Assign(info.region); | ||
| 321 | bf.faceline_type.Assign(info.face_type); | ||
| 322 | bf.faceline_color.Assign(info.face_color); | ||
| 323 | bf.faceline_wrinkle.Assign(info.face_wrinkle); | ||
| 324 | bf.faceline_makeup.Assign(info.face_makeup); | ||
| 325 | bf.hair_type.Assign(info.hair_type); | ||
| 326 | bf.hair_color.Assign(HairColorLookup[info.hair_color]); | ||
| 327 | bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip)); | ||
| 328 | bf.eye_type.Assign(info.eye_type); | ||
| 329 | bf.eye_color.Assign(EyeColorLookup[info.eye_color]); | ||
| 330 | bf.eye_scale.Assign(info.eye_scale); | ||
| 331 | bf.eye_aspect.Assign(info.eye_aspect); | ||
| 332 | bf.eye_rotate.Assign(info.eye_rotate); | ||
| 333 | bf.eye_x.Assign(info.eye_x); | ||
| 334 | bf.eye_y.Assign(info.eye_y); | ||
| 335 | bf.eyebrow_type.Assign(info.eyebrow_type); | ||
| 336 | bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]); | ||
| 337 | bf.eyebrow_scale.Assign(info.eyebrow_scale); | ||
| 338 | bf.eyebrow_aspect.Assign(info.eyebrow_aspect); | ||
| 339 | bf.eyebrow_rotate.Assign(info.eyebrow_rotate); | ||
| 340 | bf.eyebrow_x.Assign(info.eyebrow_x); | ||
| 341 | bf.eyebrow_y.Assign(info.eyebrow_y - 3); | ||
| 342 | bf.nose_type.Assign(info.nose_type); | ||
| 343 | bf.nose_scale.Assign(info.nose_scale); | ||
| 344 | bf.nose_y.Assign(info.nose_y); | ||
| 345 | bf.mouth_type.Assign(info.mouth_type); | ||
| 346 | bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]); | ||
| 347 | bf.mouth_scale.Assign(info.mouth_scale); | ||
| 348 | bf.mouth_aspect.Assign(info.mouth_aspect); | ||
| 349 | bf.mouth_y.Assign(info.mouth_y); | ||
| 350 | bf.beard_color.Assign(HairColorLookup[info.beard_color]); | ||
| 351 | bf.beard_type.Assign(static_cast<BeardType>(info.beard_type)); | ||
| 352 | bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type)); | ||
| 353 | bf.mustache_scale.Assign(info.mustache_scale); | ||
| 354 | bf.mustache_y.Assign(info.mustache_y); | ||
| 355 | bf.glasses_type.Assign(info.glasses_type); | ||
| 356 | bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]); | ||
| 357 | bf.glasses_scale.Assign(info.glasses_scale); | ||
| 358 | bf.glasses_y.Assign(info.glasses_y); | ||
| 359 | bf.mole_type.Assign(info.mole_type); | ||
| 360 | bf.mole_scale.Assign(info.mole_scale); | ||
| 361 | bf.mole_x.Assign(info.mole_x); | ||
| 362 | bf.mole_y.Assign(info.mole_y); | ||
| 363 | |||
| 364 | return {DefaultMiiName, bf, user_id}; | ||
| 365 | } | ||
| 366 | |||
| 367 | } // namespace | ||
| 368 | |||
| 369 | MiiStoreData::MiiStoreData() = default; | ||
| 370 | |||
| 371 | MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields, | ||
| 372 | const Common::UUID& user_id) { | ||
| 373 | data.name = name; | ||
| 374 | data.uuid = Common::UUID::MakeRandomRFC4122V4(); | ||
| 375 | |||
| 376 | std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields)); | ||
| 377 | data_crc = GenerateCrc16(data.data.data(), sizeof(data)); | ||
| 378 | device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID)); | ||
| 379 | } | ||
| 380 | 22 | ||
| 381 | MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} | 23 | bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { |
| 382 | |||
| 383 | bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) { | ||
| 384 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 24 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 385 | return false; | 25 | return false; |
| 386 | } | 26 | } |
| 387 | 27 | ||
| 388 | const bool result{current_update_counter != update_counter}; | 28 | const auto metadata_update_counter = metadata.update_counter; |
| 389 | 29 | metadata.update_counter = update_counter; | |
| 390 | current_update_counter = update_counter; | 30 | return metadata_update_counter != update_counter; |
| 391 | |||
| 392 | return result; | ||
| 393 | } | 31 | } |
| 394 | 32 | ||
| 395 | bool MiiManager::IsFullDatabase() const { | 33 | bool MiiManager::IsFullDatabase() const { |
| @@ -397,301 +35,138 @@ bool MiiManager::IsFullDatabase() const { | |||
| 397 | return false; | 35 | return false; |
| 398 | } | 36 | } |
| 399 | 37 | ||
| 400 | u32 MiiManager::GetCount(SourceFlag source_flag) const { | 38 | u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { |
| 401 | std::size_t count{}; | 39 | u32 mii_count{}; |
| 40 | if ((source_flag & SourceFlag::Default) != SourceFlag::None) { | ||
| 41 | mii_count += DefaultMiiCount; | ||
| 42 | } | ||
| 402 | if ((source_flag & SourceFlag::Database) != SourceFlag::None) { | 43 | if ((source_flag & SourceFlag::Database) != SourceFlag::None) { |
| 403 | // TODO(bunnei): We don't implement the Mii database, but when we do, update this | 44 | // TODO(bunnei): We don't implement the Mii database, but when we do, update this |
| 404 | count += 0; | ||
| 405 | } | 45 | } |
| 406 | if ((source_flag & SourceFlag::Default) != SourceFlag::None) { | 46 | return mii_count; |
| 407 | count += (DefaultMiiCount - BaseMiiCount); | ||
| 408 | } | ||
| 409 | return static_cast<u32>(count); | ||
| 410 | } | 47 | } |
| 411 | 48 | ||
| 412 | Result MiiManager::UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag) { | 49 | Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, |
| 50 | const CharInfo& char_info, SourceFlag source_flag) { | ||
| 413 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | 51 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 414 | return ERROR_CANNOT_FIND_ENTRY; | 52 | return ResultNotFound; |
| 415 | } | 53 | } |
| 416 | 54 | ||
| 417 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry | 55 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry |
| 418 | return ERROR_CANNOT_FIND_ENTRY; | 56 | return ResultNotFound; |
| 419 | } | 57 | } |
| 420 | 58 | ||
| 421 | CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { | 59 | void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { |
| 422 | return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); | 60 | StoreData store_data{}; |
| 61 | store_data.BuildDefault(index); | ||
| 62 | out_char_info.SetFromStoreData(store_data); | ||
| 423 | } | 63 | } |
| 424 | 64 | ||
| 425 | CharInfo MiiManager::BuildDefault(std::size_t index) { | 65 | void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { |
| 426 | return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); | 66 | StoreData store_data{}; |
| 67 | store_data.BuildBase(gender); | ||
| 68 | out_char_info.SetFromStoreData(store_data); | ||
| 427 | } | 69 | } |
| 428 | 70 | ||
| 429 | CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const { | 71 | void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { |
| 430 | Service::Mii::MiiManager manager; | 72 | StoreData store_data{}; |
| 431 | auto mii = manager.BuildDefault(0); | 73 | store_data.BuildRandom(age, gender, race); |
| 432 | 74 | out_char_info.SetFromStoreData(store_data); | |
| 433 | if (!ValidateV3Info(mii_v3)) { | ||
| 434 | return mii; | ||
| 435 | } | ||
| 436 | |||
| 437 | // TODO: We are ignoring a bunch of data from the mii_v3 | ||
| 438 | |||
| 439 | mii.gender = static_cast<u8>(mii_v3.mii_information.gender); | ||
| 440 | mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color); | ||
| 441 | mii.height = mii_v3.height; | ||
| 442 | mii.build = mii_v3.build; | ||
| 443 | |||
| 444 | // Copy name until string terminator | ||
| 445 | mii.name = {}; | ||
| 446 | for (std::size_t index = 0; index < mii.name.size() - 1; index++) { | ||
| 447 | mii.name[index] = mii_v3.mii_name[index]; | ||
| 448 | if (mii.name[index] == 0) { | ||
| 449 | break; | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | mii.font_region = mii_v3.region_information.character_set; | ||
| 454 | |||
| 455 | mii.faceline_type = mii_v3.appearance_bits1.face_shape; | ||
| 456 | mii.faceline_color = mii_v3.appearance_bits1.skin_color; | ||
| 457 | mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles; | ||
| 458 | mii.faceline_make = mii_v3.appearance_bits2.makeup; | ||
| 459 | |||
| 460 | mii.hair_type = mii_v3.hair_style; | ||
| 461 | mii.hair_color = mii_v3.appearance_bits3.hair_color; | ||
| 462 | mii.hair_flip = mii_v3.appearance_bits3.flip_hair; | ||
| 463 | |||
| 464 | mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type); | ||
| 465 | mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color); | ||
| 466 | mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale); | ||
| 467 | mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch); | ||
| 468 | mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation); | ||
| 469 | mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing); | ||
| 470 | mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position); | ||
| 471 | |||
| 472 | mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style); | ||
| 473 | mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color); | ||
| 474 | mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale); | ||
| 475 | mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale); | ||
| 476 | mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation); | ||
| 477 | mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing); | ||
| 478 | mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position); | ||
| 479 | |||
| 480 | mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type); | ||
| 481 | mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale); | ||
| 482 | mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position); | ||
| 483 | |||
| 484 | mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type); | ||
| 485 | mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color); | ||
| 486 | mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale); | ||
| 487 | mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch); | ||
| 488 | mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position); | ||
| 489 | |||
| 490 | mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type); | ||
| 491 | mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale); | ||
| 492 | mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position); | ||
| 493 | |||
| 494 | mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type); | ||
| 495 | mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color); | ||
| 496 | |||
| 497 | mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type); | ||
| 498 | mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color); | ||
| 499 | mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale); | ||
| 500 | mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position); | ||
| 501 | |||
| 502 | mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled); | ||
| 503 | mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale); | ||
| 504 | mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position); | ||
| 505 | mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position); | ||
| 506 | |||
| 507 | // TODO: Validate mii data | ||
| 508 | |||
| 509 | return mii; | ||
| 510 | } | 75 | } |
| 511 | 76 | ||
| 512 | Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const { | 77 | void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { |
| 513 | Service::Mii::MiiManager manager; | 78 | StoreData store_data{}; |
| 514 | Ver3StoreData mii_v3{}; | 79 | mii_v3.BuildToStoreData(store_data); |
| 515 | 80 | out_char_info.SetFromStoreData(store_data); | |
| 516 | // TODO: We are ignoring a bunch of data from the mii_v3 | 81 | } |
| 517 | |||
| 518 | mii_v3.version = 1; | ||
| 519 | mii_v3.mii_information.gender.Assign(mii.gender); | ||
| 520 | mii_v3.mii_information.favorite_color.Assign(mii.favorite_color); | ||
| 521 | mii_v3.height = mii.height; | ||
| 522 | mii_v3.build = mii.build; | ||
| 523 | 82 | ||
| 524 | // Copy name until string terminator | 83 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, |
| 525 | mii_v3.mii_name = {}; | 84 | std::span<CharInfoElement> out_elements, u32& out_count, |
| 526 | for (std::size_t index = 0; index < mii.name.size() - 1; index++) { | 85 | SourceFlag source_flag) { |
| 527 | mii_v3.mii_name[index] = mii.name[index]; | 86 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { |
| 528 | if (mii_v3.mii_name[index] == 0) { | 87 | return BuildDefault(out_elements, out_count, source_flag); |
| 529 | break; | ||
| 530 | } | ||
| 531 | } | 88 | } |
| 532 | 89 | ||
| 533 | mii_v3.region_information.character_set.Assign(mii.font_region); | 90 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry |
| 534 | 91 | ||
| 535 | mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type); | 92 | // Include default Mii at the end of the list |
| 536 | mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle); | 93 | return BuildDefault(out_elements, out_count, source_flag); |
| 537 | mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make); | 94 | } |
| 538 | 95 | ||
| 539 | mii_v3.hair_style = mii.hair_type; | 96 | Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, |
| 540 | mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip); | 97 | u32& out_count, SourceFlag source_flag) { |
| 98 | if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | ||
| 99 | return BuildDefault(out_char_info, out_count, source_flag); | ||
| 100 | } | ||
| 541 | 101 | ||
| 542 | mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type); | 102 | // TODO(bunnei): We don't implement the Mii database, so we can't have an entry |
| 543 | mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale); | ||
| 544 | mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect); | ||
| 545 | mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate); | ||
| 546 | mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x); | ||
| 547 | mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y); | ||
| 548 | 103 | ||
| 549 | mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type); | 104 | // Include default Mii at the end of the list |
| 550 | mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale); | 105 | return BuildDefault(out_char_info, out_count, source_flag); |
| 551 | mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect); | 106 | } |
| 552 | mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate); | ||
| 553 | mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x); | ||
| 554 | mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y); | ||
| 555 | 107 | ||
| 556 | mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type); | 108 | Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, |
| 557 | mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale); | 109 | SourceFlag source_flag) { |
| 558 | mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y); | 110 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { |
| 111 | return ResultSuccess; | ||
| 112 | } | ||
| 559 | 113 | ||
| 560 | mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type); | 114 | StoreData store_data{}; |
| 561 | mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale); | ||
| 562 | mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect); | ||
| 563 | mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y); | ||
| 564 | 115 | ||
| 565 | mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type); | 116 | for (std::size_t index = 0; index < DefaultMiiCount; ++index) { |
| 566 | mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale); | 117 | if (out_elements.size() <= static_cast<std::size_t>(out_count)) { |
| 567 | mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y); | 118 | return ResultInvalidArgumentSize; |
| 119 | } | ||
| 568 | 120 | ||
| 569 | mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type); | 121 | store_data.BuildDefault(static_cast<u32>(index)); |
| 570 | 122 | ||
| 571 | mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale); | 123 | out_elements[out_count].source = Source::Default; |
| 572 | mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y); | 124 | out_elements[out_count].char_info.SetFromStoreData(store_data); |
| 125 | out_count++; | ||
| 126 | } | ||
| 573 | 127 | ||
| 574 | mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type); | 128 | return ResultSuccess; |
| 575 | mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale); | 129 | } |
| 576 | mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x); | ||
| 577 | mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y); | ||
| 578 | 130 | ||
| 579 | // These types are converted to V3 from a table | 131 | Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, |
| 580 | mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]); | 132 | SourceFlag source_flag) { |
| 581 | mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]); | 133 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { |
| 582 | mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]); | 134 | return ResultSuccess; |
| 583 | mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]); | 135 | } |
| 584 | mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]); | ||
| 585 | mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]); | ||
| 586 | mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]); | ||
| 587 | mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]); | ||
| 588 | 136 | ||
| 589 | mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16)); | 137 | StoreData store_data{}; |
| 590 | 138 | ||
| 591 | // TODO: Validate mii_v3 data | 139 | for (std::size_t index = 0; index < DefaultMiiCount; ++index) { |
| 140 | if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { | ||
| 141 | return ResultInvalidArgumentSize; | ||
| 142 | } | ||
| 592 | 143 | ||
| 593 | return mii_v3; | 144 | store_data.BuildDefault(static_cast<u32>(index)); |
| 594 | } | ||
| 595 | 145 | ||
| 596 | NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const { | 146 | out_char_info[out_count].SetFromStoreData(store_data); |
| 597 | return { | 147 | out_count++; |
| 598 | .faceline_color = static_cast<u8>(mii.faceline_color & 0xf), | 148 | } |
| 599 | .hair_color = static_cast<u8>(mii.hair_color & 0x7f), | ||
| 600 | .eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f), | ||
| 601 | .eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f), | ||
| 602 | .mouth_color = static_cast<u8>(mii.mouth_color & 0x7f), | ||
| 603 | .beard_color = static_cast<u8>(mii.beard_color & 0x7f), | ||
| 604 | .glass_color = static_cast<u8>(mii.glasses_color & 0x7f), | ||
| 605 | .glass_type = static_cast<u8>(mii.glasses_type & 0x1f), | ||
| 606 | }; | ||
| 607 | } | ||
| 608 | 149 | ||
| 609 | bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const { | 150 | return ResultSuccess; |
| 610 | bool is_valid = mii_v3.version == 0 || mii_v3.version == 3; | ||
| 611 | |||
| 612 | is_valid = is_valid && (mii_v3.mii_name[0] != 0); | ||
| 613 | |||
| 614 | is_valid = is_valid && (mii_v3.mii_information.birth_month < 13); | ||
| 615 | is_valid = is_valid && (mii_v3.mii_information.birth_day < 32); | ||
| 616 | is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12); | ||
| 617 | is_valid = is_valid && (mii_v3.height < 128); | ||
| 618 | is_valid = is_valid && (mii_v3.build < 128); | ||
| 619 | |||
| 620 | is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12); | ||
| 621 | is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7); | ||
| 622 | is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12); | ||
| 623 | is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12); | ||
| 624 | |||
| 625 | is_valid = is_valid && (mii_v3.hair_style < 132); | ||
| 626 | is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8); | ||
| 627 | |||
| 628 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60); | ||
| 629 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6); | ||
| 630 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8); | ||
| 631 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7); | ||
| 632 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8); | ||
| 633 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13); | ||
| 634 | is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19); | ||
| 635 | |||
| 636 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25); | ||
| 637 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8); | ||
| 638 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9); | ||
| 639 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7); | ||
| 640 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12); | ||
| 641 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12); | ||
| 642 | is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19); | ||
| 643 | |||
| 644 | is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18); | ||
| 645 | is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9); | ||
| 646 | is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19); | ||
| 647 | |||
| 648 | is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36); | ||
| 649 | is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5); | ||
| 650 | is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9); | ||
| 651 | is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7); | ||
| 652 | is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19); | ||
| 653 | |||
| 654 | is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6); | ||
| 655 | is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7); | ||
| 656 | is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17); | ||
| 657 | |||
| 658 | is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6); | ||
| 659 | is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8); | ||
| 660 | |||
| 661 | is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9); | ||
| 662 | is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6); | ||
| 663 | is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8); | ||
| 664 | is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21); | ||
| 665 | |||
| 666 | is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2); | ||
| 667 | is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9); | ||
| 668 | is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17); | ||
| 669 | is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31); | ||
| 670 | |||
| 671 | return is_valid; | ||
| 672 | } | 151 | } |
| 673 | 152 | ||
| 674 | std::vector<MiiInfoElement> MiiManager::GetDefault(SourceFlag source_flag) { | 153 | Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, |
| 675 | std::vector<MiiInfoElement> result; | 154 | s32& out_index) { |
| 676 | 155 | ||
| 677 | if ((source_flag & SourceFlag::Default) == SourceFlag::None) { | 156 | if (char_info.Verify() != ValidationResult::NoErrors) { |
| 678 | return result; | 157 | return ResultInvalidCharInfo; |
| 679 | } | 158 | } |
| 680 | 159 | ||
| 681 | for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) { | ||
| 682 | result.emplace_back(BuildDefault(index), Source::Default); | ||
| 683 | } | ||
| 684 | |||
| 685 | return result; | ||
| 686 | } | ||
| 687 | |||
| 688 | Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) { | ||
| 689 | constexpr u32 INVALID_INDEX{0xFFFFFFFF}; | 160 | constexpr u32 INVALID_INDEX{0xFFFFFFFF}; |
| 690 | 161 | ||
| 691 | index = INVALID_INDEX; | 162 | out_index = INVALID_INDEX; |
| 692 | 163 | ||
| 693 | // TODO(bunnei): We don't implement the Mii database, so we can't have an index | 164 | // TODO(bunnei): We don't implement the Mii database, so we can't have an index |
| 694 | return ERROR_CANNOT_FIND_ENTRY; | 165 | return ResultNotFound; |
| 166 | } | ||
| 167 | |||
| 168 | void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) { | ||
| 169 | metadata.interface_version = version; | ||
| 695 | } | 170 | } |
| 696 | 171 | ||
| 697 | } // namespace Service::Mii | 172 | } // namespace Service::Mii |
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 45c2be3c8..a2e7a6d73 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h | |||
| @@ -6,7 +6,10 @@ | |||
| 6 | #include <vector> | 6 | #include <vector> |
| 7 | 7 | ||
| 8 | #include "core/hle/result.h" | 8 | #include "core/hle/result.h" |
| 9 | #include "core/hle/service/mii/types.h" | 9 | #include "core/hle/service/mii/mii_types.h" |
| 10 | #include "core/hle/service/mii/types/char_info.h" | ||
| 11 | #include "core/hle/service/mii/types/store_data.h" | ||
| 12 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 10 | 13 | ||
| 11 | namespace Service::Mii { | 14 | namespace Service::Mii { |
| 12 | 15 | ||
| @@ -16,25 +19,30 @@ class MiiManager { | |||
| 16 | public: | 19 | public: |
| 17 | MiiManager(); | 20 | MiiManager(); |
| 18 | 21 | ||
| 19 | bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); | 22 | bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; |
| 20 | bool IsFullDatabase() const; | ||
| 21 | u32 GetCount(SourceFlag source_flag) const; | ||
| 22 | Result UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag); | ||
| 23 | CharInfo BuildRandom(Age age, Gender gender, Race race); | ||
| 24 | CharInfo BuildDefault(std::size_t index); | ||
| 25 | CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const; | ||
| 26 | bool ValidateV3Info(const Ver3StoreData& mii_v3) const; | ||
| 27 | std::vector<MiiInfoElement> GetDefault(SourceFlag source_flag); | ||
| 28 | Result GetIndex(const CharInfo& info, u32& index); | ||
| 29 | |||
| 30 | // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData | ||
| 31 | Ver3StoreData BuildFromStoreData(const CharInfo& mii) const; | ||
| 32 | 23 | ||
| 33 | // This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData | 24 | bool IsFullDatabase() const; |
| 34 | NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const; | 25 | u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; |
| 26 | Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, | ||
| 27 | const CharInfo& char_info, SourceFlag source_flag); | ||
| 28 | Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, | ||
| 29 | u32& out_count, SourceFlag source_flag); | ||
| 30 | Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, | ||
| 31 | u32& out_count, SourceFlag source_flag); | ||
| 32 | void BuildDefault(CharInfo& out_char_info, u32 index) const; | ||
| 33 | void BuildBase(CharInfo& out_char_info, Gender gender) const; | ||
| 34 | void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; | ||
| 35 | void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; | ||
| 36 | std::vector<CharInfoElement> GetDefault(SourceFlag source_flag); | ||
| 37 | Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, | ||
| 38 | s32& out_index); | ||
| 39 | void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version); | ||
| 35 | 40 | ||
| 36 | private: | 41 | private: |
| 37 | const Common::UUID user_id{}; | 42 | Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, |
| 43 | SourceFlag source_flag); | ||
| 44 | Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag); | ||
| 45 | |||
| 38 | u64 update_counter{}; | 46 | u64 update_counter{}; |
| 39 | }; | 47 | }; |
| 40 | 48 | ||
diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h new file mode 100644 index 000000000..021cb76da --- /dev/null +++ b/src/core/hle/service/mii/mii_result.h | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/result.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | |||
| 10 | constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1}; | ||
| 11 | constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2}; | ||
| 12 | constexpr Result ResultNotUpdated{ErrorModule::Mii, 3}; | ||
| 13 | constexpr Result ResultNotFound{ErrorModule::Mii, 4}; | ||
| 14 | constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; | ||
| 15 | constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; | ||
| 16 | constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; | ||
| 17 | constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; | ||
| 18 | constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; | ||
| 19 | |||
| 20 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h new file mode 100644 index 000000000..611ff4f81 --- /dev/null +++ b/src/core/hle/service/mii/mii_types.h | |||
| @@ -0,0 +1,691 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <type_traits> | ||
| 8 | |||
| 9 | #include "common/bit_field.h" | ||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/uuid.h" | ||
| 13 | |||
| 14 | namespace Service::Mii { | ||
| 15 | |||
| 16 | constexpr u8 MaxHeight = 127; | ||
| 17 | constexpr u8 MaxBuild = 127; | ||
| 18 | constexpr u8 MaxType = 1; | ||
| 19 | constexpr u8 MaxRegionMove = 3; | ||
| 20 | constexpr u8 MaxEyeScale = 7; | ||
| 21 | constexpr u8 MaxEyeAspect = 6; | ||
| 22 | constexpr u8 MaxEyeRotate = 7; | ||
| 23 | constexpr u8 MaxEyeX = 12; | ||
| 24 | constexpr u8 MaxEyeY = 18; | ||
| 25 | constexpr u8 MaxEyebrowScale = 8; | ||
| 26 | constexpr u8 MaxEyebrowAspect = 6; | ||
| 27 | constexpr u8 MaxEyebrowRotate = 11; | ||
| 28 | constexpr u8 MaxEyebrowX = 12; | ||
| 29 | constexpr u8 MaxEyebrowY = 18; | ||
| 30 | constexpr u8 MaxNoseScale = 8; | ||
| 31 | constexpr u8 MaxNoseY = 18; | ||
| 32 | constexpr u8 MaxMouthScale = 8; | ||
| 33 | constexpr u8 MaxMoutAspect = 6; | ||
| 34 | constexpr u8 MaxMouthY = 18; | ||
| 35 | constexpr u8 MaxMustacheScale = 8; | ||
| 36 | constexpr u8 MasMustacheY = 16; | ||
| 37 | constexpr u8 MaxGlassScale = 7; | ||
| 38 | constexpr u8 MaxGlassY = 20; | ||
| 39 | constexpr u8 MaxMoleScale = 8; | ||
| 40 | constexpr u8 MaxMoleX = 16; | ||
| 41 | constexpr u8 MaxMoleY = 30; | ||
| 42 | constexpr u8 MaxVer3CommonColor = 7; | ||
| 43 | constexpr u8 MaxVer3GlassType = 8; | ||
| 44 | |||
| 45 | enum class Age : u8 { | ||
| 46 | Young, | ||
| 47 | Normal, | ||
| 48 | Old, | ||
| 49 | All, // Default | ||
| 50 | |||
| 51 | Max = All, | ||
| 52 | }; | ||
| 53 | |||
| 54 | enum class Gender : u8 { | ||
| 55 | Male, | ||
| 56 | Female, | ||
| 57 | All, // Default | ||
| 58 | |||
| 59 | Max = Female, | ||
| 60 | }; | ||
| 61 | |||
| 62 | enum class Race : u8 { | ||
| 63 | Black, | ||
| 64 | White, | ||
| 65 | Asian, | ||
| 66 | All, // Default | ||
| 67 | |||
| 68 | Max = All, | ||
| 69 | }; | ||
| 70 | |||
| 71 | enum class HairType : u8 { | ||
| 72 | NormalLong, // Default | ||
| 73 | NormalShort, | ||
| 74 | NormalMedium, | ||
| 75 | NormalExtraLong, | ||
| 76 | NormalLongBottom, | ||
| 77 | NormalTwoPeaks, | ||
| 78 | PartingLong, | ||
| 79 | FrontLock, | ||
| 80 | PartingShort, | ||
| 81 | PartingExtraLongCurved, | ||
| 82 | PartingExtraLong, | ||
| 83 | PartingMiddleLong, | ||
| 84 | PartingSquared, | ||
| 85 | PartingLongBottom, | ||
| 86 | PeaksTop, | ||
| 87 | PeaksSquared, | ||
| 88 | PartingPeaks, | ||
| 89 | PeaksLongBottom, | ||
| 90 | Peaks, | ||
| 91 | PeaksRounded, | ||
| 92 | PeaksSide, | ||
| 93 | PeaksMedium, | ||
| 94 | PeaksLong, | ||
| 95 | PeaksRoundedLong, | ||
| 96 | PartingFrontPeaks, | ||
| 97 | PartingLongFront, | ||
| 98 | PartingLongRounded, | ||
| 99 | PartingFrontPeaksLong, | ||
| 100 | PartingExtraLongRounded, | ||
| 101 | LongRounded, | ||
| 102 | NormalUnknown1, | ||
| 103 | NormalUnknown2, | ||
| 104 | NormalUnknown3, | ||
| 105 | NormalUnknown4, | ||
| 106 | NormalUnknown5, | ||
| 107 | NormalUnknown6, | ||
| 108 | DreadLocks, | ||
| 109 | PlatedMats, | ||
| 110 | Caps, | ||
| 111 | Afro, | ||
| 112 | PlatedMatsLong, | ||
| 113 | Beanie, | ||
| 114 | Short, | ||
| 115 | ShortTopLongSide, | ||
| 116 | ShortUnknown1, | ||
| 117 | ShortUnknown2, | ||
| 118 | MilitaryParting, | ||
| 119 | Military, | ||
| 120 | ShortUnknown3, | ||
| 121 | ShortUnknown4, | ||
| 122 | ShortUnknown5, | ||
| 123 | ShortUnknown6, | ||
| 124 | NoneTop, | ||
| 125 | None, | ||
| 126 | LongUnknown1, | ||
| 127 | LongUnknown2, | ||
| 128 | LongUnknown3, | ||
| 129 | LongUnknown4, | ||
| 130 | LongUnknown5, | ||
| 131 | LongUnknown6, | ||
| 132 | LongUnknown7, | ||
| 133 | LongUnknown8, | ||
| 134 | LongUnknown9, | ||
| 135 | LongUnknown10, | ||
| 136 | LongUnknown11, | ||
| 137 | LongUnknown12, | ||
| 138 | LongUnknown13, | ||
| 139 | LongUnknown14, | ||
| 140 | LongUnknown15, | ||
| 141 | LongUnknown16, | ||
| 142 | LongUnknown17, | ||
| 143 | LongUnknown18, | ||
| 144 | LongUnknown19, | ||
| 145 | LongUnknown20, | ||
| 146 | LongUnknown21, | ||
| 147 | LongUnknown22, | ||
| 148 | LongUnknown23, | ||
| 149 | LongUnknown24, | ||
| 150 | LongUnknown25, | ||
| 151 | LongUnknown26, | ||
| 152 | LongUnknown27, | ||
| 153 | LongUnknown28, | ||
| 154 | LongUnknown29, | ||
| 155 | LongUnknown30, | ||
| 156 | LongUnknown31, | ||
| 157 | LongUnknown32, | ||
| 158 | LongUnknown33, | ||
| 159 | LongUnknown34, | ||
| 160 | LongUnknown35, | ||
| 161 | LongUnknown36, | ||
| 162 | LongUnknown37, | ||
| 163 | LongUnknown38, | ||
| 164 | LongUnknown39, | ||
| 165 | LongUnknown40, | ||
| 166 | LongUnknown41, | ||
| 167 | LongUnknown42, | ||
| 168 | LongUnknown43, | ||
| 169 | LongUnknown44, | ||
| 170 | LongUnknown45, | ||
| 171 | LongUnknown46, | ||
| 172 | LongUnknown47, | ||
| 173 | LongUnknown48, | ||
| 174 | LongUnknown49, | ||
| 175 | LongUnknown50, | ||
| 176 | LongUnknown51, | ||
| 177 | LongUnknown52, | ||
| 178 | LongUnknown53, | ||
| 179 | LongUnknown54, | ||
| 180 | LongUnknown55, | ||
| 181 | LongUnknown56, | ||
| 182 | LongUnknown57, | ||
| 183 | LongUnknown58, | ||
| 184 | LongUnknown59, | ||
| 185 | LongUnknown60, | ||
| 186 | LongUnknown61, | ||
| 187 | LongUnknown62, | ||
| 188 | LongUnknown63, | ||
| 189 | LongUnknown64, | ||
| 190 | LongUnknown65, | ||
| 191 | LongUnknown66, | ||
| 192 | TwoMediumFrontStrandsOneLongBackPonyTail, | ||
| 193 | TwoFrontStrandsLongBackPonyTail, | ||
| 194 | PartingFrontTwoLongBackPonyTails, | ||
| 195 | TwoFrontStrandsOneLongBackPonyTail, | ||
| 196 | LongBackPonyTail, | ||
| 197 | LongFrontTwoLongBackPonyTails, | ||
| 198 | StrandsTwoShortSidedPonyTails, | ||
| 199 | TwoMediumSidedPonyTails, | ||
| 200 | ShortFrontTwoBackPonyTails, | ||
| 201 | TwoShortSidedPonyTails, | ||
| 202 | TwoLongSidedPonyTails, | ||
| 203 | LongFrontTwoBackPonyTails, | ||
| 204 | |||
| 205 | Max = LongFrontTwoBackPonyTails, | ||
| 206 | }; | ||
| 207 | |||
| 208 | enum class MoleType : u8 { | ||
| 209 | None, // Default | ||
| 210 | OneDot, | ||
| 211 | |||
| 212 | Max = OneDot, | ||
| 213 | }; | ||
| 214 | |||
| 215 | enum class HairFlip : u8 { | ||
| 216 | Left, // Default | ||
| 217 | Right, | ||
| 218 | |||
| 219 | Max = Right, | ||
| 220 | }; | ||
| 221 | |||
| 222 | enum class CommonColor : u8 { | ||
| 223 | // For simplicity common colors aren't listed | ||
| 224 | Max = 99, | ||
| 225 | Count = 100, | ||
| 226 | }; | ||
| 227 | |||
| 228 | enum class FavoriteColor : u8 { | ||
| 229 | Red, // Default | ||
| 230 | Orange, | ||
| 231 | Yellow, | ||
| 232 | LimeGreen, | ||
| 233 | Green, | ||
| 234 | Blue, | ||
| 235 | LightBlue, | ||
| 236 | Pink, | ||
| 237 | Purple, | ||
| 238 | Brown, | ||
| 239 | White, | ||
| 240 | Black, | ||
| 241 | |||
| 242 | Max = Black, | ||
| 243 | }; | ||
| 244 | |||
| 245 | enum class EyeType : u8 { | ||
| 246 | Normal, // Default | ||
| 247 | NormalLash, | ||
| 248 | WhiteLash, | ||
| 249 | WhiteNoBottom, | ||
| 250 | OvalAngledWhite, | ||
| 251 | AngryWhite, | ||
| 252 | DotLashType1, | ||
| 253 | Line, | ||
| 254 | DotLine, | ||
| 255 | OvalWhite, | ||
| 256 | RoundedWhite, | ||
| 257 | NormalShadow, | ||
| 258 | CircleWhite, | ||
| 259 | Circle, | ||
| 260 | CircleWhiteStroke, | ||
| 261 | NormalOvalNoBottom, | ||
| 262 | NormalOvalLarge, | ||
| 263 | NormalRoundedNoBottom, | ||
| 264 | SmallLash, | ||
| 265 | Small, | ||
| 266 | TwoSmall, | ||
| 267 | NormalLongLash, | ||
| 268 | WhiteTwoLashes, | ||
| 269 | WhiteThreeLashes, | ||
| 270 | DotAngry, | ||
| 271 | DotAngled, | ||
| 272 | Oval, | ||
| 273 | SmallWhite, | ||
| 274 | WhiteAngledNoBottom, | ||
| 275 | WhiteAngledNoLeft, | ||
| 276 | SmallWhiteTwoLashes, | ||
| 277 | LeafWhiteLash, | ||
| 278 | WhiteLargeNoBottom, | ||
| 279 | Dot, | ||
| 280 | DotLashType2, | ||
| 281 | DotThreeLashes, | ||
| 282 | WhiteOvalTop, | ||
| 283 | WhiteOvalBottom, | ||
| 284 | WhiteOvalBottomFlat, | ||
| 285 | WhiteOvalTwoLashes, | ||
| 286 | WhiteOvalThreeLashes, | ||
| 287 | WhiteOvalNoBottomTwoLashes, | ||
| 288 | DotWhite, | ||
| 289 | WhiteOvalTopFlat, | ||
| 290 | WhiteThinLeaf, | ||
| 291 | StarThreeLashes, | ||
| 292 | LineTwoLashes, | ||
| 293 | CrowsFeet, | ||
| 294 | WhiteNoBottomFlat, | ||
| 295 | WhiteNoBottomRounded, | ||
| 296 | WhiteSmallBottomLine, | ||
| 297 | WhiteNoBottomLash, | ||
| 298 | WhiteNoPartialBottomLash, | ||
| 299 | WhiteOvalBottomLine, | ||
| 300 | WhiteNoBottomLashTopLine, | ||
| 301 | WhiteNoPartialBottomTwoLashes, | ||
| 302 | NormalTopLine, | ||
| 303 | WhiteOvalLash, | ||
| 304 | RoundTired, | ||
| 305 | WhiteLarge, | ||
| 306 | |||
| 307 | Max = WhiteLarge, | ||
| 308 | }; | ||
| 309 | |||
| 310 | enum class MouthType : u8 { | ||
| 311 | Neutral, // Default | ||
| 312 | NeutralLips, | ||
| 313 | Smile, | ||
| 314 | SmileStroke, | ||
| 315 | SmileTeeth, | ||
| 316 | LipsSmall, | ||
| 317 | LipsLarge, | ||
| 318 | Wave, | ||
| 319 | WaveAngrySmall, | ||
| 320 | NeutralStrokeLarge, | ||
| 321 | TeethSurprised, | ||
| 322 | LipsExtraLarge, | ||
| 323 | LipsUp, | ||
| 324 | NeutralDown, | ||
| 325 | Surprised, | ||
| 326 | TeethMiddle, | ||
| 327 | NeutralStroke, | ||
| 328 | LipsExtraSmall, | ||
| 329 | Malicious, | ||
| 330 | LipsDual, | ||
| 331 | NeutralComma, | ||
| 332 | NeutralUp, | ||
| 333 | TeethLarge, | ||
| 334 | WaveAngry, | ||
| 335 | LipsSexy, | ||
| 336 | SmileInverted, | ||
| 337 | LipsSexyOutline, | ||
| 338 | SmileRounded, | ||
| 339 | LipsTeeth, | ||
| 340 | NeutralOpen, | ||
| 341 | TeethRounded, | ||
| 342 | WaveAngrySmallInverted, | ||
| 343 | NeutralCommaInverted, | ||
| 344 | TeethFull, | ||
| 345 | SmileDownLine, | ||
| 346 | Kiss, | ||
| 347 | |||
| 348 | Max = Kiss, | ||
| 349 | }; | ||
| 350 | |||
| 351 | enum class FontRegion : u8 { | ||
| 352 | Standard, // Default | ||
| 353 | China, | ||
| 354 | Korea, | ||
| 355 | Taiwan, | ||
| 356 | |||
| 357 | Max = Taiwan, | ||
| 358 | }; | ||
| 359 | |||
| 360 | enum class FacelineType : u8 { | ||
| 361 | Sharp, // Default | ||
| 362 | Rounded, | ||
| 363 | SharpRounded, | ||
| 364 | SharpRoundedSmall, | ||
| 365 | Large, | ||
| 366 | LargeRounded, | ||
| 367 | SharpSmall, | ||
| 368 | Flat, | ||
| 369 | Bump, | ||
| 370 | Angular, | ||
| 371 | FlatRounded, | ||
| 372 | AngularSmall, | ||
| 373 | |||
| 374 | Max = AngularSmall, | ||
| 375 | }; | ||
| 376 | |||
| 377 | enum class FacelineColor : u8 { | ||
| 378 | Beige, // Default | ||
| 379 | WarmBeige, | ||
| 380 | Natural, | ||
| 381 | Honey, | ||
| 382 | Chestnut, | ||
| 383 | Porcelain, | ||
| 384 | Ivory, | ||
| 385 | WarmIvory, | ||
| 386 | Almond, | ||
| 387 | Espresso, | ||
| 388 | |||
| 389 | Max = Espresso, | ||
| 390 | Count = Max + 1, | ||
| 391 | }; | ||
| 392 | |||
| 393 | enum class FacelineWrinkle : u8 { | ||
| 394 | None, // Default | ||
| 395 | TearTroughs, | ||
| 396 | FacialPain, | ||
| 397 | Cheeks, | ||
| 398 | Folds, | ||
| 399 | UnderTheEyes, | ||
| 400 | SplitChin, | ||
| 401 | Chin, | ||
| 402 | BrowDroop, | ||
| 403 | MouthFrown, | ||
| 404 | CrowsFeet, | ||
| 405 | FoldsCrowsFrown, | ||
| 406 | |||
| 407 | Max = FoldsCrowsFrown, | ||
| 408 | }; | ||
| 409 | |||
| 410 | enum class FacelineMake : u8 { | ||
| 411 | None, // Default | ||
| 412 | CheekPorcelain, | ||
| 413 | CheekNatural, | ||
| 414 | EyeShadowBlue, | ||
| 415 | CheekBlushPorcelain, | ||
| 416 | CheekBlushNatural, | ||
| 417 | CheekPorcelainEyeShadowBlue, | ||
| 418 | CheekPorcelainEyeShadowNatural, | ||
| 419 | CheekBlushPorcelainEyeShadowEspresso, | ||
| 420 | Freckles, | ||
| 421 | LionsManeBeard, | ||
| 422 | StubbleBeard, | ||
| 423 | |||
| 424 | Max = StubbleBeard, | ||
| 425 | }; | ||
| 426 | |||
| 427 | enum class EyebrowType : u8 { | ||
| 428 | FlatAngledLarge, // Default | ||
| 429 | LowArchRoundedThin, | ||
| 430 | SoftAngledLarge, | ||
| 431 | MediumArchRoundedThin, | ||
| 432 | RoundedMedium, | ||
| 433 | LowArchMedium, | ||
| 434 | RoundedThin, | ||
| 435 | UpThin, | ||
| 436 | MediumArchRoundedMedium, | ||
| 437 | RoundedLarge, | ||
| 438 | UpLarge, | ||
| 439 | FlatAngledLargeInverted, | ||
| 440 | MediumArchFlat, | ||
| 441 | AngledThin, | ||
| 442 | HorizontalLarge, | ||
| 443 | HighArchFlat, | ||
| 444 | Flat, | ||
| 445 | MediumArchLarge, | ||
| 446 | LowArchThin, | ||
| 447 | RoundedThinInverted, | ||
| 448 | HighArchLarge, | ||
| 449 | Hairy, | ||
| 450 | Dotted, | ||
| 451 | None, | ||
| 452 | |||
| 453 | Max = None, | ||
| 454 | }; | ||
| 455 | |||
| 456 | enum class NoseType : u8 { | ||
| 457 | Normal, // Default | ||
| 458 | Rounded, | ||
| 459 | Dot, | ||
| 460 | Arrow, | ||
| 461 | Roman, | ||
| 462 | Triangle, | ||
| 463 | Button, | ||
| 464 | RoundedInverted, | ||
| 465 | Potato, | ||
| 466 | Grecian, | ||
| 467 | Snub, | ||
| 468 | Aquiline, | ||
| 469 | ArrowLeft, | ||
| 470 | RoundedLarge, | ||
| 471 | Hooked, | ||
| 472 | Fat, | ||
| 473 | Droopy, | ||
| 474 | ArrowLarge, | ||
| 475 | |||
| 476 | Max = ArrowLarge, | ||
| 477 | }; | ||
| 478 | |||
| 479 | enum class BeardType : u8 { | ||
| 480 | None, | ||
| 481 | Goatee, | ||
| 482 | GoateeLong, | ||
| 483 | LionsManeLong, | ||
| 484 | LionsMane, | ||
| 485 | Full, | ||
| 486 | |||
| 487 | Min = Goatee, | ||
| 488 | Max = Full, | ||
| 489 | }; | ||
| 490 | |||
| 491 | enum class MustacheType : u8 { | ||
| 492 | None, | ||
| 493 | Walrus, | ||
| 494 | Pencil, | ||
| 495 | Horseshoe, | ||
| 496 | Normal, | ||
| 497 | Toothbrush, | ||
| 498 | |||
| 499 | Min = Walrus, | ||
| 500 | Max = Toothbrush, | ||
| 501 | }; | ||
| 502 | |||
| 503 | enum class GlassType : u8 { | ||
| 504 | None, | ||
| 505 | Oval, | ||
| 506 | Wayfarer, | ||
| 507 | Rectangle, | ||
| 508 | TopRimless, | ||
| 509 | Rounded, | ||
| 510 | Oversized, | ||
| 511 | CatEye, | ||
| 512 | Square, | ||
| 513 | BottomRimless, | ||
| 514 | SemiOpaqueRounded, | ||
| 515 | SemiOpaqueCatEye, | ||
| 516 | SemiOpaqueOval, | ||
| 517 | SemiOpaqueRectangle, | ||
| 518 | SemiOpaqueAviator, | ||
| 519 | OpaqueRounded, | ||
| 520 | OpaqueCatEye, | ||
| 521 | OpaqueOval, | ||
| 522 | OpaqueRectangle, | ||
| 523 | OpaqueAviator, | ||
| 524 | |||
| 525 | Max = OpaqueAviator, | ||
| 526 | Count = Max + 1, | ||
| 527 | }; | ||
| 528 | |||
| 529 | enum class BeardAndMustacheFlag : u32 { | ||
| 530 | Beard = 1, | ||
| 531 | Mustache, | ||
| 532 | All = Beard | Mustache, | ||
| 533 | }; | ||
| 534 | DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); | ||
| 535 | |||
| 536 | enum class Source : u32 { | ||
| 537 | Database = 0, | ||
| 538 | Default = 1, | ||
| 539 | Account = 2, | ||
| 540 | Friend = 3, | ||
| 541 | }; | ||
| 542 | |||
| 543 | enum class SourceFlag : u32 { | ||
| 544 | None = 0, | ||
| 545 | Database = 1 << 0, | ||
| 546 | Default = 1 << 1, | ||
| 547 | }; | ||
| 548 | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); | ||
| 549 | |||
| 550 | enum class ValidationResult : u32 { | ||
| 551 | NoErrors = 0x0, | ||
| 552 | InvalidBeardColor = 0x1, | ||
| 553 | InvalidBeardType = 0x2, | ||
| 554 | InvalidBuild = 0x3, | ||
| 555 | InvalidEyeAspect = 0x4, | ||
| 556 | InvalidEyeColor = 0x5, | ||
| 557 | InvalidEyeRotate = 0x6, | ||
| 558 | InvalidEyeScale = 0x7, | ||
| 559 | InvalidEyeType = 0x8, | ||
| 560 | InvalidEyeX = 0x9, | ||
| 561 | InvalidEyeY = 0xa, | ||
| 562 | InvalidEyebrowAspect = 0xb, | ||
| 563 | InvalidEyebrowColor = 0xc, | ||
| 564 | InvalidEyebrowRotate = 0xd, | ||
| 565 | InvalidEyebrowScale = 0xe, | ||
| 566 | InvalidEyebrowType = 0xf, | ||
| 567 | InvalidEyebrowX = 0x10, | ||
| 568 | InvalidEyebrowY = 0x11, | ||
| 569 | InvalidFacelineColor = 0x12, | ||
| 570 | InvalidFacelineMake = 0x13, | ||
| 571 | InvalidFacelineWrinkle = 0x14, | ||
| 572 | InvalidFacelineType = 0x15, | ||
| 573 | InvalidColor = 0x16, | ||
| 574 | InvalidFont = 0x17, | ||
| 575 | InvalidGender = 0x18, | ||
| 576 | InvalidGlassColor = 0x19, | ||
| 577 | InvalidGlassScale = 0x1a, | ||
| 578 | InvalidGlassType = 0x1b, | ||
| 579 | InvalidGlassY = 0x1c, | ||
| 580 | InvalidHairColor = 0x1d, | ||
| 581 | InvalidHairFlip = 0x1e, | ||
| 582 | InvalidHairType = 0x1f, | ||
| 583 | InvalidHeight = 0x20, | ||
| 584 | InvalidMoleScale = 0x21, | ||
| 585 | InvalidMoleType = 0x22, | ||
| 586 | InvalidMoleX = 0x23, | ||
| 587 | InvalidMoleY = 0x24, | ||
| 588 | InvalidMouthAspect = 0x25, | ||
| 589 | InvalidMouthColor = 0x26, | ||
| 590 | InvalidMouthScale = 0x27, | ||
| 591 | InvalidMouthType = 0x28, | ||
| 592 | InvalidMouthY = 0x29, | ||
| 593 | InvalidMustacheScale = 0x2a, | ||
| 594 | InvalidMustacheType = 0x2b, | ||
| 595 | InvalidMustacheY = 0x2c, | ||
| 596 | InvalidNoseScale = 0x2e, | ||
| 597 | InvalidNoseType = 0x2f, | ||
| 598 | InvalidNoseY = 0x30, | ||
| 599 | InvalidRegionMove = 0x31, | ||
| 600 | InvalidCreateId = 0x32, | ||
| 601 | InvalidName = 0x33, | ||
| 602 | InvalidType = 0x35, | ||
| 603 | }; | ||
| 604 | |||
| 605 | struct Nickname { | ||
| 606 | static constexpr std::size_t MaxNameSize = 10; | ||
| 607 | std::array<char16_t, MaxNameSize> data; | ||
| 608 | |||
| 609 | // Checks for null or dirty strings | ||
| 610 | bool IsValid() const { | ||
| 611 | if (data[0] == 0) { | ||
| 612 | return false; | ||
| 613 | } | ||
| 614 | |||
| 615 | std::size_t index = 1; | ||
| 616 | while (data[index] != 0) { | ||
| 617 | index++; | ||
| 618 | } | ||
| 619 | while (index < MaxNameSize && data[index] == 0) { | ||
| 620 | index++; | ||
| 621 | } | ||
| 622 | return index == MaxNameSize; | ||
| 623 | } | ||
| 624 | }; | ||
| 625 | static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size"); | ||
| 626 | |||
| 627 | struct DefaultMii { | ||
| 628 | u32 face_type{}; | ||
| 629 | u32 face_color{}; | ||
| 630 | u32 face_wrinkle{}; | ||
| 631 | u32 face_makeup{}; | ||
| 632 | u32 hair_type{}; | ||
| 633 | u32 hair_color{}; | ||
| 634 | u32 hair_flip{}; | ||
| 635 | u32 eye_type{}; | ||
| 636 | u32 eye_color{}; | ||
| 637 | u32 eye_scale{}; | ||
| 638 | u32 eye_aspect{}; | ||
| 639 | u32 eye_rotate{}; | ||
| 640 | u32 eye_x{}; | ||
| 641 | u32 eye_y{}; | ||
| 642 | u32 eyebrow_type{}; | ||
| 643 | u32 eyebrow_color{}; | ||
| 644 | u32 eyebrow_scale{}; | ||
| 645 | u32 eyebrow_aspect{}; | ||
| 646 | u32 eyebrow_rotate{}; | ||
| 647 | u32 eyebrow_x{}; | ||
| 648 | u32 eyebrow_y{}; | ||
| 649 | u32 nose_type{}; | ||
| 650 | u32 nose_scale{}; | ||
| 651 | u32 nose_y{}; | ||
| 652 | u32 mouth_type{}; | ||
| 653 | u32 mouth_color{}; | ||
| 654 | u32 mouth_scale{}; | ||
| 655 | u32 mouth_aspect{}; | ||
| 656 | u32 mouth_y{}; | ||
| 657 | u32 mustache_type{}; | ||
| 658 | u32 beard_type{}; | ||
| 659 | u32 beard_color{}; | ||
| 660 | u32 mustache_scale{}; | ||
| 661 | u32 mustache_y{}; | ||
| 662 | u32 glasses_type{}; | ||
| 663 | u32 glasses_color{}; | ||
| 664 | u32 glasses_scale{}; | ||
| 665 | u32 glasses_y{}; | ||
| 666 | u32 mole_type{}; | ||
| 667 | u32 mole_scale{}; | ||
| 668 | u32 mole_x{}; | ||
| 669 | u32 mole_y{}; | ||
| 670 | u32 height{}; | ||
| 671 | u32 weight{}; | ||
| 672 | u32 gender{}; | ||
| 673 | u32 favorite_color{}; | ||
| 674 | u32 region_move{}; | ||
| 675 | u32 font_region{}; | ||
| 676 | u32 type{}; | ||
| 677 | Nickname nickname; | ||
| 678 | }; | ||
| 679 | static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size."); | ||
| 680 | |||
| 681 | struct DatabaseSessionMetadata { | ||
| 682 | u32 interface_version; | ||
| 683 | u32 magic; | ||
| 684 | u64 update_counter; | ||
| 685 | |||
| 686 | bool IsInterfaceVersionSupported(u32 version) const { | ||
| 687 | return version <= interface_version; | ||
| 688 | } | ||
| 689 | }; | ||
| 690 | |||
| 691 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h new file mode 100644 index 000000000..ddb544c23 --- /dev/null +++ b/src/core/hle/service/mii/mii_util.h | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <random> | ||
| 7 | #include <span> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "common/swap.h" | ||
| 11 | #include "common/uuid.h" | ||
| 12 | #include "core/hle/service/mii/mii_types.h" | ||
| 13 | |||
| 14 | namespace Service::Mii { | ||
| 15 | class MiiUtil { | ||
| 16 | public: | ||
| 17 | static u16 CalculateCrc16(const void* data, std::size_t size) { | ||
| 18 | s32 crc{}; | ||
| 19 | for (std::size_t i = 0; i < size; i++) { | ||
| 20 | crc ^= static_cast<const u8*>(data)[i] << 8; | ||
| 21 | for (std::size_t j = 0; j < 8; j++) { | ||
| 22 | crc <<= 1; | ||
| 23 | if ((crc & 0x10000) != 0) { | ||
| 24 | crc = (crc ^ 0x1021) & 0xFFFF; | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
| 28 | return Common::swap16(static_cast<u16>(crc)); | ||
| 29 | } | ||
| 30 | |||
| 31 | static Common::UUID MakeCreateId() { | ||
| 32 | return Common::UUID::MakeRandomRFC4122V4(); | ||
| 33 | } | ||
| 34 | |||
| 35 | static Common::UUID GetDeviceId() { | ||
| 36 | // This should be nn::settings::detail::GetMiiAuthorId() | ||
| 37 | return Common::UUID::MakeDefault(); | ||
| 38 | } | ||
| 39 | |||
| 40 | template <typename T> | ||
| 41 | static T GetRandomValue(T min, T max) { | ||
| 42 | std::random_device device; | ||
| 43 | std::mt19937 gen(device()); | ||
| 44 | std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), | ||
| 45 | static_cast<u64>(max)); | ||
| 46 | return static_cast<T>(distribution(gen)); | ||
| 47 | } | ||
| 48 | |||
| 49 | template <typename T> | ||
| 50 | static T GetRandomValue(T max) { | ||
| 51 | return GetRandomValue<T>({}, max); | ||
| 52 | } | ||
| 53 | |||
| 54 | static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) { | ||
| 55 | // TODO: This function needs to check against the font tables | ||
| 56 | return true; | ||
| 57 | } | ||
| 58 | }; | ||
| 59 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h deleted file mode 100644 index c2bec68d4..000000000 --- a/src/core/hle/service/mii/raw_data.h +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | |||
| 8 | #include "core/hle/service/mii/types.h" | ||
| 9 | |||
| 10 | namespace Service::Mii::RawData { | ||
| 11 | |||
| 12 | extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii; | ||
| 13 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; | ||
| 14 | extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; | ||
| 15 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; | ||
| 16 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup; | ||
| 17 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType; | ||
| 18 | extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor; | ||
| 19 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType; | ||
| 20 | extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor; | ||
| 21 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType; | ||
| 22 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType; | ||
| 23 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType; | ||
| 24 | extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType; | ||
| 25 | |||
| 26 | } // namespace Service::Mii::RawData | ||
diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h deleted file mode 100644 index c48d08d79..000000000 --- a/src/core/hle/service/mii/types.h +++ /dev/null | |||
| @@ -1,553 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <type_traits> | ||
| 8 | |||
| 9 | #include "common/bit_field.h" | ||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/uuid.h" | ||
| 13 | |||
| 14 | namespace Service::Mii { | ||
| 15 | |||
| 16 | enum class Age : u32 { | ||
| 17 | Young, | ||
| 18 | Normal, | ||
| 19 | Old, | ||
| 20 | All, | ||
| 21 | }; | ||
| 22 | |||
| 23 | enum class BeardType : u32 { | ||
| 24 | None, | ||
| 25 | Beard1, | ||
| 26 | Beard2, | ||
| 27 | Beard3, | ||
| 28 | Beard4, | ||
| 29 | Beard5, | ||
| 30 | }; | ||
| 31 | |||
| 32 | enum class BeardAndMustacheFlag : u32 { | ||
| 33 | Beard = 1, | ||
| 34 | Mustache, | ||
| 35 | All = Beard | Mustache, | ||
| 36 | }; | ||
| 37 | DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); | ||
| 38 | |||
| 39 | enum class FontRegion : u32 { | ||
| 40 | Standard, | ||
| 41 | China, | ||
| 42 | Korea, | ||
| 43 | Taiwan, | ||
| 44 | }; | ||
| 45 | |||
| 46 | enum class Gender : u32 { | ||
| 47 | Male, | ||
| 48 | Female, | ||
| 49 | All, | ||
| 50 | Maximum = Female, | ||
| 51 | }; | ||
| 52 | |||
| 53 | enum class HairFlip : u32 { | ||
| 54 | Left, | ||
| 55 | Right, | ||
| 56 | Maximum = Right, | ||
| 57 | }; | ||
| 58 | |||
| 59 | enum class MustacheType : u32 { | ||
| 60 | None, | ||
| 61 | Mustache1, | ||
| 62 | Mustache2, | ||
| 63 | Mustache3, | ||
| 64 | Mustache4, | ||
| 65 | Mustache5, | ||
| 66 | }; | ||
| 67 | |||
| 68 | enum class Race : u32 { | ||
| 69 | Black, | ||
| 70 | White, | ||
| 71 | Asian, | ||
| 72 | All, | ||
| 73 | }; | ||
| 74 | |||
| 75 | enum class Source : u32 { | ||
| 76 | Database = 0, | ||
| 77 | Default = 1, | ||
| 78 | Account = 2, | ||
| 79 | Friend = 3, | ||
| 80 | }; | ||
| 81 | |||
| 82 | enum class SourceFlag : u32 { | ||
| 83 | None = 0, | ||
| 84 | Database = 1 << 0, | ||
| 85 | Default = 1 << 1, | ||
| 86 | }; | ||
| 87 | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); | ||
| 88 | |||
| 89 | // nn::mii::CharInfo | ||
| 90 | struct CharInfo { | ||
| 91 | Common::UUID uuid; | ||
| 92 | std::array<char16_t, 11> name; | ||
| 93 | u8 font_region; | ||
| 94 | u8 favorite_color; | ||
| 95 | u8 gender; | ||
| 96 | u8 height; | ||
| 97 | u8 build; | ||
| 98 | u8 type; | ||
| 99 | u8 region_move; | ||
| 100 | u8 faceline_type; | ||
| 101 | u8 faceline_color; | ||
| 102 | u8 faceline_wrinkle; | ||
| 103 | u8 faceline_make; | ||
| 104 | u8 hair_type; | ||
| 105 | u8 hair_color; | ||
| 106 | u8 hair_flip; | ||
| 107 | u8 eye_type; | ||
| 108 | u8 eye_color; | ||
| 109 | u8 eye_scale; | ||
| 110 | u8 eye_aspect; | ||
| 111 | u8 eye_rotate; | ||
| 112 | u8 eye_x; | ||
| 113 | u8 eye_y; | ||
| 114 | u8 eyebrow_type; | ||
| 115 | u8 eyebrow_color; | ||
| 116 | u8 eyebrow_scale; | ||
| 117 | u8 eyebrow_aspect; | ||
| 118 | u8 eyebrow_rotate; | ||
| 119 | u8 eyebrow_x; | ||
| 120 | u8 eyebrow_y; | ||
| 121 | u8 nose_type; | ||
| 122 | u8 nose_scale; | ||
| 123 | u8 nose_y; | ||
| 124 | u8 mouth_type; | ||
| 125 | u8 mouth_color; | ||
| 126 | u8 mouth_scale; | ||
| 127 | u8 mouth_aspect; | ||
| 128 | u8 mouth_y; | ||
| 129 | u8 beard_color; | ||
| 130 | u8 beard_type; | ||
| 131 | u8 mustache_type; | ||
| 132 | u8 mustache_scale; | ||
| 133 | u8 mustache_y; | ||
| 134 | u8 glasses_type; | ||
| 135 | u8 glasses_color; | ||
| 136 | u8 glasses_scale; | ||
| 137 | u8 glasses_y; | ||
| 138 | u8 mole_type; | ||
| 139 | u8 mole_scale; | ||
| 140 | u8 mole_x; | ||
| 141 | u8 mole_y; | ||
| 142 | u8 padding; | ||
| 143 | }; | ||
| 144 | static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); | ||
| 145 | static_assert(std::has_unique_object_representations_v<CharInfo>, | ||
| 146 | "All bits of CharInfo must contribute to its value."); | ||
| 147 | |||
| 148 | #pragma pack(push, 4) | ||
| 149 | |||
| 150 | struct MiiInfoElement { | ||
| 151 | MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {} | ||
| 152 | |||
| 153 | CharInfo info{}; | ||
| 154 | Source source{}; | ||
| 155 | }; | ||
| 156 | static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); | ||
| 157 | |||
| 158 | struct MiiStoreBitFields { | ||
| 159 | union { | ||
| 160 | u32 word_0{}; | ||
| 161 | |||
| 162 | BitField<0, 8, u32> hair_type; | ||
| 163 | BitField<8, 7, u32> height; | ||
| 164 | BitField<15, 1, u32> mole_type; | ||
| 165 | BitField<16, 7, u32> build; | ||
| 166 | BitField<23, 1, HairFlip> hair_flip; | ||
| 167 | BitField<24, 7, u32> hair_color; | ||
| 168 | BitField<31, 1, u32> type; | ||
| 169 | }; | ||
| 170 | |||
| 171 | union { | ||
| 172 | u32 word_1{}; | ||
| 173 | |||
| 174 | BitField<0, 7, u32> eye_color; | ||
| 175 | BitField<7, 1, Gender> gender; | ||
| 176 | BitField<8, 7, u32> eyebrow_color; | ||
| 177 | BitField<16, 7, u32> mouth_color; | ||
| 178 | BitField<24, 7, u32> beard_color; | ||
| 179 | }; | ||
| 180 | |||
| 181 | union { | ||
| 182 | u32 word_2{}; | ||
| 183 | |||
| 184 | BitField<0, 7, u32> glasses_color; | ||
| 185 | BitField<8, 6, u32> eye_type; | ||
| 186 | BitField<14, 2, u32> region_move; | ||
| 187 | BitField<16, 6, u32> mouth_type; | ||
| 188 | BitField<22, 2, FontRegion> font_region; | ||
| 189 | BitField<24, 5, u32> eye_y; | ||
| 190 | BitField<29, 3, u32> glasses_scale; | ||
| 191 | }; | ||
| 192 | |||
| 193 | union { | ||
| 194 | u32 word_3{}; | ||
| 195 | |||
| 196 | BitField<0, 5, u32> eyebrow_type; | ||
| 197 | BitField<5, 3, MustacheType> mustache_type; | ||
| 198 | BitField<8, 5, u32> nose_type; | ||
| 199 | BitField<13, 3, BeardType> beard_type; | ||
| 200 | BitField<16, 5, u32> nose_y; | ||
| 201 | BitField<21, 3, u32> mouth_aspect; | ||
| 202 | BitField<24, 5, u32> mouth_y; | ||
| 203 | BitField<29, 3, u32> eyebrow_aspect; | ||
| 204 | }; | ||
| 205 | |||
| 206 | union { | ||
| 207 | u32 word_4{}; | ||
| 208 | |||
| 209 | BitField<0, 5, u32> mustache_y; | ||
| 210 | BitField<5, 3, u32> eye_rotate; | ||
| 211 | BitField<8, 5, u32> glasses_y; | ||
| 212 | BitField<13, 3, u32> eye_aspect; | ||
| 213 | BitField<16, 5, u32> mole_x; | ||
| 214 | BitField<21, 3, u32> eye_scale; | ||
| 215 | BitField<24, 5, u32> mole_y; | ||
| 216 | }; | ||
| 217 | |||
| 218 | union { | ||
| 219 | u32 word_5{}; | ||
| 220 | |||
| 221 | BitField<0, 5, u32> glasses_type; | ||
| 222 | BitField<8, 4, u32> favorite_color; | ||
| 223 | BitField<12, 4, u32> faceline_type; | ||
| 224 | BitField<16, 4, u32> faceline_color; | ||
| 225 | BitField<20, 4, u32> faceline_wrinkle; | ||
| 226 | BitField<24, 4, u32> faceline_makeup; | ||
| 227 | BitField<28, 4, u32> eye_x; | ||
| 228 | }; | ||
| 229 | |||
| 230 | union { | ||
| 231 | u32 word_6{}; | ||
| 232 | |||
| 233 | BitField<0, 4, u32> eyebrow_scale; | ||
| 234 | BitField<4, 4, u32> eyebrow_rotate; | ||
| 235 | BitField<8, 4, u32> eyebrow_x; | ||
| 236 | BitField<12, 4, u32> eyebrow_y; | ||
| 237 | BitField<16, 4, u32> nose_scale; | ||
| 238 | BitField<20, 4, u32> mouth_scale; | ||
| 239 | BitField<24, 4, u32> mustache_scale; | ||
| 240 | BitField<28, 4, u32> mole_scale; | ||
| 241 | }; | ||
| 242 | }; | ||
| 243 | static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); | ||
| 244 | static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, | ||
| 245 | "MiiStoreBitFields is not trivially copyable."); | ||
| 246 | |||
| 247 | // This is nn::mii::Ver3StoreData | ||
| 248 | // Based on citra HLE::Applets::MiiData and PretendoNetwork. | ||
| 249 | // https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 | ||
| 250 | // https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 | ||
| 251 | struct Ver3StoreData { | ||
| 252 | u8 version; | ||
| 253 | union { | ||
| 254 | u8 raw; | ||
| 255 | |||
| 256 | BitField<0, 1, u8> allow_copying; | ||
| 257 | BitField<1, 1, u8> profanity_flag; | ||
| 258 | BitField<2, 2, u8> region_lock; | ||
| 259 | BitField<4, 2, u8> character_set; | ||
| 260 | } region_information; | ||
| 261 | u16_be mii_id; | ||
| 262 | u64_be system_id; | ||
| 263 | u32_be specialness_and_creation_date; | ||
| 264 | std::array<u8, 0x6> creator_mac; | ||
| 265 | u16_be padding; | ||
| 266 | union { | ||
| 267 | u16 raw; | ||
| 268 | |||
| 269 | BitField<0, 1, u16> gender; | ||
| 270 | BitField<1, 4, u16> birth_month; | ||
| 271 | BitField<5, 5, u16> birth_day; | ||
| 272 | BitField<10, 4, u16> favorite_color; | ||
| 273 | BitField<14, 1, u16> favorite; | ||
| 274 | } mii_information; | ||
| 275 | std::array<char16_t, 0xA> mii_name; | ||
| 276 | u8 height; | ||
| 277 | u8 build; | ||
| 278 | union { | ||
| 279 | u8 raw; | ||
| 280 | |||
| 281 | BitField<0, 1, u8> disable_sharing; | ||
| 282 | BitField<1, 4, u8> face_shape; | ||
| 283 | BitField<5, 3, u8> skin_color; | ||
| 284 | } appearance_bits1; | ||
| 285 | union { | ||
| 286 | u8 raw; | ||
| 287 | |||
| 288 | BitField<0, 4, u8> wrinkles; | ||
| 289 | BitField<4, 4, u8> makeup; | ||
| 290 | } appearance_bits2; | ||
| 291 | u8 hair_style; | ||
| 292 | union { | ||
| 293 | u8 raw; | ||
| 294 | |||
| 295 | BitField<0, 3, u8> hair_color; | ||
| 296 | BitField<3, 1, u8> flip_hair; | ||
| 297 | } appearance_bits3; | ||
| 298 | union { | ||
| 299 | u32 raw; | ||
| 300 | |||
| 301 | BitField<0, 6, u32> eye_type; | ||
| 302 | BitField<6, 3, u32> eye_color; | ||
| 303 | BitField<9, 4, u32> eye_scale; | ||
| 304 | BitField<13, 3, u32> eye_vertical_stretch; | ||
| 305 | BitField<16, 5, u32> eye_rotation; | ||
| 306 | BitField<21, 4, u32> eye_spacing; | ||
| 307 | BitField<25, 5, u32> eye_y_position; | ||
| 308 | } appearance_bits4; | ||
| 309 | union { | ||
| 310 | u32 raw; | ||
| 311 | |||
| 312 | BitField<0, 5, u32> eyebrow_style; | ||
| 313 | BitField<5, 3, u32> eyebrow_color; | ||
| 314 | BitField<8, 4, u32> eyebrow_scale; | ||
| 315 | BitField<12, 3, u32> eyebrow_yscale; | ||
| 316 | BitField<16, 4, u32> eyebrow_rotation; | ||
| 317 | BitField<21, 4, u32> eyebrow_spacing; | ||
| 318 | BitField<25, 5, u32> eyebrow_y_position; | ||
| 319 | } appearance_bits5; | ||
| 320 | union { | ||
| 321 | u16 raw; | ||
| 322 | |||
| 323 | BitField<0, 5, u16> nose_type; | ||
| 324 | BitField<5, 4, u16> nose_scale; | ||
| 325 | BitField<9, 5, u16> nose_y_position; | ||
| 326 | } appearance_bits6; | ||
| 327 | union { | ||
| 328 | u16 raw; | ||
| 329 | |||
| 330 | BitField<0, 6, u16> mouth_type; | ||
| 331 | BitField<6, 3, u16> mouth_color; | ||
| 332 | BitField<9, 4, u16> mouth_scale; | ||
| 333 | BitField<13, 3, u16> mouth_horizontal_stretch; | ||
| 334 | } appearance_bits7; | ||
| 335 | union { | ||
| 336 | u8 raw; | ||
| 337 | |||
| 338 | BitField<0, 5, u8> mouth_y_position; | ||
| 339 | BitField<5, 3, u8> mustache_type; | ||
| 340 | } appearance_bits8; | ||
| 341 | u8 allow_copying; | ||
| 342 | union { | ||
| 343 | u16 raw; | ||
| 344 | |||
| 345 | BitField<0, 3, u16> bear_type; | ||
| 346 | BitField<3, 3, u16> facial_hair_color; | ||
| 347 | BitField<6, 4, u16> mustache_scale; | ||
| 348 | BitField<10, 5, u16> mustache_y_position; | ||
| 349 | } appearance_bits9; | ||
| 350 | union { | ||
| 351 | u16 raw; | ||
| 352 | |||
| 353 | BitField<0, 4, u16> glasses_type; | ||
| 354 | BitField<4, 3, u16> glasses_color; | ||
| 355 | BitField<7, 4, u16> glasses_scale; | ||
| 356 | BitField<11, 5, u16> glasses_y_position; | ||
| 357 | } appearance_bits10; | ||
| 358 | union { | ||
| 359 | u16 raw; | ||
| 360 | |||
| 361 | BitField<0, 1, u16> mole_enabled; | ||
| 362 | BitField<1, 4, u16> mole_scale; | ||
| 363 | BitField<5, 5, u16> mole_x_position; | ||
| 364 | BitField<10, 5, u16> mole_y_position; | ||
| 365 | } appearance_bits11; | ||
| 366 | |||
| 367 | std::array<u16_le, 0xA> author_name; | ||
| 368 | INSERT_PADDING_BYTES(0x2); | ||
| 369 | u16_be crc; | ||
| 370 | }; | ||
| 371 | static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); | ||
| 372 | |||
| 373 | struct NfpStoreDataExtension { | ||
| 374 | u8 faceline_color; | ||
| 375 | u8 hair_color; | ||
| 376 | u8 eye_color; | ||
| 377 | u8 eyebrow_color; | ||
| 378 | u8 mouth_color; | ||
| 379 | u8 beard_color; | ||
| 380 | u8 glass_color; | ||
| 381 | u8 glass_type; | ||
| 382 | }; | ||
| 383 | static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); | ||
| 384 | |||
| 385 | constexpr std::array<u8, 0x10> Ver3FacelineColorTable{ | ||
| 386 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, | ||
| 387 | }; | ||
| 388 | |||
| 389 | constexpr std::array<u8, 100> Ver3HairColorTable{ | ||
| 390 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, | ||
| 391 | 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 392 | 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, | ||
| 393 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, | ||
| 394 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, | ||
| 395 | 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, | ||
| 396 | }; | ||
| 397 | |||
| 398 | constexpr std::array<u8, 100> Ver3EyeColorTable{ | ||
| 399 | 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, | ||
| 400 | 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 401 | 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, | ||
| 402 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, | ||
| 403 | 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, | ||
| 404 | 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, | ||
| 405 | }; | ||
| 406 | |||
| 407 | constexpr std::array<u8, 100> Ver3MouthlineColorTable{ | ||
| 408 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, | ||
| 409 | 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, | ||
| 410 | 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, | ||
| 411 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, | ||
| 412 | 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, | ||
| 413 | 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, | ||
| 414 | }; | ||
| 415 | |||
| 416 | constexpr std::array<u8, 100> Ver3GlassColorTable{ | ||
| 417 | 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, | ||
| 418 | 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 419 | 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, | ||
| 420 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, | ||
| 421 | 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, | ||
| 422 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, | ||
| 423 | }; | ||
| 424 | |||
| 425 | constexpr std::array<u8, 20> Ver3GlassTypeTable{ | ||
| 426 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, | ||
| 427 | 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, | ||
| 428 | }; | ||
| 429 | |||
| 430 | struct MiiStoreData { | ||
| 431 | using Name = std::array<char16_t, 10>; | ||
| 432 | |||
| 433 | MiiStoreData(); | ||
| 434 | MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, | ||
| 435 | const Common::UUID& user_id); | ||
| 436 | |||
| 437 | // This corresponds to the above structure MiiStoreBitFields. I did it like this because the | ||
| 438 | // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is | ||
| 439 | // not suitable for our uses. | ||
| 440 | struct { | ||
| 441 | std::array<u8, 0x1C> data{}; | ||
| 442 | static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); | ||
| 443 | |||
| 444 | Name name{}; | ||
| 445 | Common::UUID uuid{}; | ||
| 446 | } data; | ||
| 447 | |||
| 448 | u16 data_crc{}; | ||
| 449 | u16 device_crc{}; | ||
| 450 | }; | ||
| 451 | static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); | ||
| 452 | |||
| 453 | struct MiiStoreDataElement { | ||
| 454 | MiiStoreData data{}; | ||
| 455 | Source source{}; | ||
| 456 | }; | ||
| 457 | static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); | ||
| 458 | |||
| 459 | struct MiiDatabase { | ||
| 460 | u32 magic{}; // 'NFDB' | ||
| 461 | std::array<MiiStoreData, 0x64> miis{}; | ||
| 462 | INSERT_PADDING_BYTES(1); | ||
| 463 | u8 count{}; | ||
| 464 | u16 crc{}; | ||
| 465 | }; | ||
| 466 | static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); | ||
| 467 | |||
| 468 | struct RandomMiiValues { | ||
| 469 | std::array<u8, 0xbc> values{}; | ||
| 470 | }; | ||
| 471 | static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); | ||
| 472 | |||
| 473 | struct RandomMiiData4 { | ||
| 474 | Gender gender{}; | ||
| 475 | Age age{}; | ||
| 476 | Race race{}; | ||
| 477 | u32 values_count{}; | ||
| 478 | std::array<u32, 47> values{}; | ||
| 479 | }; | ||
| 480 | static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); | ||
| 481 | |||
| 482 | struct RandomMiiData3 { | ||
| 483 | u32 arg_1; | ||
| 484 | u32 arg_2; | ||
| 485 | u32 values_count; | ||
| 486 | std::array<u32, 47> values{}; | ||
| 487 | }; | ||
| 488 | static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); | ||
| 489 | |||
| 490 | struct RandomMiiData2 { | ||
| 491 | u32 arg_1; | ||
| 492 | u32 values_count; | ||
| 493 | std::array<u32, 47> values{}; | ||
| 494 | }; | ||
| 495 | static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); | ||
| 496 | |||
| 497 | struct DefaultMii { | ||
| 498 | u32 face_type{}; | ||
| 499 | u32 face_color{}; | ||
| 500 | u32 face_wrinkle{}; | ||
| 501 | u32 face_makeup{}; | ||
| 502 | u32 hair_type{}; | ||
| 503 | u32 hair_color{}; | ||
| 504 | u32 hair_flip{}; | ||
| 505 | u32 eye_type{}; | ||
| 506 | u32 eye_color{}; | ||
| 507 | u32 eye_scale{}; | ||
| 508 | u32 eye_aspect{}; | ||
| 509 | u32 eye_rotate{}; | ||
| 510 | u32 eye_x{}; | ||
| 511 | u32 eye_y{}; | ||
| 512 | u32 eyebrow_type{}; | ||
| 513 | u32 eyebrow_color{}; | ||
| 514 | u32 eyebrow_scale{}; | ||
| 515 | u32 eyebrow_aspect{}; | ||
| 516 | u32 eyebrow_rotate{}; | ||
| 517 | u32 eyebrow_x{}; | ||
| 518 | u32 eyebrow_y{}; | ||
| 519 | u32 nose_type{}; | ||
| 520 | u32 nose_scale{}; | ||
| 521 | u32 nose_y{}; | ||
| 522 | u32 mouth_type{}; | ||
| 523 | u32 mouth_color{}; | ||
| 524 | u32 mouth_scale{}; | ||
| 525 | u32 mouth_aspect{}; | ||
| 526 | u32 mouth_y{}; | ||
| 527 | u32 mustache_type{}; | ||
| 528 | u32 beard_type{}; | ||
| 529 | u32 beard_color{}; | ||
| 530 | u32 mustache_scale{}; | ||
| 531 | u32 mustache_y{}; | ||
| 532 | u32 glasses_type{}; | ||
| 533 | u32 glasses_color{}; | ||
| 534 | u32 glasses_scale{}; | ||
| 535 | u32 glasses_y{}; | ||
| 536 | u32 mole_type{}; | ||
| 537 | u32 mole_scale{}; | ||
| 538 | u32 mole_x{}; | ||
| 539 | u32 mole_y{}; | ||
| 540 | u32 height{}; | ||
| 541 | u32 weight{}; | ||
| 542 | Gender gender{}; | ||
| 543 | u32 favorite_color{}; | ||
| 544 | u32 region{}; | ||
| 545 | FontRegion font_region{}; | ||
| 546 | u32 type{}; | ||
| 547 | INSERT_PADDING_WORDS(5); | ||
| 548 | }; | ||
| 549 | static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); | ||
| 550 | |||
| 551 | #pragma pack(pop) | ||
| 552 | |||
| 553 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp new file mode 100644 index 000000000..bb948c628 --- /dev/null +++ b/src/core/hle/service/mii/types/char_info.cpp | |||
| @@ -0,0 +1,482 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/hle/service/mii/types/char_info.h" | ||
| 5 | #include "core/hle/service/mii/types/store_data.h" | ||
| 6 | |||
| 7 | namespace Service::Mii { | ||
| 8 | |||
| 9 | void CharInfo::SetFromStoreData(const StoreData& store_data) { | ||
| 10 | name = store_data.GetNickname(); | ||
| 11 | null_terminator = '\0'; | ||
| 12 | create_id = store_data.GetCreateId(); | ||
| 13 | font_region = store_data.GetFontRegion(); | ||
| 14 | favorite_color = store_data.GetFavoriteColor(); | ||
| 15 | gender = store_data.GetGender(); | ||
| 16 | height = store_data.GetHeight(); | ||
| 17 | build = store_data.GetBuild(); | ||
| 18 | type = store_data.GetType(); | ||
| 19 | region_move = store_data.GetRegionMove(); | ||
| 20 | faceline_type = store_data.GetFacelineType(); | ||
| 21 | faceline_color = store_data.GetFacelineColor(); | ||
| 22 | faceline_wrinkle = store_data.GetFacelineWrinkle(); | ||
| 23 | faceline_make = store_data.GetFacelineMake(); | ||
| 24 | hair_type = store_data.GetHairType(); | ||
| 25 | hair_color = store_data.GetHairColor(); | ||
| 26 | hair_flip = store_data.GetHairFlip(); | ||
| 27 | eye_type = store_data.GetEyeType(); | ||
| 28 | eye_color = store_data.GetEyeColor(); | ||
| 29 | eye_scale = store_data.GetEyeScale(); | ||
| 30 | eye_aspect = store_data.GetEyeAspect(); | ||
| 31 | eye_rotate = store_data.GetEyeRotate(); | ||
| 32 | eye_x = store_data.GetEyeX(); | ||
| 33 | eye_y = store_data.GetEyeY(); | ||
| 34 | eyebrow_type = store_data.GetEyebrowType(); | ||
| 35 | eyebrow_color = store_data.GetEyebrowColor(); | ||
| 36 | eyebrow_scale = store_data.GetEyebrowScale(); | ||
| 37 | eyebrow_aspect = store_data.GetEyebrowAspect(); | ||
| 38 | eyebrow_rotate = store_data.GetEyebrowRotate(); | ||
| 39 | eyebrow_x = store_data.GetEyebrowX(); | ||
| 40 | eyebrow_y = store_data.GetEyebrowY(); | ||
| 41 | nose_type = store_data.GetNoseType(); | ||
| 42 | nose_scale = store_data.GetNoseScale(); | ||
| 43 | nose_y = store_data.GetNoseY(); | ||
| 44 | mouth_type = store_data.GetMouthType(); | ||
| 45 | mouth_color = store_data.GetMouthColor(); | ||
| 46 | mouth_scale = store_data.GetMouthScale(); | ||
| 47 | mouth_aspect = store_data.GetMouthAspect(); | ||
| 48 | mouth_y = store_data.GetMouthY(); | ||
| 49 | beard_color = store_data.GetBeardColor(); | ||
| 50 | beard_type = store_data.GetBeardType(); | ||
| 51 | mustache_type = store_data.GetMustacheType(); | ||
| 52 | mustache_scale = store_data.GetMustacheScale(); | ||
| 53 | mustache_y = store_data.GetMustacheY(); | ||
| 54 | glass_type = store_data.GetGlassType(); | ||
| 55 | glass_color = store_data.GetGlassColor(); | ||
| 56 | glass_scale = store_data.GetGlassScale(); | ||
| 57 | glass_y = store_data.GetGlassY(); | ||
| 58 | mole_type = store_data.GetMoleType(); | ||
| 59 | mole_scale = store_data.GetMoleScale(); | ||
| 60 | mole_x = store_data.GetMoleX(); | ||
| 61 | mole_y = store_data.GetMoleY(); | ||
| 62 | padding = '\0'; | ||
| 63 | } | ||
| 64 | |||
| 65 | ValidationResult CharInfo::Verify() const { | ||
| 66 | if (!create_id.IsValid()) { | ||
| 67 | return ValidationResult::InvalidCreateId; | ||
| 68 | } | ||
| 69 | if (!name.IsValid()) { | ||
| 70 | return ValidationResult::InvalidName; | ||
| 71 | } | ||
| 72 | if (font_region > FontRegion::Max) { | ||
| 73 | return ValidationResult::InvalidFont; | ||
| 74 | } | ||
| 75 | if (favorite_color > FavoriteColor::Max) { | ||
| 76 | return ValidationResult::InvalidColor; | ||
| 77 | } | ||
| 78 | if (gender > Gender::Max) { | ||
| 79 | return ValidationResult::InvalidGender; | ||
| 80 | } | ||
| 81 | if (height > MaxHeight) { | ||
| 82 | return ValidationResult::InvalidHeight; | ||
| 83 | } | ||
| 84 | if (build > MaxBuild) { | ||
| 85 | return ValidationResult::InvalidBuild; | ||
| 86 | } | ||
| 87 | if (type > MaxType) { | ||
| 88 | return ValidationResult::InvalidType; | ||
| 89 | } | ||
| 90 | if (region_move > MaxRegionMove) { | ||
| 91 | return ValidationResult::InvalidRegionMove; | ||
| 92 | } | ||
| 93 | if (faceline_type > FacelineType::Max) { | ||
| 94 | return ValidationResult::InvalidFacelineType; | ||
| 95 | } | ||
| 96 | if (faceline_color > FacelineColor::Max) { | ||
| 97 | return ValidationResult::InvalidFacelineColor; | ||
| 98 | } | ||
| 99 | if (faceline_wrinkle > FacelineWrinkle::Max) { | ||
| 100 | return ValidationResult::InvalidFacelineWrinkle; | ||
| 101 | } | ||
| 102 | if (faceline_make > FacelineMake::Max) { | ||
| 103 | return ValidationResult::InvalidFacelineMake; | ||
| 104 | } | ||
| 105 | if (hair_type > HairType::Max) { | ||
| 106 | return ValidationResult::InvalidHairType; | ||
| 107 | } | ||
| 108 | if (hair_color > CommonColor::Max) { | ||
| 109 | return ValidationResult::InvalidHairColor; | ||
| 110 | } | ||
| 111 | if (hair_flip > HairFlip::Max) { | ||
| 112 | return ValidationResult::InvalidHairFlip; | ||
| 113 | } | ||
| 114 | if (eye_type > EyeType::Max) { | ||
| 115 | return ValidationResult::InvalidEyeType; | ||
| 116 | } | ||
| 117 | if (eye_color > CommonColor::Max) { | ||
| 118 | return ValidationResult::InvalidEyeColor; | ||
| 119 | } | ||
| 120 | if (eye_scale > MaxEyeScale) { | ||
| 121 | return ValidationResult::InvalidEyeScale; | ||
| 122 | } | ||
| 123 | if (eye_aspect > MaxEyeAspect) { | ||
| 124 | return ValidationResult::InvalidEyeAspect; | ||
| 125 | } | ||
| 126 | if (eye_rotate > MaxEyeX) { | ||
| 127 | return ValidationResult::InvalidEyeRotate; | ||
| 128 | } | ||
| 129 | if (eye_x > MaxEyeX) { | ||
| 130 | return ValidationResult::InvalidEyeX; | ||
| 131 | } | ||
| 132 | if (eye_y > MaxEyeY) { | ||
| 133 | return ValidationResult::InvalidEyeY; | ||
| 134 | } | ||
| 135 | if (eyebrow_type > EyebrowType::Max) { | ||
| 136 | return ValidationResult::InvalidEyebrowType; | ||
| 137 | } | ||
| 138 | if (eyebrow_color > CommonColor::Max) { | ||
| 139 | return ValidationResult::InvalidEyebrowColor; | ||
| 140 | } | ||
| 141 | if (eyebrow_scale > MaxEyebrowScale) { | ||
| 142 | return ValidationResult::InvalidEyebrowScale; | ||
| 143 | } | ||
| 144 | if (eyebrow_aspect > MaxEyebrowAspect) { | ||
| 145 | return ValidationResult::InvalidEyebrowAspect; | ||
| 146 | } | ||
| 147 | if (eyebrow_rotate > MaxEyebrowRotate) { | ||
| 148 | return ValidationResult::InvalidEyebrowRotate; | ||
| 149 | } | ||
| 150 | if (eyebrow_x > MaxEyebrowX) { | ||
| 151 | return ValidationResult::InvalidEyebrowX; | ||
| 152 | } | ||
| 153 | if (eyebrow_y > MaxEyebrowY) { | ||
| 154 | return ValidationResult::InvalidEyebrowY; | ||
| 155 | } | ||
| 156 | if (nose_type > NoseType::Max) { | ||
| 157 | return ValidationResult::InvalidNoseType; | ||
| 158 | } | ||
| 159 | if (nose_scale > MaxNoseScale) { | ||
| 160 | return ValidationResult::InvalidNoseScale; | ||
| 161 | } | ||
| 162 | if (nose_y > MaxNoseY) { | ||
| 163 | return ValidationResult::InvalidNoseY; | ||
| 164 | } | ||
| 165 | if (mouth_type > MouthType::Max) { | ||
| 166 | return ValidationResult::InvalidMouthType; | ||
| 167 | } | ||
| 168 | if (mouth_color > CommonColor::Max) { | ||
| 169 | return ValidationResult::InvalidMouthColor; | ||
| 170 | } | ||
| 171 | if (mouth_scale > MaxMouthScale) { | ||
| 172 | return ValidationResult::InvalidMouthScale; | ||
| 173 | } | ||
| 174 | if (mouth_aspect > MaxMoutAspect) { | ||
| 175 | return ValidationResult::InvalidMouthAspect; | ||
| 176 | } | ||
| 177 | if (mouth_y > MaxMouthY) { | ||
| 178 | return ValidationResult::InvalidMoleY; | ||
| 179 | } | ||
| 180 | if (beard_color > CommonColor::Max) { | ||
| 181 | return ValidationResult::InvalidBeardColor; | ||
| 182 | } | ||
| 183 | if (beard_type > BeardType::Max) { | ||
| 184 | return ValidationResult::InvalidBeardType; | ||
| 185 | } | ||
| 186 | if (mustache_type > MustacheType::Max) { | ||
| 187 | return ValidationResult::InvalidMustacheType; | ||
| 188 | } | ||
| 189 | if (mustache_scale > MaxMustacheScale) { | ||
| 190 | return ValidationResult::InvalidMustacheScale; | ||
| 191 | } | ||
| 192 | if (mustache_y > MasMustacheY) { | ||
| 193 | return ValidationResult::InvalidMustacheY; | ||
| 194 | } | ||
| 195 | if (glass_type > GlassType::Max) { | ||
| 196 | return ValidationResult::InvalidGlassType; | ||
| 197 | } | ||
| 198 | if (glass_color > CommonColor::Max) { | ||
| 199 | return ValidationResult::InvalidGlassColor; | ||
| 200 | } | ||
| 201 | if (glass_scale > MaxGlassScale) { | ||
| 202 | return ValidationResult::InvalidGlassScale; | ||
| 203 | } | ||
| 204 | if (glass_y > MaxGlassY) { | ||
| 205 | return ValidationResult::InvalidGlassY; | ||
| 206 | } | ||
| 207 | if (mole_type > MoleType::Max) { | ||
| 208 | return ValidationResult::InvalidMoleType; | ||
| 209 | } | ||
| 210 | if (mole_scale > MaxMoleScale) { | ||
| 211 | return ValidationResult::InvalidMoleScale; | ||
| 212 | } | ||
| 213 | if (mole_x > MaxMoleX) { | ||
| 214 | return ValidationResult::InvalidMoleX; | ||
| 215 | } | ||
| 216 | if (mole_y > MaxMoleY) { | ||
| 217 | return ValidationResult::InvalidMoleY; | ||
| 218 | } | ||
| 219 | return ValidationResult::NoErrors; | ||
| 220 | } | ||
| 221 | |||
| 222 | Common::UUID CharInfo::GetCreateId() const { | ||
| 223 | return create_id; | ||
| 224 | } | ||
| 225 | |||
| 226 | Nickname CharInfo::GetNickname() const { | ||
| 227 | return name; | ||
| 228 | } | ||
| 229 | |||
| 230 | FontRegion CharInfo::GetFontRegion() const { | ||
| 231 | return font_region; | ||
| 232 | } | ||
| 233 | |||
| 234 | FavoriteColor CharInfo::GetFavoriteColor() const { | ||
| 235 | return favorite_color; | ||
| 236 | } | ||
| 237 | |||
| 238 | Gender CharInfo::GetGender() const { | ||
| 239 | return gender; | ||
| 240 | } | ||
| 241 | |||
| 242 | u8 CharInfo::GetHeight() const { | ||
| 243 | return height; | ||
| 244 | } | ||
| 245 | |||
| 246 | u8 CharInfo::GetBuild() const { | ||
| 247 | return build; | ||
| 248 | } | ||
| 249 | |||
| 250 | u8 CharInfo::GetType() const { | ||
| 251 | return type; | ||
| 252 | } | ||
| 253 | |||
| 254 | u8 CharInfo::GetRegionMove() const { | ||
| 255 | return region_move; | ||
| 256 | } | ||
| 257 | |||
| 258 | FacelineType CharInfo::GetFacelineType() const { | ||
| 259 | return faceline_type; | ||
| 260 | } | ||
| 261 | |||
| 262 | FacelineColor CharInfo::GetFacelineColor() const { | ||
| 263 | return faceline_color; | ||
| 264 | } | ||
| 265 | |||
| 266 | FacelineWrinkle CharInfo::GetFacelineWrinkle() const { | ||
| 267 | return faceline_wrinkle; | ||
| 268 | } | ||
| 269 | |||
| 270 | FacelineMake CharInfo::GetFacelineMake() const { | ||
| 271 | return faceline_make; | ||
| 272 | } | ||
| 273 | |||
| 274 | HairType CharInfo::GetHairType() const { | ||
| 275 | return hair_type; | ||
| 276 | } | ||
| 277 | |||
| 278 | CommonColor CharInfo::GetHairColor() const { | ||
| 279 | return hair_color; | ||
| 280 | } | ||
| 281 | |||
| 282 | HairFlip CharInfo::GetHairFlip() const { | ||
| 283 | return hair_flip; | ||
| 284 | } | ||
| 285 | |||
| 286 | EyeType CharInfo::GetEyeType() const { | ||
| 287 | return eye_type; | ||
| 288 | } | ||
| 289 | |||
| 290 | CommonColor CharInfo::GetEyeColor() const { | ||
| 291 | return eye_color; | ||
| 292 | } | ||
| 293 | |||
| 294 | u8 CharInfo::GetEyeScale() const { | ||
| 295 | return eye_scale; | ||
| 296 | } | ||
| 297 | |||
| 298 | u8 CharInfo::GetEyeAspect() const { | ||
| 299 | return eye_aspect; | ||
| 300 | } | ||
| 301 | |||
| 302 | u8 CharInfo::GetEyeRotate() const { | ||
| 303 | return eye_rotate; | ||
| 304 | } | ||
| 305 | |||
| 306 | u8 CharInfo::GetEyeX() const { | ||
| 307 | return eye_x; | ||
| 308 | } | ||
| 309 | |||
| 310 | u8 CharInfo::GetEyeY() const { | ||
| 311 | return eye_y; | ||
| 312 | } | ||
| 313 | |||
| 314 | EyebrowType CharInfo::GetEyebrowType() const { | ||
| 315 | return eyebrow_type; | ||
| 316 | } | ||
| 317 | |||
| 318 | CommonColor CharInfo::GetEyebrowColor() const { | ||
| 319 | return eyebrow_color; | ||
| 320 | } | ||
| 321 | |||
| 322 | u8 CharInfo::GetEyebrowScale() const { | ||
| 323 | return eyebrow_scale; | ||
| 324 | } | ||
| 325 | |||
| 326 | u8 CharInfo::GetEyebrowAspect() const { | ||
| 327 | return eyebrow_aspect; | ||
| 328 | } | ||
| 329 | |||
| 330 | u8 CharInfo::GetEyebrowRotate() const { | ||
| 331 | return eyebrow_rotate; | ||
| 332 | } | ||
| 333 | |||
| 334 | u8 CharInfo::GetEyebrowX() const { | ||
| 335 | return eyebrow_x; | ||
| 336 | } | ||
| 337 | |||
| 338 | u8 CharInfo::GetEyebrowY() const { | ||
| 339 | return eyebrow_y; | ||
| 340 | } | ||
| 341 | |||
| 342 | NoseType CharInfo::GetNoseType() const { | ||
| 343 | return nose_type; | ||
| 344 | } | ||
| 345 | |||
| 346 | u8 CharInfo::GetNoseScale() const { | ||
| 347 | return nose_scale; | ||
| 348 | } | ||
| 349 | |||
| 350 | u8 CharInfo::GetNoseY() const { | ||
| 351 | return nose_y; | ||
| 352 | } | ||
| 353 | |||
| 354 | MouthType CharInfo::GetMouthType() const { | ||
| 355 | return mouth_type; | ||
| 356 | } | ||
| 357 | |||
| 358 | CommonColor CharInfo::GetMouthColor() const { | ||
| 359 | return mouth_color; | ||
| 360 | } | ||
| 361 | |||
| 362 | u8 CharInfo::GetMouthScale() const { | ||
| 363 | return mouth_scale; | ||
| 364 | } | ||
| 365 | |||
| 366 | u8 CharInfo::GetMouthAspect() const { | ||
| 367 | return mouth_aspect; | ||
| 368 | } | ||
| 369 | |||
| 370 | u8 CharInfo::GetMouthY() const { | ||
| 371 | return mouth_y; | ||
| 372 | } | ||
| 373 | |||
| 374 | CommonColor CharInfo::GetBeardColor() const { | ||
| 375 | return beard_color; | ||
| 376 | } | ||
| 377 | |||
| 378 | BeardType CharInfo::GetBeardType() const { | ||
| 379 | return beard_type; | ||
| 380 | } | ||
| 381 | |||
| 382 | MustacheType CharInfo::GetMustacheType() const { | ||
| 383 | return mustache_type; | ||
| 384 | } | ||
| 385 | |||
| 386 | u8 CharInfo::GetMustacheScale() const { | ||
| 387 | return mustache_scale; | ||
| 388 | } | ||
| 389 | |||
| 390 | u8 CharInfo::GetMustacheY() const { | ||
| 391 | return mustache_y; | ||
| 392 | } | ||
| 393 | |||
| 394 | GlassType CharInfo::GetGlassType() const { | ||
| 395 | return glass_type; | ||
| 396 | } | ||
| 397 | |||
| 398 | CommonColor CharInfo::GetGlassColor() const { | ||
| 399 | return glass_color; | ||
| 400 | } | ||
| 401 | |||
| 402 | u8 CharInfo::GetGlassScale() const { | ||
| 403 | return glass_scale; | ||
| 404 | } | ||
| 405 | |||
| 406 | u8 CharInfo::GetGlassY() const { | ||
| 407 | return glass_y; | ||
| 408 | } | ||
| 409 | |||
| 410 | MoleType CharInfo::GetMoleType() const { | ||
| 411 | return mole_type; | ||
| 412 | } | ||
| 413 | |||
| 414 | u8 CharInfo::GetMoleScale() const { | ||
| 415 | return mole_scale; | ||
| 416 | } | ||
| 417 | |||
| 418 | u8 CharInfo::GetMoleX() const { | ||
| 419 | return mole_x; | ||
| 420 | } | ||
| 421 | |||
| 422 | u8 CharInfo::GetMoleY() const { | ||
| 423 | return mole_y; | ||
| 424 | } | ||
| 425 | |||
| 426 | bool CharInfo::operator==(const CharInfo& info) { | ||
| 427 | bool is_identical = info.Verify() == ValidationResult::NoErrors; | ||
| 428 | is_identical &= name.data == info.GetNickname().data; | ||
| 429 | is_identical &= create_id == info.GetCreateId(); | ||
| 430 | is_identical &= font_region == info.GetFontRegion(); | ||
| 431 | is_identical &= favorite_color == info.GetFavoriteColor(); | ||
| 432 | is_identical &= gender == info.GetGender(); | ||
| 433 | is_identical &= height == info.GetHeight(); | ||
| 434 | is_identical &= build == info.GetBuild(); | ||
| 435 | is_identical &= type == info.GetType(); | ||
| 436 | is_identical &= region_move == info.GetRegionMove(); | ||
| 437 | is_identical &= faceline_type == info.GetFacelineType(); | ||
| 438 | is_identical &= faceline_color == info.GetFacelineColor(); | ||
| 439 | is_identical &= faceline_wrinkle == info.GetFacelineWrinkle(); | ||
| 440 | is_identical &= faceline_make == info.GetFacelineMake(); | ||
| 441 | is_identical &= hair_type == info.GetHairType(); | ||
| 442 | is_identical &= hair_color == info.GetHairColor(); | ||
| 443 | is_identical &= hair_flip == info.GetHairFlip(); | ||
| 444 | is_identical &= eye_type == info.GetEyeType(); | ||
| 445 | is_identical &= eye_color == info.GetEyeColor(); | ||
| 446 | is_identical &= eye_scale == info.GetEyeScale(); | ||
| 447 | is_identical &= eye_aspect == info.GetEyeAspect(); | ||
| 448 | is_identical &= eye_rotate == info.GetEyeRotate(); | ||
| 449 | is_identical &= eye_x == info.GetEyeX(); | ||
| 450 | is_identical &= eye_y == info.GetEyeY(); | ||
| 451 | is_identical &= eyebrow_type == info.GetEyebrowType(); | ||
| 452 | is_identical &= eyebrow_color == info.GetEyebrowColor(); | ||
| 453 | is_identical &= eyebrow_scale == info.GetEyebrowScale(); | ||
| 454 | is_identical &= eyebrow_aspect == info.GetEyebrowAspect(); | ||
| 455 | is_identical &= eyebrow_rotate == info.GetEyebrowRotate(); | ||
| 456 | is_identical &= eyebrow_x == info.GetEyebrowX(); | ||
| 457 | is_identical &= eyebrow_y == info.GetEyebrowY(); | ||
| 458 | is_identical &= nose_type == info.GetNoseType(); | ||
| 459 | is_identical &= nose_scale == info.GetNoseScale(); | ||
| 460 | is_identical &= nose_y == info.GetNoseY(); | ||
| 461 | is_identical &= mouth_type == info.GetMouthType(); | ||
| 462 | is_identical &= mouth_color == info.GetMouthColor(); | ||
| 463 | is_identical &= mouth_scale == info.GetMouthScale(); | ||
| 464 | is_identical &= mouth_aspect == info.GetMouthAspect(); | ||
| 465 | is_identical &= mouth_y == info.GetMouthY(); | ||
| 466 | is_identical &= beard_color == info.GetBeardColor(); | ||
| 467 | is_identical &= beard_type == info.GetBeardType(); | ||
| 468 | is_identical &= mustache_type == info.GetMustacheType(); | ||
| 469 | is_identical &= mustache_scale == info.GetMustacheScale(); | ||
| 470 | is_identical &= mustache_y == info.GetMustacheY(); | ||
| 471 | is_identical &= glass_type == info.GetGlassType(); | ||
| 472 | is_identical &= glass_color == info.GetGlassColor(); | ||
| 473 | is_identical &= glass_scale == info.GetGlassScale(); | ||
| 474 | is_identical &= glass_y == info.GetGlassY(); | ||
| 475 | is_identical &= mole_type == info.GetMoleType(); | ||
| 476 | is_identical &= mole_scale == info.GetMoleScale(); | ||
| 477 | is_identical &= mole_x == info.GetMoleX(); | ||
| 478 | is_identical &= mole_y == info.GetMoleY(); | ||
| 479 | return is_identical; | ||
| 480 | } | ||
| 481 | |||
| 482 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h new file mode 100644 index 000000000..d069b221f --- /dev/null +++ b/src/core/hle/service/mii/types/char_info.h | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | class StoreData; | ||
| 10 | |||
| 11 | // This is nn::mii::detail::CharInfoRaw | ||
| 12 | class CharInfo { | ||
| 13 | public: | ||
| 14 | void SetFromStoreData(const StoreData& store_data_raw); | ||
| 15 | |||
| 16 | ValidationResult Verify() const; | ||
| 17 | |||
| 18 | Common::UUID GetCreateId() const; | ||
| 19 | Nickname GetNickname() const; | ||
| 20 | FontRegion GetFontRegion() const; | ||
| 21 | FavoriteColor GetFavoriteColor() const; | ||
| 22 | Gender GetGender() const; | ||
| 23 | u8 GetHeight() const; | ||
| 24 | u8 GetBuild() const; | ||
| 25 | u8 GetType() const; | ||
| 26 | u8 GetRegionMove() const; | ||
| 27 | FacelineType GetFacelineType() const; | ||
| 28 | FacelineColor GetFacelineColor() const; | ||
| 29 | FacelineWrinkle GetFacelineWrinkle() const; | ||
| 30 | FacelineMake GetFacelineMake() const; | ||
| 31 | HairType GetHairType() const; | ||
| 32 | CommonColor GetHairColor() const; | ||
| 33 | HairFlip GetHairFlip() const; | ||
| 34 | EyeType GetEyeType() const; | ||
| 35 | CommonColor GetEyeColor() const; | ||
| 36 | u8 GetEyeScale() const; | ||
| 37 | u8 GetEyeAspect() const; | ||
| 38 | u8 GetEyeRotate() const; | ||
| 39 | u8 GetEyeX() const; | ||
| 40 | u8 GetEyeY() const; | ||
| 41 | EyebrowType GetEyebrowType() const; | ||
| 42 | CommonColor GetEyebrowColor() const; | ||
| 43 | u8 GetEyebrowScale() const; | ||
| 44 | u8 GetEyebrowAspect() const; | ||
| 45 | u8 GetEyebrowRotate() const; | ||
| 46 | u8 GetEyebrowX() const; | ||
| 47 | u8 GetEyebrowY() const; | ||
| 48 | NoseType GetNoseType() const; | ||
| 49 | u8 GetNoseScale() const; | ||
| 50 | u8 GetNoseY() const; | ||
| 51 | MouthType GetMouthType() const; | ||
| 52 | CommonColor GetMouthColor() const; | ||
| 53 | u8 GetMouthScale() const; | ||
| 54 | u8 GetMouthAspect() const; | ||
| 55 | u8 GetMouthY() const; | ||
| 56 | CommonColor GetBeardColor() const; | ||
| 57 | BeardType GetBeardType() const; | ||
| 58 | MustacheType GetMustacheType() const; | ||
| 59 | u8 GetMustacheScale() const; | ||
| 60 | u8 GetMustacheY() const; | ||
| 61 | GlassType GetGlassType() const; | ||
| 62 | CommonColor GetGlassColor() const; | ||
| 63 | u8 GetGlassScale() const; | ||
| 64 | u8 GetGlassY() const; | ||
| 65 | MoleType GetMoleType() const; | ||
| 66 | u8 GetMoleScale() const; | ||
| 67 | u8 GetMoleX() const; | ||
| 68 | u8 GetMoleY() const; | ||
| 69 | |||
| 70 | bool operator==(const CharInfo& info); | ||
| 71 | |||
| 72 | private: | ||
| 73 | Common::UUID create_id; | ||
| 74 | Nickname name; | ||
| 75 | u16 null_terminator; | ||
| 76 | FontRegion font_region; | ||
| 77 | FavoriteColor favorite_color; | ||
| 78 | Gender gender; | ||
| 79 | u8 height; | ||
| 80 | u8 build; | ||
| 81 | u8 type; | ||
| 82 | u8 region_move; | ||
| 83 | FacelineType faceline_type; | ||
| 84 | FacelineColor faceline_color; | ||
| 85 | FacelineWrinkle faceline_wrinkle; | ||
| 86 | FacelineMake faceline_make; | ||
| 87 | HairType hair_type; | ||
| 88 | CommonColor hair_color; | ||
| 89 | HairFlip hair_flip; | ||
| 90 | EyeType eye_type; | ||
| 91 | CommonColor eye_color; | ||
| 92 | u8 eye_scale; | ||
| 93 | u8 eye_aspect; | ||
| 94 | u8 eye_rotate; | ||
| 95 | u8 eye_x; | ||
| 96 | u8 eye_y; | ||
| 97 | EyebrowType eyebrow_type; | ||
| 98 | CommonColor eyebrow_color; | ||
| 99 | u8 eyebrow_scale; | ||
| 100 | u8 eyebrow_aspect; | ||
| 101 | u8 eyebrow_rotate; | ||
| 102 | u8 eyebrow_x; | ||
| 103 | u8 eyebrow_y; | ||
| 104 | NoseType nose_type; | ||
| 105 | u8 nose_scale; | ||
| 106 | u8 nose_y; | ||
| 107 | MouthType mouth_type; | ||
| 108 | CommonColor mouth_color; | ||
| 109 | u8 mouth_scale; | ||
| 110 | u8 mouth_aspect; | ||
| 111 | u8 mouth_y; | ||
| 112 | CommonColor beard_color; | ||
| 113 | BeardType beard_type; | ||
| 114 | MustacheType mustache_type; | ||
| 115 | u8 mustache_scale; | ||
| 116 | u8 mustache_y; | ||
| 117 | GlassType glass_type; | ||
| 118 | CommonColor glass_color; | ||
| 119 | u8 glass_scale; | ||
| 120 | u8 glass_y; | ||
| 121 | MoleType mole_type; | ||
| 122 | u8 mole_scale; | ||
| 123 | u8 mole_x; | ||
| 124 | u8 mole_y; | ||
| 125 | u8 padding; | ||
| 126 | }; | ||
| 127 | static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); | ||
| 128 | static_assert(std::has_unique_object_representations_v<CharInfo>, | ||
| 129 | "All bits of CharInfo must contribute to its value."); | ||
| 130 | |||
| 131 | struct CharInfoElement { | ||
| 132 | CharInfo char_info{}; | ||
| 133 | Source source{}; | ||
| 134 | }; | ||
| 135 | static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size."); | ||
| 136 | |||
| 137 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp new file mode 100644 index 000000000..659288b51 --- /dev/null +++ b/src/core/hle/service/mii/types/core_data.cpp | |||
| @@ -0,0 +1,601 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/assert.h" | ||
| 5 | #include "core/hle/service/mii/mii_util.h" | ||
| 6 | #include "core/hle/service/mii/types/core_data.h" | ||
| 7 | #include "core/hle/service/mii/types/raw_data.h" | ||
| 8 | |||
| 9 | namespace Service::Mii { | ||
| 10 | |||
| 11 | void CoreData::SetDefault() { | ||
| 12 | data = {}; | ||
| 13 | name = GetDefaultNickname(); | ||
| 14 | } | ||
| 15 | |||
| 16 | void CoreData::BuildRandom(Age age, Gender gender, Race race) { | ||
| 17 | if (gender == Gender::All) { | ||
| 18 | gender = MiiUtil::GetRandomValue(Gender::Max); | ||
| 19 | } | ||
| 20 | |||
| 21 | if (age == Age::All) { | ||
| 22 | const auto random{MiiUtil::GetRandomValue<int>(10)}; | ||
| 23 | if (random >= 8) { | ||
| 24 | age = Age::Old; | ||
| 25 | } else if (random >= 4) { | ||
| 26 | age = Age::Normal; | ||
| 27 | } else { | ||
| 28 | age = Age::Young; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | if (race == Race::All) { | ||
| 33 | const auto random{MiiUtil::GetRandomValue<int>(10)}; | ||
| 34 | if (random >= 8) { | ||
| 35 | race = Race::Black; | ||
| 36 | } else if (random >= 4) { | ||
| 37 | race = Race::White; | ||
| 38 | } else { | ||
| 39 | race = Race::Asian; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | SetGender(gender); | ||
| 44 | SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max)); | ||
| 45 | SetRegionMove(0); | ||
| 46 | SetFontRegion(FontRegion::Standard); | ||
| 47 | SetType(0); | ||
| 48 | SetHeight(64); | ||
| 49 | SetBuild(64); | ||
| 50 | |||
| 51 | u32 axis_y{}; | ||
| 52 | if (gender == Gender::Female && age == Age::Young) { | ||
| 53 | axis_y = MiiUtil::GetRandomValue<u32>(3); | ||
| 54 | } | ||
| 55 | |||
| 56 | const std::size_t index{3 * static_cast<std::size_t>(age) + | ||
| 57 | 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; | ||
| 58 | |||
| 59 | const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)}; | ||
| 60 | const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at( | ||
| 61 | 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; | ||
| 62 | const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; | ||
| 63 | const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; | ||
| 64 | const auto& hair_type_info{RawData::RandomMiiHairType.at(index)}; | ||
| 65 | const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + | ||
| 66 | static_cast<std::size_t>(age))}; | ||
| 67 | const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)}; | ||
| 68 | const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; | ||
| 69 | const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; | ||
| 70 | const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)}; | ||
| 71 | const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)}; | ||
| 72 | const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; | ||
| 73 | |||
| 74 | data.faceline_type.Assign( | ||
| 75 | faceline_type_info | ||
| 76 | .values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]); | ||
| 77 | data.faceline_color.Assign( | ||
| 78 | faceline_color_info | ||
| 79 | .values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]); | ||
| 80 | data.faceline_wrinkle.Assign( | ||
| 81 | faceline_wrinkle_info | ||
| 82 | .values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); | ||
| 83 | data.faceline_makeup.Assign( | ||
| 84 | faceline_makeup_info | ||
| 85 | .values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); | ||
| 86 | |||
| 87 | data.hair_type.Assign( | ||
| 88 | hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]); | ||
| 89 | SetHairColor(RawData::GetHairColorFromVer3( | ||
| 90 | hair_color_info | ||
| 91 | .values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)])); | ||
| 92 | SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max)); | ||
| 93 | |||
| 94 | data.eye_type.Assign( | ||
| 95 | eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]); | ||
| 96 | |||
| 97 | const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; | ||
| 98 | const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; | ||
| 99 | const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; | ||
| 100 | const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]}; | ||
| 101 | |||
| 102 | SetEyeColor(RawData::GetEyeColorFromVer3( | ||
| 103 | eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)])); | ||
| 104 | SetEyeScale(4); | ||
| 105 | SetEyeAspect(3); | ||
| 106 | SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate)); | ||
| 107 | SetEyeX(2); | ||
| 108 | SetEyeY(static_cast<u8>(axis_y + 12)); | ||
| 109 | |||
| 110 | data.eyebrow_type.Assign( | ||
| 111 | eyebrow_type_info | ||
| 112 | .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); | ||
| 113 | |||
| 114 | const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; | ||
| 115 | const auto eyebrow_y{race == Race::Asian ? 9 : 10}; | ||
| 116 | const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6}; | ||
| 117 | const auto eyebrow_rotate{ | ||
| 118 | 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]}; | ||
| 119 | |||
| 120 | SetEyebrowColor(GetHairColor()); | ||
| 121 | SetEyebrowScale(4); | ||
| 122 | SetEyebrowAspect(3); | ||
| 123 | SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate)); | ||
| 124 | SetEyebrowX(2); | ||
| 125 | SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y)); | ||
| 126 | |||
| 127 | data.nose_type.Assign( | ||
| 128 | nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]); | ||
| 129 | SetNoseScale(gender == Gender::Female ? 3 : 4); | ||
| 130 | SetNoseY(static_cast<u8>(axis_y + 9)); | ||
| 131 | |||
| 132 | const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0}; | ||
| 133 | |||
| 134 | data.mouth_type.Assign( | ||
| 135 | mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]); | ||
| 136 | SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color)); | ||
| 137 | SetMouthScale(4); | ||
| 138 | SetMouthAspect(3); | ||
| 139 | SetMouthY(static_cast<u8>(axis_y + 13)); | ||
| 140 | |||
| 141 | SetBeardColor(GetHairColor()); | ||
| 142 | SetMustacheScale(4); | ||
| 143 | |||
| 144 | if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) { | ||
| 145 | const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)}; | ||
| 146 | |||
| 147 | auto beard_type{BeardType::None}; | ||
| 148 | auto mustache_type{MustacheType::None}; | ||
| 149 | |||
| 150 | if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == | ||
| 151 | BeardAndMustacheFlag::Beard) { | ||
| 152 | beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max); | ||
| 153 | } | ||
| 154 | |||
| 155 | if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == | ||
| 156 | BeardAndMustacheFlag::Mustache) { | ||
| 157 | mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max); | ||
| 158 | } | ||
| 159 | |||
| 160 | SetMustacheType(mustache_type); | ||
| 161 | SetBeardType(beard_type); | ||
| 162 | SetMustacheY(10); | ||
| 163 | } else { | ||
| 164 | SetMustacheType(MustacheType::None); | ||
| 165 | SetBeardType(BeardType::None); | ||
| 166 | SetMustacheY(static_cast<u8>(axis_y + 10)); | ||
| 167 | } | ||
| 168 | |||
| 169 | const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)}; | ||
| 170 | u8 glasses_type{}; | ||
| 171 | while (glasses_type_start < glasses_type_info.values[glasses_type]) { | ||
| 172 | if (++glasses_type >= glasses_type_info.values_count) { | ||
| 173 | ASSERT(false); | ||
| 174 | break; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | SetGlassType(static_cast<GlassType>(glasses_type)); | ||
| 179 | SetGlassColor(RawData::GetGlassColorFromVer3(0)); | ||
| 180 | SetGlassScale(4); | ||
| 181 | |||
| 182 | SetMoleType(MoleType::None); | ||
| 183 | SetMoleScale(4); | ||
| 184 | SetMoleX(2); | ||
| 185 | SetMoleY(20); | ||
| 186 | } | ||
| 187 | |||
| 188 | u32 CoreData::IsValid() const { | ||
| 189 | // TODO: Complete this | ||
| 190 | return 0; | ||
| 191 | } | ||
| 192 | |||
| 193 | void CoreData::SetFontRegion(FontRegion value) { | ||
| 194 | data.font_region.Assign(static_cast<u32>(value)); | ||
| 195 | } | ||
| 196 | |||
| 197 | void CoreData::SetFavoriteColor(FavoriteColor value) { | ||
| 198 | data.favorite_color.Assign(static_cast<u32>(value)); | ||
| 199 | } | ||
| 200 | |||
| 201 | void CoreData::SetGender(Gender value) { | ||
| 202 | data.gender.Assign(static_cast<u32>(value)); | ||
| 203 | } | ||
| 204 | |||
| 205 | void CoreData::SetHeight(u8 value) { | ||
| 206 | data.height.Assign(value); | ||
| 207 | } | ||
| 208 | |||
| 209 | void CoreData::SetBuild(u8 value) { | ||
| 210 | data.build.Assign(value); | ||
| 211 | } | ||
| 212 | |||
| 213 | void CoreData::SetType(u8 value) { | ||
| 214 | data.type.Assign(value); | ||
| 215 | } | ||
| 216 | |||
| 217 | void CoreData::SetRegionMove(u8 value) { | ||
| 218 | data.region_move.Assign(value); | ||
| 219 | } | ||
| 220 | |||
| 221 | void CoreData::SetFacelineType(FacelineType value) { | ||
| 222 | data.faceline_type.Assign(static_cast<u32>(value)); | ||
| 223 | } | ||
| 224 | |||
| 225 | void CoreData::SetFacelineColor(FacelineColor value) { | ||
| 226 | data.faceline_color.Assign(static_cast<u32>(value)); | ||
| 227 | } | ||
| 228 | |||
| 229 | void CoreData::SetFacelineWrinkle(FacelineWrinkle value) { | ||
| 230 | data.faceline_wrinkle.Assign(static_cast<u32>(value)); | ||
| 231 | } | ||
| 232 | |||
| 233 | void CoreData::SetFacelineMake(FacelineMake value) { | ||
| 234 | data.faceline_makeup.Assign(static_cast<u32>(value)); | ||
| 235 | } | ||
| 236 | |||
| 237 | void CoreData::SetHairType(HairType value) { | ||
| 238 | data.hair_type.Assign(static_cast<u32>(value)); | ||
| 239 | } | ||
| 240 | |||
| 241 | void CoreData::SetHairColor(CommonColor value) { | ||
| 242 | data.hair_color.Assign(static_cast<u32>(value)); | ||
| 243 | } | ||
| 244 | |||
| 245 | void CoreData::SetHairFlip(HairFlip value) { | ||
| 246 | data.hair_flip.Assign(static_cast<u32>(value)); | ||
| 247 | } | ||
| 248 | |||
| 249 | void CoreData::SetEyeType(EyeType value) { | ||
| 250 | data.eye_type.Assign(static_cast<u32>(value)); | ||
| 251 | } | ||
| 252 | |||
| 253 | void CoreData::SetEyeColor(CommonColor value) { | ||
| 254 | data.eye_color.Assign(static_cast<u32>(value)); | ||
| 255 | } | ||
| 256 | |||
| 257 | void CoreData::SetEyeScale(u8 value) { | ||
| 258 | data.eye_scale.Assign(value); | ||
| 259 | } | ||
| 260 | |||
| 261 | void CoreData::SetEyeAspect(u8 value) { | ||
| 262 | data.eye_aspect.Assign(value); | ||
| 263 | } | ||
| 264 | |||
| 265 | void CoreData::SetEyeRotate(u8 value) { | ||
| 266 | data.eye_rotate.Assign(value); | ||
| 267 | } | ||
| 268 | |||
| 269 | void CoreData::SetEyeX(u8 value) { | ||
| 270 | data.eye_x.Assign(value); | ||
| 271 | } | ||
| 272 | |||
| 273 | void CoreData::SetEyeY(u8 value) { | ||
| 274 | data.eye_y.Assign(value); | ||
| 275 | } | ||
| 276 | |||
| 277 | void CoreData::SetEyebrowType(EyebrowType value) { | ||
| 278 | data.eyebrow_type.Assign(static_cast<u32>(value)); | ||
| 279 | } | ||
| 280 | |||
| 281 | void CoreData::SetEyebrowColor(CommonColor value) { | ||
| 282 | data.eyebrow_color.Assign(static_cast<u32>(value)); | ||
| 283 | } | ||
| 284 | |||
| 285 | void CoreData::SetEyebrowScale(u8 value) { | ||
| 286 | data.eyebrow_scale.Assign(value); | ||
| 287 | } | ||
| 288 | |||
| 289 | void CoreData::SetEyebrowAspect(u8 value) { | ||
| 290 | data.eyebrow_aspect.Assign(value); | ||
| 291 | } | ||
| 292 | |||
| 293 | void CoreData::SetEyebrowRotate(u8 value) { | ||
| 294 | data.eyebrow_rotate.Assign(value); | ||
| 295 | } | ||
| 296 | |||
| 297 | void CoreData::SetEyebrowX(u8 value) { | ||
| 298 | data.eyebrow_x.Assign(value); | ||
| 299 | } | ||
| 300 | |||
| 301 | void CoreData::SetEyebrowY(u8 value) { | ||
| 302 | data.eyebrow_y.Assign(value); | ||
| 303 | } | ||
| 304 | |||
| 305 | void CoreData::SetNoseType(NoseType value) { | ||
| 306 | data.nose_type.Assign(static_cast<u32>(value)); | ||
| 307 | } | ||
| 308 | |||
| 309 | void CoreData::SetNoseScale(u8 value) { | ||
| 310 | data.nose_scale.Assign(value); | ||
| 311 | } | ||
| 312 | |||
| 313 | void CoreData::SetNoseY(u8 value) { | ||
| 314 | data.nose_y.Assign(value); | ||
| 315 | } | ||
| 316 | |||
| 317 | void CoreData::SetMouthType(u8 value) { | ||
| 318 | data.mouth_type.Assign(value); | ||
| 319 | } | ||
| 320 | |||
| 321 | void CoreData::SetMouthColor(CommonColor value) { | ||
| 322 | data.mouth_color.Assign(static_cast<u32>(value)); | ||
| 323 | } | ||
| 324 | |||
| 325 | void CoreData::SetMouthScale(u8 value) { | ||
| 326 | data.mouth_scale.Assign(value); | ||
| 327 | } | ||
| 328 | |||
| 329 | void CoreData::SetMouthAspect(u8 value) { | ||
| 330 | data.mouth_aspect.Assign(value); | ||
| 331 | } | ||
| 332 | |||
| 333 | void CoreData::SetMouthY(u8 value) { | ||
| 334 | data.mouth_y.Assign(value); | ||
| 335 | } | ||
| 336 | |||
| 337 | void CoreData::SetBeardColor(CommonColor value) { | ||
| 338 | data.beard_color.Assign(static_cast<u32>(value)); | ||
| 339 | } | ||
| 340 | |||
| 341 | void CoreData::SetBeardType(BeardType value) { | ||
| 342 | data.beard_type.Assign(static_cast<u32>(value)); | ||
| 343 | } | ||
| 344 | |||
| 345 | void CoreData::SetMustacheType(MustacheType value) { | ||
| 346 | data.mustache_type.Assign(static_cast<u32>(value)); | ||
| 347 | } | ||
| 348 | |||
| 349 | void CoreData::SetMustacheScale(u8 value) { | ||
| 350 | data.mustache_scale.Assign(value); | ||
| 351 | } | ||
| 352 | |||
| 353 | void CoreData::SetMustacheY(u8 value) { | ||
| 354 | data.mustache_y.Assign(value); | ||
| 355 | } | ||
| 356 | |||
| 357 | void CoreData::SetGlassType(GlassType value) { | ||
| 358 | data.glasses_type.Assign(static_cast<u32>(value)); | ||
| 359 | } | ||
| 360 | |||
| 361 | void CoreData::SetGlassColor(CommonColor value) { | ||
| 362 | data.glasses_color.Assign(static_cast<u32>(value)); | ||
| 363 | } | ||
| 364 | |||
| 365 | void CoreData::SetGlassScale(u8 value) { | ||
| 366 | data.glasses_scale.Assign(value); | ||
| 367 | } | ||
| 368 | |||
| 369 | void CoreData::SetGlassY(u8 value) { | ||
| 370 | data.glasses_y.Assign(value); | ||
| 371 | } | ||
| 372 | |||
| 373 | void CoreData::SetMoleType(MoleType value) { | ||
| 374 | data.mole_type.Assign(static_cast<u32>(value)); | ||
| 375 | } | ||
| 376 | |||
| 377 | void CoreData::SetMoleScale(u8 value) { | ||
| 378 | data.mole_scale.Assign(value); | ||
| 379 | } | ||
| 380 | |||
| 381 | void CoreData::SetMoleX(u8 value) { | ||
| 382 | data.mole_x.Assign(value); | ||
| 383 | } | ||
| 384 | |||
| 385 | void CoreData::SetMoleY(u8 value) { | ||
| 386 | data.mole_y.Assign(value); | ||
| 387 | } | ||
| 388 | |||
| 389 | void CoreData::SetNickname(Nickname nickname) { | ||
| 390 | name = nickname; | ||
| 391 | } | ||
| 392 | |||
| 393 | FontRegion CoreData::GetFontRegion() const { | ||
| 394 | return static_cast<FontRegion>(data.font_region.Value()); | ||
| 395 | } | ||
| 396 | |||
| 397 | FavoriteColor CoreData::GetFavoriteColor() const { | ||
| 398 | return static_cast<FavoriteColor>(data.favorite_color.Value()); | ||
| 399 | } | ||
| 400 | |||
| 401 | Gender CoreData::GetGender() const { | ||
| 402 | return static_cast<Gender>(data.gender.Value()); | ||
| 403 | } | ||
| 404 | |||
| 405 | u8 CoreData::GetHeight() const { | ||
| 406 | return static_cast<u8>(data.height.Value()); | ||
| 407 | } | ||
| 408 | |||
| 409 | u8 CoreData::GetBuild() const { | ||
| 410 | return static_cast<u8>(data.build.Value()); | ||
| 411 | } | ||
| 412 | |||
| 413 | u8 CoreData::GetType() const { | ||
| 414 | return static_cast<u8>(data.type.Value()); | ||
| 415 | } | ||
| 416 | |||
| 417 | u8 CoreData::GetRegionMove() const { | ||
| 418 | return static_cast<u8>(data.region_move.Value()); | ||
| 419 | } | ||
| 420 | |||
| 421 | FacelineType CoreData::GetFacelineType() const { | ||
| 422 | return static_cast<FacelineType>(data.faceline_type.Value()); | ||
| 423 | } | ||
| 424 | |||
| 425 | FacelineColor CoreData::GetFacelineColor() const { | ||
| 426 | return static_cast<FacelineColor>(data.faceline_color.Value()); | ||
| 427 | } | ||
| 428 | |||
| 429 | FacelineWrinkle CoreData::GetFacelineWrinkle() const { | ||
| 430 | return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value()); | ||
| 431 | } | ||
| 432 | |||
| 433 | FacelineMake CoreData::GetFacelineMake() const { | ||
| 434 | return static_cast<FacelineMake>(data.faceline_makeup.Value()); | ||
| 435 | } | ||
| 436 | |||
| 437 | HairType CoreData::GetHairType() const { | ||
| 438 | return static_cast<HairType>(data.hair_type.Value()); | ||
| 439 | } | ||
| 440 | |||
| 441 | CommonColor CoreData::GetHairColor() const { | ||
| 442 | return static_cast<CommonColor>(data.hair_color.Value()); | ||
| 443 | } | ||
| 444 | |||
| 445 | HairFlip CoreData::GetHairFlip() const { | ||
| 446 | return static_cast<HairFlip>(data.hair_flip.Value()); | ||
| 447 | } | ||
| 448 | |||
| 449 | EyeType CoreData::GetEyeType() const { | ||
| 450 | return static_cast<EyeType>(data.eye_type.Value()); | ||
| 451 | } | ||
| 452 | |||
| 453 | CommonColor CoreData::GetEyeColor() const { | ||
| 454 | return static_cast<CommonColor>(data.eye_color.Value()); | ||
| 455 | } | ||
| 456 | |||
| 457 | u8 CoreData::GetEyeScale() const { | ||
| 458 | return static_cast<u8>(data.eye_scale.Value()); | ||
| 459 | } | ||
| 460 | |||
| 461 | u8 CoreData::GetEyeAspect() const { | ||
| 462 | return static_cast<u8>(data.eye_aspect.Value()); | ||
| 463 | } | ||
| 464 | |||
| 465 | u8 CoreData::GetEyeRotate() const { | ||
| 466 | return static_cast<u8>(data.eye_rotate.Value()); | ||
| 467 | } | ||
| 468 | |||
| 469 | u8 CoreData::GetEyeX() const { | ||
| 470 | return static_cast<u8>(data.eye_x.Value()); | ||
| 471 | } | ||
| 472 | |||
| 473 | u8 CoreData::GetEyeY() const { | ||
| 474 | return static_cast<u8>(data.eye_y.Value()); | ||
| 475 | } | ||
| 476 | |||
| 477 | EyebrowType CoreData::GetEyebrowType() const { | ||
| 478 | return static_cast<EyebrowType>(data.eyebrow_type.Value()); | ||
| 479 | } | ||
| 480 | |||
| 481 | CommonColor CoreData::GetEyebrowColor() const { | ||
| 482 | return static_cast<CommonColor>(data.eyebrow_color.Value()); | ||
| 483 | } | ||
| 484 | |||
| 485 | u8 CoreData::GetEyebrowScale() const { | ||
| 486 | return static_cast<u8>(data.eyebrow_scale.Value()); | ||
| 487 | } | ||
| 488 | |||
| 489 | u8 CoreData::GetEyebrowAspect() const { | ||
| 490 | return static_cast<u8>(data.eyebrow_aspect.Value()); | ||
| 491 | } | ||
| 492 | |||
| 493 | u8 CoreData::GetEyebrowRotate() const { | ||
| 494 | return static_cast<u8>(data.eyebrow_rotate.Value()); | ||
| 495 | } | ||
| 496 | |||
| 497 | u8 CoreData::GetEyebrowX() const { | ||
| 498 | return static_cast<u8>(data.eyebrow_x.Value()); | ||
| 499 | } | ||
| 500 | |||
| 501 | u8 CoreData::GetEyebrowY() const { | ||
| 502 | return static_cast<u8>(data.eyebrow_y.Value()); | ||
| 503 | } | ||
| 504 | |||
| 505 | NoseType CoreData::GetNoseType() const { | ||
| 506 | return static_cast<NoseType>(data.nose_type.Value()); | ||
| 507 | } | ||
| 508 | |||
| 509 | u8 CoreData::GetNoseScale() const { | ||
| 510 | return static_cast<u8>(data.nose_scale.Value()); | ||
| 511 | } | ||
| 512 | |||
| 513 | u8 CoreData::GetNoseY() const { | ||
| 514 | return static_cast<u8>(data.nose_y.Value()); | ||
| 515 | } | ||
| 516 | |||
| 517 | MouthType CoreData::GetMouthType() const { | ||
| 518 | return static_cast<MouthType>(data.mouth_type.Value()); | ||
| 519 | } | ||
| 520 | |||
| 521 | CommonColor CoreData::GetMouthColor() const { | ||
| 522 | return static_cast<CommonColor>(data.mouth_color.Value()); | ||
| 523 | } | ||
| 524 | |||
| 525 | u8 CoreData::GetMouthScale() const { | ||
| 526 | return static_cast<u8>(data.mouth_scale.Value()); | ||
| 527 | } | ||
| 528 | |||
| 529 | u8 CoreData::GetMouthAspect() const { | ||
| 530 | return static_cast<u8>(data.mouth_aspect.Value()); | ||
| 531 | } | ||
| 532 | |||
| 533 | u8 CoreData::GetMouthY() const { | ||
| 534 | return static_cast<u8>(data.mouth_y.Value()); | ||
| 535 | } | ||
| 536 | |||
| 537 | CommonColor CoreData::GetBeardColor() const { | ||
| 538 | return static_cast<CommonColor>(data.beard_color.Value()); | ||
| 539 | } | ||
| 540 | |||
| 541 | BeardType CoreData::GetBeardType() const { | ||
| 542 | return static_cast<BeardType>(data.beard_type.Value()); | ||
| 543 | } | ||
| 544 | |||
| 545 | MustacheType CoreData::GetMustacheType() const { | ||
| 546 | return static_cast<MustacheType>(data.mustache_type.Value()); | ||
| 547 | } | ||
| 548 | |||
| 549 | u8 CoreData::GetMustacheScale() const { | ||
| 550 | return static_cast<u8>(data.mustache_scale.Value()); | ||
| 551 | } | ||
| 552 | |||
| 553 | u8 CoreData::GetMustacheY() const { | ||
| 554 | return static_cast<u8>(data.mustache_y.Value()); | ||
| 555 | } | ||
| 556 | |||
| 557 | GlassType CoreData::GetGlassType() const { | ||
| 558 | return static_cast<GlassType>(data.glasses_type.Value()); | ||
| 559 | } | ||
| 560 | |||
| 561 | CommonColor CoreData::GetGlassColor() const { | ||
| 562 | return static_cast<CommonColor>(data.glasses_color.Value()); | ||
| 563 | } | ||
| 564 | |||
| 565 | u8 CoreData::GetGlassScale() const { | ||
| 566 | return static_cast<u8>(data.glasses_scale.Value()); | ||
| 567 | } | ||
| 568 | |||
| 569 | u8 CoreData::GetGlassY() const { | ||
| 570 | return static_cast<u8>(data.glasses_y.Value()); | ||
| 571 | } | ||
| 572 | |||
| 573 | MoleType CoreData::GetMoleType() const { | ||
| 574 | return static_cast<MoleType>(data.mole_type.Value()); | ||
| 575 | } | ||
| 576 | |||
| 577 | u8 CoreData::GetMoleScale() const { | ||
| 578 | return static_cast<u8>(data.mole_scale.Value()); | ||
| 579 | } | ||
| 580 | |||
| 581 | u8 CoreData::GetMoleX() const { | ||
| 582 | return static_cast<u8>(data.mole_x.Value()); | ||
| 583 | } | ||
| 584 | |||
| 585 | u8 CoreData::GetMoleY() const { | ||
| 586 | return static_cast<u8>(data.mole_y.Value()); | ||
| 587 | } | ||
| 588 | |||
| 589 | Nickname CoreData::GetNickname() const { | ||
| 590 | return name; | ||
| 591 | } | ||
| 592 | |||
| 593 | Nickname CoreData::GetDefaultNickname() const { | ||
| 594 | return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}; | ||
| 595 | } | ||
| 596 | |||
| 597 | Nickname CoreData::GetInvalidNickname() const { | ||
| 598 | return {u'?', u'?', u'?'}; | ||
| 599 | } | ||
| 600 | |||
| 601 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h new file mode 100644 index 000000000..cebcd2ee4 --- /dev/null +++ b/src/core/hle/service/mii/types/core_data.h | |||
| @@ -0,0 +1,216 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | |||
| 10 | struct StoreDataBitFields { | ||
| 11 | union { | ||
| 12 | u32 word_0{}; | ||
| 13 | |||
| 14 | BitField<0, 8, u32> hair_type; | ||
| 15 | BitField<8, 7, u32> height; | ||
| 16 | BitField<15, 1, u32> mole_type; | ||
| 17 | BitField<16, 7, u32> build; | ||
| 18 | BitField<23, 1, u32> hair_flip; | ||
| 19 | BitField<24, 7, u32> hair_color; | ||
| 20 | BitField<31, 1, u32> type; | ||
| 21 | }; | ||
| 22 | |||
| 23 | union { | ||
| 24 | u32 word_1{}; | ||
| 25 | |||
| 26 | BitField<0, 7, u32> eye_color; | ||
| 27 | BitField<7, 1, u32> gender; | ||
| 28 | BitField<8, 7, u32> eyebrow_color; | ||
| 29 | BitField<16, 7, u32> mouth_color; | ||
| 30 | BitField<24, 7, u32> beard_color; | ||
| 31 | }; | ||
| 32 | |||
| 33 | union { | ||
| 34 | u32 word_2{}; | ||
| 35 | |||
| 36 | BitField<0, 7, u32> glasses_color; | ||
| 37 | BitField<8, 6, u32> eye_type; | ||
| 38 | BitField<14, 2, u32> region_move; | ||
| 39 | BitField<16, 6, u32> mouth_type; | ||
| 40 | BitField<22, 2, u32> font_region; | ||
| 41 | BitField<24, 5, u32> eye_y; | ||
| 42 | BitField<29, 3, u32> glasses_scale; | ||
| 43 | }; | ||
| 44 | |||
| 45 | union { | ||
| 46 | u32 word_3{}; | ||
| 47 | |||
| 48 | BitField<0, 5, u32> eyebrow_type; | ||
| 49 | BitField<5, 3, u32> mustache_type; | ||
| 50 | BitField<8, 5, u32> nose_type; | ||
| 51 | BitField<13, 3, u32> beard_type; | ||
| 52 | BitField<16, 5, u32> nose_y; | ||
| 53 | BitField<21, 3, u32> mouth_aspect; | ||
| 54 | BitField<24, 5, u32> mouth_y; | ||
| 55 | BitField<29, 3, u32> eyebrow_aspect; | ||
| 56 | }; | ||
| 57 | |||
| 58 | union { | ||
| 59 | u32 word_4{}; | ||
| 60 | |||
| 61 | BitField<0, 5, u32> mustache_y; | ||
| 62 | BitField<5, 3, u32> eye_rotate; | ||
| 63 | BitField<8, 5, u32> glasses_y; | ||
| 64 | BitField<13, 3, u32> eye_aspect; | ||
| 65 | BitField<16, 5, u32> mole_x; | ||
| 66 | BitField<21, 3, u32> eye_scale; | ||
| 67 | BitField<24, 5, u32> mole_y; | ||
| 68 | }; | ||
| 69 | |||
| 70 | union { | ||
| 71 | u32 word_5{}; | ||
| 72 | |||
| 73 | BitField<0, 5, u32> glasses_type; | ||
| 74 | BitField<8, 4, u32> favorite_color; | ||
| 75 | BitField<12, 4, u32> faceline_type; | ||
| 76 | BitField<16, 4, u32> faceline_color; | ||
| 77 | BitField<20, 4, u32> faceline_wrinkle; | ||
| 78 | BitField<24, 4, u32> faceline_makeup; | ||
| 79 | BitField<28, 4, u32> eye_x; | ||
| 80 | }; | ||
| 81 | |||
| 82 | union { | ||
| 83 | u32 word_6{}; | ||
| 84 | |||
| 85 | BitField<0, 4, u32> eyebrow_scale; | ||
| 86 | BitField<4, 4, u32> eyebrow_rotate; | ||
| 87 | BitField<8, 4, u32> eyebrow_x; | ||
| 88 | BitField<12, 4, u32> eyebrow_y; | ||
| 89 | BitField<16, 4, u32> nose_scale; | ||
| 90 | BitField<20, 4, u32> mouth_scale; | ||
| 91 | BitField<24, 4, u32> mustache_scale; | ||
| 92 | BitField<28, 4, u32> mole_scale; | ||
| 93 | }; | ||
| 94 | }; | ||
| 95 | static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size."); | ||
| 96 | static_assert(std::is_trivially_copyable_v<StoreDataBitFields>, | ||
| 97 | "StoreDataBitFields is not trivially copyable."); | ||
| 98 | |||
| 99 | class CoreData { | ||
| 100 | public: | ||
| 101 | void SetDefault(); | ||
| 102 | void BuildRandom(Age age, Gender gender, Race race); | ||
| 103 | |||
| 104 | u32 IsValid() const; | ||
| 105 | |||
| 106 | void SetFontRegion(FontRegion value); | ||
| 107 | void SetFavoriteColor(FavoriteColor value); | ||
| 108 | void SetGender(Gender value); | ||
| 109 | void SetHeight(u8 value); | ||
| 110 | void SetBuild(u8 value); | ||
| 111 | void SetType(u8 value); | ||
| 112 | void SetRegionMove(u8 value); | ||
| 113 | void SetFacelineType(FacelineType value); | ||
| 114 | void SetFacelineColor(FacelineColor value); | ||
| 115 | void SetFacelineWrinkle(FacelineWrinkle value); | ||
| 116 | void SetFacelineMake(FacelineMake value); | ||
| 117 | void SetHairType(HairType value); | ||
| 118 | void SetHairColor(CommonColor value); | ||
| 119 | void SetHairFlip(HairFlip value); | ||
| 120 | void SetEyeType(EyeType value); | ||
| 121 | void SetEyeColor(CommonColor value); | ||
| 122 | void SetEyeScale(u8 value); | ||
| 123 | void SetEyeAspect(u8 value); | ||
| 124 | void SetEyeRotate(u8 value); | ||
| 125 | void SetEyeX(u8 value); | ||
| 126 | void SetEyeY(u8 value); | ||
| 127 | void SetEyebrowType(EyebrowType value); | ||
| 128 | void SetEyebrowColor(CommonColor value); | ||
| 129 | void SetEyebrowScale(u8 value); | ||
| 130 | void SetEyebrowAspect(u8 value); | ||
| 131 | void SetEyebrowRotate(u8 value); | ||
| 132 | void SetEyebrowX(u8 value); | ||
| 133 | void SetEyebrowY(u8 value); | ||
| 134 | void SetNoseType(NoseType value); | ||
| 135 | void SetNoseScale(u8 value); | ||
| 136 | void SetNoseY(u8 value); | ||
| 137 | void SetMouthType(u8 value); | ||
| 138 | void SetMouthColor(CommonColor value); | ||
| 139 | void SetMouthScale(u8 value); | ||
| 140 | void SetMouthAspect(u8 value); | ||
| 141 | void SetMouthY(u8 value); | ||
| 142 | void SetBeardColor(CommonColor value); | ||
| 143 | void SetBeardType(BeardType value); | ||
| 144 | void SetMustacheType(MustacheType value); | ||
| 145 | void SetMustacheScale(u8 value); | ||
| 146 | void SetMustacheY(u8 value); | ||
| 147 | void SetGlassType(GlassType value); | ||
| 148 | void SetGlassColor(CommonColor value); | ||
| 149 | void SetGlassScale(u8 value); | ||
| 150 | void SetGlassY(u8 value); | ||
| 151 | void SetMoleType(MoleType value); | ||
| 152 | void SetMoleScale(u8 value); | ||
| 153 | void SetMoleX(u8 value); | ||
| 154 | void SetMoleY(u8 value); | ||
| 155 | void SetNickname(Nickname nickname); | ||
| 156 | |||
| 157 | FontRegion GetFontRegion() const; | ||
| 158 | FavoriteColor GetFavoriteColor() const; | ||
| 159 | Gender GetGender() const; | ||
| 160 | u8 GetHeight() const; | ||
| 161 | u8 GetBuild() const; | ||
| 162 | u8 GetType() const; | ||
| 163 | u8 GetRegionMove() const; | ||
| 164 | FacelineType GetFacelineType() const; | ||
| 165 | FacelineColor GetFacelineColor() const; | ||
| 166 | FacelineWrinkle GetFacelineWrinkle() const; | ||
| 167 | FacelineMake GetFacelineMake() const; | ||
| 168 | HairType GetHairType() const; | ||
| 169 | CommonColor GetHairColor() const; | ||
| 170 | HairFlip GetHairFlip() const; | ||
| 171 | EyeType GetEyeType() const; | ||
| 172 | CommonColor GetEyeColor() const; | ||
| 173 | u8 GetEyeScale() const; | ||
| 174 | u8 GetEyeAspect() const; | ||
| 175 | u8 GetEyeRotate() const; | ||
| 176 | u8 GetEyeX() const; | ||
| 177 | u8 GetEyeY() const; | ||
| 178 | EyebrowType GetEyebrowType() const; | ||
| 179 | CommonColor GetEyebrowColor() const; | ||
| 180 | u8 GetEyebrowScale() const; | ||
| 181 | u8 GetEyebrowAspect() const; | ||
| 182 | u8 GetEyebrowRotate() const; | ||
| 183 | u8 GetEyebrowX() const; | ||
| 184 | u8 GetEyebrowY() const; | ||
| 185 | NoseType GetNoseType() const; | ||
| 186 | u8 GetNoseScale() const; | ||
| 187 | u8 GetNoseY() const; | ||
| 188 | MouthType GetMouthType() const; | ||
| 189 | CommonColor GetMouthColor() const; | ||
| 190 | u8 GetMouthScale() const; | ||
| 191 | u8 GetMouthAspect() const; | ||
| 192 | u8 GetMouthY() const; | ||
| 193 | CommonColor GetBeardColor() const; | ||
| 194 | BeardType GetBeardType() const; | ||
| 195 | MustacheType GetMustacheType() const; | ||
| 196 | u8 GetMustacheScale() const; | ||
| 197 | u8 GetMustacheY() const; | ||
| 198 | GlassType GetGlassType() const; | ||
| 199 | CommonColor GetGlassColor() const; | ||
| 200 | u8 GetGlassScale() const; | ||
| 201 | u8 GetGlassY() const; | ||
| 202 | MoleType GetMoleType() const; | ||
| 203 | u8 GetMoleScale() const; | ||
| 204 | u8 GetMoleX() const; | ||
| 205 | u8 GetMoleY() const; | ||
| 206 | Nickname GetNickname() const; | ||
| 207 | Nickname GetDefaultNickname() const; | ||
| 208 | Nickname GetInvalidNickname() const; | ||
| 209 | |||
| 210 | private: | ||
| 211 | StoreDataBitFields data{}; | ||
| 212 | Nickname name{}; | ||
| 213 | }; | ||
| 214 | static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); | ||
| 215 | |||
| 216 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp index 1442280c8..5143abcc8 100644 --- a/src/core/hle/service/mii/raw_data.cpp +++ b/src/core/hle/service/mii/types/raw_data.cpp | |||
| @@ -1,11 +1,88 @@ | |||
| 1 | // SPDX-FileCopyrightText: Ryujinx Team and Contributors | 1 | // SPDX-FileCopyrightText: Ryujinx Team and Contributors |
| 2 | // SPDX-License-Identifier: MIT | 2 | // SPDX-License-Identifier: MIT |
| 3 | 3 | ||
| 4 | #include "core/hle/service/mii/raw_data.h" | 4 | #include "core/hle/service/mii/types/raw_data.h" |
| 5 | 5 | ||
| 6 | namespace Service::Mii::RawData { | 6 | namespace Service::Mii::RawData { |
| 7 | 7 | ||
| 8 | const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | 8 | constexpr std::array<u8, static_cast<u8>(FacelineColor::Count)> FromVer3FacelineColorTable{ |
| 9 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, | ||
| 10 | }; | ||
| 11 | |||
| 12 | constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3HairColorTable{ | ||
| 13 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, | ||
| 14 | 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 15 | 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, | ||
| 16 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, | ||
| 17 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, | ||
| 18 | 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, | ||
| 19 | }; | ||
| 20 | |||
| 21 | constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3EyeColorTable{ | ||
| 22 | 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, | ||
| 23 | 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 24 | 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, | ||
| 25 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, | ||
| 26 | 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, | ||
| 27 | 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, | ||
| 28 | }; | ||
| 29 | |||
| 30 | constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3MouthlineColorTable{ | ||
| 31 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, | ||
| 32 | 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, | ||
| 33 | 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, | ||
| 34 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, | ||
| 35 | 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, | ||
| 36 | 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, | ||
| 37 | }; | ||
| 38 | |||
| 39 | constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3GlassColorTable{ | ||
| 40 | 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, | ||
| 41 | 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, | ||
| 42 | 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, | ||
| 43 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, | ||
| 44 | 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, | ||
| 45 | 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, | ||
| 46 | }; | ||
| 47 | |||
| 48 | constexpr std::array<u8, static_cast<u8>(GlassType::Count)> FromVer3GlassTypeTable{ | ||
| 49 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, | ||
| 50 | 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, | ||
| 51 | }; | ||
| 52 | |||
| 53 | constexpr std::array<u8, 8> Ver3FacelineColorTable{ | ||
| 54 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, | ||
| 55 | }; | ||
| 56 | |||
| 57 | constexpr std::array<u8, 8> Ver3HairColorTable{ | ||
| 58 | 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, | ||
| 59 | }; | ||
| 60 | |||
| 61 | constexpr std::array<u8, 6> Ver3EyeColorTable{ | ||
| 62 | 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, | ||
| 63 | }; | ||
| 64 | |||
| 65 | constexpr std::array<u8, 5> Ver3MouthColorTable{ | ||
| 66 | 0x13, 0x14, 0x15, 0x16, 0x17, | ||
| 67 | }; | ||
| 68 | |||
| 69 | constexpr std::array<u8, 7> Ver3GlassColorTable{ | ||
| 70 | 0x8, 0xe, 0xf, 0x10, 0x11, 0x12, 0x0, | ||
| 71 | }; | ||
| 72 | |||
| 73 | const std::array<u8, 62> EyeRotateLookup{ | ||
| 74 | 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, | ||
| 75 | 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, | ||
| 76 | 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, | ||
| 77 | 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, | ||
| 78 | }; | ||
| 79 | |||
| 80 | const std::array<u8, 24> EyebrowRotateLookup{ | ||
| 81 | 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08, | ||
| 82 | 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05, | ||
| 83 | }; | ||
| 84 | |||
| 85 | const std::array<Service::Mii::DefaultMii, 2> BaseMii{ | ||
| 9 | Service::Mii::DefaultMii{ | 86 | Service::Mii::DefaultMii{ |
| 10 | .face_type = 0, | 87 | .face_type = 0, |
| 11 | .face_color = 0, | 88 | .face_color = 0, |
| @@ -51,11 +128,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 51 | .mole_y = 20, | 128 | .mole_y = 20, |
| 52 | .height = 64, | 129 | .height = 64, |
| 53 | .weight = 64, | 130 | .weight = 64, |
| 54 | .gender = Gender::Male, | 131 | .gender = 0, |
| 55 | .favorite_color = 0, | 132 | .favorite_color = 0, |
| 56 | .region = 0, | 133 | .region_move = 0, |
| 57 | .font_region = FontRegion::Standard, | 134 | .font_region = 0, |
| 58 | .type = 0, | 135 | .type = 0, |
| 136 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 59 | }, | 137 | }, |
| 60 | Service::Mii::DefaultMii{ | 138 | Service::Mii::DefaultMii{ |
| 61 | .face_type = 0, | 139 | .face_type = 0, |
| @@ -102,12 +180,16 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 102 | .mole_y = 20, | 180 | .mole_y = 20, |
| 103 | .height = 64, | 181 | .height = 64, |
| 104 | .weight = 64, | 182 | .weight = 64, |
| 105 | .gender = Gender::Female, | 183 | .gender = 1, |
| 106 | .favorite_color = 0, | 184 | .favorite_color = 0, |
| 107 | .region = 0, | 185 | .region_move = 0, |
| 108 | .font_region = FontRegion::Standard, | 186 | .font_region = 0, |
| 109 | .type = 0, | 187 | .type = 0, |
| 188 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 110 | }, | 189 | }, |
| 190 | }; | ||
| 191 | |||
| 192 | const std::array<Service::Mii::DefaultMii, 6> DefaultMii{ | ||
| 111 | Service::Mii::DefaultMii{ | 193 | Service::Mii::DefaultMii{ |
| 112 | .face_type = 0, | 194 | .face_type = 0, |
| 113 | .face_color = 4, | 195 | .face_color = 4, |
| @@ -153,11 +235,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 153 | .mole_y = 20, | 235 | .mole_y = 20, |
| 154 | .height = 64, | 236 | .height = 64, |
| 155 | .weight = 64, | 237 | .weight = 64, |
| 156 | .gender = Gender::Male, | 238 | .gender = 0, |
| 157 | .favorite_color = 4, | 239 | .favorite_color = 4, |
| 158 | .region = 0, | 240 | .region_move = 0, |
| 159 | .font_region = FontRegion::Standard, | 241 | .font_region = 0, |
| 160 | .type = 0, | 242 | .type = 0, |
| 243 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 161 | }, | 244 | }, |
| 162 | Service::Mii::DefaultMii{ | 245 | Service::Mii::DefaultMii{ |
| 163 | .face_type = 0, | 246 | .face_type = 0, |
| @@ -204,11 +287,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 204 | .mole_y = 20, | 287 | .mole_y = 20, |
| 205 | .height = 64, | 288 | .height = 64, |
| 206 | .weight = 64, | 289 | .weight = 64, |
| 207 | .gender = Gender::Male, | 290 | .gender = 0, |
| 208 | .favorite_color = 5, | 291 | .favorite_color = 5, |
| 209 | .region = 0, | 292 | .region_move = 0, |
| 210 | .font_region = FontRegion::Standard, | 293 | .font_region = 0, |
| 211 | .type = 0, | 294 | .type = 0, |
| 295 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 212 | }, | 296 | }, |
| 213 | Service::Mii::DefaultMii{ | 297 | Service::Mii::DefaultMii{ |
| 214 | .face_type = 0, | 298 | .face_type = 0, |
| @@ -255,11 +339,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 255 | .mole_y = 20, | 339 | .mole_y = 20, |
| 256 | .height = 64, | 340 | .height = 64, |
| 257 | .weight = 64, | 341 | .weight = 64, |
| 258 | .gender = Gender::Male, | 342 | .gender = 0, |
| 259 | .favorite_color = 0, | 343 | .favorite_color = 0, |
| 260 | .region = 0, | 344 | .region_move = 0, |
| 261 | .font_region = FontRegion::Standard, | 345 | .font_region = 0, |
| 262 | .type = 0, | 346 | .type = 0, |
| 347 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 263 | }, | 348 | }, |
| 264 | Service::Mii::DefaultMii{ | 349 | Service::Mii::DefaultMii{ |
| 265 | .face_type = 0, | 350 | .face_type = 0, |
| @@ -306,11 +391,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 306 | .mole_y = 20, | 391 | .mole_y = 20, |
| 307 | .height = 64, | 392 | .height = 64, |
| 308 | .weight = 64, | 393 | .weight = 64, |
| 309 | .gender = Gender::Female, | 394 | .gender = 1, |
| 310 | .favorite_color = 2, | 395 | .favorite_color = 2, |
| 311 | .region = 0, | 396 | .region_move = 0, |
| 312 | .font_region = FontRegion::Standard, | 397 | .font_region = 0, |
| 313 | .type = 0, | 398 | .type = 0, |
| 399 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 314 | }, | 400 | }, |
| 315 | Service::Mii::DefaultMii{ | 401 | Service::Mii::DefaultMii{ |
| 316 | .face_type = 0, | 402 | .face_type = 0, |
| @@ -357,11 +443,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 357 | .mole_y = 20, | 443 | .mole_y = 20, |
| 358 | .height = 64, | 444 | .height = 64, |
| 359 | .weight = 64, | 445 | .weight = 64, |
| 360 | .gender = Gender::Female, | 446 | .gender = 1, |
| 361 | .favorite_color = 6, | 447 | .favorite_color = 6, |
| 362 | .region = 0, | 448 | .region_move = 0, |
| 363 | .font_region = FontRegion::Standard, | 449 | .font_region = 0, |
| 364 | .type = 0, | 450 | .type = 0, |
| 451 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 365 | }, | 452 | }, |
| 366 | Service::Mii::DefaultMii{ | 453 | Service::Mii::DefaultMii{ |
| 367 | .face_type = 0, | 454 | .face_type = 0, |
| @@ -408,176 +495,177 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | |||
| 408 | .mole_y = 20, | 495 | .mole_y = 20, |
| 409 | .height = 64, | 496 | .height = 64, |
| 410 | .weight = 64, | 497 | .weight = 64, |
| 411 | .gender = Gender::Female, | 498 | .gender = 1, |
| 412 | .favorite_color = 7, | 499 | .favorite_color = 7, |
| 413 | .region = 0, | 500 | .region_move = 0, |
| 414 | .font_region = FontRegion::Standard, | 501 | .font_region = 0, |
| 415 | .type = 0, | 502 | .type = 0, |
| 503 | .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, | ||
| 416 | }, | 504 | }, |
| 417 | 505 | ||
| 418 | }; | 506 | }; |
| 419 | 507 | ||
| 420 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{ | 508 | const std::array<RandomMiiData4, 18> RandomMiiFaceline{ |
| 421 | Service::Mii::RandomMiiData4{ | 509 | RandomMiiData4{ |
| 422 | .gender = Gender::Male, | 510 | .gender = static_cast<u32>(Gender::Male), |
| 423 | .age = Age::Young, | 511 | .age = static_cast<u32>(Age::Young), |
| 424 | .race = Race::Black, | 512 | .race = static_cast<u32>(Race::Black), |
| 425 | .values_count = 10, | 513 | .values_count = 10, |
| 426 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 514 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 427 | }, | 515 | }, |
| 428 | Service::Mii::RandomMiiData4{ | 516 | RandomMiiData4{ |
| 429 | .gender = Gender::Male, | 517 | .gender = static_cast<u32>(Gender::Male), |
| 430 | .age = Age::Normal, | 518 | .age = static_cast<u32>(Age::Normal), |
| 431 | .race = Race::Black, | 519 | .race = static_cast<u32>(Race::Black), |
| 432 | .values_count = 10, | 520 | .values_count = 10, |
| 433 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 521 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 434 | }, | 522 | }, |
| 435 | Service::Mii::RandomMiiData4{ | 523 | RandomMiiData4{ |
| 436 | .gender = Gender::Male, | 524 | .gender = static_cast<u32>(Gender::Male), |
| 437 | .age = Age::Old, | 525 | .age = static_cast<u32>(Age::Old), |
| 438 | .race = Race::Black, | 526 | .race = static_cast<u32>(Race::Black), |
| 439 | .values_count = 10, | 527 | .values_count = 10, |
| 440 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 528 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 441 | }, | 529 | }, |
| 442 | Service::Mii::RandomMiiData4{ | 530 | RandomMiiData4{ |
| 443 | .gender = Gender::Male, | 531 | .gender = static_cast<u32>(Gender::Male), |
| 444 | .age = Age::Young, | 532 | .age = static_cast<u32>(Age::Young), |
| 445 | .race = Race::White, | 533 | .race = static_cast<u32>(Race::White), |
| 446 | .values_count = 12, | 534 | .values_count = 12, |
| 447 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, | 535 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, |
| 448 | }, | 536 | }, |
| 449 | Service::Mii::RandomMiiData4{ | 537 | RandomMiiData4{ |
| 450 | .gender = Gender::Male, | 538 | .gender = static_cast<u32>(Gender::Male), |
| 451 | .age = Age::Normal, | 539 | .age = static_cast<u32>(Age::Normal), |
| 452 | .race = Race::White, | 540 | .race = static_cast<u32>(Race::White), |
| 453 | .values_count = 13, | 541 | .values_count = 13, |
| 454 | .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, | 542 | .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, |
| 455 | }, | 543 | }, |
| 456 | Service::Mii::RandomMiiData4{ | 544 | RandomMiiData4{ |
| 457 | .gender = Gender::Male, | 545 | .gender = static_cast<u32>(Gender::Male), |
| 458 | .age = Age::Old, | 546 | .age = static_cast<u32>(Age::Old), |
| 459 | .race = Race::White, | 547 | .race = static_cast<u32>(Race::White), |
| 460 | .values_count = 12, | 548 | .values_count = 12, |
| 461 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, | 549 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, |
| 462 | }, | 550 | }, |
| 463 | Service::Mii::RandomMiiData4{ | 551 | RandomMiiData4{ |
| 464 | .gender = Gender::Male, | 552 | .gender = static_cast<u32>(Gender::Male), |
| 465 | .age = Age::Young, | 553 | .age = static_cast<u32>(Age::Young), |
| 466 | .race = Race::Asian, | 554 | .race = static_cast<u32>(Race::Asian), |
| 467 | .values_count = 12, | 555 | .values_count = 12, |
| 468 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, | 556 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, |
| 469 | }, | 557 | }, |
| 470 | Service::Mii::RandomMiiData4{ | 558 | RandomMiiData4{ |
| 471 | .gender = Gender::Male, | 559 | .gender = static_cast<u32>(Gender::Male), |
| 472 | .age = Age::Normal, | 560 | .age = static_cast<u32>(Age::Normal), |
| 473 | .race = Race::Asian, | 561 | .race = static_cast<u32>(Race::Asian), |
| 474 | .values_count = 13, | 562 | .values_count = 13, |
| 475 | .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, | 563 | .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, |
| 476 | }, | 564 | }, |
| 477 | Service::Mii::RandomMiiData4{ | 565 | RandomMiiData4{ |
| 478 | .gender = Gender::Male, | 566 | .gender = static_cast<u32>(Gender::Male), |
| 479 | .age = Age::Old, | 567 | .age = static_cast<u32>(Age::Old), |
| 480 | .race = Race::Asian, | 568 | .race = static_cast<u32>(Race::Asian), |
| 481 | .values_count = 12, | 569 | .values_count = 12, |
| 482 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, | 570 | .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, |
| 483 | }, | 571 | }, |
| 484 | Service::Mii::RandomMiiData4{ | 572 | RandomMiiData4{ |
| 485 | .gender = Gender::Female, | 573 | .gender = static_cast<u32>(Gender::Female), |
| 486 | .age = Age::Young, | 574 | .age = static_cast<u32>(Age::Young), |
| 487 | .race = Race::Black, | 575 | .race = static_cast<u32>(Race::Black), |
| 488 | .values_count = 10, | 576 | .values_count = 10, |
| 489 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 577 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 490 | }, | 578 | }, |
| 491 | Service::Mii::RandomMiiData4{ | 579 | RandomMiiData4{ |
| 492 | .gender = Gender::Female, | 580 | .gender = static_cast<u32>(Gender::Female), |
| 493 | .age = Age::Normal, | 581 | .age = static_cast<u32>(Age::Normal), |
| 494 | .race = Race::Black, | 582 | .race = static_cast<u32>(Race::Black), |
| 495 | .values_count = 10, | 583 | .values_count = 10, |
| 496 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 584 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 497 | }, | 585 | }, |
| 498 | Service::Mii::RandomMiiData4{ | 586 | RandomMiiData4{ |
| 499 | .gender = Gender::Female, | 587 | .gender = static_cast<u32>(Gender::Female), |
| 500 | .age = Age::Old, | 588 | .age = static_cast<u32>(Age::Old), |
| 501 | .race = Race::Black, | 589 | .race = static_cast<u32>(Race::Black), |
| 502 | .values_count = 10, | 590 | .values_count = 10, |
| 503 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, | 591 | .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, |
| 504 | }, | 592 | }, |
| 505 | Service::Mii::RandomMiiData4{ | 593 | RandomMiiData4{ |
| 506 | .gender = Gender::Female, | 594 | .gender = static_cast<u32>(Gender::Female), |
| 507 | .age = Age::Young, | 595 | .age = static_cast<u32>(Age::Young), |
| 508 | .race = Race::White, | 596 | .race = static_cast<u32>(Race::White), |
| 509 | .values_count = 12, | 597 | .values_count = 12, |
| 510 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 598 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 511 | }, | 599 | }, |
| 512 | Service::Mii::RandomMiiData4{ | 600 | RandomMiiData4{ |
| 513 | .gender = Gender::Female, | 601 | .gender = static_cast<u32>(Gender::Female), |
| 514 | .age = Age::Normal, | 602 | .age = static_cast<u32>(Age::Normal), |
| 515 | .race = Race::White, | 603 | .race = static_cast<u32>(Race::White), |
| 516 | .values_count = 12, | 604 | .values_count = 12, |
| 517 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 605 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 518 | }, | 606 | }, |
| 519 | Service::Mii::RandomMiiData4{ | 607 | RandomMiiData4{ |
| 520 | .gender = Gender::Female, | 608 | .gender = static_cast<u32>(Gender::Female), |
| 521 | .age = Age::Old, | 609 | .age = static_cast<u32>(Age::Old), |
| 522 | .race = Race::White, | 610 | .race = static_cast<u32>(Race::White), |
| 523 | .values_count = 12, | 611 | .values_count = 12, |
| 524 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 612 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 525 | }, | 613 | }, |
| 526 | Service::Mii::RandomMiiData4{ | 614 | RandomMiiData4{ |
| 527 | .gender = Gender::Female, | 615 | .gender = static_cast<u32>(Gender::Female), |
| 528 | .age = Age::Young, | 616 | .age = static_cast<u32>(Age::Young), |
| 529 | .race = Race::Asian, | 617 | .race = static_cast<u32>(Race::Asian), |
| 530 | .values_count = 12, | 618 | .values_count = 12, |
| 531 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 619 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 532 | }, | 620 | }, |
| 533 | Service::Mii::RandomMiiData4{ | 621 | RandomMiiData4{ |
| 534 | .gender = Gender::Female, | 622 | .gender = static_cast<u32>(Gender::Female), |
| 535 | .age = Age::Normal, | 623 | .age = static_cast<u32>(Age::Normal), |
| 536 | .race = Race::Asian, | 624 | .race = static_cast<u32>(Race::Asian), |
| 537 | .values_count = 12, | 625 | .values_count = 12, |
| 538 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 626 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 539 | }, | 627 | }, |
| 540 | Service::Mii::RandomMiiData4{ | 628 | RandomMiiData4{ |
| 541 | .gender = Gender::Female, | 629 | .gender = static_cast<u32>(Gender::Female), |
| 542 | .age = Age::Old, | 630 | .age = static_cast<u32>(Age::Old), |
| 543 | .race = Race::Asian, | 631 | .race = static_cast<u32>(Race::Asian), |
| 544 | .values_count = 12, | 632 | .values_count = 12, |
| 545 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, | 633 | .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, |
| 546 | }, | 634 | }, |
| 547 | }; | 635 | }; |
| 548 | 636 | ||
| 549 | const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ | 637 | const std::array<RandomMiiData3, 6> RandomMiiFacelineColor{ |
| 550 | Service::Mii::RandomMiiData3{ | 638 | RandomMiiData3{ |
| 551 | .arg_1 = 0, | 639 | .arg_1 = 0, |
| 552 | .arg_2 = 0, | 640 | .arg_2 = 0, |
| 553 | .values_count = 10, | 641 | .values_count = 10, |
| 554 | .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, | 642 | .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, |
| 555 | }, | 643 | }, |
| 556 | Service::Mii::RandomMiiData3{ | 644 | RandomMiiData3{ |
| 557 | .arg_1 = 0, | 645 | .arg_1 = 0, |
| 558 | .arg_2 = 1, | 646 | .arg_2 = 1, |
| 559 | .values_count = 10, | 647 | .values_count = 10, |
| 560 | .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3}, | 648 | .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3}, |
| 561 | }, | 649 | }, |
| 562 | Service::Mii::RandomMiiData3{ | 650 | RandomMiiData3{ |
| 563 | .arg_1 = 0, | 651 | .arg_1 = 0, |
| 564 | .arg_2 = 2, | 652 | .arg_2 = 2, |
| 565 | .values_count = 10, | 653 | .values_count = 10, |
| 566 | .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2}, | 654 | .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2}, |
| 567 | }, | 655 | }, |
| 568 | Service::Mii::RandomMiiData3{ | 656 | RandomMiiData3{ |
| 569 | .arg_1 = 1, | 657 | .arg_1 = 1, |
| 570 | .arg_2 = 0, | 658 | .arg_2 = 0, |
| 571 | .values_count = 10, | 659 | .values_count = 10, |
| 572 | .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, | 660 | .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, |
| 573 | }, | 661 | }, |
| 574 | Service::Mii::RandomMiiData3{ | 662 | RandomMiiData3{ |
| 575 | .arg_1 = 1, | 663 | .arg_1 = 1, |
| 576 | .arg_2 = 1, | 664 | .arg_2 = 1, |
| 577 | .values_count = 10, | 665 | .values_count = 10, |
| 578 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3}, | 666 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3}, |
| 579 | }, | 667 | }, |
| 580 | Service::Mii::RandomMiiData3{ | 668 | RandomMiiData3{ |
| 581 | .arg_1 = 1, | 669 | .arg_1 = 1, |
| 582 | .arg_2 = 2, | 670 | .arg_2 = 2, |
| 583 | .values_count = 10, | 671 | .values_count = 10, |
| @@ -585,407 +673,407 @@ const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ | |||
| 585 | }, | 673 | }, |
| 586 | }; | 674 | }; |
| 587 | 675 | ||
| 588 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{ | 676 | const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle{ |
| 589 | Service::Mii::RandomMiiData4{ | 677 | RandomMiiData4{ |
| 590 | .gender = Gender::Male, | 678 | .gender = static_cast<u32>(Gender::Male), |
| 591 | .age = Age::Young, | 679 | .age = static_cast<u32>(Age::Young), |
| 592 | .race = Race::Black, | 680 | .race = static_cast<u32>(Race::Black), |
| 593 | .values_count = 20, | 681 | .values_count = 20, |
| 594 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 682 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 595 | }, | 683 | }, |
| 596 | Service::Mii::RandomMiiData4{ | 684 | RandomMiiData4{ |
| 597 | .gender = Gender::Male, | 685 | .gender = static_cast<u32>(Gender::Male), |
| 598 | .age = Age::Normal, | 686 | .age = static_cast<u32>(Age::Normal), |
| 599 | .race = Race::Black, | 687 | .race = static_cast<u32>(Race::Black), |
| 600 | .values_count = 20, | 688 | .values_count = 20, |
| 601 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 689 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 602 | }, | 690 | }, |
| 603 | Service::Mii::RandomMiiData4{ | 691 | RandomMiiData4{ |
| 604 | .gender = Gender::Male, | 692 | .gender = static_cast<u32>(Gender::Male), |
| 605 | .age = Age::Old, | 693 | .age = static_cast<u32>(Age::Old), |
| 606 | .race = Race::Black, | 694 | .race = static_cast<u32>(Race::Black), |
| 607 | .values_count = 20, | 695 | .values_count = 20, |
| 608 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8}, | 696 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8}, |
| 609 | }, | 697 | }, |
| 610 | Service::Mii::RandomMiiData4{ | 698 | RandomMiiData4{ |
| 611 | .gender = Gender::Male, | 699 | .gender = static_cast<u32>(Gender::Male), |
| 612 | .age = Age::Young, | 700 | .age = static_cast<u32>(Age::Young), |
| 613 | .race = Race::White, | 701 | .race = static_cast<u32>(Race::White), |
| 614 | .values_count = 20, | 702 | .values_count = 20, |
| 615 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, | 703 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, |
| 616 | }, | 704 | }, |
| 617 | Service::Mii::RandomMiiData4{ | 705 | RandomMiiData4{ |
| 618 | .gender = Gender::Male, | 706 | .gender = static_cast<u32>(Gender::Male), |
| 619 | .age = Age::Normal, | 707 | .age = static_cast<u32>(Age::Normal), |
| 620 | .race = Race::White, | 708 | .race = static_cast<u32>(Race::White), |
| 621 | .values_count = 20, | 709 | .values_count = 20, |
| 622 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, | 710 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, |
| 623 | }, | 711 | }, |
| 624 | Service::Mii::RandomMiiData4{ | 712 | RandomMiiData4{ |
| 625 | .gender = Gender::Male, | 713 | .gender = static_cast<u32>(Gender::Male), |
| 626 | .age = Age::Old, | 714 | .age = static_cast<u32>(Age::Old), |
| 627 | .race = Race::White, | 715 | .race = static_cast<u32>(Race::White), |
| 628 | .values_count = 20, | 716 | .values_count = 20, |
| 629 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, | 717 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, |
| 630 | }, | 718 | }, |
| 631 | Service::Mii::RandomMiiData4{ | 719 | RandomMiiData4{ |
| 632 | .gender = Gender::Male, | 720 | .gender = static_cast<u32>(Gender::Male), |
| 633 | .age = Age::Young, | 721 | .age = static_cast<u32>(Age::Young), |
| 634 | .race = Race::Asian, | 722 | .race = static_cast<u32>(Race::Asian), |
| 635 | .values_count = 20, | 723 | .values_count = 20, |
| 636 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, | 724 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, |
| 637 | }, | 725 | }, |
| 638 | Service::Mii::RandomMiiData4{ | 726 | RandomMiiData4{ |
| 639 | .gender = Gender::Male, | 727 | .gender = static_cast<u32>(Gender::Male), |
| 640 | .age = Age::Normal, | 728 | .age = static_cast<u32>(Age::Normal), |
| 641 | .race = Race::Asian, | 729 | .race = static_cast<u32>(Race::Asian), |
| 642 | .values_count = 20, | 730 | .values_count = 20, |
| 643 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, | 731 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, |
| 644 | }, | 732 | }, |
| 645 | Service::Mii::RandomMiiData4{ | 733 | RandomMiiData4{ |
| 646 | .gender = Gender::Male, | 734 | .gender = static_cast<u32>(Gender::Male), |
| 647 | .age = Age::Old, | 735 | .age = static_cast<u32>(Age::Old), |
| 648 | .race = Race::Asian, | 736 | .race = static_cast<u32>(Race::Asian), |
| 649 | .values_count = 20, | 737 | .values_count = 20, |
| 650 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, | 738 | .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, |
| 651 | }, | 739 | }, |
| 652 | Service::Mii::RandomMiiData4{ | 740 | RandomMiiData4{ |
| 653 | .gender = Gender::Female, | 741 | .gender = static_cast<u32>(Gender::Female), |
| 654 | .age = Age::Young, | 742 | .age = static_cast<u32>(Age::Young), |
| 655 | .race = Race::Black, | 743 | .race = static_cast<u32>(Race::Black), |
| 656 | .values_count = 20, | 744 | .values_count = 20, |
| 657 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 745 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 658 | }, | 746 | }, |
| 659 | Service::Mii::RandomMiiData4{ | 747 | RandomMiiData4{ |
| 660 | .gender = Gender::Female, | 748 | .gender = static_cast<u32>(Gender::Female), |
| 661 | .age = Age::Normal, | 749 | .age = static_cast<u32>(Age::Normal), |
| 662 | .race = Race::Black, | 750 | .race = static_cast<u32>(Race::Black), |
| 663 | .values_count = 20, | 751 | .values_count = 20, |
| 664 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 752 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 665 | }, | 753 | }, |
| 666 | Service::Mii::RandomMiiData4{ | 754 | RandomMiiData4{ |
| 667 | .gender = Gender::Female, | 755 | .gender = static_cast<u32>(Gender::Female), |
| 668 | .age = Age::Old, | 756 | .age = static_cast<u32>(Age::Old), |
| 669 | .race = Race::Black, | 757 | .race = static_cast<u32>(Race::Black), |
| 670 | .values_count = 20, | 758 | .values_count = 20, |
| 671 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, | 759 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, |
| 672 | }, | 760 | }, |
| 673 | Service::Mii::RandomMiiData4{ | 761 | RandomMiiData4{ |
| 674 | .gender = Gender::Female, | 762 | .gender = static_cast<u32>(Gender::Female), |
| 675 | .age = Age::Young, | 763 | .age = static_cast<u32>(Age::Young), |
| 676 | .race = Race::White, | 764 | .race = static_cast<u32>(Race::White), |
| 677 | .values_count = 20, | 765 | .values_count = 20, |
| 678 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, | 766 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, |
| 679 | }, | 767 | }, |
| 680 | Service::Mii::RandomMiiData4{ | 768 | RandomMiiData4{ |
| 681 | .gender = Gender::Female, | 769 | .gender = static_cast<u32>(Gender::Female), |
| 682 | .age = Age::Normal, | 770 | .age = static_cast<u32>(Age::Normal), |
| 683 | .race = Race::White, | 771 | .race = static_cast<u32>(Race::White), |
| 684 | .values_count = 20, | 772 | .values_count = 20, |
| 685 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, | 773 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, |
| 686 | }, | 774 | }, |
| 687 | Service::Mii::RandomMiiData4{ | 775 | RandomMiiData4{ |
| 688 | .gender = Gender::Female, | 776 | .gender = static_cast<u32>(Gender::Female), |
| 689 | .age = Age::Old, | 777 | .age = static_cast<u32>(Age::Old), |
| 690 | .race = Race::White, | 778 | .race = static_cast<u32>(Race::White), |
| 691 | .values_count = 20, | 779 | .values_count = 20, |
| 692 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4}, | 780 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4}, |
| 693 | }, | 781 | }, |
| 694 | Service::Mii::RandomMiiData4{ | 782 | RandomMiiData4{ |
| 695 | .gender = Gender::Female, | 783 | .gender = static_cast<u32>(Gender::Female), |
| 696 | .age = Age::Young, | 784 | .age = static_cast<u32>(Age::Young), |
| 697 | .race = Race::Asian, | 785 | .race = static_cast<u32>(Race::Asian), |
| 698 | .values_count = 20, | 786 | .values_count = 20, |
| 699 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, | 787 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, |
| 700 | }, | 788 | }, |
| 701 | Service::Mii::RandomMiiData4{ | 789 | RandomMiiData4{ |
| 702 | .gender = Gender::Female, | 790 | .gender = static_cast<u32>(Gender::Female), |
| 703 | .age = Age::Normal, | 791 | .age = static_cast<u32>(Age::Normal), |
| 704 | .race = Race::Asian, | 792 | .race = static_cast<u32>(Race::Asian), |
| 705 | .values_count = 20, | 793 | .values_count = 20, |
| 706 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, | 794 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, |
| 707 | }, | 795 | }, |
| 708 | Service::Mii::RandomMiiData4{ | 796 | RandomMiiData4{ |
| 709 | .gender = Gender::Female, | 797 | .gender = static_cast<u32>(Gender::Female), |
| 710 | .age = Age::Old, | 798 | .age = static_cast<u32>(Age::Old), |
| 711 | .race = Race::Asian, | 799 | .race = static_cast<u32>(Race::Asian), |
| 712 | .values_count = 20, | 800 | .values_count = 20, |
| 713 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, | 801 | .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, |
| 714 | }, | 802 | }, |
| 715 | }; | 803 | }; |
| 716 | 804 | ||
| 717 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{ | 805 | const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup{ |
| 718 | Service::Mii::RandomMiiData4{ | 806 | RandomMiiData4{ |
| 719 | .gender = Gender::Male, | 807 | .gender = static_cast<u32>(Gender::Male), |
| 720 | .age = Age::Young, | 808 | .age = static_cast<u32>(Age::Young), |
| 721 | .race = Race::Black, | 809 | .race = static_cast<u32>(Race::Black), |
| 722 | .values_count = 20, | 810 | .values_count = 20, |
| 723 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 811 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
| 724 | }, | 812 | }, |
| 725 | Service::Mii::RandomMiiData4{ | 813 | RandomMiiData4{ |
| 726 | .gender = Gender::Male, | 814 | .gender = static_cast<u32>(Gender::Male), |
| 727 | .age = Age::Normal, | 815 | .age = static_cast<u32>(Age::Normal), |
| 728 | .race = Race::Black, | 816 | .race = static_cast<u32>(Race::Black), |
| 729 | .values_count = 20, | 817 | .values_count = 20, |
| 730 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9}, | 818 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9}, |
| 731 | }, | 819 | }, |
| 732 | Service::Mii::RandomMiiData4{ | 820 | RandomMiiData4{ |
| 733 | .gender = Gender::Male, | 821 | .gender = static_cast<u32>(Gender::Male), |
| 734 | .age = Age::Old, | 822 | .age = static_cast<u32>(Age::Old), |
| 735 | .race = Race::Black, | 823 | .race = static_cast<u32>(Race::Black), |
| 736 | .values_count = 20, | 824 | .values_count = 20, |
| 737 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9}, | 825 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9}, |
| 738 | }, | 826 | }, |
| 739 | Service::Mii::RandomMiiData4{ | 827 | RandomMiiData4{ |
| 740 | .gender = Gender::Male, | 828 | .gender = static_cast<u32>(Gender::Male), |
| 741 | .age = Age::Young, | 829 | .age = static_cast<u32>(Age::Young), |
| 742 | .race = Race::White, | 830 | .race = static_cast<u32>(Race::White), |
| 743 | .values_count = 20, | 831 | .values_count = 20, |
| 744 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 832 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 745 | }, | 833 | }, |
| 746 | Service::Mii::RandomMiiData4{ | 834 | RandomMiiData4{ |
| 747 | .gender = Gender::Male, | 835 | .gender = static_cast<u32>(Gender::Male), |
| 748 | .age = Age::Normal, | 836 | .age = static_cast<u32>(Age::Normal), |
| 749 | .race = Race::White, | 837 | .race = static_cast<u32>(Race::White), |
| 750 | .values_count = 20, | 838 | .values_count = 20, |
| 751 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 839 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 752 | }, | 840 | }, |
| 753 | Service::Mii::RandomMiiData4{ | 841 | RandomMiiData4{ |
| 754 | .gender = Gender::Male, | 842 | .gender = static_cast<u32>(Gender::Male), |
| 755 | .age = Age::Old, | 843 | .age = static_cast<u32>(Age::Old), |
| 756 | .race = Race::White, | 844 | .race = static_cast<u32>(Race::White), |
| 757 | .values_count = 20, | 845 | .values_count = 20, |
| 758 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 846 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 759 | }, | 847 | }, |
| 760 | Service::Mii::RandomMiiData4{ | 848 | RandomMiiData4{ |
| 761 | .gender = Gender::Male, | 849 | .gender = static_cast<u32>(Gender::Male), |
| 762 | .age = Age::Young, | 850 | .age = static_cast<u32>(Age::Young), |
| 763 | .race = Race::Asian, | 851 | .race = static_cast<u32>(Race::Asian), |
| 764 | .values_count = 20, | 852 | .values_count = 20, |
| 765 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 853 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 766 | }, | 854 | }, |
| 767 | Service::Mii::RandomMiiData4{ | 855 | RandomMiiData4{ |
| 768 | .gender = Gender::Male, | 856 | .gender = static_cast<u32>(Gender::Male), |
| 769 | .age = Age::Normal, | 857 | .age = static_cast<u32>(Age::Normal), |
| 770 | .race = Race::Asian, | 858 | .race = static_cast<u32>(Race::Asian), |
| 771 | .values_count = 20, | 859 | .values_count = 20, |
| 772 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 860 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 773 | }, | 861 | }, |
| 774 | Service::Mii::RandomMiiData4{ | 862 | RandomMiiData4{ |
| 775 | .gender = Gender::Male, | 863 | .gender = static_cast<u32>(Gender::Male), |
| 776 | .age = Age::Old, | 864 | .age = static_cast<u32>(Age::Old), |
| 777 | .race = Race::Asian, | 865 | .race = static_cast<u32>(Race::Asian), |
| 778 | .values_count = 20, | 866 | .values_count = 20, |
| 779 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, | 867 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, |
| 780 | }, | 868 | }, |
| 781 | Service::Mii::RandomMiiData4{ | 869 | RandomMiiData4{ |
| 782 | .gender = Gender::Female, | 870 | .gender = static_cast<u32>(Gender::Female), |
| 783 | .age = Age::Young, | 871 | .age = static_cast<u32>(Age::Young), |
| 784 | .race = Race::Black, | 872 | .race = static_cast<u32>(Race::Black), |
| 785 | .values_count = 20, | 873 | .values_count = 20, |
| 786 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2}, | 874 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2}, |
| 787 | }, | 875 | }, |
| 788 | Service::Mii::RandomMiiData4{ | 876 | RandomMiiData4{ |
| 789 | .gender = Gender::Female, | 877 | .gender = static_cast<u32>(Gender::Female), |
| 790 | .age = Age::Normal, | 878 | .age = static_cast<u32>(Age::Normal), |
| 791 | .race = Race::Black, | 879 | .race = static_cast<u32>(Race::Black), |
| 792 | .values_count = 20, | 880 | .values_count = 20, |
| 793 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, | 881 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, |
| 794 | }, | 882 | }, |
| 795 | Service::Mii::RandomMiiData4{ | 883 | RandomMiiData4{ |
| 796 | .gender = Gender::Female, | 884 | .gender = static_cast<u32>(Gender::Female), |
| 797 | .age = Age::Old, | 885 | .age = static_cast<u32>(Age::Old), |
| 798 | .race = Race::Black, | 886 | .race = static_cast<u32>(Race::Black), |
| 799 | .values_count = 20, | 887 | .values_count = 20, |
| 800 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, | 888 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, |
| 801 | }, | 889 | }, |
| 802 | Service::Mii::RandomMiiData4{ | 890 | RandomMiiData4{ |
| 803 | .gender = Gender::Female, | 891 | .gender = static_cast<u32>(Gender::Female), |
| 804 | .age = Age::Young, | 892 | .age = static_cast<u32>(Age::Young), |
| 805 | .race = Race::White, | 893 | .race = static_cast<u32>(Race::White), |
| 806 | .values_count = 20, | 894 | .values_count = 20, |
| 807 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9}, | 895 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9}, |
| 808 | }, | 896 | }, |
| 809 | Service::Mii::RandomMiiData4{ | 897 | RandomMiiData4{ |
| 810 | .gender = Gender::Female, | 898 | .gender = static_cast<u32>(Gender::Female), |
| 811 | .age = Age::Normal, | 899 | .age = static_cast<u32>(Age::Normal), |
| 812 | .race = Race::White, | 900 | .race = static_cast<u32>(Race::White), |
| 813 | .values_count = 20, | 901 | .values_count = 20, |
| 814 | .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, | 902 | .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, |
| 815 | }, | 903 | }, |
| 816 | Service::Mii::RandomMiiData4{ | 904 | RandomMiiData4{ |
| 817 | .gender = Gender::Female, | 905 | .gender = static_cast<u32>(Gender::Female), |
| 818 | .age = Age::Old, | 906 | .age = static_cast<u32>(Age::Old), |
| 819 | .race = Race::White, | 907 | .race = static_cast<u32>(Race::White), |
| 820 | .values_count = 20, | 908 | .values_count = 20, |
| 821 | .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9}, | 909 | .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9}, |
| 822 | }, | 910 | }, |
| 823 | Service::Mii::RandomMiiData4{ | 911 | RandomMiiData4{ |
| 824 | .gender = Gender::Female, | 912 | .gender = static_cast<u32>(Gender::Female), |
| 825 | .age = Age::Young, | 913 | .age = static_cast<u32>(Age::Young), |
| 826 | .race = Race::Asian, | 914 | .race = static_cast<u32>(Race::Asian), |
| 827 | .values_count = 20, | 915 | .values_count = 20, |
| 828 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, | 916 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, |
| 829 | }, | 917 | }, |
| 830 | Service::Mii::RandomMiiData4{ | 918 | RandomMiiData4{ |
| 831 | .gender = Gender::Female, | 919 | .gender = static_cast<u32>(Gender::Female), |
| 832 | .age = Age::Normal, | 920 | .age = static_cast<u32>(Age::Normal), |
| 833 | .race = Race::Asian, | 921 | .race = static_cast<u32>(Race::Asian), |
| 834 | .values_count = 20, | 922 | .values_count = 20, |
| 835 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, | 923 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, |
| 836 | }, | 924 | }, |
| 837 | Service::Mii::RandomMiiData4{ | 925 | RandomMiiData4{ |
| 838 | .gender = Gender::Female, | 926 | .gender = static_cast<u32>(Gender::Female), |
| 839 | .age = Age::Old, | 927 | .age = static_cast<u32>(Age::Old), |
| 840 | .race = Race::Asian, | 928 | .race = static_cast<u32>(Race::Asian), |
| 841 | .values_count = 20, | 929 | .values_count = 20, |
| 842 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, | 930 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, |
| 843 | }, | 931 | }, |
| 844 | }; | 932 | }; |
| 845 | 933 | ||
| 846 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ | 934 | const std::array<RandomMiiData4, 18> RandomMiiHairType{ |
| 847 | Service::Mii::RandomMiiData4{ | 935 | RandomMiiData4{ |
| 848 | .gender = Gender::Male, | 936 | .gender = static_cast<u32>(Gender::Male), |
| 849 | .age = Age::Young, | 937 | .age = static_cast<u32>(Age::Young), |
| 850 | .race = Race::Black, | 938 | .race = static_cast<u32>(Race::Black), |
| 851 | .values_count = 30, | 939 | .values_count = 30, |
| 852 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, | 940 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, |
| 853 | 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89}, | 941 | 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89}, |
| 854 | }, | 942 | }, |
| 855 | Service::Mii::RandomMiiData4{ | 943 | RandomMiiData4{ |
| 856 | .gender = Gender::Male, | 944 | .gender = static_cast<u32>(Gender::Male), |
| 857 | .age = Age::Normal, | 945 | .age = static_cast<u32>(Age::Normal), |
| 858 | .race = Race::Black, | 946 | .race = static_cast<u32>(Race::Black), |
| 859 | .values_count = 31, | 947 | .values_count = 31, |
| 860 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, | 948 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, |
| 861 | 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, | 949 | 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, |
| 862 | }, | 950 | }, |
| 863 | Service::Mii::RandomMiiData4{ | 951 | RandomMiiData4{ |
| 864 | .gender = Gender::Male, | 952 | .gender = static_cast<u32>(Gender::Male), |
| 865 | .age = Age::Old, | 953 | .age = static_cast<u32>(Age::Old), |
| 866 | .race = Race::Black, | 954 | .race = static_cast<u32>(Race::Black), |
| 867 | .values_count = 31, | 955 | .values_count = 31, |
| 868 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, | 956 | .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, |
| 869 | 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, | 957 | 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, |
| 870 | }, | 958 | }, |
| 871 | Service::Mii::RandomMiiData4{ | 959 | RandomMiiData4{ |
| 872 | .gender = Gender::Male, | 960 | .gender = static_cast<u32>(Gender::Male), |
| 873 | .age = Age::Young, | 961 | .age = static_cast<u32>(Age::Young), |
| 874 | .race = Race::White, | 962 | .race = static_cast<u32>(Race::White), |
| 875 | .values_count = 38, | 963 | .values_count = 38, |
| 876 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50, | 964 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50, |
| 877 | 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89}, | 965 | 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89}, |
| 878 | }, | 966 | }, |
| 879 | Service::Mii::RandomMiiData4{ | 967 | RandomMiiData4{ |
| 880 | .gender = Gender::Male, | 968 | .gender = static_cast<u32>(Gender::Male), |
| 881 | .age = Age::Normal, | 969 | .age = static_cast<u32>(Age::Normal), |
| 882 | .race = Race::White, | 970 | .race = static_cast<u32>(Race::White), |
| 883 | .values_count = 39, | 971 | .values_count = 39, |
| 884 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, | 972 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, |
| 885 | 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, | 973 | 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, |
| 886 | }, | 974 | }, |
| 887 | Service::Mii::RandomMiiData4{ | 975 | RandomMiiData4{ |
| 888 | .gender = Gender::Male, | 976 | .gender = static_cast<u32>(Gender::Male), |
| 889 | .age = Age::Old, | 977 | .age = static_cast<u32>(Age::Old), |
| 890 | .race = Race::White, | 978 | .race = static_cast<u32>(Race::White), |
| 891 | .values_count = 39, | 979 | .values_count = 39, |
| 892 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, | 980 | .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, |
| 893 | 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, | 981 | 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, |
| 894 | }, | 982 | }, |
| 895 | Service::Mii::RandomMiiData4{ | 983 | RandomMiiData4{ |
| 896 | .gender = Gender::Male, | 984 | .gender = static_cast<u32>(Gender::Male), |
| 897 | .age = Age::Young, | 985 | .age = static_cast<u32>(Age::Young), |
| 898 | .race = Race::Asian, | 986 | .race = static_cast<u32>(Race::Asian), |
| 899 | .values_count = 18, | 987 | .values_count = 18, |
| 900 | .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, | 988 | .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, |
| 901 | }, | 989 | }, |
| 902 | Service::Mii::RandomMiiData4{ | 990 | RandomMiiData4{ |
| 903 | .gender = Gender::Male, | 991 | .gender = static_cast<u32>(Gender::Male), |
| 904 | .age = Age::Normal, | 992 | .age = static_cast<u32>(Age::Normal), |
| 905 | .race = Race::Asian, | 993 | .race = static_cast<u32>(Race::Asian), |
| 906 | .values_count = 19, | 994 | .values_count = 19, |
| 907 | .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, | 995 | .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, |
| 908 | }, | 996 | }, |
| 909 | Service::Mii::RandomMiiData4{ | 997 | RandomMiiData4{ |
| 910 | .gender = Gender::Male, | 998 | .gender = static_cast<u32>(Gender::Male), |
| 911 | .age = Age::Old, | 999 | .age = static_cast<u32>(Age::Old), |
| 912 | .race = Race::Asian, | 1000 | .race = static_cast<u32>(Race::Asian), |
| 913 | .values_count = 19, | 1001 | .values_count = 19, |
| 914 | .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, | 1002 | .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, |
| 915 | }, | 1003 | }, |
| 916 | Service::Mii::RandomMiiData4{ | 1004 | RandomMiiData4{ |
| 917 | .gender = Gender::Female, | 1005 | .gender = static_cast<u32>(Gender::Female), |
| 918 | .age = Age::Young, | 1006 | .age = static_cast<u32>(Age::Young), |
| 919 | .race = Race::Black, | 1007 | .race = static_cast<u32>(Race::Black), |
| 920 | .values_count = 39, | 1008 | .values_count = 39, |
| 921 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, | 1009 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, |
| 922 | 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85}, | 1010 | 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85}, |
| 923 | }, | 1011 | }, |
| 924 | Service::Mii::RandomMiiData4{ | 1012 | RandomMiiData4{ |
| 925 | .gender = Gender::Female, | 1013 | .gender = static_cast<u32>(Gender::Female), |
| 926 | .age = Age::Normal, | 1014 | .age = static_cast<u32>(Age::Normal), |
| 927 | .race = Race::Black, | 1015 | .race = static_cast<u32>(Race::Black), |
| 928 | .values_count = 42, | 1016 | .values_count = 42, |
| 929 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, | 1017 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, |
| 930 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, | 1018 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, |
| 931 | 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, | 1019 | 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, |
| 932 | }, | 1020 | }, |
| 933 | Service::Mii::RandomMiiData4{ | 1021 | RandomMiiData4{ |
| 934 | .gender = Gender::Female, | 1022 | .gender = static_cast<u32>(Gender::Female), |
| 935 | .age = Age::Old, | 1023 | .age = static_cast<u32>(Age::Old), |
| 936 | .race = Race::Black, | 1024 | .race = static_cast<u32>(Race::Black), |
| 937 | .values_count = 42, | 1025 | .values_count = 42, |
| 938 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, | 1026 | .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, |
| 939 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, | 1027 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, |
| 940 | 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, | 1028 | 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, |
| 941 | }, | 1029 | }, |
| 942 | Service::Mii::RandomMiiData4{ | 1030 | RandomMiiData4{ |
| 943 | .gender = Gender::Female, | 1031 | .gender = static_cast<u32>(Gender::Female), |
| 944 | .age = Age::Young, | 1032 | .age = static_cast<u32>(Age::Young), |
| 945 | .race = Race::White, | 1033 | .race = static_cast<u32>(Race::White), |
| 946 | .values_count = 44, | 1034 | .values_count = 44, |
| 947 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | 1035 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 948 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50, | 1036 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50, |
| 949 | 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86}, | 1037 | 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86}, |
| 950 | }, | 1038 | }, |
| 951 | Service::Mii::RandomMiiData4{ | 1039 | RandomMiiData4{ |
| 952 | .gender = Gender::Female, | 1040 | .gender = static_cast<u32>(Gender::Female), |
| 953 | .age = Age::Normal, | 1041 | .age = static_cast<u32>(Age::Normal), |
| 954 | .race = Race::White, | 1042 | .race = static_cast<u32>(Race::White), |
| 955 | .values_count = 44, | 1043 | .values_count = 44, |
| 956 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | 1044 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 957 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, | 1045 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, |
| 958 | 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, | 1046 | 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, |
| 959 | }, | 1047 | }, |
| 960 | Service::Mii::RandomMiiData4{ | 1048 | RandomMiiData4{ |
| 961 | .gender = Gender::Female, | 1049 | .gender = static_cast<u32>(Gender::Female), |
| 962 | .age = Age::Old, | 1050 | .age = static_cast<u32>(Age::Old), |
| 963 | .race = Race::White, | 1051 | .race = static_cast<u32>(Race::White), |
| 964 | .values_count = 44, | 1052 | .values_count = 44, |
| 965 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | 1053 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 966 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, | 1054 | 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, |
| 967 | 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, | 1055 | 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, |
| 968 | }, | 1056 | }, |
| 969 | Service::Mii::RandomMiiData4{ | 1057 | RandomMiiData4{ |
| 970 | .gender = Gender::Female, | 1058 | .gender = static_cast<u32>(Gender::Female), |
| 971 | .age = Age::Young, | 1059 | .age = static_cast<u32>(Age::Young), |
| 972 | .race = Race::Asian, | 1060 | .race = static_cast<u32>(Race::Asian), |
| 973 | .values_count = 24, | 1061 | .values_count = 24, |
| 974 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, | 1062 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, |
| 975 | 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83}, | 1063 | 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83}, |
| 976 | }, | 1064 | }, |
| 977 | Service::Mii::RandomMiiData4{ | 1065 | RandomMiiData4{ |
| 978 | .gender = Gender::Female, | 1066 | .gender = static_cast<u32>(Gender::Female), |
| 979 | .age = Age::Normal, | 1067 | .age = static_cast<u32>(Age::Normal), |
| 980 | .race = Race::Asian, | 1068 | .race = static_cast<u32>(Race::Asian), |
| 981 | .values_count = 27, | 1069 | .values_count = 27, |
| 982 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, | 1070 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, |
| 983 | 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, | 1071 | 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, |
| 984 | }, | 1072 | }, |
| 985 | Service::Mii::RandomMiiData4{ | 1073 | RandomMiiData4{ |
| 986 | .gender = Gender::Female, | 1074 | .gender = static_cast<u32>(Gender::Female), |
| 987 | .age = Age::Old, | 1075 | .age = static_cast<u32>(Age::Old), |
| 988 | .race = Race::Asian, | 1076 | .race = static_cast<u32>(Race::Asian), |
| 989 | .values_count = 27, | 1077 | .values_count = 27, |
| 990 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, | 1078 | .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, |
| 991 | 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, | 1079 | 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, |
| @@ -993,55 +1081,55 @@ const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ | |||
| 993 | }; | 1081 | }; |
| 994 | 1082 | ||
| 995 | const std::array<RandomMiiData3, 9> RandomMiiHairColor{ | 1083 | const std::array<RandomMiiData3, 9> RandomMiiHairColor{ |
| 996 | Service::Mii::RandomMiiData3{ | 1084 | RandomMiiData3{ |
| 997 | .arg_1 = 0, | 1085 | .arg_1 = 0, |
| 998 | .arg_2 = 0, | 1086 | .arg_2 = 0, |
| 999 | .values_count = 20, | 1087 | .values_count = 20, |
| 1000 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 1088 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
| 1001 | }, | 1089 | }, |
| 1002 | Service::Mii::RandomMiiData3{ | 1090 | RandomMiiData3{ |
| 1003 | .arg_1 = 0, | 1091 | .arg_1 = 0, |
| 1004 | .arg_2 = 1, | 1092 | .arg_2 = 1, |
| 1005 | .values_count = 20, | 1093 | .values_count = 20, |
| 1006 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | 1094 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
| 1007 | }, | 1095 | }, |
| 1008 | Service::Mii::RandomMiiData3{ | 1096 | RandomMiiData3{ |
| 1009 | .arg_1 = 0, | 1097 | .arg_1 = 0, |
| 1010 | .arg_2 = 2, | 1098 | .arg_2 = 2, |
| 1011 | .values_count = 20, | 1099 | .values_count = 20, |
| 1012 | .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, | 1100 | .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, |
| 1013 | }, | 1101 | }, |
| 1014 | Service::Mii::RandomMiiData3{ | 1102 | RandomMiiData3{ |
| 1015 | .arg_1 = 1, | 1103 | .arg_1 = 1, |
| 1016 | .arg_2 = 0, | 1104 | .arg_2 = 0, |
| 1017 | .values_count = 20, | 1105 | .values_count = 20, |
| 1018 | .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7}, | 1106 | .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7}, |
| 1019 | }, | 1107 | }, |
| 1020 | Service::Mii::RandomMiiData3{ | 1108 | RandomMiiData3{ |
| 1021 | .arg_1 = 1, | 1109 | .arg_1 = 1, |
| 1022 | .arg_2 = 1, | 1110 | .arg_2 = 1, |
| 1023 | .values_count = 20, | 1111 | .values_count = 20, |
| 1024 | .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, | 1112 | .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, |
| 1025 | }, | 1113 | }, |
| 1026 | Service::Mii::RandomMiiData3{ | 1114 | RandomMiiData3{ |
| 1027 | .arg_1 = 1, | 1115 | .arg_1 = 1, |
| 1028 | .arg_2 = 2, | 1116 | .arg_2 = 2, |
| 1029 | .values_count = 20, | 1117 | .values_count = 20, |
| 1030 | .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, | 1118 | .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, |
| 1031 | }, | 1119 | }, |
| 1032 | Service::Mii::RandomMiiData3{ | 1120 | RandomMiiData3{ |
| 1033 | .arg_1 = 2, | 1121 | .arg_1 = 2, |
| 1034 | .arg_2 = 0, | 1122 | .arg_2 = 0, |
| 1035 | .values_count = 20, | 1123 | .values_count = 20, |
| 1036 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, | 1124 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, |
| 1037 | }, | 1125 | }, |
| 1038 | Service::Mii::RandomMiiData3{ | 1126 | RandomMiiData3{ |
| 1039 | .arg_1 = 2, | 1127 | .arg_1 = 2, |
| 1040 | .arg_2 = 1, | 1128 | .arg_2 = 1, |
| 1041 | .values_count = 20, | 1129 | .values_count = 20, |
| 1042 | .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}, | 1130 | .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}, |
| 1043 | }, | 1131 | }, |
| 1044 | Service::Mii::RandomMiiData3{ | 1132 | RandomMiiData3{ |
| 1045 | .arg_1 = 2, | 1133 | .arg_1 = 2, |
| 1046 | .arg_2 = 2, | 1134 | .arg_2 = 2, |
| 1047 | .values_count = 20, | 1135 | .values_count = 20, |
| @@ -1049,598 +1137,642 @@ const std::array<RandomMiiData3, 9> RandomMiiHairColor{ | |||
| 1049 | }, | 1137 | }, |
| 1050 | }; | 1138 | }; |
| 1051 | 1139 | ||
| 1052 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{ | 1140 | const std::array<RandomMiiData4, 18> RandomMiiEyeType{ |
| 1053 | Service::Mii::RandomMiiData4{ | 1141 | RandomMiiData4{ |
| 1054 | .gender = Gender::Male, | 1142 | .gender = static_cast<u32>(Gender::Male), |
| 1055 | .age = Age::Young, | 1143 | .age = static_cast<u32>(Age::Young), |
| 1056 | .race = Race::Black, | 1144 | .race = static_cast<u32>(Race::Black), |
| 1057 | .values_count = 26, | 1145 | .values_count = 26, |
| 1058 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, | 1146 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, |
| 1059 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, | 1147 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, |
| 1060 | }, | 1148 | }, |
| 1061 | Service::Mii::RandomMiiData4{ | 1149 | RandomMiiData4{ |
| 1062 | .gender = Gender::Male, | 1150 | .gender = static_cast<u32>(Gender::Male), |
| 1063 | .age = Age::Normal, | 1151 | .age = static_cast<u32>(Age::Normal), |
| 1064 | .race = Race::Black, | 1152 | .race = static_cast<u32>(Race::Black), |
| 1065 | .values_count = 26, | 1153 | .values_count = 26, |
| 1066 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, | 1154 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, |
| 1067 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, | 1155 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, |
| 1068 | }, | 1156 | }, |
| 1069 | Service::Mii::RandomMiiData4{ | 1157 | RandomMiiData4{ |
| 1070 | .gender = Gender::Male, | 1158 | .gender = static_cast<u32>(Gender::Male), |
| 1071 | .age = Age::Old, | 1159 | .age = static_cast<u32>(Age::Old), |
| 1072 | .race = Race::Black, | 1160 | .race = static_cast<u32>(Race::Black), |
| 1073 | .values_count = 27, | 1161 | .values_count = 27, |
| 1074 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27, | 1162 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27, |
| 1075 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57}, | 1163 | 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57}, |
| 1076 | }, | 1164 | }, |
| 1077 | Service::Mii::RandomMiiData4{ | 1165 | RandomMiiData4{ |
| 1078 | .gender = Gender::Male, | 1166 | .gender = static_cast<u32>(Gender::Male), |
| 1079 | .age = Age::Young, | 1167 | .age = static_cast<u32>(Age::Young), |
| 1080 | .race = Race::White, | 1168 | .race = static_cast<u32>(Race::White), |
| 1081 | .values_count = 35, | 1169 | .values_count = 35, |
| 1082 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, | 1170 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, |
| 1083 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, | 1171 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, |
| 1084 | }, | 1172 | }, |
| 1085 | Service::Mii::RandomMiiData4{ | 1173 | RandomMiiData4{ |
| 1086 | .gender = Gender::Male, | 1174 | .gender = static_cast<u32>(Gender::Male), |
| 1087 | .age = Age::Normal, | 1175 | .age = static_cast<u32>(Age::Normal), |
| 1088 | .race = Race::White, | 1176 | .race = static_cast<u32>(Race::White), |
| 1089 | .values_count = 35, | 1177 | .values_count = 35, |
| 1090 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, | 1178 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, |
| 1091 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, | 1179 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, |
| 1092 | }, | 1180 | }, |
| 1093 | Service::Mii::RandomMiiData4{ | 1181 | RandomMiiData4{ |
| 1094 | .gender = Gender::Male, | 1182 | .gender = static_cast<u32>(Gender::Male), |
| 1095 | .age = Age::Old, | 1183 | .age = static_cast<u32>(Age::Old), |
| 1096 | .race = Race::White, | 1184 | .race = static_cast<u32>(Race::White), |
| 1097 | .values_count = 35, | 1185 | .values_count = 35, |
| 1098 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29, | 1186 | .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29, |
| 1099 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57}, | 1187 | 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57}, |
| 1100 | }, | 1188 | }, |
| 1101 | Service::Mii::RandomMiiData4{ | 1189 | RandomMiiData4{ |
| 1102 | .gender = Gender::Male, | 1190 | .gender = static_cast<u32>(Gender::Male), |
| 1103 | .age = Age::Young, | 1191 | .age = static_cast<u32>(Age::Young), |
| 1104 | .race = Race::Asian, | 1192 | .race = static_cast<u32>(Race::Asian), |
| 1105 | .values_count = 30, | 1193 | .values_count = 30, |
| 1106 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, | 1194 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, |
| 1107 | 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, | 1195 | 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, |
| 1108 | }, | 1196 | }, |
| 1109 | Service::Mii::RandomMiiData4{ | 1197 | RandomMiiData4{ |
| 1110 | .gender = Gender::Male, | 1198 | .gender = static_cast<u32>(Gender::Male), |
| 1111 | .age = Age::Normal, | 1199 | .age = static_cast<u32>(Age::Normal), |
| 1112 | .race = Race::Asian, | 1200 | .race = static_cast<u32>(Race::Asian), |
| 1113 | .values_count = 30, | 1201 | .values_count = 30, |
| 1114 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, | 1202 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, |
| 1115 | 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, | 1203 | 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, |
| 1116 | }, | 1204 | }, |
| 1117 | Service::Mii::RandomMiiData4{ | 1205 | RandomMiiData4{ |
| 1118 | .gender = Gender::Male, | 1206 | .gender = static_cast<u32>(Gender::Male), |
| 1119 | .age = Age::Old, | 1207 | .age = static_cast<u32>(Age::Old), |
| 1120 | .race = Race::Asian, | 1208 | .race = static_cast<u32>(Race::Asian), |
| 1121 | .values_count = 30, | 1209 | .values_count = 30, |
| 1122 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22, | 1210 | .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22, |
| 1123 | 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57}, | 1211 | 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57}, |
| 1124 | }, | 1212 | }, |
| 1125 | Service::Mii::RandomMiiData4{ | 1213 | RandomMiiData4{ |
| 1126 | .gender = Gender::Female, | 1214 | .gender = static_cast<u32>(Gender::Female), |
| 1127 | .age = Age::Young, | 1215 | .age = static_cast<u32>(Age::Young), |
| 1128 | .race = Race::Black, | 1216 | .race = static_cast<u32>(Race::Black), |
| 1129 | .values_count = 39, | 1217 | .values_count = 39, |
| 1130 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, | 1218 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, |
| 1131 | 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, | 1219 | 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, |
| 1132 | }, | 1220 | }, |
| 1133 | Service::Mii::RandomMiiData4{ | 1221 | RandomMiiData4{ |
| 1134 | .gender = Gender::Female, | 1222 | .gender = static_cast<u32>(Gender::Female), |
| 1135 | .age = Age::Normal, | 1223 | .age = static_cast<u32>(Age::Normal), |
| 1136 | .race = Race::Black, | 1224 | .race = static_cast<u32>(Race::Black), |
| 1137 | .values_count = 39, | 1225 | .values_count = 39, |
| 1138 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, | 1226 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, |
| 1139 | 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, | 1227 | 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, |
| 1140 | }, | 1228 | }, |
| 1141 | Service::Mii::RandomMiiData4{ | 1229 | RandomMiiData4{ |
| 1142 | .gender = Gender::Female, | 1230 | .gender = static_cast<u32>(Gender::Female), |
| 1143 | .age = Age::Old, | 1231 | .age = static_cast<u32>(Age::Old), |
| 1144 | .race = Race::Black, | 1232 | .race = static_cast<u32>(Race::Black), |
| 1145 | .values_count = 40, | 1233 | .values_count = 40, |
| 1146 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, | 1234 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, |
| 1147 | 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, | 1235 | 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, |
| 1148 | }, | 1236 | }, |
| 1149 | Service::Mii::RandomMiiData4{ | 1237 | RandomMiiData4{ |
| 1150 | .gender = Gender::Female, | 1238 | .gender = static_cast<u32>(Gender::Female), |
| 1151 | .age = Age::Young, | 1239 | .age = static_cast<u32>(Age::Young), |
| 1152 | .race = Race::White, | 1240 | .race = static_cast<u32>(Race::White), |
| 1153 | .values_count = 46, | 1241 | .values_count = 46, |
| 1154 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, | 1242 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, |
| 1155 | 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, | 1243 | 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, |
| 1156 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, | 1244 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, |
| 1157 | }, | 1245 | }, |
| 1158 | Service::Mii::RandomMiiData4{ | 1246 | RandomMiiData4{ |
| 1159 | .gender = Gender::Female, | 1247 | .gender = static_cast<u32>(Gender::Female), |
| 1160 | .age = Age::Normal, | 1248 | .age = static_cast<u32>(Age::Normal), |
| 1161 | .race = Race::White, | 1249 | .race = static_cast<u32>(Race::White), |
| 1162 | .values_count = 46, | 1250 | .values_count = 46, |
| 1163 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, | 1251 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, |
| 1164 | 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, | 1252 | 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, |
| 1165 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, | 1253 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, |
| 1166 | }, | 1254 | }, |
| 1167 | Service::Mii::RandomMiiData4{ | 1255 | RandomMiiData4{ |
| 1168 | .gender = Gender::Female, | 1256 | .gender = static_cast<u32>(Gender::Female), |
| 1169 | .age = Age::Old, | 1257 | .age = static_cast<u32>(Age::Old), |
| 1170 | .race = Race::White, | 1258 | .race = static_cast<u32>(Race::White), |
| 1171 | .values_count = 46, | 1259 | .values_count = 46, |
| 1172 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, | 1260 | .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, |
| 1173 | 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, | 1261 | 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, |
| 1174 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, | 1262 | 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, |
| 1175 | }, | 1263 | }, |
| 1176 | Service::Mii::RandomMiiData4{ | 1264 | RandomMiiData4{ |
| 1177 | .gender = Gender::Female, | 1265 | .gender = static_cast<u32>(Gender::Female), |
| 1178 | .age = Age::Young, | 1266 | .age = static_cast<u32>(Age::Young), |
| 1179 | .race = Race::Asian, | 1267 | .race = static_cast<u32>(Race::Asian), |
| 1180 | .values_count = 34, | 1268 | .values_count = 34, |
| 1181 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, | 1269 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, |
| 1182 | 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, | 1270 | 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, |
| 1183 | }, | 1271 | }, |
| 1184 | Service::Mii::RandomMiiData4{ | 1272 | RandomMiiData4{ |
| 1185 | .gender = Gender::Female, | 1273 | .gender = static_cast<u32>(Gender::Female), |
| 1186 | .age = Age::Normal, | 1274 | .age = static_cast<u32>(Age::Normal), |
| 1187 | .race = Race::Asian, | 1275 | .race = static_cast<u32>(Race::Asian), |
| 1188 | .values_count = 34, | 1276 | .values_count = 34, |
| 1189 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, | 1277 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, |
| 1190 | 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, | 1278 | 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, |
| 1191 | }, | 1279 | }, |
| 1192 | Service::Mii::RandomMiiData4{ | 1280 | RandomMiiData4{ |
| 1193 | .gender = Gender::Female, | 1281 | .gender = static_cast<u32>(Gender::Female), |
| 1194 | .age = Age::Old, | 1282 | .age = static_cast<u32>(Age::Old), |
| 1195 | .race = Race::Asian, | 1283 | .race = static_cast<u32>(Race::Asian), |
| 1196 | .values_count = 35, | 1284 | .values_count = 35, |
| 1197 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, | 1285 | .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, |
| 1198 | 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, | 1286 | 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, |
| 1199 | }, | 1287 | }, |
| 1200 | }; | 1288 | }; |
| 1201 | 1289 | ||
| 1202 | const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{ | 1290 | const std::array<RandomMiiData2, 3> RandomMiiEyeColor{ |
| 1203 | Service::Mii::RandomMiiData2{ | 1291 | RandomMiiData2{ |
| 1204 | .arg_1 = 0, | 1292 | .arg_1 = 0, |
| 1205 | .values_count = 10, | 1293 | .values_count = 10, |
| 1206 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, | 1294 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, |
| 1207 | }, | 1295 | }, |
| 1208 | Service::Mii::RandomMiiData2{ | 1296 | RandomMiiData2{ |
| 1209 | .arg_1 = 1, | 1297 | .arg_1 = 1, |
| 1210 | .values_count = 10, | 1298 | .values_count = 10, |
| 1211 | .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5}, | 1299 | .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5}, |
| 1212 | }, | 1300 | }, |
| 1213 | Service::Mii::RandomMiiData2{ | 1301 | RandomMiiData2{ |
| 1214 | .arg_1 = 2, | 1302 | .arg_1 = 2, |
| 1215 | .values_count = 10, | 1303 | .values_count = 10, |
| 1216 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, | 1304 | .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, |
| 1217 | }, | 1305 | }, |
| 1218 | }; | 1306 | }; |
| 1219 | 1307 | ||
| 1220 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{ | 1308 | const std::array<RandomMiiData4, 18> RandomMiiEyebrowType{ |
| 1221 | Service::Mii::RandomMiiData4{ | 1309 | RandomMiiData4{ |
| 1222 | .gender = Gender::Male, | 1310 | .gender = static_cast<u32>(Gender::Male), |
| 1223 | .age = Age::Young, | 1311 | .age = static_cast<u32>(Age::Young), |
| 1224 | .race = Race::Black, | 1312 | .race = static_cast<u32>(Race::Black), |
| 1225 | .values_count = 18, | 1313 | .values_count = 18, |
| 1226 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, | 1314 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, |
| 1227 | }, | 1315 | }, |
| 1228 | Service::Mii::RandomMiiData4{ | 1316 | RandomMiiData4{ |
| 1229 | .gender = Gender::Male, | 1317 | .gender = static_cast<u32>(Gender::Male), |
| 1230 | .age = Age::Normal, | 1318 | .age = static_cast<u32>(Age::Normal), |
| 1231 | .race = Race::Black, | 1319 | .race = static_cast<u32>(Race::Black), |
| 1232 | .values_count = 18, | 1320 | .values_count = 18, |
| 1233 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, | 1321 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, |
| 1234 | }, | 1322 | }, |
| 1235 | Service::Mii::RandomMiiData4{ | 1323 | RandomMiiData4{ |
| 1236 | .gender = Gender::Male, | 1324 | .gender = static_cast<u32>(Gender::Male), |
| 1237 | .age = Age::Old, | 1325 | .age = static_cast<u32>(Age::Old), |
| 1238 | .race = Race::Black, | 1326 | .race = static_cast<u32>(Race::Black), |
| 1239 | .values_count = 18, | 1327 | .values_count = 18, |
| 1240 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, | 1328 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, |
| 1241 | }, | 1329 | }, |
| 1242 | Service::Mii::RandomMiiData4{ | 1330 | RandomMiiData4{ |
| 1243 | .gender = Gender::Male, | 1331 | .gender = static_cast<u32>(Gender::Male), |
| 1244 | .age = Age::Young, | 1332 | .age = static_cast<u32>(Age::Young), |
| 1245 | .race = Race::White, | 1333 | .race = static_cast<u32>(Race::White), |
| 1246 | .values_count = 23, | 1334 | .values_count = 23, |
| 1247 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, | 1335 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 1248 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, | 1336 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, |
| 1249 | }, | 1337 | }, |
| 1250 | Service::Mii::RandomMiiData4{ | 1338 | RandomMiiData4{ |
| 1251 | .gender = Gender::Male, | 1339 | .gender = static_cast<u32>(Gender::Male), |
| 1252 | .age = Age::Normal, | 1340 | .age = static_cast<u32>(Age::Normal), |
| 1253 | .race = Race::White, | 1341 | .race = static_cast<u32>(Race::White), |
| 1254 | .values_count = 23, | 1342 | .values_count = 23, |
| 1255 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, | 1343 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 1256 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, | 1344 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, |
| 1257 | }, | 1345 | }, |
| 1258 | Service::Mii::RandomMiiData4{ | 1346 | RandomMiiData4{ |
| 1259 | .gender = Gender::Male, | 1347 | .gender = static_cast<u32>(Gender::Male), |
| 1260 | .age = Age::Old, | 1348 | .age = static_cast<u32>(Age::Old), |
| 1261 | .race = Race::White, | 1349 | .race = static_cast<u32>(Race::White), |
| 1262 | .values_count = 23, | 1350 | .values_count = 23, |
| 1263 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, | 1351 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 1264 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, | 1352 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, |
| 1265 | }, | 1353 | }, |
| 1266 | Service::Mii::RandomMiiData4{ | 1354 | RandomMiiData4{ |
| 1267 | .gender = Gender::Male, | 1355 | .gender = static_cast<u32>(Gender::Male), |
| 1268 | .age = Age::Young, | 1356 | .age = static_cast<u32>(Age::Young), |
| 1269 | .race = Race::Asian, | 1357 | .race = static_cast<u32>(Race::Asian), |
| 1270 | .values_count = 21, | 1358 | .values_count = 21, |
| 1271 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, | 1359 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, |
| 1272 | }, | 1360 | }, |
| 1273 | Service::Mii::RandomMiiData4{ | 1361 | RandomMiiData4{ |
| 1274 | .gender = Gender::Male, | 1362 | .gender = static_cast<u32>(Gender::Male), |
| 1275 | .age = Age::Normal, | 1363 | .age = static_cast<u32>(Age::Normal), |
| 1276 | .race = Race::Asian, | 1364 | .race = static_cast<u32>(Race::Asian), |
| 1277 | .values_count = 21, | 1365 | .values_count = 21, |
| 1278 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, | 1366 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, |
| 1279 | }, | 1367 | }, |
| 1280 | Service::Mii::RandomMiiData4{ | 1368 | RandomMiiData4{ |
| 1281 | .gender = Gender::Male, | 1369 | .gender = static_cast<u32>(Gender::Male), |
| 1282 | .age = Age::Old, | 1370 | .age = static_cast<u32>(Age::Old), |
| 1283 | .race = Race::Asian, | 1371 | .race = static_cast<u32>(Race::Asian), |
| 1284 | .values_count = 21, | 1372 | .values_count = 21, |
| 1285 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, | 1373 | .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, |
| 1286 | }, | 1374 | }, |
| 1287 | Service::Mii::RandomMiiData4{ | 1375 | RandomMiiData4{ |
| 1288 | .gender = Gender::Female, | 1376 | .gender = static_cast<u32>(Gender::Female), |
| 1289 | .age = Age::Young, | 1377 | .age = static_cast<u32>(Age::Young), |
| 1290 | .race = Race::Black, | 1378 | .race = static_cast<u32>(Race::Black), |
| 1291 | .values_count = 9, | 1379 | .values_count = 9, |
| 1292 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, | 1380 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, |
| 1293 | }, | 1381 | }, |
| 1294 | Service::Mii::RandomMiiData4{ | 1382 | RandomMiiData4{ |
| 1295 | .gender = Gender::Female, | 1383 | .gender = static_cast<u32>(Gender::Female), |
| 1296 | .age = Age::Normal, | 1384 | .age = static_cast<u32>(Age::Normal), |
| 1297 | .race = Race::Black, | 1385 | .race = static_cast<u32>(Race::Black), |
| 1298 | .values_count = 9, | 1386 | .values_count = 9, |
| 1299 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, | 1387 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, |
| 1300 | }, | 1388 | }, |
| 1301 | Service::Mii::RandomMiiData4{ | 1389 | RandomMiiData4{ |
| 1302 | .gender = Gender::Female, | 1390 | .gender = static_cast<u32>(Gender::Female), |
| 1303 | .age = Age::Old, | 1391 | .age = static_cast<u32>(Age::Old), |
| 1304 | .race = Race::Black, | 1392 | .race = static_cast<u32>(Race::Black), |
| 1305 | .values_count = 9, | 1393 | .values_count = 9, |
| 1306 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, | 1394 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, |
| 1307 | }, | 1395 | }, |
| 1308 | Service::Mii::RandomMiiData4{ | 1396 | RandomMiiData4{ |
| 1309 | .gender = Gender::Female, | 1397 | .gender = static_cast<u32>(Gender::Female), |
| 1310 | .age = Age::Young, | 1398 | .age = static_cast<u32>(Age::Young), |
| 1311 | .race = Race::White, | 1399 | .race = static_cast<u32>(Race::White), |
| 1312 | .values_count = 11, | 1400 | .values_count = 11, |
| 1313 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, | 1401 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, |
| 1314 | }, | 1402 | }, |
| 1315 | Service::Mii::RandomMiiData4{ | 1403 | RandomMiiData4{ |
| 1316 | .gender = Gender::Female, | 1404 | .gender = static_cast<u32>(Gender::Female), |
| 1317 | .age = Age::Normal, | 1405 | .age = static_cast<u32>(Age::Normal), |
| 1318 | .race = Race::White, | 1406 | .race = static_cast<u32>(Race::White), |
| 1319 | .values_count = 11, | 1407 | .values_count = 11, |
| 1320 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, | 1408 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, |
| 1321 | }, | 1409 | }, |
| 1322 | Service::Mii::RandomMiiData4{ | 1410 | RandomMiiData4{ |
| 1323 | .gender = Gender::Female, | 1411 | .gender = static_cast<u32>(Gender::Female), |
| 1324 | .age = Age::Old, | 1412 | .age = static_cast<u32>(Age::Old), |
| 1325 | .race = Race::White, | 1413 | .race = static_cast<u32>(Race::White), |
| 1326 | .values_count = 11, | 1414 | .values_count = 11, |
| 1327 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, | 1415 | .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, |
| 1328 | }, | 1416 | }, |
| 1329 | Service::Mii::RandomMiiData4{ | 1417 | RandomMiiData4{ |
| 1330 | .gender = Gender::Female, | 1418 | .gender = static_cast<u32>(Gender::Female), |
| 1331 | .age = Age::Young, | 1419 | .age = static_cast<u32>(Age::Young), |
| 1332 | .race = Race::Asian, | 1420 | .race = static_cast<u32>(Race::Asian), |
| 1333 | .values_count = 9, | 1421 | .values_count = 9, |
| 1334 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, | 1422 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, |
| 1335 | }, | 1423 | }, |
| 1336 | Service::Mii::RandomMiiData4{ | 1424 | RandomMiiData4{ |
| 1337 | .gender = Gender::Female, | 1425 | .gender = static_cast<u32>(Gender::Female), |
| 1338 | .age = Age::Normal, | 1426 | .age = static_cast<u32>(Age::Normal), |
| 1339 | .race = Race::Asian, | 1427 | .race = static_cast<u32>(Race::Asian), |
| 1340 | .values_count = 9, | 1428 | .values_count = 9, |
| 1341 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, | 1429 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, |
| 1342 | }, | 1430 | }, |
| 1343 | Service::Mii::RandomMiiData4{ | 1431 | RandomMiiData4{ |
| 1344 | .gender = Gender::Female, | 1432 | .gender = static_cast<u32>(Gender::Female), |
| 1345 | .age = Age::Old, | 1433 | .age = static_cast<u32>(Age::Old), |
| 1346 | .race = Race::Asian, | 1434 | .race = static_cast<u32>(Race::Asian), |
| 1347 | .values_count = 9, | 1435 | .values_count = 9, |
| 1348 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, | 1436 | .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, |
| 1349 | }, | 1437 | }, |
| 1350 | }; | 1438 | }; |
| 1351 | 1439 | ||
| 1352 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{ | 1440 | const std::array<RandomMiiData4, 18> RandomMiiNoseType{ |
| 1353 | Service::Mii::RandomMiiData4{ | 1441 | RandomMiiData4{ |
| 1354 | .gender = Gender::Male, | 1442 | .gender = static_cast<u32>(Gender::Male), |
| 1355 | .age = Age::Young, | 1443 | .age = static_cast<u32>(Age::Young), |
| 1356 | .race = Race::Black, | 1444 | .race = static_cast<u32>(Race::Black), |
| 1357 | .values_count = 11, | 1445 | .values_count = 11, |
| 1358 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, | 1446 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, |
| 1359 | }, | 1447 | }, |
| 1360 | Service::Mii::RandomMiiData4{ | 1448 | RandomMiiData4{ |
| 1361 | .gender = Gender::Male, | 1449 | .gender = static_cast<u32>(Gender::Male), |
| 1362 | .age = Age::Normal, | 1450 | .age = static_cast<u32>(Age::Normal), |
| 1363 | .race = Race::Black, | 1451 | .race = static_cast<u32>(Race::Black), |
| 1364 | .values_count = 11, | 1452 | .values_count = 11, |
| 1365 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, | 1453 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, |
| 1366 | }, | 1454 | }, |
| 1367 | Service::Mii::RandomMiiData4{ | 1455 | RandomMiiData4{ |
| 1368 | .gender = Gender::Male, | 1456 | .gender = static_cast<u32>(Gender::Male), |
| 1369 | .age = Age::Old, | 1457 | .age = static_cast<u32>(Age::Old), |
| 1370 | .race = Race::Black, | 1458 | .race = static_cast<u32>(Race::Black), |
| 1371 | .values_count = 11, | 1459 | .values_count = 11, |
| 1372 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, | 1460 | .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, |
| 1373 | }, | 1461 | }, |
| 1374 | Service::Mii::RandomMiiData4{ | 1462 | RandomMiiData4{ |
| 1375 | .gender = Gender::Male, | 1463 | .gender = static_cast<u32>(Gender::Male), |
| 1376 | .age = Age::Young, | 1464 | .age = static_cast<u32>(Age::Young), |
| 1377 | .race = Race::White, | 1465 | .race = static_cast<u32>(Race::White), |
| 1378 | .values_count = 18, | 1466 | .values_count = 18, |
| 1379 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, | 1467 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, |
| 1380 | }, | 1468 | }, |
| 1381 | Service::Mii::RandomMiiData4{ | 1469 | RandomMiiData4{ |
| 1382 | .gender = Gender::Male, | 1470 | .gender = static_cast<u32>(Gender::Male), |
| 1383 | .age = Age::Normal, | 1471 | .age = static_cast<u32>(Age::Normal), |
| 1384 | .race = Race::White, | 1472 | .race = static_cast<u32>(Race::White), |
| 1385 | .values_count = 18, | 1473 | .values_count = 18, |
| 1386 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, | 1474 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, |
| 1387 | }, | 1475 | }, |
| 1388 | Service::Mii::RandomMiiData4{ | 1476 | RandomMiiData4{ |
| 1389 | .gender = Gender::Male, | 1477 | .gender = static_cast<u32>(Gender::Male), |
| 1390 | .age = Age::Old, | 1478 | .age = static_cast<u32>(Age::Old), |
| 1391 | .race = Race::White, | 1479 | .race = static_cast<u32>(Race::White), |
| 1392 | .values_count = 15, | 1480 | .values_count = 15, |
| 1393 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, | 1481 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, |
| 1394 | }, | 1482 | }, |
| 1395 | Service::Mii::RandomMiiData4{ | 1483 | RandomMiiData4{ |
| 1396 | .gender = Gender::Male, | 1484 | .gender = static_cast<u32>(Gender::Male), |
| 1397 | .age = Age::Young, | 1485 | .age = static_cast<u32>(Age::Young), |
| 1398 | .race = Race::Asian, | 1486 | .race = static_cast<u32>(Race::Asian), |
| 1399 | .values_count = 18, | 1487 | .values_count = 18, |
| 1400 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, | 1488 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, |
| 1401 | }, | 1489 | }, |
| 1402 | Service::Mii::RandomMiiData4{ | 1490 | RandomMiiData4{ |
| 1403 | .gender = Gender::Male, | 1491 | .gender = static_cast<u32>(Gender::Male), |
| 1404 | .age = Age::Normal, | 1492 | .age = static_cast<u32>(Age::Normal), |
| 1405 | .race = Race::Asian, | 1493 | .race = static_cast<u32>(Race::Asian), |
| 1406 | .values_count = 18, | 1494 | .values_count = 18, |
| 1407 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, | 1495 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, |
| 1408 | }, | 1496 | }, |
| 1409 | Service::Mii::RandomMiiData4{ | 1497 | RandomMiiData4{ |
| 1410 | .gender = Gender::Male, | 1498 | .gender = static_cast<u32>(Gender::Male), |
| 1411 | .age = Age::Old, | 1499 | .age = static_cast<u32>(Age::Old), |
| 1412 | .race = Race::Asian, | 1500 | .race = static_cast<u32>(Race::Asian), |
| 1413 | .values_count = 15, | 1501 | .values_count = 15, |
| 1414 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, | 1502 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, |
| 1415 | }, | 1503 | }, |
| 1416 | Service::Mii::RandomMiiData4{ | 1504 | RandomMiiData4{ |
| 1417 | .gender = Gender::Female, | 1505 | .gender = static_cast<u32>(Gender::Female), |
| 1418 | .age = Age::Young, | 1506 | .age = static_cast<u32>(Age::Young), |
| 1419 | .race = Race::Black, | 1507 | .race = static_cast<u32>(Race::Black), |
| 1420 | .values_count = 8, | 1508 | .values_count = 8, |
| 1421 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, | 1509 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, |
| 1422 | }, | 1510 | }, |
| 1423 | Service::Mii::RandomMiiData4{ | 1511 | RandomMiiData4{ |
| 1424 | .gender = Gender::Female, | 1512 | .gender = static_cast<u32>(Gender::Female), |
| 1425 | .age = Age::Normal, | 1513 | .age = static_cast<u32>(Age::Normal), |
| 1426 | .race = Race::Black, | 1514 | .race = static_cast<u32>(Race::Black), |
| 1427 | .values_count = 8, | 1515 | .values_count = 8, |
| 1428 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, | 1516 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, |
| 1429 | }, | 1517 | }, |
| 1430 | Service::Mii::RandomMiiData4{ | 1518 | RandomMiiData4{ |
| 1431 | .gender = Gender::Female, | 1519 | .gender = static_cast<u32>(Gender::Female), |
| 1432 | .age = Age::Old, | 1520 | .age = static_cast<u32>(Age::Old), |
| 1433 | .race = Race::Black, | 1521 | .race = static_cast<u32>(Race::Black), |
| 1434 | .values_count = 8, | 1522 | .values_count = 8, |
| 1435 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, | 1523 | .values = {0, 1, 3, 4, 8, 10, 13, 14}, |
| 1436 | }, | 1524 | }, |
| 1437 | Service::Mii::RandomMiiData4{ | 1525 | RandomMiiData4{ |
| 1438 | .gender = Gender::Female, | 1526 | .gender = static_cast<u32>(Gender::Female), |
| 1439 | .age = Age::Young, | 1527 | .age = static_cast<u32>(Age::Young), |
| 1440 | .race = Race::White, | 1528 | .race = static_cast<u32>(Race::White), |
| 1441 | .values_count = 12, | 1529 | .values_count = 12, |
| 1442 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, | 1530 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, |
| 1443 | }, | 1531 | }, |
| 1444 | Service::Mii::RandomMiiData4{ | 1532 | RandomMiiData4{ |
| 1445 | .gender = Gender::Female, | 1533 | .gender = static_cast<u32>(Gender::Female), |
| 1446 | .age = Age::Normal, | 1534 | .age = static_cast<u32>(Age::Normal), |
| 1447 | .race = Race::White, | 1535 | .race = static_cast<u32>(Race::White), |
| 1448 | .values_count = 11, | 1536 | .values_count = 11, |
| 1449 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, | 1537 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, |
| 1450 | }, | 1538 | }, |
| 1451 | Service::Mii::RandomMiiData4{ | 1539 | RandomMiiData4{ |
| 1452 | .gender = Gender::Female, | 1540 | .gender = static_cast<u32>(Gender::Female), |
| 1453 | .age = Age::Old, | 1541 | .age = static_cast<u32>(Age::Old), |
| 1454 | .race = Race::White, | 1542 | .race = static_cast<u32>(Race::White), |
| 1455 | .values_count = 10, | 1543 | .values_count = 10, |
| 1456 | .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, | 1544 | .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, |
| 1457 | }, | 1545 | }, |
| 1458 | Service::Mii::RandomMiiData4{ | 1546 | RandomMiiData4{ |
| 1459 | .gender = Gender::Female, | 1547 | .gender = static_cast<u32>(Gender::Female), |
| 1460 | .age = Age::Young, | 1548 | .age = static_cast<u32>(Age::Young), |
| 1461 | .race = Race::Asian, | 1549 | .race = static_cast<u32>(Race::Asian), |
| 1462 | .values_count = 12, | 1550 | .values_count = 12, |
| 1463 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, | 1551 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, |
| 1464 | }, | 1552 | }, |
| 1465 | Service::Mii::RandomMiiData4{ | 1553 | RandomMiiData4{ |
| 1466 | .gender = Gender::Female, | 1554 | .gender = static_cast<u32>(Gender::Female), |
| 1467 | .age = Age::Normal, | 1555 | .age = static_cast<u32>(Age::Normal), |
| 1468 | .race = Race::Asian, | 1556 | .race = static_cast<u32>(Race::Asian), |
| 1469 | .values_count = 11, | 1557 | .values_count = 11, |
| 1470 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, | 1558 | .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, |
| 1471 | }, | 1559 | }, |
| 1472 | Service::Mii::RandomMiiData4{ | 1560 | RandomMiiData4{ |
| 1473 | .gender = Gender::Female, | 1561 | .gender = static_cast<u32>(Gender::Female), |
| 1474 | .age = Age::Old, | 1562 | .age = static_cast<u32>(Age::Old), |
| 1475 | .race = Race::Asian, | 1563 | .race = static_cast<u32>(Race::Asian), |
| 1476 | .values_count = 10, | 1564 | .values_count = 10, |
| 1477 | .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, | 1565 | .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, |
| 1478 | }, | 1566 | }, |
| 1479 | }; | 1567 | }; |
| 1480 | 1568 | ||
| 1481 | const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{ | 1569 | const std::array<RandomMiiData4, 18> RandomMiiMouthType{ |
| 1482 | Service::Mii::RandomMiiData4{ | 1570 | RandomMiiData4{ |
| 1483 | .gender = Gender::Male, | 1571 | .gender = static_cast<u32>(Gender::Male), |
| 1484 | .age = Age::Young, | 1572 | .age = static_cast<u32>(Age::Young), |
| 1485 | .race = Race::Black, | 1573 | .race = static_cast<u32>(Race::Black), |
| 1486 | .values_count = 25, | 1574 | .values_count = 25, |
| 1487 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, | 1575 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, |
| 1488 | 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, | 1576 | 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, |
| 1489 | }, | 1577 | }, |
| 1490 | Service::Mii::RandomMiiData4{ | 1578 | RandomMiiData4{ |
| 1491 | .gender = Gender::Male, | 1579 | .gender = static_cast<u32>(Gender::Male), |
| 1492 | .age = Age::Normal, | 1580 | .age = static_cast<u32>(Age::Normal), |
| 1493 | .race = Race::Black, | 1581 | .race = static_cast<u32>(Race::Black), |
| 1494 | .values_count = 27, | 1582 | .values_count = 27, |
| 1495 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, | 1583 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, |
| 1496 | 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, | 1584 | 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, |
| 1497 | }, | 1585 | }, |
| 1498 | Service::Mii::RandomMiiData4{ | 1586 | RandomMiiData4{ |
| 1499 | .gender = Gender::Male, | 1587 | .gender = static_cast<u32>(Gender::Male), |
| 1500 | .age = Age::Old, | 1588 | .age = static_cast<u32>(Age::Old), |
| 1501 | .race = Race::Black, | 1589 | .race = static_cast<u32>(Race::Black), |
| 1502 | .values_count = 28, | 1590 | .values_count = 28, |
| 1503 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, | 1591 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, |
| 1504 | 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35}, | 1592 | 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35}, |
| 1505 | }, | 1593 | }, |
| 1506 | Service::Mii::RandomMiiData4{ | 1594 | RandomMiiData4{ |
| 1507 | .gender = Gender::Male, | 1595 | .gender = static_cast<u32>(Gender::Male), |
| 1508 | .age = Age::Young, | 1596 | .age = static_cast<u32>(Age::Young), |
| 1509 | .race = Race::White, | 1597 | .race = static_cast<u32>(Race::White), |
| 1510 | .values_count = 24, | 1598 | .values_count = 24, |
| 1511 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, | 1599 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, |
| 1512 | 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1600 | 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1513 | }, | 1601 | }, |
| 1514 | Service::Mii::RandomMiiData4{ | 1602 | RandomMiiData4{ |
| 1515 | .gender = Gender::Male, | 1603 | .gender = static_cast<u32>(Gender::Male), |
| 1516 | .age = Age::Normal, | 1604 | .age = static_cast<u32>(Age::Normal), |
| 1517 | .race = Race::White, | 1605 | .race = static_cast<u32>(Race::White), |
| 1518 | .values_count = 26, | 1606 | .values_count = 26, |
| 1519 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 1607 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 1520 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1608 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1521 | }, | 1609 | }, |
| 1522 | Service::Mii::RandomMiiData4{ | 1610 | RandomMiiData4{ |
| 1523 | .gender = Gender::Male, | 1611 | .gender = static_cast<u32>(Gender::Male), |
| 1524 | .age = Age::Old, | 1612 | .age = static_cast<u32>(Age::Old), |
| 1525 | .race = Race::White, | 1613 | .race = static_cast<u32>(Race::White), |
| 1526 | .values_count = 26, | 1614 | .values_count = 26, |
| 1527 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 1615 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 1528 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1616 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1529 | }, | 1617 | }, |
| 1530 | Service::Mii::RandomMiiData4{ | 1618 | RandomMiiData4{ |
| 1531 | .gender = Gender::Male, | 1619 | .gender = static_cast<u32>(Gender::Male), |
| 1532 | .age = Age::Young, | 1620 | .age = static_cast<u32>(Age::Young), |
| 1533 | .race = Race::Asian, | 1621 | .race = static_cast<u32>(Race::Asian), |
| 1534 | .values_count = 24, | 1622 | .values_count = 24, |
| 1535 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, | 1623 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, |
| 1536 | 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1624 | 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1537 | }, | 1625 | }, |
| 1538 | Service::Mii::RandomMiiData4{ | 1626 | RandomMiiData4{ |
| 1539 | .gender = Gender::Male, | 1627 | .gender = static_cast<u32>(Gender::Male), |
| 1540 | .age = Age::Normal, | 1628 | .age = static_cast<u32>(Age::Normal), |
| 1541 | .race = Race::Asian, | 1629 | .race = static_cast<u32>(Race::Asian), |
| 1542 | .values_count = 26, | 1630 | .values_count = 26, |
| 1543 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 1631 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 1544 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1632 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1545 | }, | 1633 | }, |
| 1546 | Service::Mii::RandomMiiData4{ | 1634 | RandomMiiData4{ |
| 1547 | .gender = Gender::Male, | 1635 | .gender = static_cast<u32>(Gender::Male), |
| 1548 | .age = Age::Old, | 1636 | .age = static_cast<u32>(Age::Old), |
| 1549 | .race = Race::Asian, | 1637 | .race = static_cast<u32>(Race::Asian), |
| 1550 | .values_count = 26, | 1638 | .values_count = 26, |
| 1551 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | 1639 | .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 1552 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, | 1640 | 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, |
| 1553 | }, | 1641 | }, |
| 1554 | Service::Mii::RandomMiiData4{ | 1642 | RandomMiiData4{ |
| 1555 | .gender = Gender::Female, | 1643 | .gender = static_cast<u32>(Gender::Female), |
| 1556 | .age = Age::Young, | 1644 | .age = static_cast<u32>(Age::Young), |
| 1557 | .race = Race::Black, | 1645 | .race = static_cast<u32>(Race::Black), |
| 1558 | .values_count = 25, | 1646 | .values_count = 25, |
| 1559 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, | 1647 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, |
| 1560 | 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, | 1648 | 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, |
| 1561 | }, | 1649 | }, |
| 1562 | Service::Mii::RandomMiiData4{ | 1650 | RandomMiiData4{ |
| 1563 | .gender = Gender::Female, | 1651 | .gender = static_cast<u32>(Gender::Female), |
| 1564 | .age = Age::Normal, | 1652 | .age = static_cast<u32>(Age::Normal), |
| 1565 | .race = Race::Black, | 1653 | .race = static_cast<u32>(Race::Black), |
| 1566 | .values_count = 26, | 1654 | .values_count = 26, |
| 1567 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1655 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1568 | 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, | 1656 | 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, |
| 1569 | }, | 1657 | }, |
| 1570 | Service::Mii::RandomMiiData4{ | 1658 | RandomMiiData4{ |
| 1571 | .gender = Gender::Female, | 1659 | .gender = static_cast<u32>(Gender::Female), |
| 1572 | .age = Age::Old, | 1660 | .age = static_cast<u32>(Age::Old), |
| 1573 | .race = Race::Black, | 1661 | .race = static_cast<u32>(Race::Black), |
| 1574 | .values_count = 26, | 1662 | .values_count = 26, |
| 1575 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1663 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1576 | 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, | 1664 | 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, |
| 1577 | }, | 1665 | }, |
| 1578 | Service::Mii::RandomMiiData4{ | 1666 | RandomMiiData4{ |
| 1579 | .gender = Gender::Female, | 1667 | .gender = static_cast<u32>(Gender::Female), |
| 1580 | .age = Age::Young, | 1668 | .age = static_cast<u32>(Age::Young), |
| 1581 | .race = Race::White, | 1669 | .race = static_cast<u32>(Race::White), |
| 1582 | .values_count = 25, | 1670 | .values_count = 25, |
| 1583 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, | 1671 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, |
| 1584 | 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, | 1672 | 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, |
| 1585 | }, | 1673 | }, |
| 1586 | Service::Mii::RandomMiiData4{ | 1674 | RandomMiiData4{ |
| 1587 | .gender = Gender::Female, | 1675 | .gender = static_cast<u32>(Gender::Female), |
| 1588 | .age = Age::Normal, | 1676 | .age = static_cast<u32>(Age::Normal), |
| 1589 | .race = Race::White, | 1677 | .race = static_cast<u32>(Race::White), |
| 1590 | .values_count = 26, | 1678 | .values_count = 26, |
| 1591 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1679 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1592 | 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, | 1680 | 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, |
| 1593 | }, | 1681 | }, |
| 1594 | Service::Mii::RandomMiiData4{ | 1682 | RandomMiiData4{ |
| 1595 | .gender = Gender::Female, | 1683 | .gender = static_cast<u32>(Gender::Female), |
| 1596 | .age = Age::Old, | 1684 | .age = static_cast<u32>(Age::Old), |
| 1597 | .race = Race::White, | 1685 | .race = static_cast<u32>(Race::White), |
| 1598 | .values_count = 25, | 1686 | .values_count = 25, |
| 1599 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1687 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1600 | 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35}, | 1688 | 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35}, |
| 1601 | }, | 1689 | }, |
| 1602 | Service::Mii::RandomMiiData4{ | 1690 | RandomMiiData4{ |
| 1603 | .gender = Gender::Female, | 1691 | .gender = static_cast<u32>(Gender::Female), |
| 1604 | .age = Age::Young, | 1692 | .age = static_cast<u32>(Age::Young), |
| 1605 | .race = Race::Asian, | 1693 | .race = static_cast<u32>(Race::Asian), |
| 1606 | .values_count = 24, | 1694 | .values_count = 24, |
| 1607 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, | 1695 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, |
| 1608 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, | 1696 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, |
| 1609 | }, | 1697 | }, |
| 1610 | Service::Mii::RandomMiiData4{ | 1698 | RandomMiiData4{ |
| 1611 | .gender = Gender::Female, | 1699 | .gender = static_cast<u32>(Gender::Female), |
| 1612 | .age = Age::Normal, | 1700 | .age = static_cast<u32>(Age::Normal), |
| 1613 | .race = Race::Asian, | 1701 | .race = static_cast<u32>(Race::Asian), |
| 1614 | .values_count = 25, | 1702 | .values_count = 25, |
| 1615 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1703 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1616 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, | 1704 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, |
| 1617 | }, | 1705 | }, |
| 1618 | Service::Mii::RandomMiiData4{ | 1706 | RandomMiiData4{ |
| 1619 | .gender = Gender::Female, | 1707 | .gender = static_cast<u32>(Gender::Female), |
| 1620 | .age = Age::Old, | 1708 | .age = static_cast<u32>(Age::Old), |
| 1621 | .race = Race::Asian, | 1709 | .race = static_cast<u32>(Race::Asian), |
| 1622 | .values_count = 25, | 1710 | .values_count = 25, |
| 1623 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, | 1711 | .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, |
| 1624 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, | 1712 | 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, |
| 1625 | }, | 1713 | }, |
| 1626 | }; | 1714 | }; |
| 1627 | 1715 | ||
| 1628 | const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{ | 1716 | const std::array<RandomMiiData2, 3> RandomMiiGlassType{ |
| 1629 | Service::Mii::RandomMiiData2{ | 1717 | RandomMiiData2{ |
| 1630 | .arg_1 = 0, | 1718 | .arg_1 = 0, |
| 1631 | .values_count = 9, | 1719 | .values_count = 9, |
| 1632 | .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, | 1720 | .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, |
| 1633 | }, | 1721 | }, |
| 1634 | Service::Mii::RandomMiiData2{ | 1722 | RandomMiiData2{ |
| 1635 | .arg_1 = 1, | 1723 | .arg_1 = 1, |
| 1636 | .values_count = 9, | 1724 | .values_count = 9, |
| 1637 | .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, | 1725 | .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, |
| 1638 | }, | 1726 | }, |
| 1639 | Service::Mii::RandomMiiData2{ | 1727 | RandomMiiData2{ |
| 1640 | .arg_1 = 2, | 1728 | .arg_1 = 2, |
| 1641 | .values_count = 9, | 1729 | .values_count = 9, |
| 1642 | .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, | 1730 | .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, |
| 1643 | }, | 1731 | }, |
| 1644 | }; | 1732 | }; |
| 1645 | 1733 | ||
| 1734 | u8 FromVer3GetFacelineColor(u8 color) { | ||
| 1735 | return FromVer3FacelineColorTable[color]; | ||
| 1736 | } | ||
| 1737 | |||
| 1738 | u8 FromVer3GetHairColor(u8 color) { | ||
| 1739 | return FromVer3HairColorTable[color]; | ||
| 1740 | } | ||
| 1741 | |||
| 1742 | u8 FromVer3GetEyeColor(u8 color) { | ||
| 1743 | return FromVer3EyeColorTable[color]; | ||
| 1744 | } | ||
| 1745 | |||
| 1746 | u8 FromVer3GetMouthlineColor(u8 color) { | ||
| 1747 | return FromVer3MouthlineColorTable[color]; | ||
| 1748 | } | ||
| 1749 | |||
| 1750 | u8 FromVer3GetGlassColor(u8 color) { | ||
| 1751 | return FromVer3GlassColorTable[color]; | ||
| 1752 | } | ||
| 1753 | |||
| 1754 | u8 FromVer3GetGlassType(u8 type) { | ||
| 1755 | return FromVer3GlassTypeTable[type]; | ||
| 1756 | } | ||
| 1757 | |||
| 1758 | FacelineColor GetFacelineColorFromVer3(u32 color) { | ||
| 1759 | return static_cast<FacelineColor>(Ver3FacelineColorTable[color]); | ||
| 1760 | } | ||
| 1761 | |||
| 1762 | CommonColor GetHairColorFromVer3(u32 color) { | ||
| 1763 | return static_cast<CommonColor>(Ver3HairColorTable[color]); | ||
| 1764 | } | ||
| 1765 | |||
| 1766 | CommonColor GetEyeColorFromVer3(u32 color) { | ||
| 1767 | return static_cast<CommonColor>(Ver3EyeColorTable[color]); | ||
| 1768 | } | ||
| 1769 | |||
| 1770 | CommonColor GetMouthColorFromVer3(u32 color) { | ||
| 1771 | return static_cast<CommonColor>(Ver3MouthColorTable[color]); | ||
| 1772 | } | ||
| 1773 | |||
| 1774 | CommonColor GetGlassColorFromVer3(u32 color) { | ||
| 1775 | return static_cast<CommonColor>(Ver3GlassColorTable[color]); | ||
| 1776 | } | ||
| 1777 | |||
| 1646 | } // namespace Service::Mii::RawData | 1778 | } // namespace Service::Mii::RawData |
diff --git a/src/core/hle/service/mii/types/raw_data.h b/src/core/hle/service/mii/types/raw_data.h new file mode 100644 index 000000000..9a4cfa738 --- /dev/null +++ b/src/core/hle/service/mii/types/raw_data.h | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | |||
| 8 | #include "core/hle/service/mii/mii_types.h" | ||
| 9 | |||
| 10 | namespace Service::Mii::RawData { | ||
| 11 | |||
| 12 | struct RandomMiiValues { | ||
| 13 | std::array<u8, 188> values{}; | ||
| 14 | }; | ||
| 15 | static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); | ||
| 16 | |||
| 17 | struct RandomMiiData4 { | ||
| 18 | u32 gender{}; | ||
| 19 | u32 age{}; | ||
| 20 | u32 race{}; | ||
| 21 | u32 values_count{}; | ||
| 22 | std::array<u32, 47> values{}; | ||
| 23 | }; | ||
| 24 | static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); | ||
| 25 | |||
| 26 | struct RandomMiiData3 { | ||
| 27 | u32 arg_1; | ||
| 28 | u32 arg_2; | ||
| 29 | u32 values_count; | ||
| 30 | std::array<u32, 47> values{}; | ||
| 31 | }; | ||
| 32 | static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); | ||
| 33 | |||
| 34 | struct RandomMiiData2 { | ||
| 35 | u32 arg_1; | ||
| 36 | u32 values_count; | ||
| 37 | std::array<u32, 47> values{}; | ||
| 38 | }; | ||
| 39 | static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); | ||
| 40 | |||
| 41 | extern const std::array<Service::Mii::DefaultMii, 2> BaseMii; | ||
| 42 | extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii; | ||
| 43 | |||
| 44 | extern const std::array<u8, 62> EyeRotateLookup; | ||
| 45 | extern const std::array<u8, 24> EyebrowRotateLookup; | ||
| 46 | |||
| 47 | extern const std::array<RandomMiiData4, 18> RandomMiiFaceline; | ||
| 48 | extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor; | ||
| 49 | extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle; | ||
| 50 | extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup; | ||
| 51 | extern const std::array<RandomMiiData4, 18> RandomMiiHairType; | ||
| 52 | extern const std::array<RandomMiiData3, 9> RandomMiiHairColor; | ||
| 53 | extern const std::array<RandomMiiData4, 18> RandomMiiEyeType; | ||
| 54 | extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor; | ||
| 55 | extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType; | ||
| 56 | extern const std::array<RandomMiiData4, 18> RandomMiiNoseType; | ||
| 57 | extern const std::array<RandomMiiData4, 18> RandomMiiMouthType; | ||
| 58 | extern const std::array<RandomMiiData2, 3> RandomMiiGlassType; | ||
| 59 | |||
| 60 | u8 FromVer3GetFacelineColor(u8 color); | ||
| 61 | u8 FromVer3GetHairColor(u8 color); | ||
| 62 | u8 FromVer3GetEyeColor(u8 color); | ||
| 63 | u8 FromVer3GetMouthlineColor(u8 color); | ||
| 64 | u8 FromVer3GetGlassColor(u8 color); | ||
| 65 | u8 FromVer3GetGlassType(u8 type); | ||
| 66 | |||
| 67 | FacelineColor GetFacelineColorFromVer3(u32 color); | ||
| 68 | CommonColor GetHairColorFromVer3(u32 color); | ||
| 69 | CommonColor GetEyeColorFromVer3(u32 color); | ||
| 70 | CommonColor GetMouthColorFromVer3(u32 color); | ||
| 71 | CommonColor GetGlassColorFromVer3(u32 color); | ||
| 72 | |||
| 73 | } // namespace Service::Mii::RawData | ||
diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp new file mode 100644 index 000000000..8fce636c7 --- /dev/null +++ b/src/core/hle/service/mii/types/store_data.cpp | |||
| @@ -0,0 +1,643 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/hle/service/mii/mii_util.h" | ||
| 5 | #include "core/hle/service/mii/types/raw_data.h" | ||
| 6 | #include "core/hle/service/mii/types/store_data.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | |||
| 10 | void StoreData::BuildDefault(u32 mii_index) { | ||
| 11 | const auto& default_mii = RawData::DefaultMii[mii_index]; | ||
| 12 | core_data.SetDefault(); | ||
| 13 | |||
| 14 | core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type)); | ||
| 15 | core_data.SetFacelineColor( | ||
| 16 | RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color))); | ||
| 17 | core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle)); | ||
| 18 | core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup)); | ||
| 19 | |||
| 20 | core_data.SetHairType(static_cast<HairType>(default_mii.hair_type)); | ||
| 21 | core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color))); | ||
| 22 | core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip)); | ||
| 23 | core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type)); | ||
| 24 | core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color))); | ||
| 25 | core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale)); | ||
| 26 | core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect)); | ||
| 27 | core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate)); | ||
| 28 | core_data.SetEyeX(static_cast<u8>(default_mii.eye_x)); | ||
| 29 | core_data.SetEyeY(static_cast<u8>(default_mii.eye_y)); | ||
| 30 | |||
| 31 | core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type)); | ||
| 32 | core_data.SetEyebrowColor( | ||
| 33 | RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color))); | ||
| 34 | core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale)); | ||
| 35 | core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); | ||
| 36 | core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); | ||
| 37 | core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); | ||
| 38 | core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); | ||
| 39 | |||
| 40 | core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); | ||
| 41 | core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); | ||
| 42 | core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); | ||
| 43 | |||
| 44 | core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); | ||
| 45 | core_data.SetMouthColor( | ||
| 46 | RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); | ||
| 47 | core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); | ||
| 48 | core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect)); | ||
| 49 | core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y)); | ||
| 50 | |||
| 51 | core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type)); | ||
| 52 | core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type)); | ||
| 53 | core_data.SetBeardColor( | ||
| 54 | RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color))); | ||
| 55 | core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale)); | ||
| 56 | core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y)); | ||
| 57 | |||
| 58 | core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type)); | ||
| 59 | core_data.SetGlassColor( | ||
| 60 | RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color))); | ||
| 61 | core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale)); | ||
| 62 | core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y)); | ||
| 63 | |||
| 64 | core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type)); | ||
| 65 | core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale)); | ||
| 66 | core_data.SetMoleX(static_cast<u8>(default_mii.mole_x)); | ||
| 67 | core_data.SetMoleY(static_cast<u8>(default_mii.mole_y)); | ||
| 68 | |||
| 69 | core_data.SetHeight(static_cast<u8>(default_mii.height)); | ||
| 70 | core_data.SetBuild(static_cast<u8>(default_mii.weight)); | ||
| 71 | core_data.SetGender(static_cast<Gender>(default_mii.gender)); | ||
| 72 | core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color)); | ||
| 73 | core_data.SetRegionMove(static_cast<u8>(default_mii.region_move)); | ||
| 74 | core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region)); | ||
| 75 | core_data.SetType(static_cast<u8>(default_mii.type)); | ||
| 76 | core_data.SetNickname(default_mii.nickname); | ||
| 77 | |||
| 78 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 79 | create_id = MiiUtil::MakeCreateId(); | ||
| 80 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | ||
| 81 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 82 | } | ||
| 83 | |||
| 84 | void StoreData::BuildBase(Gender gender) { | ||
| 85 | const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0]; | ||
| 86 | core_data.SetDefault(); | ||
| 87 | |||
| 88 | core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type)); | ||
| 89 | core_data.SetFacelineColor( | ||
| 90 | RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color))); | ||
| 91 | core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle)); | ||
| 92 | core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup)); | ||
| 93 | |||
| 94 | core_data.SetHairType(static_cast<HairType>(default_mii.hair_type)); | ||
| 95 | core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color))); | ||
| 96 | core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip)); | ||
| 97 | core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type)); | ||
| 98 | core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color))); | ||
| 99 | core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale)); | ||
| 100 | core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect)); | ||
| 101 | core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate)); | ||
| 102 | core_data.SetEyeX(static_cast<u8>(default_mii.eye_x)); | ||
| 103 | core_data.SetEyeY(static_cast<u8>(default_mii.eye_y)); | ||
| 104 | |||
| 105 | core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type)); | ||
| 106 | core_data.SetEyebrowColor( | ||
| 107 | RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color))); | ||
| 108 | core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale)); | ||
| 109 | core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); | ||
| 110 | core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); | ||
| 111 | core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); | ||
| 112 | core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); | ||
| 113 | |||
| 114 | core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); | ||
| 115 | core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); | ||
| 116 | core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); | ||
| 117 | |||
| 118 | core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); | ||
| 119 | core_data.SetMouthColor( | ||
| 120 | RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); | ||
| 121 | core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); | ||
| 122 | core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect)); | ||
| 123 | core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y)); | ||
| 124 | |||
| 125 | core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type)); | ||
| 126 | core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type)); | ||
| 127 | core_data.SetBeardColor( | ||
| 128 | RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color))); | ||
| 129 | core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale)); | ||
| 130 | core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y)); | ||
| 131 | |||
| 132 | core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type)); | ||
| 133 | core_data.SetGlassColor( | ||
| 134 | RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color))); | ||
| 135 | core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale)); | ||
| 136 | core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y)); | ||
| 137 | |||
| 138 | core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type)); | ||
| 139 | core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale)); | ||
| 140 | core_data.SetMoleX(static_cast<u8>(default_mii.mole_x)); | ||
| 141 | core_data.SetMoleY(static_cast<u8>(default_mii.mole_y)); | ||
| 142 | |||
| 143 | core_data.SetHeight(static_cast<u8>(default_mii.height)); | ||
| 144 | core_data.SetBuild(static_cast<u8>(default_mii.weight)); | ||
| 145 | core_data.SetGender(static_cast<Gender>(default_mii.gender)); | ||
| 146 | core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color)); | ||
| 147 | core_data.SetRegionMove(static_cast<u8>(default_mii.region_move)); | ||
| 148 | core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region)); | ||
| 149 | core_data.SetType(static_cast<u8>(default_mii.type)); | ||
| 150 | core_data.SetNickname(default_mii.nickname); | ||
| 151 | |||
| 152 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 153 | create_id = MiiUtil::MakeCreateId(); | ||
| 154 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | ||
| 155 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 156 | } | ||
| 157 | |||
| 158 | void StoreData::BuildRandom(Age age, Gender gender, Race race) { | ||
| 159 | core_data.BuildRandom(age, gender, race); | ||
| 160 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 161 | create_id = MiiUtil::MakeCreateId(); | ||
| 162 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | ||
| 163 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 164 | } | ||
| 165 | |||
| 166 | void StoreData::SetInvalidName() { | ||
| 167 | const auto& invalid_name = core_data.GetInvalidNickname(); | ||
| 168 | const auto device_id = MiiUtil::GetDeviceId(); | ||
| 169 | core_data.SetNickname(invalid_name); | ||
| 170 | device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); | ||
| 171 | data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); | ||
| 172 | } | ||
| 173 | |||
| 174 | bool StoreData::IsSpecial() const { | ||
| 175 | return GetType() == 1; | ||
| 176 | } | ||
| 177 | |||
| 178 | u32 StoreData::IsValid() const { | ||
| 179 | // TODO: complete this | ||
| 180 | return 0; | ||
| 181 | } | ||
| 182 | |||
| 183 | void StoreData::SetFontRegion(FontRegion value) { | ||
| 184 | core_data.SetFontRegion(value); | ||
| 185 | } | ||
| 186 | |||
| 187 | void StoreData::SetFavoriteColor(FavoriteColor value) { | ||
| 188 | core_data.SetFavoriteColor(value); | ||
| 189 | } | ||
| 190 | |||
| 191 | void StoreData::SetGender(Gender value) { | ||
| 192 | core_data.SetGender(value); | ||
| 193 | } | ||
| 194 | |||
| 195 | void StoreData::SetHeight(u8 value) { | ||
| 196 | core_data.SetHeight(value); | ||
| 197 | } | ||
| 198 | |||
| 199 | void StoreData::SetBuild(u8 value) { | ||
| 200 | core_data.SetBuild(value); | ||
| 201 | } | ||
| 202 | |||
| 203 | void StoreData::SetType(u8 value) { | ||
| 204 | core_data.SetType(value); | ||
| 205 | } | ||
| 206 | |||
| 207 | void StoreData::SetRegionMove(u8 value) { | ||
| 208 | core_data.SetRegionMove(value); | ||
| 209 | } | ||
| 210 | |||
| 211 | void StoreData::SetFacelineType(FacelineType value) { | ||
| 212 | core_data.SetFacelineType(value); | ||
| 213 | } | ||
| 214 | |||
| 215 | void StoreData::SetFacelineColor(FacelineColor value) { | ||
| 216 | core_data.SetFacelineColor(value); | ||
| 217 | } | ||
| 218 | |||
| 219 | void StoreData::SetFacelineWrinkle(FacelineWrinkle value) { | ||
| 220 | core_data.SetFacelineWrinkle(value); | ||
| 221 | } | ||
| 222 | |||
| 223 | void StoreData::SetFacelineMake(FacelineMake value) { | ||
| 224 | core_data.SetFacelineMake(value); | ||
| 225 | } | ||
| 226 | |||
| 227 | void StoreData::SetHairType(HairType value) { | ||
| 228 | core_data.SetHairType(value); | ||
| 229 | } | ||
| 230 | |||
| 231 | void StoreData::SetHairColor(CommonColor value) { | ||
| 232 | core_data.SetHairColor(value); | ||
| 233 | } | ||
| 234 | |||
| 235 | void StoreData::SetHairFlip(HairFlip value) { | ||
| 236 | core_data.SetHairFlip(value); | ||
| 237 | } | ||
| 238 | |||
| 239 | void StoreData::SetEyeType(EyeType value) { | ||
| 240 | core_data.SetEyeType(value); | ||
| 241 | } | ||
| 242 | |||
| 243 | void StoreData::SetEyeColor(CommonColor value) { | ||
| 244 | core_data.SetEyeColor(value); | ||
| 245 | } | ||
| 246 | |||
| 247 | void StoreData::SetEyeScale(u8 value) { | ||
| 248 | core_data.SetEyeScale(value); | ||
| 249 | } | ||
| 250 | |||
| 251 | void StoreData::SetEyeAspect(u8 value) { | ||
| 252 | core_data.SetEyeAspect(value); | ||
| 253 | } | ||
| 254 | |||
| 255 | void StoreData::SetEyeRotate(u8 value) { | ||
| 256 | core_data.SetEyeRotate(value); | ||
| 257 | } | ||
| 258 | |||
| 259 | void StoreData::SetEyeX(u8 value) { | ||
| 260 | core_data.SetEyeX(value); | ||
| 261 | } | ||
| 262 | |||
| 263 | void StoreData::SetEyeY(u8 value) { | ||
| 264 | core_data.SetEyeY(value); | ||
| 265 | } | ||
| 266 | |||
| 267 | void StoreData::SetEyebrowType(EyebrowType value) { | ||
| 268 | core_data.SetEyebrowType(value); | ||
| 269 | } | ||
| 270 | |||
| 271 | void StoreData::SetEyebrowColor(CommonColor value) { | ||
| 272 | core_data.SetEyebrowColor(value); | ||
| 273 | } | ||
| 274 | |||
| 275 | void StoreData::SetEyebrowScale(u8 value) { | ||
| 276 | core_data.SetEyebrowScale(value); | ||
| 277 | } | ||
| 278 | |||
| 279 | void StoreData::SetEyebrowAspect(u8 value) { | ||
| 280 | core_data.SetEyebrowAspect(value); | ||
| 281 | } | ||
| 282 | |||
| 283 | void StoreData::SetEyebrowRotate(u8 value) { | ||
| 284 | core_data.SetEyebrowRotate(value); | ||
| 285 | } | ||
| 286 | |||
| 287 | void StoreData::SetEyebrowX(u8 value) { | ||
| 288 | core_data.SetEyebrowX(value); | ||
| 289 | } | ||
| 290 | |||
| 291 | void StoreData::SetEyebrowY(u8 value) { | ||
| 292 | core_data.SetEyebrowY(value); | ||
| 293 | } | ||
| 294 | |||
| 295 | void StoreData::SetNoseType(NoseType value) { | ||
| 296 | core_data.SetNoseType(value); | ||
| 297 | } | ||
| 298 | |||
| 299 | void StoreData::SetNoseScale(u8 value) { | ||
| 300 | core_data.SetNoseScale(value); | ||
| 301 | } | ||
| 302 | |||
| 303 | void StoreData::SetNoseY(u8 value) { | ||
| 304 | core_data.SetNoseY(value); | ||
| 305 | } | ||
| 306 | |||
| 307 | void StoreData::SetMouthType(u8 value) { | ||
| 308 | core_data.SetMouthType(value); | ||
| 309 | } | ||
| 310 | |||
| 311 | void StoreData::SetMouthColor(CommonColor value) { | ||
| 312 | core_data.SetMouthColor(value); | ||
| 313 | } | ||
| 314 | |||
| 315 | void StoreData::SetMouthScale(u8 value) { | ||
| 316 | core_data.SetMouthScale(value); | ||
| 317 | } | ||
| 318 | |||
| 319 | void StoreData::SetMouthAspect(u8 value) { | ||
| 320 | core_data.SetMouthAspect(value); | ||
| 321 | } | ||
| 322 | |||
| 323 | void StoreData::SetMouthY(u8 value) { | ||
| 324 | core_data.SetMouthY(value); | ||
| 325 | } | ||
| 326 | |||
| 327 | void StoreData::SetBeardColor(CommonColor value) { | ||
| 328 | core_data.SetBeardColor(value); | ||
| 329 | } | ||
| 330 | |||
| 331 | void StoreData::SetBeardType(BeardType value) { | ||
| 332 | core_data.SetBeardType(value); | ||
| 333 | } | ||
| 334 | |||
| 335 | void StoreData::SetMustacheType(MustacheType value) { | ||
| 336 | core_data.SetMustacheType(value); | ||
| 337 | } | ||
| 338 | |||
| 339 | void StoreData::SetMustacheScale(u8 value) { | ||
| 340 | core_data.SetMustacheScale(value); | ||
| 341 | } | ||
| 342 | |||
| 343 | void StoreData::SetMustacheY(u8 value) { | ||
| 344 | core_data.SetMustacheY(value); | ||
| 345 | } | ||
| 346 | |||
| 347 | void StoreData::SetGlassType(GlassType value) { | ||
| 348 | core_data.SetGlassType(value); | ||
| 349 | } | ||
| 350 | |||
| 351 | void StoreData::SetGlassColor(CommonColor value) { | ||
| 352 | core_data.SetGlassColor(value); | ||
| 353 | } | ||
| 354 | |||
| 355 | void StoreData::SetGlassScale(u8 value) { | ||
| 356 | core_data.SetGlassScale(value); | ||
| 357 | } | ||
| 358 | |||
| 359 | void StoreData::SetGlassY(u8 value) { | ||
| 360 | core_data.SetGlassY(value); | ||
| 361 | } | ||
| 362 | |||
| 363 | void StoreData::SetMoleType(MoleType value) { | ||
| 364 | core_data.SetMoleType(value); | ||
| 365 | } | ||
| 366 | |||
| 367 | void StoreData::SetMoleScale(u8 value) { | ||
| 368 | core_data.SetMoleScale(value); | ||
| 369 | } | ||
| 370 | |||
| 371 | void StoreData::SetMoleX(u8 value) { | ||
| 372 | core_data.SetMoleX(value); | ||
| 373 | } | ||
| 374 | |||
| 375 | void StoreData::SetMoleY(u8 value) { | ||
| 376 | core_data.SetMoleY(value); | ||
| 377 | } | ||
| 378 | |||
| 379 | void StoreData::SetNickname(Nickname value) { | ||
| 380 | core_data.SetNickname(value); | ||
| 381 | } | ||
| 382 | |||
| 383 | Common::UUID StoreData::GetCreateId() const { | ||
| 384 | return create_id; | ||
| 385 | } | ||
| 386 | |||
| 387 | FontRegion StoreData::GetFontRegion() const { | ||
| 388 | return static_cast<FontRegion>(core_data.GetFontRegion()); | ||
| 389 | } | ||
| 390 | |||
| 391 | FavoriteColor StoreData::GetFavoriteColor() const { | ||
| 392 | return core_data.GetFavoriteColor(); | ||
| 393 | } | ||
| 394 | |||
| 395 | Gender StoreData::GetGender() const { | ||
| 396 | return core_data.GetGender(); | ||
| 397 | } | ||
| 398 | |||
| 399 | u8 StoreData::GetHeight() const { | ||
| 400 | return core_data.GetHeight(); | ||
| 401 | } | ||
| 402 | |||
| 403 | u8 StoreData::GetBuild() const { | ||
| 404 | return core_data.GetBuild(); | ||
| 405 | } | ||
| 406 | |||
| 407 | u8 StoreData::GetType() const { | ||
| 408 | return core_data.GetType(); | ||
| 409 | } | ||
| 410 | |||
| 411 | u8 StoreData::GetRegionMove() const { | ||
| 412 | return core_data.GetRegionMove(); | ||
| 413 | } | ||
| 414 | |||
| 415 | FacelineType StoreData::GetFacelineType() const { | ||
| 416 | return core_data.GetFacelineType(); | ||
| 417 | } | ||
| 418 | |||
| 419 | FacelineColor StoreData::GetFacelineColor() const { | ||
| 420 | return core_data.GetFacelineColor(); | ||
| 421 | } | ||
| 422 | |||
| 423 | FacelineWrinkle StoreData::GetFacelineWrinkle() const { | ||
| 424 | return core_data.GetFacelineWrinkle(); | ||
| 425 | } | ||
| 426 | |||
| 427 | FacelineMake StoreData::GetFacelineMake() const { | ||
| 428 | return core_data.GetFacelineMake(); | ||
| 429 | } | ||
| 430 | |||
| 431 | HairType StoreData::GetHairType() const { | ||
| 432 | return core_data.GetHairType(); | ||
| 433 | } | ||
| 434 | |||
| 435 | CommonColor StoreData::GetHairColor() const { | ||
| 436 | return core_data.GetHairColor(); | ||
| 437 | } | ||
| 438 | |||
| 439 | HairFlip StoreData::GetHairFlip() const { | ||
| 440 | return core_data.GetHairFlip(); | ||
| 441 | } | ||
| 442 | |||
| 443 | EyeType StoreData::GetEyeType() const { | ||
| 444 | return core_data.GetEyeType(); | ||
| 445 | } | ||
| 446 | |||
| 447 | CommonColor StoreData::GetEyeColor() const { | ||
| 448 | return core_data.GetEyeColor(); | ||
| 449 | } | ||
| 450 | |||
| 451 | u8 StoreData::GetEyeScale() const { | ||
| 452 | return core_data.GetEyeScale(); | ||
| 453 | } | ||
| 454 | |||
| 455 | u8 StoreData::GetEyeAspect() const { | ||
| 456 | return core_data.GetEyeAspect(); | ||
| 457 | } | ||
| 458 | |||
| 459 | u8 StoreData::GetEyeRotate() const { | ||
| 460 | return core_data.GetEyeRotate(); | ||
| 461 | } | ||
| 462 | |||
| 463 | u8 StoreData::GetEyeX() const { | ||
| 464 | return core_data.GetEyeX(); | ||
| 465 | } | ||
| 466 | |||
| 467 | u8 StoreData::GetEyeY() const { | ||
| 468 | return core_data.GetEyeY(); | ||
| 469 | } | ||
| 470 | |||
| 471 | EyebrowType StoreData::GetEyebrowType() const { | ||
| 472 | return core_data.GetEyebrowType(); | ||
| 473 | } | ||
| 474 | |||
| 475 | CommonColor StoreData::GetEyebrowColor() const { | ||
| 476 | return core_data.GetEyebrowColor(); | ||
| 477 | } | ||
| 478 | |||
| 479 | u8 StoreData::GetEyebrowScale() const { | ||
| 480 | return core_data.GetEyebrowScale(); | ||
| 481 | } | ||
| 482 | |||
| 483 | u8 StoreData::GetEyebrowAspect() const { | ||
| 484 | return core_data.GetEyebrowAspect(); | ||
| 485 | } | ||
| 486 | |||
| 487 | u8 StoreData::GetEyebrowRotate() const { | ||
| 488 | return core_data.GetEyebrowRotate(); | ||
| 489 | } | ||
| 490 | |||
| 491 | u8 StoreData::GetEyebrowX() const { | ||
| 492 | return core_data.GetEyebrowX(); | ||
| 493 | } | ||
| 494 | |||
| 495 | u8 StoreData::GetEyebrowY() const { | ||
| 496 | return core_data.GetEyebrowY(); | ||
| 497 | } | ||
| 498 | |||
| 499 | NoseType StoreData::GetNoseType() const { | ||
| 500 | return core_data.GetNoseType(); | ||
| 501 | } | ||
| 502 | |||
| 503 | u8 StoreData::GetNoseScale() const { | ||
| 504 | return core_data.GetNoseScale(); | ||
| 505 | } | ||
| 506 | |||
| 507 | u8 StoreData::GetNoseY() const { | ||
| 508 | return core_data.GetNoseY(); | ||
| 509 | } | ||
| 510 | |||
| 511 | MouthType StoreData::GetMouthType() const { | ||
| 512 | return core_data.GetMouthType(); | ||
| 513 | } | ||
| 514 | |||
| 515 | CommonColor StoreData::GetMouthColor() const { | ||
| 516 | return core_data.GetMouthColor(); | ||
| 517 | } | ||
| 518 | |||
| 519 | u8 StoreData::GetMouthScale() const { | ||
| 520 | return core_data.GetMouthScale(); | ||
| 521 | } | ||
| 522 | |||
| 523 | u8 StoreData::GetMouthAspect() const { | ||
| 524 | return core_data.GetMouthAspect(); | ||
| 525 | } | ||
| 526 | |||
| 527 | u8 StoreData::GetMouthY() const { | ||
| 528 | return core_data.GetMouthY(); | ||
| 529 | } | ||
| 530 | |||
| 531 | CommonColor StoreData::GetBeardColor() const { | ||
| 532 | return core_data.GetBeardColor(); | ||
| 533 | } | ||
| 534 | |||
| 535 | BeardType StoreData::GetBeardType() const { | ||
| 536 | return core_data.GetBeardType(); | ||
| 537 | } | ||
| 538 | |||
| 539 | MustacheType StoreData::GetMustacheType() const { | ||
| 540 | return core_data.GetMustacheType(); | ||
| 541 | } | ||
| 542 | |||
| 543 | u8 StoreData::GetMustacheScale() const { | ||
| 544 | return core_data.GetMustacheScale(); | ||
| 545 | } | ||
| 546 | |||
| 547 | u8 StoreData::GetMustacheY() const { | ||
| 548 | return core_data.GetMustacheY(); | ||
| 549 | } | ||
| 550 | |||
| 551 | GlassType StoreData::GetGlassType() const { | ||
| 552 | return core_data.GetGlassType(); | ||
| 553 | } | ||
| 554 | |||
| 555 | CommonColor StoreData::GetGlassColor() const { | ||
| 556 | return core_data.GetGlassColor(); | ||
| 557 | } | ||
| 558 | |||
| 559 | u8 StoreData::GetGlassScale() const { | ||
| 560 | return core_data.GetGlassScale(); | ||
| 561 | } | ||
| 562 | |||
| 563 | u8 StoreData::GetGlassY() const { | ||
| 564 | return core_data.GetGlassY(); | ||
| 565 | } | ||
| 566 | |||
| 567 | MoleType StoreData::GetMoleType() const { | ||
| 568 | return core_data.GetMoleType(); | ||
| 569 | } | ||
| 570 | |||
| 571 | u8 StoreData::GetMoleScale() const { | ||
| 572 | return core_data.GetMoleScale(); | ||
| 573 | } | ||
| 574 | |||
| 575 | u8 StoreData::GetMoleX() const { | ||
| 576 | return core_data.GetMoleX(); | ||
| 577 | } | ||
| 578 | |||
| 579 | u8 StoreData::GetMoleY() const { | ||
| 580 | return core_data.GetMoleY(); | ||
| 581 | } | ||
| 582 | |||
| 583 | Nickname StoreData::GetNickname() const { | ||
| 584 | return core_data.GetNickname(); | ||
| 585 | } | ||
| 586 | |||
| 587 | bool StoreData::operator==(const StoreData& data) { | ||
| 588 | bool is_identical = data.core_data.IsValid() == 0; | ||
| 589 | is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; | ||
| 590 | is_identical &= GetCreateId() == data.GetCreateId(); | ||
| 591 | is_identical &= GetFontRegion() == data.GetFontRegion(); | ||
| 592 | is_identical &= GetFavoriteColor() == data.GetFavoriteColor(); | ||
| 593 | is_identical &= GetGender() == data.GetGender(); | ||
| 594 | is_identical &= GetHeight() == data.GetHeight(); | ||
| 595 | is_identical &= GetBuild() == data.GetBuild(); | ||
| 596 | is_identical &= GetType() == data.GetType(); | ||
| 597 | is_identical &= GetRegionMove() == data.GetRegionMove(); | ||
| 598 | is_identical &= GetFacelineType() == data.GetFacelineType(); | ||
| 599 | is_identical &= GetFacelineColor() == data.GetFacelineColor(); | ||
| 600 | is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle(); | ||
| 601 | is_identical &= GetFacelineMake() == data.GetFacelineMake(); | ||
| 602 | is_identical &= GetHairType() == data.GetHairType(); | ||
| 603 | is_identical &= GetHairColor() == data.GetHairColor(); | ||
| 604 | is_identical &= GetHairFlip() == data.GetHairFlip(); | ||
| 605 | is_identical &= GetEyeType() == data.GetEyeType(); | ||
| 606 | is_identical &= GetEyeColor() == data.GetEyeColor(); | ||
| 607 | is_identical &= GetEyeScale() == data.GetEyeScale(); | ||
| 608 | is_identical &= GetEyeAspect() == data.GetEyeAspect(); | ||
| 609 | is_identical &= GetEyeRotate() == data.GetEyeRotate(); | ||
| 610 | is_identical &= GetEyeX() == data.GetEyeX(); | ||
| 611 | is_identical &= GetEyeY() == data.GetEyeY(); | ||
| 612 | is_identical &= GetEyebrowType() == data.GetEyebrowType(); | ||
| 613 | is_identical &= GetEyebrowColor() == data.GetEyebrowColor(); | ||
| 614 | is_identical &= GetEyebrowScale() == data.GetEyebrowScale(); | ||
| 615 | is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect(); | ||
| 616 | is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate(); | ||
| 617 | is_identical &= GetEyebrowX() == data.GetEyebrowX(); | ||
| 618 | is_identical &= GetEyebrowY() == data.GetEyebrowY(); | ||
| 619 | is_identical &= GetNoseType() == data.GetNoseType(); | ||
| 620 | is_identical &= GetNoseScale() == data.GetNoseScale(); | ||
| 621 | is_identical &= GetNoseY() == data.GetNoseY(); | ||
| 622 | is_identical &= GetMouthType() == data.GetMouthType(); | ||
| 623 | is_identical &= GetMouthColor() == data.GetMouthColor(); | ||
| 624 | is_identical &= GetMouthScale() == data.GetMouthScale(); | ||
| 625 | is_identical &= GetMouthAspect() == data.GetMouthAspect(); | ||
| 626 | is_identical &= GetMouthY() == data.GetMouthY(); | ||
| 627 | is_identical &= GetBeardColor() == data.GetBeardColor(); | ||
| 628 | is_identical &= GetBeardType() == data.GetBeardType(); | ||
| 629 | is_identical &= GetMustacheType() == data.GetMustacheType(); | ||
| 630 | is_identical &= GetMustacheScale() == data.GetMustacheScale(); | ||
| 631 | is_identical &= GetMustacheY() == data.GetMustacheY(); | ||
| 632 | is_identical &= GetGlassType() == data.GetGlassType(); | ||
| 633 | is_identical &= GetGlassColor() == data.GetGlassColor(); | ||
| 634 | is_identical &= GetGlassScale() == data.GetGlassScale(); | ||
| 635 | is_identical &= GetGlassY() == data.GetGlassY(); | ||
| 636 | is_identical &= GetMoleType() == data.GetMoleType(); | ||
| 637 | is_identical &= GetMoleScale() == data.GetMoleScale(); | ||
| 638 | is_identical &= GetMoleX() == data.GetMoleX(); | ||
| 639 | is_identical &= data.GetMoleY() == data.GetMoleY(); | ||
| 640 | return is_identical; | ||
| 641 | } | ||
| 642 | |||
| 643 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h new file mode 100644 index 000000000..224c32cf8 --- /dev/null +++ b/src/core/hle/service/mii/types/store_data.h | |||
| @@ -0,0 +1,145 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | ||
| 7 | #include "core/hle/service/mii/types/core_data.h" | ||
| 8 | |||
| 9 | namespace Service::Mii { | ||
| 10 | |||
| 11 | class StoreData { | ||
| 12 | public: | ||
| 13 | // nn::mii::detail::StoreDataRaw::BuildDefault | ||
| 14 | void BuildDefault(u32 mii_index); | ||
| 15 | // nn::mii::detail::StoreDataRaw::BuildDefault | ||
| 16 | |||
| 17 | void BuildBase(Gender gender); | ||
| 18 | // nn::mii::detail::StoreDataRaw::BuildRandom | ||
| 19 | void BuildRandom(Age age, Gender gender, Race race); | ||
| 20 | |||
| 21 | bool IsSpecial() const; | ||
| 22 | |||
| 23 | u32 IsValid() const; | ||
| 24 | |||
| 25 | void SetFontRegion(FontRegion value); | ||
| 26 | void SetFavoriteColor(FavoriteColor value); | ||
| 27 | void SetGender(Gender value); | ||
| 28 | void SetHeight(u8 value); | ||
| 29 | void SetBuild(u8 value); | ||
| 30 | void SetType(u8 value); | ||
| 31 | void SetRegionMove(u8 value); | ||
| 32 | void SetFacelineType(FacelineType value); | ||
| 33 | void SetFacelineColor(FacelineColor value); | ||
| 34 | void SetFacelineWrinkle(FacelineWrinkle value); | ||
| 35 | void SetFacelineMake(FacelineMake value); | ||
| 36 | void SetHairType(HairType value); | ||
| 37 | void SetHairColor(CommonColor value); | ||
| 38 | void SetHairFlip(HairFlip value); | ||
| 39 | void SetEyeType(EyeType value); | ||
| 40 | void SetEyeColor(CommonColor value); | ||
| 41 | void SetEyeScale(u8 value); | ||
| 42 | void SetEyeAspect(u8 value); | ||
| 43 | void SetEyeRotate(u8 value); | ||
| 44 | void SetEyeX(u8 value); | ||
| 45 | void SetEyeY(u8 value); | ||
| 46 | void SetEyebrowType(EyebrowType value); | ||
| 47 | void SetEyebrowColor(CommonColor value); | ||
| 48 | void SetEyebrowScale(u8 value); | ||
| 49 | void SetEyebrowAspect(u8 value); | ||
| 50 | void SetEyebrowRotate(u8 value); | ||
| 51 | void SetEyebrowX(u8 value); | ||
| 52 | void SetEyebrowY(u8 value); | ||
| 53 | void SetNoseType(NoseType value); | ||
| 54 | void SetNoseScale(u8 value); | ||
| 55 | void SetNoseY(u8 value); | ||
| 56 | void SetMouthType(u8 value); | ||
| 57 | void SetMouthColor(CommonColor value); | ||
| 58 | void SetMouthScale(u8 value); | ||
| 59 | void SetMouthAspect(u8 value); | ||
| 60 | void SetMouthY(u8 value); | ||
| 61 | void SetBeardColor(CommonColor value); | ||
| 62 | void SetBeardType(BeardType value); | ||
| 63 | void SetMustacheType(MustacheType value); | ||
| 64 | void SetMustacheScale(u8 value); | ||
| 65 | void SetMustacheY(u8 value); | ||
| 66 | void SetGlassType(GlassType value); | ||
| 67 | void SetGlassColor(CommonColor value); | ||
| 68 | void SetGlassScale(u8 value); | ||
| 69 | void SetGlassY(u8 value); | ||
| 70 | void SetMoleType(MoleType value); | ||
| 71 | void SetMoleScale(u8 value); | ||
| 72 | void SetMoleX(u8 value); | ||
| 73 | void SetMoleY(u8 value); | ||
| 74 | void SetNickname(Nickname nickname); | ||
| 75 | void SetInvalidName(); | ||
| 76 | |||
| 77 | Common::UUID GetCreateId() const; | ||
| 78 | FontRegion GetFontRegion() const; | ||
| 79 | FavoriteColor GetFavoriteColor() const; | ||
| 80 | Gender GetGender() const; | ||
| 81 | u8 GetHeight() const; | ||
| 82 | u8 GetBuild() const; | ||
| 83 | u8 GetType() const; | ||
| 84 | u8 GetRegionMove() const; | ||
| 85 | FacelineType GetFacelineType() const; | ||
| 86 | FacelineColor GetFacelineColor() const; | ||
| 87 | FacelineWrinkle GetFacelineWrinkle() const; | ||
| 88 | FacelineMake GetFacelineMake() const; | ||
| 89 | HairType GetHairType() const; | ||
| 90 | CommonColor GetHairColor() const; | ||
| 91 | HairFlip GetHairFlip() const; | ||
| 92 | EyeType GetEyeType() const; | ||
| 93 | CommonColor GetEyeColor() const; | ||
| 94 | u8 GetEyeScale() const; | ||
| 95 | u8 GetEyeAspect() const; | ||
| 96 | u8 GetEyeRotate() const; | ||
| 97 | u8 GetEyeX() const; | ||
| 98 | u8 GetEyeY() const; | ||
| 99 | EyebrowType GetEyebrowType() const; | ||
| 100 | CommonColor GetEyebrowColor() const; | ||
| 101 | u8 GetEyebrowScale() const; | ||
| 102 | u8 GetEyebrowAspect() const; | ||
| 103 | u8 GetEyebrowRotate() const; | ||
| 104 | u8 GetEyebrowX() const; | ||
| 105 | u8 GetEyebrowY() const; | ||
| 106 | NoseType GetNoseType() const; | ||
| 107 | u8 GetNoseScale() const; | ||
| 108 | u8 GetNoseY() const; | ||
| 109 | MouthType GetMouthType() const; | ||
| 110 | CommonColor GetMouthColor() const; | ||
| 111 | u8 GetMouthScale() const; | ||
| 112 | u8 GetMouthAspect() const; | ||
| 113 | u8 GetMouthY() const; | ||
| 114 | CommonColor GetBeardColor() const; | ||
| 115 | BeardType GetBeardType() const; | ||
| 116 | MustacheType GetMustacheType() const; | ||
| 117 | u8 GetMustacheScale() const; | ||
| 118 | u8 GetMustacheY() const; | ||
| 119 | GlassType GetGlassType() const; | ||
| 120 | CommonColor GetGlassColor() const; | ||
| 121 | u8 GetGlassScale() const; | ||
| 122 | u8 GetGlassY() const; | ||
| 123 | MoleType GetMoleType() const; | ||
| 124 | u8 GetMoleScale() const; | ||
| 125 | u8 GetMoleX() const; | ||
| 126 | u8 GetMoleY() const; | ||
| 127 | Nickname GetNickname() const; | ||
| 128 | |||
| 129 | bool operator==(const StoreData& data); | ||
| 130 | |||
| 131 | private: | ||
| 132 | CoreData core_data{}; | ||
| 133 | Common::UUID create_id{}; | ||
| 134 | u16 data_crc{}; | ||
| 135 | u16 device_crc{}; | ||
| 136 | }; | ||
| 137 | static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); | ||
| 138 | |||
| 139 | struct StoreDataElement { | ||
| 140 | StoreData store_data{}; | ||
| 141 | Source source{}; | ||
| 142 | }; | ||
| 143 | static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size."); | ||
| 144 | |||
| 145 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp new file mode 100644 index 000000000..1c28e0b1b --- /dev/null +++ b/src/core/hle/service/mii/types/ver3_store_data.cpp | |||
| @@ -0,0 +1,241 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/hle/service/mii/mii_util.h" | ||
| 5 | #include "core/hle/service/mii/types/raw_data.h" | ||
| 6 | #include "core/hle/service/mii/types/store_data.h" | ||
| 7 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 8 | |||
| 9 | namespace Service::Mii { | ||
| 10 | |||
| 11 | void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) { | ||
| 12 | faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf; | ||
| 13 | hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f; | ||
| 14 | eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f; | ||
| 15 | eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f; | ||
| 16 | mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f; | ||
| 17 | beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f; | ||
| 18 | glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f; | ||
| 19 | glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f; | ||
| 20 | } | ||
| 21 | |||
| 22 | void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { | ||
| 23 | out_store_data.BuildBase(Gender::Male); | ||
| 24 | |||
| 25 | if (!IsValid()) { | ||
| 26 | return; | ||
| 27 | } | ||
| 28 | |||
| 29 | // TODO: We are ignoring a bunch of data from the mii_v3 | ||
| 30 | |||
| 31 | out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); | ||
| 32 | out_store_data.SetFavoriteColor( | ||
| 33 | static_cast<FavoriteColor>(mii_information.favorite_color.Value())); | ||
| 34 | out_store_data.SetHeight(height); | ||
| 35 | out_store_data.SetBuild(build); | ||
| 36 | |||
| 37 | out_store_data.SetNickname(mii_name); | ||
| 38 | out_store_data.SetFontRegion( | ||
| 39 | static_cast<FontRegion>(static_cast<u8>(region_information.font_region))); | ||
| 40 | |||
| 41 | out_store_data.SetFacelineType( | ||
| 42 | static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); | ||
| 43 | out_store_data.SetFacelineColor( | ||
| 44 | static_cast<FacelineColor>(appearance_bits1.faceline_color.Value())); | ||
| 45 | out_store_data.SetFacelineWrinkle( | ||
| 46 | static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); | ||
| 47 | out_store_data.SetFacelineMake( | ||
| 48 | static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); | ||
| 49 | |||
| 50 | out_store_data.SetHairType(static_cast<HairType>(hair_type)); | ||
| 51 | out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value())); | ||
| 52 | out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); | ||
| 53 | |||
| 54 | out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); | ||
| 55 | out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value())); | ||
| 56 | out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale)); | ||
| 57 | out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect)); | ||
| 58 | out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate)); | ||
| 59 | out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x)); | ||
| 60 | out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y)); | ||
| 61 | |||
| 62 | out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); | ||
| 63 | out_store_data.SetEyebrowColor( | ||
| 64 | static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value())); | ||
| 65 | out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale)); | ||
| 66 | out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect)); | ||
| 67 | out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate)); | ||
| 68 | out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x)); | ||
| 69 | out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y)); | ||
| 70 | |||
| 71 | out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); | ||
| 72 | out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale)); | ||
| 73 | out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y)); | ||
| 74 | |||
| 75 | out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type)); | ||
| 76 | out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value())); | ||
| 77 | out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale)); | ||
| 78 | out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect)); | ||
| 79 | out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y)); | ||
| 80 | |||
| 81 | out_store_data.SetMustacheType( | ||
| 82 | static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); | ||
| 83 | out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale)); | ||
| 84 | out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y)); | ||
| 85 | |||
| 86 | out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); | ||
| 87 | out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value())); | ||
| 88 | |||
| 89 | out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); | ||
| 90 | out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value())); | ||
| 91 | out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale)); | ||
| 92 | out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y)); | ||
| 93 | |||
| 94 | out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); | ||
| 95 | out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale)); | ||
| 96 | out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x)); | ||
| 97 | out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y)); | ||
| 98 | } | ||
| 99 | |||
| 100 | void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { | ||
| 101 | version = 1; | ||
| 102 | mii_information.gender.Assign(static_cast<u8>(store_data.GetGender())); | ||
| 103 | mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor())); | ||
| 104 | height = store_data.GetHeight(); | ||
| 105 | build = store_data.GetBuild(); | ||
| 106 | |||
| 107 | mii_name = store_data.GetNickname(); | ||
| 108 | region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion())); | ||
| 109 | |||
| 110 | appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType())); | ||
| 111 | appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle())); | ||
| 112 | appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake())); | ||
| 113 | |||
| 114 | hair_type = static_cast<u8>(store_data.GetHairType()); | ||
| 115 | appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip())); | ||
| 116 | |||
| 117 | appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType())); | ||
| 118 | appearance_bits4.eye_scale.Assign(store_data.GetEyeScale()); | ||
| 119 | appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect()); | ||
| 120 | appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate()); | ||
| 121 | appearance_bits4.eye_x.Assign(store_data.GetEyeX()); | ||
| 122 | appearance_bits4.eye_y.Assign(store_data.GetEyeY()); | ||
| 123 | |||
| 124 | appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType())); | ||
| 125 | appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale()); | ||
| 126 | appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect()); | ||
| 127 | appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate()); | ||
| 128 | appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX()); | ||
| 129 | appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY()); | ||
| 130 | |||
| 131 | appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType())); | ||
| 132 | appearance_bits6.nose_scale.Assign(store_data.GetNoseScale()); | ||
| 133 | appearance_bits6.nose_y.Assign(store_data.GetNoseY()); | ||
| 134 | |||
| 135 | appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType())); | ||
| 136 | appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale()); | ||
| 137 | appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect()); | ||
| 138 | appearance_bits8.mouth_y.Assign(store_data.GetMouthY()); | ||
| 139 | |||
| 140 | appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType())); | ||
| 141 | appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale()); | ||
| 142 | appearance_bits9.mustache_y.Assign(store_data.GetMustacheY()); | ||
| 143 | |||
| 144 | appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType())); | ||
| 145 | |||
| 146 | appearance_bits10.glass_scale.Assign(store_data.GetGlassScale()); | ||
| 147 | appearance_bits10.glass_y.Assign(store_data.GetGlassY()); | ||
| 148 | |||
| 149 | appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType())); | ||
| 150 | appearance_bits11.mole_scale.Assign(store_data.GetMoleScale()); | ||
| 151 | appearance_bits11.mole_x.Assign(store_data.GetMoleX()); | ||
| 152 | appearance_bits11.mole_y.Assign(store_data.GetMoleY()); | ||
| 153 | |||
| 154 | // These types are converted to V3 from a table | ||
| 155 | appearance_bits1.faceline_color.Assign( | ||
| 156 | RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor()))); | ||
| 157 | appearance_bits3.hair_color.Assign( | ||
| 158 | RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor()))); | ||
| 159 | appearance_bits4.eye_color.Assign( | ||
| 160 | RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor()))); | ||
| 161 | appearance_bits5.eyebrow_color.Assign( | ||
| 162 | RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor()))); | ||
| 163 | appearance_bits7.mouth_color.Assign( | ||
| 164 | RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor()))); | ||
| 165 | appearance_bits9.beard_color.Assign( | ||
| 166 | RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor()))); | ||
| 167 | appearance_bits10.glass_color.Assign( | ||
| 168 | RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor()))); | ||
| 169 | appearance_bits10.glass_type.Assign( | ||
| 170 | RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType()))); | ||
| 171 | |||
| 172 | crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16)); | ||
| 173 | } | ||
| 174 | |||
| 175 | u32 Ver3StoreData::IsValid() const { | ||
| 176 | bool is_valid = version == 0 || version == 3; | ||
| 177 | |||
| 178 | is_valid = is_valid && (mii_name.data[0] != '\0'); | ||
| 179 | |||
| 180 | is_valid = is_valid && (mii_information.birth_month < 13); | ||
| 181 | is_valid = is_valid && (mii_information.birth_day < 32); | ||
| 182 | is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max)); | ||
| 183 | is_valid = is_valid && (height <= MaxHeight); | ||
| 184 | is_valid = is_valid && (build <= MaxBuild); | ||
| 185 | |||
| 186 | is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max)); | ||
| 187 | is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2); | ||
| 188 | is_valid = | ||
| 189 | is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max)); | ||
| 190 | is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max)); | ||
| 191 | |||
| 192 | is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max)); | ||
| 193 | is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor); | ||
| 194 | |||
| 195 | is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max)); | ||
| 196 | is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2); | ||
| 197 | is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale); | ||
| 198 | is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect); | ||
| 199 | is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate); | ||
| 200 | is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX); | ||
| 201 | is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY); | ||
| 202 | |||
| 203 | is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max)); | ||
| 204 | is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor); | ||
| 205 | is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale); | ||
| 206 | is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect); | ||
| 207 | is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate); | ||
| 208 | is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX); | ||
| 209 | is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY); | ||
| 210 | |||
| 211 | is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max)); | ||
| 212 | is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale); | ||
| 213 | is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY); | ||
| 214 | |||
| 215 | is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max)); | ||
| 216 | is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3); | ||
| 217 | is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale); | ||
| 218 | is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect); | ||
| 219 | is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY); | ||
| 220 | |||
| 221 | is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); | ||
| 222 | is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); | ||
| 223 | is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY); | ||
| 224 | |||
| 225 | is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); | ||
| 226 | is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); | ||
| 227 | |||
| 228 | is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); | ||
| 229 | is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); | ||
| 230 | is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); | ||
| 231 | is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale); | ||
| 232 | |||
| 233 | is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); | ||
| 234 | is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); | ||
| 235 | is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX); | ||
| 236 | is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY); | ||
| 237 | |||
| 238 | return is_valid; | ||
| 239 | } | ||
| 240 | |||
| 241 | } // namespace Service::Mii | ||
diff --git a/src/core/hle/service/mii/types/ver3_store_data.h b/src/core/hle/service/mii/types/ver3_store_data.h new file mode 100644 index 000000000..47907bf7d --- /dev/null +++ b/src/core/hle/service/mii/types/ver3_store_data.h | |||
| @@ -0,0 +1,160 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/service/mii/mii_types.h" | ||
| 7 | |||
| 8 | namespace Service::Mii { | ||
| 9 | class StoreData; | ||
| 10 | |||
| 11 | // This is nn::mii::Ver3StoreData | ||
| 12 | // Based on citra HLE::Applets::MiiData and PretendoNetwork. | ||
| 13 | // https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 | ||
| 14 | // https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 | ||
| 15 | |||
| 16 | struct NfpStoreDataExtension { | ||
| 17 | void SetFromStoreData(const StoreData& store_data); | ||
| 18 | |||
| 19 | u8 faceline_color; | ||
| 20 | u8 hair_color; | ||
| 21 | u8 eye_color; | ||
| 22 | u8 eyebrow_color; | ||
| 23 | u8 mouth_color; | ||
| 24 | u8 beard_color; | ||
| 25 | u8 glass_color; | ||
| 26 | u8 glass_type; | ||
| 27 | }; | ||
| 28 | static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); | ||
| 29 | |||
| 30 | #pragma pack(push, 4) | ||
| 31 | class Ver3StoreData { | ||
| 32 | public: | ||
| 33 | void BuildToStoreData(StoreData& out_store_data) const; | ||
| 34 | void BuildFromStoreData(const StoreData& store_data); | ||
| 35 | |||
| 36 | u32 IsValid() const; | ||
| 37 | |||
| 38 | u8 version; | ||
| 39 | union { | ||
| 40 | u8 raw; | ||
| 41 | |||
| 42 | BitField<0, 1, u8> allow_copying; | ||
| 43 | BitField<1, 1, u8> profanity_flag; | ||
| 44 | BitField<2, 2, u8> region_lock; | ||
| 45 | BitField<4, 2, u8> font_region; | ||
| 46 | } region_information; | ||
| 47 | u16_be mii_id; | ||
| 48 | u64_be system_id; | ||
| 49 | u32_be specialness_and_creation_date; | ||
| 50 | std::array<u8, 6> creator_mac; | ||
| 51 | u16_be padding; | ||
| 52 | union { | ||
| 53 | u16 raw; | ||
| 54 | |||
| 55 | BitField<0, 1, u16> gender; | ||
| 56 | BitField<1, 4, u16> birth_month; | ||
| 57 | BitField<5, 5, u16> birth_day; | ||
| 58 | BitField<10, 4, u16> favorite_color; | ||
| 59 | BitField<14, 1, u16> favorite; | ||
| 60 | } mii_information; | ||
| 61 | Nickname mii_name; | ||
| 62 | u8 height; | ||
| 63 | u8 build; | ||
| 64 | union { | ||
| 65 | u8 raw; | ||
| 66 | |||
| 67 | BitField<0, 1, u8> disable_sharing; | ||
| 68 | BitField<1, 4, u8> faceline_type; | ||
| 69 | BitField<5, 3, u8> faceline_color; | ||
| 70 | } appearance_bits1; | ||
| 71 | union { | ||
| 72 | u8 raw; | ||
| 73 | |||
| 74 | BitField<0, 4, u8> faceline_wrinkle; | ||
| 75 | BitField<4, 4, u8> faceline_make; | ||
| 76 | } appearance_bits2; | ||
| 77 | u8 hair_type; | ||
| 78 | union { | ||
| 79 | u8 raw; | ||
| 80 | |||
| 81 | BitField<0, 3, u8> hair_color; | ||
| 82 | BitField<3, 1, u8> hair_flip; | ||
| 83 | } appearance_bits3; | ||
| 84 | union { | ||
| 85 | u32 raw; | ||
| 86 | |||
| 87 | BitField<0, 6, u32> eye_type; | ||
| 88 | BitField<6, 3, u32> eye_color; | ||
| 89 | BitField<9, 4, u32> eye_scale; | ||
| 90 | BitField<13, 3, u32> eye_aspect; | ||
| 91 | BitField<16, 5, u32> eye_rotate; | ||
| 92 | BitField<21, 4, u32> eye_x; | ||
| 93 | BitField<25, 5, u32> eye_y; | ||
| 94 | } appearance_bits4; | ||
| 95 | union { | ||
| 96 | u32 raw; | ||
| 97 | |||
| 98 | BitField<0, 5, u32> eyebrow_type; | ||
| 99 | BitField<5, 3, u32> eyebrow_color; | ||
| 100 | BitField<8, 4, u32> eyebrow_scale; | ||
| 101 | BitField<12, 3, u32> eyebrow_aspect; | ||
| 102 | BitField<16, 4, u32> eyebrow_rotate; | ||
| 103 | BitField<21, 4, u32> eyebrow_x; | ||
| 104 | BitField<25, 5, u32> eyebrow_y; | ||
| 105 | } appearance_bits5; | ||
| 106 | union { | ||
| 107 | u16 raw; | ||
| 108 | |||
| 109 | BitField<0, 5, u16> nose_type; | ||
| 110 | BitField<5, 4, u16> nose_scale; | ||
| 111 | BitField<9, 5, u16> nose_y; | ||
| 112 | } appearance_bits6; | ||
| 113 | union { | ||
| 114 | u16 raw; | ||
| 115 | |||
| 116 | BitField<0, 6, u16> mouth_type; | ||
| 117 | BitField<6, 3, u16> mouth_color; | ||
| 118 | BitField<9, 4, u16> mouth_scale; | ||
| 119 | BitField<13, 3, u16> mouth_aspect; | ||
| 120 | } appearance_bits7; | ||
| 121 | union { | ||
| 122 | u8 raw; | ||
| 123 | |||
| 124 | BitField<0, 5, u8> mouth_y; | ||
| 125 | BitField<5, 3, u8> mustache_type; | ||
| 126 | } appearance_bits8; | ||
| 127 | u8 allow_copying; | ||
| 128 | union { | ||
| 129 | u16 raw; | ||
| 130 | |||
| 131 | BitField<0, 3, u16> beard_type; | ||
| 132 | BitField<3, 3, u16> beard_color; | ||
| 133 | BitField<6, 4, u16> mustache_scale; | ||
| 134 | BitField<10, 5, u16> mustache_y; | ||
| 135 | } appearance_bits9; | ||
| 136 | union { | ||
| 137 | u16 raw; | ||
| 138 | |||
| 139 | BitField<0, 4, u16> glass_type; | ||
| 140 | BitField<4, 3, u16> glass_color; | ||
| 141 | BitField<7, 4, u16> glass_scale; | ||
| 142 | BitField<11, 5, u16> glass_y; | ||
| 143 | } appearance_bits10; | ||
| 144 | union { | ||
| 145 | u16 raw; | ||
| 146 | |||
| 147 | BitField<0, 1, u16> mole_type; | ||
| 148 | BitField<1, 4, u16> mole_scale; | ||
| 149 | BitField<5, 5, u16> mole_x; | ||
| 150 | BitField<10, 5, u16> mole_y; | ||
| 151 | } appearance_bits11; | ||
| 152 | |||
| 153 | Nickname author_name; | ||
| 154 | INSERT_PADDING_BYTES(0x2); | ||
| 155 | u16_be crc; | ||
| 156 | }; | ||
| 157 | static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); | ||
| 158 | #pragma pack(pop) | ||
| 159 | |||
| 160 | }; // namespace Service::Mii | ||
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 49446bc42..674d2e4b2 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp | |||
| @@ -28,7 +28,6 @@ | |||
| 28 | #include "core/hle/kernel/k_event.h" | 28 | #include "core/hle/kernel/k_event.h" |
| 29 | #include "core/hle/service/ipc_helpers.h" | 29 | #include "core/hle/service/ipc_helpers.h" |
| 30 | #include "core/hle/service/mii/mii_manager.h" | 30 | #include "core/hle/service/mii/mii_manager.h" |
| 31 | #include "core/hle/service/mii/types.h" | ||
| 32 | #include "core/hle/service/nfc/common/amiibo_crypto.h" | 31 | #include "core/hle/service/nfc/common/amiibo_crypto.h" |
| 33 | #include "core/hle/service/nfc/common/device.h" | 32 | #include "core/hle/service/nfc/common/device.h" |
| 34 | #include "core/hle/service/nfc/mifare_result.h" | 33 | #include "core/hle/service/nfc/mifare_result.h" |
| @@ -681,12 +680,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const { | |||
| 681 | return ResultRegistrationIsNotInitialized; | 680 | return ResultRegistrationIsNotInitialized; |
| 682 | } | 681 | } |
| 683 | 682 | ||
| 684 | Service::Mii::MiiManager manager; | 683 | Mii::CharInfo char_info{}; |
| 684 | Mii::StoreData store_data{}; | ||
| 685 | tag_data.owner_mii.BuildToStoreData(store_data); | ||
| 686 | char_info.SetFromStoreData(store_data); | ||
| 687 | |||
| 685 | const auto& settings = tag_data.settings; | 688 | const auto& settings = tag_data.settings; |
| 686 | 689 | ||
| 687 | // TODO: Validate this data | 690 | // TODO: Validate this data |
| 688 | register_info = { | 691 | register_info = { |
| 689 | .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), | 692 | .mii_char_info = char_info, |
| 690 | .creation_date = settings.init_date.GetWriteDate(), | 693 | .creation_date = settings.init_date.GetWriteDate(), |
| 691 | .amiibo_name = GetAmiiboName(settings), | 694 | .amiibo_name = GetAmiiboName(settings), |
| 692 | .font_region = settings.settings.font_region, | 695 | .font_region = settings.settings.font_region, |
| @@ -825,8 +828,11 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe | |||
| 825 | return ResultWrongDeviceState; | 828 | return ResultWrongDeviceState; |
| 826 | } | 829 | } |
| 827 | 830 | ||
| 828 | Service::Mii::MiiManager manager; | 831 | Service::Mii::StoreData store_data{}; |
| 829 | const auto mii = manager.BuildDefault(0); | 832 | Service::Mii::NfpStoreDataExtension extension{}; |
| 833 | store_data.BuildBase(Mii::Gender::Male); | ||
| 834 | extension.SetFromStoreData(store_data); | ||
| 835 | |||
| 830 | auto& settings = tag_data.settings; | 836 | auto& settings = tag_data.settings; |
| 831 | 837 | ||
| 832 | if (tag_data.settings.settings.amiibo_initialized == 0) { | 838 | if (tag_data.settings.settings.amiibo_initialized == 0) { |
| @@ -835,8 +841,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe | |||
| 835 | } | 841 | } |
| 836 | 842 | ||
| 837 | SetAmiiboName(settings, register_info.amiibo_name); | 843 | SetAmiiboName(settings, register_info.amiibo_name); |
| 838 | tag_data.owner_mii = manager.BuildFromStoreData(mii); | 844 | tag_data.owner_mii.BuildFromStoreData(store_data); |
| 839 | tag_data.mii_extension = manager.SetFromStoreData(mii); | 845 | tag_data.mii_extension = extension; |
| 840 | tag_data.unknown = 0; | 846 | tag_data.unknown = 0; |
| 841 | tag_data.unknown2 = {}; | 847 | tag_data.unknown2 = {}; |
| 842 | settings.country_code_id = 0; | 848 | settings.country_code_id = 0; |
| @@ -868,17 +874,19 @@ Result NfcDevice::RestoreAmiibo() { | |||
| 868 | } | 874 | } |
| 869 | 875 | ||
| 870 | Result NfcDevice::Format() { | 876 | Result NfcDevice::Format() { |
| 871 | auto result1 = DeleteApplicationArea(); | 877 | Result result = ResultSuccess; |
| 872 | auto result2 = DeleteRegisterInfo(); | ||
| 873 | 878 | ||
| 874 | if (result1.IsError()) { | 879 | if (device_state == DeviceState::TagFound) { |
| 875 | return result1; | 880 | result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All); |
| 876 | } | 881 | } |
| 877 | 882 | ||
| 878 | if (result2.IsError()) { | 883 | if (result.IsError()) { |
| 879 | return result2; | 884 | return result; |
| 880 | } | 885 | } |
| 881 | 886 | ||
| 887 | DeleteApplicationArea(); | ||
| 888 | DeleteRegisterInfo(); | ||
| 889 | |||
| 882 | return Flush(); | 890 | return Flush(); |
| 883 | } | 891 | } |
| 884 | 892 | ||
| @@ -1453,7 +1461,7 @@ void NfcDevice::UpdateRegisterInfoCrc() { | |||
| 1453 | 1461 | ||
| 1454 | void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, | 1462 | void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, |
| 1455 | const NFP::EncryptedNTAG215File& encrypted_file) const { | 1463 | const NFP::EncryptedNTAG215File& encrypted_file) const { |
| 1456 | Service::Mii::MiiManager manager; | 1464 | Service::Mii::StoreData store_data{}; |
| 1457 | auto& settings = stubbed_tag_data.settings; | 1465 | auto& settings = stubbed_tag_data.settings; |
| 1458 | 1466 | ||
| 1459 | stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); | 1467 | stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); |
| @@ -1467,7 +1475,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, | |||
| 1467 | SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); | 1475 | SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); |
| 1468 | settings.settings.font_region.Assign(0); | 1476 | settings.settings.font_region.Assign(0); |
| 1469 | settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); | 1477 | settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); |
| 1470 | stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); | 1478 | store_data.BuildBase(Mii::Gender::Male); |
| 1479 | stubbed_tag_data.owner_mii.BuildFromStoreData(store_data); | ||
| 1471 | 1480 | ||
| 1472 | // Admin info | 1481 | // Admin info |
| 1473 | settings.settings.amiibo_initialized.Assign(1); | 1482 | settings.settings.amiibo_initialized.Assign(1); |
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index aed12a7f8..f96d21220 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h | |||
| @@ -6,7 +6,9 @@ | |||
| 6 | #include <array> | 6 | #include <array> |
| 7 | 7 | ||
| 8 | #include "common/swap.h" | 8 | #include "common/swap.h" |
| 9 | #include "core/hle/service/mii/types.h" | 9 | #include "core/hle/service/mii/types/char_info.h" |
| 10 | #include "core/hle/service/mii/types/store_data.h" | ||
| 11 | #include "core/hle/service/mii/types/ver3_store_data.h" | ||
| 10 | #include "core/hle/service/nfc/nfc_types.h" | 12 | #include "core/hle/service/nfc/nfc_types.h" |
| 11 | 13 | ||
| 12 | namespace Service::NFP { | 14 | namespace Service::NFP { |
| @@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); | |||
| 322 | 324 | ||
| 323 | // This is nn::nfp::RegisterInfoPrivate | 325 | // This is nn::nfp::RegisterInfoPrivate |
| 324 | struct RegisterInfoPrivate { | 326 | struct RegisterInfoPrivate { |
| 325 | Service::Mii::MiiStoreData mii_store_data; | 327 | Service::Mii::StoreData mii_store_data; |
| 326 | WriteDate creation_date; | 328 | WriteDate creation_date; |
| 327 | AmiiboName amiibo_name; | 329 | AmiiboName amiibo_name; |
| 328 | u8 font_region; | 330 | u8 font_region; |
diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp new file mode 100644 index 000000000..c26019ec0 --- /dev/null +++ b/src/core/hle/service/ngc/ngc.cpp | |||
| @@ -0,0 +1,150 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/string_util.h" | ||
| 5 | #include "core/core.h" | ||
| 6 | #include "core/hle/service/ipc_helpers.h" | ||
| 7 | #include "core/hle/service/ngc/ngc.h" | ||
| 8 | #include "core/hle/service/server_manager.h" | ||
| 9 | #include "core/hle/service/service.h" | ||
| 10 | |||
| 11 | namespace Service::NGC { | ||
| 12 | |||
| 13 | class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> { | ||
| 14 | public: | ||
| 15 | explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { | ||
| 16 | // clang-format off | ||
| 17 | static const FunctionInfo functions[] = { | ||
| 18 | {0, &NgctServiceImpl::Match, "Match"}, | ||
| 19 | {1, &NgctServiceImpl::Filter, "Filter"}, | ||
| 20 | }; | ||
| 21 | // clang-format on | ||
| 22 | |||
| 23 | RegisterHandlers(functions); | ||
| 24 | } | ||
| 25 | |||
| 26 | private: | ||
| 27 | void Match(HLERequestContext& ctx) { | ||
| 28 | const auto buffer = ctx.ReadBuffer(); | ||
| 29 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 30 | reinterpret_cast<const char*>(buffer.data()), buffer.size()); | ||
| 31 | |||
| 32 | LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); | ||
| 33 | |||
| 34 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 35 | rb.Push(ResultSuccess); | ||
| 36 | // Return false since we don't censor anything | ||
| 37 | rb.Push(false); | ||
| 38 | } | ||
| 39 | |||
| 40 | void Filter(HLERequestContext& ctx) { | ||
| 41 | const auto buffer = ctx.ReadBuffer(); | ||
| 42 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 43 | reinterpret_cast<const char*>(buffer.data()), buffer.size()); | ||
| 44 | |||
| 45 | LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); | ||
| 46 | |||
| 47 | // Return the same string since we don't censor anything | ||
| 48 | ctx.WriteBuffer(buffer); | ||
| 49 | |||
| 50 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 51 | rb.Push(ResultSuccess); | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | |||
| 55 | class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> { | ||
| 56 | public: | ||
| 57 | explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") { | ||
| 58 | // clang-format off | ||
| 59 | static const FunctionInfo functions[] = { | ||
| 60 | {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"}, | ||
| 61 | {1, &NgcServiceImpl::Check, "Check"}, | ||
| 62 | {2, &NgcServiceImpl::Mask, "Mask"}, | ||
| 63 | {3, &NgcServiceImpl::Reload, "Reload"}, | ||
| 64 | }; | ||
| 65 | // clang-format on | ||
| 66 | |||
| 67 | RegisterHandlers(functions); | ||
| 68 | } | ||
| 69 | |||
| 70 | private: | ||
| 71 | static constexpr u32 NgcContentVersion = 1; | ||
| 72 | |||
| 73 | // This is nn::ngc::detail::ProfanityFilterOption | ||
| 74 | struct ProfanityFilterOption { | ||
| 75 | INSERT_PADDING_BYTES_NOINIT(0x20); | ||
| 76 | }; | ||
| 77 | static_assert(sizeof(ProfanityFilterOption) == 0x20, | ||
| 78 | "ProfanityFilterOption has incorrect size"); | ||
| 79 | |||
| 80 | void GetContentVersion(HLERequestContext& ctx) { | ||
| 81 | LOG_INFO(Service_NGC, "(STUBBED) called"); | ||
| 82 | |||
| 83 | // This calls nn::ngc::ProfanityFilter::GetContentVersion | ||
| 84 | const u32 version = NgcContentVersion; | ||
| 85 | |||
| 86 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 87 | rb.Push(ResultSuccess); | ||
| 88 | rb.Push(version); | ||
| 89 | } | ||
| 90 | |||
| 91 | void Check(HLERequestContext& ctx) { | ||
| 92 | LOG_INFO(Service_NGC, "(STUBBED) called"); | ||
| 93 | |||
| 94 | struct InputParameters { | ||
| 95 | u32 flags; | ||
| 96 | ProfanityFilterOption option; | ||
| 97 | }; | ||
| 98 | |||
| 99 | IPC::RequestParser rp{ctx}; | ||
| 100 | [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>(); | ||
| 101 | [[maybe_unused]] const auto input = ctx.ReadBuffer(0); | ||
| 102 | |||
| 103 | // This calls nn::ngc::ProfanityFilter::CheckProfanityWords | ||
| 104 | const u32 out_flags = 0; | ||
| 105 | |||
| 106 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 107 | rb.Push(ResultSuccess); | ||
| 108 | rb.Push(out_flags); | ||
| 109 | } | ||
| 110 | |||
| 111 | void Mask(HLERequestContext& ctx) { | ||
| 112 | LOG_INFO(Service_NGC, "(STUBBED) called"); | ||
| 113 | |||
| 114 | struct InputParameters { | ||
| 115 | u32 flags; | ||
| 116 | ProfanityFilterOption option; | ||
| 117 | }; | ||
| 118 | |||
| 119 | IPC::RequestParser rp{ctx}; | ||
| 120 | [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>(); | ||
| 121 | const auto input = ctx.ReadBuffer(0); | ||
| 122 | |||
| 123 | // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText | ||
| 124 | const u32 out_flags = 0; | ||
| 125 | ctx.WriteBuffer(input); | ||
| 126 | |||
| 127 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 128 | rb.Push(ResultSuccess); | ||
| 129 | rb.Push(out_flags); | ||
| 130 | } | ||
| 131 | |||
| 132 | void Reload(HLERequestContext& ctx) { | ||
| 133 | LOG_INFO(Service_NGC, "(STUBBED) called"); | ||
| 134 | |||
| 135 | // This reloads the database. | ||
| 136 | |||
| 137 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 138 | rb.Push(ResultSuccess); | ||
| 139 | } | ||
| 140 | }; | ||
| 141 | |||
| 142 | void LoopProcess(Core::System& system) { | ||
| 143 | auto server_manager = std::make_unique<ServerManager>(system); | ||
| 144 | |||
| 145 | server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system)); | ||
| 146 | server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system)); | ||
| 147 | ServerManager::RunServer(std::move(server_manager)); | ||
| 148 | } | ||
| 149 | |||
| 150 | } // namespace Service::NGC | ||
diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngc/ngc.h index 27c34dad4..823b1aa81 100644 --- a/src/core/hle/service/ngct/ngct.h +++ b/src/core/hle/service/ngc/ngc.h | |||
| @@ -7,8 +7,8 @@ namespace Core { | |||
| 7 | class System; | 7 | class System; |
| 8 | } | 8 | } |
| 9 | 9 | ||
| 10 | namespace Service::NGCT { | 10 | namespace Service::NGC { |
| 11 | 11 | ||
| 12 | void LoopProcess(Core::System& system); | 12 | void LoopProcess(Core::System& system); |
| 13 | 13 | ||
| 14 | } // namespace Service::NGCT | 14 | } // namespace Service::NGC |
diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp deleted file mode 100644 index 493c80ed2..000000000 --- a/src/core/hle/service/ngct/ngct.cpp +++ /dev/null | |||
| @@ -1,62 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/string_util.h" | ||
| 5 | #include "core/core.h" | ||
| 6 | #include "core/hle/service/ipc_helpers.h" | ||
| 7 | #include "core/hle/service/ngct/ngct.h" | ||
| 8 | #include "core/hle/service/server_manager.h" | ||
| 9 | #include "core/hle/service/service.h" | ||
| 10 | |||
| 11 | namespace Service::NGCT { | ||
| 12 | |||
| 13 | class IService final : public ServiceFramework<IService> { | ||
| 14 | public: | ||
| 15 | explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { | ||
| 16 | // clang-format off | ||
| 17 | static const FunctionInfo functions[] = { | ||
| 18 | {0, &IService::Match, "Match"}, | ||
| 19 | {1, &IService::Filter, "Filter"}, | ||
| 20 | }; | ||
| 21 | // clang-format on | ||
| 22 | |||
| 23 | RegisterHandlers(functions); | ||
| 24 | } | ||
| 25 | |||
| 26 | private: | ||
| 27 | void Match(HLERequestContext& ctx) { | ||
| 28 | const auto buffer = ctx.ReadBuffer(); | ||
| 29 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 30 | reinterpret_cast<const char*>(buffer.data()), buffer.size()); | ||
| 31 | |||
| 32 | LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); | ||
| 33 | |||
| 34 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 35 | rb.Push(ResultSuccess); | ||
| 36 | // Return false since we don't censor anything | ||
| 37 | rb.Push(false); | ||
| 38 | } | ||
| 39 | |||
| 40 | void Filter(HLERequestContext& ctx) { | ||
| 41 | const auto buffer = ctx.ReadBuffer(); | ||
| 42 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 43 | reinterpret_cast<const char*>(buffer.data()), buffer.size()); | ||
| 44 | |||
| 45 | LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); | ||
| 46 | |||
| 47 | // Return the same string since we don't censor anything | ||
| 48 | ctx.WriteBuffer(buffer); | ||
| 49 | |||
| 50 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 51 | rb.Push(ResultSuccess); | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | |||
| 55 | void LoopProcess(Core::System& system) { | ||
| 56 | auto server_manager = std::make_unique<ServerManager>(system); | ||
| 57 | |||
| 58 | server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system)); | ||
| 59 | ServerManager::RunServer(std::move(server_manager)); | ||
| 60 | } | ||
| 61 | |||
| 62 | } // namespace Service::NGCT | ||
diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp index a51ca5444..0ca05257e 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.cpp +++ b/src/core/hle/service/nvdrv/core/nvmap.cpp | |||
| @@ -160,8 +160,8 @@ u32 NvMap::PinHandle(NvMap::Handle::Id handle) { | |||
| 160 | u32 address{}; | 160 | u32 address{}; |
| 161 | auto& smmu_allocator = host1x.Allocator(); | 161 | auto& smmu_allocator = host1x.Allocator(); |
| 162 | auto& smmu_memory_manager = host1x.MemoryManager(); | 162 | auto& smmu_memory_manager = host1x.MemoryManager(); |
| 163 | while (!(address = | 163 | while ((address = smmu_allocator.Allocate( |
| 164 | smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) { | 164 | static_cast<u32>(handle_description->aligned_size))) == 0) { |
| 165 | // Free handles until the allocation succeeds | 165 | // Free handles until the allocation succeeds |
| 166 | std::scoped_lock queueLock(unmap_queue_lock); | 166 | std::scoped_lock queueLock(unmap_queue_lock); |
| 167 | if (auto freeHandleDesc{unmap_queue.front()}) { | 167 | if (auto freeHandleDesc{unmap_queue.front()}) { |
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 07e570a9f..7d7bb8687 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp | |||
| @@ -204,9 +204,11 @@ void nvhost_as_gpu::FreeMappingLocked(u64 offset) { | |||
| 204 | if (!mapping->fixed) { | 204 | if (!mapping->fixed) { |
| 205 | auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; | 205 | auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; |
| 206 | u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; | 206 | u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; |
| 207 | u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE}; | ||
| 208 | u64 aligned_size{Common::AlignUp(mapping->size, page_size)}; | ||
| 207 | 209 | ||
| 208 | allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits), | 210 | allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits), |
| 209 | static_cast<u32>(mapping->size >> page_size_bits)); | 211 | static_cast<u32>(aligned_size >> page_size_bits)); |
| 210 | } | 212 | } |
| 211 | 213 | ||
| 212 | // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state | 214 | // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state |
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index b16f9933f..dc6917d5d 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp | |||
| @@ -449,6 +449,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, | |||
| 449 | case NativeWindowScalingMode::ScaleToWindow: | 449 | case NativeWindowScalingMode::ScaleToWindow: |
| 450 | case NativeWindowScalingMode::ScaleCrop: | 450 | case NativeWindowScalingMode::ScaleCrop: |
| 451 | case NativeWindowScalingMode::NoScaleCrop: | 451 | case NativeWindowScalingMode::NoScaleCrop: |
| 452 | case NativeWindowScalingMode::PreserveAspectRatio: | ||
| 452 | break; | 453 | break; |
| 453 | default: | 454 | default: |
| 454 | LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode); | 455 | LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode); |
diff --git a/src/core/hle/service/nvnflinger/window.h b/src/core/hle/service/nvnflinger/window.h index 61cca5b01..36d6cde3d 100644 --- a/src/core/hle/service/nvnflinger/window.h +++ b/src/core/hle/service/nvnflinger/window.h | |||
| @@ -41,6 +41,7 @@ enum class NativeWindowScalingMode : s32 { | |||
| 41 | ScaleToWindow = 1, | 41 | ScaleToWindow = 1, |
| 42 | ScaleCrop = 2, | 42 | ScaleCrop = 2, |
| 43 | NoScaleCrop = 3, | 43 | NoScaleCrop = 3, |
| 44 | PreserveAspectRatio = 4, | ||
| 44 | }; | 45 | }; |
| 45 | 46 | ||
| 46 | /// Transform parameter for QueueBuffer | 47 | /// Transform parameter for QueueBuffer |
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 69cdb5918..0ad607391 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp | |||
| @@ -43,7 +43,7 @@ | |||
| 43 | #include "core/hle/service/ncm/ncm.h" | 43 | #include "core/hle/service/ncm/ncm.h" |
| 44 | #include "core/hle/service/nfc/nfc.h" | 44 | #include "core/hle/service/nfc/nfc.h" |
| 45 | #include "core/hle/service/nfp/nfp.h" | 45 | #include "core/hle/service/nfp/nfp.h" |
| 46 | #include "core/hle/service/ngct/ngct.h" | 46 | #include "core/hle/service/ngc/ngc.h" |
| 47 | #include "core/hle/service/nifm/nifm.h" | 47 | #include "core/hle/service/nifm/nifm.h" |
| 48 | #include "core/hle/service/nim/nim.h" | 48 | #include "core/hle/service/nim/nim.h" |
| 49 | #include "core/hle/service/npns/npns.h" | 49 | #include "core/hle/service/npns/npns.h" |
| @@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system | |||
| 257 | kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); | 257 | kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); |
| 258 | kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); | 258 | kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); |
| 259 | kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); | 259 | kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); |
| 260 | kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); }); | 260 | kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); }); |
| 261 | kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); | 261 | kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); |
| 262 | kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); | 262 | kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); |
| 263 | kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); | 263 | kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); |
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 45b2c43b7..d539ed0f4 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h | |||
| @@ -79,8 +79,8 @@ protected: | |||
| 79 | using HandlerFnP = void (Self::*)(HLERequestContext&); | 79 | using HandlerFnP = void (Self::*)(HLERequestContext&); |
| 80 | 80 | ||
| 81 | /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. | 81 | /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. |
| 82 | [[nodiscard]] std::scoped_lock<std::mutex> LockService() { | 82 | [[nodiscard]] virtual std::unique_lock<std::mutex> LockService() { |
| 83 | return std::scoped_lock{lock_service}; | 83 | return std::unique_lock{lock_service}; |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | /// System context that the service operates under. | 86 | /// System context that the service operates under. |
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 11f8efbac..85849d5f3 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp | |||
| @@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) { | |||
| 170 | } | 170 | } |
| 171 | 171 | ||
| 172 | void BSD::Select(HLERequestContext& ctx) { | 172 | void BSD::Select(HLERequestContext& ctx) { |
| 173 | LOG_WARNING(Service, "(STUBBED) called"); | 173 | LOG_DEBUG(Service, "(STUBBED) called"); |
| 174 | 174 | ||
| 175 | IPC::ResponseBuilder rb{ctx, 4}; | 175 | IPC::ResponseBuilder rb{ctx, 4}; |
| 176 | 176 | ||
| @@ -1029,6 +1029,11 @@ BSD::~BSD() { | |||
| 1029 | } | 1029 | } |
| 1030 | } | 1030 | } |
| 1031 | 1031 | ||
| 1032 | std::unique_lock<std::mutex> BSD::LockService() { | ||
| 1033 | // Do not lock socket IClient instances. | ||
| 1034 | return {}; | ||
| 1035 | } | ||
| 1036 | |||
| 1032 | BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { | 1037 | BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { |
| 1033 | // clang-format off | 1038 | // clang-format off |
| 1034 | static const FunctionInfo functions[] = { | 1039 | static const FunctionInfo functions[] = { |
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 430edb97c..161f22b9b 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h | |||
| @@ -186,6 +186,9 @@ private: | |||
| 186 | 186 | ||
| 187 | // Callback identifier for the OnProxyPacketReceived event. | 187 | // Callback identifier for the OnProxyPacketReceived event. |
| 188 | Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received; | 188 | Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received; |
| 189 | |||
| 190 | protected: | ||
| 191 | virtual std::unique_lock<std::mutex> LockService() override; | ||
| 189 | }; | 192 | }; |
| 190 | 193 | ||
| 191 | class BSDCFG final : public ServiceFramework<BSDCFG> { | 194 | class BSDCFG final : public ServiceFramework<BSDCFG> { |
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index bac21752a..491b76d48 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp | |||
| @@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 { | |||
| 19 | Dp, | 19 | Dp, |
| 20 | }; | 20 | }; |
| 21 | 21 | ||
| 22 | // This is nn::nsd::EnvironmentIdentifier | ||
| 23 | struct EnvironmentIdentifier { | ||
| 24 | std::array<u8, 8> identifier; | ||
| 25 | }; | ||
| 26 | static_assert(sizeof(EnvironmentIdentifier) == 0x8); | ||
| 27 | |||
| 22 | NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { | 28 | NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { |
| 23 | // clang-format off | 29 | // clang-format off |
| 24 | static const FunctionInfo functions[] = { | 30 | static const FunctionInfo functions[] = { |
| @@ -101,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) { | |||
| 101 | } | 107 | } |
| 102 | 108 | ||
| 103 | void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { | 109 | void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { |
| 104 | const std::string environment_identifier = "lp1"; | 110 | constexpr EnvironmentIdentifier lp1 = { |
| 105 | ctx.WriteBuffer(environment_identifier); | 111 | .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}}; |
| 112 | ctx.WriteBuffer(lp1); | ||
| 106 | 113 | ||
| 107 | IPC::ResponseBuilder rb{ctx, 2}; | 114 | IPC::ResponseBuilder rb{ctx, 2}; |
| 108 | rb.Push(ResultSuccess); | 115 | rb.Push(ResultSuccess); |
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 22e4a6f49..c657c4efd 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp | |||
| @@ -150,6 +150,12 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte | |||
| 150 | const std::string host = Common::StringFromBuffer(host_buffer); | 150 | const std::string host = Common::StringFromBuffer(host_buffer); |
| 151 | // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. | 151 | // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. |
| 152 | 152 | ||
| 153 | // Prevent resolution of Nintendo servers | ||
| 154 | if (host.find("srv.nintendo.net") != std::string::npos) { | ||
| 155 | LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); | ||
| 156 | return {0, GetAddrInfoError::AGAIN}; | ||
| 157 | } | ||
| 158 | |||
| 153 | auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); | 159 | auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); |
| 154 | if (!res.has_value()) { | 160 | if (!res.has_value()) { |
| 155 | return {0, Translate(res.error())}; | 161 | return {0, Translate(res.error())}; |
| @@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext | |||
| 261 | const auto host_buffer = ctx.ReadBuffer(0); | 267 | const auto host_buffer = ctx.ReadBuffer(0); |
| 262 | const std::string host = Common::StringFromBuffer(host_buffer); | 268 | const std::string host = Common::StringFromBuffer(host_buffer); |
| 263 | 269 | ||
| 270 | // Prevent resolution of Nintendo servers | ||
| 271 | if (host.find("srv.nintendo.net") != std::string::npos) { | ||
| 272 | LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); | ||
| 273 | return {0, GetAddrInfoError::AGAIN}; | ||
| 274 | } | ||
| 275 | |||
| 264 | std::optional<std::string> service = std::nullopt; | 276 | std::optional<std::string> service = std::nullopt; |
| 265 | if (ctx.CanReadBuffer(1)) { | 277 | if (ctx.CanReadBuffer(1)) { |
| 266 | const std::span<const u8> service_buffer = ctx.ReadBuffer(1); | 278 | const std::span<const u8> service_buffer = ctx.ReadBuffer(1); |
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 77426c46e..f86af01a4 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h | |||
| @@ -18,7 +18,9 @@ enum class Errno : u32 { | |||
| 18 | AGAIN = 11, | 18 | AGAIN = 11, |
| 19 | INVAL = 22, | 19 | INVAL = 22, |
| 20 | MFILE = 24, | 20 | MFILE = 24, |
| 21 | PIPE = 32, | ||
| 21 | MSGSIZE = 90, | 22 | MSGSIZE = 90, |
| 23 | CONNABORTED = 103, | ||
| 22 | CONNRESET = 104, | 24 | CONNRESET = 104, |
| 23 | NOTCONN = 107, | 25 | NOTCONN = 107, |
| 24 | TIMEDOUT = 110, | 26 | TIMEDOUT = 110, |
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index c1187209f..aed05250c 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp | |||
| @@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) { | |||
| 23 | return Errno::INVAL; | 23 | return Errno::INVAL; |
| 24 | case Network::Errno::MFILE: | 24 | case Network::Errno::MFILE: |
| 25 | return Errno::MFILE; | 25 | return Errno::MFILE; |
| 26 | case Network::Errno::PIPE: | ||
| 27 | return Errno::PIPE; | ||
| 26 | case Network::Errno::NOTCONN: | 28 | case Network::Errno::NOTCONN: |
| 27 | return Errno::NOTCONN; | 29 | return Errno::NOTCONN; |
| 28 | case Network::Errno::TIMEDOUT: | 30 | case Network::Errno::TIMEDOUT: |
| 29 | return Errno::TIMEDOUT; | 31 | return Errno::TIMEDOUT; |
| 32 | case Network::Errno::CONNABORTED: | ||
| 33 | return Errno::CONNABORTED; | ||
| 30 | case Network::Errno::CONNRESET: | 34 | case Network::Errno::CONNRESET: |
| 31 | return Errno::CONNRESET; | 35 | return Errno::CONNRESET; |
| 32 | case Network::Errno::INPROGRESS: | 36 | case Network::Errno::INPROGRESS: |
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 2cba9e5c9..6c8427b0d 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp | |||
| @@ -139,7 +139,6 @@ private: | |||
| 139 | bool do_not_close_socket = false; | 139 | bool do_not_close_socket = false; |
| 140 | bool get_server_cert_chain = false; | 140 | bool get_server_cert_chain = false; |
| 141 | std::shared_ptr<Network::SocketBase> socket; | 141 | std::shared_ptr<Network::SocketBase> socket; |
| 142 | bool did_set_host_name = false; | ||
| 143 | bool did_handshake = false; | 142 | bool did_handshake = false; |
| 144 | 143 | ||
| 145 | Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { | 144 | Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { |
| @@ -174,11 +173,7 @@ private: | |||
| 174 | Result SetHostNameImpl(const std::string& hostname) { | 173 | Result SetHostNameImpl(const std::string& hostname) { |
| 175 | LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); | 174 | LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); |
| 176 | ASSERT(!did_handshake); | 175 | ASSERT(!did_handshake); |
| 177 | Result res = backend->SetHostName(hostname); | 176 | return backend->SetHostName(hostname); |
| 178 | if (res == ResultSuccess) { | ||
| 179 | did_set_host_name = true; | ||
| 180 | } | ||
| 181 | return res; | ||
| 182 | } | 177 | } |
| 183 | 178 | ||
| 184 | Result SetVerifyOptionImpl(u32 option) { | 179 | Result SetVerifyOptionImpl(u32 option) { |
| @@ -208,9 +203,6 @@ private: | |||
| 208 | 203 | ||
| 209 | Result DoHandshakeImpl() { | 204 | Result DoHandshakeImpl() { |
| 210 | ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); | 205 | ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); |
| 211 | ASSERT_OR_EXECUTE_MSG( | ||
| 212 | did_set_host_name, { return ResultInternalError; }, | ||
| 213 | "Expected SetHostName before DoHandshake"); | ||
| 214 | Result res = backend->DoHandshake(); | 206 | Result res = backend->DoHandshake(); |
| 215 | did_handshake = res.IsSuccess(); | 207 | did_handshake = res.IsSuccess(); |
| 216 | return res; | 208 | return res; |
diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index b2dd37cd4..5714e6f3c 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp | |||
| @@ -167,9 +167,8 @@ public: | |||
| 167 | } | 167 | } |
| 168 | 168 | ||
| 169 | ~SSLConnectionBackendOpenSSL() { | 169 | ~SSLConnectionBackendOpenSSL() { |
| 170 | // these are null-tolerant: | 170 | // this is null-tolerant: |
| 171 | SSL_free(ssl); | 171 | SSL_free(ssl); |
| 172 | BIO_free(bio); | ||
| 173 | } | 172 | } |
| 174 | 173 | ||
| 175 | static void KeyLogCallback(const SSL* ssl, const char* line) { | 174 | static void KeyLogCallback(const SSL* ssl, const char* line) { |
diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index bda12b761..212057cfc 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp | |||
| @@ -31,9 +31,9 @@ CredHandle cred_handle; | |||
| 31 | static void OneTimeInit() { | 31 | static void OneTimeInit() { |
| 32 | schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; | 32 | schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; |
| 33 | schannel_cred.dwFlags = | 33 | schannel_cred.dwFlags = |
| 34 | SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols | 34 | SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols |
| 35 | SCH_CRED_AUTO_CRED_VALIDATION | // validate certs | 35 | SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names |
| 36 | SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate | 36 | SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate |
| 37 | // ^ I'm assuming that nobody would want to connect Yuzu to a | 37 | // ^ I'm assuming that nobody would want to connect Yuzu to a |
| 38 | // service that requires some OS-provided corporate client | 38 | // service that requires some OS-provided corporate client |
| 39 | // certificate, and presenting one to some arbitrary server | 39 | // certificate, and presenting one to some arbitrary server |
| @@ -227,16 +227,15 @@ public: | |||
| 227 | ciphertext_read_buf.size()); | 227 | ciphertext_read_buf.size()); |
| 228 | } | 228 | } |
| 229 | 229 | ||
| 230 | const SECURITY_STATUS ret = | 230 | char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr; |
| 231 | InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt : nullptr, | 231 | const SECURITY_STATUS ret = InitializeSecurityContextA( |
| 232 | // Caller ensured we have set a hostname: | 232 | &cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req, |
| 233 | const_cast<char*>(hostname.value().c_str()), req, | 233 | 0, // Reserved1 |
| 234 | 0, // Reserved1 | 234 | 0, // TargetDataRep not used with Schannel |
| 235 | 0, // TargetDataRep not used with Schannel | 235 | initial_call_done ? &input_desc : nullptr, |
| 236 | initial_call_done ? &input_desc : nullptr, | 236 | 0, // Reserved2 |
| 237 | 0, // Reserved2 | 237 | initial_call_done ? nullptr : &ctxt, &output_desc, &attr, |
| 238 | initial_call_done ? nullptr : &ctxt, &output_desc, &attr, | 238 | nullptr); // ptsExpiry |
| 239 | nullptr); // ptsExpiry | ||
| 240 | 239 | ||
| 241 | if (output_buffers[0].pvBuffer) { | 240 | if (output_buffers[0].pvBuffer) { |
| 242 | const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer), | 241 | const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer), |
| @@ -478,7 +477,8 @@ public: | |||
| 478 | return ResultInternalError; | 477 | return ResultInternalError; |
| 479 | } | 478 | } |
| 480 | PCCERT_CONTEXT some_cert = nullptr; | 479 | PCCERT_CONTEXT some_cert = nullptr; |
| 481 | while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) { | 480 | while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) != |
| 481 | nullptr) { | ||
| 482 | out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), | 482 | out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), |
| 483 | static_cast<u8*>(some_cert->pbCertEncoded) + | 483 | static_cast<u8*>(some_cert->pbCertEncoded) + |
| 484 | some_cert->cbCertEncoded); | 484 | some_cert->cbCertEncoded); |
diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp index 370678f48..c48914f64 100644 --- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp +++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp | |||
| @@ -100,7 +100,7 @@ public: | |||
| 100 | 100 | ||
| 101 | Result DoHandshake() override { | 101 | Result DoHandshake() override { |
| 102 | OSStatus status = SSLHandshake(context); | 102 | OSStatus status = SSLHandshake(context); |
| 103 | return HandleReturn("SSLHandshake", 0, status).Code(); | 103 | return HandleReturn("SSLHandshake", 0, status); |
| 104 | } | 104 | } |
| 105 | 105 | ||
| 106 | Result Read(size_t* out_size, std::span<u8> data) override { | 106 | Result Read(size_t* out_size, std::span<u8> data) override { |
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 6bb02393c..2eb978379 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp | |||
| @@ -217,7 +217,7 @@ private: | |||
| 217 | IPC::ResponseBuilder rb{ctx, 6}; | 217 | IPC::ResponseBuilder rb{ctx, 6}; |
| 218 | rb.Push(ResultSuccess); | 218 | rb.Push(ResultSuccess); |
| 219 | 219 | ||
| 220 | if (Settings::values.use_docked_mode.GetValue()) { | 220 | if (Settings::IsDockedMode()) { |
| 221 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); | 221 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); |
| 222 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); | 222 | rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); |
| 223 | } else { | 223 | } else { |
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 28f89c599..a983f23ea 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp | |||
| @@ -39,19 +39,41 @@ namespace Network { | |||
| 39 | 39 | ||
| 40 | namespace { | 40 | namespace { |
| 41 | 41 | ||
| 42 | enum class CallType { | ||
| 43 | Send, | ||
| 44 | Other, | ||
| 45 | }; | ||
| 46 | |||
| 42 | #ifdef _WIN32 | 47 | #ifdef _WIN32 |
| 43 | 48 | ||
| 44 | using socklen_t = int; | 49 | using socklen_t = int; |
| 45 | 50 | ||
| 51 | SOCKET interrupt_socket = static_cast<SOCKET>(-1); | ||
| 52 | |||
| 53 | void InterruptSocketOperations() { | ||
| 54 | closesocket(interrupt_socket); | ||
| 55 | } | ||
| 56 | |||
| 57 | void AcknowledgeInterrupt() { | ||
| 58 | interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | ||
| 59 | } | ||
| 60 | |||
| 46 | void Initialize() { | 61 | void Initialize() { |
| 47 | WSADATA wsa_data; | 62 | WSADATA wsa_data; |
| 48 | (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); | 63 | (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); |
| 64 | |||
| 65 | AcknowledgeInterrupt(); | ||
| 49 | } | 66 | } |
| 50 | 67 | ||
| 51 | void Finalize() { | 68 | void Finalize() { |
| 69 | InterruptSocketOperations(); | ||
| 52 | WSACleanup(); | 70 | WSACleanup(); |
| 53 | } | 71 | } |
| 54 | 72 | ||
| 73 | SOCKET GetInterruptSocket() { | ||
| 74 | return interrupt_socket; | ||
| 75 | } | ||
| 76 | |||
| 55 | sockaddr TranslateFromSockAddrIn(SockAddrIn input) { | 77 | sockaddr TranslateFromSockAddrIn(SockAddrIn input) { |
| 56 | sockaddr_in result; | 78 | sockaddr_in result; |
| 57 | 79 | ||
| @@ -96,7 +118,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) { | |||
| 96 | return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; | 118 | return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; |
| 97 | } | 119 | } |
| 98 | 120 | ||
| 99 | Errno TranslateNativeError(int e) { | 121 | Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { |
| 100 | switch (e) { | 122 | switch (e) { |
| 101 | case 0: | 123 | case 0: |
| 102 | return Errno::SUCCESS; | 124 | return Errno::SUCCESS; |
| @@ -112,6 +134,14 @@ Errno TranslateNativeError(int e) { | |||
| 112 | return Errno::AGAIN; | 134 | return Errno::AGAIN; |
| 113 | case WSAECONNREFUSED: | 135 | case WSAECONNREFUSED: |
| 114 | return Errno::CONNREFUSED; | 136 | return Errno::CONNREFUSED; |
| 137 | case WSAECONNABORTED: | ||
| 138 | if (call_type == CallType::Send) { | ||
| 139 | // Winsock yields WSAECONNABORTED from `send` in situations where Unix | ||
| 140 | // systems, and actual Switches, yield EPIPE. | ||
| 141 | return Errno::PIPE; | ||
| 142 | } else { | ||
| 143 | return Errno::CONNABORTED; | ||
| 144 | } | ||
| 115 | case WSAECONNRESET: | 145 | case WSAECONNRESET: |
| 116 | return Errno::CONNRESET; | 146 | return Errno::CONNRESET; |
| 117 | case WSAEHOSTUNREACH: | 147 | case WSAEHOSTUNREACH: |
| @@ -144,9 +174,42 @@ constexpr int SD_RECEIVE = SHUT_RD; | |||
| 144 | constexpr int SD_SEND = SHUT_WR; | 174 | constexpr int SD_SEND = SHUT_WR; |
| 145 | constexpr int SD_BOTH = SHUT_RDWR; | 175 | constexpr int SD_BOTH = SHUT_RDWR; |
| 146 | 176 | ||
| 147 | void Initialize() {} | 177 | int interrupt_pipe_fd[2] = {-1, -1}; |
| 148 | 178 | ||
| 149 | void Finalize() {} | 179 | void Initialize() { |
| 180 | if (pipe(interrupt_pipe_fd) != 0) { | ||
| 181 | LOG_ERROR(Network, "Failed to create interrupt pipe!"); | ||
| 182 | } | ||
| 183 | int flags = fcntl(interrupt_pipe_fd[0], F_GETFL); | ||
| 184 | ASSERT_MSG(fcntl(interrupt_pipe_fd[0], F_SETFL, flags | O_NONBLOCK) == 0, | ||
| 185 | "Failed to set nonblocking state for interrupt pipe"); | ||
| 186 | } | ||
| 187 | |||
| 188 | void Finalize() { | ||
| 189 | if (interrupt_pipe_fd[0] >= 0) { | ||
| 190 | close(interrupt_pipe_fd[0]); | ||
| 191 | } | ||
| 192 | if (interrupt_pipe_fd[1] >= 0) { | ||
| 193 | close(interrupt_pipe_fd[1]); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | void InterruptSocketOperations() { | ||
| 198 | u8 value = 0; | ||
| 199 | ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1); | ||
| 200 | } | ||
| 201 | |||
| 202 | void AcknowledgeInterrupt() { | ||
| 203 | u8 value = 0; | ||
| 204 | ssize_t ret = read(interrupt_pipe_fd[0], &value, sizeof(value)); | ||
| 205 | if (ret != 1 && errno != EAGAIN && errno != EWOULDBLOCK) { | ||
| 206 | LOG_ERROR(Network, "Failed to acknowledge interrupt on shutdown"); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | SOCKET GetInterruptSocket() { | ||
| 211 | return interrupt_pipe_fd[0]; | ||
| 212 | } | ||
| 150 | 213 | ||
| 151 | sockaddr TranslateFromSockAddrIn(SockAddrIn input) { | 214 | sockaddr TranslateFromSockAddrIn(SockAddrIn input) { |
| 152 | sockaddr_in result; | 215 | sockaddr_in result; |
| @@ -198,7 +261,7 @@ bool EnableNonBlock(int fd, bool enable) { | |||
| 198 | return fcntl(fd, F_SETFL, flags) == 0; | 261 | return fcntl(fd, F_SETFL, flags) == 0; |
| 199 | } | 262 | } |
| 200 | 263 | ||
| 201 | Errno TranslateNativeError(int e) { | 264 | Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { |
| 202 | switch (e) { | 265 | switch (e) { |
| 203 | case 0: | 266 | case 0: |
| 204 | return Errno::SUCCESS; | 267 | return Errno::SUCCESS; |
| @@ -208,6 +271,10 @@ Errno TranslateNativeError(int e) { | |||
| 208 | return Errno::INVAL; | 271 | return Errno::INVAL; |
| 209 | case EMFILE: | 272 | case EMFILE: |
| 210 | return Errno::MFILE; | 273 | return Errno::MFILE; |
| 274 | case EPIPE: | ||
| 275 | return Errno::PIPE; | ||
| 276 | case ECONNABORTED: | ||
| 277 | return Errno::CONNABORTED; | ||
| 211 | case ENOTCONN: | 278 | case ENOTCONN: |
| 212 | return Errno::NOTCONN; | 279 | return Errno::NOTCONN; |
| 213 | case EAGAIN: | 280 | case EAGAIN: |
| @@ -236,13 +303,13 @@ Errno TranslateNativeError(int e) { | |||
| 236 | 303 | ||
| 237 | #endif | 304 | #endif |
| 238 | 305 | ||
| 239 | Errno GetAndLogLastError() { | 306 | Errno GetAndLogLastError(CallType call_type = CallType::Other) { |
| 240 | #ifdef _WIN32 | 307 | #ifdef _WIN32 |
| 241 | int e = WSAGetLastError(); | 308 | int e = WSAGetLastError(); |
| 242 | #else | 309 | #else |
| 243 | int e = errno; | 310 | int e = errno; |
| 244 | #endif | 311 | #endif |
| 245 | const Errno err = TranslateNativeError(e); | 312 | const Errno err = TranslateNativeError(e, call_type); |
| 246 | if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { | 313 | if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { |
| 247 | // These happen during normal operation, so only log them at debug level. | 314 | // These happen during normal operation, so only log them at debug level. |
| 248 | LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); | 315 | LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); |
| @@ -473,10 +540,24 @@ NetworkInstance::~NetworkInstance() { | |||
| 473 | Finalize(); | 540 | Finalize(); |
| 474 | } | 541 | } |
| 475 | 542 | ||
| 543 | void CancelPendingSocketOperations() { | ||
| 544 | InterruptSocketOperations(); | ||
| 545 | } | ||
| 546 | |||
| 547 | void RestartSocketOperations() { | ||
| 548 | AcknowledgeInterrupt(); | ||
| 549 | } | ||
| 550 | |||
| 476 | std::optional<IPv4Address> GetHostIPv4Address() { | 551 | std::optional<IPv4Address> GetHostIPv4Address() { |
| 477 | const auto network_interface = Network::GetSelectedNetworkInterface(); | 552 | const auto network_interface = Network::GetSelectedNetworkInterface(); |
| 478 | if (!network_interface.has_value()) { | 553 | if (!network_interface.has_value()) { |
| 479 | LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); | 554 | // Only print the error once to avoid log spam |
| 555 | static bool print_error = true; | ||
| 556 | if (print_error) { | ||
| 557 | LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); | ||
| 558 | print_error = false; | ||
| 559 | } | ||
| 560 | |||
| 480 | return {}; | 561 | return {}; |
| 481 | } | 562 | } |
| 482 | 563 | ||
| @@ -537,7 +618,14 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { | |||
| 537 | return result; | 618 | return result; |
| 538 | }); | 619 | }); |
| 539 | 620 | ||
| 540 | const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout); | 621 | host_pollfds.push_back(WSAPOLLFD{ |
| 622 | .fd = GetInterruptSocket(), | ||
| 623 | .events = POLLIN, | ||
| 624 | .revents = 0, | ||
| 625 | }); | ||
| 626 | |||
| 627 | const int result = | ||
| 628 | WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), timeout); | ||
| 541 | if (result == 0) { | 629 | if (result == 0) { |
| 542 | ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), | 630 | ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), |
| 543 | [](WSAPOLLFD fd) { return fd.revents == 0; })); | 631 | [](WSAPOLLFD fd) { return fd.revents == 0; })); |
| @@ -604,6 +692,24 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { | |||
| 604 | std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { | 692 | std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { |
| 605 | sockaddr_in addr; | 693 | sockaddr_in addr; |
| 606 | socklen_t addrlen = sizeof(addr); | 694 | socklen_t addrlen = sizeof(addr); |
| 695 | |||
| 696 | std::vector<WSAPOLLFD> host_pollfds{ | ||
| 697 | WSAPOLLFD{fd, POLLIN, 0}, | ||
| 698 | WSAPOLLFD{GetInterruptSocket(), POLLIN, 0}, | ||
| 699 | }; | ||
| 700 | |||
| 701 | while (true) { | ||
| 702 | const int pollres = | ||
| 703 | WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), -1); | ||
| 704 | if (host_pollfds[1].revents != 0) { | ||
| 705 | // Interrupt signaled before a client could be accepted, break | ||
| 706 | return {AcceptResult{}, Errno::AGAIN}; | ||
| 707 | } | ||
| 708 | if (pollres > 0) { | ||
| 709 | break; | ||
| 710 | } | ||
| 711 | } | ||
| 712 | |||
| 607 | const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen); | 713 | const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen); |
| 608 | 714 | ||
| 609 | if (new_socket == INVALID_SOCKET) { | 715 | if (new_socket == INVALID_SOCKET) { |
| @@ -725,13 +831,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { | |||
| 725 | ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); | 831 | ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); |
| 726 | ASSERT(flags == 0); | 832 | ASSERT(flags == 0); |
| 727 | 833 | ||
| 834 | int native_flags = 0; | ||
| 835 | #if YUZU_UNIX | ||
| 836 | native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE | ||
| 837 | #endif | ||
| 728 | const auto result = send(fd, reinterpret_cast<const char*>(message.data()), | 838 | const auto result = send(fd, reinterpret_cast<const char*>(message.data()), |
| 729 | static_cast<int>(message.size()), 0); | 839 | static_cast<int>(message.size()), native_flags); |
| 730 | if (result != SOCKET_ERROR) { | 840 | if (result != SOCKET_ERROR) { |
| 731 | return {static_cast<s32>(result), Errno::SUCCESS}; | 841 | return {static_cast<s32>(result), Errno::SUCCESS}; |
| 732 | } | 842 | } |
| 733 | 843 | ||
| 734 | return {-1, GetAndLogLastError()}; | 844 | return {-1, GetAndLogLastError(CallType::Send)}; |
| 735 | } | 845 | } |
| 736 | 846 | ||
| 737 | std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, | 847 | std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, |
| @@ -753,7 +863,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, | |||
| 753 | return {static_cast<s32>(result), Errno::SUCCESS}; | 863 | return {static_cast<s32>(result), Errno::SUCCESS}; |
| 754 | } | 864 | } |
| 755 | 865 | ||
| 756 | return {-1, GetAndLogLastError()}; | 866 | return {-1, GetAndLogLastError(CallType::Send)}; |
| 757 | } | 867 | } |
| 758 | 868 | ||
| 759 | Errno Socket::Close() { | 869 | Errno Socket::Close() { |
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index badcb8369..b7b7d773a 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h | |||
| @@ -33,10 +33,12 @@ enum class Errno { | |||
| 33 | BADF, | 33 | BADF, |
| 34 | INVAL, | 34 | INVAL, |
| 35 | MFILE, | 35 | MFILE, |
| 36 | PIPE, | ||
| 36 | NOTCONN, | 37 | NOTCONN, |
| 37 | AGAIN, | 38 | AGAIN, |
| 38 | CONNREFUSED, | 39 | CONNREFUSED, |
| 39 | CONNRESET, | 40 | CONNRESET, |
| 41 | CONNABORTED, | ||
| 40 | HOSTUNREACH, | 42 | HOSTUNREACH, |
| 41 | NETDOWN, | 43 | NETDOWN, |
| 42 | NETUNREACH, | 44 | NETUNREACH, |
| @@ -94,6 +96,9 @@ public: | |||
| 94 | ~NetworkInstance(); | 96 | ~NetworkInstance(); |
| 95 | }; | 97 | }; |
| 96 | 98 | ||
| 99 | void CancelPendingSocketOperations(); | ||
| 100 | void RestartSocketOperations(); | ||
| 101 | |||
| 97 | #ifdef _WIN32 | 102 | #ifdef _WIN32 |
| 98 | constexpr IPv4Address TranslateIPv4(in_addr addr) { | 103 | constexpr IPv4Address TranslateIPv4(in_addr addr) { |
| 99 | auto& bytes = addr.S_un.S_un_b; | 104 | auto& bytes = addr.S_un.S_un_b; |
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 4c909a6d3..7c37f660b 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp | |||
| @@ -200,7 +200,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() { | |||
| 200 | }); | 200 | }); |
| 201 | 201 | ||
| 202 | if (res == network_interfaces.end()) { | 202 | if (res == network_interfaces.end()) { |
| 203 | LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); | 203 | // Only print the error once to avoid log spam |
| 204 | static bool print_error = true; | ||
| 205 | if (print_error) { | ||
| 206 | LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", | ||
| 207 | selected_network_interface); | ||
| 208 | print_error = false; | ||
| 209 | } | ||
| 210 | |||
| 204 | return std::nullopt; | 211 | return std::nullopt; |
| 205 | } | 212 | } |
| 206 | 213 | ||
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index e04ad19db..5a42dea48 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp | |||
| @@ -18,7 +18,7 @@ namespace Loader { | |||
| 18 | 18 | ||
| 19 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, | 19 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, |
| 20 | bool override_update_) | 20 | bool override_update_) |
| 21 | : AppLoader(std::move(file_)), override_update(override_update_) { | 21 | : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) { |
| 22 | const auto file_dir = file->GetContainingDirectory(); | 22 | const auto file_dir = file->GetContainingDirectory(); |
| 23 | 23 | ||
| 24 | // Title ID | 24 | // Title ID |
| @@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys | |||
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( | 71 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( |
| 72 | FileSys::VirtualDir directory, bool override_update_) | 72 | FileSys::VirtualDir directory, bool override_update_, bool is_hbl_) |
| 73 | : AppLoader(directory->GetFile("main")), dir(std::move(directory)), | 73 | : AppLoader(directory->GetFile("main")), dir(std::move(directory)), |
| 74 | override_update(override_update_) {} | 74 | override_update(override_update_), is_hbl(is_hbl_) {} |
| 75 | 75 | ||
| 76 | FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { | 76 | FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { |
| 77 | if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { | 77 | if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { |
| @@ -147,13 +147,13 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect | |||
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | // Setup the process code layout | 149 | // Setup the process code layout |
| 150 | if (process.LoadFromMetadata(metadata, code_size).IsError()) { | 150 | if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) { |
| 151 | return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; | 151 | return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; |
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | // Load NSO modules | 154 | // Load NSO modules |
| 155 | modules.clear(); | 155 | modules.clear(); |
| 156 | const VAddr base_address{GetInteger(process.GetPageTable().GetCodeRegionStart())}; | 156 | const VAddr base_address{GetInteger(process.GetEntryPoint())}; |
| 157 | VAddr next_load_addr{base_address}; | 157 | VAddr next_load_addr{base_address}; |
| 158 | const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(), | 158 | const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(), |
| 159 | system.GetContentProvider()}; | 159 | system.GetContentProvider()}; |
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index f7702225e..1e9f765c9 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h | |||
| @@ -27,7 +27,8 @@ public: | |||
| 27 | 27 | ||
| 28 | // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' | 28 | // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' |
| 29 | explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, | 29 | explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, |
| 30 | bool override_update_ = false); | 30 | bool override_update_ = false, |
| 31 | bool is_hbl_ = false); | ||
| 31 | 32 | ||
| 32 | /** | 33 | /** |
| 33 | * Identifies whether or not the given file is a deconstructed ROM directory. | 34 | * Identifies whether or not the given file is a deconstructed ROM directory. |
| @@ -62,6 +63,7 @@ private: | |||
| 62 | std::string name; | 63 | std::string name; |
| 63 | u64 title_id{}; | 64 | u64 title_id{}; |
| 64 | bool override_update; | 65 | bool override_update; |
| 66 | bool is_hbl; | ||
| 65 | 67 | ||
| 66 | Modules modules; | 68 | Modules modules; |
| 67 | }; | 69 | }; |
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp index ffe976b94..bf56a08b4 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp | |||
| @@ -90,13 +90,14 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process, | |||
| 90 | codeset.DataSegment().size += kip->GetBSSSize(); | 90 | codeset.DataSegment().size += kip->GetBSSSize(); |
| 91 | 91 | ||
| 92 | // Setup the process code layout | 92 | // Setup the process code layout |
| 93 | if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) | 93 | if (process |
| 94 | .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false) | ||
| 94 | .IsError()) { | 95 | .IsError()) { |
| 95 | return {ResultStatus::ErrorNotInitialized, {}}; | 96 | return {ResultStatus::ErrorNotInitialized, {}}; |
| 96 | } | 97 | } |
| 97 | 98 | ||
| 98 | codeset.memory = std::move(program_image); | 99 | codeset.memory = std::move(program_image); |
| 99 | const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart()); | 100 | const VAddr base_address = GetInteger(process.GetEntryPoint()); |
| 100 | process.LoadModule(std::move(codeset), base_address); | 101 | process.LoadModule(std::move(codeset), base_address); |
| 101 | 102 | ||
| 102 | LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address); | 103 | LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address); |
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index f24474ed8..b6e355622 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp | |||
| @@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) { | |||
| 108 | return "unknown"; | 108 | return "unknown"; |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | constexpr std::array<const char*, 66> RESULT_MESSAGES{ | 111 | constexpr std::array<const char*, 68> RESULT_MESSAGES{ |
| 112 | "The operation completed successfully.", | 112 | "The operation completed successfully.", |
| 113 | "The loader requested to load is already loaded.", | 113 | "The loader requested to load is already loaded.", |
| 114 | "The operation is not implemented.", | 114 | "The operation is not implemented.", |
| @@ -135,7 +135,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 135 | "The titlekey and/or titlekek is incorrect or the section header is invalid.", | 135 | "The titlekey and/or titlekek is incorrect or the section header is invalid.", |
| 136 | "The XCI file is missing a Program-type NCA.", | 136 | "The XCI file is missing a Program-type NCA.", |
| 137 | "The NCA file is not an application.", | 137 | "The NCA file is not an application.", |
| 138 | "The ExeFS partition could not be found.", | 138 | "The Program-type NCA contains no executable. An update may be required.", |
| 139 | "The XCI file has a bad header.", | 139 | "The XCI file has a bad header.", |
| 140 | "The XCI file is missing a partition.", | 140 | "The XCI file is missing a partition.", |
| 141 | "The file could not be found or does not exist.", | 141 | "The file could not be found or does not exist.", |
| @@ -169,12 +169,14 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 169 | "The BKTR-type NCA has a bad Subsection block.", | 169 | "The BKTR-type NCA has a bad Subsection block.", |
| 170 | "The BKTR-type NCA has a bad Relocation bucket.", | 170 | "The BKTR-type NCA has a bad Relocation bucket.", |
| 171 | "The BKTR-type NCA has a bad Subsection bucket.", | 171 | "The BKTR-type NCA has a bad Subsection bucket.", |
| 172 | "The BKTR-type NCA is missing the base RomFS.", | 172 | "Game updates cannot be loaded directly. Load the base game instead.", |
| 173 | "The NSP or XCI does not contain an update in addition to the base game.", | 173 | "The NSP or XCI does not contain an update in addition to the base game.", |
| 174 | "The KIP file has a bad header.", | 174 | "The KIP file has a bad header.", |
| 175 | "The KIP BLZ decompression of the section failed unexpectedly.", | 175 | "The KIP BLZ decompression of the section failed unexpectedly.", |
| 176 | "The INI file has a bad header.", | 176 | "The INI file has a bad header.", |
| 177 | "The INI file contains more than the maximum allowable number of KIP files.", | 177 | "The INI file contains more than the maximum allowable number of KIP files.", |
| 178 | "Integrity verification could not be performed for this file.", | ||
| 179 | "Integrity verification failed.", | ||
| 178 | }; | 180 | }; |
| 179 | 181 | ||
| 180 | std::string GetResultStatusString(ResultStatus status) { | 182 | std::string GetResultStatusString(ResultStatus status) { |
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 7a2a52fd4..b4828f7cd 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <functional> | ||
| 6 | #include <iosfwd> | 7 | #include <iosfwd> |
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <optional> | 9 | #include <optional> |
| @@ -79,8 +80,6 @@ enum class ResultStatus : u16 { | |||
| 79 | ErrorBadPFSHeader, | 80 | ErrorBadPFSHeader, |
| 80 | ErrorIncorrectPFSFileSize, | 81 | ErrorIncorrectPFSFileSize, |
| 81 | ErrorBadNCAHeader, | 82 | ErrorBadNCAHeader, |
| 82 | ErrorCompressedNCA, | ||
| 83 | ErrorSparseNCA, | ||
| 84 | ErrorMissingProductionKeyFile, | 83 | ErrorMissingProductionKeyFile, |
| 85 | ErrorMissingHeaderKey, | 84 | ErrorMissingHeaderKey, |
| 86 | ErrorIncorrectHeaderKey, | 85 | ErrorIncorrectHeaderKey, |
| @@ -134,6 +133,8 @@ enum class ResultStatus : u16 { | |||
| 134 | ErrorBLZDecompressionFailed, | 133 | ErrorBLZDecompressionFailed, |
| 135 | ErrorBadINIHeader, | 134 | ErrorBadINIHeader, |
| 136 | ErrorINITooManyKIPs, | 135 | ErrorINITooManyKIPs, |
| 136 | ErrorIntegrityVerificationNotImplemented, | ||
| 137 | ErrorIntegrityVerificationFailed, | ||
| 137 | }; | 138 | }; |
| 138 | 139 | ||
| 139 | std::string GetResultStatusString(ResultStatus status); | 140 | std::string GetResultStatusString(ResultStatus status); |
| @@ -172,6 +173,13 @@ public: | |||
| 172 | virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; | 173 | virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; |
| 173 | 174 | ||
| 174 | /** | 175 | /** |
| 176 | * Try to verify the integrity of the file. | ||
| 177 | */ | ||
| 178 | virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 179 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 180 | } | ||
| 181 | |||
| 182 | /** | ||
| 175 | * Get the code (typically .code section) of the application | 183 | * Get the code (typically .code section) of the application |
| 176 | * | 184 | * |
| 177 | * @param[out] buffer Reference to buffer to store data | 185 | * @param[out] buffer Reference to buffer to store data |
| @@ -276,16 +284,6 @@ public: | |||
| 276 | } | 284 | } |
| 277 | 285 | ||
| 278 | /** | 286 | /** |
| 279 | * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS) | ||
| 280 | * data. Needed for BKTR patching. | ||
| 281 | * | ||
| 282 | * @return IVFC offset for RomFS. | ||
| 283 | */ | ||
| 284 | virtual u64 ReadRomFSIVFCOffset() const { | ||
| 285 | return 0; | ||
| 286 | } | ||
| 287 | |||
| 288 | /** | ||
| 289 | * Get the title of the application | 287 | * Get the title of the application |
| 290 | * | 288 | * |
| 291 | * @param[out] title Reference to store the application title into | 289 | * @param[out] title Reference to store the application title into |
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index cf35b1249..3b7b005ff 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp | |||
| @@ -76,10 +76,6 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) { | |||
| 76 | return nca_loader->ReadRomFS(dir); | 76 | return nca_loader->ReadRomFS(dir); |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { | ||
| 80 | return nca_loader->ReadRomFSIVFCOffset(); | ||
| 81 | } | ||
| 82 | |||
| 83 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { | 79 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { |
| 84 | return nca_loader->ReadProgramId(out_program_id); | 80 | return nca_loader->ReadProgramId(out_program_id); |
| 85 | } | 81 | } |
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index d7f70db43..81df2bbcd 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h | |||
| @@ -39,7 +39,6 @@ public: | |||
| 39 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 39 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 40 | 40 | ||
| 41 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 41 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 42 | u64 ReadRomFSIVFCOffset() const override; | ||
| 43 | ResultStatus ReadProgramId(u64& out_program_id) override; | 42 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 44 | 43 | ||
| 45 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; | 44 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; |
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 513af194d..4feb6968a 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp | |||
| @@ -3,13 +3,18 @@ | |||
| 3 | 3 | ||
| 4 | #include <utility> | 4 | #include <utility> |
| 5 | 5 | ||
| 6 | #include "common/hex_util.h" | ||
| 7 | #include "common/scope_exit.h" | ||
| 6 | #include "core/core.h" | 8 | #include "core/core.h" |
| 7 | #include "core/file_sys/content_archive.h" | 9 | #include "core/file_sys/content_archive.h" |
| 10 | #include "core/file_sys/nca_metadata.h" | ||
| 11 | #include "core/file_sys/registered_cache.h" | ||
| 8 | #include "core/file_sys/romfs_factory.h" | 12 | #include "core/file_sys/romfs_factory.h" |
| 9 | #include "core/hle/kernel/k_process.h" | 13 | #include "core/hle/kernel/k_process.h" |
| 10 | #include "core/hle/service/filesystem/filesystem.h" | 14 | #include "core/hle/service/filesystem/filesystem.h" |
| 11 | #include "core/loader/deconstructed_rom_directory.h" | 15 | #include "core/loader/deconstructed_rom_directory.h" |
| 12 | #include "core/loader/nca.h" | 16 | #include "core/loader/nca.h" |
| 17 | #include "mbedtls/sha256.h" | ||
| 13 | 18 | ||
| 14 | namespace Loader { | 19 | namespace Loader { |
| 15 | 20 | ||
| @@ -43,9 +48,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S | |||
| 43 | return {ResultStatus::ErrorNCANotProgram, {}}; | 48 | return {ResultStatus::ErrorNCANotProgram, {}}; |
| 44 | } | 49 | } |
| 45 | 50 | ||
| 46 | const auto exefs = nca->GetExeFS(); | 51 | auto exefs = nca->GetExeFS(); |
| 47 | if (exefs == nullptr) { | 52 | if (exefs == nullptr) { |
| 48 | return {ResultStatus::ErrorNoExeFS, {}}; | 53 | LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); |
| 54 | |||
| 55 | // This NCA may be a sparse base of an installed title. | ||
| 56 | // Try to fetch the ExeFS from the installed update. | ||
| 57 | const auto& installed = system.GetContentProvider(); | ||
| 58 | const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()), | ||
| 59 | FileSys::ContentRecordType::Program); | ||
| 60 | |||
| 61 | if (update_nca) { | ||
| 62 | exefs = update_nca->GetExeFS(); | ||
| 63 | } | ||
| 64 | |||
| 65 | if (exefs == nullptr) { | ||
| 66 | return {ResultStatus::ErrorNoExeFS, {}}; | ||
| 67 | } | ||
| 49 | } | 68 | } |
| 50 | 69 | ||
| 51 | directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); | 70 | directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); |
| @@ -64,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S | |||
| 64 | return load_result; | 83 | return load_result; |
| 65 | } | 84 | } |
| 66 | 85 | ||
| 86 | ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 87 | using namespace Common::Literals; | ||
| 88 | |||
| 89 | constexpr size_t NcaFileNameWithHashLength = 36; | ||
| 90 | constexpr size_t NcaFileNameHashLength = 32; | ||
| 91 | constexpr size_t NcaSha256HashLength = 32; | ||
| 92 | constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2; | ||
| 93 | |||
| 94 | // Get the file name. | ||
| 95 | const auto name = file->GetName(); | ||
| 96 | |||
| 97 | // We won't try to verify meta NCAs. | ||
| 98 | if (name.ends_with(".cnmt.nca")) { | ||
| 99 | return ResultStatus::Success; | ||
| 100 | } | ||
| 101 | |||
| 102 | // Check if we can verify this file. NCAs should be named after their hashes. | ||
| 103 | if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) { | ||
| 104 | LOG_WARNING(Loader, "Unable to validate NCA with name {}", name); | ||
| 105 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 106 | } | ||
| 107 | |||
| 108 | // Get the expected truncated hash of the NCA. | ||
| 109 | const auto input_hash = | ||
| 110 | Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false); | ||
| 111 | |||
| 112 | // Declare buffer to read into. | ||
| 113 | std::vector<u8> buffer(4_MiB); | ||
| 114 | |||
| 115 | // Initialize sha256 verification context. | ||
| 116 | mbedtls_sha256_context ctx; | ||
| 117 | mbedtls_sha256_init(&ctx); | ||
| 118 | mbedtls_sha256_starts_ret(&ctx, 0); | ||
| 119 | |||
| 120 | // Ensure we maintain a clean state on exit. | ||
| 121 | SCOPE_EXIT({ mbedtls_sha256_free(&ctx); }); | ||
| 122 | |||
| 123 | // Declare counters. | ||
| 124 | const size_t total_size = file->GetSize(); | ||
| 125 | size_t processed_size = 0; | ||
| 126 | |||
| 127 | // Begin iterating the file. | ||
| 128 | while (processed_size < total_size) { | ||
| 129 | // Refill the buffer. | ||
| 130 | const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size); | ||
| 131 | const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size); | ||
| 132 | |||
| 133 | // Update the hash function with the buffer contents. | ||
| 134 | mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size); | ||
| 135 | |||
| 136 | // Update counters. | ||
| 137 | processed_size += read_size; | ||
| 138 | |||
| 139 | // Call the progress function. | ||
| 140 | if (!progress_callback(processed_size, total_size)) { | ||
| 141 | return ResultStatus::ErrorIntegrityVerificationFailed; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | // Finalize context and compute the output hash. | ||
| 146 | std::array<u8, NcaSha256HashLength> output_hash; | ||
| 147 | mbedtls_sha256_finish_ret(&ctx, output_hash.data()); | ||
| 148 | |||
| 149 | // Compare to expected. | ||
| 150 | if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) { | ||
| 151 | LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name); | ||
| 152 | return ResultStatus::ErrorIntegrityVerificationFailed; | ||
| 153 | } | ||
| 154 | |||
| 155 | // File verified. | ||
| 156 | return ResultStatus::Success; | ||
| 157 | } | ||
| 158 | |||
| 67 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | 159 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { |
| 68 | if (nca == nullptr) { | 160 | if (nca == nullptr) { |
| 69 | return ResultStatus::ErrorNotInitialized; | 161 | return ResultStatus::ErrorNotInitialized; |
| @@ -77,14 +169,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | |||
| 77 | return ResultStatus::Success; | 169 | return ResultStatus::Success; |
| 78 | } | 170 | } |
| 79 | 171 | ||
| 80 | u64 AppLoader_NCA::ReadRomFSIVFCOffset() const { | ||
| 81 | if (nca == nullptr) { | ||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | return nca->GetBaseIVFCOffset(); | ||
| 86 | } | ||
| 87 | |||
| 88 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { | 172 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { |
| 89 | if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { | 173 | if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { |
| 90 | return ResultStatus::ErrorNotInitialized; | 174 | return ResultStatus::ErrorNotInitialized; |
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index d22d9146e..96779e27f 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h | |||
| @@ -39,8 +39,9 @@ public: | |||
| 39 | 39 | ||
| 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 41 | 41 | ||
| 42 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 43 | |||
| 42 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 44 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 43 | u64 ReadRomFSIVFCOffset() const override; | ||
| 44 | ResultStatus ReadProgramId(u64& out_program_id) override; | 45 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 45 | 46 | ||
| 46 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; | 47 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; |
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 506808b5d..69f1a54ed 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp | |||
| @@ -196,14 +196,15 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) | |||
| 196 | program_image.resize(static_cast<u32>(program_image.size()) + bss_size); | 196 | program_image.resize(static_cast<u32>(program_image.size()) + bss_size); |
| 197 | 197 | ||
| 198 | // Setup the process code layout | 198 | // Setup the process code layout |
| 199 | if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) | 199 | if (process |
| 200 | .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false) | ||
| 200 | .IsError()) { | 201 | .IsError()) { |
| 201 | return false; | 202 | return false; |
| 202 | } | 203 | } |
| 203 | 204 | ||
| 204 | // Load codeset for current process | 205 | // Load codeset for current process |
| 205 | codeset.memory = std::move(program_image); | 206 | codeset.memory = std::move(program_image); |
| 206 | process.LoadModule(std::move(codeset), process.GetPageTable().GetCodeRegionStart()); | 207 | process.LoadModule(std::move(codeset), process.GetEntryPoint()); |
| 207 | 208 | ||
| 208 | return true; | 209 | return true; |
| 209 | } | 210 | } |
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 74cc9579f..1350da8dc 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp | |||
| @@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: | |||
| 127 | } | 127 | } |
| 128 | 128 | ||
| 129 | // Apply patches if necessary | 129 | // Apply patches if necessary |
| 130 | if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { | 130 | const auto name = nso_file.GetName(); |
| 131 | if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) { | ||
| 131 | std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); | 132 | std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); |
| 132 | std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); | 133 | std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); |
| 133 | std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), | 134 | std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), |
| 134 | program_image.size()); | 135 | program_image.size()); |
| 135 | 136 | ||
| 136 | pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); | 137 | pi_header = pm->PatchNSO(pi_header, name); |
| 137 | 138 | ||
| 138 | std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); | 139 | std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); |
| 139 | } | 140 | } |
| @@ -167,7 +168,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::S | |||
| 167 | modules.clear(); | 168 | modules.clear(); |
| 168 | 169 | ||
| 169 | // Load module | 170 | // Load module |
| 170 | const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart()); | 171 | const VAddr base_address = GetInteger(process.GetEntryPoint()); |
| 171 | if (!LoadModule(process, system, *file, base_address, true, true)) { | 172 | if (!LoadModule(process, system, *file, base_address, true, true)) { |
| 172 | return {ResultStatus::ErrorLoadingNSO, {}}; | 173 | return {ResultStatus::ErrorLoadingNSO, {}}; |
| 173 | } | 174 | } |
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 80663e0e0..f4ab75b77 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp | |||
| @@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_, | |||
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | if (nsp->IsExtractedType()) { | 32 | if (nsp->IsExtractedType()) { |
| 33 | secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); | 33 | secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>( |
| 34 | nsp->GetExeFS(), false, file->GetName() == "hbl.nsp"); | ||
| 34 | } else { | 35 | } else { |
| 35 | const auto control_nca = | 36 | const auto control_nca = |
| 36 | nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); | 37 | nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); |
| @@ -117,12 +118,44 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S | |||
| 117 | return result; | 118 | return result; |
| 118 | } | 119 | } |
| 119 | 120 | ||
| 120 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { | 121 | ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { |
| 121 | return secondary_loader->ReadRomFS(out_file); | 122 | // Extracted-type NSPs can't be verified. |
| 123 | if (nsp->IsExtractedType()) { | ||
| 124 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 125 | } | ||
| 126 | |||
| 127 | // Get list of all NCAs. | ||
| 128 | const auto ncas = nsp->GetNCAsCollapsed(); | ||
| 129 | |||
| 130 | size_t total_size = 0; | ||
| 131 | size_t processed_size = 0; | ||
| 132 | |||
| 133 | // Loop over NCAs, collecting the total size to verify. | ||
| 134 | for (const auto& nca : ncas) { | ||
| 135 | total_size += nca->GetBaseFile()->GetSize(); | ||
| 136 | } | ||
| 137 | |||
| 138 | // Loop over NCAs again, verifying each. | ||
| 139 | for (const auto& nca : ncas) { | ||
| 140 | AppLoader_NCA loader_nca(nca->GetBaseFile()); | ||
| 141 | |||
| 142 | const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { | ||
| 143 | return progress_callback(processed_size + nca_processed_size, total_size); | ||
| 144 | }; | ||
| 145 | |||
| 146 | const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); | ||
| 147 | if (verification_result != ResultStatus::Success) { | ||
| 148 | return verification_result; | ||
| 149 | } | ||
| 150 | |||
| 151 | processed_size += nca->GetBaseFile()->GetSize(); | ||
| 152 | } | ||
| 153 | |||
| 154 | return ResultStatus::Success; | ||
| 122 | } | 155 | } |
| 123 | 156 | ||
| 124 | u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { | 157 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { |
| 125 | return secondary_loader->ReadRomFSIVFCOffset(); | 158 | return secondary_loader->ReadRomFS(out_file); |
| 126 | } | 159 | } |
| 127 | 160 | ||
| 128 | ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { | 161 | ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { |
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 003cc345c..7ce436c67 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h | |||
| @@ -45,8 +45,9 @@ public: | |||
| 45 | 45 | ||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 49 | |||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 50 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | u64 ReadRomFSIVFCOffset() const override; | ||
| 50 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 51 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 51 | ResultStatus ReadProgramId(u64& out_program_id) override; | 52 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 52 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; | 53 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; |
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index c7b1b3815..12d72c380 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp | |||
| @@ -85,12 +85,42 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S | |||
| 85 | return result; | 85 | return result; |
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { | 88 | ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { |
| 89 | return nca_loader->ReadRomFS(out_file); | 89 | // Verify secure partition, as it is the only thing we can process. |
| 90 | auto secure_partition = xci->GetSecurePartitionNSP(); | ||
| 91 | |||
| 92 | // Get list of all NCAs. | ||
| 93 | const auto ncas = secure_partition->GetNCAsCollapsed(); | ||
| 94 | |||
| 95 | size_t total_size = 0; | ||
| 96 | size_t processed_size = 0; | ||
| 97 | |||
| 98 | // Loop over NCAs, collecting the total size to verify. | ||
| 99 | for (const auto& nca : ncas) { | ||
| 100 | total_size += nca->GetBaseFile()->GetSize(); | ||
| 101 | } | ||
| 102 | |||
| 103 | // Loop over NCAs again, verifying each. | ||
| 104 | for (const auto& nca : ncas) { | ||
| 105 | AppLoader_NCA loader_nca(nca->GetBaseFile()); | ||
| 106 | |||
| 107 | const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { | ||
| 108 | return progress_callback(processed_size + nca_processed_size, total_size); | ||
| 109 | }; | ||
| 110 | |||
| 111 | const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); | ||
| 112 | if (verification_result != ResultStatus::Success) { | ||
| 113 | return verification_result; | ||
| 114 | } | ||
| 115 | |||
| 116 | processed_size += nca->GetBaseFile()->GetSize(); | ||
| 117 | } | ||
| 118 | |||
| 119 | return ResultStatus::Success; | ||
| 90 | } | 120 | } |
| 91 | 121 | ||
| 92 | u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { | 122 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { |
| 93 | return nca_loader->ReadRomFSIVFCOffset(); | 123 | return nca_loader->ReadRomFS(out_file); |
| 94 | } | 124 | } |
| 95 | 125 | ||
| 96 | ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { | 126 | ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { |
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 2affb6c6e..b02e136d3 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h | |||
| @@ -45,8 +45,9 @@ public: | |||
| 45 | 45 | ||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 49 | |||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 50 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | u64 ReadRomFSIVFCOffset() const override; | ||
| 50 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 51 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 51 | ResultStatus ReadProgramId(u64& out_program_id) override; | 52 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 52 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; | 53 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; |
diff --git a/src/core/memory.h b/src/core/memory.h index 2eb61ffd3..13047a545 100644 --- a/src/core/memory.h +++ b/src/core/memory.h | |||
| @@ -509,9 +509,9 @@ class GuestMemory { | |||
| 509 | 509 | ||
| 510 | public: | 510 | public: |
| 511 | GuestMemory() = delete; | 511 | GuestMemory() = delete; |
| 512 | explicit GuestMemory(M& memory_, u64 addr_, std::size_t size_, | 512 | explicit GuestMemory(M& memory, u64 addr, std::size_t size, |
| 513 | Common::ScratchBuffer<T>* backup = nullptr) | 513 | Common::ScratchBuffer<T>* backup = nullptr) |
| 514 | : memory{memory_}, addr{addr_}, size{size_} { | 514 | : m_memory{memory}, m_addr{addr}, m_size{size} { |
| 515 | static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write); | 515 | static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write); |
| 516 | if constexpr (FLAGS & GuestMemoryFlags::Read) { | 516 | if constexpr (FLAGS & GuestMemoryFlags::Read) { |
| 517 | Read(addr, size, backup); | 517 | Read(addr, size, backup); |
| @@ -521,89 +521,97 @@ public: | |||
| 521 | ~GuestMemory() = default; | 521 | ~GuestMemory() = default; |
| 522 | 522 | ||
| 523 | T* data() noexcept { | 523 | T* data() noexcept { |
| 524 | return data_span.data(); | 524 | return m_data_span.data(); |
| 525 | } | 525 | } |
| 526 | 526 | ||
| 527 | const T* data() const noexcept { | 527 | const T* data() const noexcept { |
| 528 | return data_span.data(); | 528 | return m_data_span.data(); |
| 529 | } | ||
| 530 | |||
| 531 | size_t size() const noexcept { | ||
| 532 | return m_size; | ||
| 533 | } | ||
| 534 | |||
| 535 | size_t size_bytes() const noexcept { | ||
| 536 | return this->size() * sizeof(T); | ||
| 529 | } | 537 | } |
| 530 | 538 | ||
| 531 | [[nodiscard]] T* begin() noexcept { | 539 | [[nodiscard]] T* begin() noexcept { |
| 532 | return data(); | 540 | return this->data(); |
| 533 | } | 541 | } |
| 534 | 542 | ||
| 535 | [[nodiscard]] const T* begin() const noexcept { | 543 | [[nodiscard]] const T* begin() const noexcept { |
| 536 | return data(); | 544 | return this->data(); |
| 537 | } | 545 | } |
| 538 | 546 | ||
| 539 | [[nodiscard]] T* end() noexcept { | 547 | [[nodiscard]] T* end() noexcept { |
| 540 | return data() + size; | 548 | return this->data() + this->size(); |
| 541 | } | 549 | } |
| 542 | 550 | ||
| 543 | [[nodiscard]] const T* end() const noexcept { | 551 | [[nodiscard]] const T* end() const noexcept { |
| 544 | return data() + size; | 552 | return this->data() + this->size(); |
| 545 | } | 553 | } |
| 546 | 554 | ||
| 547 | T& operator[](size_t index) noexcept { | 555 | T& operator[](size_t index) noexcept { |
| 548 | return data_span[index]; | 556 | return m_data_span[index]; |
| 549 | } | 557 | } |
| 550 | 558 | ||
| 551 | const T& operator[](size_t index) const noexcept { | 559 | const T& operator[](size_t index) const noexcept { |
| 552 | return data_span[index]; | 560 | return m_data_span[index]; |
| 553 | } | 561 | } |
| 554 | 562 | ||
| 555 | void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept { | 563 | void SetAddressAndSize(u64 addr, std::size_t size) noexcept { |
| 556 | addr = addr_; | 564 | m_addr = addr; |
| 557 | size = size_; | 565 | m_size = size; |
| 558 | addr_changed = true; | 566 | m_addr_changed = true; |
| 559 | } | 567 | } |
| 560 | 568 | ||
| 561 | std::span<T> Read(u64 addr_, std::size_t size_, | 569 | std::span<T> Read(u64 addr, std::size_t size, |
| 562 | Common::ScratchBuffer<T>* backup = nullptr) noexcept { | 570 | Common::ScratchBuffer<T>* backup = nullptr) noexcept { |
| 563 | addr = addr_; | 571 | m_addr = addr; |
| 564 | size = size_; | 572 | m_size = size; |
| 565 | if (size == 0) { | 573 | if (m_size == 0) { |
| 566 | is_data_copy = true; | 574 | m_is_data_copy = true; |
| 567 | return {}; | 575 | return {}; |
| 568 | } | 576 | } |
| 569 | 577 | ||
| 570 | if (TrySetSpan()) { | 578 | if (this->TrySetSpan()) { |
| 571 | if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 579 | if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 572 | memory.FlushRegion(addr, size * sizeof(T)); | 580 | m_memory.FlushRegion(m_addr, this->size_bytes()); |
| 573 | } | 581 | } |
| 574 | } else { | 582 | } else { |
| 575 | if (backup) { | 583 | if (backup) { |
| 576 | backup->resize_destructive(size); | 584 | backup->resize_destructive(this->size()); |
| 577 | data_span = *backup; | 585 | m_data_span = *backup; |
| 578 | } else { | 586 | } else { |
| 579 | data_copy.resize(size); | 587 | m_data_copy.resize(this->size()); |
| 580 | data_span = std::span(data_copy); | 588 | m_data_span = std::span(m_data_copy); |
| 581 | } | 589 | } |
| 582 | is_data_copy = true; | 590 | m_is_data_copy = true; |
| 583 | span_valid = true; | 591 | m_span_valid = true; |
| 584 | if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 592 | if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 585 | memory.ReadBlock(addr, data_span.data(), size * sizeof(T)); | 593 | m_memory.ReadBlock(m_addr, this->data(), this->size_bytes()); |
| 586 | } else { | 594 | } else { |
| 587 | memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T)); | 595 | m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes()); |
| 588 | } | 596 | } |
| 589 | } | 597 | } |
| 590 | return data_span; | 598 | return m_data_span; |
| 591 | } | 599 | } |
| 592 | 600 | ||
| 593 | void Write(std::span<T> write_data) noexcept { | 601 | void Write(std::span<T> write_data) noexcept { |
| 594 | if constexpr (FLAGS & GuestMemoryFlags::Cached) { | 602 | if constexpr (FLAGS & GuestMemoryFlags::Cached) { |
| 595 | memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T)); | 603 | m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes()); |
| 596 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 604 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 597 | memory.WriteBlock(addr, write_data.data(), size * sizeof(T)); | 605 | m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes()); |
| 598 | } else { | 606 | } else { |
| 599 | memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T)); | 607 | m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes()); |
| 600 | } | 608 | } |
| 601 | } | 609 | } |
| 602 | 610 | ||
| 603 | bool TrySetSpan() noexcept { | 611 | bool TrySetSpan() noexcept { |
| 604 | if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) { | 612 | if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) { |
| 605 | data_span = {reinterpret_cast<T*>(ptr), size}; | 613 | m_data_span = {reinterpret_cast<T*>(ptr), this->size()}; |
| 606 | span_valid = true; | 614 | m_span_valid = true; |
| 607 | return true; | 615 | return true; |
| 608 | } | 616 | } |
| 609 | return false; | 617 | return false; |
| @@ -611,36 +619,36 @@ public: | |||
| 611 | 619 | ||
| 612 | protected: | 620 | protected: |
| 613 | bool IsDataCopy() const noexcept { | 621 | bool IsDataCopy() const noexcept { |
| 614 | return is_data_copy; | 622 | return m_is_data_copy; |
| 615 | } | 623 | } |
| 616 | 624 | ||
| 617 | bool AddressChanged() const noexcept { | 625 | bool AddressChanged() const noexcept { |
| 618 | return addr_changed; | 626 | return m_addr_changed; |
| 619 | } | 627 | } |
| 620 | 628 | ||
| 621 | M& memory; | 629 | M& m_memory; |
| 622 | u64 addr; | 630 | u64 m_addr{}; |
| 623 | size_t size; | 631 | size_t m_size{}; |
| 624 | std::span<T> data_span{}; | 632 | std::span<T> m_data_span{}; |
| 625 | std::vector<T> data_copy; | 633 | std::vector<T> m_data_copy{}; |
| 626 | bool span_valid{false}; | 634 | bool m_span_valid{false}; |
| 627 | bool is_data_copy{false}; | 635 | bool m_is_data_copy{false}; |
| 628 | bool addr_changed{false}; | 636 | bool m_addr_changed{false}; |
| 629 | }; | 637 | }; |
| 630 | 638 | ||
| 631 | template <typename M, typename T, GuestMemoryFlags FLAGS> | 639 | template <typename M, typename T, GuestMemoryFlags FLAGS> |
| 632 | class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { | 640 | class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { |
| 633 | public: | 641 | public: |
| 634 | GuestMemoryScoped() = delete; | 642 | GuestMemoryScoped() = delete; |
| 635 | explicit GuestMemoryScoped(M& memory_, u64 addr_, std::size_t size_, | 643 | explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size, |
| 636 | Common::ScratchBuffer<T>* backup = nullptr) | 644 | Common::ScratchBuffer<T>* backup = nullptr) |
| 637 | : GuestMemory<M, T, FLAGS>(memory_, addr_, size_, backup) { | 645 | : GuestMemory<M, T, FLAGS>(memory, addr, size, backup) { |
| 638 | if constexpr (!(FLAGS & GuestMemoryFlags::Read)) { | 646 | if constexpr (!(FLAGS & GuestMemoryFlags::Read)) { |
| 639 | if (!this->TrySetSpan()) { | 647 | if (!this->TrySetSpan()) { |
| 640 | if (backup) { | 648 | if (backup) { |
| 641 | this->data_span = *backup; | 649 | this->m_data_span = *backup; |
| 642 | this->span_valid = true; | 650 | this->m_span_valid = true; |
| 643 | this->is_data_copy = true; | 651 | this->m_is_data_copy = true; |
| 644 | } | 652 | } |
| 645 | } | 653 | } |
| 646 | } | 654 | } |
| @@ -648,24 +656,21 @@ public: | |||
| 648 | 656 | ||
| 649 | ~GuestMemoryScoped() { | 657 | ~GuestMemoryScoped() { |
| 650 | if constexpr (FLAGS & GuestMemoryFlags::Write) { | 658 | if constexpr (FLAGS & GuestMemoryFlags::Write) { |
| 651 | if (this->size == 0) [[unlikely]] { | 659 | if (this->size() == 0) [[unlikely]] { |
| 652 | return; | 660 | return; |
| 653 | } | 661 | } |
| 654 | 662 | ||
| 655 | if (this->AddressChanged() || this->IsDataCopy()) { | 663 | if (this->AddressChanged() || this->IsDataCopy()) { |
| 656 | ASSERT(this->span_valid); | 664 | ASSERT(this->m_span_valid); |
| 657 | if constexpr (FLAGS & GuestMemoryFlags::Cached) { | 665 | if constexpr (FLAGS & GuestMemoryFlags::Cached) { |
| 658 | this->memory.WriteBlockCached(this->addr, this->data_span.data(), | 666 | this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes()); |
| 659 | this->size * sizeof(T)); | ||
| 660 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 667 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 661 | this->memory.WriteBlock(this->addr, this->data_span.data(), | 668 | this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes()); |
| 662 | this->size * sizeof(T)); | ||
| 663 | } else { | 669 | } else { |
| 664 | this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(), | 670 | this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes()); |
| 665 | this->size * sizeof(T)); | ||
| 666 | } | 671 | } |
| 667 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { | 672 | } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { |
| 668 | this->memory.InvalidateRegion(this->addr, this->size * sizeof(T)); | 673 | this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes()); |
| 669 | } | 674 | } |
| 670 | } | 675 | } |
| 671 | } | 676 | } |
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index 7b52f61a7..a06e99166 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp | |||
| @@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { | |||
| 154 | return {}; | 154 | return {}; |
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); | 157 | const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10)); |
| 158 | out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = | 158 | out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = |
| 159 | value; | 159 | value; |
| 160 | 160 | ||
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index b5b3e7eda..ed875d444 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp | |||
| @@ -117,8 +117,8 @@ json GetProcessorStateDataAuto(Core::System& system) { | |||
| 117 | arm.SaveContext(context); | 117 | arm.SaveContext(context); |
| 118 | 118 | ||
| 119 | return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", | 119 | return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", |
| 120 | GetInteger(process->GetPageTable().GetCodeRegionStart()), | 120 | GetInteger(process->GetEntryPoint()), context.sp, context.pc, |
| 121 | context.sp, context.pc, context.pstate, context.cpu_registers); | 121 | context.pstate, context.cpu_registers); |
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | json GetBacktraceData(Core::System& system) { | 124 | json GetBacktraceData(Core::System& system) { |
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 62b3f6636..c26179e03 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | #include "common/logging/log.h" | 14 | #include "common/logging/log.h" |
| 15 | 15 | ||
| 16 | #include "common/settings.h" | 16 | #include "common/settings.h" |
| 17 | #include "common/settings_enums.h" | ||
| 17 | #include "core/file_sys/control_metadata.h" | 18 | #include "core/file_sys/control_metadata.h" |
| 18 | #include "core/file_sys/patch_manager.h" | 19 | #include "core/file_sys/patch_manager.h" |
| 19 | #include "core/loader/loader.h" | 20 | #include "core/loader/loader.h" |
| @@ -275,7 +276,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, | |||
| 275 | static_cast<u32>(Settings::values.shader_backend.GetValue())); | 276 | static_cast<u32>(Settings::values.shader_backend.GetValue())); |
| 276 | AddField(field_type, "Renderer_UseAsynchronousShaders", | 277 | AddField(field_type, "Renderer_UseAsynchronousShaders", |
| 277 | Settings::values.use_asynchronous_shaders.GetValue()); | 278 | Settings::values.use_asynchronous_shaders.GetValue()); |
| 278 | AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue()); | 279 | AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode()); |
| 279 | } | 280 | } |
| 280 | 281 | ||
| 281 | bool TelemetrySession::SubmitTestcase() { | 282 | bool TelemetrySession::SubmitTestcase() { |
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp new file mode 100644 index 000000000..44d24822a --- /dev/null +++ b/src/core/tools/renderdoc.cpp | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <renderdoc_app.h> | ||
| 5 | |||
| 6 | #include "common/assert.h" | ||
| 7 | #include "common/dynamic_library.h" | ||
| 8 | #include "core/tools/renderdoc.h" | ||
| 9 | |||
| 10 | #ifdef WIN32 | ||
| 11 | #include <windows.h> | ||
| 12 | #else | ||
| 13 | #include <dlfcn.h> | ||
| 14 | #endif | ||
| 15 | |||
| 16 | namespace Tools { | ||
| 17 | |||
| 18 | RenderdocAPI::RenderdocAPI() { | ||
| 19 | #ifdef WIN32 | ||
| 20 | if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) { | ||
| 21 | const auto RENDERDOC_GetAPI = | ||
| 22 | reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI")); | ||
| 23 | const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); | ||
| 24 | ASSERT(ret == 1); | ||
| 25 | } | ||
| 26 | #else | ||
| 27 | #ifdef ANDROID | ||
| 28 | static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so"; | ||
| 29 | #else | ||
| 30 | static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so"; | ||
| 31 | #endif | ||
| 32 | if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) { | ||
| 33 | const auto RENDERDOC_GetAPI = | ||
| 34 | reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI")); | ||
| 35 | const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); | ||
| 36 | ASSERT(ret == 1); | ||
| 37 | } | ||
| 38 | #endif | ||
| 39 | } | ||
| 40 | |||
| 41 | RenderdocAPI::~RenderdocAPI() = default; | ||
| 42 | |||
| 43 | void RenderdocAPI::ToggleCapture() { | ||
| 44 | if (!rdoc_api) [[unlikely]] { | ||
| 45 | return; | ||
| 46 | } | ||
| 47 | if (!is_capturing) { | ||
| 48 | rdoc_api->StartFrameCapture(NULL, NULL); | ||
| 49 | } else { | ||
| 50 | rdoc_api->EndFrameCapture(NULL, NULL); | ||
| 51 | } | ||
| 52 | is_capturing = !is_capturing; | ||
| 53 | } | ||
| 54 | |||
| 55 | } // namespace Tools | ||
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h new file mode 100644 index 000000000..0e5e43da5 --- /dev/null +++ b/src/core/tools/renderdoc.h | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | struct RENDERDOC_API_1_6_0; | ||
| 7 | |||
| 8 | namespace Tools { | ||
| 9 | |||
| 10 | class RenderdocAPI { | ||
| 11 | public: | ||
| 12 | explicit RenderdocAPI(); | ||
| 13 | ~RenderdocAPI(); | ||
| 14 | |||
| 15 | void ToggleCapture(); | ||
| 16 | |||
| 17 | private: | ||
| 18 | RENDERDOC_API_1_6_0* rdoc_api{}; | ||
| 19 | bool is_capturing{false}; | ||
| 20 | }; | ||
| 21 | |||
| 22 | } // namespace Tools | ||