summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/CMakeLists.txt70
-rw-r--r--src/core/core.cpp44
-rw-r--r--src/core/core.h20
-rw-r--r--src/core/crypto/key_manager.cpp234
-rw-r--r--src/core/crypto/key_manager.h59
-rw-r--r--src/core/debugger/gdbstub.cpp17
-rw-r--r--src/core/file_sys/card_image.cpp54
-rw-r--r--src/core/file_sys/card_image.h1
-rw-r--r--src/core/file_sys/content_archive.cpp586
-rw-r--r--src/core/file_sys/content_archive.h66
-rw-r--r--src/core/file_sys/errors.h70
-rw-r--r--src/core/file_sys/fssystem/fs_i_storage.h58
-rw-r--r--src/core/file_sys/fssystem/fs_types.h46
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp251
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h114
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp129
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h43
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp112
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_xts_storage.h42
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h146
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp204
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h21
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree.cpp598
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree.h416
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h170
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h110
-rw-r--r--src/core/file_sys/fssystem/fssystem_compressed_storage.h963
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_common.h43
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_configuration.cpp36
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_configuration.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp65
-rw-r--r--src/core/file_sys/fssystem/fssystem_crypto_configuration.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp127
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h164
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp80
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h44
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.cpp119
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.h294
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp30
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h42
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp91
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h65
-rw-r--r--src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h61
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp1351
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h364
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_header.cpp20
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_header.h338
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_reader.cpp531
-rw-r--r--src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp61
-rw-r--r--src/core/file_sys/fssystem/fssystem_pooled_buffer.h95
-rw-r--r--src/core/file_sys/fssystem/fssystem_sparse_storage.cpp39
-rw-r--r--src/core/file_sys/fssystem/fssystem_sparse_storage.h72
-rw-r--r--src/core/file_sys/fssystem/fssystem_switch_storage.h80
-rw-r--r--src/core/file_sys/fssystem/fssystem_utility.cpp27
-rw-r--r--src/core/file_sys/fssystem/fssystem_utility.h12
-rw-r--r--src/core/file_sys/ips_layer.cpp4
-rw-r--r--src/core/file_sys/nca_metadata.cpp4
-rw-r--r--src/core/file_sys/nca_metadata.h1
-rw-r--r--src/core/file_sys/nca_patch.cpp217
-rw-r--r--src/core/file_sys/nca_patch.h145
-rw-r--r--src/core/file_sys/patch_manager.cpp52
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/file_sys/registered_cache.cpp73
-rw-r--r--src/core/file_sys/registered_cache.h5
-rw-r--r--src/core/file_sys/romfs_factory.cpp9
-rw-r--r--src/core/file_sys/romfs_factory.h11
-rw-r--r--src/core/file_sys/submission_package.cpp39
-rw-r--r--src/core/file_sys/submission_package.h1
-rw-r--r--src/core/frontend/applets/controller.cpp4
-rw-r--r--src/core/frontend/framebuffer_layout.cpp3
-rw-r--r--src/core/hid/hid_core.cpp8
-rw-r--r--src/core/hid/hid_core.h7
-rw-r--r--src/core/hid/hid_types.h26
-rw-r--r--src/core/hle/kernel/k_capabilities.cpp1
-rw-r--r--src/core/hle/kernel/k_hardware_timer.h9
-rw-r--r--src/core/hle/kernel/k_process.cpp30
-rw-r--r--src/core/hle/kernel/k_process.h15
-rw-r--r--src/core/hle/kernel/k_resource_limit.cpp11
-rw-r--r--src/core/hle/kernel/k_resource_limit.h3
-rw-r--r--src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h2
-rw-r--r--src/core/hle/kernel/kernel.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_address_arbiter.cpp3
-rw-r--r--src/core/hle/kernel/svc/svc_condition_variable.cpp3
-rw-r--r--src/core/hle/kernel/svc/svc_debug_string.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_exception.cpp6
-rw-r--r--src/core/hle/kernel/svc/svc_ipc.cpp20
-rw-r--r--src/core/hle/kernel/svc/svc_resource_limit.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_synchronization.cpp16
-rw-r--r--src/core/hle/kernel/svc/svc_thread.cpp29
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/am/am.cpp84
-rw-r--r--src/core/hle/service/am/am.h2
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp11
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit_types.h3
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.cpp2
-rw-r--r--src/core/hle/service/apm/apm_controller.cpp4
-rw-r--r--src/core/hle/service/audio/audin_u.cpp4
-rw-r--r--src/core/hle/service/audio/audout_u.cpp2
-rw-r--r--src/core/hle/service/audio/audren_u.cpp2
-rw-r--r--src/core/hle/service/audio/audren_u.h2
-rw-r--r--src/core/hle/service/audio/errors.h12
-rw-r--r--src/core/hle/service/audio/hwopus.cpp697
-rw-r--r--src/core/hle/service/audio/hwopus.h21
-rw-r--r--src/core/hle/service/es/es.cpp10
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp16
-rw-r--r--src/core/hle/service/filesystem/filesystem.h3
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp68
-rw-r--r--src/core/hle/service/hid/controllers/npad.h22
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h16
-rw-r--r--src/core/hle/service/hid/hid.cpp95
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/mii/mii.cpp139
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp717
-rw-r--r--src/core/hle/service/mii/mii_manager.h42
-rw-r--r--src/core/hle/service/mii/mii_result.h20
-rw-r--r--src/core/hle/service/mii/mii_types.h691
-rw-r--r--src/core/hle/service/mii/mii_util.h59
-rw-r--r--src/core/hle/service/mii/raw_data.h26
-rw-r--r--src/core/hle/service/mii/types.h553
-rw-r--r--src/core/hle/service/mii/types/char_info.cpp482
-rw-r--r--src/core/hle/service/mii/types/char_info.h137
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp601
-rw-r--r--src/core/hle/service/mii/types/core_data.h216
-rw-r--r--src/core/hle/service/mii/types/raw_data.cpp (renamed from src/core/hle/service/mii/raw_data.cpp)1400
-rw-r--r--src/core/hle/service/mii/types/raw_data.h73
-rw-r--r--src/core/hle/service/mii/types/store_data.cpp643
-rw-r--r--src/core/hle/service/mii/types/store_data.h145
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp241
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.h160
-rw-r--r--src/core/hle/service/nfc/common/device.cpp39
-rw-r--r--src/core/hle/service/nfp/nfp_types.h6
-rw-r--r--src/core/hle/service/ngc/ngc.cpp150
-rw-r--r--src/core/hle/service/ngc/ngc.h (renamed from src/core/hle/service/ngct/ngct.h)4
-rw-r--r--src/core/hle/service/ngct/ngct.cpp62
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.cpp4
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp1
-rw-r--r--src/core/hle/service/nvnflinger/window.h1
-rw-r--r--src/core/hle/service/service.cpp4
-rw-r--r--src/core/hle/service/service.h4
-rw-r--r--src/core/hle/service/sockets/bsd.cpp7
-rw-r--r--src/core/hle/service/sockets/bsd.h3
-rw-r--r--src/core/hle/service/sockets/nsd.cpp11
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp12
-rw-r--r--src/core/hle/service/sockets/sockets.h2
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp4
-rw-r--r--src/core/hle/service/ssl/ssl.cpp10
-rw-r--r--src/core/hle/service/ssl/ssl_backend_openssl.cpp3
-rw-r--r--src/core/hle/service/ssl/ssl_backend_schannel.cpp28
-rw-r--r--src/core/hle/service/ssl/ssl_backend_securetransport.cpp2
-rw-r--r--src/core/hle/service/vi/vi.cpp2
-rw-r--r--src/core/internal_network/network.cpp132
-rw-r--r--src/core/internal_network/network.h5
-rw-r--r--src/core/internal_network/network_interface.cpp9
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp10
-rw-r--r--src/core/loader/deconstructed_rom_directory.h4
-rw-r--r--src/core/loader/kip.cpp5
-rw-r--r--src/core/loader/loader.cpp8
-rw-r--r--src/core/loader/loader.h22
-rw-r--r--src/core/loader/nax.cpp4
-rw-r--r--src/core/loader/nax.h1
-rw-r--r--src/core/loader/nca.cpp104
-rw-r--r--src/core/loader/nca.h3
-rw-r--r--src/core/loader/nro.cpp5
-rw-r--r--src/core/loader/nso.cpp7
-rw-r--r--src/core/loader/nsp.cpp43
-rw-r--r--src/core/loader/nsp.h3
-rw-r--r--src/core/loader/xci.cpp38
-rw-r--r--src/core/loader/xci.h3
-rw-r--r--src/core/memory.h125
-rw-r--r--src/core/memory/cheat_engine.cpp2
-rw-r--r--src/core/reporter.cpp4
-rw-r--r--src/core/telemetry_session.cpp3
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
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
818if (MSVC) 871if (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()
836create_target_directory_groups(core) 890create_target_directory_groups(core)
837 891
838target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) 892target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
839target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) 893target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc)
840if (MINGW) 894if (MINGW)
841 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 895 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
842endif() 896endif()
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
564System::System() : impl{std::make_unique<Impl>(*this)} {} 578System::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
946void System::SetExitLock(bool locked) { 960void System::SetExitLocked(bool locked) {
947 impl->exit_lock = locked; 961 impl->exit_locked = locked;
962}
963
964bool System::GetExitLocked() const {
965 return impl->exit_locked;
948} 966}
949 967
950bool System::GetExitLock() const { 968void System::SetExitRequested(bool requested) {
951 return impl->exit_lock; 969 impl->exit_requested = requested;
970}
971
972bool System::GetExitRequested() const {
973 return impl->exit_requested;
952} 974}
953 975
954void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { 976void 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
1034Tools::RenderdocAPI& System::GetRenderdocAPI() {
1035 return *impl->renderdoc_api;
1036}
1037
1012void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { 1038void 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
1054std::deque<std::vector<u8>>& System::GetUserChannel() {
1055 return impl->user_channel;
1056}
1057
1028void System::RegisterExitCallback(ExitCallback&& callback) { 1058void 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 {
101class RoomNetwork; 102class RoomNetwork;
102} 103}
103 104
105namespace Tools {
106class RenderdocAPI;
107}
108
104namespace Core { 109namespace Core {
105 110
106class ARM_Interface; 111class 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 {
35namespace { 35namespace {
36 36
37constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; 37constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
38constexpr u64 FULL_TICKET_SIZE = 0x400;
39 38
40using Common::AsArray; 39using Common::AsArray;
41 40
@@ -156,6 +155,10 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
156 UNREACHABLE(); 155 UNREACHABLE();
157} 156}
158 157
158bool Ticket::IsValid() const {
159 return !std::holds_alternative<std::monostate>(data);
160}
161
159SignatureType Ticket::GetSignatureType() const { 162SignatureType 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
216Ticket 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
228Ticket 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
213Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { 264Key128 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
293RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { 344void 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
315Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { 366Key128 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
506std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, 559std::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
571KeyManager::KeyManager() { 637KeyManager::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
1116void KeyManager::PopulateTickets() { 1191void 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
1294bool KeyManager::AddTicketCommon(Ticket raw) { 1350bool 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
1313bool 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
30namespace Core::Crypto { 31namespace Core::Crypto {
31 32
32constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
33
34using Key128 = std::array<u8, 0x10>; 33using Key128 = std::array<u8, 0x10>;
35using Key256 = std::array<u8, 0x20>; 34using Key256 = std::array<u8, 0x20>;
36using SHA256Hash = std::array<u8, 0x20>; 35using 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};
84static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size.");
85 85
86struct RSA2048Ticket { 86struct 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};
92static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size.");
92 93
93struct ECDSATicket { 94struct 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};
100static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size.");
99 101
100struct Ticket { 102struct 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
111static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); 137static_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
302Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); 332Key128 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
312std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); 342std::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)
316std::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
315Loader::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
319u8 XCI::GetFormatVersion() { 353u8 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
129private: 129private:
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
20namespace FileSys { 23namespace FileSys {
21 24
22// Media offsets in headers are stored divided by 512. Mult. by this to get real offset. 25static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
23constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; 26 return std::max<u8>(key_generation, 1) - 1;
24
25constexpr u64 SECTION_HEADER_SIZE = 0x200;
26constexpr u64 SECTION_HEADER_OFFSET = 0x400;
27
28constexpr u32 IVFC_MAX_LEVEL = 6;
29
30enum class NCASectionFilesystemType : u8 {
31 PFS0 = 0x2,
32 ROMFS = 0x3,
33};
34
35struct IVFCLevel {
36 u64_le offset;
37 u64_le size;
38 u32_le block_size;
39 u32_le reserved;
40};
41static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
42
43struct 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};
50static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
51
52struct NCASectionHeaderBlock {
53 INSERT_PADDING_BYTES_NOINIT(3);
54 NCASectionFilesystemType filesystem_type;
55 NCASectionCryptoType crypto_type;
56 INSERT_PADDING_BYTES_NOINIT(3);
57};
58static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
59
60struct NCABucketInfo {
61 u64 table_offset;
62 u64 table_size;
63 std::array<u8, 0x10> table_header;
64};
65static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size.");
66
67struct NCASparseInfo {
68 NCABucketInfo bucket;
69 u64 physical_offset;
70 u16 generation;
71 INSERT_PADDING_BYTES_NOINIT(0x6);
72};
73static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size.");
74
75struct NCACompressionInfo {
76 NCABucketInfo bucket;
77 INSERT_PADDING_BYTES_NOINIT(0x8);
78};
79static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size.");
80
81struct 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};
89static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
90
91struct 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};
102static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
103
104struct RomFSSuperblock {
105 NCASectionHeaderBlock header_block;
106 IVFCHeader ivfc;
107 INSERT_PADDING_BYTES_NOINIT(0x118);
108};
109static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
110
111struct 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};
119static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
120
121struct 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};
129static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
130
131union NCASectionHeader {
132 NCASectionRaw raw{};
133 PFS0Superblock pfs0;
134 RomFSSuperblock romfs;
135 BKTRSuperblock bktr;
136};
137static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
138
139static 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
144NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) 29NCA::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{}) {
176NCA::~NCA() = default; 58 // External decryption key required; provide it here.
177 59 u128 rights_id_u128;
178bool 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
192bool 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
225std::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
247bool 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
279bool 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
403bool 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
438u8 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
447std::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
478std::optional<Core::Crypto::Key128> NCA::GetTitlekey() { 126NCA::~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
506VirtualFile 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
555Loader::ResultStatus NCA::GetStatus() const { 128Loader::ResultStatus NCA::GetStatus() const {
556 return status; 129 return status;
@@ -579,21 +152,24 @@ VirtualDir NCA::GetParentDirectory() const {
579} 152}
580 153
581NCAContentType NCA::GetType() const { 154NCAContentType NCA::GetType() const {
582 return header.content_type; 155 return static_cast<NCAContentType>(reader->GetContentType());
583} 156}
584 157
585u64 NCA::GetTitleId() const { 158u64 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
591std::array<u8, 16> NCA::GetRightsId() const { 165RightsId 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
595u32 NCA::GetSDKVersion() const { 171u32 NCA::GetSDKVersion() const {
596 return header.sdk_version; 172 return reader->GetSdkAddonVersion();
597} 173}
598 174
599bool NCA::IsUpdate() const { 175bool NCA::IsUpdate() const {
@@ -612,10 +188,6 @@ VirtualFile NCA::GetBaseFile() const {
612 return file; 188 return file;
613} 189}
614 190
615u64 NCA::GetBaseIVFCOffset() const {
616 return ivfc_offset;
617}
618
619VirtualDir NCA::GetLogoPartition() const { 191VirtualDir 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
22namespace FileSys { 22namespace FileSys {
23 23
24union NCASectionHeader; 24class NcaReader;
25 25
26/// Describes the type of content within an NCA archive. 26/// Describes the type of content within an NCA archive.
27enum class NCAContentType : u8 { 27enum class NCAContentType : u8 {
@@ -45,41 +45,7 @@ enum class NCAContentType : u8 {
45 PublicData = 5, 45 PublicData = 5,
46}; 46};
47 47
48enum class NCASectionCryptoType : u8 { 48using RightsId = std::array<u8, 0x10>;
49 NONE = 1,
50 XTS = 2,
51 CTR = 3,
52 BKTR = 4,
53};
54
55struct NCASectionTableEntry {
56 u32_le media_offset;
57 u32_le media_end_offset;
58 INSERT_PADDING_BYTES(0x8);
59};
60static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
61
62struct 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};
82static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
83 49
84inline bool IsDirectoryExeFS(const VirtualDir& pfs) { 50inline 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.
98class NCA : public ReadOnlyVfsDirectory { 64class NCA : public ReadOnlyVfsDirectory {
99public: 65public:
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
127private: 89private:
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};
17constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; 17constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
18constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; 18constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
19 19
20constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50};
21constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001};
22constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002};
23constexpr Result ResultOutOfRange{ErrorModule::FS, 3005};
24constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294};
25constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341};
26constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363};
27constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399};
28constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412};
29constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422};
30constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423};
31constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012};
32constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021};
33constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022};
34constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023};
35constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024};
36constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032};
37constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033};
38constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034};
39constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035};
40constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036};
41constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037};
42constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038};
43constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039};
44constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084};
45constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085};
46constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086};
47constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087};
48constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088};
49constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089};
50constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090};
51constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091};
52constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091};
53constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509};
54constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510};
55constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511};
56constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517};
57constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520};
58constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521};
59constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522};
60constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523};
61constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524};
62constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525};
63constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526};
64constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528};
65constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529};
66constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530};
67constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532};
68constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533};
69constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534};
70constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535};
71constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541};
72constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542};
73constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543};
74constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547};
75constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548};
76constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549};
77constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
78constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
79constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
80constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
81constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
82constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
83constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
84constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
85constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
86constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
87constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
88constexpr 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
10namespace FileSys {
11
12class IStorage : public VfsFile {
13public:
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
47class IReadOnlyStorage : public IStorage {
48public:
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
8namespace FileSys {
9
10struct 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
33struct HashSalt {
34 static constexpr size_t Size = 32;
35
36 std::array<u8, Size> value;
37};
38static_assert(std::is_trivial_v<HashSalt>);
39static_assert(sizeof(HashSalt) == HashSalt::Size);
40
41constexpr inline size_t IntegrityMinLayerCount = 2;
42constexpr inline size_t IntegrityMaxLayerCount = 7;
43constexpr inline size_t IntegrityLayerCountSave = 5;
44constexpr 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
9namespace FileSys {
10
11namespace {
12
13class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
14public:
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
22Result 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
29Result 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
55Result 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
84void AesCtrCounterExtendedStorage::Finalize() {
85 if (this->IsInitialized()) {
86 m_table.Finalize();
87 m_data_storage = VirtualFile();
88 }
89}
90
91Result 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
153size_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
242void 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
12namespace FileSys {
13
14using namespace Common::Literals;
15
16class AesCtrCounterExtendedStorage : public IReadOnlyStorage {
17 YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage);
18 YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage);
19
20public:
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
58public:
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
73public:
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
101private:
102 Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage,
103 VirtualFile table_storage);
104
105private:
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
10namespace FileSys {
11
12void 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
23AesCtrStorage::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
38size_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
66size_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
125size_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
14namespace FileSys {
15
16class AesCtrStorage : public IStorage {
17 YUZU_NON_COPYABLE(AesCtrStorage);
18 YUZU_NON_MOVEABLE(AesCtrStorage);
19
20public:
21 static constexpr size_t BlockSize = 0x10;
22 static constexpr size_t KeySize = 0x10;
23 static constexpr size_t IvSize = 0x10;
24
25public:
26 static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset);
27
28public:
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
36private:
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
11namespace FileSys {
12
13void 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
23AesXtsStorage::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
41size_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
108size_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
12namespace FileSys {
13
14class AesXtsStorage : public IReadOnlyStorage {
15 YUZU_NON_COPYABLE(AesXtsStorage);
16 YUZU_NON_MOVEABLE(AesXtsStorage);
17
18public:
19 static constexpr size_t AesBlockSize = 0x10;
20 static constexpr size_t KeySize = 0x20;
21 static constexpr size_t IvSize = 0x10;
22
23public:
24 static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size);
25
26public:
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
33private:
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
12namespace FileSys {
13
14template <size_t DataAlign_, size_t BufferAlign_>
15class AlignmentMatchingStorage : public IStorage {
16 YUZU_NON_COPYABLE(AlignmentMatchingStorage);
17 YUZU_NON_MOVEABLE(AlignmentMatchingStorage);
18
19public:
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
28private:
29 VirtualFile m_base_storage;
30 s64 m_base_storage_size;
31
32public:
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
78template <size_t BufferAlign_>
79class AlignmentMatchingStoragePooledBuffer : public IStorage {
80 YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer);
81 YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer);
82
83public:
84 static constexpr size_t BufferAlign = BufferAlign_;
85
86 static_assert(Common::IsPowerOfTwo(BufferAlign));
87
88private:
89 VirtualFile m_base_storage;
90 s64 m_base_storage_size;
91 size_t m_data_align;
92
93public:
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
7namespace FileSys {
8
9namespace {
10
11template <typename T>
12constexpr size_t GetRoundDownDifference(T x, size_t align) {
13 return static_cast<size_t>(x - Common::AlignDown(x, align));
14}
15
16template <typename T>
17constexpr size_t GetRoundUpDifference(T x, size_t align) {
18 return static_cast<size_t>(Common::AlignUp(x, align) - x);
19}
20
21template <typename T>
22size_t GetRoundUpDifference(T* x, size_t align) {
23 return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align);
24}
25
26} // namespace
27
28size_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
123size_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
9namespace FileSys {
10
11class AlignmentMatchingStorageImpl {
12public:
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
9namespace FileSys {
10
11namespace {
12
13using Node = impl::BucketTreeNode<const s64*>;
14static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader));
15static_assert(std::is_trivial_v<Node>);
16
17constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader);
18
19class StorageNode {
20private:
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
84private:
85 const Offset m_start;
86 const s32 m_count;
87 s32 m_index;
88
89public:
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
147void 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
156Result 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
163Result 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
175Result 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
233void 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
246void 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
263Result 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
278Result BucketTree::InvalidateCache() {
279 // Reset our offsets.
280 m_offset_cache.is_initialized = false;
281
282 R_SUCCEED();
283}
284
285Result 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
321Result 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
336Result 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
374Result 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
414Result 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
463Result 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
476Result 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
501Result 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
523Result 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
535Result 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
568Result 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
16namespace FileSys {
17
18using namespace Common::Literals;
19
20class BucketTree {
21 YUZU_NON_COPYABLE(BucketTree);
22 YUZU_NON_MOVEABLE(BucketTree);
23
24public:
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
31public:
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
124private:
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
192private:
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
221public:
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
257public:
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
291private:
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
302private:
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
320private:
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
332class BucketTree::Visitor {
333 YUZU_NON_COPYABLE(Visitor);
334 YUZU_NON_MOVEABLE(Visitor);
335
336public:
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
379private:
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
393private:
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
11namespace FileSys {
12
13template <typename EntryType>
14Result 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
147template <typename EntryType>
148Result 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
8namespace FileSys::impl {
9
10class SafeValue {
11public:
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
39template <typename IteratorType>
40struct 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
98constexpr 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
104constexpr 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
15namespace FileSys {
16
17using namespace Common::Literals;
18
19class CompressedStorage : public IReadOnlyStorage {
20 YUZU_NON_COPYABLE(CompressedStorage);
21 YUZU_NON_MOVEABLE(CompressedStorage);
22
23public:
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
39public:
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
48private:
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
898public:
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
943public:
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
958private:
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
8namespace FileSys {
9
10enum class CompressionType : u8 {
11 None = 0,
12 Zeros = 1,
13 Two = 2,
14 Lz4 = 3,
15 Unknown = 4,
16};
17
18using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t);
19using GetDecompressorFunction = DecompressorFunction (*)(CompressionType);
20
21constexpr s64 CompressionBlockAlignment = 0x10;
22
23namespace CompressionTypeUtility {
24
25constexpr bool IsBlockAlignmentRequired(CompressionType type) {
26 return type != CompressionType::None && type != CompressionType::Zeros;
27}
28
29constexpr bool IsDataStorageAccessRequired(CompressionType type) {
30 return type != CompressionType::Zeros;
31}
32
33constexpr bool IsRandomAccessible(CompressionType type) {
34 return type == CompressionType::None;
35}
36
37constexpr 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
7namespace FileSys {
8
9namespace {
10
11Result 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
17constexpr 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
28const 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
8namespace FileSys {
9
10const 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
8namespace FileSys {
9
10namespace {
11
12void 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
49const 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
8namespace FileSys {
9
10const 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
7namespace FileSys {
8
9HierarchicalIntegrityVerificationStorage::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
16Result 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
93void 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
106size_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
123size_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
13namespace FileSys {
14
15struct HierarchicalIntegrityVerificationLevelInformation {
16 Int64 offset;
17 Int64 size;
18 s32 block_order;
19 std::array<u8, 4> reserved;
20};
21static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>);
22static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18);
23static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4);
24
25struct 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};
42static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>);
43
44struct HierarchicalIntegrityVerificationMetaInformation {
45 u32 magic;
46 u32 version;
47 u32 master_hash_size;
48 HierarchicalIntegrityVerificationInformation level_hash_info;
49};
50static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>);
51
52struct HierarchicalIntegrityVerificationSizeSet {
53 s64 control_size;
54 s64 master_hash_size;
55 std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes;
56};
57static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>);
58
59class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage {
60 YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage);
61 YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage);
62
63public:
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
110public:
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
138public:
139 static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) {
140 return static_cast<s8>(16 + max_layers - 2);
141 }
142
143protected:
144 static constexpr s64 HashSize = 256 / 8;
145 static constexpr size_t MaxLayers = IntegrityMaxLayerCount;
146
147private:
148 static GenerateRandomFunction s_generate_random;
149
150 static void SetGenerateRandomFunction(GenerateRandomFunction func) {
151 s_generate_random = func;
152 }
153
154private:
155 friend struct HierarchicalIntegrityVerificationMetaInformation;
156
157private:
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
8namespace FileSys {
9
10namespace {
11
12s32 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
25Result 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
67size_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
12namespace FileSys {
13
14class HierarchicalSha256Storage : public IReadOnlyStorage {
15 YUZU_NON_COPYABLE(HierarchicalSha256Storage);
16 YUZU_NON_MOVEABLE(HierarchicalSha256Storage);
17
18public:
19 static constexpr s32 LayerCount = 3;
20 static constexpr size_t HashSize = 256 / 8;
21
22public:
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
34private:
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
7namespace FileSys {
8
9Result 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
28void 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
37Result 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
99size_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
13namespace FileSys {
14
15class IndirectStorage : public IReadOnlyStorage {
16 YUZU_NON_COPYABLE(IndirectStorage);
17 YUZU_NON_MOVEABLE(IndirectStorage);
18
19public:
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
64public:
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
105public:
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
118protected:
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
131private:
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
151private:
152 mutable BucketTree m_table;
153 std::array<VirtualFile, StorageCount> m_data_storage;
154};
155
156template <bool ContinuousCheck, bool RangeCheck, typename F>
157Result 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
6namespace FileSys {
7
8Result 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
26void 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
10namespace FileSys {
11
12constexpr inline size_t IntegrityLayerCountRomFs = 7;
13constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB;
14
15class IntegrityRomFsStorage : public IReadOnlyStorage {
16public:
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
36private:
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
7namespace FileSys {
8
9constexpr 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
14void 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
45void IntegrityVerificationStorage::Finalize() {
46 m_hash_storage = VirtualFile();
47 m_data_storage = VirtualFile();
48}
49
50size_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
87size_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
11namespace FileSys {
12
13class IntegrityVerificationStorage : public IReadOnlyStorage {
14 YUZU_NON_COPYABLE(IntegrityVerificationStorage);
15 YUZU_NON_MOVEABLE(IntegrityVerificationStorage);
16
17public:
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
25public:
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
44private:
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
55private:
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
8namespace FileSys {
9
10class MemoryResourceBufferHoldStorage : public IStorage {
11 YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage);
12 YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage);
13
14public:
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
33public:
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
55private:
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
20namespace FileSys {
21
22namespace {
23
24constexpr inline s32 IntegrityDataCacheCount = 24;
25constexpr inline s32 IntegrityHashCacheCount = 8;
26
27constexpr inline s32 IntegrityDataCacheCountForMeta = 16;
28constexpr inline s32 IntegrityHashCacheCountForMeta = 2;
29
30class SharedNcaBodyStorage : public IReadOnlyStorage {
31 YUZU_NON_COPYABLE(SharedNcaBodyStorage);
32 YUZU_NON_MOVEABLE(SharedNcaBodyStorage);
33
34private:
35 VirtualFile m_storage;
36 std::shared_ptr<NcaReader> m_nca_reader;
37
38public:
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
58inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) {
59 return static_cast<s64>(reader.GetFsOffset(fs_index));
60}
61
62inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) {
63 return static_cast<s64>(reader.GetFsEndOffset(fs_index));
64}
65
66using Sha256DataRegion = NcaFsHeader::Region;
67using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo;
68using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation;
69
70} // namespace
71
72Result 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
79Result 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
292Result 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
328Result 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
397Result 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
418Result 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
456Result 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
484Result 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
523Result 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
568Result 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
635Result 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
702Result 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
776Result 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
835Result 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
925Result 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
957Result 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
1014Result 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
1096Result 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
1160Result 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
1169Result 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
1208Result 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
1271Result 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
1292Result 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
1300Result 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
10namespace FileSys {
11
12class CompressedStorage;
13class AesCtrCounterExtendedStorage;
14class IndirectStorage;
15class SparseStorage;
16
17struct NcaCryptoConfiguration;
18
19using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key,
20 size_t src_key_size, s32 key_type);
21using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data,
22 size_t data_size, u8 generation);
23
24struct 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};
52static_assert(std::is_trivial_v<NcaCryptoConfiguration>);
53
54struct NcaCompressionConfiguration {
55 GetDecompressorFunction get_decompressor;
56};
57static_assert(std::is_trivial_v<NcaCompressionConfiguration>);
58
59constexpr inline s32 KeyAreaEncryptionKeyCount =
60 NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount *
61 NcaCryptoConfiguration::KeyGenerationMax;
62
63enum 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
74constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) {
75 return key_type < 0;
76}
77
78constexpr 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
90class NcaReader {
91 YUZU_NON_COPYABLE(NcaReader);
92 YUZU_NON_MOVEABLE(NcaReader);
93
94public:
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
141private:
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
156class NcaFsHeaderReader {
157 YUZU_NON_COPYABLE(NcaFsHeaderReader);
158 YUZU_NON_MOVEABLE(NcaFsHeaderReader);
159
160public:
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
204private:
205 NcaFsHeader m_data;
206 s32 m_fs_index;
207};
208
209class NcaFileSystemDriver {
210 YUZU_NON_COPYABLE(NcaFileSystemDriver);
211 YUZU_NON_MOVEABLE(NcaFileSystemDriver);
212
213public:
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
237private:
238 enum class AlignmentStorageRequirement {
239 CacheBlockSize = 0,
240 None = 1,
241 };
242
243public:
244 static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader,
245 s32 fs_index);
246
247public:
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
269public:
270 Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
271 VirtualFile raw_storage, StorageContext* ctx);
272
273private:
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
353public:
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
359private:
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
6namespace FileSys {
7
8u8 NcaHeader::GetProperKeyGeneration() const {
9 return std::max(this->key_generation, this->key_generation_2);
10}
11
12bool NcaPatchInfo::HasIndirectTable() const {
13 return this->indirect_size != 0;
14}
15
16bool 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
13namespace FileSys {
14
15using namespace Common::Literals;
16
17struct Hash {
18 static constexpr std::size_t Size = 256 / 8;
19 std::array<u8, Size> value;
20};
21static_assert(sizeof(Hash) == Hash::Size);
22static_assert(std::is_trivial_v<Hash>);
23
24using NcaDigest = Hash;
25
26struct 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};
122static_assert(sizeof(NcaHeader) == NcaHeader::Size);
123static_assert(std::is_trivial_v<NcaHeader>);
124
125struct NcaBucketInfo {
126 static constexpr size_t HeaderSize = 0x10;
127 Int64 offset;
128 Int64 size;
129 std::array<u8, HeaderSize> header;
130};
131static_assert(std::is_trivial_v<NcaBucketInfo>);
132
133struct 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};
147static_assert(std::is_trivial_v<NcaPatchInfo>);
148
149union NcaAesCtrUpperIv {
150 u64 value;
151 struct {
152 u32 generation;
153 u32 secure_value;
154 } part;
155};
156static_assert(std::is_trivial_v<NcaAesCtrUpperIv>);
157
158struct 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};
178static_assert(std::is_trivial_v<NcaSparseInfo>);
179
180struct NcaCompressionInfo {
181 NcaBucketInfo bucket;
182 std::array<u8, 8> resreved;
183};
184static_assert(std::is_trivial_v<NcaCompressionInfo>);
185
186struct NcaMetaDataHashDataInfo {
187 Int64 offset;
188 Int64 size;
189 Hash hash;
190};
191static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>);
192
193struct 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};
321static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size);
322static_assert(std::is_trivial_v<NcaFsHeader>);
323static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset);
324
325inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset =
326 offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash);
327inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset =
328 offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash);
329
330struct NcaMetaDataHashData {
331 s64 layer_info_offset;
332 NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info;
333};
334static_assert(sizeof(NcaMetaDataHashData) ==
335 sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64));
336static_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
8namespace FileSys {
9
10namespace {
11
12constexpr inline u32 SdkAddonVersionMin = 0x000B0000;
13constexpr inline size_t Aes128KeySize = 0x10;
14constexpr const std::array<u8, Aes128KeySize> ZeroKey{};
15
16constexpr 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
30NcaReader::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
39NcaReader::~NcaReader() {}
40
41Result 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
169VirtualFile NcaReader::GetSharedBodyStorage() {
170 ASSERT(m_body_storage != nullptr);
171 return m_body_storage;
172}
173
174u32 NcaReader::GetMagic() const {
175 ASSERT(m_body_storage != nullptr);
176 return m_header.magic;
177}
178
179NcaHeader::DistributionType NcaReader::GetDistributionType() const {
180 ASSERT(m_body_storage != nullptr);
181 return m_header.distribution_type;
182}
183
184NcaHeader::ContentType NcaReader::GetContentType() const {
185 ASSERT(m_body_storage != nullptr);
186 return m_header.content_type;
187}
188
189u8 NcaReader::GetHeaderSign1KeyGeneration() const {
190 ASSERT(m_body_storage != nullptr);
191 return m_header.header1_signature_key_generation;
192}
193
194u8 NcaReader::GetKeyGeneration() const {
195 ASSERT(m_body_storage != nullptr);
196 return m_header.GetProperKeyGeneration();
197}
198
199u8 NcaReader::GetKeyIndex() const {
200 ASSERT(m_body_storage != nullptr);
201 return m_header.key_index;
202}
203
204u64 NcaReader::GetContentSize() const {
205 ASSERT(m_body_storage != nullptr);
206 return m_header.content_size;
207}
208
209u64 NcaReader::GetProgramId() const {
210 ASSERT(m_body_storage != nullptr);
211 return m_header.program_id;
212}
213
214u32 NcaReader::GetContentIndex() const {
215 ASSERT(m_body_storage != nullptr);
216 return m_header.content_index;
217}
218
219u32 NcaReader::GetSdkAddonVersion() const {
220 ASSERT(m_body_storage != nullptr);
221 return m_header.sdk_addon_version;
222}
223
224void 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
231bool 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
236s32 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
246const 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
252void 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
259void 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
266u64 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
272u64 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
278u64 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
285void 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
293const 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
299bool 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
309bool NcaReader::HasInternalDecryptionKeyForAesHw() const {
310 return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw),
311 Aes128KeySize) != 0;
312}
313
314bool NcaReader::IsSoftwareAesPrioritized() const {
315 return m_is_software_aes_prioritized;
316}
317
318void NcaReader::PrioritizeSoftwareAes() {
319 m_is_software_aes_prioritized = true;
320}
321
322bool NcaReader::IsAvailableSwKey() const {
323 return m_is_available_sw_key;
324}
325
326bool NcaReader::HasExternalDecryptionKey() const {
327 return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0;
328}
329
330const void* NcaReader::GetExternalDecryptionKey() const {
331 return m_external_decryption_key.data();
332}
333
334void 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
341void 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
349GetDecompressorFunction NcaReader::GetDecompressor() const {
350 ASSERT(m_get_decompressor != nullptr);
351 return m_get_decompressor;
352}
353
354NcaHeader::EncryptionType NcaReader::GetEncryptionType() const {
355 return m_header_encryption_type;
356}
357
358Result 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
368bool NcaReader::GetHeaderSign1Valid() const {
369 return m_is_header_sign1_signature_valid;
370}
371
372void 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
379Result 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
391void 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
399NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() {
400 ASSERT(this->IsInitialized());
401 return m_data.hash_data;
402}
403
404const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const {
405 ASSERT(this->IsInitialized());
406 return m_data.hash_data;
407}
408
409u16 NcaFsHeaderReader::GetVersion() const {
410 ASSERT(this->IsInitialized());
411 return m_data.version;
412}
413
414s32 NcaFsHeaderReader::GetFsIndex() const {
415 ASSERT(this->IsInitialized());
416 return m_fs_index;
417}
418
419NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const {
420 ASSERT(this->IsInitialized());
421 return m_data.fs_type;
422}
423
424NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const {
425 ASSERT(this->IsInitialized());
426 return m_data.hash_type;
427}
428
429NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const {
430 ASSERT(this->IsInitialized());
431 return m_data.encryption_type;
432}
433
434NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() {
435 ASSERT(this->IsInitialized());
436 return m_data.patch_info;
437}
438
439const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const {
440 ASSERT(this->IsInitialized());
441 return m_data.patch_info;
442}
443
444const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const {
445 ASSERT(this->IsInitialized());
446 return m_data.aes_ctr_upper_iv;
447}
448
449bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const {
450 ASSERT(this->IsInitialized());
451 return m_data.IsSkipLayerHashEncryption();
452}
453
454Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const {
455 ASSERT(out != nullptr);
456 ASSERT(this->IsInitialized());
457
458 R_RETURN(m_data.GetHashTargetOffset(out));
459}
460
461bool NcaFsHeaderReader::ExistsSparseLayer() const {
462 ASSERT(this->IsInitialized());
463 return m_data.sparse_info.generation != 0;
464}
465
466NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() {
467 ASSERT(this->IsInitialized());
468 return m_data.sparse_info;
469}
470
471const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const {
472 ASSERT(this->IsInitialized());
473 return m_data.sparse_info;
474}
475
476bool 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
481NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() {
482 ASSERT(this->IsInitialized());
483 return m_data.compression_info;
484}
485
486const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const {
487 ASSERT(this->IsInitialized());
488 return m_data.compression_info;
489}
490
491bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const {
492 ASSERT(this->IsInitialized());
493 return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable();
494}
495
496NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() {
497 ASSERT(this->IsInitialized());
498 return m_data.meta_data_hash_data_info;
499}
500
501const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const {
502 ASSERT(this->IsInitialized());
503 return m_data.meta_data_hash_data_info;
504}
505
506NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const {
507 ASSERT(this->IsInitialized());
508 return m_data.meta_data_hash_type;
509}
510
511bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const {
512 ASSERT(this->IsInitialized());
513 return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer();
514}
515
516NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() {
517 ASSERT(this->IsInitialized());
518 return m_data.meta_data_hash_data_info;
519}
520
521const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const {
522 ASSERT(this->IsInitialized());
523 return m_data.meta_data_hash_data_info;
524}
525
526NcaFsHeader::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
7namespace FileSys {
8
9namespace {
10
11constexpr size_t HeapBlockSize = BufferPoolAlignment;
12static_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.
16constexpr s32 HeapOrderMax = 7;
17constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3;
18
19constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax);
20constexpr size_t HeapAllocatableSizeMaxForLarge =
21 HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge);
22
23} // namespace
24
25size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) {
26 return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax;
27}
28
29void 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
50void 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
11namespace FileSys {
12
13using namespace Common::Literals;
14
15constexpr inline size_t BufferPoolAlignment = 4_KiB;
16constexpr inline size_t BufferPoolWorkSize = 320;
17
18class PooledBuffer {
19 YUZU_NON_COPYABLE(PooledBuffer);
20
21public:
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
71public:
72 static size_t GetAllocatableSizeMax() {
73 return GetAllocatableSizeMaxCore(false);
74 }
75 static size_t GetAllocatableParticularlyLargeSizeMax() {
76 return GetAllocatableSizeMaxCore(true);
77 }
78
79private:
80 static size_t GetAllocatableSizeMaxCore(bool large);
81
82private:
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
90private:
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
6namespace FileSys {
7
8size_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
8namespace FileSys {
9
10class SparseStorage : public IndirectStorage {
11 YUZU_NON_COPYABLE(SparseStorage);
12 YUZU_NON_MOVEABLE(SparseStorage);
13
14private:
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
35public:
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
63private:
64 void SetZeroStorage() {
65 return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max());
66 }
67
68private:
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
8namespace FileSys {
9
10class RegionSwitchStorage : public IReadOnlyStorage {
11 YUZU_NON_COPYABLE(RegionSwitchStorage);
12 YUZU_NON_MOVEABLE(RegionSwitchStorage);
13
14public:
15 struct Region {
16 s64 offset;
17 s64 size;
18 };
19
20public:
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
49private:
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
74private:
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
6namespace FileSys {
7
8void 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
8namespace FileSys {
9
10void 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) {
165void IPSwitchCompiler::ParseFlag(const std::string& line) { 165void 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
46CNMT::~CNMT() = default; 46CNMT::~CNMT() = default;
47 47
48const CNMTHeader& CNMT::GetHeader() const {
49 return header;
50}
51
48u64 CNMT::GetTitleID() const { 52u64 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
13namespace FileSys {
14namespace {
15template <bool Subsection, typename BlockType, typename BucketType>
16std::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
58BKTR::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
81BKTR::~BKTR() = default;
82
83std::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
159RelocationEntry 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
164RelocationEntry 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
172SubsectionEntry 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
177SubsectionEntry 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
185std::string BKTR::GetName() const {
186 return base_romfs->GetName();
187}
188
189std::size_t BKTR::GetSize() const {
190 return relocation.size;
191}
192
193bool BKTR::Resize(std::size_t new_size) {
194 return false;
195}
196
197VirtualDir BKTR::GetContainingDirectory() const {
198 return base_romfs->GetContainingDirectory();
199}
200
201bool BKTR::IsWritable() const {
202 return false;
203}
204
205bool BKTR::IsReadable() const {
206 return true;
207}
208
209std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) {
210 return 0;
211}
212
213bool 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
15namespace FileSys {
16
17#pragma pack(push, 1)
18struct RelocationEntry {
19 u64_le address_patch;
20 u64_le address_source;
21 u32 from_patch;
22};
23#pragma pack(pop)
24static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
25
26struct 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};
33static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
34
35// Vector version of RelocationBucketRaw
36struct RelocationBucket {
37 u32 number_entries;
38 u64 end_offset;
39 std::vector<RelocationEntry> entries;
40};
41
42struct RelocationBlock {
43 INSERT_PADDING_BYTES(4);
44 u32_le number_buckets;
45 u64_le size;
46 std::array<u64, 0x7FE> base_offsets;
47};
48static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
49
50struct SubsectionEntry {
51 u64_le address_patch;
52 INSERT_PADDING_BYTES(0x4);
53 u32_le ctr;
54};
55static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
56
57struct SubsectionBucketRaw {
58 INSERT_PADDING_BYTES(4);
59 u32_le number_entries;
60 u64_le end_offset;
61 std::array<SubsectionEntry, 0x3FF> subsection_entries;
62};
63static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
64
65// Vector version of SubsectionBucketRaw
66struct SubsectionBucket {
67 u32 number_entries;
68 u64 end_offset;
69 std::vector<SubsectionEntry> entries;
70};
71
72struct SubsectionBlock {
73 INSERT_PADDING_BYTES(4);
74 u32_le number_buckets;
75 u64_le size;
76 std::array<u64, 0x7FE> base_offsets;
77};
78static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
79
80inline 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
86inline 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
92class BKTR : public VfsFile {
93public:
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
118private:
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
298bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { 297bool 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
415VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, 421VirtualFile 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
506template <typename T> 507template <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
676InstallResult 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
665bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { 701bool 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
716InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, 763InstallResult 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
970std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( 1017std::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;
24enum class TitleType : u8; 24enum class TitleType : u8;
25 25
26struct ContentRecord; 26struct ContentRecord;
27struct CNMTHeader;
27struct MetaRecord; 28struct MetaRecord;
28class RegisteredCache; 29class 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
32RomFSFactory::~RomFSFactory() = default; 31RomFSFactory::~RomFSFactory() = default;
33 32
34void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { 33void 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
38VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { 37VirtualFile 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
48VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { 49VirtualFile 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
60VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, 61VirtualFile 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
50private:
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
52private:
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
167std::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
185std::vector<VirtualFile> NSP::GetFiles() const { 167std::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
10namespace Layout { 11namespace Layout {
@@ -49,7 +50,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
49} 50}
50 51
51FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) { 52FramebufferLayout 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
157void HIDCore::SetLastActiveController(NpadIdType npad_id) {
158 last_active_controller = npad_id;
159}
160
161NpadIdType HIDCore::GetLastActiveController() const {
162 return last_active_controller;
163}
164
157void HIDCore::EnableAllControllerConfiguration() { 165void 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
293enum class TouchScreenMode : u32 {
294 Stylus = 0,
295 Standard = 1,
296};
297
298// This is nn::hid::TouchScreenModeForNx
299enum class TouchScreenModeForNx : u8 {
300 UseSystemSetting,
301 Finger,
302 Heat2,
303};
304
292// This is nn::hid::NpadStyleTag 305// This is nn::hid::NpadStyleTag
293struct NpadStyleTag { 306struct NpadStyleTag {
294 union { 307 union {
@@ -334,6 +347,14 @@ struct TouchState {
334}; 347};
335static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); 348static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
336 349
350// This is nn::hid::TouchScreenConfigurationForNx
351struct TouchScreenConfigurationForNx {
352 TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
353 INSERT_PADDING_BYTES(0xF);
354};
355static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10,
356 "TouchScreenConfigurationForNx is an invalid size");
357
337struct NpadColor { 358struct NpadColor {
338 u8 r{}; 359 u8 r{};
339 u8 g{}; 360 u8 g{};
@@ -662,6 +683,11 @@ struct MouseState {
662}; 683};
663static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); 684static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
664 685
686struct UniquePadId {
687 u64 id;
688};
689static_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.
666constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { 692constexpr 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
48private: 41private:
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 */
39void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, 39void 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
354Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { 355Result 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} {}
16KResourceLimit::~KResourceLimit() = default; 17KResourceLimit::~KResourceLimit() = default;
17 18
18void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) { 19void KResourceLimit::Initialize() {}
19 m_core_timing = core_timing;
20}
21 20
22void KResourceLimit::Finalize() {} 21void KResourceLimit::Finalize() {}
23 22
@@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
86} 85}
87 86
88bool KResourceLimit::Reserve(LimitableResource which, s64 value) { 87bool 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
92bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { 91bool 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
155KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { 154KResourceLimit* 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
63KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); 62KResourceLimit* 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
105void SleepThread(Core::System& system, s64 nanoseconds) { 106void 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};
45constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; 46constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
46 47
47enum class LaunchParameterKind : u32 { 48enum 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) {
340void ISelfController::LockExit(HLERequestContext& ctx) { 341void 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) {
349void ISelfController::UnlockExit(HLERequestContext& ctx) { 350void 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
358void ISelfController::EnterFatalSection(HLERequestContext& ctx) { 363void 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
923void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) { 928void 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
1564void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { 1566void 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
1824void 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
1822void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { 1836void 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
1852void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { 1866void 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
1859void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { 1875void 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
12namespace Service::AM::Applets { 13namespace 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
69PerformanceMode Controller::GetCurrentPerformanceMode() const { 70PerformanceMode 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
74PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { 74PerformanceConfiguration 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_)
220AudInU::~AudInU() = default; 220AudInU::~AudInU() = default;
221 221
222void AudInU::ListAudioIns(HLERequestContext& ctx) { 222void 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
242void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) { 242void 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_)
228AudOutU::~AudOutU() = default; 228AudOutU::~AudOutU() = default;
229 229
230void AudOutU::ListAudioOuts(HLERequestContext& ctx) { 230void 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
29using namespace AudioCore::AudioRenderer; 29using namespace AudioCore::Renderer;
30 30
31namespace Service::Audio { 31namespace 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};
20constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; 20constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536};
21constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; 21constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537};
22 22
23constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7};
24constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8};
25constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6};
26constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5};
27constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17};
28constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4};
29constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
30constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
31constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
32constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
33constexpr 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
18namespace Service::Audio { 16namespace Service::Audio {
19namespace { 17using namespace AudioCore::OpusDecoder;
20struct OpusDeleter { 18
21 void operator()(OpusMSDecoder* ptr) const { 19class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> {
22 opus_multistream_decoder_destroy(ptr); 20public:
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
26using 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
28struct 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};
34static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
35 51
36class OpusDecoderState { 52private:
37public: 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
67private: 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
161class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { 122 auto input_data{ctx.ReadBuffer(0)};
162public: 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
184private: 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
212std::size_t WorkerBufferSize(u32 channel_count) { 275void 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.
227std::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
236void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { 302void 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
317void 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(&params, 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
256void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { 354void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) {
257 GetWorkBufferSize(ctx); 355 IPC::RequestParser rp{ctx};
356
357 auto input{ctx.ReadBuffer()};
358 OpusMultiStreamParameters params;
359 std::memcpy(&params, 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
260void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { 371void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
261 OpusMultiStreamParametersEx param; 372 IPC::RequestParser rp{ctx};
262 std::memcpy(&param, 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
286void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { 394void 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
324void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { 408void 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(&params, 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
438void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
439 IPC::RequestParser rp{ctx};
440
441 auto input{ctx.ReadBuffer()};
442 OpusMultiStreamParametersEx params;
443 std::memcpy(&params, 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
459void 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
473void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) {
474 IPC::RequestParser rp{ctx};
475
476 auto input{ctx.ReadBuffer()};
477 OpusMultiStreamParametersEx params;
478 std::memcpy(&params, 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
357HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { 490HwOpus::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
8namespace Core { 9namespace Core {
@@ -11,16 +12,6 @@ class System;
11 12
12namespace Service::Audio { 13namespace Service::Audio {
13 14
14struct 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
24class HwOpus final : public ServiceFramework<HwOpus> { 15class HwOpus final : public ServiceFramework<HwOpus> {
25public: 16public:
26 explicit HwOpus(Core::System& system_); 17 explicit HwOpus(Core::System& system_);
@@ -28,10 +19,18 @@ public:
28 19
29private: 20private:
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
385std::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
376Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, 390Result 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
16namespace FileSys { 16namespace FileSys {
17class BISFactory; 17class BISFactory;
18class NCA;
18class RegisteredCache; 19class RegisteredCache;
19class RegisteredCacheUnion; 20class RegisteredCacheUnion;
20class PlaceholderCache; 21class 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
473void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { 484void 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
749Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { 752Core::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
1514void 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
1511bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { 1539bool 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;
16namespace Service::HID { 16namespace Service::HID {
17class Controller_Touchscreen final : public ControllerBase { 17class Controller_Touchscreen final : public ControllerBase {
18public: 18public:
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
234Hid::Hid(Core::System& system_) 234Hid::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
2369void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { 2371void 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
2544class HidSys final : public ServiceFramework<HidSys> { 2546class HidSys final : public ServiceFramework<HidSys> {
2545public: 2547public:
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
2754private: 2760private:
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
2776void LoopProcess(Core::System& system) { 2843void 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
96class Hid final : public ServiceFramework<Hid> { 96class Hid final : public ServiceFramework<Hid> {
97public: 97public:
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
13namespace Service::Mii { 14namespace Service::Mii {
14 15
15constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
16
17class IDatabaseService final : public ServiceFramework<IDatabaseService> { 16class IDatabaseService final : public ServiceFramework<IDatabaseService> {
18public: 17public:
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
56private: 55private:
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
254class MiiDBModule final : public ServiceFramework<MiiDBModule> { 251class MiiDBModule final : public ServiceFramework<MiiDBModule> {
255public: 252public:
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
277class MiiImg final : public ServiceFramework<MiiImg> { 276class MiiImg final : public ServiceFramework<MiiImg> {
@@ -303,8 +302,10 @@ public:
303void LoopProcess(Core::System& system) { 302void 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
15namespace Service::Mii { 18namespace Service::Mii {
16
17namespace {
18
19constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
20
21constexpr std::size_t BaseMiiCount{2};
22constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; 19constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
23 20
24constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; 21MiiManager::MiiManager() {}
25constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7};
26constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13};
27constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23};
28constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0};
29constexpr 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}};
34constexpr 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
38template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
39std::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
45CharInfo 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
105u16 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
119template <typename T>
120T 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
127template <typename T>
128T GetRandomValue(T max) {
129 return GetRandomValue<T>({}, max);
130}
131
132MiiStoreData 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
311MiiStoreData 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
369MiiStoreData::MiiStoreData() = default;
370
371MiiStoreData::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
381MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} 23bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
382
383bool 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
395bool MiiManager::IsFullDatabase() const { 33bool MiiManager::IsFullDatabase() const {
@@ -397,301 +35,138 @@ bool MiiManager::IsFullDatabase() const {
397 return false; 35 return false;
398} 36}
399 37
400u32 MiiManager::GetCount(SourceFlag source_flag) const { 38u32 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
412Result MiiManager::UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag) { 49Result 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
421CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { 59void 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
425CharInfo MiiManager::BuildDefault(std::size_t index) { 65void 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
429CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const { 71void 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
512Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const { 77void 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 83Result 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; 96Result 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); 108Result 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 131Result 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
596NfpStoreDataExtension 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
609bool 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
674std::vector<MiiInfoElement> MiiManager::GetDefault(SourceFlag source_flag) { 153Result 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
688Result 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
168void 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
11namespace Service::Mii { 14namespace Service::Mii {
12 15
@@ -16,25 +19,30 @@ class MiiManager {
16public: 19public:
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
36private: 41private:
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
8namespace Service::Mii {
9
10constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1};
11constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2};
12constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
13constexpr Result ResultNotFound{ErrorModule::Mii, 4};
14constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
15constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
16constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
17constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
18constexpr 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
14namespace Service::Mii {
15
16constexpr u8 MaxHeight = 127;
17constexpr u8 MaxBuild = 127;
18constexpr u8 MaxType = 1;
19constexpr u8 MaxRegionMove = 3;
20constexpr u8 MaxEyeScale = 7;
21constexpr u8 MaxEyeAspect = 6;
22constexpr u8 MaxEyeRotate = 7;
23constexpr u8 MaxEyeX = 12;
24constexpr u8 MaxEyeY = 18;
25constexpr u8 MaxEyebrowScale = 8;
26constexpr u8 MaxEyebrowAspect = 6;
27constexpr u8 MaxEyebrowRotate = 11;
28constexpr u8 MaxEyebrowX = 12;
29constexpr u8 MaxEyebrowY = 18;
30constexpr u8 MaxNoseScale = 8;
31constexpr u8 MaxNoseY = 18;
32constexpr u8 MaxMouthScale = 8;
33constexpr u8 MaxMoutAspect = 6;
34constexpr u8 MaxMouthY = 18;
35constexpr u8 MaxMustacheScale = 8;
36constexpr u8 MasMustacheY = 16;
37constexpr u8 MaxGlassScale = 7;
38constexpr u8 MaxGlassY = 20;
39constexpr u8 MaxMoleScale = 8;
40constexpr u8 MaxMoleX = 16;
41constexpr u8 MaxMoleY = 30;
42constexpr u8 MaxVer3CommonColor = 7;
43constexpr u8 MaxVer3GlassType = 8;
44
45enum class Age : u8 {
46 Young,
47 Normal,
48 Old,
49 All, // Default
50
51 Max = All,
52};
53
54enum class Gender : u8 {
55 Male,
56 Female,
57 All, // Default
58
59 Max = Female,
60};
61
62enum class Race : u8 {
63 Black,
64 White,
65 Asian,
66 All, // Default
67
68 Max = All,
69};
70
71enum 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
208enum class MoleType : u8 {
209 None, // Default
210 OneDot,
211
212 Max = OneDot,
213};
214
215enum class HairFlip : u8 {
216 Left, // Default
217 Right,
218
219 Max = Right,
220};
221
222enum class CommonColor : u8 {
223 // For simplicity common colors aren't listed
224 Max = 99,
225 Count = 100,
226};
227
228enum 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
245enum 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
310enum 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
351enum class FontRegion : u8 {
352 Standard, // Default
353 China,
354 Korea,
355 Taiwan,
356
357 Max = Taiwan,
358};
359
360enum 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
377enum 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
393enum 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
410enum 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
427enum 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
456enum 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
479enum class BeardType : u8 {
480 None,
481 Goatee,
482 GoateeLong,
483 LionsManeLong,
484 LionsMane,
485 Full,
486
487 Min = Goatee,
488 Max = Full,
489};
490
491enum class MustacheType : u8 {
492 None,
493 Walrus,
494 Pencil,
495 Horseshoe,
496 Normal,
497 Toothbrush,
498
499 Min = Walrus,
500 Max = Toothbrush,
501};
502
503enum 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
529enum class BeardAndMustacheFlag : u32 {
530 Beard = 1,
531 Mustache,
532 All = Beard | Mustache,
533};
534DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
535
536enum class Source : u32 {
537 Database = 0,
538 Default = 1,
539 Account = 2,
540 Friend = 3,
541};
542
543enum class SourceFlag : u32 {
544 None = 0,
545 Database = 1 << 0,
546 Default = 1 << 1,
547};
548DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
549
550enum 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
605struct 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};
625static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size");
626
627struct 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};
679static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size.");
680
681struct 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
14namespace Service::Mii {
15class MiiUtil {
16public:
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
10namespace Service::Mii::RawData {
11
12extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii;
13extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline;
14extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor;
15extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle;
16extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup;
17extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType;
18extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor;
19extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType;
20extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor;
21extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType;
22extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType;
23extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType;
24extern 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
14namespace Service::Mii {
15
16enum class Age : u32 {
17 Young,
18 Normal,
19 Old,
20 All,
21};
22
23enum class BeardType : u32 {
24 None,
25 Beard1,
26 Beard2,
27 Beard3,
28 Beard4,
29 Beard5,
30};
31
32enum class BeardAndMustacheFlag : u32 {
33 Beard = 1,
34 Mustache,
35 All = Beard | Mustache,
36};
37DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
38
39enum class FontRegion : u32 {
40 Standard,
41 China,
42 Korea,
43 Taiwan,
44};
45
46enum class Gender : u32 {
47 Male,
48 Female,
49 All,
50 Maximum = Female,
51};
52
53enum class HairFlip : u32 {
54 Left,
55 Right,
56 Maximum = Right,
57};
58
59enum class MustacheType : u32 {
60 None,
61 Mustache1,
62 Mustache2,
63 Mustache3,
64 Mustache4,
65 Mustache5,
66};
67
68enum class Race : u32 {
69 Black,
70 White,
71 Asian,
72 All,
73};
74
75enum class Source : u32 {
76 Database = 0,
77 Default = 1,
78 Account = 2,
79 Friend = 3,
80};
81
82enum class SourceFlag : u32 {
83 None = 0,
84 Database = 1 << 0,
85 Default = 1 << 1,
86};
87DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
88
89// nn::mii::CharInfo
90struct 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};
144static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
145static_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
150struct MiiInfoElement {
151 MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
152
153 CharInfo info{};
154 Source source{};
155};
156static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
157
158struct 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};
243static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
244static_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
251struct 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};
371static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
372
373struct 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};
383static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
384
385constexpr std::array<u8, 0x10> Ver3FacelineColorTable{
386 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
387};
388
389constexpr 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
398constexpr 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
407constexpr 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
416constexpr 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
425constexpr 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
430struct 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};
451static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
452
453struct MiiStoreDataElement {
454 MiiStoreData data{};
455 Source source{};
456};
457static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
458
459struct MiiDatabase {
460 u32 magic{}; // 'NFDB'
461 std::array<MiiStoreData, 0x64> miis{};
462 INSERT_PADDING_BYTES(1);
463 u8 count{};
464 u16 crc{};
465};
466static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
467
468struct RandomMiiValues {
469 std::array<u8, 0xbc> values{};
470};
471static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
472
473struct RandomMiiData4 {
474 Gender gender{};
475 Age age{};
476 Race race{};
477 u32 values_count{};
478 std::array<u32, 47> values{};
479};
480static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
481
482struct RandomMiiData3 {
483 u32 arg_1;
484 u32 arg_2;
485 u32 values_count;
486 std::array<u32, 47> values{};
487};
488static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
489
490struct RandomMiiData2 {
491 u32 arg_1;
492 u32 values_count;
493 std::array<u32, 47> values{};
494};
495static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
496
497struct 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};
549static_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
7namespace Service::Mii {
8
9void 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
65ValidationResult 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
222Common::UUID CharInfo::GetCreateId() const {
223 return create_id;
224}
225
226Nickname CharInfo::GetNickname() const {
227 return name;
228}
229
230FontRegion CharInfo::GetFontRegion() const {
231 return font_region;
232}
233
234FavoriteColor CharInfo::GetFavoriteColor() const {
235 return favorite_color;
236}
237
238Gender CharInfo::GetGender() const {
239 return gender;
240}
241
242u8 CharInfo::GetHeight() const {
243 return height;
244}
245
246u8 CharInfo::GetBuild() const {
247 return build;
248}
249
250u8 CharInfo::GetType() const {
251 return type;
252}
253
254u8 CharInfo::GetRegionMove() const {
255 return region_move;
256}
257
258FacelineType CharInfo::GetFacelineType() const {
259 return faceline_type;
260}
261
262FacelineColor CharInfo::GetFacelineColor() const {
263 return faceline_color;
264}
265
266FacelineWrinkle CharInfo::GetFacelineWrinkle() const {
267 return faceline_wrinkle;
268}
269
270FacelineMake CharInfo::GetFacelineMake() const {
271 return faceline_make;
272}
273
274HairType CharInfo::GetHairType() const {
275 return hair_type;
276}
277
278CommonColor CharInfo::GetHairColor() const {
279 return hair_color;
280}
281
282HairFlip CharInfo::GetHairFlip() const {
283 return hair_flip;
284}
285
286EyeType CharInfo::GetEyeType() const {
287 return eye_type;
288}
289
290CommonColor CharInfo::GetEyeColor() const {
291 return eye_color;
292}
293
294u8 CharInfo::GetEyeScale() const {
295 return eye_scale;
296}
297
298u8 CharInfo::GetEyeAspect() const {
299 return eye_aspect;
300}
301
302u8 CharInfo::GetEyeRotate() const {
303 return eye_rotate;
304}
305
306u8 CharInfo::GetEyeX() const {
307 return eye_x;
308}
309
310u8 CharInfo::GetEyeY() const {
311 return eye_y;
312}
313
314EyebrowType CharInfo::GetEyebrowType() const {
315 return eyebrow_type;
316}
317
318CommonColor CharInfo::GetEyebrowColor() const {
319 return eyebrow_color;
320}
321
322u8 CharInfo::GetEyebrowScale() const {
323 return eyebrow_scale;
324}
325
326u8 CharInfo::GetEyebrowAspect() const {
327 return eyebrow_aspect;
328}
329
330u8 CharInfo::GetEyebrowRotate() const {
331 return eyebrow_rotate;
332}
333
334u8 CharInfo::GetEyebrowX() const {
335 return eyebrow_x;
336}
337
338u8 CharInfo::GetEyebrowY() const {
339 return eyebrow_y;
340}
341
342NoseType CharInfo::GetNoseType() const {
343 return nose_type;
344}
345
346u8 CharInfo::GetNoseScale() const {
347 return nose_scale;
348}
349
350u8 CharInfo::GetNoseY() const {
351 return nose_y;
352}
353
354MouthType CharInfo::GetMouthType() const {
355 return mouth_type;
356}
357
358CommonColor CharInfo::GetMouthColor() const {
359 return mouth_color;
360}
361
362u8 CharInfo::GetMouthScale() const {
363 return mouth_scale;
364}
365
366u8 CharInfo::GetMouthAspect() const {
367 return mouth_aspect;
368}
369
370u8 CharInfo::GetMouthY() const {
371 return mouth_y;
372}
373
374CommonColor CharInfo::GetBeardColor() const {
375 return beard_color;
376}
377
378BeardType CharInfo::GetBeardType() const {
379 return beard_type;
380}
381
382MustacheType CharInfo::GetMustacheType() const {
383 return mustache_type;
384}
385
386u8 CharInfo::GetMustacheScale() const {
387 return mustache_scale;
388}
389
390u8 CharInfo::GetMustacheY() const {
391 return mustache_y;
392}
393
394GlassType CharInfo::GetGlassType() const {
395 return glass_type;
396}
397
398CommonColor CharInfo::GetGlassColor() const {
399 return glass_color;
400}
401
402u8 CharInfo::GetGlassScale() const {
403 return glass_scale;
404}
405
406u8 CharInfo::GetGlassY() const {
407 return glass_y;
408}
409
410MoleType CharInfo::GetMoleType() const {
411 return mole_type;
412}
413
414u8 CharInfo::GetMoleScale() const {
415 return mole_scale;
416}
417
418u8 CharInfo::GetMoleX() const {
419 return mole_x;
420}
421
422u8 CharInfo::GetMoleY() const {
423 return mole_y;
424}
425
426bool 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
8namespace Service::Mii {
9class StoreData;
10
11// This is nn::mii::detail::CharInfoRaw
12class CharInfo {
13public:
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
72private:
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};
127static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
128static_assert(std::has_unique_object_representations_v<CharInfo>,
129 "All bits of CharInfo must contribute to its value.");
130
131struct CharInfoElement {
132 CharInfo char_info{};
133 Source source{};
134};
135static_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
9namespace Service::Mii {
10
11void CoreData::SetDefault() {
12 data = {};
13 name = GetDefaultNickname();
14}
15
16void 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
188u32 CoreData::IsValid() const {
189 // TODO: Complete this
190 return 0;
191}
192
193void CoreData::SetFontRegion(FontRegion value) {
194 data.font_region.Assign(static_cast<u32>(value));
195}
196
197void CoreData::SetFavoriteColor(FavoriteColor value) {
198 data.favorite_color.Assign(static_cast<u32>(value));
199}
200
201void CoreData::SetGender(Gender value) {
202 data.gender.Assign(static_cast<u32>(value));
203}
204
205void CoreData::SetHeight(u8 value) {
206 data.height.Assign(value);
207}
208
209void CoreData::SetBuild(u8 value) {
210 data.build.Assign(value);
211}
212
213void CoreData::SetType(u8 value) {
214 data.type.Assign(value);
215}
216
217void CoreData::SetRegionMove(u8 value) {
218 data.region_move.Assign(value);
219}
220
221void CoreData::SetFacelineType(FacelineType value) {
222 data.faceline_type.Assign(static_cast<u32>(value));
223}
224
225void CoreData::SetFacelineColor(FacelineColor value) {
226 data.faceline_color.Assign(static_cast<u32>(value));
227}
228
229void CoreData::SetFacelineWrinkle(FacelineWrinkle value) {
230 data.faceline_wrinkle.Assign(static_cast<u32>(value));
231}
232
233void CoreData::SetFacelineMake(FacelineMake value) {
234 data.faceline_makeup.Assign(static_cast<u32>(value));
235}
236
237void CoreData::SetHairType(HairType value) {
238 data.hair_type.Assign(static_cast<u32>(value));
239}
240
241void CoreData::SetHairColor(CommonColor value) {
242 data.hair_color.Assign(static_cast<u32>(value));
243}
244
245void CoreData::SetHairFlip(HairFlip value) {
246 data.hair_flip.Assign(static_cast<u32>(value));
247}
248
249void CoreData::SetEyeType(EyeType value) {
250 data.eye_type.Assign(static_cast<u32>(value));
251}
252
253void CoreData::SetEyeColor(CommonColor value) {
254 data.eye_color.Assign(static_cast<u32>(value));
255}
256
257void CoreData::SetEyeScale(u8 value) {
258 data.eye_scale.Assign(value);
259}
260
261void CoreData::SetEyeAspect(u8 value) {
262 data.eye_aspect.Assign(value);
263}
264
265void CoreData::SetEyeRotate(u8 value) {
266 data.eye_rotate.Assign(value);
267}
268
269void CoreData::SetEyeX(u8 value) {
270 data.eye_x.Assign(value);
271}
272
273void CoreData::SetEyeY(u8 value) {
274 data.eye_y.Assign(value);
275}
276
277void CoreData::SetEyebrowType(EyebrowType value) {
278 data.eyebrow_type.Assign(static_cast<u32>(value));
279}
280
281void CoreData::SetEyebrowColor(CommonColor value) {
282 data.eyebrow_color.Assign(static_cast<u32>(value));
283}
284
285void CoreData::SetEyebrowScale(u8 value) {
286 data.eyebrow_scale.Assign(value);
287}
288
289void CoreData::SetEyebrowAspect(u8 value) {
290 data.eyebrow_aspect.Assign(value);
291}
292
293void CoreData::SetEyebrowRotate(u8 value) {
294 data.eyebrow_rotate.Assign(value);
295}
296
297void CoreData::SetEyebrowX(u8 value) {
298 data.eyebrow_x.Assign(value);
299}
300
301void CoreData::SetEyebrowY(u8 value) {
302 data.eyebrow_y.Assign(value);
303}
304
305void CoreData::SetNoseType(NoseType value) {
306 data.nose_type.Assign(static_cast<u32>(value));
307}
308
309void CoreData::SetNoseScale(u8 value) {
310 data.nose_scale.Assign(value);
311}
312
313void CoreData::SetNoseY(u8 value) {
314 data.nose_y.Assign(value);
315}
316
317void CoreData::SetMouthType(u8 value) {
318 data.mouth_type.Assign(value);
319}
320
321void CoreData::SetMouthColor(CommonColor value) {
322 data.mouth_color.Assign(static_cast<u32>(value));
323}
324
325void CoreData::SetMouthScale(u8 value) {
326 data.mouth_scale.Assign(value);
327}
328
329void CoreData::SetMouthAspect(u8 value) {
330 data.mouth_aspect.Assign(value);
331}
332
333void CoreData::SetMouthY(u8 value) {
334 data.mouth_y.Assign(value);
335}
336
337void CoreData::SetBeardColor(CommonColor value) {
338 data.beard_color.Assign(static_cast<u32>(value));
339}
340
341void CoreData::SetBeardType(BeardType value) {
342 data.beard_type.Assign(static_cast<u32>(value));
343}
344
345void CoreData::SetMustacheType(MustacheType value) {
346 data.mustache_type.Assign(static_cast<u32>(value));
347}
348
349void CoreData::SetMustacheScale(u8 value) {
350 data.mustache_scale.Assign(value);
351}
352
353void CoreData::SetMustacheY(u8 value) {
354 data.mustache_y.Assign(value);
355}
356
357void CoreData::SetGlassType(GlassType value) {
358 data.glasses_type.Assign(static_cast<u32>(value));
359}
360
361void CoreData::SetGlassColor(CommonColor value) {
362 data.glasses_color.Assign(static_cast<u32>(value));
363}
364
365void CoreData::SetGlassScale(u8 value) {
366 data.glasses_scale.Assign(value);
367}
368
369void CoreData::SetGlassY(u8 value) {
370 data.glasses_y.Assign(value);
371}
372
373void CoreData::SetMoleType(MoleType value) {
374 data.mole_type.Assign(static_cast<u32>(value));
375}
376
377void CoreData::SetMoleScale(u8 value) {
378 data.mole_scale.Assign(value);
379}
380
381void CoreData::SetMoleX(u8 value) {
382 data.mole_x.Assign(value);
383}
384
385void CoreData::SetMoleY(u8 value) {
386 data.mole_y.Assign(value);
387}
388
389void CoreData::SetNickname(Nickname nickname) {
390 name = nickname;
391}
392
393FontRegion CoreData::GetFontRegion() const {
394 return static_cast<FontRegion>(data.font_region.Value());
395}
396
397FavoriteColor CoreData::GetFavoriteColor() const {
398 return static_cast<FavoriteColor>(data.favorite_color.Value());
399}
400
401Gender CoreData::GetGender() const {
402 return static_cast<Gender>(data.gender.Value());
403}
404
405u8 CoreData::GetHeight() const {
406 return static_cast<u8>(data.height.Value());
407}
408
409u8 CoreData::GetBuild() const {
410 return static_cast<u8>(data.build.Value());
411}
412
413u8 CoreData::GetType() const {
414 return static_cast<u8>(data.type.Value());
415}
416
417u8 CoreData::GetRegionMove() const {
418 return static_cast<u8>(data.region_move.Value());
419}
420
421FacelineType CoreData::GetFacelineType() const {
422 return static_cast<FacelineType>(data.faceline_type.Value());
423}
424
425FacelineColor CoreData::GetFacelineColor() const {
426 return static_cast<FacelineColor>(data.faceline_color.Value());
427}
428
429FacelineWrinkle CoreData::GetFacelineWrinkle() const {
430 return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value());
431}
432
433FacelineMake CoreData::GetFacelineMake() const {
434 return static_cast<FacelineMake>(data.faceline_makeup.Value());
435}
436
437HairType CoreData::GetHairType() const {
438 return static_cast<HairType>(data.hair_type.Value());
439}
440
441CommonColor CoreData::GetHairColor() const {
442 return static_cast<CommonColor>(data.hair_color.Value());
443}
444
445HairFlip CoreData::GetHairFlip() const {
446 return static_cast<HairFlip>(data.hair_flip.Value());
447}
448
449EyeType CoreData::GetEyeType() const {
450 return static_cast<EyeType>(data.eye_type.Value());
451}
452
453CommonColor CoreData::GetEyeColor() const {
454 return static_cast<CommonColor>(data.eye_color.Value());
455}
456
457u8 CoreData::GetEyeScale() const {
458 return static_cast<u8>(data.eye_scale.Value());
459}
460
461u8 CoreData::GetEyeAspect() const {
462 return static_cast<u8>(data.eye_aspect.Value());
463}
464
465u8 CoreData::GetEyeRotate() const {
466 return static_cast<u8>(data.eye_rotate.Value());
467}
468
469u8 CoreData::GetEyeX() const {
470 return static_cast<u8>(data.eye_x.Value());
471}
472
473u8 CoreData::GetEyeY() const {
474 return static_cast<u8>(data.eye_y.Value());
475}
476
477EyebrowType CoreData::GetEyebrowType() const {
478 return static_cast<EyebrowType>(data.eyebrow_type.Value());
479}
480
481CommonColor CoreData::GetEyebrowColor() const {
482 return static_cast<CommonColor>(data.eyebrow_color.Value());
483}
484
485u8 CoreData::GetEyebrowScale() const {
486 return static_cast<u8>(data.eyebrow_scale.Value());
487}
488
489u8 CoreData::GetEyebrowAspect() const {
490 return static_cast<u8>(data.eyebrow_aspect.Value());
491}
492
493u8 CoreData::GetEyebrowRotate() const {
494 return static_cast<u8>(data.eyebrow_rotate.Value());
495}
496
497u8 CoreData::GetEyebrowX() const {
498 return static_cast<u8>(data.eyebrow_x.Value());
499}
500
501u8 CoreData::GetEyebrowY() const {
502 return static_cast<u8>(data.eyebrow_y.Value());
503}
504
505NoseType CoreData::GetNoseType() const {
506 return static_cast<NoseType>(data.nose_type.Value());
507}
508
509u8 CoreData::GetNoseScale() const {
510 return static_cast<u8>(data.nose_scale.Value());
511}
512
513u8 CoreData::GetNoseY() const {
514 return static_cast<u8>(data.nose_y.Value());
515}
516
517MouthType CoreData::GetMouthType() const {
518 return static_cast<MouthType>(data.mouth_type.Value());
519}
520
521CommonColor CoreData::GetMouthColor() const {
522 return static_cast<CommonColor>(data.mouth_color.Value());
523}
524
525u8 CoreData::GetMouthScale() const {
526 return static_cast<u8>(data.mouth_scale.Value());
527}
528
529u8 CoreData::GetMouthAspect() const {
530 return static_cast<u8>(data.mouth_aspect.Value());
531}
532
533u8 CoreData::GetMouthY() const {
534 return static_cast<u8>(data.mouth_y.Value());
535}
536
537CommonColor CoreData::GetBeardColor() const {
538 return static_cast<CommonColor>(data.beard_color.Value());
539}
540
541BeardType CoreData::GetBeardType() const {
542 return static_cast<BeardType>(data.beard_type.Value());
543}
544
545MustacheType CoreData::GetMustacheType() const {
546 return static_cast<MustacheType>(data.mustache_type.Value());
547}
548
549u8 CoreData::GetMustacheScale() const {
550 return static_cast<u8>(data.mustache_scale.Value());
551}
552
553u8 CoreData::GetMustacheY() const {
554 return static_cast<u8>(data.mustache_y.Value());
555}
556
557GlassType CoreData::GetGlassType() const {
558 return static_cast<GlassType>(data.glasses_type.Value());
559}
560
561CommonColor CoreData::GetGlassColor() const {
562 return static_cast<CommonColor>(data.glasses_color.Value());
563}
564
565u8 CoreData::GetGlassScale() const {
566 return static_cast<u8>(data.glasses_scale.Value());
567}
568
569u8 CoreData::GetGlassY() const {
570 return static_cast<u8>(data.glasses_y.Value());
571}
572
573MoleType CoreData::GetMoleType() const {
574 return static_cast<MoleType>(data.mole_type.Value());
575}
576
577u8 CoreData::GetMoleScale() const {
578 return static_cast<u8>(data.mole_scale.Value());
579}
580
581u8 CoreData::GetMoleX() const {
582 return static_cast<u8>(data.mole_x.Value());
583}
584
585u8 CoreData::GetMoleY() const {
586 return static_cast<u8>(data.mole_y.Value());
587}
588
589Nickname CoreData::GetNickname() const {
590 return name;
591}
592
593Nickname CoreData::GetDefaultNickname() const {
594 return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'};
595}
596
597Nickname 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
8namespace Service::Mii {
9
10struct 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};
95static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size.");
96static_assert(std::is_trivially_copyable_v<StoreDataBitFields>,
97 "StoreDataBitFields is not trivially copyable.");
98
99class CoreData {
100public:
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
210private:
211 StoreDataBitFields data{};
212 Nickname name{};
213};
214static_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
6namespace Service::Mii::RawData { 6namespace Service::Mii::RawData {
7 7
8const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ 8constexpr std::array<u8, static_cast<u8>(FacelineColor::Count)> FromVer3FacelineColorTable{
9 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
10};
11
12constexpr 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
21constexpr 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
30constexpr 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
39constexpr 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
48constexpr 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
53constexpr std::array<u8, 8> Ver3FacelineColorTable{
54 0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
55};
56
57constexpr std::array<u8, 8> Ver3HairColorTable{
58 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
59};
60
61constexpr std::array<u8, 6> Ver3EyeColorTable{
62 0x8, 0x9, 0xa, 0xb, 0xc, 0xd,
63};
64
65constexpr std::array<u8, 5> Ver3MouthColorTable{
66 0x13, 0x14, 0x15, 0x16, 0x17,
67};
68
69constexpr std::array<u8, 7> Ver3GlassColorTable{
70 0x8, 0xe, 0xf, 0x10, 0x11, 0x12, 0x0,
71};
72
73const 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
80const 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
85const 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
192const 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
420const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{ 508const 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
549const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ 637const 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
588const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{ 676const 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
717const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{ 805const 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
846const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ 934const 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
995const std::array<RandomMiiData3, 9> RandomMiiHairColor{ 1083const 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
1052const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{ 1140const 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
1202const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{ 1290const 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
1220const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{ 1308const 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
1352const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{ 1440const 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
1481const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{ 1569const 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
1628const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{ 1716const 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
1734u8 FromVer3GetFacelineColor(u8 color) {
1735 return FromVer3FacelineColorTable[color];
1736}
1737
1738u8 FromVer3GetHairColor(u8 color) {
1739 return FromVer3HairColorTable[color];
1740}
1741
1742u8 FromVer3GetEyeColor(u8 color) {
1743 return FromVer3EyeColorTable[color];
1744}
1745
1746u8 FromVer3GetMouthlineColor(u8 color) {
1747 return FromVer3MouthlineColorTable[color];
1748}
1749
1750u8 FromVer3GetGlassColor(u8 color) {
1751 return FromVer3GlassColorTable[color];
1752}
1753
1754u8 FromVer3GetGlassType(u8 type) {
1755 return FromVer3GlassTypeTable[type];
1756}
1757
1758FacelineColor GetFacelineColorFromVer3(u32 color) {
1759 return static_cast<FacelineColor>(Ver3FacelineColorTable[color]);
1760}
1761
1762CommonColor GetHairColorFromVer3(u32 color) {
1763 return static_cast<CommonColor>(Ver3HairColorTable[color]);
1764}
1765
1766CommonColor GetEyeColorFromVer3(u32 color) {
1767 return static_cast<CommonColor>(Ver3EyeColorTable[color]);
1768}
1769
1770CommonColor GetMouthColorFromVer3(u32 color) {
1771 return static_cast<CommonColor>(Ver3MouthColorTable[color]);
1772}
1773
1774CommonColor 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
10namespace Service::Mii::RawData {
11
12struct RandomMiiValues {
13 std::array<u8, 188> values{};
14};
15static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
16
17struct RandomMiiData4 {
18 u32 gender{};
19 u32 age{};
20 u32 race{};
21 u32 values_count{};
22 std::array<u32, 47> values{};
23};
24static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
25
26struct RandomMiiData3 {
27 u32 arg_1;
28 u32 arg_2;
29 u32 values_count;
30 std::array<u32, 47> values{};
31};
32static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
33
34struct RandomMiiData2 {
35 u32 arg_1;
36 u32 values_count;
37 std::array<u32, 47> values{};
38};
39static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
40
41extern const std::array<Service::Mii::DefaultMii, 2> BaseMii;
42extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii;
43
44extern const std::array<u8, 62> EyeRotateLookup;
45extern const std::array<u8, 24> EyebrowRotateLookup;
46
47extern const std::array<RandomMiiData4, 18> RandomMiiFaceline;
48extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor;
49extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle;
50extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup;
51extern const std::array<RandomMiiData4, 18> RandomMiiHairType;
52extern const std::array<RandomMiiData3, 9> RandomMiiHairColor;
53extern const std::array<RandomMiiData4, 18> RandomMiiEyeType;
54extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor;
55extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType;
56extern const std::array<RandomMiiData4, 18> RandomMiiNoseType;
57extern const std::array<RandomMiiData4, 18> RandomMiiMouthType;
58extern const std::array<RandomMiiData2, 3> RandomMiiGlassType;
59
60u8 FromVer3GetFacelineColor(u8 color);
61u8 FromVer3GetHairColor(u8 color);
62u8 FromVer3GetEyeColor(u8 color);
63u8 FromVer3GetMouthlineColor(u8 color);
64u8 FromVer3GetGlassColor(u8 color);
65u8 FromVer3GetGlassType(u8 type);
66
67FacelineColor GetFacelineColorFromVer3(u32 color);
68CommonColor GetHairColorFromVer3(u32 color);
69CommonColor GetEyeColorFromVer3(u32 color);
70CommonColor GetMouthColorFromVer3(u32 color);
71CommonColor 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
8namespace Service::Mii {
9
10void 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
84void 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
158void 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
166void 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
174bool StoreData::IsSpecial() const {
175 return GetType() == 1;
176}
177
178u32 StoreData::IsValid() const {
179 // TODO: complete this
180 return 0;
181}
182
183void StoreData::SetFontRegion(FontRegion value) {
184 core_data.SetFontRegion(value);
185}
186
187void StoreData::SetFavoriteColor(FavoriteColor value) {
188 core_data.SetFavoriteColor(value);
189}
190
191void StoreData::SetGender(Gender value) {
192 core_data.SetGender(value);
193}
194
195void StoreData::SetHeight(u8 value) {
196 core_data.SetHeight(value);
197}
198
199void StoreData::SetBuild(u8 value) {
200 core_data.SetBuild(value);
201}
202
203void StoreData::SetType(u8 value) {
204 core_data.SetType(value);
205}
206
207void StoreData::SetRegionMove(u8 value) {
208 core_data.SetRegionMove(value);
209}
210
211void StoreData::SetFacelineType(FacelineType value) {
212 core_data.SetFacelineType(value);
213}
214
215void StoreData::SetFacelineColor(FacelineColor value) {
216 core_data.SetFacelineColor(value);
217}
218
219void StoreData::SetFacelineWrinkle(FacelineWrinkle value) {
220 core_data.SetFacelineWrinkle(value);
221}
222
223void StoreData::SetFacelineMake(FacelineMake value) {
224 core_data.SetFacelineMake(value);
225}
226
227void StoreData::SetHairType(HairType value) {
228 core_data.SetHairType(value);
229}
230
231void StoreData::SetHairColor(CommonColor value) {
232 core_data.SetHairColor(value);
233}
234
235void StoreData::SetHairFlip(HairFlip value) {
236 core_data.SetHairFlip(value);
237}
238
239void StoreData::SetEyeType(EyeType value) {
240 core_data.SetEyeType(value);
241}
242
243void StoreData::SetEyeColor(CommonColor value) {
244 core_data.SetEyeColor(value);
245}
246
247void StoreData::SetEyeScale(u8 value) {
248 core_data.SetEyeScale(value);
249}
250
251void StoreData::SetEyeAspect(u8 value) {
252 core_data.SetEyeAspect(value);
253}
254
255void StoreData::SetEyeRotate(u8 value) {
256 core_data.SetEyeRotate(value);
257}
258
259void StoreData::SetEyeX(u8 value) {
260 core_data.SetEyeX(value);
261}
262
263void StoreData::SetEyeY(u8 value) {
264 core_data.SetEyeY(value);
265}
266
267void StoreData::SetEyebrowType(EyebrowType value) {
268 core_data.SetEyebrowType(value);
269}
270
271void StoreData::SetEyebrowColor(CommonColor value) {
272 core_data.SetEyebrowColor(value);
273}
274
275void StoreData::SetEyebrowScale(u8 value) {
276 core_data.SetEyebrowScale(value);
277}
278
279void StoreData::SetEyebrowAspect(u8 value) {
280 core_data.SetEyebrowAspect(value);
281}
282
283void StoreData::SetEyebrowRotate(u8 value) {
284 core_data.SetEyebrowRotate(value);
285}
286
287void StoreData::SetEyebrowX(u8 value) {
288 core_data.SetEyebrowX(value);
289}
290
291void StoreData::SetEyebrowY(u8 value) {
292 core_data.SetEyebrowY(value);
293}
294
295void StoreData::SetNoseType(NoseType value) {
296 core_data.SetNoseType(value);
297}
298
299void StoreData::SetNoseScale(u8 value) {
300 core_data.SetNoseScale(value);
301}
302
303void StoreData::SetNoseY(u8 value) {
304 core_data.SetNoseY(value);
305}
306
307void StoreData::SetMouthType(u8 value) {
308 core_data.SetMouthType(value);
309}
310
311void StoreData::SetMouthColor(CommonColor value) {
312 core_data.SetMouthColor(value);
313}
314
315void StoreData::SetMouthScale(u8 value) {
316 core_data.SetMouthScale(value);
317}
318
319void StoreData::SetMouthAspect(u8 value) {
320 core_data.SetMouthAspect(value);
321}
322
323void StoreData::SetMouthY(u8 value) {
324 core_data.SetMouthY(value);
325}
326
327void StoreData::SetBeardColor(CommonColor value) {
328 core_data.SetBeardColor(value);
329}
330
331void StoreData::SetBeardType(BeardType value) {
332 core_data.SetBeardType(value);
333}
334
335void StoreData::SetMustacheType(MustacheType value) {
336 core_data.SetMustacheType(value);
337}
338
339void StoreData::SetMustacheScale(u8 value) {
340 core_data.SetMustacheScale(value);
341}
342
343void StoreData::SetMustacheY(u8 value) {
344 core_data.SetMustacheY(value);
345}
346
347void StoreData::SetGlassType(GlassType value) {
348 core_data.SetGlassType(value);
349}
350
351void StoreData::SetGlassColor(CommonColor value) {
352 core_data.SetGlassColor(value);
353}
354
355void StoreData::SetGlassScale(u8 value) {
356 core_data.SetGlassScale(value);
357}
358
359void StoreData::SetGlassY(u8 value) {
360 core_data.SetGlassY(value);
361}
362
363void StoreData::SetMoleType(MoleType value) {
364 core_data.SetMoleType(value);
365}
366
367void StoreData::SetMoleScale(u8 value) {
368 core_data.SetMoleScale(value);
369}
370
371void StoreData::SetMoleX(u8 value) {
372 core_data.SetMoleX(value);
373}
374
375void StoreData::SetMoleY(u8 value) {
376 core_data.SetMoleY(value);
377}
378
379void StoreData::SetNickname(Nickname value) {
380 core_data.SetNickname(value);
381}
382
383Common::UUID StoreData::GetCreateId() const {
384 return create_id;
385}
386
387FontRegion StoreData::GetFontRegion() const {
388 return static_cast<FontRegion>(core_data.GetFontRegion());
389}
390
391FavoriteColor StoreData::GetFavoriteColor() const {
392 return core_data.GetFavoriteColor();
393}
394
395Gender StoreData::GetGender() const {
396 return core_data.GetGender();
397}
398
399u8 StoreData::GetHeight() const {
400 return core_data.GetHeight();
401}
402
403u8 StoreData::GetBuild() const {
404 return core_data.GetBuild();
405}
406
407u8 StoreData::GetType() const {
408 return core_data.GetType();
409}
410
411u8 StoreData::GetRegionMove() const {
412 return core_data.GetRegionMove();
413}
414
415FacelineType StoreData::GetFacelineType() const {
416 return core_data.GetFacelineType();
417}
418
419FacelineColor StoreData::GetFacelineColor() const {
420 return core_data.GetFacelineColor();
421}
422
423FacelineWrinkle StoreData::GetFacelineWrinkle() const {
424 return core_data.GetFacelineWrinkle();
425}
426
427FacelineMake StoreData::GetFacelineMake() const {
428 return core_data.GetFacelineMake();
429}
430
431HairType StoreData::GetHairType() const {
432 return core_data.GetHairType();
433}
434
435CommonColor StoreData::GetHairColor() const {
436 return core_data.GetHairColor();
437}
438
439HairFlip StoreData::GetHairFlip() const {
440 return core_data.GetHairFlip();
441}
442
443EyeType StoreData::GetEyeType() const {
444 return core_data.GetEyeType();
445}
446
447CommonColor StoreData::GetEyeColor() const {
448 return core_data.GetEyeColor();
449}
450
451u8 StoreData::GetEyeScale() const {
452 return core_data.GetEyeScale();
453}
454
455u8 StoreData::GetEyeAspect() const {
456 return core_data.GetEyeAspect();
457}
458
459u8 StoreData::GetEyeRotate() const {
460 return core_data.GetEyeRotate();
461}
462
463u8 StoreData::GetEyeX() const {
464 return core_data.GetEyeX();
465}
466
467u8 StoreData::GetEyeY() const {
468 return core_data.GetEyeY();
469}
470
471EyebrowType StoreData::GetEyebrowType() const {
472 return core_data.GetEyebrowType();
473}
474
475CommonColor StoreData::GetEyebrowColor() const {
476 return core_data.GetEyebrowColor();
477}
478
479u8 StoreData::GetEyebrowScale() const {
480 return core_data.GetEyebrowScale();
481}
482
483u8 StoreData::GetEyebrowAspect() const {
484 return core_data.GetEyebrowAspect();
485}
486
487u8 StoreData::GetEyebrowRotate() const {
488 return core_data.GetEyebrowRotate();
489}
490
491u8 StoreData::GetEyebrowX() const {
492 return core_data.GetEyebrowX();
493}
494
495u8 StoreData::GetEyebrowY() const {
496 return core_data.GetEyebrowY();
497}
498
499NoseType StoreData::GetNoseType() const {
500 return core_data.GetNoseType();
501}
502
503u8 StoreData::GetNoseScale() const {
504 return core_data.GetNoseScale();
505}
506
507u8 StoreData::GetNoseY() const {
508 return core_data.GetNoseY();
509}
510
511MouthType StoreData::GetMouthType() const {
512 return core_data.GetMouthType();
513}
514
515CommonColor StoreData::GetMouthColor() const {
516 return core_data.GetMouthColor();
517}
518
519u8 StoreData::GetMouthScale() const {
520 return core_data.GetMouthScale();
521}
522
523u8 StoreData::GetMouthAspect() const {
524 return core_data.GetMouthAspect();
525}
526
527u8 StoreData::GetMouthY() const {
528 return core_data.GetMouthY();
529}
530
531CommonColor StoreData::GetBeardColor() const {
532 return core_data.GetBeardColor();
533}
534
535BeardType StoreData::GetBeardType() const {
536 return core_data.GetBeardType();
537}
538
539MustacheType StoreData::GetMustacheType() const {
540 return core_data.GetMustacheType();
541}
542
543u8 StoreData::GetMustacheScale() const {
544 return core_data.GetMustacheScale();
545}
546
547u8 StoreData::GetMustacheY() const {
548 return core_data.GetMustacheY();
549}
550
551GlassType StoreData::GetGlassType() const {
552 return core_data.GetGlassType();
553}
554
555CommonColor StoreData::GetGlassColor() const {
556 return core_data.GetGlassColor();
557}
558
559u8 StoreData::GetGlassScale() const {
560 return core_data.GetGlassScale();
561}
562
563u8 StoreData::GetGlassY() const {
564 return core_data.GetGlassY();
565}
566
567MoleType StoreData::GetMoleType() const {
568 return core_data.GetMoleType();
569}
570
571u8 StoreData::GetMoleScale() const {
572 return core_data.GetMoleScale();
573}
574
575u8 StoreData::GetMoleX() const {
576 return core_data.GetMoleX();
577}
578
579u8 StoreData::GetMoleY() const {
580 return core_data.GetMoleY();
581}
582
583Nickname StoreData::GetNickname() const {
584 return core_data.GetNickname();
585}
586
587bool 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
9namespace Service::Mii {
10
11class StoreData {
12public:
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
131private:
132 CoreData core_data{};
133 Common::UUID create_id{};
134 u16 data_crc{};
135 u16 device_crc{};
136};
137static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
138
139struct StoreDataElement {
140 StoreData store_data{};
141 Source source{};
142};
143static_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
9namespace Service::Mii {
10
11void 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
22void 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
100void 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
175u32 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
8namespace Service::Mii {
9class 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
16struct 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};
28static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
29
30#pragma pack(push, 4)
31class Ver3StoreData {
32public:
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};
157static_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
870Result NfcDevice::Format() { 876Result 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
1454void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, 1462void 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
12namespace Service::NFP { 14namespace 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
324struct RegisterInfoPrivate { 326struct 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
11namespace Service::NGC {
12
13class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
14public:
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
26private:
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
55class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
56public:
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
70private:
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
142void 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 {
7class System; 7class System;
8} 8}
9 9
10namespace Service::NGCT { 10namespace Service::NGC {
11 11
12void LoopProcess(Core::System& system); 12void 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
11namespace Service::NGCT {
12
13class IService final : public ServiceFramework<IService> {
14public:
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
26private:
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
55void 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
172void BSD::Select(HLERequestContext& ctx) { 172void 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
1032std::unique_lock<std::mutex> BSD::LockService() {
1033 // Do not lock socket IClient instances.
1034 return {};
1035}
1036
1032BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { 1037BSDCFG::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
190protected:
191 virtual std::unique_lock<std::mutex> LockService() override;
189}; 192};
190 193
191class BSDCFG final : public ServiceFramework<BSDCFG> { 194class 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
23struct EnvironmentIdentifier {
24 std::array<u8, 8> identifier;
25};
26static_assert(sizeof(EnvironmentIdentifier) == 0x8);
27
22NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { 28NSD::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
103void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { 109void 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;
31static void OneTimeInit() { 31static 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
40namespace { 40namespace {
41 41
42enum class CallType {
43 Send,
44 Other,
45};
46
42#ifdef _WIN32 47#ifdef _WIN32
43 48
44using socklen_t = int; 49using socklen_t = int;
45 50
51SOCKET interrupt_socket = static_cast<SOCKET>(-1);
52
53void InterruptSocketOperations() {
54 closesocket(interrupt_socket);
55}
56
57void AcknowledgeInterrupt() {
58 interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
59}
60
46void Initialize() { 61void 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
51void Finalize() { 68void Finalize() {
69 InterruptSocketOperations();
52 WSACleanup(); 70 WSACleanup();
53} 71}
54 72
73SOCKET GetInterruptSocket() {
74 return interrupt_socket;
75}
76
55sockaddr TranslateFromSockAddrIn(SockAddrIn input) { 77sockaddr 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
99Errno TranslateNativeError(int e) { 121Errno 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;
144constexpr int SD_SEND = SHUT_WR; 174constexpr int SD_SEND = SHUT_WR;
145constexpr int SD_BOTH = SHUT_RDWR; 175constexpr int SD_BOTH = SHUT_RDWR;
146 176
147void Initialize() {} 177int interrupt_pipe_fd[2] = {-1, -1};
148 178
149void Finalize() {} 179void 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
188void 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
197void InterruptSocketOperations() {
198 u8 value = 0;
199 ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1);
200}
201
202void 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
210SOCKET GetInterruptSocket() {
211 return interrupt_pipe_fd[0];
212}
150 213
151sockaddr TranslateFromSockAddrIn(SockAddrIn input) { 214sockaddr 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
201Errno TranslateNativeError(int e) { 264Errno 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
239Errno GetAndLogLastError() { 306Errno 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
543void CancelPendingSocketOperations() {
544 InterruptSocketOperations();
545}
546
547void RestartSocketOperations() {
548 AcknowledgeInterrupt();
549}
550
476std::optional<IPv4Address> GetHostIPv4Address() { 551std::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) {
604std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { 692std::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
737std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, 847std::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
759Errno Socket::Close() { 869Errno 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
99void CancelPendingSocketOperations();
100void RestartSocketOperations();
101
97#ifdef _WIN32 102#ifdef _WIN32
98constexpr IPv4Address TranslateIPv4(in_addr addr) { 103constexpr 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
19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, 19AppLoader_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
71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( 71AppLoader_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
76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { 76FileType 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
111constexpr std::array<const char*, 66> RESULT_MESSAGES{ 111constexpr 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
180std::string GetResultStatusString(ResultStatus status) { 182std::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
139std::string GetResultStatusString(ResultStatus status); 140std::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
79u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
80 return nca_loader->ReadRomFSIVFCOffset();
81}
82
83ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { 79ResultStatus 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
14namespace Loader { 19namespace 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
86ResultStatus 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
67ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { 159ResultStatus 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
80u64 AppLoader_NCA::ReadRomFSIVFCOffset() const {
81 if (nca == nullptr) {
82 return 0;
83 }
84
85 return nca->GetBaseIVFCOffset();
86}
87
88ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { 172ResultStatus 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
120ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { 121ResultStatus 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
124u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { 157ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
125 return secondary_loader->ReadRomFSIVFCOffset(); 158 return secondary_loader->ReadRomFS(out_file);
126} 159}
127 160
128ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { 161ResultStatus 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
88ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { 88ResultStatus 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
92u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { 122ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
93 return nca_loader->ReadRomFSIVFCOffset(); 123 return nca_loader->ReadRomFS(out_file);
94} 124}
95 125
96ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { 126ResultStatus 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
510public: 510public:
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
612protected: 620protected:
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
631template <typename M, typename T, GuestMemoryFlags FLAGS> 639template <typename M, typename T, GuestMemoryFlags FLAGS>
632class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { 640class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> {
633public: 641public:
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
124json GetBacktraceData(Core::System& system) { 124json 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
281bool TelemetrySession::SubmitTestcase() { 282bool 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
16namespace Tools {
17
18RenderdocAPI::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
41RenderdocAPI::~RenderdocAPI() = default;
42
43void 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
6struct RENDERDOC_API_1_6_0;
7
8namespace Tools {
9
10class RenderdocAPI {
11public:
12 explicit RenderdocAPI();
13 ~RenderdocAPI();
14
15 void ToggleCapture();
16
17private:
18 RENDERDOC_API_1_6_0* rdoc_api{};
19 bool is_capturing{false};
20};
21
22} // namespace Tools