summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/CMakeLists.txt74
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp6
-rw-r--r--src/core/core.cpp72
-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/control_metadata.cpp3
-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/partition_filesystem.cpp1
-rw-r--r--src/core/file_sys/patch_manager.cpp56
-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.cpp34
-rw-r--r--src/core/file_sys/romfs_factory.h23
-rw-r--r--src/core/file_sys/savedata_factory.cpp22
-rw-r--r--src/core/file_sys/savedata_factory.h4
-rw-r--r--src/core/file_sys/sdmc_factory.cpp2
-rw-r--r--src/core/file_sys/sdmc_factory.h2
-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/board/nintendo/nx/k_system_control.cpp22
-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_page_table.cpp2
-rw-r--r--src/core/hle/kernel/k_process.cpp33
-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.h155
-rw-r--r--src/core/hle/service/acc/acc.cpp11
-rw-r--r--src/core/hle/service/am/am.cpp154
-rw-r--r--src/core/hle/service/am/am.h32
-rw-r--r--src/core/hle/service/am/applet_ae.cpp8
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp53
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.h7
-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/aoc/aoc_u.cpp20
-rw-r--r--src/core/hle/service/apm/apm_controller.cpp4
-rw-r--r--src/core/hle/service/audio/audctl.cpp46
-rw-r--r--src/core/hle/service/audio/audctl.h22
-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.cpp7
-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.cpp162
-rw-r--r--src/core/hle/service/filesystem/filesystem.h44
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp76
-rw-r--r--src/core/hle/service/glue/arp.cpp36
-rw-r--r--src/core/hle/service/glue/glue_manager.cpp11
-rw-r--r--src/core/hle/service/glue/glue_manager.h4
-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/ldr/ldr.cpp46
-rw-r--r--src/core/hle/service/mii/mii.cpp514
-rw-r--r--src/core/hle/service/mii/mii.h18
-rw-r--r--src/core/hle/service/mii/mii_database.cpp142
-rw-r--r--src/core/hle/service/mii/mii_database.h66
-rw-r--r--src/core/hle/service/mii/mii_database_manager.cpp420
-rw-r--r--src/core/hle/service/mii/mii_database_manager.h58
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp948
-rw-r--r--src/core/hle/service/mii/mii_manager.h84
-rw-r--r--src/core/hle/service/mii/mii_result.h27
-rw-r--r--src/core/hle/service/mii/mii_types.h692
-rw-r--r--src/core/hle/service/mii/mii_util.h85
-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.cpp805
-rw-r--r--src/core/hle/service/mii/types/core_data.h219
-rw-r--r--src/core/hle/service/mii/types/raw_data.cpp (renamed from src/core/hle/service/mii/raw_data.cpp)1412
-rw-r--r--src/core/hle/service/mii/types/raw_data.h73
-rw-r--r--src/core/hle/service/mii/types/store_data.cpp676
-rw-r--r--src/core/hle/service/mii/types/store_data.h150
-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.cpp47
-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/ns/ns.cpp43
-rw-r--r--src/core/hle/service/ns/ns.h5
-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/nvnflinger.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h2
-rw-r--r--src/core/hle/service/nvnflinger/window.h1
-rw-r--r--src/core/hle/service/olsc/olsc.cpp158
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp144
-rw-r--r--src/core/hle/service/server_manager.cpp9
-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/set/set.cpp70
-rw-r--r--src/core/hle/service/set/set.h61
-rw-r--r--src/core/hle/service/set/set_sys.cpp375
-rw-r--r--src/core/hle/service/set/set_sys.h322
-rw-r--r--src/core/hle/service/sm/sm.cpp43
-rw-r--r--src/core/hle/service/sm/sm.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.cpp20
-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/spl/spl_module.cpp51
-rw-r--r--src/core/hle/service/spl/spl_module.h2
-rw-r--r--src/core/hle/service/ssl/ssl.cpp96
-rw-r--r--src/core/hle/service/ssl/ssl_backend.h8
-rw-r--r--src/core/hle/service/ssl/ssl_backend_none.cpp2
-rw-r--r--src/core/hle/service/ssl/ssl_backend_openssl.cpp48
-rw-r--r--src/core/hle/service/ssl/ssl_backend_schannel.cpp90
-rw-r--r--src/core/hle/service/ssl/ssl_backend_securetransport.cpp41
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp20
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.h2
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp5
-rw-r--r--src/core/hle/service/vi/display/vi_display.h2
-rw-r--r--src/core/hle/service/vi/vi.cpp37
-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.cpp21
-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.cpp37
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
224 files changed, 17768 insertions, 4561 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4b7395be8..d0f76e57e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -37,6 +37,49 @@ add_library(core STATIC
debugger/gdbstub.h
device_memory.cpp
device_memory.h
+ file_sys/fssystem/fs_i_storage.h
+ file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
+ file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
+ file_sys/fssystem/fssystem_aes_ctr_storage.cpp
+ file_sys/fssystem/fssystem_aes_ctr_storage.h
+ file_sys/fssystem/fssystem_aes_xts_storage.cpp
+ file_sys/fssystem/fssystem_aes_xts_storage.h
+ file_sys/fssystem/fssystem_alignment_matching_storage.h
+ file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp
+ file_sys/fssystem/fssystem_alignment_matching_storage_impl.h
+ file_sys/fssystem/fssystem_bucket_tree.cpp
+ file_sys/fssystem/fssystem_bucket_tree.h
+ file_sys/fssystem/fssystem_bucket_tree_utils.h
+ file_sys/fssystem/fssystem_compressed_storage.h
+ file_sys/fssystem/fssystem_compression_common.h
+ file_sys/fssystem/fssystem_compression_configuration.cpp
+ file_sys/fssystem/fssystem_compression_configuration.h
+ file_sys/fssystem/fssystem_crypto_configuration.cpp
+ file_sys/fssystem/fssystem_crypto_configuration.h
+ file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
+ file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
+ file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
+ file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
+ file_sys/fssystem/fssystem_indirect_storage.cpp
+ file_sys/fssystem/fssystem_indirect_storage.h
+ file_sys/fssystem/fssystem_integrity_romfs_storage.cpp
+ file_sys/fssystem/fssystem_integrity_romfs_storage.h
+ file_sys/fssystem/fssystem_integrity_verification_storage.cpp
+ file_sys/fssystem/fssystem_integrity_verification_storage.h
+ file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h
+ file_sys/fssystem/fssystem_nca_file_system_driver.cpp
+ file_sys/fssystem/fssystem_nca_file_system_driver.h
+ file_sys/fssystem/fssystem_nca_header.cpp
+ file_sys/fssystem/fssystem_nca_header.h
+ file_sys/fssystem/fssystem_nca_reader.cpp
+ file_sys/fssystem/fssystem_pooled_buffer.cpp
+ file_sys/fssystem/fssystem_pooled_buffer.h
+ file_sys/fssystem/fssystem_sparse_storage.cpp
+ file_sys/fssystem/fssystem_sparse_storage.h
+ file_sys/fssystem/fssystem_switch_storage.h
+ file_sys/fssystem/fssystem_utility.cpp
+ file_sys/fssystem/fssystem_utility.h
+ file_sys/fssystem/fs_types.h
file_sys/bis_factory.cpp
file_sys/bis_factory.h
file_sys/card_image.cpp
@@ -57,8 +100,6 @@ add_library(core STATIC
file_sys/mode.h
file_sys/nca_metadata.cpp
file_sys/nca_metadata.h
- file_sys/nca_patch.cpp
- file_sys/nca_patch.h
file_sys/partition_filesystem.cpp
file_sys/partition_filesystem.h
file_sys/patch_manager.cpp
@@ -543,13 +584,27 @@ add_library(core STATIC
hle/service/lm/lm.h
hle/service/mig/mig.cpp
hle/service/mig/mig.h
+ hle/service/mii/types/char_info.cpp
+ hle/service/mii/types/char_info.h
+ hle/service/mii/types/core_data.cpp
+ hle/service/mii/types/core_data.h
+ hle/service/mii/types/raw_data.cpp
+ hle/service/mii/types/raw_data.h
+ hle/service/mii/types/store_data.cpp
+ hle/service/mii/types/store_data.h
+ hle/service/mii/types/ver3_store_data.cpp
+ hle/service/mii/types/ver3_store_data.h
hle/service/mii/mii.cpp
hle/service/mii/mii.h
+ hle/service/mii/mii_database.cpp
+ hle/service/mii/mii_database.h
+ hle/service/mii/mii_database_manager.cpp
+ hle/service/mii/mii_database_manager.h
hle/service/mii/mii_manager.cpp
hle/service/mii/mii_manager.h
- hle/service/mii/raw_data.cpp
- hle/service/mii/raw_data.h
- hle/service/mii/types.h
+ hle/service/mii/mii_result.h
+ hle/service/mii/mii_types.h
+ hle/service/mii/mii_util.h
hle/service/mm/mm_u.cpp
hle/service/mm/mm_u.h
hle/service/mnpp/mnpp_app.cpp
@@ -576,8 +631,8 @@ add_library(core STATIC
hle/service/nfp/nfp_interface.h
hle/service/nfp/nfp_result.h
hle/service/nfp/nfp_types.h
- hle/service/ngct/ngct.cpp
- hle/service/ngct/ngct.h
+ hle/service/ngc/ngc.cpp
+ hle/service/ngc/ngc.h
hle/service/nifm/nifm.cpp
hle/service/nifm/nifm.h
hle/service/nim/nim.cpp
@@ -813,6 +868,8 @@ add_library(core STATIC
telemetry_session.h
tools/freezer.cpp
tools/freezer.h
+ tools/renderdoc.cpp
+ tools/renderdoc.h
)
if (MSVC)
@@ -828,6 +885,7 @@ else()
-Werror=conversion
-Wno-sign-conversion
+ -Wno-cast-function-type
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
)
@@ -836,7 +894,7 @@ endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
-target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus)
+target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API)
if (MINGW)
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
endif()
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index c97158a71..44a297cdc 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -287,7 +287,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
} else {
// Unsafe optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) {
config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
@@ -307,7 +307,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
// Curated optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Auto) {
config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
@@ -316,7 +316,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
// Paranoia mode for debugging optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) {
config.unsafe_optimizations = false;
config.optimizations = Dynarmic::no_optimizations;
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 791d466ca..2e3674b6d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -347,7 +347,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
}
} else {
// Unsafe optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) {
config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
@@ -367,7 +367,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
}
// Curated optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Auto) {
config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.fastmem_address_space_bits = 64;
@@ -375,7 +375,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
}
// Paranoia mode for debugging optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) {
config.unsafe_optimizations = false;
config.optimizations = Dynarmic::no_optimizations;
}
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 48233d7c8..e8300cd05 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -12,6 +12,7 @@
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
@@ -50,6 +51,7 @@
#include "core/reporter.h"
#include "core/telemetry_session.h"
#include "core/tools/freezer.h"
+#include "core/tools/renderdoc.h"
#include "network/network.h"
#include "video_core/host1x/host1x.h"
#include "video_core/renderer_base.h"
@@ -140,16 +142,13 @@ struct System::Impl {
device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue();
- extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
+ extended_memory_layout =
+ Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
- const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
- const auto current_time =
- std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
- Settings::values.custom_rtc_differential =
- Settings::values.custom_rtc.value_or(current_time) - current_time;
+ RefreshTime();
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr) {
@@ -172,7 +171,8 @@ struct System::Impl {
void ReinitializeIfNecessary(System& system) {
const bool must_reinitialize =
is_multicore != Settings::values.use_multi_core.GetValue() ||
- extended_memory_layout != Settings::values.use_unsafe_extended_memory_layout.GetValue();
+ extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
+ Settings::MemoryLayout::Memory_4Gb);
if (!must_reinitialize) {
return;
@@ -181,11 +181,22 @@ struct System::Impl {
LOG_DEBUG(Kernel, "Re-initializing");
is_multicore = Settings::values.use_multi_core.GetValue();
- extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
+ extended_memory_layout =
+ Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
Initialize(system);
}
+ void RefreshTime() {
+ const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
+ const auto current_time =
+ std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
+ Settings::values.custom_rtc_differential =
+ (Settings::values.custom_rtc_enabled ? Settings::values.custom_rtc.GetValue()
+ : current_time) -
+ current_time;
+ }
+
void Run() {
std::unique_lock<std::mutex> lk(suspend_guard);
@@ -263,13 +274,18 @@ struct System::Impl {
time_manager.Initialize();
is_powered_on = true;
- exit_lock = false;
+ exit_locked = false;
+ exit_requested = false;
microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0);
microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1);
microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
+ if (Settings::values.enable_renderdoc_hotkey) {
+ renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
+ }
+
LOG_DEBUG(Core, "Initialized OK");
return SystemResultStatus::Success;
@@ -388,12 +404,14 @@ struct System::Impl {
}
is_powered_on = false;
- exit_lock = false;
+ exit_locked = false;
+ exit_requested = false;
if (gpu_core != nullptr) {
gpu_core->NotifyShutdown();
}
+ Network::CancelPendingSocketOperations();
kernel.SuspendApplication(true);
if (services) {
services->KillNVNFlinger();
@@ -415,6 +433,7 @@ struct System::Impl {
debugger.reset();
kernel.Shutdown();
memory.Reset();
+ Network::RestartSocketOperations();
if (auto room_member = room_network.GetRoomMember().lock()) {
Network::GameInfo game_info{};
@@ -497,7 +516,8 @@ struct System::Impl {
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};
- bool exit_lock = false;
+ bool exit_locked = false;
+ bool exit_requested = false;
bool nvdec_active{};
@@ -506,6 +526,8 @@ struct System::Impl {
std::unique_ptr<Tools::Freezer> memory_freezer;
std::array<u8, 0x20> build_id{};
+ std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
+
/// Frontend applets
Service::AM::Applets::AppletManager applet_manager;
@@ -549,6 +571,8 @@ struct System::Impl {
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
gpu_dirty_memory_write_manager{};
+
+ std::deque<std::vector<u8>> user_channel;
};
System::System() : impl{std::make_unique<Impl>(*this)} {}
@@ -933,12 +957,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const {
return impl->time_manager;
}
-void System::SetExitLock(bool locked) {
- impl->exit_lock = locked;
+void System::SetExitLocked(bool locked) {
+ impl->exit_locked = locked;
+}
+
+bool System::GetExitLocked() const {
+ return impl->exit_locked;
+}
+
+void System::SetExitRequested(bool requested) {
+ impl->exit_requested = requested;
}
-bool System::GetExitLock() const {
- return impl->exit_lock;
+bool System::GetExitRequested() const {
+ return impl->exit_requested;
}
void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) {
@@ -999,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
return impl->room_network;
}
+Tools::RenderdocAPI& System::GetRenderdocAPI() {
+ return *impl->renderdoc_api;
+}
+
void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
return impl->kernel.RunServer(std::move(server_manager));
}
@@ -1015,6 +1051,10 @@ void System::ExecuteProgram(std::size_t program_index) {
}
}
+std::deque<std::vector<u8>>& System::GetUserChannel() {
+ return impl->user_channel;
+}
+
void System::RegisterExitCallback(ExitCallback&& callback) {
impl->exit_callback = std::move(callback);
}
@@ -1028,6 +1068,8 @@ void System::Exit() {
}
void System::ApplySettings() {
+ impl->RefreshTime();
+
if (IsPoweredOn()) {
Renderer().RefreshBaseSettings();
}
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 @@
#pragma once
#include <cstddef>
+#include <deque>
#include <functional>
#include <memory>
#include <mutex>
@@ -101,6 +102,10 @@ namespace Network {
class RoomNetwork;
}
+namespace Tools {
+class RenderdocAPI;
+}
+
namespace Core {
class ARM_Interface;
@@ -412,8 +417,13 @@ public:
/// Gets an immutable reference to the Room Network.
[[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
- void SetExitLock(bool locked);
- [[nodiscard]] bool GetExitLock() const;
+ [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
+
+ void SetExitLocked(bool locked);
+ bool GetExitLocked() const;
+
+ void SetExitRequested(bool requested);
+ bool GetExitRequested() const;
void SetApplicationProcessBuildID(const CurrentBuildProcessID& id);
[[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const;
@@ -456,6 +466,12 @@ public:
*/
void ExecuteProgram(std::size_t program_index);
+ /**
+ * Gets a reference to the user channel stack.
+ * It is used to transfer data between programs.
+ */
+ [[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel();
+
/// Type used for the frontend to designate a callback for System to exit the application.
using ExitCallback = std::function<void()>;
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 {
namespace {
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
-constexpr u64 FULL_TICKET_SIZE = 0x400;
using Common::AsArray;
@@ -156,6 +155,10 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
UNREACHABLE();
}
+bool Ticket::IsValid() const {
+ return !std::holds_alternative<std::monostate>(data);
+}
+
SignatureType Ticket::GetSignatureType() const {
if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->sig_type;
@@ -210,6 +213,54 @@ Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& righ
return Ticket{out};
}
+Ticket Ticket::Read(const FileSys::VirtualFile& file) {
+ // Attempt to read up to the largest ticket size, and make sure we read at least a signature
+ // type.
+ std::array<u8, sizeof(RSA4096Ticket)> raw_data{};
+ auto read_size = file->Read(raw_data.data(), raw_data.size(), 0);
+ if (read_size < sizeof(SignatureType)) {
+ LOG_WARNING(Crypto, "Attempted to read ticket file with invalid size {}.", read_size);
+ return Ticket{std::monostate()};
+ }
+ return Read(std::span{raw_data});
+}
+
+Ticket Ticket::Read(std::span<const u8> raw_data) {
+ // Some tools read only 0x180 bytes of ticket data instead of 0x2C0, so
+ // just make sure we have at least the bare minimum of data to work with.
+ SignatureType sig_type;
+ if (raw_data.size() < sizeof(SignatureType)) {
+ LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.",
+ raw_data.size());
+ return Ticket{std::monostate()};
+ }
+ std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type));
+
+ switch (sig_type) {
+ case SignatureType::RSA_4096_SHA1:
+ case SignatureType::RSA_4096_SHA256: {
+ RSA4096Ticket ticket{};
+ std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
+ return Ticket{ticket};
+ }
+ case SignatureType::RSA_2048_SHA1:
+ case SignatureType::RSA_2048_SHA256: {
+ RSA2048Ticket ticket{};
+ std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
+ return Ticket{ticket};
+ }
+ case SignatureType::ECDSA_SHA1:
+ case SignatureType::ECDSA_SHA256: {
+ ECDSATicket ticket{};
+ std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
+ return Ticket{ticket};
+ }
+ default:
+ LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid type {}.", sig_type);
+ return Ticket{std::monostate()};
+ }
+}
+
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
Key128 out{};
@@ -290,9 +341,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
}
}
-RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
+void KeyManager::DeriveETicketRSAKey() {
if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) {
- return {};
+ return;
}
const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
@@ -304,12 +355,12 @@ RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
extended_dec.data(), Op::Decrypt);
- RSAKeyPair<2048> rsa_key{};
- std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
- std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
- std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
-
- return rsa_key;
+ std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(),
+ eticket_rsa_keypair.decryption_key.size());
+ std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100,
+ eticket_rsa_keypair.modulus.size());
+ std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200,
+ eticket_rsa_keypair.exponent.size());
}
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
@@ -447,10 +498,12 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
buffer[offset + 3] == 0x0) {
- out.emplace_back();
- auto& next = out.back();
- std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
- offset += FULL_TICKET_SIZE;
+ // NOTE: Assumes ticket blob will only contain RSA-2048 tickets.
+ auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)});
+ offset += sizeof(RSA2048Ticket);
+ if (ticket.IsValid()) {
+ out.push_back(ticket);
+ }
}
}
@@ -503,25 +556,36 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
return offset;
}
-std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
- const RSAKeyPair<2048>& key) {
+std::optional<Key128> KeyManager::ParseTicketTitleKey(const Ticket& ticket) {
+ if (!ticket.IsValid()) {
+ LOG_WARNING(Crypto, "Attempted to parse title key of invalid ticket.");
+ return std::nullopt;
+ }
+
+ if (ticket.GetData().rights_id == Key128{}) {
+ LOG_WARNING(Crypto, "Attempted to parse title key of ticket with no rights ID.");
+ return std::nullopt;
+ }
+
const auto issuer = ticket.GetData().issuer;
if (IsAllZeroArray(issuer)) {
+ LOG_WARNING(Crypto, "Attempted to parse title key of ticket with invalid issuer.");
return std::nullopt;
}
+
if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
- LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
+ LOG_WARNING(Crypto, "Parsing ticket with non-standard certificate authority.");
}
- Key128 rights_id = ticket.GetData().rights_id;
-
- if (rights_id == Key128{}) {
- return std::nullopt;
+ if (ticket.GetData().type == TitleKeyType::Common) {
+ return ticket.GetData().title_key_common;
}
- if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
- ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
- return std::make_pair(rights_id, ticket.GetData().title_key_common);
+ if (eticket_rsa_keypair == RSAKeyPair<2048>{}) {
+ LOG_WARNING(
+ Crypto,
+ "Skipping personalized ticket title key parsing due to missing ETicket RSA key-pair.");
+ return std::nullopt;
}
mbedtls_mpi D; // RSA Private Exponent
@@ -534,9 +598,12 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
mbedtls_mpi_init(&S);
mbedtls_mpi_init(&M);
- mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
- mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
- mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100);
+ const auto& title_key_block = ticket.GetData().title_key_block;
+ mbedtls_mpi_read_binary(&D, eticket_rsa_keypair.decryption_key.data(),
+ eticket_rsa_keypair.decryption_key.size());
+ mbedtls_mpi_read_binary(&N, eticket_rsa_keypair.modulus.data(),
+ eticket_rsa_keypair.modulus.size());
+ mbedtls_mpi_read_binary(&S, title_key_block.data(), title_key_block.size());
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
@@ -564,8 +631,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
Key128 key_temp{};
std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
-
- return std::make_pair(rights_id, key_temp);
+ return key_temp;
}
KeyManager::KeyManager() {
@@ -658,17 +724,25 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
continue;
}
- const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
+ const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
if (!ValidCryptoRevisionString(out[0], 18, 2)) {
continue;
}
- const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
+ const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
} else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
+ } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) {
+ const auto key_data = Common::HexStringToArray<528>(out[1]);
+ std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(),
+ eticket_rsa_keypair.decryption_key.size());
+ std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100,
+ eticket_rsa_keypair.modulus.size());
+ std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200,
+ eticket_rsa_keypair.exponent.size());
} else {
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) {
@@ -676,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
}
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
const auto index =
- std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
+ std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
const auto sub = kv.first.second;
if (sub == 0) {
s128_keys[{kv.first.first, index, 0}] =
@@ -696,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
const auto& match = kak_names[j];
if (out[0].compare(0, std::strlen(match), match) == 0) {
const auto index =
- std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
+ std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
s128_keys[{S128KeyType::KeyArea, index, j}] =
Common::HexStringToArray<16>(out[1]);
}
@@ -1110,56 +1184,38 @@ void KeyManager::DeriveETicket(PartitionDataManager& data,
eticket_extended_kek = data.GetETicketExtendedKek();
WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
+ DeriveETicketRSAKey();
PopulateTickets();
}
void KeyManager::PopulateTickets() {
- const auto rsa_key = GetETicketRSAKey();
-
- if (rsa_key == RSAKeyPair<2048>{}) {
+ if (ticket_databases_loaded) {
return;
}
+ ticket_databases_loaded = true;
- if (!common_tickets.empty() && !personal_tickets.empty()) {
- return;
- }
+ std::vector<Ticket> tickets;
const auto system_save_e1_path =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1";
-
- const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read,
- Common::FS::FileType::BinaryFile};
+ if (Common::FS::Exists(system_save_e1_path)) {
+ const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+ const auto blob1 = GetTicketblob(save_e1);
+ tickets.insert(tickets.end(), blob1.begin(), blob1.end());
+ }
const auto system_save_e2_path =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2";
+ if (Common::FS::Exists(system_save_e2_path)) {
+ const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+ const auto blob2 = GetTicketblob(save_e2);
+ tickets.insert(tickets.end(), blob2.begin(), blob2.end());
+ }
- const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read,
- Common::FS::FileType::BinaryFile};
-
- const auto blob2 = GetTicketblob(save_e2);
- auto res = GetTicketblob(save_e1);
-
- const auto idx = res.size();
- res.insert(res.end(), blob2.begin(), blob2.end());
-
- for (std::size_t i = 0; i < res.size(); ++i) {
- const auto common = i < idx;
- const auto pair = ParseTicket(res[i], rsa_key);
- if (!pair) {
- continue;
- }
-
- const auto& [rid, key] = *pair;
- u128 rights_id;
- std::memcpy(rights_id.data(), rid.data(), rid.size());
-
- if (common) {
- common_tickets[rights_id] = res[i];
- } else {
- personal_tickets[rights_id] = res[i];
- }
-
- SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ for (const auto& ticket : tickets) {
+ AddTicket(ticket);
}
}
@@ -1291,41 +1347,33 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
return personal_tickets;
}
-bool KeyManager::AddTicketCommon(Ticket raw) {
- const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{}) {
- return false;
- }
-
- const auto pair = ParseTicket(raw, rsa_key);
- if (!pair) {
+bool KeyManager::AddTicket(const Ticket& ticket) {
+ if (!ticket.IsValid()) {
+ LOG_WARNING(Crypto, "Attempted to add invalid ticket.");
return false;
}
- const auto& [rid, key] = *pair;
+ const auto& rid = ticket.GetData().rights_id;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
- common_tickets[rights_id] = raw;
- SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
- return true;
-}
+ if (ticket.GetData().type == Core::Crypto::TitleKeyType::Common) {
+ common_tickets[rights_id] = ticket;
+ } else {
+ personal_tickets[rights_id] = ticket;
+ }
-bool KeyManager::AddTicketPersonalized(Ticket raw) {
- const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{}) {
- return false;
+ if (HasKey(S128KeyType::Titlekey, rights_id[1], rights_id[0])) {
+ LOG_DEBUG(Crypto,
+ "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.",
+ rights_id[1], rights_id[0]);
+ return true;
}
- const auto pair = ParseTicket(raw, rsa_key);
- if (!pair) {
+ const auto key = ParseTicketTitleKey(ticket);
+ if (!key) {
return false;
}
-
- const auto& [rid, key] = *pair;
- u128 rights_id;
- std::memcpy(rights_id.data(), rid.data(), rid.size());
- common_tickets[rights_id] = raw;
- SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]);
return true;
}
} // 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 @@
#include <filesystem>
#include <map>
#include <optional>
+#include <span>
#include <string>
#include <variant>
@@ -29,8 +30,6 @@ enum class ResultStatus : u16;
namespace Core::Crypto {
-constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
-
using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>;
@@ -82,6 +81,7 @@ struct RSA4096Ticket {
INSERT_PADDING_BYTES(0x3C);
TicketData data;
};
+static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size.");
struct RSA2048Ticket {
SignatureType sig_type;
@@ -89,6 +89,7 @@ struct RSA2048Ticket {
INSERT_PADDING_BYTES(0x3C);
TicketData data;
};
+static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size.");
struct ECDSATicket {
SignatureType sig_type;
@@ -96,16 +97,41 @@ struct ECDSATicket {
INSERT_PADDING_BYTES(0x40);
TicketData data;
};
+static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size.");
struct Ticket {
- std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
-
- SignatureType GetSignatureType() const;
- TicketData& GetData();
- const TicketData& GetData() const;
- u64 GetSize() const;
-
+ std::variant<std::monostate, RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
+
+ [[nodiscard]] bool IsValid() const;
+ [[nodiscard]] SignatureType GetSignatureType() const;
+ [[nodiscard]] TicketData& GetData();
+ [[nodiscard]] const TicketData& GetData() const;
+ [[nodiscard]] u64 GetSize() const;
+
+ /**
+ * Synthesizes a common ticket given a title key and rights ID.
+ *
+ * @param title_key Title key to store in the ticket.
+ * @param rights_id Rights ID the ticket is for.
+ * @return The synthesized common ticket.
+ */
static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
+
+ /**
+ * Reads a ticket from a file.
+ *
+ * @param file File to read the ticket from.
+ * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false.
+ */
+ static Ticket Read(const FileSys::VirtualFile& file);
+
+ /**
+ * Reads a ticket from a memory buffer.
+ *
+ * @param raw_data Buffer to read the ticket from.
+ * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false.
+ */
+ static Ticket Read(std::span<const u8> raw_data);
};
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
@@ -264,8 +290,7 @@ public:
const std::map<u128, Ticket>& GetCommonTickets() const;
const std::map<u128, Ticket>& GetPersonalizedTickets() const;
- bool AddTicketCommon(Ticket raw);
- bool AddTicketPersonalized(Ticket raw);
+ bool AddTicket(const Ticket& ticket);
void ReloadKeys();
bool AreKeysLoaded() const;
@@ -279,10 +304,12 @@ private:
// Map from rights ID to ticket
std::map<u128, Ticket> common_tickets;
std::map<u128, Ticket> personal_tickets;
+ bool ticket_databases_loaded = false;
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
std::array<u8, 576> eticket_extended_kek{};
+ RSAKeyPair<2048> eticket_rsa_keypair{};
bool dev_mode;
void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys);
@@ -293,10 +320,13 @@ private:
void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
- RSAKeyPair<2048> GetETicketRSAKey() const;
+ void DeriveETicketRSAKey();
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
+
+ /// Parses the title key section of a ticket.
+ std::optional<Key128> ParseTicketTitleKey(const Ticket& ticket);
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
@@ -311,9 +341,4 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save);
-// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
-// (offset 0x140-0x144 is zero)
-std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
- const RSAKeyPair<2048>& eticket_extended_key);
-
} // 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
std::vector<u8> mem(size);
if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) {
+ // Restore any bytes belonging to replaced instructions.
+ auto it = replaced_instructions.lower_bound(addr);
+ for (; it != replaced_instructions.end() && it->first < addr + size; it++) {
+ // Get the bytes of the instruction we previously replaced.
+ const u32 original_bytes = it->second;
+
+ // Calculate where to start writing to the output buffer.
+ const size_t output_offset = it->first - addr;
+
+ // Calculate how many bytes to write.
+ // The loop condition ensures output_offset < size.
+ const size_t n = std::min<size_t>(size - output_offset, sizeof(u32));
+
+ // Write the bytes to the output buffer.
+ std::memcpy(mem.data() + output_offset, &original_bytes, n);
+ }
+
SendReply(Common::HexToString(mem));
} else {
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)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
partitions(partition_names.size()),
partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
- if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
- status = Loader::ResultStatus::ErrorBadXCIHeader;
- return;
- }
-
- if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
- status = Loader::ResultStatus::ErrorBadXCIHeader;
+ const auto header_status = TryReadHeader();
+ if (header_status != Loader::ResultStatus::Success) {
+ status = header_status;
return;
}
@@ -183,9 +179,9 @@ u32 XCI::GetSystemUpdateVersion() {
}
for (const auto& update_file : update->GetFiles()) {
- NCA nca{update_file, nullptr, 0};
+ NCA nca{update_file};
- if (nca.GetStatus() != Loader::ResultStatus::Success) {
+ if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) {
continue;
}
@@ -296,7 +292,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
continue;
}
- auto nca = std::make_shared<NCA>(partition_file, nullptr, 0);
+ auto nca = std::make_shared<NCA>(partition_file);
if (nca->IsUpdate()) {
continue;
}
@@ -316,6 +312,44 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
return Loader::ResultStatus::Success;
}
+Loader::ResultStatus XCI::TryReadHeader() {
+ constexpr size_t CardInitialDataRegionSize = 0x1000;
+
+ // Define the function we'll use to determine if we read a valid header.
+ const auto ReadCardHeader = [&]() {
+ // Ensure we can read the entire header. If we can't, we can't read the card image.
+ if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
+ return Loader::ResultStatus::ErrorBadXCIHeader;
+ }
+
+ // Ensure the header magic matches. If it doesn't, this isn't a card image header.
+ if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
+ return Loader::ResultStatus::ErrorBadXCIHeader;
+ }
+
+ // We read a card image header.
+ return Loader::ResultStatus::Success;
+ };
+
+ // Try to read the header directly.
+ if (ReadCardHeader() == Loader::ResultStatus::Success) {
+ return Loader::ResultStatus::Success;
+ }
+
+ // Get the size of the file.
+ const size_t card_image_size = file->GetSize();
+
+ // If we are large enough to have a key area, offset past the key area and retry.
+ if (card_image_size >= CardInitialDataRegionSize) {
+ file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize,
+ CardInitialDataRegionSize);
+ return ReadCardHeader();
+ }
+
+ // We had no header and aren't large enough to have a key area, so this can't be parsed.
+ return Loader::ResultStatus::ErrorBadXCIHeader;
+}
+
u8 XCI::GetFormatVersion() {
return GetLogoPartition() == nullptr ? 0x1 : 0x2;
}
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:
private:
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
+ Loader::ResultStatus TryReadHeader();
VirtualFile file;
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 @@
#include "core/crypto/ctr_encryption_layer.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/content_archive.h"
-#include "core/file_sys/nca_patch.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
+#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
+#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+
namespace FileSys {
-// Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
-constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
-
-constexpr u64 SECTION_HEADER_SIZE = 0x200;
-constexpr u64 SECTION_HEADER_OFFSET = 0x400;
-
-constexpr u32 IVFC_MAX_LEVEL = 6;
-
-enum class NCASectionFilesystemType : u8 {
- PFS0 = 0x2,
- ROMFS = 0x3,
-};
-
-struct IVFCLevel {
- u64_le offset;
- u64_le size;
- u32_le block_size;
- u32_le reserved;
-};
-static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
-
-struct IVFCHeader {
- u32_le magic;
- u32_le magic_number;
- INSERT_PADDING_BYTES_NOINIT(8);
- std::array<IVFCLevel, 6> levels;
- INSERT_PADDING_BYTES_NOINIT(64);
-};
-static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
-
-struct NCASectionHeaderBlock {
- INSERT_PADDING_BYTES_NOINIT(3);
- NCASectionFilesystemType filesystem_type;
- NCASectionCryptoType crypto_type;
- INSERT_PADDING_BYTES_NOINIT(3);
-};
-static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
-
-struct NCABucketInfo {
- u64 table_offset;
- u64 table_size;
- std::array<u8, 0x10> table_header;
-};
-static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size.");
-
-struct NCASparseInfo {
- NCABucketInfo bucket;
- u64 physical_offset;
- u16 generation;
- INSERT_PADDING_BYTES_NOINIT(0x6);
-};
-static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size.");
-
-struct NCACompressionInfo {
- NCABucketInfo bucket;
- INSERT_PADDING_BYTES_NOINIT(0x8);
-};
-static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size.");
-
-struct NCASectionRaw {
- NCASectionHeaderBlock header;
- std::array<u8, 0x138> block_data;
- std::array<u8, 0x8> section_ctr;
- NCASparseInfo sparse_info;
- NCACompressionInfo compression_info;
- INSERT_PADDING_BYTES_NOINIT(0x60);
-};
-static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
-
-struct PFS0Superblock {
- NCASectionHeaderBlock header_block;
- std::array<u8, 0x20> hash;
- u32_le size;
- INSERT_PADDING_BYTES_NOINIT(4);
- u64_le hash_table_offset;
- u64_le hash_table_size;
- u64_le pfs0_header_offset;
- u64_le pfs0_size;
- INSERT_PADDING_BYTES_NOINIT(0x1B0);
-};
-static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
-
-struct RomFSSuperblock {
- NCASectionHeaderBlock header_block;
- IVFCHeader ivfc;
- INSERT_PADDING_BYTES_NOINIT(0x118);
-};
-static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
-
-struct BKTRHeader {
- u64_le offset;
- u64_le size;
- u32_le magic;
- INSERT_PADDING_BYTES_NOINIT(0x4);
- u32_le number_entries;
- INSERT_PADDING_BYTES_NOINIT(0x4);
-};
-static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
-
-struct BKTRSuperblock {
- NCASectionHeaderBlock header_block;
- IVFCHeader ivfc;
- INSERT_PADDING_BYTES_NOINIT(0x18);
- BKTRHeader relocation;
- BKTRHeader subsection;
- INSERT_PADDING_BYTES_NOINIT(0xC0);
-};
-static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
-
-union NCASectionHeader {
- NCASectionRaw raw{};
- PFS0Superblock pfs0;
- RomFSSuperblock romfs;
- BKTRSuperblock bktr;
-};
-static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
-
-static bool IsValidNCA(const NCAHeader& header) {
- // TODO(DarkLordZach): Add NCA2/NCA0 support.
- return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
+static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
+ return std::max<u8>(key_generation, 1) - 1;
}
-NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
- : file(std::move(file_)),
- bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} {
+NCA::NCA(VirtualFile file_, const NCA* base_nca)
+ : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
return;
}
- if (sizeof(NCAHeader) != file->ReadObject(&header)) {
- LOG_ERROR(Loader, "File reader errored out during header read.");
+ reader = std::make_shared<NcaReader>();
+ if (Result rc =
+ reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration());
+ R_FAILED(rc)) {
+ if (rc != ResultInvalidNcaSignature) {
+ LOG_ERROR(Loader, "File reader errored out during header read: {:#x}",
+ rc.GetInnerValue());
+ }
status = Loader::ResultStatus::ErrorBadNCAHeader;
return;
}
- if (!HandlePotentialHeaderDecryption()) {
- return;
- }
-
- has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; });
-
- const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
- is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) {
- return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
- });
-
- if (!ReadSections(sections, bktr_base_ivfc_offset)) {
+ // Ensure we have the proper key area keys to continue.
+ const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration());
+ if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) {
+ status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return;
}
- status = Loader::ResultStatus::Success;
-}
-
-NCA::~NCA() = default;
-
-bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
- if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
- status = Loader::ResultStatus::ErrorNCA2;
- return false;
- }
+ RightsId rights_id{};
+ reader->GetRightsId(rights_id.data(), rights_id.size());
+ if (rights_id != RightsId{}) {
+ // External decryption key required; provide it here.
+ u128 rights_id_u128;
+ std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id));
- if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
- status = Loader::ResultStatus::ErrorNCA0;
- return false;
- }
-
- return true;
-}
-
-bool NCA::HandlePotentialHeaderDecryption() {
- if (IsValidNCA(header)) {
- return true;
- }
-
- if (!CheckSupportedNCA(header)) {
- return false;
- }
-
- NCAHeader dec_header{};
- Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
- keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
- cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
- Core::Crypto::Op::Decrypt);
- if (IsValidNCA(dec_header)) {
- header = dec_header;
- encrypted = true;
- } else {
- if (!CheckSupportedNCA(dec_header)) {
- return false;
+ auto titlekey =
+ keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]);
+ if (titlekey == Core::Crypto::Key128{}) {
+ status = Loader::ResultStatus::ErrorMissingTitlekey;
+ return;
}
- if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
- status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
- } else {
- status = Loader::ResultStatus::ErrorMissingHeaderKey;
+ if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
+ status = Loader::ResultStatus::ErrorMissingTitlekek;
+ return;
}
- return false;
- }
- return true;
-}
+ auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id);
+ Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB);
+ cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(),
+ Core::Crypto::Op::Decrypt);
-std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
- const std::ptrdiff_t number_sections =
- std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) {
- return entry.media_offset > 0;
- });
-
- std::vector<NCASectionHeader> sections(number_sections);
- const auto length_sections = SECTION_HEADER_SIZE * number_sections;
-
- if (encrypted) {
- auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
- Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
- keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
- cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
- Core::Crypto::Op::Decrypt);
- } else {
- file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
- }
-
- return sections;
-}
-
-bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
- for (std::size_t i = 0; i < sections.size(); ++i) {
- const auto& section = sections[i];
-
- if (section.raw.sparse_info.bucket.table_offset != 0 &&
- section.raw.sparse_info.bucket.table_size != 0) {
- LOG_ERROR(Loader, "Sparse NCAs are not supported.");
- status = Loader::ResultStatus::ErrorSparseNCA;
- return false;
- }
-
- if (section.raw.compression_info.bucket.table_offset != 0 &&
- section.raw.compression_info.bucket.table_size != 0) {
- LOG_ERROR(Loader, "Compressed NCAs are not supported.");
- status = Loader::ResultStatus::ErrorCompressedNCA;
- return false;
- }
-
- if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
- if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
- return false;
- }
- } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
- if (!ReadPFS0Section(section, header.section_tables[i])) {
- return false;
- }
- }
- }
-
- return true;
-}
-
-bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
- u64 bktr_base_ivfc_offset) {
- const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
- ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
- const std::size_t romfs_offset = base_offset + ivfc_offset;
- const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
- auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
- auto dec = Decrypt(section, raw, romfs_offset);
-
- if (dec == nullptr) {
- if (status != Loader::ResultStatus::Success)
- return false;
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return false;
+ reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size());
}
- if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
- if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
- section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
- status = Loader::ResultStatus::ErrorBadBKTRHeader;
- return false;
- }
-
- if (section.bktr.relocation.offset + section.bktr.relocation.size !=
- section.bktr.subsection.offset) {
- status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
- return false;
- }
-
- const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
- if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
- status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
- return false;
- }
-
- const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
- RelocationBlock relocation_block{};
- if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
- sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadRelocationBlock;
- return false;
- }
- SubsectionBlock subsection_block{};
- if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
- sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadSubsectionBlock;
- return false;
- }
-
- std::vector<RelocationBucketRaw> relocation_buckets_raw(
- (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
- if (dec->ReadBytes(relocation_buckets_raw.data(),
- section.bktr.relocation.size - sizeof(RelocationBlock),
- section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
- section.bktr.relocation.size - sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadRelocationBuckets;
- return false;
+ const s32 fs_count = reader->GetFsCount();
+ NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader);
+ std::vector<VirtualFile> filesystems(fs_count);
+ for (s32 i = 0; i < fs_count; i++) {
+ NcaFsHeaderReader header_reader;
+ const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i);
+ if (R_FAILED(rc)) {
+ LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i,
+ rc.GetInnerValue());
+ status = Loader::ResultStatus::ErrorBadNCAHeader;
+ return;
}
- std::vector<SubsectionBucketRaw> subsection_buckets_raw(
- (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
- if (dec->ReadBytes(subsection_buckets_raw.data(),
- section.bktr.subsection.size - sizeof(SubsectionBlock),
- section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
- section.bktr.subsection.size - sizeof(SubsectionBlock)) {
- status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
- return false;
+ if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) {
+ files.push_back(filesystems[i]);
+ romfs = files.back();
}
- std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
- std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(),
- &ConvertRelocationBucketRaw);
- std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
- std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(),
- &ConvertSubsectionBucketRaw);
-
- u32 ctr_low;
- std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
- subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
- subsection_buckets.back().entries.push_back({size, {0}, 0});
-
- std::optional<Core::Crypto::Key128> key;
- if (encrypted) {
- if (has_rights_id) {
- status = Loader::ResultStatus::Success;
- key = GetTitlekey();
- if (!key) {
- status = Loader::ResultStatus::ErrorMissingTitlekey;
- return false;
- }
- } else {
- key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
- if (!key) {
- status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
- return false;
+ if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) {
+ auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]);
+ if (npfs->GetStatus() == Loader::ResultStatus::Success) {
+ dirs.push_back(npfs);
+ if (IsDirectoryExeFS(npfs)) {
+ exefs = dirs.back();
+ } else if (IsDirectoryLogoPartition(npfs)) {
+ logo = dirs.back();
+ } else {
+ continue;
}
}
}
- if (bktr_base_romfs == nullptr) {
- status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
- return false;
+ if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) {
+ is_update = true;
}
-
- auto bktr = std::make_shared<BKTR>(
- bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
- relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
- encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
- section.raw.section_ctr);
-
- // BKTR applies to entire IVFC, so make an offset version to level 6
- files.push_back(std::make_shared<OffsetVfsFile>(
- bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
- } else {
- files.push_back(std::move(dec));
}
- romfs = files.back();
- return true;
-}
-
-bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
- const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
- section.pfs0.pfs0_header_offset;
- const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
-
- auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
- if (dec != nullptr) {
- auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
-
- if (npfs->GetStatus() == Loader::ResultStatus::Success) {
- dirs.push_back(std::move(npfs));
- if (IsDirectoryExeFS(dirs.back()))
- exefs = dirs.back();
- else if (IsDirectoryLogoPartition(dirs.back()))
- logo = dirs.back();
- } else {
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return false;
- }
+ if (is_update && base_nca == nullptr) {
+ status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
} else {
- if (status != Loader::ResultStatus::Success)
- return false;
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return false;
+ status = Loader::ResultStatus::Success;
}
-
- return true;
-}
-
-u8 NCA::GetCryptoRevision() const {
- u8 master_key_id = header.crypto_type;
- if (header.crypto_type_2 > master_key_id)
- master_key_id = header.crypto_type_2;
- if (master_key_id > 0)
- --master_key_id;
- return master_key_id;
-}
-
-std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
- const auto master_key_id = GetCryptoRevision();
-
- if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) {
- return std::nullopt;
- }
-
- std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
- Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
- keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
- Core::Crypto::Mode::ECB);
- cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
-
- Core::Crypto::Key128 out{};
- if (type == NCASectionCryptoType::XTS) {
- std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
- } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) {
- std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
- } else {
- LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
- type);
- }
-
- u128 out_128{};
- std::memcpy(out_128.data(), out.data(), sizeof(u128));
- LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
- master_key_id, header.key_index, out_128[1], out_128[0]);
-
- return out;
}
-std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
- const auto master_key_id = GetCryptoRevision();
-
- u128 rights_id{};
- memcpy(rights_id.data(), header.rights_id.data(), 16);
- if (rights_id == u128{}) {
- status = Loader::ResultStatus::ErrorInvalidRightsID;
- return std::nullopt;
- }
-
- auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
- if (titlekey == Core::Crypto::Key128{}) {
- status = Loader::ResultStatus::ErrorMissingTitlekey;
- return std::nullopt;
- }
-
- if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
- status = Loader::ResultStatus::ErrorMissingTitlekek;
- return std::nullopt;
- }
-
- Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
- keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB);
- cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt);
-
- return titlekey;
-}
-
-VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
- if (!encrypted)
- return in;
-
- switch (s_header.raw.header.crypto_type) {
- case NCASectionCryptoType::NONE:
- LOG_TRACE(Crypto, "called with mode=NONE");
- return in;
- case NCASectionCryptoType::CTR:
- // During normal BKTR decryption, this entire function is skipped. This is for the metadata,
- // which uses the same CTR as usual.
- case NCASectionCryptoType::BKTR:
- LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
- {
- std::optional<Core::Crypto::Key128> key;
- if (has_rights_id) {
- status = Loader::ResultStatus::Success;
- key = GetTitlekey();
- if (!key) {
- if (status == Loader::ResultStatus::Success)
- status = Loader::ResultStatus::ErrorMissingTitlekey;
- return nullptr;
- }
- } else {
- key = GetKeyAreaKey(NCASectionCryptoType::CTR);
- if (!key) {
- status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
- return nullptr;
- }
- }
-
- auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
- starting_offset);
- Core::Crypto::CTREncryptionLayer::IVData iv{};
- for (std::size_t i = 0; i < 8; ++i) {
- iv[i] = s_header.raw.section_ctr[8 - i - 1];
- }
- out->SetIV(iv);
- return std::static_pointer_cast<VfsFile>(out);
- }
- case NCASectionCryptoType::XTS:
- // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
- default:
- LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
- s_header.raw.header.crypto_type);
- return nullptr;
- }
-}
+NCA::~NCA() = default;
Loader::ResultStatus NCA::GetStatus() const {
return status;
@@ -579,21 +152,24 @@ VirtualDir NCA::GetParentDirectory() const {
}
NCAContentType NCA::GetType() const {
- return header.content_type;
+ return static_cast<NCAContentType>(reader->GetContentType());
}
u64 NCA::GetTitleId() const {
- if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS)
- return header.title_id | 0x800;
- return header.title_id;
+ if (is_update) {
+ return reader->GetProgramId() | 0x800;
+ }
+ return reader->GetProgramId();
}
-std::array<u8, 16> NCA::GetRightsId() const {
- return header.rights_id;
+RightsId NCA::GetRightsId() const {
+ RightsId result;
+ reader->GetRightsId(result.data(), result.size());
+ return result;
}
u32 NCA::GetSDKVersion() const {
- return header.sdk_version;
+ return reader->GetSdkAddonVersion();
}
bool NCA::IsUpdate() const {
@@ -612,10 +188,6 @@ VirtualFile NCA::GetBaseFile() const {
return file;
}
-u64 NCA::GetBaseIVFCOffset() const {
- return ivfc_offset;
-}
-
VirtualDir NCA::GetLogoPartition() const {
return logo;
}
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;
namespace FileSys {
-union NCASectionHeader;
+class NcaReader;
/// Describes the type of content within an NCA archive.
enum class NCAContentType : u8 {
@@ -45,41 +45,7 @@ enum class NCAContentType : u8 {
PublicData = 5,
};
-enum class NCASectionCryptoType : u8 {
- NONE = 1,
- XTS = 2,
- CTR = 3,
- BKTR = 4,
-};
-
-struct NCASectionTableEntry {
- u32_le media_offset;
- u32_le media_end_offset;
- INSERT_PADDING_BYTES(0x8);
-};
-static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
-
-struct NCAHeader {
- std::array<u8, 0x100> rsa_signature_1;
- std::array<u8, 0x100> rsa_signature_2;
- u32_le magic;
- u8 is_system;
- NCAContentType content_type;
- u8 crypto_type;
- u8 key_index;
- u64_le size;
- u64_le title_id;
- INSERT_PADDING_BYTES(0x4);
- u32_le sdk_version;
- u8 crypto_type_2;
- INSERT_PADDING_BYTES(15);
- std::array<u8, 0x10> rights_id;
- std::array<NCASectionTableEntry, 0x4> section_tables;
- std::array<std::array<u8, 0x20>, 0x4> hash_tables;
- std::array<u8, 0x40> key_area;
- INSERT_PADDING_BYTES(0xC0);
-};
-static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
+using RightsId = std::array<u8, 0x10>;
inline bool IsDirectoryExeFS(const VirtualDir& pfs) {
// According to switchbrew, an exefs must only contain these two files:
@@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
public:
- explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
- u64 bktr_base_ivfc_offset = 0);
+ explicit NCA(VirtualFile file, const NCA* base_nca = nullptr);
~NCA() override;
Loader::ResultStatus GetStatus() const;
@@ -110,7 +75,7 @@ public:
NCAContentType GetType() const;
u64 GetTitleId() const;
- std::array<u8, 0x10> GetRightsId() const;
+ RightsId GetRightsId() const;
u32 GetSDKVersion() const;
bool IsUpdate() const;
@@ -119,26 +84,9 @@ public:
VirtualFile GetBaseFile() const;
- // Returns the base ivfc offset used in BKTR patching.
- u64 GetBaseIVFCOffset() const;
-
VirtualDir GetLogoPartition() const;
private:
- bool CheckSupportedNCA(const NCAHeader& header);
- bool HandlePotentialHeaderDecryption();
-
- std::vector<NCASectionHeader> ReadSectionHeaders() const;
- bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
- bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
- u64 bktr_base_ivfc_offset);
- bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
-
- u8 GetCryptoRevision() const;
- std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
- std::optional<Core::Crypto::Key128> GetTitlekey();
- VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
-
std::vector<VirtualDir> dirs;
std::vector<VirtualFile> files;
@@ -146,11 +94,6 @@ private:
VirtualDir exefs = nullptr;
VirtualDir logo = nullptr;
VirtualFile file;
- VirtualFile bktr_base_romfs;
- u64 ivfc_offset = 0;
-
- NCAHeader header{};
- bool has_rights_id{};
Loader::ResultStatus status{};
@@ -158,6 +101,7 @@ private:
bool is_update = false;
Core::Crypto::KeyManager& keys;
+ std::shared_ptr<NcaReader> reader;
};
} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index cd9ac2e75..0697c29ae 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -68,7 +68,8 @@ NACP::NACP(VirtualFile file) {
NACP::~NACP() = default;
const LanguageEntry& NACP::GetLanguageEntry() const {
- Language language = language_to_codes[Settings::values.language_index.GetValue()];
+ Language language =
+ language_to_codes[static_cast<s32>(Settings::values.language_index.GetValue())];
{
const auto& language_entry = raw.language_entries.at(static_cast<u8>(language));
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};
constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
+constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50};
+constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001};
+constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002};
+constexpr Result ResultOutOfRange{ErrorModule::FS, 3005};
+constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294};
+constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341};
+constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363};
+constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399};
+constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412};
+constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422};
+constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423};
+constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012};
+constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021};
+constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022};
+constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023};
+constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024};
+constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032};
+constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033};
+constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034};
+constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035};
+constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036};
+constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037};
+constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038};
+constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039};
+constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084};
+constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085};
+constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086};
+constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087};
+constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088};
+constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089};
+constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090};
+constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091};
+constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091};
+constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509};
+constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510};
+constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511};
+constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517};
+constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520};
+constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521};
+constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522};
+constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523};
+constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524};
+constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525};
+constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526};
+constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528};
+constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529};
+constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530};
+constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532};
+constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533};
+constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534};
+constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535};
+constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541};
+constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542};
+constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543};
+constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547};
+constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548};
+constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549};
+constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
+constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
+constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
+constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
+constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
+constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
+constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
+constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
+constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
+constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
+constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
+constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705};
+
} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/overflow.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+class IStorage : public VfsFile {
+public:
+ virtual std::string GetName() const override {
+ return {};
+ }
+
+ virtual VirtualDir GetContainingDirectory() const override {
+ return {};
+ }
+
+ virtual bool IsWritable() const override {
+ return true;
+ }
+
+ virtual bool IsReadable() const override {
+ return true;
+ }
+
+ virtual bool Resize(size_t size) override {
+ return false;
+ }
+
+ virtual bool Rename(std::string_view name) override {
+ return false;
+ }
+
+ static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) {
+ R_UNLESS(offset >= 0, ResultInvalidOffset);
+ R_UNLESS(size >= 0, ResultInvalidSize);
+ R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange);
+ R_UNLESS(offset + size <= total_size, ResultOutOfRange);
+ R_SUCCEED();
+ }
+};
+
+class IReadOnlyStorage : public IStorage {
+public:
+ virtual bool IsWritable() const override {
+ return false;
+ }
+
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
+ return 0;
+ }
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+
+namespace FileSys {
+
+struct Int64 {
+ u32 low;
+ u32 high;
+
+ constexpr void Set(s64 v) {
+ this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0);
+ this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32);
+ }
+
+ constexpr s64 Get() const {
+ return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low));
+ }
+
+ constexpr Int64& operator=(s64 v) {
+ this->Set(v);
+ return *this;
+ }
+
+ constexpr operator s64() const {
+ return this->Get();
+ }
+};
+
+struct HashSalt {
+ static constexpr size_t Size = 32;
+
+ std::array<u8, Size> value;
+};
+static_assert(std::is_trivial_v<HashSalt>);
+static_assert(sizeof(HashSalt) == HashSalt::Size);
+
+constexpr inline size_t IntegrityMinLayerCount = 2;
+constexpr inline size_t IntegrityMaxLayerCount = 7;
+constexpr inline size_t IntegrityLayerCountSave = 5;
+constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4;
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
+#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
+#include "core/file_sys/fssystem/fssystem_nca_header.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+namespace {
+
+class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
+public:
+ virtual void Decrypt(
+ u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
+ const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final;
+};
+
+} // namespace
+
+Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) {
+ std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>();
+ R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA);
+ *out = std::move(decryptor);
+ R_SUCCEED();
+}
+
+Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
+ VirtualFile data_storage,
+ VirtualFile table_storage) {
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ table_storage->ReadObject(std::addressof(header), 0);
+ R_TRY(header.Verify());
+
+ // Determine extents.
+ const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
+ const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
+ const auto node_storage_offset = QueryHeaderStorageSize();
+ const auto entry_storage_offset = node_storage_offset + node_storage_size;
+
+ // Create a software decryptor.
+ std::unique_ptr<IDecryptor> sw_decryptor;
+ R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
+
+ // Initialize.
+ R_RETURN(this->Initialize(
+ key, key_size, secure_value, 0, data_storage,
+ std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
+ std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
+ header.entry_count, std::move(sw_decryptor)));
+}
+
+Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
+ s64 counter_offset, VirtualFile data_storage,
+ VirtualFile node_storage, VirtualFile entry_storage,
+ s32 entry_count,
+ std::unique_ptr<IDecryptor>&& decryptor) {
+ // Validate preconditions.
+ ASSERT(key != nullptr);
+ ASSERT(key_size == KeySize);
+ ASSERT(counter_offset >= 0);
+ ASSERT(decryptor != nullptr);
+
+ // Initialize the bucket tree table.
+ if (entry_count > 0) {
+ R_TRY(
+ m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
+ } else {
+ m_table.Initialize(NodeSize, 0);
+ }
+
+ // Set members.
+ m_data_storage = data_storage;
+ std::memcpy(m_key.data(), key, key_size);
+ m_secure_value = secure_value;
+ m_counter_offset = counter_offset;
+ m_decryptor = std::move(decryptor);
+
+ R_SUCCEED();
+}
+
+void AesCtrCounterExtendedStorage::Finalize() {
+ if (this->IsInitialized()) {
+ m_table.Finalize();
+ m_data_storage = VirtualFile();
+ }
+}
+
+Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count,
+ s32 entry_count, s64 offset, s64 size) {
+ // Validate pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Clear the out count.
+ R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
+ *out_entry_count = 0;
+
+ // Succeed if there's no range.
+ R_SUCCEED_IF(size == 0);
+
+ // If we have an output array, we need it to be non-null.
+ R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
+
+ // Check that our range is valid.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->GetOffset();
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultInvalidAesCtrCounterExtendedEntryOffset);
+ }
+
+ // Prepare to loop over entries.
+ const auto end_offset = offset + static_cast<s64>(size);
+ s32 count = 0;
+
+ auto cur_entry = *visitor.Get<Entry>();
+ while (cur_entry.GetOffset() < end_offset) {
+ // Try to write the entry to the out list.
+ if (entry_count != 0) {
+ if (count >= entry_count) {
+ break;
+ }
+ std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
+ }
+
+ count++;
+
+ // Advance.
+ if (visitor.CanMoveNext()) {
+ R_TRY(visitor.MoveNext());
+ cur_entry = *visitor.Get<Entry>();
+ } else {
+ break;
+ }
+ }
+
+ // Write the output count.
+ *out_entry_count = count;
+ R_SUCCEED();
+}
+
+size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Validate preconditions.
+ ASSERT(this->IsInitialized());
+
+ // Allow zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+ ASSERT(Common::IsAligned(offset, BlockSize));
+ ASSERT(Common::IsAligned(size, BlockSize));
+
+ BucketTree::Offsets table_offsets;
+ ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets))));
+
+ ASSERT(table_offsets.IsInclude(offset, size));
+
+ // Read the data.
+ m_data_storage->Read(buffer, size, offset);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset)));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->GetOffset();
+ ASSERT(Common::IsAligned(entry_offset, BlockSize));
+ ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset));
+ }
+
+ // Prepare to read in chunks.
+ u8* cur_data = static_cast<u8*>(buffer);
+ auto cur_offset = offset;
+ const auto end_offset = offset + static_cast<s64>(size);
+
+ while (cur_offset < end_offset) {
+ // Get the current entry.
+ const auto cur_entry = *visitor.Get<Entry>();
+
+ // Get and validate the entry's offset.
+ const auto cur_entry_offset = cur_entry.GetOffset();
+ ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset);
+
+ // Get and validate the next entry offset.
+ s64 next_entry_offset;
+ if (visitor.CanMoveNext()) {
+ ASSERT(R_SUCCEEDED(visitor.MoveNext()));
+ next_entry_offset = visitor.Get<Entry>()->GetOffset();
+ ASSERT(table_offsets.IsInclude(next_entry_offset));
+ } else {
+ next_entry_offset = table_offsets.end_offset;
+ }
+ ASSERT(Common::IsAligned(next_entry_offset, BlockSize));
+ ASSERT(cur_offset < static_cast<size_t>(next_entry_offset));
+
+ // Get the offset of the entry in the data we read.
+ const auto data_offset = cur_offset - cur_entry_offset;
+ const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset;
+ ASSERT(data_size > 0);
+
+ // Determine how much is left.
+ const auto remaining_size = end_offset - cur_offset;
+ const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size));
+ ASSERT(cur_size <= size);
+
+ // If necessary, perform decryption.
+ if (cur_entry.encryption_value == Entry::Encryption::Encrypted) {
+ // Make the CTR for the data we're decrypting.
+ const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset;
+ NcaAesCtrUpperIv upper_iv = {
+ .part = {.generation = static_cast<u32>(cur_entry.generation),
+ .secure_value = m_secure_value}};
+
+ std::array<u8, IvSize> iv;
+ AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset);
+
+ // Decrypt.
+ m_decryptor->Decrypt(cur_data, cur_size, m_key, iv);
+ }
+
+ // Advance.
+ cur_data += cur_size;
+ cur_offset += cur_size;
+ }
+
+ return size;
+}
+
+void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size,
+ const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
+ const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) {
+ Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher(
+ key, Core::Crypto::Mode::CTR);
+ cipher.SetIV(iv);
+ cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt);
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+
+#include "common/literals.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+class AesCtrCounterExtendedStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage);
+ YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage);
+
+public:
+ static constexpr size_t BlockSize = 0x10;
+ static constexpr size_t KeySize = 0x10;
+ static constexpr size_t IvSize = 0x10;
+ static constexpr size_t NodeSize = 16_KiB;
+
+ class IDecryptor {
+ public:
+ virtual ~IDecryptor() {}
+ virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key,
+ const std::array<u8, IvSize>& iv) = 0;
+ };
+
+ struct Entry {
+ enum class Encryption : u8 {
+ Encrypted = 0,
+ NotEncrypted = 1,
+ };
+
+ std::array<u8, sizeof(s64)> offset;
+ Encryption encryption_value;
+ std::array<u8, 3> reserved;
+ s32 generation;
+
+ void SetOffset(s64 value) {
+ std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64));
+ }
+
+ s64 GetOffset() const {
+ s64 value;
+ std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64));
+ return value;
+ }
+ };
+ static_assert(sizeof(Entry) == 0x10);
+ static_assert(alignof(Entry) == 4);
+ static_assert(std::is_trivial_v<Entry>);
+
+public:
+ static constexpr s64 QueryHeaderStorageSize() {
+ return BucketTree::QueryHeaderStorageSize();
+ }
+
+ static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
+ return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+ static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
+ return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+ static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out);
+
+public:
+ AesCtrCounterExtendedStorage()
+ : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {}
+ virtual ~AesCtrCounterExtendedStorage() {
+ this->Finalize();
+ }
+
+ Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset,
+ VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
+ s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor);
+ void Finalize();
+
+ bool IsInitialized() const {
+ return m_table.IsInitialized();
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+
+ virtual size_t GetSize() const override {
+ BucketTree::Offsets offsets;
+ ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets))));
+
+ return offsets.end_offset;
+ }
+
+ Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
+ s64 size);
+
+private:
+ Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage,
+ VirtualFile table_storage);
+
+private:
+ mutable BucketTree m_table;
+ VirtualFile m_data_storage;
+ std::array<u8, KeySize> m_key;
+ u32 m_secure_value;
+ s64 m_counter_offset;
+ std::unique_ptr<IDecryptor> m_decryptor;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/swap.h"
+#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+#include "core/file_sys/fssystem/fssystem_utility.h"
+
+namespace FileSys {
+
+void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) {
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size == IvSize);
+ ASSERT(offset >= 0);
+
+ const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
+
+ *reinterpret_cast<u64_be*>(out_addr + 0) = upper;
+ *reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize);
+}
+
+AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
+ size_t iv_size)
+ : m_base_storage(std::move(base)) {
+ ASSERT(m_base_storage != nullptr);
+ ASSERT(key != nullptr);
+ ASSERT(iv != nullptr);
+ ASSERT(key_size == KeySize);
+ ASSERT(iv_size == IvSize);
+
+ std::memcpy(m_key.data(), key, KeySize);
+ std::memcpy(m_iv.data(), iv, IvSize);
+
+ m_cipher.emplace(m_key, Core::Crypto::Mode::CTR);
+}
+
+size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Allow zero-size reads.
+ if (size == 0) {
+ return size;
+ }
+
+ // Ensure buffer is valid.
+ ASSERT(buffer != nullptr);
+
+ // We can only read at block aligned offsets.
+ ASSERT(Common::IsAligned(offset, BlockSize));
+ ASSERT(Common::IsAligned(size, BlockSize));
+
+ // Read the data.
+ m_base_storage->Read(buffer, size, offset);
+
+ // Setup the counter.
+ std::array<u8, IvSize> ctr;
+ std::memcpy(ctr.data(), m_iv.data(), IvSize);
+ AddCounter(ctr.data(), IvSize, offset / BlockSize);
+
+ // Decrypt.
+ m_cipher->SetIV(ctr);
+ m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt);
+
+ return size;
+}
+
+size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) {
+ // Allow zero-size writes.
+ if (size == 0) {
+ return size;
+ }
+
+ // Ensure buffer is valid.
+ ASSERT(buffer != nullptr);
+
+ // We can only write at block aligned offsets.
+ ASSERT(Common::IsAligned(offset, BlockSize));
+ ASSERT(Common::IsAligned(size, BlockSize));
+
+ // Get a pooled buffer.
+ PooledBuffer pooled_buffer;
+ const bool use_work_buffer = true;
+ if (use_work_buffer) {
+ pooled_buffer.Allocate(size, BlockSize);
+ }
+
+ // Setup the counter.
+ std::array<u8, IvSize> ctr;
+ std::memcpy(ctr.data(), m_iv.data(), IvSize);
+ AddCounter(ctr.data(), IvSize, offset / BlockSize);
+
+ // Loop until all data is written.
+ size_t remaining = size;
+ s64 cur_offset = 0;
+ while (remaining > 0) {
+ // Determine data we're writing and where.
+ const size_t write_size =
+ use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
+
+ void* write_buf;
+ if (use_work_buffer) {
+ write_buf = pooled_buffer.GetBuffer();
+ } else {
+ write_buf = const_cast<u8*>(buffer);
+ }
+
+ // Encrypt the data.
+ m_cipher->SetIV(ctr);
+ m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf),
+ Core::Crypto::Op::Encrypt);
+
+ // Write the encrypted data.
+ m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset);
+
+ // Advance.
+ cur_offset += write_size;
+ remaining -= write_size;
+ if (remaining > 0) {
+ AddCounter(ctr.data(), IvSize, write_size / BlockSize);
+ }
+ }
+
+ return size;
+}
+
+size_t AesCtrStorage::GetSize() const {
+ return m_base_storage->GetSize();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+
+#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+class AesCtrStorage : public IStorage {
+ YUZU_NON_COPYABLE(AesCtrStorage);
+ YUZU_NON_MOVEABLE(AesCtrStorage);
+
+public:
+ static constexpr size_t BlockSize = 0x10;
+ static constexpr size_t KeySize = 0x10;
+ static constexpr size_t IvSize = 0x10;
+
+public:
+ static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset);
+
+public:
+ AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
+ size_t iv_size);
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override;
+ virtual size_t GetSize() const override;
+
+private:
+ VirtualFile m_base_storage;
+ std::array<u8, KeySize> m_key;
+ std::array<u8, IvSize> m_iv;
+ mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/swap.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+#include "core/file_sys/fssystem/fssystem_utility.h"
+
+namespace FileSys {
+
+void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) {
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size == IvSize);
+ ASSERT(offset >= 0);
+
+ const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
+
+ *reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size;
+}
+
+AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
+ const void* iv, size_t iv_size, size_t block_size)
+ : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() {
+ ASSERT(m_base_storage != nullptr);
+ ASSERT(key1 != nullptr);
+ ASSERT(key2 != nullptr);
+ ASSERT(iv != nullptr);
+ ASSERT(key_size == KeySize);
+ ASSERT(iv_size == IvSize);
+ ASSERT(Common::IsAligned(m_block_size, AesBlockSize));
+
+ std::memcpy(m_key.data() + 0, key1, KeySize);
+ std::memcpy(m_key.data() + 0x10, key2, KeySize);
+ std::memcpy(m_iv.data(), iv, IvSize);
+
+ m_cipher.emplace(m_key, Core::Crypto::Mode::XTS);
+}
+
+size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Allow zero-size reads.
+ if (size == 0) {
+ return size;
+ }
+
+ // Ensure buffer is valid.
+ ASSERT(buffer != nullptr);
+
+ // We can only read at block aligned offsets.
+ ASSERT(Common::IsAligned(offset, AesBlockSize));
+ ASSERT(Common::IsAligned(size, AesBlockSize));
+
+ // Read the data.
+ m_base_storage->Read(buffer, size, offset);
+
+ // Setup the counter.
+ std::array<u8, IvSize> ctr;
+ std::memcpy(ctr.data(), m_iv.data(), IvSize);
+ AddCounter(ctr.data(), IvSize, offset / m_block_size);
+
+ // Handle any unaligned data before the start.
+ size_t processed_size = 0;
+ if ((offset % m_block_size) != 0) {
+ // Determine the size of the pre-data read.
+ const size_t skip_size =
+ static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size));
+ const size_t data_size = std::min(size, m_block_size - skip_size);
+
+ // Decrypt into a pooled buffer.
+ {
+ PooledBuffer tmp_buf(m_block_size, m_block_size);
+ ASSERT(tmp_buf.GetSize() >= m_block_size);
+
+ std::memset(tmp_buf.GetBuffer(), 0, skip_size);
+ std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
+
+ m_cipher->SetIV(ctr);
+ m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(),
+ Core::Crypto::Op::Decrypt);
+
+ std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size);
+ }
+
+ AddCounter(ctr.data(), IvSize, 1);
+ processed_size += data_size;
+ ASSERT(processed_size == std::min(size, m_block_size - skip_size));
+ }
+
+ // Decrypt aligned chunks.
+ char* cur = reinterpret_cast<char*>(buffer) + processed_size;
+ size_t remaining = size - processed_size;
+ while (remaining > 0) {
+ const size_t cur_size = std::min(m_block_size, remaining);
+
+ m_cipher->SetIV(ctr);
+ m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt);
+
+ remaining -= cur_size;
+ cur += cur_size;
+
+ AddCounter(ctr.data(), IvSize, 1);
+ }
+
+ return size;
+}
+
+size_t AesXtsStorage::GetSize() const {
+ return m_base_storage->GetSize();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+
+#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+
+namespace FileSys {
+
+class AesXtsStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(AesXtsStorage);
+ YUZU_NON_MOVEABLE(AesXtsStorage);
+
+public:
+ static constexpr size_t AesBlockSize = 0x10;
+ static constexpr size_t KeySize = 0x20;
+ static constexpr size_t IvSize = 0x10;
+
+public:
+ static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size);
+
+public:
+ AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
+ const void* iv, size_t iv_size, size_t block_size);
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+ virtual size_t GetSize() const override;
+
+private:
+ VirtualFile m_base_storage;
+ std::array<u8, KeySize> m_key;
+ std::array<u8, IvSize> m_iv;
+ const size_t m_block_size;
+ std::mutex m_mutex;
+ mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/alignment.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+
+namespace FileSys {
+
+template <size_t DataAlign_, size_t BufferAlign_>
+class AlignmentMatchingStorage : public IStorage {
+ YUZU_NON_COPYABLE(AlignmentMatchingStorage);
+ YUZU_NON_MOVEABLE(AlignmentMatchingStorage);
+
+public:
+ static constexpr size_t DataAlign = DataAlign_;
+ static constexpr size_t BufferAlign = BufferAlign_;
+
+ static constexpr size_t DataAlignMax = 0x200;
+ static_assert(DataAlign <= DataAlignMax);
+ static_assert(Common::IsPowerOfTwo(DataAlign));
+ static_assert(Common::IsPowerOfTwo(BufferAlign));
+
+private:
+ VirtualFile m_base_storage;
+ s64 m_base_storage_size;
+
+public:
+ explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {}
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Allocate a work buffer on stack.
+ alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
+
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ s64 bs_size = this->GetSize();
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
+
+ return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(),
+ DataAlign, BufferAlign, offset, buffer, size);
+ }
+
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
+ // Allocate a work buffer on stack.
+ alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
+
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ s64 bs_size = this->GetSize();
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
+
+ return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(),
+ DataAlign, BufferAlign, offset, buffer, size);
+ }
+
+ virtual size_t GetSize() const override {
+ return m_base_storage->GetSize();
+ }
+};
+
+template <size_t BufferAlign_>
+class AlignmentMatchingStoragePooledBuffer : public IStorage {
+ YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer);
+ YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer);
+
+public:
+ static constexpr size_t BufferAlign = BufferAlign_;
+
+ static_assert(Common::IsPowerOfTwo(BufferAlign));
+
+private:
+ VirtualFile m_base_storage;
+ s64 m_base_storage_size;
+ size_t m_data_align;
+
+public:
+ explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da)
+ : m_base_storage(std::move(bs)), m_data_align(da) {
+ ASSERT(Common::IsPowerOfTwo(da));
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ s64 bs_size = this->GetSize();
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
+
+ // Allocate a pooled buffer.
+ PooledBuffer pooled_buffer;
+ pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
+
+ return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(),
+ pooled_buffer.GetSize(), m_data_align,
+ BufferAlign, offset, buffer, size);
+ }
+
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ s64 bs_size = this->GetSize();
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
+
+ // Allocate a pooled buffer.
+ PooledBuffer pooled_buffer;
+ pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
+
+ return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(),
+ pooled_buffer.GetSize(), m_data_align,
+ BufferAlign, offset, buffer, size);
+ }
+
+ virtual size_t GetSize() const override {
+ return m_base_storage->GetSize();
+ }
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
+
+namespace FileSys {
+
+namespace {
+
+template <typename T>
+constexpr size_t GetRoundDownDifference(T x, size_t align) {
+ return static_cast<size_t>(x - Common::AlignDown(x, align));
+}
+
+template <typename T>
+constexpr size_t GetRoundUpDifference(T x, size_t align) {
+ return static_cast<size_t>(Common::AlignUp(x, align) - x);
+}
+
+template <typename T>
+size_t GetRoundUpDifference(T* x, size_t align) {
+ return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align);
+}
+
+} // namespace
+
+size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf,
+ size_t work_buf_size, size_t data_alignment,
+ size_t buffer_alignment, s64 offset, u8* buffer,
+ size_t size) {
+ // Check preconditions.
+ ASSERT(work_buf_size >= data_alignment);
+
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ // Determine extents.
+ u8* aligned_core_buffer;
+ s64 core_offset;
+ size_t core_size;
+ size_t buffer_gap;
+ size_t offset_gap;
+ s64 covered_offset;
+
+ const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
+ if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
+ buffer_alignment)) {
+ aligned_core_buffer = buffer + offset_round_up_difference;
+
+ core_offset = Common::AlignUp(offset, data_alignment);
+ core_size = (size < offset_round_up_difference)
+ ? 0
+ : Common::AlignDown(size - offset_round_up_difference, data_alignment);
+ buffer_gap = 0;
+ offset_gap = 0;
+
+ covered_offset = core_size > 0 ? core_offset : offset;
+ } else {
+ const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment);
+
+ aligned_core_buffer = buffer + buffer_round_up_difference;
+
+ core_offset = Common::AlignDown(offset, data_alignment);
+ core_size = (size < buffer_round_up_difference)
+ ? 0
+ : Common::AlignDown(size - buffer_round_up_difference, data_alignment);
+ buffer_gap = buffer_round_up_difference;
+ offset_gap = GetRoundDownDifference(offset, data_alignment);
+
+ covered_offset = offset;
+ }
+
+ // Read the core portion.
+ if (core_size > 0) {
+ base_storage->Read(aligned_core_buffer, core_size, core_offset);
+
+ if (offset_gap != 0 || buffer_gap != 0) {
+ std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap,
+ core_size - offset_gap);
+ core_size -= offset_gap;
+ }
+ }
+
+ // Handle the head portion.
+ if (offset < covered_offset) {
+ const s64 head_offset = Common::AlignDown(offset, data_alignment);
+ const size_t head_size = static_cast<size_t>(covered_offset - offset);
+
+ ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size);
+
+ base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
+ std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size);
+ }
+
+ // Handle the tail portion.
+ s64 tail_offset = covered_offset + core_size;
+ size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
+ while (remaining_tail_size > 0) {
+ const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
+ const auto cur_size =
+ std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
+ remaining_tail_size);
+ base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
+
+ ASSERT((tail_offset - offset) + cur_size <= size);
+ ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment);
+ std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset),
+ work_buf + (tail_offset - aligned_tail_offset), cur_size);
+
+ remaining_tail_size -= cur_size;
+ tail_offset += cur_size;
+ }
+
+ return size;
+}
+
+size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf,
+ size_t work_buf_size, size_t data_alignment,
+ size_t buffer_alignment, s64 offset, const u8* buffer,
+ size_t size) {
+ // Check preconditions.
+ ASSERT(work_buf_size >= data_alignment);
+
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ // Determine extents.
+ const u8* aligned_core_buffer;
+ s64 core_offset;
+ size_t core_size;
+ s64 covered_offset;
+
+ const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
+ if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
+ buffer_alignment)) {
+ aligned_core_buffer = buffer + offset_round_up_difference;
+
+ core_offset = Common::AlignUp(offset, data_alignment);
+ core_size = (size < offset_round_up_difference)
+ ? 0
+ : Common::AlignDown(size - offset_round_up_difference, data_alignment);
+
+ covered_offset = core_size > 0 ? core_offset : offset;
+ } else {
+ aligned_core_buffer = nullptr;
+
+ core_offset = Common::AlignDown(offset, data_alignment);
+ core_size = 0;
+
+ covered_offset = offset;
+ }
+
+ // Write the core portion.
+ if (core_size > 0) {
+ base_storage->Write(aligned_core_buffer, core_size, core_offset);
+ }
+
+ // Handle the head portion.
+ if (offset < covered_offset) {
+ const s64 head_offset = Common::AlignDown(offset, data_alignment);
+ const size_t head_size = static_cast<size_t>(covered_offset - offset);
+
+ ASSERT((offset - head_offset) + head_size <= data_alignment);
+
+ base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
+ std::memcpy(work_buf + (offset - head_offset), buffer, head_size);
+ base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
+ }
+
+ // Handle the tail portion.
+ s64 tail_offset = covered_offset + core_size;
+ size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
+ while (remaining_tail_size > 0) {
+ ASSERT(static_cast<size_t>(tail_offset - offset) < size);
+
+ const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
+ const auto cur_size =
+ std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
+ remaining_tail_size);
+
+ base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
+ std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment),
+ buffer + (tail_offset - offset), cur_size);
+ base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
+
+ remaining_tail_size -= cur_size;
+ tail_offset += cur_size;
+ }
+
+ return size;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+
+namespace FileSys {
+
+class AlignmentMatchingStorageImpl {
+public:
+ static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
+ size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer,
+ size_t size);
+ static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
+ size_t data_alignment, size_t buffer_alignment, s64 offset,
+ const u8* buffer, size_t size);
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+
+namespace FileSys {
+
+namespace {
+
+using Node = impl::BucketTreeNode<const s64*>;
+static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader));
+static_assert(std::is_trivial_v<Node>);
+
+constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader);
+
+class StorageNode {
+private:
+ class Offset {
+ public:
+ using difference_type = s64;
+
+ private:
+ s64 m_offset;
+ s32 m_stride;
+
+ public:
+ constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {}
+
+ constexpr Offset& operator++() {
+ m_offset += m_stride;
+ return *this;
+ }
+ constexpr Offset operator++(int) {
+ Offset ret(*this);
+ m_offset += m_stride;
+ return ret;
+ }
+
+ constexpr Offset& operator--() {
+ m_offset -= m_stride;
+ return *this;
+ }
+ constexpr Offset operator--(int) {
+ Offset ret(*this);
+ m_offset -= m_stride;
+ return ret;
+ }
+
+ constexpr difference_type operator-(const Offset& rhs) const {
+ return (m_offset - rhs.m_offset) / m_stride;
+ }
+
+ constexpr Offset operator+(difference_type ofs) const {
+ return Offset(m_offset + ofs * m_stride, m_stride);
+ }
+ constexpr Offset operator-(difference_type ofs) const {
+ return Offset(m_offset - ofs * m_stride, m_stride);
+ }
+
+ constexpr Offset& operator+=(difference_type ofs) {
+ m_offset += ofs * m_stride;
+ return *this;
+ }
+ constexpr Offset& operator-=(difference_type ofs) {
+ m_offset -= ofs * m_stride;
+ return *this;
+ }
+
+ constexpr bool operator==(const Offset& rhs) const {
+ return m_offset == rhs.m_offset;
+ }
+ constexpr bool operator!=(const Offset& rhs) const {
+ return m_offset != rhs.m_offset;
+ }
+
+ constexpr s64 Get() const {
+ return m_offset;
+ }
+ };
+
+private:
+ const Offset m_start;
+ const s32 m_count;
+ s32 m_index;
+
+public:
+ StorageNode(size_t size, s32 count)
+ : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {}
+ StorageNode(s64 ofs, size_t size, s32 count)
+ : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {}
+
+ s32 GetIndex() const {
+ return m_index;
+ }
+
+ void Find(const char* buffer, s64 virtual_address) {
+ s32 end = m_count;
+ auto pos = m_start;
+
+ while (end > 0) {
+ auto half = end / 2;
+ auto mid = pos + half;
+
+ s64 offset = 0;
+ std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64));
+
+ if (offset <= virtual_address) {
+ pos = mid + 1;
+ end -= half + 1;
+ } else {
+ end = half;
+ }
+ }
+
+ m_index = static_cast<s32>(pos - m_start) - 1;
+ }
+
+ Result Find(VirtualFile storage, s64 virtual_address) {
+ s32 end = m_count;
+ auto pos = m_start;
+
+ while (end > 0) {
+ auto half = end / 2;
+ auto mid = pos + half;
+
+ s64 offset = 0;
+ storage->ReadObject(std::addressof(offset), mid.Get());
+
+ if (offset <= virtual_address) {
+ pos = mid + 1;
+ end -= half + 1;
+ } else {
+ end = half;
+ }
+ }
+
+ m_index = static_cast<s32>(pos - m_start) - 1;
+ R_SUCCEED();
+ }
+};
+
+} // namespace
+
+void BucketTree::Header::Format(s32 entry_count_) {
+ ASSERT(entry_count_ >= 0);
+
+ this->magic = Magic;
+ this->version = Version;
+ this->entry_count = entry_count_;
+ this->reserved = 0;
+}
+
+Result BucketTree::Header::Verify() const {
+ R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature);
+ R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount);
+ R_UNLESS(this->version <= Version, ResultUnsupportedVersion);
+ R_SUCCEED();
+}
+
+Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const {
+ R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex);
+ R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize);
+
+ const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size;
+ R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count,
+ ResultInvalidBucketTreeNodeEntryCount);
+ R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset);
+
+ R_SUCCEED();
+}
+
+Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
+ size_t entry_size, s32 entry_count) {
+ // Validate preconditions.
+ ASSERT(entry_size >= sizeof(s64));
+ ASSERT(node_size >= entry_size + sizeof(NodeHeader));
+ ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
+ ASSERT(Common::IsPowerOfTwo(node_size));
+ ASSERT(!this->IsInitialized());
+
+ // Ensure valid entry count.
+ R_UNLESS(entry_count > 0, ResultInvalidArgument);
+
+ // Allocate node.
+ R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed);
+ ON_RESULT_FAILURE {
+ m_node_l1.Free(node_size);
+ };
+
+ // Read node.
+ node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size);
+
+ // Verify node.
+ R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64)));
+
+ // Validate offsets.
+ const auto offset_count = GetOffsetCount(node_size);
+ const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
+ const auto* const node = m_node_l1.Get<Node>();
+
+ s64 start_offset;
+ if (offset_count < entry_set_count && node->GetCount() < offset_count) {
+ start_offset = *node->GetEnd();
+ } else {
+ start_offset = *node->GetBegin();
+ }
+ const auto end_offset = node->GetEndOffset();
+
+ R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
+ ResultInvalidBucketTreeEntryOffset);
+ R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
+
+ // Set member variables.
+ m_node_storage = node_storage;
+ m_entry_storage = entry_storage;
+ m_node_size = node_size;
+ m_entry_size = entry_size;
+ m_entry_count = entry_count;
+ m_offset_count = offset_count;
+ m_entry_set_count = entry_set_count;
+
+ m_offset_cache.offsets.start_offset = start_offset;
+ m_offset_cache.offsets.end_offset = end_offset;
+ m_offset_cache.is_initialized = true;
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+void BucketTree::Initialize(size_t node_size, s64 end_offset) {
+ ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
+ ASSERT(Common::IsPowerOfTwo(node_size));
+ ASSERT(end_offset > 0);
+ ASSERT(!this->IsInitialized());
+
+ m_node_size = node_size;
+
+ m_offset_cache.offsets.start_offset = 0;
+ m_offset_cache.offsets.end_offset = end_offset;
+ m_offset_cache.is_initialized = true;
+}
+
+void BucketTree::Finalize() {
+ if (this->IsInitialized()) {
+ m_node_storage = VirtualFile();
+ m_entry_storage = VirtualFile();
+ m_node_l1.Free(m_node_size);
+ m_node_size = 0;
+ m_entry_size = 0;
+ m_entry_count = 0;
+ m_offset_count = 0;
+ m_entry_set_count = 0;
+
+ m_offset_cache.offsets.start_offset = 0;
+ m_offset_cache.offsets.end_offset = 0;
+ m_offset_cache.is_initialized = false;
+ }
+}
+
+Result BucketTree::Find(Visitor* visitor, s64 virtual_address) {
+ ASSERT(visitor != nullptr);
+ ASSERT(this->IsInitialized());
+
+ R_UNLESS(virtual_address >= 0, ResultInvalidOffset);
+ R_UNLESS(!this->IsEmpty(), ResultOutOfRange);
+
+ BucketTree::Offsets offsets;
+ R_TRY(this->GetOffsets(std::addressof(offsets)));
+
+ R_TRY(visitor->Initialize(this, offsets));
+
+ R_RETURN(visitor->Find(virtual_address));
+}
+
+Result BucketTree::InvalidateCache() {
+ // Reset our offsets.
+ m_offset_cache.is_initialized = false;
+
+ R_SUCCEED();
+}
+
+Result BucketTree::EnsureOffsetCache() {
+ // If we already have an offset cache, we're good.
+ R_SUCCEED_IF(m_offset_cache.is_initialized);
+
+ // Acquire exclusive right to edit the offset cache.
+ std::scoped_lock lk(m_offset_cache.mutex);
+
+ // Check again, to be sure.
+ R_SUCCEED_IF(m_offset_cache.is_initialized);
+
+ // Read/verify L1.
+ m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size);
+ R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64)));
+
+ // Get the node.
+ auto* const node = m_node_l1.Get<Node>();
+
+ s64 start_offset;
+ if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) {
+ start_offset = *node->GetEnd();
+ } else {
+ start_offset = *node->GetBegin();
+ }
+ const auto end_offset = node->GetEndOffset();
+
+ R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
+ ResultInvalidBucketTreeEntryOffset);
+ R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
+
+ m_offset_cache.offsets.start_offset = start_offset;
+ m_offset_cache.offsets.end_offset = end_offset;
+ m_offset_cache.is_initialized = true;
+
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) {
+ ASSERT(tree != nullptr);
+ ASSERT(m_tree == nullptr || m_tree == tree);
+
+ if (m_entry == nullptr) {
+ m_entry = ::operator new(tree->m_entry_size);
+ R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed);
+
+ m_tree = tree;
+ m_offsets = offsets;
+ }
+
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::MoveNext() {
+ R_UNLESS(this->IsValid(), ResultOutOfRange);
+
+ // Invalidate our index, and read the header for the next index.
+ auto entry_index = m_entry_index + 1;
+ if (entry_index == m_entry_set.info.count) {
+ const auto entry_set_index = m_entry_set.info.index + 1;
+ R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange);
+
+ m_entry_index = -1;
+
+ const auto end = m_entry_set.info.end;
+
+ const auto entry_set_size = m_tree->m_node_size;
+ const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
+
+ m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
+ R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
+
+ R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end,
+ ResultInvalidBucketTreeEntrySetOffset);
+
+ entry_index = 0;
+ } else {
+ m_entry_index = -1;
+ }
+
+ // Read the new entry.
+ const auto entry_size = m_tree->m_entry_size;
+ const auto entry_offset = impl::GetBucketTreeEntryOffset(
+ m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
+ m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
+
+ // Note that we changed index.
+ m_entry_index = entry_index;
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::MovePrevious() {
+ R_UNLESS(this->IsValid(), ResultOutOfRange);
+
+ // Invalidate our index, and read the header for the previous index.
+ auto entry_index = m_entry_index;
+ if (entry_index == 0) {
+ R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange);
+
+ m_entry_index = -1;
+
+ const auto start = m_entry_set.info.start;
+
+ const auto entry_set_size = m_tree->m_node_size;
+ const auto entry_set_index = m_entry_set.info.index - 1;
+ const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
+
+ m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
+ R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
+
+ R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end,
+ ResultInvalidBucketTreeEntrySetOffset);
+
+ entry_index = m_entry_set.info.count;
+ } else {
+ m_entry_index = -1;
+ }
+
+ --entry_index;
+
+ // Read the new entry.
+ const auto entry_size = m_tree->m_entry_size;
+ const auto entry_offset = impl::GetBucketTreeEntryOffset(
+ m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
+ m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
+
+ // Note that we changed index.
+ m_entry_index = entry_index;
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::Find(s64 virtual_address) {
+ ASSERT(m_tree != nullptr);
+
+ // Get the node.
+ const auto* const node = m_tree->m_node_l1.Get<Node>();
+ R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange);
+
+ // Get the entry set index.
+ s32 entry_set_index = -1;
+ if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) {
+ const auto start = node->GetEnd();
+ const auto end = node->GetBegin() + m_tree->m_offset_count;
+
+ auto pos = std::upper_bound(start, end, virtual_address);
+ R_UNLESS(start < pos, ResultOutOfRange);
+ --pos;
+
+ entry_set_index = static_cast<s32>(pos - start);
+ } else {
+ const auto start = node->GetBegin();
+ const auto end = node->GetEnd();
+
+ auto pos = std::upper_bound(start, end, virtual_address);
+ R_UNLESS(start < pos, ResultOutOfRange);
+ --pos;
+
+ if (m_tree->IsExistL2()) {
+ const auto node_index = static_cast<s32>(pos - start);
+ R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count,
+ ResultInvalidBucketTreeNodeOffset);
+
+ R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index));
+ } else {
+ entry_set_index = static_cast<s32>(pos - start);
+ }
+ }
+
+ // Validate the entry set index.
+ R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count,
+ ResultInvalidBucketTreeNodeOffset);
+
+ // Find the entry.
+ R_TRY(this->FindEntry(virtual_address, entry_set_index));
+
+ // Set count.
+ m_entry_set_count = m_tree->m_entry_set_count;
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) {
+ const auto node_size = m_tree->m_node_size;
+
+ PooledBuffer pool(node_size, 1);
+ if (node_size <= pool.GetSize()) {
+ R_RETURN(
+ this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer()));
+ } else {
+ pool.Deallocate();
+ R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index));
+ }
+}
+
+Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address,
+ s32 node_index, char* buffer) {
+ // Calculate node extents.
+ const auto node_size = m_tree->m_node_size;
+ const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
+ VirtualFile storage = m_tree->m_node_storage;
+
+ // Read the node.
+ storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset);
+
+ // Validate the header.
+ NodeHeader header;
+ std::memcpy(std::addressof(header), buffer, NodeHeaderSize);
+ R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
+
+ // Create the node, and find.
+ StorageNode node(sizeof(s64), header.count);
+ node.Find(buffer, virtual_address);
+ R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset);
+
+ // Return the index.
+ *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address,
+ s32 node_index) {
+ // Calculate node extents.
+ const auto node_size = m_tree->m_node_size;
+ const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
+ VirtualFile storage = m_tree->m_node_storage;
+
+ // Read and validate the header.
+ NodeHeader header;
+ storage->ReadObject(std::addressof(header), node_offset);
+ R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
+
+ // Create the node, and find.
+ StorageNode node(node_offset, sizeof(s64), header.count);
+ R_TRY(node.Find(storage, virtual_address));
+ R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
+
+ // Return the index.
+ *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) {
+ const auto entry_set_size = m_tree->m_node_size;
+
+ PooledBuffer pool(entry_set_size, 1);
+ if (entry_set_size <= pool.GetSize()) {
+ R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer()));
+ } else {
+ pool.Deallocate();
+ R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index));
+ }
+}
+
+Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index,
+ char* buffer) {
+ // Calculate entry set extents.
+ const auto entry_size = m_tree->m_entry_size;
+ const auto entry_set_size = m_tree->m_node_size;
+ const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
+ VirtualFile storage = m_tree->m_entry_storage;
+
+ // Read the entry set.
+ storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset);
+
+ // Validate the entry_set.
+ EntrySetHeader entry_set;
+ std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader));
+ R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
+
+ // Create the node, and find.
+ StorageNode node(entry_size, entry_set.info.count);
+ node.Find(buffer, virtual_address);
+ R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
+
+ // Copy the data into entry.
+ const auto entry_index = node.GetIndex();
+ const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index);
+ std::memcpy(m_entry, buffer + entry_offset, entry_size);
+
+ // Set our entry set/index.
+ m_entry_set = entry_set;
+ m_entry_index = entry_index;
+
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) {
+ // Calculate entry set extents.
+ const auto entry_size = m_tree->m_entry_size;
+ const auto entry_set_size = m_tree->m_node_size;
+ const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
+ VirtualFile storage = m_tree->m_entry_storage;
+
+ // Read and validate the entry_set.
+ EntrySetHeader entry_set;
+ storage->ReadObject(std::addressof(entry_set), entry_set_offset);
+ R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
+
+ // Create the node, and find.
+ StorageNode node(entry_set_offset, entry_size, entry_set.info.count);
+ R_TRY(node.Find(storage, virtual_address));
+ R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
+
+ // Copy the data into entry.
+ const auto entry_index = node.GetIndex();
+ const auto entry_offset =
+ impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index);
+ storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
+
+ // Set our entry set/index.
+ m_entry_set = entry_set;
+ m_entry_index = entry_index;
+
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "common/alignment.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/literals.h"
+
+#include "core/file_sys/vfs.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+class BucketTree {
+ YUZU_NON_COPYABLE(BucketTree);
+ YUZU_NON_MOVEABLE(BucketTree);
+
+public:
+ static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R');
+ static constexpr u32 Version = 1;
+
+ static constexpr size_t NodeSizeMin = 1_KiB;
+ static constexpr size_t NodeSizeMax = 512_KiB;
+
+public:
+ class Visitor;
+
+ struct Header {
+ u32 magic;
+ u32 version;
+ s32 entry_count;
+ s32 reserved;
+
+ void Format(s32 entry_count);
+ Result Verify() const;
+ };
+ static_assert(std::is_trivial_v<Header>);
+ static_assert(sizeof(Header) == 0x10);
+
+ struct NodeHeader {
+ s32 index;
+ s32 count;
+ s64 offset;
+
+ Result Verify(s32 node_index, size_t node_size, size_t entry_size) const;
+ };
+ static_assert(std::is_trivial_v<NodeHeader>);
+ static_assert(sizeof(NodeHeader) == 0x10);
+
+ struct Offsets {
+ s64 start_offset;
+ s64 end_offset;
+
+ constexpr bool IsInclude(s64 offset) const {
+ return this->start_offset <= offset && offset < this->end_offset;
+ }
+
+ constexpr bool IsInclude(s64 offset, s64 size) const {
+ return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset);
+ }
+ };
+ static_assert(std::is_trivial_v<Offsets>);
+ static_assert(sizeof(Offsets) == 0x10);
+
+ struct OffsetCache {
+ Offsets offsets;
+ std::mutex mutex;
+ bool is_initialized;
+
+ OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {}
+ };
+
+ class ContinuousReadingInfo {
+ public:
+ constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {}
+
+ constexpr void Reset() {
+ m_read_size = 0;
+ m_skip_count = 0;
+ m_done = false;
+ }
+
+ constexpr void SetSkipCount(s32 count) {
+ ASSERT(count >= 0);
+ m_skip_count = count;
+ }
+ constexpr s32 GetSkipCount() const {
+ return m_skip_count;
+ }
+ constexpr bool CheckNeedScan() {
+ return (--m_skip_count) <= 0;
+ }
+
+ constexpr void Done() {
+ m_read_size = 0;
+ m_done = true;
+ }
+ constexpr bool IsDone() const {
+ return m_done;
+ }
+
+ constexpr void SetReadSize(size_t size) {
+ m_read_size = size;
+ }
+ constexpr size_t GetReadSize() const {
+ return m_read_size;
+ }
+ constexpr bool CanDo() const {
+ return m_read_size > 0;
+ }
+
+ private:
+ size_t m_read_size;
+ s32 m_skip_count;
+ bool m_done;
+ };
+
+private:
+ class NodeBuffer {
+ YUZU_NON_COPYABLE(NodeBuffer);
+
+ public:
+ NodeBuffer() : m_header() {}
+
+ ~NodeBuffer() {
+ ASSERT(m_header == nullptr);
+ }
+
+ NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) {
+ rhs.m_header = nullptr;
+ }
+
+ NodeBuffer& operator=(NodeBuffer&& rhs) {
+ if (this != std::addressof(rhs)) {
+ ASSERT(m_header == nullptr);
+
+ m_header = rhs.m_header;
+
+ rhs.m_header = nullptr;
+ }
+ return *this;
+ }
+
+ bool Allocate(size_t node_size) {
+ ASSERT(m_header == nullptr);
+
+ m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)});
+
+ // ASSERT(Common::IsAligned(m_header, sizeof(s64)));
+
+ return m_header != nullptr;
+ }
+
+ void Free(size_t node_size) {
+ if (m_header) {
+ ::operator delete(m_header, std::align_val_t{sizeof(s64)});
+ m_header = nullptr;
+ }
+ }
+
+ void FillZero(size_t node_size) const {
+ if (m_header) {
+ std::memset(m_header, 0, node_size);
+ }
+ }
+
+ NodeHeader* Get() const {
+ return reinterpret_cast<NodeHeader*>(m_header);
+ }
+
+ NodeHeader* operator->() const {
+ return this->Get();
+ }
+
+ template <typename T>
+ T* Get() const {
+ static_assert(std::is_trivial_v<T>);
+ static_assert(sizeof(T) == sizeof(NodeHeader));
+ return reinterpret_cast<T*>(m_header);
+ }
+
+ private:
+ void* m_header;
+ };
+
+private:
+ static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) {
+ return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size);
+ }
+
+ static constexpr s32 GetOffsetCount(size_t node_size) {
+ return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64));
+ }
+
+ static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) {
+ const s32 entry_count_per_node = GetEntryCount(node_size, entry_size);
+ return Common::DivideUp(entry_count, entry_count_per_node);
+ }
+
+ static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) {
+ const s32 offset_count_per_node = GetOffsetCount(node_size);
+ const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
+
+ if (entry_set_count <= offset_count_per_node) {
+ return 0;
+ }
+
+ const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node);
+ ASSERT(node_l2_count <= offset_count_per_node);
+
+ return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)),
+ offset_count_per_node);
+ }
+
+public:
+ BucketTree()
+ : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(),
+ m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {}
+ ~BucketTree() {
+ this->Finalize();
+ }
+
+ Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
+ size_t entry_size, s32 entry_count);
+ void Initialize(size_t node_size, s64 end_offset);
+ void Finalize();
+
+ bool IsInitialized() const {
+ return m_node_size > 0;
+ }
+ bool IsEmpty() const {
+ return m_entry_size == 0;
+ }
+
+ Result Find(Visitor* visitor, s64 virtual_address);
+ Result InvalidateCache();
+
+ s32 GetEntryCount() const {
+ return m_entry_count;
+ }
+
+ Result GetOffsets(Offsets* out) {
+ // Ensure we have an offset cache.
+ R_TRY(this->EnsureOffsetCache());
+
+ // Set the output.
+ *out = m_offset_cache.offsets;
+ R_SUCCEED();
+ }
+
+public:
+ static constexpr s64 QueryHeaderStorageSize() {
+ return sizeof(Header);
+ }
+
+ static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size,
+ s32 entry_count) {
+ ASSERT(entry_size >= sizeof(s64));
+ ASSERT(node_size >= entry_size + sizeof(NodeHeader));
+ ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
+ ASSERT(Common::IsPowerOfTwo(node_size));
+ ASSERT(entry_count >= 0);
+
+ if (entry_count <= 0) {
+ return 0;
+ }
+ return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) *
+ static_cast<s64>(node_size);
+ }
+
+ static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size,
+ s32 entry_count) {
+ ASSERT(entry_size >= sizeof(s64));
+ ASSERT(node_size >= entry_size + sizeof(NodeHeader));
+ ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
+ ASSERT(Common::IsPowerOfTwo(node_size));
+ ASSERT(entry_count >= 0);
+
+ if (entry_count <= 0) {
+ return 0;
+ }
+ return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size);
+ }
+
+private:
+ template <typename EntryType>
+ struct ContinuousReadingParam {
+ s64 offset;
+ size_t size;
+ NodeHeader entry_set;
+ s32 entry_index;
+ Offsets offsets;
+ EntryType entry;
+ };
+
+private:
+ template <typename EntryType>
+ Result ScanContinuousReading(ContinuousReadingInfo* out_info,
+ const ContinuousReadingParam<EntryType>& param) const;
+
+ bool IsExistL2() const {
+ return m_offset_count < m_entry_set_count;
+ }
+ bool IsExistOffsetL2OnL1() const {
+ return this->IsExistL2() && m_node_l1->count < m_offset_count;
+ }
+
+ s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const {
+ return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index;
+ }
+
+ Result EnsureOffsetCache();
+
+private:
+ mutable VirtualFile m_node_storage;
+ mutable VirtualFile m_entry_storage;
+ NodeBuffer m_node_l1;
+ size_t m_node_size;
+ size_t m_entry_size;
+ s32 m_entry_count;
+ s32 m_offset_count;
+ s32 m_entry_set_count;
+ OffsetCache m_offset_cache;
+};
+
+class BucketTree::Visitor {
+ YUZU_NON_COPYABLE(Visitor);
+ YUZU_NON_MOVEABLE(Visitor);
+
+public:
+ constexpr Visitor()
+ : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {}
+ ~Visitor() {
+ if (m_entry != nullptr) {
+ ::operator delete(m_entry, m_tree->m_entry_size);
+ m_tree = nullptr;
+ m_entry = nullptr;
+ }
+ }
+
+ bool IsValid() const {
+ return m_entry_index >= 0;
+ }
+ bool CanMoveNext() const {
+ return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count ||
+ m_entry_set.info.index + 1 < m_entry_set_count);
+ }
+ bool CanMovePrevious() const {
+ return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0);
+ }
+
+ Result MoveNext();
+ Result MovePrevious();
+
+ template <typename EntryType>
+ Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const;
+
+ const void* Get() const {
+ ASSERT(this->IsValid());
+ return m_entry;
+ }
+
+ template <typename T>
+ const T* Get() const {
+ ASSERT(this->IsValid());
+ return reinterpret_cast<const T*>(m_entry);
+ }
+
+ const BucketTree* GetTree() const {
+ return m_tree;
+ }
+
+private:
+ Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets);
+
+ Result Find(s64 virtual_address);
+
+ Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index);
+ Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index,
+ char* buffer);
+ Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index);
+
+ Result FindEntry(s64 virtual_address, s32 entry_set_index);
+ Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer);
+ Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index);
+
+private:
+ friend class BucketTree;
+
+ union EntrySetHeader {
+ NodeHeader header;
+ struct Info {
+ s32 index;
+ s32 count;
+ s64 end;
+ s64 start;
+ } info;
+ static_assert(std::is_trivial_v<Info>);
+ };
+ static_assert(std::is_trivial_v<EntrySetHeader>);
+
+ const BucketTree* m_tree;
+ BucketTree::Offsets m_offsets;
+ void* m_entry;
+ s32 m_entry_index;
+ s32 m_entry_set_count;
+ EntrySetHeader m_entry_set;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+
+namespace FileSys {
+
+template <typename EntryType>
+Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info,
+ const ContinuousReadingParam<EntryType>& param) const {
+ static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>);
+
+ // Validate our preconditions.
+ ASSERT(this->IsInitialized());
+ ASSERT(out_info != nullptr);
+ ASSERT(m_entry_size == sizeof(EntryType));
+
+ // Reset the output.
+ out_info->Reset();
+
+ // If there's nothing to read, we're done.
+ R_SUCCEED_IF(param.size == 0);
+
+ // If we're reading a fragment, we're done.
+ R_SUCCEED_IF(param.entry.IsFragment());
+
+ // Validate the first entry.
+ auto entry = param.entry;
+ auto cur_offset = param.offset;
+ R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange);
+
+ // Create a pooled buffer for our scan.
+ PooledBuffer pool(m_node_size, 1);
+ char* buffer = nullptr;
+
+ s64 entry_storage_size = m_entry_storage->GetSize();
+
+ // Read the node.
+ if (m_node_size <= pool.GetSize()) {
+ buffer = pool.GetBuffer();
+ const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size);
+ R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size),
+ ResultInvalidBucketTreeNodeEntryCount);
+
+ m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs);
+ }
+
+ // Calculate extents.
+ const auto end_offset = cur_offset + static_cast<s64>(param.size);
+ s64 phys_offset = entry.GetPhysicalOffset();
+
+ // Start merge tracking.
+ s64 merge_size = 0;
+ s64 readable_size = 0;
+ bool merged = false;
+
+ // Iterate.
+ auto entry_index = param.entry_index;
+ for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) {
+ // If we're past the end, we're done.
+ if (end_offset <= cur_offset) {
+ break;
+ }
+
+ // Validate the entry offset.
+ const auto entry_offset = entry.GetVirtualOffset();
+ R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
+
+ // Get the next entry.
+ EntryType next_entry = {};
+ s64 next_entry_offset;
+
+ if (entry_index + 1 < entry_count) {
+ if (buffer != nullptr) {
+ const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1);
+ std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size);
+ } else {
+ const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size,
+ m_entry_size, entry_index + 1);
+ m_entry_storage->ReadObject(std::addressof(next_entry), ofs);
+ }
+
+ next_entry_offset = next_entry.GetVirtualOffset();
+ R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
+ } else {
+ next_entry_offset = param.entry_set.offset;
+ }
+
+ // Validate the next entry offset.
+ R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
+
+ // Determine the much data there is.
+ const auto data_size = next_entry_offset - cur_offset;
+ ASSERT(data_size > 0);
+
+ // Determine how much data we should read.
+ const auto remaining_size = end_offset - cur_offset;
+ const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size));
+ ASSERT(read_size <= param.size);
+
+ // Update our merge tracking.
+ if (entry.IsFragment()) {
+ // If we can't merge, stop looping.
+ if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) {
+ break;
+ }
+
+ // Otherwise, add the current size to the merge size.
+ merge_size += read_size;
+ } else {
+ // If we can't merge, stop looping.
+ if (phys_offset != entry.GetPhysicalOffset()) {
+ break;
+ }
+
+ // Add the size to the readable amount.
+ readable_size += merge_size + read_size;
+ ASSERT(readable_size <= static_cast<s64>(param.size));
+
+ // Update whether we've merged.
+ merged |= merge_size > 0;
+ merge_size = 0;
+ }
+
+ // Advance.
+ cur_offset += read_size;
+ ASSERT(cur_offset <= end_offset);
+
+ phys_offset += next_entry_offset - entry_offset;
+ entry = next_entry;
+ }
+
+ // If we merged, set our readable size.
+ if (merged) {
+ out_info->SetReadSize(static_cast<size_t>(readable_size));
+ }
+ out_info->SetSkipCount(entry_index - param.entry_index);
+
+ R_SUCCEED();
+}
+
+template <typename EntryType>
+Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset,
+ size_t size) const {
+ static_assert(std::is_trivial_v<EntryType>);
+ ASSERT(this->IsValid());
+
+ // Create our parameters.
+ ContinuousReadingParam<EntryType> param = {
+ .offset = offset,
+ .size = size,
+ .entry_set = m_entry_set.header,
+ .entry_index = m_entry_index,
+ .offsets{},
+ .entry{},
+ };
+ std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets),
+ sizeof(BucketTree::Offsets));
+ std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType));
+
+ // Scan.
+ R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param));
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+
+namespace FileSys::impl {
+
+class SafeValue {
+public:
+ static s64 GetInt64(const void* ptr) {
+ s64 value;
+ std::memcpy(std::addressof(value), ptr, sizeof(s64));
+ return value;
+ }
+
+ static s64 GetInt64(const s64* ptr) {
+ return GetInt64(static_cast<const void*>(ptr));
+ }
+
+ static s64 GetInt64(const s64& v) {
+ return GetInt64(std::addressof(v));
+ }
+
+ static void SetInt64(void* dst, const void* src) {
+ std::memcpy(dst, src, sizeof(s64));
+ }
+
+ static void SetInt64(void* dst, const s64* src) {
+ return SetInt64(dst, static_cast<const void*>(src));
+ }
+
+ static void SetInt64(void* dst, const s64& v) {
+ return SetInt64(dst, std::addressof(v));
+ }
+};
+
+template <typename IteratorType>
+struct BucketTreeNode {
+ using Header = BucketTree::NodeHeader;
+
+ Header header;
+
+ s32 GetCount() const {
+ return this->header.count;
+ }
+
+ void* GetArray() {
+ return std::addressof(this->header) + 1;
+ }
+ template <typename T>
+ T* GetArray() {
+ return reinterpret_cast<T*>(this->GetArray());
+ }
+ const void* GetArray() const {
+ return std::addressof(this->header) + 1;
+ }
+ template <typename T>
+ const T* GetArray() const {
+ return reinterpret_cast<const T*>(this->GetArray());
+ }
+
+ s64 GetBeginOffset() const {
+ return *this->GetArray<s64>();
+ }
+ s64 GetEndOffset() const {
+ return this->header.offset;
+ }
+
+ IteratorType GetBegin() {
+ return IteratorType(this->GetArray<s64>());
+ }
+ IteratorType GetEnd() {
+ return IteratorType(this->GetArray<s64>()) + this->header.count;
+ }
+ IteratorType GetBegin() const {
+ return IteratorType(this->GetArray<s64>());
+ }
+ IteratorType GetEnd() const {
+ return IteratorType(this->GetArray<s64>()) + this->header.count;
+ }
+
+ IteratorType GetBegin(size_t entry_size) {
+ return IteratorType(this->GetArray(), entry_size);
+ }
+ IteratorType GetEnd(size_t entry_size) {
+ return IteratorType(this->GetArray(), entry_size) + this->header.count;
+ }
+ IteratorType GetBegin(size_t entry_size) const {
+ return IteratorType(this->GetArray(), entry_size);
+ }
+ IteratorType GetEnd(size_t entry_size) const {
+ return IteratorType(this->GetArray(), entry_size) + this->header.count;
+ }
+};
+
+constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size,
+ s32 entry_index) {
+ return entry_set_offset + sizeof(BucketTree::NodeHeader) +
+ entry_index * static_cast<s64>(entry_size);
+}
+
+constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size,
+ size_t entry_size, s32 entry_index) {
+ return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size,
+ entry_index);
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/literals.h"
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+#include "core/file_sys/fssystem/fssystem_compression_common.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+class CompressedStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(CompressedStorage);
+ YUZU_NON_MOVEABLE(CompressedStorage);
+
+public:
+ static constexpr size_t NodeSize = 16_KiB;
+
+ struct Entry {
+ s64 virt_offset;
+ s64 phys_offset;
+ CompressionType compression_type;
+ s32 phys_size;
+
+ s64 GetPhysicalSize() const {
+ return this->phys_size;
+ }
+ };
+ static_assert(std::is_trivial_v<Entry>);
+ static_assert(sizeof(Entry) == 0x18);
+
+public:
+ static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
+ return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+ static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
+ return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+private:
+ class CompressedStorageCore {
+ YUZU_NON_COPYABLE(CompressedStorageCore);
+ YUZU_NON_MOVEABLE(CompressedStorageCore);
+
+ public:
+ CompressedStorageCore() : m_table(), m_data_storage() {}
+
+ ~CompressedStorageCore() {
+ this->Finalize();
+ }
+
+ public:
+ Result Initialize(VirtualFile data_storage, VirtualFile node_storage,
+ VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max,
+ size_t continuous_reading_size_max,
+ GetDecompressorFunction get_decompressor) {
+ // Check pre-conditions.
+ ASSERT(0 < block_size_max);
+ ASSERT(block_size_max <= continuous_reading_size_max);
+ ASSERT(get_decompressor != nullptr);
+
+ // Initialize our entry table.
+ R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry),
+ bktr_entry_count));
+
+ // Set our other fields.
+ m_block_size_max = block_size_max;
+ m_continuous_reading_size_max = continuous_reading_size_max;
+ m_data_storage = data_storage;
+ m_get_decompressor_function = get_decompressor;
+
+ R_SUCCEED();
+ }
+
+ void Finalize() {
+ if (this->IsInitialized()) {
+ m_table.Finalize();
+ m_data_storage = VirtualFile();
+ }
+ }
+
+ VirtualFile GetDataStorage() {
+ return m_data_storage;
+ }
+
+ Result GetDataStorageSize(s64* out) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+
+ // Get size.
+ *out = m_data_storage->GetSize();
+
+ R_SUCCEED();
+ }
+
+ BucketTree& GetEntryTable() {
+ return m_table;
+ }
+
+ Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count,
+ s64 offset, s64 size) {
+ // Check pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Check that we can output the count.
+ R_UNLESS(out_read_count != nullptr, ResultNullptrArgument);
+
+ // Check that we have anything to read at all.
+ R_SUCCEED_IF(size == 0);
+
+ // Check that either we have a buffer, or this is to determine how many we need.
+ if (max_entry_count != 0) {
+ R_UNLESS(out_entries != nullptr, ResultNullptrArgument);
+ }
+
+ // Get the table offsets.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ // Validate arguments.
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->virt_offset;
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultUnexpectedInCompressedStorageA);
+ }
+
+ // Get the entries.
+ const auto end_offset = offset + size;
+ s32 read_count = 0;
+ while (visitor.Get<Entry>()->virt_offset < end_offset) {
+ // If we should be setting the output, do so.
+ if (max_entry_count != 0) {
+ // Ensure we only read as many entries as we can.
+ if (read_count >= max_entry_count) {
+ break;
+ }
+
+ // Set the current output entry.
+ out_entries[read_count] = *visitor.Get<Entry>();
+ }
+
+ // Increase the read count.
+ ++read_count;
+
+ // If we're at the end, we're done.
+ if (!visitor.CanMoveNext()) {
+ break;
+ }
+
+ // Move to the next entry.
+ R_TRY(visitor.MoveNext());
+ }
+
+ // Set the output read count.
+ *out_read_count = read_count;
+ R_SUCCEED();
+ }
+
+ Result GetSize(s64* out) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+
+ // Get our table offsets.
+ BucketTree::Offsets offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(offsets)));
+
+ // Set the output.
+ *out = offsets.end_offset;
+ R_SUCCEED();
+ }
+
+ Result OperatePerEntry(s64 offset, s64 size, auto f) {
+ // Check pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Succeed if there's nothing to operate on.
+ R_SUCCEED_IF(size == 0);
+
+ // Get the table offsets.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ // Validate arguments.
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->virt_offset;
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultUnexpectedInCompressedStorageA);
+ }
+
+ // Prepare to operate in chunks.
+ auto cur_offset = offset;
+ const auto end_offset = offset + static_cast<s64>(size);
+
+ while (cur_offset < end_offset) {
+ // Get the current entry.
+ const auto cur_entry = *visitor.Get<Entry>();
+
+ // Get and validate the entry's offset.
+ const auto cur_entry_offset = cur_entry.virt_offset;
+ R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA);
+
+ // Get and validate the next entry offset.
+ s64 next_entry_offset;
+ if (visitor.CanMoveNext()) {
+ R_TRY(visitor.MoveNext());
+ next_entry_offset = visitor.Get<Entry>()->virt_offset;
+ R_UNLESS(table_offsets.IsInclude(next_entry_offset),
+ ResultUnexpectedInCompressedStorageA);
+ } else {
+ next_entry_offset = table_offsets.end_offset;
+ }
+ R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA);
+
+ // Get the offset of the entry in the data we read.
+ const auto data_offset = cur_offset - cur_entry_offset;
+ const auto data_size = (next_entry_offset - cur_entry_offset);
+ ASSERT(data_size > 0);
+
+ // Determine how much is left.
+ const auto remaining_size = end_offset - cur_offset;
+ const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
+ ASSERT(cur_size <= size);
+
+ // Get the data storage size.
+ s64 storage_size = m_data_storage->GetSize();
+
+ // Check that our read remains naively physically in bounds.
+ R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size,
+ ResultUnexpectedInCompressedStorageC);
+
+ // If we have any compression, verify that we remain physically in bounds.
+ if (cur_entry.compression_type != CompressionType::None) {
+ R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size,
+ ResultUnexpectedInCompressedStorageC);
+ }
+
+ // Check that block alignment requirements are met.
+ if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) {
+ R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment),
+ ResultUnexpectedInCompressedStorageA);
+ }
+
+ // Invoke the operator.
+ bool is_continuous = true;
+ R_TRY(
+ f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size));
+
+ // If not continuous, we're done.
+ if (!is_continuous) {
+ break;
+ }
+
+ // Advance.
+ cur_offset += cur_size;
+ }
+
+ R_SUCCEED();
+ }
+
+ public:
+ using ReadImplFunction = std::function<Result(void*, size_t)>;
+ using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>;
+
+ public:
+ Result Read(s64 offset, s64 size, const ReadFunction& read_func) {
+ // Check pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Succeed immediately, if we have nothing to read.
+ R_SUCCEED_IF(size == 0);
+
+ // Declare read lambda.
+ constexpr int EntriesCountMax = 0x80;
+ struct Entries {
+ CompressionType compression_type;
+ u32 gap_from_prev;
+ u32 physical_size;
+ u32 virtual_size;
+ };
+ std::array<Entries, EntriesCountMax> entries;
+ s32 entry_count = 0;
+ Entry prev_entry = {
+ .virt_offset = -1,
+ .phys_offset{},
+ .compression_type{},
+ .phys_size{},
+ };
+ bool will_allocate_pooled_buffer = false;
+ s64 required_access_physical_offset = 0;
+ s64 required_access_physical_size = 0;
+
+ auto PerformRequiredRead = [&]() -> Result {
+ // If there are no entries, we have nothing to do.
+ R_SUCCEED_IF(entry_count == 0);
+
+ // Get the remaining size in a convenient form.
+ const size_t total_required_size =
+ static_cast<size_t>(required_access_physical_size);
+
+ // Perform the read based on whether we need to allocate a buffer.
+ if (will_allocate_pooled_buffer) {
+ // Allocate a pooled buffer.
+ PooledBuffer pooled_buffer;
+ if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) {
+ pooled_buffer.Allocate(total_required_size, m_block_size_max);
+ } else {
+ pooled_buffer.AllocateParticularlyLarge(
+ std::min<size_t>(
+ total_required_size,
+ PooledBuffer::GetAllocatableParticularlyLargeSizeMax()),
+ m_block_size_max);
+ }
+
+ // Read each of the entries.
+ for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) {
+ // Determine the current read size.
+ bool will_use_pooled_buffer = false;
+ const size_t cur_read_size = [&]() -> size_t {
+ if (const size_t target_entry_size =
+ static_cast<size_t>(entries[entry_idx].physical_size) +
+ static_cast<size_t>(entries[entry_idx].gap_from_prev);
+ target_entry_size <= pooled_buffer.GetSize()) {
+ // We'll be using the pooled buffer.
+ will_use_pooled_buffer = true;
+
+ // Determine how much we can read.
+ const size_t max_size = std::min<size_t>(
+ required_access_physical_size, pooled_buffer.GetSize());
+
+ size_t read_size = 0;
+ for (auto n = entry_idx; n < entry_count; ++n) {
+ const size_t cur_entry_size =
+ static_cast<size_t>(entries[n].physical_size) +
+ static_cast<size_t>(entries[n].gap_from_prev);
+ if (read_size + cur_entry_size > max_size) {
+ break;
+ }
+
+ read_size += cur_entry_size;
+ }
+
+ return read_size;
+ } else {
+ // If we don't fit, we must be uncompressed.
+ ASSERT(entries[entry_idx].compression_type ==
+ CompressionType::None);
+
+ // We can perform the whole of an uncompressed read directly.
+ return entries[entry_idx].virtual_size;
+ }
+ }();
+
+ // Perform the read based on whether or not we'll use the pooled buffer.
+ if (will_use_pooled_buffer) {
+ // Read the compressed data into the pooled buffer.
+ auto* const buffer = pooled_buffer.GetBuffer();
+ m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size,
+ required_access_physical_offset);
+
+ // Decompress the data.
+ size_t buffer_offset;
+ for (buffer_offset = 0;
+ entry_idx < entry_count &&
+ ((static_cast<size_t>(entries[entry_idx].physical_size) +
+ static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 ||
+ buffer_offset < cur_read_size);
+ buffer_offset += entries[entry_idx++].physical_size) {
+ // Advance by the relevant gap.
+ buffer_offset += entries[entry_idx].gap_from_prev;
+
+ const auto compression_type = entries[entry_idx].compression_type;
+ switch (compression_type) {
+ case CompressionType::None: {
+ // Check that we can remain within bounds.
+ ASSERT(buffer_offset + entries[entry_idx].virtual_size <=
+ cur_read_size);
+
+ // Perform no decompression.
+ R_TRY(read_func(
+ entries[entry_idx].virtual_size,
+ [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size == entries[entry_idx].virtual_size);
+
+ // We have no compression, so just copy the data
+ // out.
+ std::memcpy(dst, buffer + buffer_offset,
+ entries[entry_idx].virtual_size);
+ R_SUCCEED();
+ }));
+
+ break;
+ }
+ case CompressionType::Zeros: {
+ // Check that we can remain within bounds.
+ ASSERT(buffer_offset <= cur_read_size);
+
+ // Zero the memory.
+ R_TRY(read_func(
+ entries[entry_idx].virtual_size,
+ [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size == entries[entry_idx].virtual_size);
+
+ // The data is zeroes, so zero the buffer.
+ std::memset(dst, 0, entries[entry_idx].virtual_size);
+ R_SUCCEED();
+ }));
+
+ break;
+ }
+ default: {
+ // Check that we can remain within bounds.
+ ASSERT(buffer_offset + entries[entry_idx].physical_size <=
+ cur_read_size);
+
+ // Get the decompressor.
+ const auto decompressor =
+ this->GetDecompressor(compression_type);
+ R_UNLESS(decompressor != nullptr,
+ ResultUnexpectedInCompressedStorageB);
+
+ // Decompress the data.
+ R_TRY(read_func(entries[entry_idx].virtual_size,
+ [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size ==
+ entries[entry_idx].virtual_size);
+
+ // Perform the decompression.
+ R_RETURN(decompressor(
+ dst, entries[entry_idx].virtual_size,
+ buffer + buffer_offset,
+ entries[entry_idx].physical_size));
+ }));
+
+ break;
+ }
+ }
+ }
+
+ // Check that we processed the correct amount of data.
+ ASSERT(buffer_offset == cur_read_size);
+ } else {
+ // Account for the gap from the previous entry.
+ required_access_physical_offset += entries[entry_idx].gap_from_prev;
+ required_access_physical_size -= entries[entry_idx].gap_from_prev;
+
+ // We don't need the buffer (as the data is uncompressed), so just
+ // execute the read.
+ R_TRY(
+ read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size == cur_read_size);
+
+ // Perform the read.
+ m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size,
+ required_access_physical_offset);
+
+ R_SUCCEED();
+ }));
+ }
+
+ // Advance on.
+ required_access_physical_offset += cur_read_size;
+ required_access_physical_size -= cur_read_size;
+ }
+
+ // Verify that we have nothing remaining to read.
+ ASSERT(required_access_physical_size == 0);
+
+ R_SUCCEED();
+ } else {
+ // We don't need a buffer, so just execute the read.
+ R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size == total_required_size);
+
+ // Perform the read.
+ m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size,
+ required_access_physical_offset);
+
+ R_SUCCEED();
+ }));
+ }
+
+ R_SUCCEED();
+ };
+
+ R_TRY(this->OperatePerEntry(
+ offset, size,
+ [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
+ s64 data_offset, s64 read_size) -> Result {
+ // Determine the physical extents.
+ s64 physical_offset, physical_size;
+ if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) {
+ physical_offset = entry.phys_offset + data_offset;
+ physical_size = read_size;
+ } else {
+ physical_offset = entry.phys_offset;
+ physical_size = entry.GetPhysicalSize();
+ }
+
+ // If we have a pending data storage operation, perform it if we have to.
+ const s64 required_access_physical_end =
+ required_access_physical_offset + required_access_physical_size;
+ if (required_access_physical_size > 0) {
+ const bool required_by_gap =
+ !(required_access_physical_end <= physical_offset &&
+ physical_offset <= Common::AlignUp(required_access_physical_end,
+ CompressionBlockAlignment));
+ const bool required_by_continuous_size =
+ ((physical_size + physical_offset) - required_access_physical_end) +
+ required_access_physical_size >
+ static_cast<s64>(m_continuous_reading_size_max);
+ const bool required_by_entry_count = entry_count == EntriesCountMax;
+ if (required_by_gap || required_by_continuous_size ||
+ required_by_entry_count) {
+ // Check that our planned access is sane.
+ ASSERT(!will_allocate_pooled_buffer ||
+ required_access_physical_size <=
+ static_cast<s64>(m_continuous_reading_size_max));
+
+ // Perform the required read.
+ const Result rc = PerformRequiredRead();
+ if (R_FAILED(rc)) {
+ R_THROW(rc);
+ }
+
+ // Reset our requirements.
+ prev_entry.virt_offset = -1;
+ required_access_physical_size = 0;
+ entry_count = 0;
+ will_allocate_pooled_buffer = false;
+ }
+ }
+
+ // Sanity check that we're within bounds on entries.
+ ASSERT(entry_count < EntriesCountMax);
+
+ // Determine if a buffer allocation is needed.
+ if (entry.compression_type != CompressionType::None ||
+ (prev_entry.virt_offset >= 0 &&
+ entry.virt_offset - prev_entry.virt_offset !=
+ entry.phys_offset - prev_entry.phys_offset)) {
+ will_allocate_pooled_buffer = true;
+ }
+
+ // If we need to access the data storage, update our required access parameters.
+ if (CompressionTypeUtility::IsDataStorageAccessRequired(
+ entry.compression_type)) {
+ // If the data is compressed, ensure the access is sane.
+ if (entry.compression_type != CompressionType::None) {
+ R_UNLESS(data_offset == 0, ResultInvalidOffset);
+ R_UNLESS(virtual_data_size == read_size, ResultInvalidSize);
+ R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max),
+ ResultUnexpectedInCompressedStorageD);
+ }
+
+ // Update the required access parameters.
+ s64 gap_from_prev;
+ if (required_access_physical_size > 0) {
+ gap_from_prev = physical_offset - required_access_physical_end;
+ } else {
+ gap_from_prev = 0;
+ required_access_physical_offset = physical_offset;
+ }
+ required_access_physical_size += physical_size + gap_from_prev;
+
+ // Create an entry to access the data storage.
+ entries[entry_count++] = {
+ .compression_type = entry.compression_type,
+ .gap_from_prev = static_cast<u32>(gap_from_prev),
+ .physical_size = static_cast<u32>(physical_size),
+ .virtual_size = static_cast<u32>(read_size),
+ };
+ } else {
+ // Verify that we're allowed to be operating on the non-data-storage-access
+ // type.
+ R_UNLESS(entry.compression_type == CompressionType::Zeros,
+ ResultUnexpectedInCompressedStorageB);
+
+ // If we have entries, create a fake entry for the zero region.
+ if (entry_count != 0) {
+ // We need to have a physical size.
+ R_UNLESS(entry.GetPhysicalSize() != 0,
+ ResultUnexpectedInCompressedStorageD);
+
+ // Create a fake entry.
+ entries[entry_count++] = {
+ .compression_type = CompressionType::Zeros,
+ .gap_from_prev = 0,
+ .physical_size = 0,
+ .virtual_size = static_cast<u32>(read_size),
+ };
+ } else {
+ // We have no entries, so we can just perform the read.
+ const Result rc =
+ read_func(static_cast<size_t>(read_size),
+ [&](void* dst, size_t dst_size) -> Result {
+ // Check the space we should zero is correct.
+ ASSERT(dst_size == static_cast<size_t>(read_size));
+
+ // Zero the memory.
+ std::memset(dst, 0, read_size);
+ R_SUCCEED();
+ });
+ if (R_FAILED(rc)) {
+ R_THROW(rc);
+ }
+ }
+ }
+
+ // Set the previous entry.
+ prev_entry = entry;
+
+ // We're continuous.
+ *out_continuous = true;
+ R_SUCCEED();
+ }));
+
+ // If we still have a pending access, perform it.
+ if (required_access_physical_size != 0) {
+ R_TRY(PerformRequiredRead());
+ }
+
+ R_SUCCEED();
+ }
+
+ private:
+ DecompressorFunction GetDecompressor(CompressionType type) const {
+ // Check that we can get a decompressor for the type.
+ if (CompressionTypeUtility::IsUnknownType(type)) {
+ return nullptr;
+ }
+
+ // Get the decompressor.
+ return m_get_decompressor_function(type);
+ }
+
+ bool IsInitialized() const {
+ return m_table.IsInitialized();
+ }
+
+ private:
+ size_t m_block_size_max;
+ size_t m_continuous_reading_size_max;
+ BucketTree m_table;
+ VirtualFile m_data_storage;
+ GetDecompressorFunction m_get_decompressor_function;
+ };
+
+ class CacheManager {
+ YUZU_NON_COPYABLE(CacheManager);
+ YUZU_NON_MOVEABLE(CacheManager);
+
+ private:
+ struct AccessRange {
+ s64 virtual_offset;
+ s64 virtual_size;
+ u32 physical_size;
+ bool is_block_alignment_required;
+
+ s64 GetEndVirtualOffset() const {
+ return this->virtual_offset + this->virtual_size;
+ }
+ };
+ static_assert(std::is_trivial_v<AccessRange>);
+
+ public:
+ CacheManager() = default;
+
+ public:
+ Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1,
+ size_t max_cache_entries) {
+ // Set our fields.
+ m_storage_size = storage_size;
+
+ R_SUCCEED();
+ }
+
+ Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) {
+ // If we have nothing to read, succeed.
+ R_SUCCEED_IF(size == 0);
+
+ // Check that we have a buffer to read into.
+ R_UNLESS(buffer != nullptr, ResultNullptrArgument);
+
+ // Check that the read is in bounds.
+ R_UNLESS(offset <= m_storage_size, ResultInvalidOffset);
+
+ // Determine how much we can read.
+ const size_t read_size = std::min<size_t>(size, m_storage_size - offset);
+
+ // Create head/tail ranges.
+ AccessRange head_range = {};
+ AccessRange tail_range = {};
+ bool is_tail_set = false;
+
+ // Operate to determine the head range.
+ R_TRY(core.OperatePerEntry(
+ offset, 1,
+ [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
+ s64 data_offset, s64 data_read_size) -> Result {
+ // Set the head range.
+ head_range = {
+ .virtual_offset = entry.virt_offset,
+ .virtual_size = virtual_data_size,
+ .physical_size = static_cast<u32>(entry.phys_size),
+ .is_block_alignment_required =
+ CompressionTypeUtility::IsBlockAlignmentRequired(
+ entry.compression_type),
+ };
+
+ // If required, set the tail range.
+ if (static_cast<s64>(offset + read_size) <=
+ entry.virt_offset + virtual_data_size) {
+ tail_range = {
+ .virtual_offset = entry.virt_offset,
+ .virtual_size = virtual_data_size,
+ .physical_size = static_cast<u32>(entry.phys_size),
+ .is_block_alignment_required =
+ CompressionTypeUtility::IsBlockAlignmentRequired(
+ entry.compression_type),
+ };
+ is_tail_set = true;
+ }
+
+ // We only want to determine the head range, so we're not continuous.
+ *out_continuous = false;
+ R_SUCCEED();
+ }));
+
+ // If necessary, determine the tail range.
+ if (!is_tail_set) {
+ R_TRY(core.OperatePerEntry(
+ offset + read_size - 1, 1,
+ [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
+ s64 data_offset, s64 data_read_size) -> Result {
+ // Set the tail range.
+ tail_range = {
+ .virtual_offset = entry.virt_offset,
+ .virtual_size = virtual_data_size,
+ .physical_size = static_cast<u32>(entry.phys_size),
+ .is_block_alignment_required =
+ CompressionTypeUtility::IsBlockAlignmentRequired(
+ entry.compression_type),
+ };
+
+ // We only want to determine the tail range, so we're not continuous.
+ *out_continuous = false;
+ R_SUCCEED();
+ }));
+ }
+
+ // Begin performing the accesses.
+ s64 cur_offset = offset;
+ size_t cur_size = read_size;
+ char* cur_dst = static_cast<char*>(buffer);
+
+ // Determine our alignment.
+ const bool head_unaligned = head_range.is_block_alignment_required &&
+ (cur_offset != head_range.virtual_offset ||
+ static_cast<s64>(cur_size) < head_range.virtual_size);
+ const bool tail_unaligned = [&]() -> bool {
+ if (tail_range.is_block_alignment_required) {
+ if (static_cast<s64>(cur_size + cur_offset) ==
+ tail_range.GetEndVirtualOffset()) {
+ return false;
+ } else if (!head_unaligned) {
+ return true;
+ } else {
+ return head_range.GetEndVirtualOffset() <
+ static_cast<s64>(cur_size + cur_offset);
+ }
+ } else {
+ return false;
+ }
+ }();
+
+ // Determine start/end offsets.
+ const s64 start_offset =
+ head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset;
+ const s64 end_offset = tail_range.is_block_alignment_required
+ ? tail_range.GetEndVirtualOffset()
+ : cur_offset + cur_size;
+
+ // Perform the read.
+ bool is_burst_reading = false;
+ R_TRY(core.Read(
+ start_offset, end_offset - start_offset,
+ [&](size_t size_buffer_required,
+ const CompressedStorageCore::ReadImplFunction& read_impl) -> Result {
+ // Determine whether we're burst reading.
+ const AccessRange* unaligned_range = nullptr;
+ if (!is_burst_reading) {
+ // Check whether we're using head, tail, or none as unaligned.
+ if (head_unaligned && head_range.virtual_offset <= cur_offset &&
+ cur_offset < head_range.GetEndVirtualOffset()) {
+ unaligned_range = std::addressof(head_range);
+ } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset &&
+ cur_offset < tail_range.GetEndVirtualOffset()) {
+ unaligned_range = std::addressof(tail_range);
+ } else {
+ is_burst_reading = true;
+ }
+ }
+ ASSERT((is_burst_reading ^ (unaligned_range != nullptr)));
+
+ // Perform reading by burst, or not.
+ if (is_burst_reading) {
+ // Check that the access is valid for burst reading.
+ ASSERT(size_buffer_required <= cur_size);
+
+ // Perform the read.
+ Result rc = read_impl(cur_dst, size_buffer_required);
+ if (R_FAILED(rc)) {
+ R_THROW(rc);
+ }
+
+ // Advance.
+ cur_dst += size_buffer_required;
+ cur_offset += size_buffer_required;
+ cur_size -= size_buffer_required;
+
+ // Determine whether we're going to continue burst reading.
+ const s64 offset_aligned =
+ tail_unaligned ? tail_range.virtual_offset : end_offset;
+ ASSERT(cur_offset <= offset_aligned);
+
+ if (offset_aligned <= cur_offset) {
+ is_burst_reading = false;
+ }
+ } else {
+ // We're not burst reading, so we have some unaligned range.
+ ASSERT(unaligned_range != nullptr);
+
+ // Check that the size is correct.
+ ASSERT(size_buffer_required ==
+ static_cast<size_t>(unaligned_range->virtual_size));
+
+ // Get a pooled buffer for our read.
+ PooledBuffer pooled_buffer;
+ pooled_buffer.Allocate(size_buffer_required, size_buffer_required);
+
+ // Perform read.
+ Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required);
+ if (R_FAILED(rc)) {
+ R_THROW(rc);
+ }
+
+ // Copy the data we read to the destination.
+ const size_t skip_size = cur_offset - unaligned_range->virtual_offset;
+ const size_t copy_size = std::min<size_t>(
+ cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset);
+
+ std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size);
+
+ // Advance.
+ cur_dst += copy_size;
+ cur_offset += copy_size;
+ cur_size -= copy_size;
+ }
+
+ R_SUCCEED();
+ }));
+
+ R_SUCCEED();
+ }
+
+ private:
+ s64 m_storage_size = 0;
+ };
+
+public:
+ CompressedStorage() = default;
+ virtual ~CompressedStorage() {
+ this->Finalize();
+ }
+
+ Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
+ s32 bktr_entry_count, size_t block_size_max,
+ size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor,
+ size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) {
+ // Initialize our core.
+ R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count,
+ block_size_max, continuous_reading_size_max, get_decompressor));
+
+ // Get our core size.
+ s64 core_size = 0;
+ R_TRY(m_core.GetSize(std::addressof(core_size)));
+
+ // Initialize our cache manager.
+ R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries));
+
+ R_SUCCEED();
+ }
+
+ void Finalize() {
+ m_core.Finalize();
+ }
+
+ VirtualFile GetDataStorage() {
+ return m_core.GetDataStorage();
+ }
+
+ Result GetDataStorageSize(s64* out) {
+ R_RETURN(m_core.GetDataStorageSize(out));
+ }
+
+ Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset,
+ s64 size) {
+ R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size));
+ }
+
+ BucketTree& GetEntryTable() {
+ return m_core.GetEntryTable();
+ }
+
+public:
+ virtual size_t GetSize() const override {
+ s64 ret{};
+ m_core.GetSize(&ret);
+ return ret;
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) {
+ return size;
+ } else {
+ return 0;
+ }
+ }
+
+private:
+ mutable CompressedStorageCore m_core;
+ mutable CacheManager m_cache_manager;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+enum class CompressionType : u8 {
+ None = 0,
+ Zeros = 1,
+ Two = 2,
+ Lz4 = 3,
+ Unknown = 4,
+};
+
+using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t);
+using GetDecompressorFunction = DecompressorFunction (*)(CompressionType);
+
+constexpr s64 CompressionBlockAlignment = 0x10;
+
+namespace CompressionTypeUtility {
+
+constexpr bool IsBlockAlignmentRequired(CompressionType type) {
+ return type != CompressionType::None && type != CompressionType::Zeros;
+}
+
+constexpr bool IsDataStorageAccessRequired(CompressionType type) {
+ return type != CompressionType::Zeros;
+}
+
+constexpr bool IsRandomAccessible(CompressionType type) {
+ return type == CompressionType::None;
+}
+
+constexpr bool IsUnknownType(CompressionType type) {
+ return type >= CompressionType::Unknown;
+}
+
+} // namespace CompressionTypeUtility
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/lz4_compression.h"
+#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
+
+namespace FileSys {
+
+namespace {
+
+Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) {
+ auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size);
+ R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC);
+ R_SUCCEED();
+}
+
+constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) {
+ switch (type) {
+ case CompressionType::Lz4:
+ return DecompressLz4;
+ default:
+ return nullptr;
+ }
+}
+
+} // namespace
+
+const NcaCompressionConfiguration& GetNcaCompressionConfiguration() {
+ static const NcaCompressionConfiguration configuration = {
+ .get_decompressor = GetNcaDecompressorFunction,
+ };
+
+ return configuration;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+
+namespace FileSys {
+
+const NcaCompressionConfiguration& GetNcaCompressionConfiguration();
+
+}
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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
+#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
+
+namespace FileSys {
+
+namespace {
+
+void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size,
+ s32 key_type) {
+ if (key_type == static_cast<s32>(KeyType::ZeroKey)) {
+ std::memset(dst_key, 0, dst_key_size);
+ return;
+ }
+
+ if (key_type == static_cast<s32>(KeyType::InvalidKey) ||
+ key_type < static_cast<s32>(KeyType::ZeroKey) ||
+ key_type >= static_cast<s32>(KeyType::NcaExternalKey)) {
+ std::memset(dst_key, 0xFF, dst_key_size);
+ return;
+ }
+
+ const auto& instance = Core::Crypto::KeyManager::Instance();
+
+ if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) ||
+ key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) {
+ const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type;
+ const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header);
+ std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2));
+ return;
+ }
+
+ const s32 key_generation =
+ std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1;
+ const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount;
+
+ Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
+ instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index),
+ Core::Crypto::Mode::ECB);
+ cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size,
+ reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt);
+}
+
+} // namespace
+
+const NcaCryptoConfiguration& GetCryptoConfiguration() {
+ static const NcaCryptoConfiguration configuration = {
+ .header_1_sign_key_moduli{},
+ .header_1_sign_key_public_exponent{},
+ .key_area_encryption_key_source{},
+ .header_encryption_key_source{},
+ .header_encrypted_encryption_keys{},
+ .generate_key = GenerateKey,
+ .verify_sign1{},
+ .is_plaintext_header_available{},
+ .is_available_sw_key{},
+ };
+
+ return configuration;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+
+namespace FileSys {
+
+const NcaCryptoConfiguration& GetCryptoConfiguration();
+
+}
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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage()
+ : m_data_size(-1) {
+ for (size_t i = 0; i < MaxLayers - 1; i++) {
+ m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>();
+ }
+}
+
+Result HierarchicalIntegrityVerificationStorage::Initialize(
+ const HierarchicalIntegrityVerificationInformation& info,
+ HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries,
+ s8 buffer_level) {
+ // Validate preconditions.
+ ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount);
+
+ // Set member variables.
+ m_max_layers = info.max_layers;
+
+ // Initialize the top level verification storage.
+ m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage],
+ storage[HierarchicalStorageInformation::Layer1Storage],
+ static_cast<s64>(1) << info.info[0].block_order, HashSize,
+ false);
+
+ // Ensure we don't leak state if further initialization goes wrong.
+ ON_RESULT_FAILURE {
+ m_verify_storages[0]->Finalize();
+ m_data_size = -1;
+ };
+
+ // Initialize the top level buffer storage.
+ m_buffer_storages[0] = m_verify_storages[0];
+ R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Prepare to initialize the level storages.
+ s32 level = 0;
+
+ // Ensure we don't leak state if further initialization goes wrong.
+ ON_RESULT_FAILURE_2 {
+ m_verify_storages[level + 1]->Finalize();
+ for (; level > 0; --level) {
+ m_buffer_storages[level].reset();
+ m_verify_storages[level]->Finalize();
+ }
+ };
+
+ // Initialize the level storages.
+ for (; level < m_max_layers - 3; ++level) {
+ // Initialize the verification storage.
+ auto buffer_storage =
+ std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
+ m_verify_storages[level + 1]->Initialize(
+ std::move(buffer_storage), storage[level + 2],
+ static_cast<s64>(1) << info.info[level + 1].block_order,
+ static_cast<s64>(1) << info.info[level].block_order, false);
+
+ // Initialize the buffer storage.
+ m_buffer_storages[level + 1] = m_verify_storages[level + 1];
+ R_UNLESS(m_buffer_storages[level + 1] != nullptr,
+ ResultAllocationMemoryFailedAllocateShared);
+ }
+
+ // Initialize the final level storage.
+ {
+ // Initialize the verification storage.
+ auto buffer_storage =
+ std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
+ m_verify_storages[level + 1]->Initialize(
+ std::move(buffer_storage), storage[level + 2],
+ static_cast<s64>(1) << info.info[level + 1].block_order,
+ static_cast<s64>(1) << info.info[level].block_order, true);
+
+ // Initialize the buffer storage.
+ m_buffer_storages[level + 1] = m_verify_storages[level + 1];
+ R_UNLESS(m_buffer_storages[level + 1] != nullptr,
+ ResultAllocationMemoryFailedAllocateShared);
+ }
+
+ // Set the data size.
+ m_data_size = info.info[level + 1].size;
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+void HierarchicalIntegrityVerificationStorage::Finalize() {
+ if (m_data_size >= 0) {
+ m_data_size = 0;
+
+ for (s32 level = m_max_layers - 2; level >= 0; --level) {
+ m_buffer_storages[level].reset();
+ m_verify_storages[level]->Finalize();
+ }
+
+ m_data_size = -1;
+ }
+}
+
+size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size,
+ size_t offset) const {
+ // Validate preconditions.
+ ASSERT(m_data_size >= 0);
+
+ // Succeed if zero-size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ // Read the data.
+ return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset);
+}
+
+size_t HierarchicalIntegrityVerificationStorage::GetSize() const {
+ return m_data_size;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/alignment.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fs_types.h"
+#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
+#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+struct HierarchicalIntegrityVerificationLevelInformation {
+ Int64 offset;
+ Int64 size;
+ s32 block_order;
+ std::array<u8, 4> reserved;
+};
+static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>);
+static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18);
+static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4);
+
+struct HierarchicalIntegrityVerificationInformation {
+ u32 max_layers;
+ std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info;
+ HashSalt seed;
+
+ s64 GetLayeredHashSize() const {
+ return this->info[this->max_layers - 2].offset;
+ }
+
+ s64 GetDataOffset() const {
+ return this->info[this->max_layers - 2].offset;
+ }
+
+ s64 GetDataSize() const {
+ return this->info[this->max_layers - 2].size;
+ }
+};
+static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>);
+
+struct HierarchicalIntegrityVerificationMetaInformation {
+ u32 magic;
+ u32 version;
+ u32 master_hash_size;
+ HierarchicalIntegrityVerificationInformation level_hash_info;
+};
+static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>);
+
+struct HierarchicalIntegrityVerificationSizeSet {
+ s64 control_size;
+ s64 master_hash_size;
+ std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes;
+};
+static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>);
+
+class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage);
+ YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage);
+
+public:
+ using GenerateRandomFunction = void (*)(void* dst, size_t size);
+
+ class HierarchicalStorageInformation {
+ public:
+ enum {
+ MasterStorage = 0,
+ Layer1Storage = 1,
+ Layer2Storage = 2,
+ Layer3Storage = 3,
+ Layer4Storage = 4,
+ Layer5Storage = 5,
+ DataStorage = 6,
+ };
+
+ private:
+ std::array<VirtualFile, DataStorage + 1> m_storages;
+
+ public:
+ void SetMasterHashStorage(VirtualFile s) {
+ m_storages[MasterStorage] = s;
+ }
+ void SetLayer1HashStorage(VirtualFile s) {
+ m_storages[Layer1Storage] = s;
+ }
+ void SetLayer2HashStorage(VirtualFile s) {
+ m_storages[Layer2Storage] = s;
+ }
+ void SetLayer3HashStorage(VirtualFile s) {
+ m_storages[Layer3Storage] = s;
+ }
+ void SetLayer4HashStorage(VirtualFile s) {
+ m_storages[Layer4Storage] = s;
+ }
+ void SetLayer5HashStorage(VirtualFile s) {
+ m_storages[Layer5Storage] = s;
+ }
+ void SetDataStorage(VirtualFile s) {
+ m_storages[DataStorage] = s;
+ }
+
+ VirtualFile& operator[](s32 index) {
+ ASSERT(MasterStorage <= index && index <= DataStorage);
+ return m_storages[index];
+ }
+ };
+
+public:
+ HierarchicalIntegrityVerificationStorage();
+ virtual ~HierarchicalIntegrityVerificationStorage() override {
+ this->Finalize();
+ }
+
+ Result Initialize(const HierarchicalIntegrityVerificationInformation& info,
+ HierarchicalStorageInformation storage, int max_data_cache_entries,
+ int max_hash_cache_entries, s8 buffer_level);
+ void Finalize();
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+ virtual size_t GetSize() const override;
+
+ bool IsInitialized() const {
+ return m_data_size >= 0;
+ }
+
+ s64 GetL1HashVerificationBlockSize() const {
+ return m_verify_storages[m_max_layers - 2]->GetBlockSize();
+ }
+
+ VirtualFile GetL1HashStorage() {
+ return std::make_shared<OffsetVfsFile>(
+ m_buffer_storages[m_max_layers - 3],
+ Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0);
+ }
+
+public:
+ static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) {
+ return static_cast<s8>(16 + max_layers - 2);
+ }
+
+protected:
+ static constexpr s64 HashSize = 256 / 8;
+ static constexpr size_t MaxLayers = IntegrityMaxLayerCount;
+
+private:
+ static GenerateRandomFunction s_generate_random;
+
+ static void SetGenerateRandomFunction(GenerateRandomFunction func) {
+ s_generate_random = func;
+ }
+
+private:
+ friend struct HierarchicalIntegrityVerificationMetaInformation;
+
+private:
+ std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages;
+ std::array<VirtualFile, MaxLayers - 1> m_buffer_storages;
+ s64 m_data_size;
+ s32 m_max_layers;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/scope_exit.h"
+#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h"
+
+namespace FileSys {
+
+namespace {
+
+s32 Log2(s32 value) {
+ ASSERT(value > 0);
+ ASSERT(Common::IsPowerOfTwo(value));
+
+ s32 log = 0;
+ while ((value >>= 1) > 0) {
+ ++log;
+ }
+ return log;
+}
+
+} // namespace
+
+Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count,
+ size_t htbs, void* hash_buf, size_t hash_buf_size) {
+ // Validate preconditions.
+ ASSERT(layer_count == LayerCount);
+ ASSERT(Common::IsPowerOfTwo(htbs));
+ ASSERT(hash_buf != nullptr);
+
+ // Set size tracking members.
+ m_hash_target_block_size = static_cast<s32>(htbs);
+ m_log_size_ratio = Log2(m_hash_target_block_size / HashSize);
+
+ // Get the base storage size.
+ m_base_storage_size = base_storages[2]->GetSize();
+ {
+ auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; });
+ R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize)
+ << m_log_size_ratio << m_log_size_ratio,
+ ResultHierarchicalSha256BaseStorageTooLarge);
+ size_guard.Cancel();
+ }
+
+ // Set hash buffer tracking members.
+ m_base_storage = base_storages[2];
+ m_hash_buffer = static_cast<char*>(hash_buf);
+ m_hash_buffer_size = hash_buf_size;
+
+ // Read the master hash.
+ std::array<u8, HashSize> master_hash{};
+ base_storages[0]->ReadObject(std::addressof(master_hash));
+
+ // Read and validate the data being hashed.
+ s64 hash_storage_size = base_storages[1]->GetSize();
+ ASSERT(Common::IsAligned(hash_storage_size, HashSize));
+ ASSERT(hash_storage_size <= m_hash_target_block_size);
+ ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size));
+
+ base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer),
+ static_cast<size_t>(hash_storage_size), 0);
+
+ R_SUCCEED();
+}
+
+size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Succeed if zero-size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate that we have a buffer to read into.
+ ASSERT(buffer != nullptr);
+
+ // Read the data.
+ return m_base_storage->Read(buffer, size, offset);
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+class HierarchicalSha256Storage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(HierarchicalSha256Storage);
+ YUZU_NON_MOVEABLE(HierarchicalSha256Storage);
+
+public:
+ static constexpr s32 LayerCount = 3;
+ static constexpr size_t HashSize = 256 / 8;
+
+public:
+ HierarchicalSha256Storage() : m_mutex() {}
+
+ Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf,
+ size_t hash_buf_size);
+
+ virtual size_t GetSize() const override {
+ return m_base_storage->GetSize();
+ }
+
+ virtual size_t Read(u8* buffer, size_t length, size_t offset) const override;
+
+private:
+ VirtualFile m_base_storage;
+ s64 m_base_storage_size;
+ char* m_hash_buffer;
+ size_t m_hash_buffer_size;
+ s32 m_hash_target_block_size;
+ s32 m_log_size_ratio;
+ std::mutex m_mutex;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
+
+namespace FileSys {
+
+Result IndirectStorage::Initialize(VirtualFile table_storage) {
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ table_storage->ReadObject(std::addressof(header));
+ R_TRY(header.Verify());
+
+ // Determine extents.
+ const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
+ const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
+ const auto node_storage_offset = QueryHeaderStorageSize();
+ const auto entry_storage_offset = node_storage_offset + node_storage_size;
+
+ // Initialize.
+ R_RETURN(this->Initialize(
+ std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
+ std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
+ header.entry_count));
+}
+
+void IndirectStorage::Finalize() {
+ if (this->IsInitialized()) {
+ m_table.Finalize();
+ for (auto i = 0; i < StorageCount; i++) {
+ m_data_storage[i] = VirtualFile();
+ }
+ }
+}
+
+Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count,
+ s64 offset, s64 size) {
+ // Validate pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Clear the out count.
+ R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
+ *out_entry_count = 0;
+
+ // Succeed if there's no range.
+ R_SUCCEED_IF(size == 0);
+
+ // If we have an output array, we need it to be non-null.
+ R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
+
+ // Check that our range is valid.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultInvalidIndirectEntryOffset);
+ }
+
+ // Prepare to loop over entries.
+ const auto end_offset = offset + static_cast<s64>(size);
+ s32 count = 0;
+
+ auto cur_entry = *visitor.Get<Entry>();
+ while (cur_entry.GetVirtualOffset() < end_offset) {
+ // Try to write the entry to the out list.
+ if (entry_count != 0) {
+ if (count >= entry_count) {
+ break;
+ }
+ std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
+ }
+
+ count++;
+
+ // Advance.
+ if (visitor.CanMoveNext()) {
+ R_TRY(visitor.MoveNext());
+ cur_entry = *visitor.Get<Entry>();
+ } else {
+ break;
+ }
+ }
+
+ // Write the output count.
+ *out_entry_count = count;
+ R_SUCCEED();
+}
+
+size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Validate pre-conditions.
+ ASSERT(this->IsInitialized());
+ ASSERT(buffer != nullptr);
+
+ // Succeed if there's nothing to read.
+ if (size == 0) {
+ return 0;
+ }
+
+ const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>(
+ offset, size,
+ [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
+ storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
+ static_cast<size_t>(cur_size), data_offset);
+ R_SUCCEED();
+ });
+
+ return size;
+}
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h"
+#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+class IndirectStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(IndirectStorage);
+ YUZU_NON_MOVEABLE(IndirectStorage);
+
+public:
+ static constexpr s32 StorageCount = 2;
+ static constexpr size_t NodeSize = 16_KiB;
+
+ struct Entry {
+ std::array<u8, sizeof(s64)> virt_offset;
+ std::array<u8, sizeof(s64)> phys_offset;
+ s32 storage_index;
+
+ void SetVirtualOffset(const s64& ofs) {
+ std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64));
+ }
+
+ s64 GetVirtualOffset() const {
+ s64 offset;
+ std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64));
+ return offset;
+ }
+
+ void SetPhysicalOffset(const s64& ofs) {
+ std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64));
+ }
+
+ s64 GetPhysicalOffset() const {
+ s64 offset;
+ std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64));
+ return offset;
+ }
+ };
+ static_assert(std::is_trivial_v<Entry>);
+ static_assert(sizeof(Entry) == 0x14);
+
+ struct EntryData {
+ s64 virt_offset;
+ s64 phys_offset;
+ s32 storage_index;
+
+ void Set(const Entry& entry) {
+ this->virt_offset = entry.GetVirtualOffset();
+ this->phys_offset = entry.GetPhysicalOffset();
+ this->storage_index = entry.storage_index;
+ }
+ };
+ static_assert(std::is_trivial_v<EntryData>);
+
+public:
+ IndirectStorage() : m_table(), m_data_storage() {}
+ virtual ~IndirectStorage() {
+ this->Finalize();
+ }
+
+ Result Initialize(VirtualFile table_storage);
+ void Finalize();
+
+ bool IsInitialized() const {
+ return m_table.IsInitialized();
+ }
+
+ Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) {
+ R_RETURN(
+ m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
+ }
+
+ void SetStorage(s32 idx, VirtualFile storage) {
+ ASSERT(0 <= idx && idx < StorageCount);
+ m_data_storage[idx] = storage;
+ }
+
+ template <typename T>
+ void SetStorage(s32 idx, T storage, s64 offset, s64 size) {
+ ASSERT(0 <= idx && idx < StorageCount);
+ m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset);
+ }
+
+ Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
+ s64 size);
+
+ virtual size_t GetSize() const override {
+ BucketTree::Offsets offsets{};
+ m_table.GetOffsets(std::addressof(offsets));
+
+ return offsets.end_offset;
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+
+public:
+ static constexpr s64 QueryHeaderStorageSize() {
+ return BucketTree::QueryHeaderStorageSize();
+ }
+
+ static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
+ return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+ static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
+ return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+protected:
+ BucketTree& GetEntryTable() {
+ return m_table;
+ }
+
+ VirtualFile& GetDataStorage(s32 index) {
+ ASSERT(0 <= index && index < StorageCount);
+ return m_data_storage[index];
+ }
+
+ template <bool ContinuousCheck, bool RangeCheck, typename F>
+ Result OperatePerEntry(s64 offset, s64 size, F func);
+
+private:
+ struct ContinuousReadingEntry {
+ static constexpr size_t FragmentSizeMax = 4_KiB;
+
+ IndirectStorage::Entry entry;
+
+ s64 GetVirtualOffset() const {
+ return this->entry.GetVirtualOffset();
+ }
+
+ s64 GetPhysicalOffset() const {
+ return this->entry.GetPhysicalOffset();
+ }
+
+ bool IsFragment() const {
+ return this->entry.storage_index != 0;
+ }
+ };
+ static_assert(std::is_trivial_v<ContinuousReadingEntry>);
+
+private:
+ mutable BucketTree m_table;
+ std::array<VirtualFile, StorageCount> m_data_storage;
+};
+
+template <bool ContinuousCheck, bool RangeCheck, typename F>
+Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
+ // Validate preconditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Succeed if there's nothing to operate on.
+ R_SUCCEED_IF(size == 0);
+
+ // Get the table offsets.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ // Validate arguments.
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultInvalidIndirectEntryOffset);
+ }
+
+ // Prepare to operate in chunks.
+ auto cur_offset = offset;
+ const auto end_offset = offset + static_cast<s64>(size);
+ BucketTree::ContinuousReadingInfo cr_info;
+
+ while (cur_offset < end_offset) {
+ // Get the current entry.
+ const auto cur_entry = *visitor.Get<Entry>();
+
+ // Get and validate the entry's offset.
+ const auto cur_entry_offset = cur_entry.GetVirtualOffset();
+ R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
+
+ // Validate the storage index.
+ R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount,
+ ResultInvalidIndirectEntryStorageIndex);
+
+ // If we need to check the continuous info, do so.
+ if constexpr (ContinuousCheck) {
+ // Scan, if we need to.
+ if (cr_info.CheckNeedScan()) {
+ R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>(
+ std::addressof(cr_info), cur_offset,
+ static_cast<size_t>(end_offset - cur_offset)));
+ }
+
+ // Process a base storage entry.
+ if (cr_info.CanDo()) {
+ // Ensure that we can process.
+ R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex);
+
+ // Ensure that we remain within range.
+ const auto data_offset = cur_offset - cur_entry_offset;
+ const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
+ const auto cur_size = static_cast<s64>(cr_info.GetReadSize());
+
+ // If we should, verify the range.
+ if constexpr (RangeCheck) {
+ // Get the current data storage's size.
+ s64 cur_data_storage_size = m_data_storage[0]->GetSize();
+
+ R_UNLESS(0 <= cur_entry_phys_offset &&
+ cur_entry_phys_offset <= cur_data_storage_size,
+ ResultInvalidIndirectEntryOffset);
+ R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <=
+ cur_data_storage_size,
+ ResultInvalidIndirectStorageSize);
+ }
+
+ // Operate.
+ R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset,
+ cur_size));
+
+ // Mark as done.
+ cr_info.Done();
+ }
+ }
+
+ // Get and validate the next entry offset.
+ s64 next_entry_offset;
+ if (visitor.CanMoveNext()) {
+ R_TRY(visitor.MoveNext());
+ next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
+ R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
+ } else {
+ next_entry_offset = table_offsets.end_offset;
+ }
+ R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
+
+ // Get the offset of the entry in the data we read.
+ const auto data_offset = cur_offset - cur_entry_offset;
+ const auto data_size = (next_entry_offset - cur_entry_offset);
+ ASSERT(data_size > 0);
+
+ // Determine how much is left.
+ const auto remaining_size = end_offset - cur_offset;
+ const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
+ ASSERT(cur_size <= size);
+
+ // Operate, if we need to.
+ bool needs_operate;
+ if constexpr (!ContinuousCheck) {
+ needs_operate = true;
+ } else {
+ needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0;
+ }
+
+ if (needs_operate) {
+ const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
+
+ if constexpr (RangeCheck) {
+ // Get the current data storage's size.
+ s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
+
+ // Ensure that we remain within range.
+ R_UNLESS(0 <= cur_entry_phys_offset &&
+ cur_entry_phys_offset <= cur_data_storage_size,
+ ResultIndirectStorageCorrupted);
+ R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size,
+ ResultIndirectStorageCorrupted);
+ }
+
+ R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset,
+ cur_offset, cur_size));
+ }
+
+ cur_offset += cur_size;
+ }
+
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h"
+
+namespace FileSys {
+
+Result IntegrityRomFsStorage::Initialize(
+ HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
+ HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
+ int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) {
+ // Set master hash.
+ m_master_hash = master_hash;
+ m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value);
+ R_UNLESS(m_master_hash_storage != nullptr,
+ ResultAllocationMemoryFailedInIntegrityRomFsStorageA);
+
+ // Set the master hash storage.
+ storage_info[0] = m_master_hash_storage;
+
+ // Initialize our integrity storage.
+ R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries,
+ max_hash_cache_entries, buffer_level));
+}
+
+void IntegrityRomFsStorage::Finalize() {
+ m_integrity_storage.Finalize();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
+#include "core/file_sys/fssystem/fssystem_nca_header.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys {
+
+constexpr inline size_t IntegrityLayerCountRomFs = 7;
+constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB;
+
+class IntegrityRomFsStorage : public IReadOnlyStorage {
+public:
+ IntegrityRomFsStorage() {}
+ virtual ~IntegrityRomFsStorage() override {
+ this->Finalize();
+ }
+
+ Result Initialize(
+ HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
+ HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
+ int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
+ void Finalize();
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ return m_integrity_storage.Read(buffer, size, offset);
+ }
+
+ virtual size_t GetSize() const override {
+ return m_integrity_storage.GetSize();
+ }
+
+private:
+ HierarchicalIntegrityVerificationStorage m_integrity_storage;
+ Hash m_master_hash;
+ std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
+
+namespace FileSys {
+
+constexpr inline u32 ILog2(u32 val) {
+ ASSERT(val > 0);
+ return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val));
+}
+
+void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
+ s64 upper_layer_verif_block_size, bool is_real_data) {
+ // Validate preconditions.
+ ASSERT(verif_block_size >= HashSize);
+
+ // Set storages.
+ m_hash_storage = hs;
+ m_data_storage = ds;
+
+ // Set verification block sizes.
+ m_verification_block_size = verif_block_size;
+ m_verification_block_order = ILog2(static_cast<u32>(verif_block_size));
+ ASSERT(m_verification_block_size == 1ll << m_verification_block_order);
+
+ // Set upper layer block sizes.
+ upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize);
+ m_upper_layer_verification_block_size = upper_layer_verif_block_size;
+ m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size));
+ ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order);
+
+ // Validate sizes.
+ {
+ s64 hash_size = m_hash_storage->GetSize();
+ s64 data_size = m_data_storage->GetSize();
+ ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size);
+ }
+
+ // Set data.
+ m_is_real_data = is_real_data;
+}
+
+void IntegrityVerificationStorage::Finalize() {
+ m_hash_storage = VirtualFile();
+ m_data_storage = VirtualFile();
+}
+
+size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ // Validate the offset.
+ s64 data_size = m_data_storage->GetSize();
+ ASSERT(offset <= static_cast<size_t>(data_size));
+
+ // Validate the access range.
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(
+ offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size)))));
+
+ // Determine the read extents.
+ size_t read_size = size;
+ if (static_cast<s64>(offset + read_size) > data_size) {
+ // Determine the padding sizes.
+ s64 padding_offset = data_size - offset;
+ size_t padding_size = static_cast<size_t>(
+ m_verification_block_size - (padding_offset & (m_verification_block_size - 1)));
+ ASSERT(static_cast<s64>(padding_size) < m_verification_block_size);
+
+ // Clear the padding.
+ std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size);
+
+ // Set the new in-bounds size.
+ read_size = static_cast<size_t>(data_size - offset);
+ }
+
+ // Perform the read.
+ return m_data_storage->Read(buffer, read_size, offset);
+}
+
+size_t IntegrityVerificationStorage::GetSize() const {
+ return m_data_storage->GetSize();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fs_types.h"
+
+namespace FileSys {
+
+class IntegrityVerificationStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(IntegrityVerificationStorage);
+ YUZU_NON_MOVEABLE(IntegrityVerificationStorage);
+
+public:
+ static constexpr s64 HashSize = 256 / 8;
+
+ struct BlockHash {
+ std::array<u8, HashSize> hash;
+ };
+ static_assert(std::is_trivial_v<BlockHash>);
+
+public:
+ IntegrityVerificationStorage()
+ : m_verification_block_size(0), m_verification_block_order(0),
+ m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {}
+ virtual ~IntegrityVerificationStorage() override {
+ this->Finalize();
+ }
+
+ void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
+ s64 upper_layer_verif_block_size, bool is_real_data);
+ void Finalize();
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+ virtual size_t GetSize() const override;
+
+ s64 GetBlockSize() const {
+ return m_verification_block_size;
+ }
+
+private:
+ static void SetValidationBit(BlockHash* hash) {
+ ASSERT(hash != nullptr);
+ hash->hash[HashSize - 1] |= 0x80;
+ }
+
+ static bool IsValidationBit(const BlockHash* hash) {
+ ASSERT(hash != nullptr);
+ return (hash->hash[HashSize - 1] & 0x80) != 0;
+ }
+
+private:
+ VirtualFile m_hash_storage;
+ VirtualFile m_data_storage;
+ s64 m_verification_block_size;
+ s64 m_verification_block_order;
+ s64 m_upper_layer_verification_block_size;
+ s64 m_upper_layer_verification_block_order;
+ bool m_is_real_data;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fs_i_storage.h"
+
+namespace FileSys {
+
+class MemoryResourceBufferHoldStorage : public IStorage {
+ YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage);
+ YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage);
+
+public:
+ MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size)
+ : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)),
+ m_buffer_size(buffer_size) {}
+
+ virtual ~MemoryResourceBufferHoldStorage() {
+ // If we have a buffer, deallocate it.
+ if (m_buffer != nullptr) {
+ ::operator delete(m_buffer);
+ }
+ }
+
+ bool IsValid() const {
+ return m_buffer != nullptr;
+ }
+ void* GetBuffer() const {
+ return m_buffer;
+ }
+
+public:
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Check pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ return m_storage->Read(buffer, size, offset);
+ }
+
+ virtual size_t GetSize() const override {
+ // Check pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ return m_storage->GetSize();
+ }
+
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
+ // Check pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ return m_storage->Write(buffer, size, offset);
+ }
+
+private:
+ VirtualFile m_storage;
+ void* m_buffer;
+ size_t m_buffer_size;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
+#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
+#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
+#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
+#include "core/file_sys/fssystem/fssystem_compressed_storage.h"
+#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
+#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h"
+#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
+#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h"
+#include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h"
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+#include "core/file_sys/fssystem/fssystem_sparse_storage.h"
+#include "core/file_sys/fssystem/fssystem_switch_storage.h"
+#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys {
+
+namespace {
+
+constexpr inline s32 IntegrityDataCacheCount = 24;
+constexpr inline s32 IntegrityHashCacheCount = 8;
+
+constexpr inline s32 IntegrityDataCacheCountForMeta = 16;
+constexpr inline s32 IntegrityHashCacheCountForMeta = 2;
+
+class SharedNcaBodyStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(SharedNcaBodyStorage);
+ YUZU_NON_MOVEABLE(SharedNcaBodyStorage);
+
+private:
+ VirtualFile m_storage;
+ std::shared_ptr<NcaReader> m_nca_reader;
+
+public:
+ SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r)
+ : m_storage(std::move(s)), m_nca_reader(std::move(r)) {}
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Validate pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ // Read from the base storage.
+ return m_storage->Read(buffer, size, offset);
+ }
+
+ virtual size_t GetSize() const override {
+ // Validate pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ return m_storage->GetSize();
+ }
+};
+
+inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) {
+ return static_cast<s64>(reader.GetFsOffset(fs_index));
+}
+
+inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) {
+ return static_cast<s64>(reader.GetFsEndOffset(fs_index));
+}
+
+using Sha256DataRegion = NcaFsHeader::Region;
+using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo;
+using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation;
+
+} // namespace
+
+Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out,
+ NcaFsHeaderReader* out_header_reader,
+ s32 fs_index, StorageContext* ctx) {
+ // Open storage.
+ R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx));
+}
+
+Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader,
+ s32 fs_index, StorageContext* ctx) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(out_header_reader != nullptr);
+ ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax);
+
+ // Validate the fs index.
+ R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound);
+
+ // Initialize our header reader for the fs index.
+ R_TRY(out_header_reader->Initialize(*m_reader, fs_index));
+
+ // Declare the storage we're opening.
+ VirtualFile storage;
+
+ // Process sparse layer.
+ s64 fs_data_offset = 0;
+ if (out_header_reader->ExistsSparseLayer()) {
+ // Get the sparse info.
+ const auto& sparse_info = out_header_reader->GetSparseInfo();
+
+ // Create based on whether we have a meta hash layer.
+ if (out_header_reader->ExistsSparseMetaHashLayer()) {
+ // Create the sparse storage with verification.
+ R_TRY(this->CreateSparseStorageWithVerification(
+ std::addressof(storage), std::addressof(fs_data_offset),
+ ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index,
+ out_header_reader->GetAesCtrUpperIv(), sparse_info,
+ out_header_reader->GetSparseMetaDataHashDataInfo(),
+ out_header_reader->GetSparseMetaHashType()));
+ } else {
+ // Create the sparse storage.
+ R_TRY(this->CreateSparseStorage(
+ std::addressof(storage), std::addressof(fs_data_offset),
+ ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
+ fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info));
+ }
+ } else {
+ // Get the data offsets.
+ fs_data_offset = GetFsOffset(*m_reader, fs_index);
+ const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index);
+
+ // Validate that we're within range.
+ const auto data_size = fs_end_offset - fs_data_offset;
+ R_UNLESS(data_size > 0, ResultInvalidNcaHeader);
+
+ // Create the body substorage.
+ R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size));
+
+ // Potentially save the body substorage to our context.
+ if (ctx != nullptr) {
+ ctx->body_substorage = storage;
+ }
+ }
+
+ // Process patch layer.
+ const auto& patch_info = out_header_reader->GetPatchInfo();
+ VirtualFile patch_meta_aes_ctr_ex_meta_storage;
+ VirtualFile patch_meta_indirect_meta_storage;
+ if (out_header_reader->ExistsPatchMetaHashLayer()) {
+ // Check the meta hash type.
+ R_UNLESS(out_header_reader->GetPatchMetaHashType() ==
+ NcaFsHeader::MetaDataHashType::HierarchicalIntegrity,
+ ResultRomNcaInvalidPatchMetaDataHashType);
+
+ // Create the patch meta storage.
+ R_TRY(this->CreatePatchMetaStorage(
+ std::addressof(patch_meta_aes_ctr_ex_meta_storage),
+ std::addressof(patch_meta_indirect_meta_storage),
+ ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage,
+ fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info,
+ out_header_reader->GetPatchMetaDataHashDataInfo()));
+ }
+
+ if (patch_info.HasAesCtrExTable()) {
+ // Check the encryption type.
+ ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None ||
+ out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx ||
+ out_header_reader->GetEncryptionType() ==
+ NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
+
+ // Create the ex meta storage.
+ VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage;
+ if (aes_ctr_ex_storage_meta_storage == nullptr) {
+ // If we don't have a meta storage, we must not have a patch meta hash layer.
+ ASSERT(!out_header_reader->ExistsPatchMetaHashLayer());
+
+ R_TRY(this->CreateAesCtrExStorageMetaStorage(
+ std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset,
+ out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(),
+ patch_info));
+ }
+
+ // Create the ex storage.
+ VirtualFile aes_ctr_ex_storage;
+ R_TRY(this->CreateAesCtrExStorage(
+ std::addressof(aes_ctr_ex_storage),
+ ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage),
+ aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
+ patch_info));
+
+ // Set the base storage as the ex storage.
+ storage = std::move(aes_ctr_ex_storage);
+
+ // Potentially save storages to our context.
+ if (ctx != nullptr) {
+ ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage;
+ ctx->aes_ctr_ex_storage_data_storage = storage;
+ ctx->fs_data_storage = storage;
+ }
+ } else {
+ // Create the appropriate storage for the encryption type.
+ switch (out_header_reader->GetEncryptionType()) {
+ case NcaFsHeader::EncryptionType::None:
+ // If there's no encryption, use the base storage we made previously.
+ break;
+ case NcaFsHeader::EncryptionType::AesXts:
+ R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
+ fs_data_offset));
+ break;
+ case NcaFsHeader::EncryptionType::AesCtr:
+ R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
+ fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
+ AlignmentStorageRequirement::None));
+ break;
+ case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: {
+ // Create the aes ctr storage.
+ VirtualFile aes_ctr_storage;
+ R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage,
+ fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
+ AlignmentStorageRequirement::None));
+
+ // Create region switch storage.
+ R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
+ std::move(storage), std::move(aes_ctr_storage)));
+ } break;
+ default:
+ R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
+ }
+
+ // Potentially save storages to our context.
+ if (ctx != nullptr) {
+ ctx->fs_data_storage = storage;
+ }
+ }
+
+ // Process indirect layer.
+ if (patch_info.HasIndirectTable()) {
+ // Create the indirect meta storage.
+ VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage;
+ if (indirect_storage_meta_storage == nullptr) {
+ // If we don't have a meta storage, we must not have a patch meta hash layer.
+ ASSERT(!out_header_reader->ExistsPatchMetaHashLayer());
+
+ R_TRY(this->CreateIndirectStorageMetaStorage(
+ std::addressof(indirect_storage_meta_storage), storage, patch_info));
+ }
+
+ // Potentially save the indirect meta storage to our context.
+ if (ctx != nullptr) {
+ ctx->indirect_storage_meta_storage = indirect_storage_meta_storage;
+ }
+
+ // Get the original indirectable storage.
+ VirtualFile original_indirectable_storage;
+ if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) {
+ // Create a driver for the original.
+ NcaFileSystemDriver original_driver(m_original_reader);
+
+ // Create a header reader for the original.
+ NcaFsHeaderReader original_header_reader;
+ R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index));
+
+ // Open original indirectable storage.
+ R_TRY(original_driver.OpenIndirectableStorageAsOriginal(
+ std::addressof(original_indirectable_storage),
+ std::addressof(original_header_reader), ctx));
+ } else if (ctx != nullptr && ctx->external_original_storage != nullptr) {
+ // Use the external original storage.
+ original_indirectable_storage = ctx->external_original_storage;
+ } else {
+ // Allocate a dummy memory storage as original storage.
+ original_indirectable_storage = std::make_shared<VectorVfsFile>();
+ R_UNLESS(original_indirectable_storage != nullptr,
+ ResultAllocationMemoryFailedAllocateShared);
+ }
+
+ // Create the indirect storage.
+ VirtualFile indirect_storage;
+ R_TRY(this->CreateIndirectStorage(
+ std::addressof(indirect_storage),
+ ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage),
+ std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage),
+ patch_info));
+
+ // Set storage as the indirect storage.
+ storage = std::move(indirect_storage);
+ }
+
+ // Check if we're sparse or requested to skip the integrity layer.
+ if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) {
+ *out = std::move(storage);
+ R_SUCCEED();
+ }
+
+ // Create the non-raw storage.
+ R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx));
+}
+
+Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out,
+ const NcaFsHeaderReader* header_reader,
+ VirtualFile raw_storage,
+ StorageContext* ctx) {
+ // Initialize storage as raw storage.
+ VirtualFile storage = std::move(raw_storage);
+
+ // Process hash/integrity layer.
+ switch (header_reader->GetHashType()) {
+ case NcaFsHeader::HashType::HierarchicalSha256Hash:
+ R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage),
+ header_reader->GetHashData().hierarchical_sha256_data));
+ break;
+ case NcaFsHeader::HashType::HierarchicalIntegrityHash:
+ R_TRY(this->CreateIntegrityVerificationStorage(
+ std::addressof(storage), std::move(storage),
+ header_reader->GetHashData().integrity_meta_info));
+ break;
+ default:
+ R_THROW(ResultInvalidNcaFsHeaderHashType);
+ }
+
+ // Process compression layer.
+ if (header_reader->ExistsCompressionLayer()) {
+ R_TRY(this->CreateCompressedStorage(
+ std::addressof(storage),
+ ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr,
+ std::move(storage), header_reader->GetCompressionInfo()));
+ }
+
+ // Set output storage.
+ *out = std::move(storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(
+ VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) {
+ // Get the fs index.
+ const auto fs_index = header_reader->GetFsIndex();
+
+ // Declare the storage we're opening.
+ VirtualFile storage;
+
+ // Process sparse layer.
+ s64 fs_data_offset = 0;
+ if (header_reader->ExistsSparseLayer()) {
+ // Get the sparse info.
+ const auto& sparse_info = header_reader->GetSparseInfo();
+
+ // Create based on whether we have a meta hash layer.
+ if (header_reader->ExistsSparseMetaHashLayer()) {
+ // Create the sparse storage with verification.
+ R_TRY(this->CreateSparseStorageWithVerification(
+ std::addressof(storage), std::addressof(fs_data_offset),
+ ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index,
+ header_reader->GetAesCtrUpperIv(), sparse_info,
+ header_reader->GetSparseMetaDataHashDataInfo(),
+ header_reader->GetSparseMetaHashType()));
+ } else {
+ // Create the sparse storage.
+ R_TRY(this->CreateSparseStorage(
+ std::addressof(storage), std::addressof(fs_data_offset),
+ ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
+ fs_index, header_reader->GetAesCtrUpperIv(), sparse_info));
+ }
+ } else {
+ // Get the data offsets.
+ fs_data_offset = GetFsOffset(*m_reader, fs_index);
+ const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index);
+
+ // Validate that we're within range.
+ const auto data_size = fs_end_offset - fs_data_offset;
+ R_UNLESS(data_size > 0, ResultInvalidNcaHeader);
+
+ // Create the body substorage.
+ R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size));
+ }
+
+ // Create the appropriate storage for the encryption type.
+ switch (header_reader->GetEncryptionType()) {
+ case NcaFsHeader::EncryptionType::None:
+ // If there's no encryption, use the base storage we made previously.
+ break;
+ case NcaFsHeader::EncryptionType::AesXts:
+ R_TRY(
+ this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset));
+ break;
+ case NcaFsHeader::EncryptionType::AesCtr:
+ R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset,
+ header_reader->GetAesCtrUpperIv(),
+ AlignmentStorageRequirement::CacheBlockSize));
+ break;
+ default:
+ R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
+ }
+
+ // Set output storage.
+ *out = std::move(storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) {
+ // Create the body storage.
+ auto body_storage =
+ std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader);
+ R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Get the body storage size.
+ s64 body_size = body_storage->GetSize();
+
+ // Check that we're within range.
+ R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB);
+
+ // Create substorage.
+ auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset);
+ R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output storage.
+ *out = std::move(body_substorage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateAesCtrStorage(
+ VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv,
+ AlignmentStorageRequirement alignment_storage_requirement) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Create the iv.
+ std::array<u8, AesCtrStorage::IvSize> iv{};
+ AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset);
+
+ // Create the ctr storage.
+ VirtualFile aes_ctr_storage;
+ if (m_reader->HasExternalDecryptionKey()) {
+ aes_ctr_storage = std::make_shared<AesCtrStorage>(
+ std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize,
+ iv.data(), AesCtrStorage::IvSize);
+ R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+ } else {
+ // Create software decryption storage.
+ auto sw_storage = std::make_shared<AesCtrStorage>(
+ base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr),
+ AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize);
+ R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ aes_ctr_storage = std::move(sw_storage);
+ }
+
+ // Create alignment matching storage.
+ auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>(
+ std::move(aes_ctr_storage));
+ R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the out storage.
+ *out = std::move(aligned_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage,
+ s64 offset) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Create the iv.
+ std::array<u8, AesXtsStorage::IvSize> iv{};
+ AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize);
+
+ // Make the aes xts storage.
+ const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1);
+ const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2);
+ auto xts_storage =
+ std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize,
+ iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize);
+ R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create alignment matching storage.
+ auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>(
+ std::move(xts_storage));
+ R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the out storage.
+ *out = std::move(xts_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out,
+ VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv,
+ const NcaSparseInfo& sparse_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Get the meta extents.
+ const auto meta_offset = sparse_info.bucket.offset;
+ const auto meta_size = sparse_info.bucket.size;
+ R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB);
+
+ // Create the encrypted storage.
+ auto enc_storage =
+ std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset);
+ R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the decrypted storage.
+ VirtualFile decrypted_storage;
+ R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
+ offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv),
+ AlignmentStorageRequirement::None));
+
+ // Create buffered storage.
+ std::vector<u8> meta_data(meta_size);
+ decrypted_storage->Read(meta_data.data(), meta_size, 0);
+
+ auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
+ R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(buffered_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out,
+ VirtualFile base_storage, s64 base_size,
+ VirtualFile meta_storage,
+ const NcaSparseInfo& sparse_info,
+ bool external_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(meta_storage != nullptr);
+
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine storage extents.
+ const auto node_offset = 0;
+ const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count);
+ const auto entry_offset = node_offset + node_size;
+ const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count);
+
+ // Create the sparse storage.
+ auto sparse_storage = std::make_shared<SparseStorage>();
+ R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Sanity check that we can be doing this.
+ ASSERT(header.entry_count != 0);
+
+ // Initialize the sparse storage.
+ R_TRY(sparse_storage->Initialize(
+ std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset),
+ std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset),
+ header.entry_count));
+
+ // If not external, set the data storage.
+ if (!external_info) {
+ sparse_storage->SetDataStorage(
+ std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0));
+ }
+
+ // Set the output.
+ *out = std::move(sparse_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset,
+ std::shared_ptr<SparseStorage>* out_sparse_storage,
+ VirtualFile* out_meta_storage, s32 index,
+ const NcaAesCtrUpperIv& upper_iv,
+ const NcaSparseInfo& sparse_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(out_fs_data_offset != nullptr);
+
+ // Check the sparse info generation.
+ R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader);
+
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the storage extents.
+ const auto fs_offset = GetFsOffset(*m_reader, index);
+ const auto fs_end_offset = GetFsEndOffset(*m_reader, index);
+ const auto fs_size = fs_end_offset - fs_offset;
+
+ // Create the sparse storage.
+ std::shared_ptr<SparseStorage> sparse_storage;
+ if (header.entry_count != 0) {
+ // Create the body substorage.
+ VirtualFile body_substorage;
+ R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage),
+ sparse_info.physical_offset,
+ sparse_info.GetPhysicalSize()));
+
+ // Create the meta storage.
+ VirtualFile meta_storage;
+ R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage,
+ sparse_info.physical_offset, upper_iv,
+ sparse_info));
+
+ // Potentially set the output meta storage.
+ if (out_meta_storage != nullptr) {
+ *out_meta_storage = meta_storage;
+ }
+
+ // Create the sparse storage.
+ R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage,
+ sparse_info.GetPhysicalSize(), std::move(meta_storage),
+ sparse_info, false));
+ } else {
+ // If there are no entries, there's nothing to actually do.
+ sparse_storage = std::make_shared<SparseStorage>();
+ R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ sparse_storage->Initialize(fs_size);
+ }
+
+ // Potentially set the output sparse storage.
+ if (out_sparse_storage != nullptr) {
+ *out_sparse_storage = sparse_storage;
+ }
+
+ // Set the output fs data offset.
+ *out_fs_data_offset = fs_offset;
+
+ // Set the output storage.
+ *out = std::move(sparse_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification(
+ VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Get the meta extents.
+ const auto meta_offset = sparse_info.bucket.offset;
+ const auto meta_size = sparse_info.bucket.size;
+ R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB);
+
+ // Get the meta data hash data extents.
+ const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset;
+ const s64 meta_data_hash_data_size =
+ Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize);
+ R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeB);
+
+ // Check that the meta is before the hash data.
+ R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset,
+ ResultRomNcaInvalidSparseMetaDataHashDataOffset);
+
+ // Check that offsets are appropriately aligned.
+ R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize),
+ ResultRomNcaInvalidSparseMetaDataHashDataOffset);
+ R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize),
+ ResultInvalidNcaFsHeader);
+
+ // Create the meta storage.
+ auto enc_storage = std::make_shared<OffsetVfsFile>(
+ std::move(base_storage),
+ meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset);
+ R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the decrypted storage.
+ VirtualFile decrypted_storage;
+ R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
+ offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv),
+ AlignmentStorageRequirement::None));
+
+ // Create the verification storage.
+ VirtualFile integrity_storage;
+ Result rc = this->CreateIntegrityVerificationStorageForMeta(
+ std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage),
+ meta_offset, meta_data_hash_data_info);
+ if (rc == ResultInvalidNcaMetaDataHashDataSize) {
+ R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize);
+ }
+ if (rc == ResultInvalidNcaMetaDataHashDataHash) {
+ R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash);
+ }
+ R_TRY(rc);
+
+ // Create the meta storage.
+ auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(meta_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorageWithVerification(
+ VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage,
+ VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index,
+ const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info,
+ NcaFsHeader::MetaDataHashType meta_data_hash_type) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(out_fs_data_offset != nullptr);
+
+ // Check the sparse info generation.
+ R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader);
+
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the storage extents.
+ const auto fs_offset = GetFsOffset(*m_reader, index);
+ const auto fs_end_offset = GetFsEndOffset(*m_reader, index);
+ const auto fs_size = fs_end_offset - fs_offset;
+
+ // Create the sparse storage.
+ std::shared_ptr<SparseStorage> sparse_storage;
+ if (header.entry_count != 0) {
+ // Create the body substorage.
+ VirtualFile body_substorage;
+ R_TRY(this->CreateBodySubStorage(
+ std::addressof(body_substorage), sparse_info.physical_offset,
+ Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) +
+ static_cast<s64>(meta_data_hash_data_info.size),
+ NcaHeader::CtrBlockSize)));
+
+ // Check the meta data hash type.
+ R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity,
+ ResultRomNcaInvalidSparseMetaDataHashType);
+
+ // Create the meta storage.
+ VirtualFile meta_storage;
+ R_TRY(this->CreateSparseStorageMetaStorageWithVerification(
+ std::addressof(meta_storage), out_layer_info_storage, body_substorage,
+ sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info));
+
+ // Potentially set the output meta storage.
+ if (out_meta_storage != nullptr) {
+ *out_meta_storage = meta_storage;
+ }
+
+ // Create the sparse storage.
+ R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage,
+ sparse_info.GetPhysicalSize(), std::move(meta_storage),
+ sparse_info, false));
+ } else {
+ // If there are no entries, there's nothing to actually do.
+ sparse_storage = std::make_shared<SparseStorage>();
+ R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ sparse_storage->Initialize(fs_size);
+ }
+
+ // Potentially set the output sparse storage.
+ if (out_sparse_storage != nullptr) {
+ *out_sparse_storage = sparse_storage;
+ }
+
+ // Set the output fs data offset.
+ *out_fs_data_offset = fs_offset;
+
+ // Set the output storage.
+ *out = std::move(sparse_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(
+ VirtualFile* out, VirtualFile base_storage, s64 offset,
+ NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv,
+ const NcaPatchInfo& patch_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(encryption_type == NcaFsHeader::EncryptionType::None ||
+ encryption_type == NcaFsHeader::EncryptionType::AesCtrEx ||
+ encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
+ ASSERT(patch_info.HasAesCtrExTable());
+
+ // Validate patch info extents.
+ R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize);
+ R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize);
+ R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset,
+ ResultInvalidNcaPatchInfoAesCtrExOffset);
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Get and validate the meta extents.
+ const s64 meta_offset = patch_info.aes_ctr_ex_offset;
+ const s64 meta_size =
+ Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize);
+ R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB);
+
+ // Create the encrypted storage.
+ auto enc_storage =
+ std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset);
+ R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the decrypted storage.
+ VirtualFile decrypted_storage;
+ if (encryption_type != NcaFsHeader::EncryptionType::None) {
+ R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
+ offset + meta_offset, upper_iv,
+ AlignmentStorageRequirement::None));
+ } else {
+ // If encryption type is none, don't do any decryption.
+ decrypted_storage = std::move(enc_storage);
+ }
+
+ // Create meta storage.
+ auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create buffered storage.
+ std::vector<u8> meta_data(meta_size);
+ meta_storage->Read(meta_data.data(), meta_size, 0);
+
+ auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
+ R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(buffered_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateAesCtrExStorage(
+ VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext,
+ VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset,
+ const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) {
+ // Validate pre-conditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(meta_storage != nullptr);
+ ASSERT(patch_info.HasAesCtrExTable());
+
+ // Read the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the bucket extents.
+ const auto entry_count = header.entry_count;
+ const s64 data_offset = 0;
+ const s64 data_size = patch_info.aes_ctr_ex_offset;
+ const s64 node_offset = 0;
+ const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count);
+ const s64 entry_offset = node_offset + node_size;
+ const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count);
+
+ // Create bucket storages.
+ auto data_storage =
+ std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset);
+ auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset);
+ auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset);
+
+ // Get the secure value.
+ const auto secure_value = upper_iv.part.secure_value;
+
+ // Create the aes ctr ex storage.
+ VirtualFile aes_ctr_ex_storage;
+ if (m_reader->HasExternalDecryptionKey()) {
+ // Create the decryptor.
+ std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor;
+ R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor)));
+
+ // Create the aes ctr ex storage.
+ auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>();
+ R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the aes ctr ex storage.
+ R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize,
+ secure_value, counter_offset, data_storage, node_storage,
+ entry_storage, entry_count, std::move(decryptor)));
+
+ // Potentially set the output implementation storage.
+ if (out_ext != nullptr) {
+ *out_ext = impl_storage;
+ }
+
+ // Set the implementation storage.
+ aes_ctr_ex_storage = std::move(impl_storage);
+ } else {
+ // Create the software decryptor.
+ std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor;
+ R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
+
+ // Make the software storage.
+ auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>();
+ R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the software storage.
+ R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr),
+ AesCtrStorage::KeySize, secure_value, counter_offset,
+ data_storage, node_storage, entry_storage, entry_count,
+ std::move(sw_decryptor)));
+
+ // Potentially set the output implementation storage.
+ if (out_ext != nullptr) {
+ *out_ext = sw_storage;
+ }
+
+ // Set the implementation storage.
+ aes_ctr_ex_storage = std::move(sw_storage);
+ }
+
+ // Create an alignment-matching storage.
+ using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>;
+ auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage));
+ R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(aligned_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out,
+ VirtualFile base_storage,
+ const NcaPatchInfo& patch_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(patch_info.HasIndirectTable());
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Check that we're within range.
+ R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeE);
+
+ // Create the meta storage.
+ auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size,
+ patch_info.indirect_offset);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create buffered storage.
+ std::vector<u8> meta_data(patch_info.indirect_size);
+ meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0);
+
+ auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
+ R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(buffered_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateIndirectStorage(
+ VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage,
+ VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(meta_storage != nullptr);
+ ASSERT(patch_info.HasIndirectTable());
+
+ // Read the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the storage sizes.
+ const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count);
+ const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count);
+ R_UNLESS(node_size + entry_size <= patch_info.indirect_size,
+ ResultInvalidNcaIndirectStorageOutOfRange);
+
+ // Get the indirect data size.
+ const s64 indirect_data_size = patch_info.indirect_offset;
+ ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize));
+
+ // Create the indirect data storage.
+ auto indirect_data_storage =
+ std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0);
+ R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the indirect storage.
+ auto indirect_storage = std::make_shared<IndirectStorage>();
+ R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the indirect storage.
+ R_TRY(indirect_storage->Initialize(
+ std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0),
+ std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count));
+
+ // Get the original data size.
+ s64 original_data_size = original_data_storage->GetSize();
+
+ // Set the indirect storages.
+ indirect_storage->SetStorage(
+ 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0));
+ indirect_storage->SetStorage(
+ 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0));
+
+ // If necessary, set the output indirect storage.
+ if (out_ind != nullptr) {
+ *out_ind = indirect_storage;
+ }
+
+ // Set the output.
+ *out = std::move(indirect_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreatePatchMetaStorage(
+ VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta,
+ VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
+ // Validate preconditions.
+ ASSERT(out_aes_ctr_ex_meta != nullptr);
+ ASSERT(out_indirect_meta != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(patch_info.HasAesCtrExTable());
+ ASSERT(patch_info.HasIndirectTable());
+ ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize));
+
+ // Validate patch info extents.
+ R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize);
+ R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize);
+ R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset,
+ ResultInvalidNcaPatchInfoAesCtrExOffset);
+ R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <=
+ meta_data_hash_data_info.offset,
+ ResultRomNcaInvalidPatchMetaDataHashDataOffset);
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Check that extents remain within range.
+ R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeE);
+ R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeB);
+
+ // Check that metadata hash data extents remain within range.
+ const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset;
+ const s64 meta_data_hash_data_size =
+ Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize);
+ R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeB);
+
+ // Create the encrypted storage.
+ auto enc_storage = std::make_shared<OffsetVfsFile>(
+ std::move(base_storage),
+ meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset,
+ patch_info.indirect_offset);
+ R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the decrypted storage.
+ VirtualFile decrypted_storage;
+ R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
+ offset + patch_info.indirect_offset, upper_iv,
+ AlignmentStorageRequirement::None));
+
+ // Create the verification storage.
+ VirtualFile integrity_storage;
+ Result rc = this->CreateIntegrityVerificationStorageForMeta(
+ std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage),
+ patch_info.indirect_offset, meta_data_hash_data_info);
+ if (rc == ResultInvalidNcaMetaDataHashDataSize) {
+ R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize);
+ }
+ if (rc == ResultInvalidNcaMetaDataHashDataHash) {
+ R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash);
+ }
+ R_TRY(rc);
+
+ // Create the indirect meta storage.
+ auto indirect_meta_storage =
+ std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size,
+ patch_info.indirect_offset - patch_info.indirect_offset);
+ R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the aes ctr ex meta storage.
+ auto aes_ctr_ex_meta_storage =
+ std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size,
+ patch_info.aes_ctr_ex_offset - patch_info.indirect_offset);
+ R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage);
+ *out_indirect_meta = std::move(indirect_meta_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSha256Storage(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Define storage types.
+ using VerificationStorage = HierarchicalSha256Storage;
+
+ // Validate the hash data.
+ R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size),
+ ResultInvalidHierarchicalSha256BlockSize);
+ R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1,
+ ResultInvalidHierarchicalSha256LayerCount);
+
+ // Get the regions.
+ const auto& hash_region = hash_data.hash_layer_region[0];
+ const auto& data_region = hash_data.hash_layer_region[1];
+
+ // Determine buffer sizes.
+ constexpr s32 CacheBlockCount = 2;
+ const auto hash_buffer_size = static_cast<size_t>(hash_region.size);
+ const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size;
+ const auto total_buffer_size = hash_buffer_size + cache_buffer_size;
+
+ // Make a buffer holder storage.
+ auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>(
+ std::move(base_storage), total_buffer_size);
+ R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+ R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI);
+
+ // Get storage size.
+ s64 base_size = buffer_hold_storage->GetSize();
+
+ // Check that we're within range.
+ R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC);
+ R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC);
+
+ // Create the master hash storage.
+ auto master_hash_storage =
+ std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value);
+
+ // Make the verification storage.
+ auto verification_storage = std::make_shared<VerificationStorage>();
+ R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Make layer storages.
+ std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{
+ std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0),
+ std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset),
+ std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset),
+ };
+
+ // Initialize the verification storage.
+ R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount,
+ hash_data.hash_block_size,
+ buffer_hold_storage->GetBuffer(), hash_buffer_size));
+
+ // Set the output.
+ *out = std::move(verification_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateIntegrityVerificationStorage(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) {
+ R_RETURN(this->CreateIntegrityVerificationStorageImpl(
+ out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount,
+ HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel(
+ meta_info.level_hash_info.max_layers)));
+}
+
+Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta(
+ VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+
+ // Check the meta data hash data size.
+ R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData),
+ ResultInvalidNcaMetaDataHashDataSize);
+
+ // Read the meta data hash data.
+ NcaMetaDataHashData meta_data_hash_data;
+ base_storage->ReadObject(std::addressof(meta_data_hash_data),
+ meta_data_hash_data_info.offset - offset);
+
+ // Set the out layer info storage, if necessary.
+ if (out_layer_info_storage != nullptr) {
+ auto layer_info_storage = std::make_shared<OffsetVfsFile>(
+ base_storage,
+ meta_data_hash_data_info.offset + meta_data_hash_data_info.size -
+ meta_data_hash_data.layer_info_offset,
+ meta_data_hash_data.layer_info_offset - offset);
+ R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ *out_layer_info_storage = std::move(layer_info_storage);
+ }
+
+ // Create the meta storage.
+ auto meta_storage = std::make_shared<OffsetVfsFile>(
+ std::move(base_storage), meta_data_hash_data_info.offset - offset, 0);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the integrity verification storage.
+ R_RETURN(this->CreateIntegrityVerificationStorageImpl(
+ out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info,
+ meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta,
+ IntegrityHashCacheCountForMeta, 0));
+}
+
+Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset,
+ int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(layer_info_offset >= 0);
+
+ // Define storage types.
+ using VerificationStorage = HierarchicalIntegrityVerificationStorage;
+ using StorageInfo = VerificationStorage::HierarchicalStorageInformation;
+
+ // Validate the meta info.
+ HierarchicalIntegrityVerificationInformation level_hash_info;
+ std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info),
+ sizeof(level_hash_info));
+
+ R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers,
+ ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
+ R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount,
+ ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
+
+ // Get the base storage size.
+ s64 base_storage_size = base_storage->GetSize();
+
+ // Create storage info.
+ StorageInfo storage_info;
+ for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) {
+ const auto& layer_info = level_hash_info.info[i];
+ R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size,
+ ResultNcaBaseStorageOutOfRangeD);
+
+ storage_info[i + 1] = std::make_shared<OffsetVfsFile>(
+ base_storage, layer_info.size, layer_info_offset + layer_info.offset);
+ }
+
+ // Set the last layer info.
+ const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2];
+ const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get();
+ R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size,
+ ResultNcaBaseStorageOutOfRangeD);
+ if (layer_info_offset > 0) {
+ R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset,
+ ResultRomNcaInvalidIntegrityLayerInfoOffset);
+ }
+ storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>(
+ std::move(base_storage), layer_info.size, last_layer_info_offset));
+
+ // Make the integrity romfs storage.
+ auto integrity_storage = std::make_shared<IntegrityRomFsStorage>();
+ R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the integrity storage.
+ R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info,
+ max_data_cache_entries, max_hash_cache_entries,
+ buffer_level));
+
+ // Set the output.
+ *out = std::move(integrity_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out,
+ const NcaFsHeaderReader* header_reader,
+ VirtualFile inside_storage,
+ VirtualFile outside_storage) {
+ // Check pre-conditions.
+ ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash);
+
+ // Create the region.
+ RegionSwitchStorage::Region region = {};
+ R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size)));
+
+ // Create the region switch storage.
+ auto region_switch_storage = std::make_shared<RegionSwitchStorage>(
+ std::move(inside_storage), std::move(outside_storage), region);
+ R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(region_switch_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out,
+ std::shared_ptr<CompressedStorage>* out_cmp,
+ VirtualFile* out_meta, VirtualFile base_storage,
+ const NcaCompressionInfo& compression_info) {
+ R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage),
+ compression_info, m_reader->GetDecompressor()));
+}
+
+Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out,
+ std::shared_ptr<CompressedStorage>* out_cmp,
+ VirtualFile* out_meta, VirtualFile base_storage,
+ const NcaCompressionInfo& compression_info,
+ GetDecompressorFunction get_decompressor) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(get_decompressor != nullptr);
+
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the storage extents.
+ const auto table_offset = compression_info.bucket.offset;
+ const auto table_size = compression_info.bucket.size;
+ const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count);
+ const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count);
+ R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize);
+
+ // If we should, set the output meta storage.
+ if (out_meta != nullptr) {
+ auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ *out_meta = std::move(meta_storage);
+ }
+
+ // Allocate the compressed storage.
+ auto compressed_storage = std::make_shared<CompressedStorage>();
+ R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the compressed storage.
+ R_TRY(compressed_storage->Initialize(
+ std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0),
+ std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset),
+ std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size),
+ header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32));
+
+ // Potentially set the output compressed storage.
+ if (out_cmp) {
+ *out_cmp = compressed_storage;
+ }
+
+ // Set the output.
+ *out = std::move(compressed_storage);
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_compression_common.h"
+#include "core/file_sys/fssystem/fssystem_nca_header.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+class CompressedStorage;
+class AesCtrCounterExtendedStorage;
+class IndirectStorage;
+class SparseStorage;
+
+struct NcaCryptoConfiguration;
+
+using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key,
+ size_t src_key_size, s32 key_type);
+using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data,
+ size_t data_size, u8 generation);
+
+struct NcaCryptoConfiguration {
+ static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8;
+ static constexpr size_t Rsa2048KeyPublicExponentSize = 3;
+ static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize;
+
+ static constexpr size_t Aes128KeySize = 128 / 8;
+
+ static constexpr size_t Header1SignatureKeyGenerationMax = 1;
+
+ static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3;
+ static constexpr s32 HeaderEncryptionKeyCount = 2;
+
+ static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF;
+
+ static constexpr size_t KeyGenerationMax = 32;
+
+ std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli;
+ std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent;
+ std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount>
+ key_area_encryption_key_source;
+ std::array<u8, Aes128KeySize> header_encryption_key_source;
+ std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount>
+ header_encrypted_encryption_keys;
+ KeyGenerationFunction generate_key;
+ VerifySign1Function verify_sign1;
+ bool is_plaintext_header_available;
+ bool is_available_sw_key;
+};
+static_assert(std::is_trivial_v<NcaCryptoConfiguration>);
+
+struct NcaCompressionConfiguration {
+ GetDecompressorFunction get_decompressor;
+};
+static_assert(std::is_trivial_v<NcaCompressionConfiguration>);
+
+constexpr inline s32 KeyAreaEncryptionKeyCount =
+ NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount *
+ NcaCryptoConfiguration::KeyGenerationMax;
+
+enum class KeyType : s32 {
+ ZeroKey = -2,
+ InvalidKey = -1,
+ NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0,
+ NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1,
+ NcaExternalKey = KeyAreaEncryptionKeyCount + 2,
+ SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3,
+ SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4,
+ SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5,
+};
+
+constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) {
+ return key_type < 0;
+}
+
+constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) {
+ if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) {
+ return static_cast<s32>(KeyType::ZeroKey);
+ }
+
+ if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) {
+ return static_cast<s32>(KeyType::InvalidKey);
+ }
+
+ return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index;
+}
+
+class NcaReader {
+ YUZU_NON_COPYABLE(NcaReader);
+ YUZU_NON_MOVEABLE(NcaReader);
+
+public:
+ NcaReader();
+ ~NcaReader();
+
+ Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg,
+ const NcaCompressionConfiguration& compression_cfg);
+
+ VirtualFile GetSharedBodyStorage();
+ u32 GetMagic() const;
+ NcaHeader::DistributionType GetDistributionType() const;
+ NcaHeader::ContentType GetContentType() const;
+ u8 GetHeaderSign1KeyGeneration() const;
+ u8 GetKeyGeneration() const;
+ u8 GetKeyIndex() const;
+ u64 GetContentSize() const;
+ u64 GetProgramId() const;
+ u32 GetContentIndex() const;
+ u32 GetSdkAddonVersion() const;
+ void GetRightsId(u8* dst, size_t dst_size) const;
+ bool HasFsInfo(s32 index) const;
+ s32 GetFsCount() const;
+ const Hash& GetFsHeaderHash(s32 index) const;
+ void GetFsHeaderHash(Hash* dst, s32 index) const;
+ void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const;
+ u64 GetFsOffset(s32 index) const;
+ u64 GetFsEndOffset(s32 index) const;
+ u64 GetFsSize(s32 index) const;
+ void GetEncryptedKey(void* dst, size_t size) const;
+ const void* GetDecryptionKey(s32 index) const;
+ bool HasValidInternalKey() const;
+ bool HasInternalDecryptionKeyForAesHw() const;
+ bool IsSoftwareAesPrioritized() const;
+ void PrioritizeSoftwareAes();
+ bool IsAvailableSwKey() const;
+ bool HasExternalDecryptionKey() const;
+ const void* GetExternalDecryptionKey() const;
+ void SetExternalDecryptionKey(const void* src, size_t size);
+ void GetRawData(void* dst, size_t dst_size) const;
+ NcaHeader::EncryptionType GetEncryptionType() const;
+ Result ReadHeader(NcaFsHeader* dst, s32 index) const;
+
+ GetDecompressorFunction GetDecompressor() const;
+
+ bool GetHeaderSign1Valid() const;
+
+ void GetHeaderSign2(void* dst, size_t size) const;
+
+private:
+ NcaHeader m_header;
+ std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>,
+ NcaHeader::DecryptionKey_Count>
+ m_decryption_keys;
+ VirtualFile m_body_storage;
+ VirtualFile m_header_storage;
+ std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key;
+ bool m_is_software_aes_prioritized;
+ bool m_is_available_sw_key;
+ NcaHeader::EncryptionType m_header_encryption_type;
+ bool m_is_header_sign1_signature_valid;
+ GetDecompressorFunction m_get_decompressor;
+};
+
+class NcaFsHeaderReader {
+ YUZU_NON_COPYABLE(NcaFsHeaderReader);
+ YUZU_NON_MOVEABLE(NcaFsHeaderReader);
+
+public:
+ NcaFsHeaderReader() : m_fs_index(-1) {
+ std::memset(std::addressof(m_data), 0, sizeof(m_data));
+ }
+
+ Result Initialize(const NcaReader& reader, s32 index);
+ bool IsInitialized() const {
+ return m_fs_index >= 0;
+ }
+
+ void GetRawData(void* dst, size_t dst_size) const;
+
+ NcaFsHeader::HashData& GetHashData();
+ const NcaFsHeader::HashData& GetHashData() const;
+ u16 GetVersion() const;
+ s32 GetFsIndex() const;
+ NcaFsHeader::FsType GetFsType() const;
+ NcaFsHeader::HashType GetHashType() const;
+ NcaFsHeader::EncryptionType GetEncryptionType() const;
+ NcaPatchInfo& GetPatchInfo();
+ const NcaPatchInfo& GetPatchInfo() const;
+ const NcaAesCtrUpperIv GetAesCtrUpperIv() const;
+
+ bool IsSkipLayerHashEncryption() const;
+ Result GetHashTargetOffset(s64* out) const;
+
+ bool ExistsSparseLayer() const;
+ NcaSparseInfo& GetSparseInfo();
+ const NcaSparseInfo& GetSparseInfo() const;
+
+ bool ExistsCompressionLayer() const;
+ NcaCompressionInfo& GetCompressionInfo();
+ const NcaCompressionInfo& GetCompressionInfo() const;
+
+ bool ExistsPatchMetaHashLayer() const;
+ NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo();
+ const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const;
+ NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const;
+
+ bool ExistsSparseMetaHashLayer() const;
+ NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo();
+ const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const;
+ NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const;
+
+private:
+ NcaFsHeader m_data;
+ s32 m_fs_index;
+};
+
+class NcaFileSystemDriver {
+ YUZU_NON_COPYABLE(NcaFileSystemDriver);
+ YUZU_NON_MOVEABLE(NcaFileSystemDriver);
+
+public:
+ struct StorageContext {
+ bool open_raw_storage;
+ VirtualFile body_substorage;
+ std::shared_ptr<SparseStorage> current_sparse_storage;
+ VirtualFile sparse_storage_meta_storage;
+ std::shared_ptr<SparseStorage> original_sparse_storage;
+ void* external_current_sparse_storage;
+ void* external_original_sparse_storage;
+ VirtualFile aes_ctr_ex_storage_meta_storage;
+ VirtualFile aes_ctr_ex_storage_data_storage;
+ std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage;
+ VirtualFile indirect_storage_meta_storage;
+ std::shared_ptr<IndirectStorage> indirect_storage;
+ VirtualFile fs_data_storage;
+ VirtualFile compressed_storage_meta_storage;
+ std::shared_ptr<CompressedStorage> compressed_storage;
+
+ VirtualFile patch_layer_info_storage;
+ VirtualFile sparse_layer_info_storage;
+
+ VirtualFile external_original_storage;
+ };
+
+private:
+ enum class AlignmentStorageRequirement {
+ CacheBlockSize = 0,
+ None = 1,
+ };
+
+public:
+ static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader,
+ s32 fs_index);
+
+public:
+ NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) {
+ ASSERT(m_reader != nullptr);
+ }
+
+ NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader,
+ std::shared_ptr<NcaReader> reader)
+ : m_original_reader(original_reader), m_reader(reader) {
+ ASSERT(m_reader != nullptr);
+ }
+
+ Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader,
+ s32 fs_index, StorageContext* ctx);
+
+ Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) {
+ // Create a storage context.
+ StorageContext ctx{};
+
+ // Open the storage.
+ R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx)));
+ }
+
+public:
+ Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
+ VirtualFile raw_storage, StorageContext* ctx);
+
+private:
+ Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index,
+ StorageContext* ctx);
+
+ Result OpenIndirectableStorageAsOriginal(VirtualFile* out,
+ const NcaFsHeaderReader* header_reader,
+ StorageContext* ctx);
+
+ Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size);
+
+ Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv,
+ AlignmentStorageRequirement alignment_storage_requirement);
+ Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset);
+
+ Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv,
+ const NcaSparseInfo& sparse_info);
+ Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage,
+ s64 base_size, VirtualFile meta_storage,
+ const NcaSparseInfo& sparse_info, bool external_info);
+ Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset,
+ std::shared_ptr<SparseStorage>* out_sparse_storage,
+ VirtualFile* out_meta_storage, s32 index,
+ const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info);
+
+ Result CreateSparseStorageMetaStorageWithVerification(
+ VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
+ Result CreateSparseStorageWithVerification(
+ VirtualFile* out, s64* out_fs_data_offset,
+ std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage,
+ VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv,
+ const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info,
+ NcaFsHeader::MetaDataHashType meta_data_hash_type);
+
+ Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
+ NcaFsHeader::EncryptionType encryption_type,
+ const NcaAesCtrUpperIv& upper_iv,
+ const NcaPatchInfo& patch_info);
+ Result CreateAesCtrExStorage(VirtualFile* out,
+ std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext,
+ VirtualFile base_storage, VirtualFile meta_storage,
+ s64 counter_offset, const NcaAesCtrUpperIv& upper_iv,
+ const NcaPatchInfo& patch_info);
+
+ Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage,
+ const NcaPatchInfo& patch_info);
+ Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind,
+ VirtualFile base_storage, VirtualFile original_data_storage,
+ VirtualFile meta_storage, const NcaPatchInfo& patch_info);
+
+ Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta,
+ VirtualFile* out_verification, VirtualFile base_storage,
+ s64 offset, const NcaAesCtrUpperIv& upper_iv,
+ const NcaPatchInfo& patch_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
+
+ Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data);
+
+ Result CreateIntegrityVerificationStorage(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info);
+ Result CreateIntegrityVerificationStorageForMeta(
+ VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
+ Result CreateIntegrityVerificationStorageImpl(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset,
+ int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
+
+ Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
+ VirtualFile inside_storage, VirtualFile outside_storage);
+
+ Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp,
+ VirtualFile* out_meta, VirtualFile base_storage,
+ const NcaCompressionInfo& compression_info);
+
+public:
+ Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp,
+ VirtualFile* out_meta, VirtualFile base_storage,
+ const NcaCompressionInfo& compression_info,
+ GetDecompressorFunction get_decompressor);
+
+private:
+ std::shared_ptr<NcaReader> m_original_reader;
+ std::shared_ptr<NcaReader> m_reader;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_nca_header.h"
+
+namespace FileSys {
+
+u8 NcaHeader::GetProperKeyGeneration() const {
+ return std::max(this->key_generation, this->key_generation_2);
+}
+
+bool NcaPatchInfo::HasIndirectTable() const {
+ return this->indirect_size != 0;
+}
+
+bool NcaPatchInfo::HasAesCtrExTable() const {
+ return this->aes_ctr_ex_size != 0;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/literals.h"
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_types.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+struct Hash {
+ static constexpr std::size_t Size = 256 / 8;
+ std::array<u8, Size> value;
+};
+static_assert(sizeof(Hash) == Hash::Size);
+static_assert(std::is_trivial_v<Hash>);
+
+using NcaDigest = Hash;
+
+struct NcaHeader {
+ enum class ContentType : u8 {
+ Program = 0,
+ Meta = 1,
+ Control = 2,
+ Manual = 3,
+ Data = 4,
+ PublicData = 5,
+
+ Start = Program,
+ End = PublicData,
+ };
+
+ enum class DistributionType : u8 {
+ Download = 0,
+ GameCard = 1,
+
+ Start = Download,
+ End = GameCard,
+ };
+
+ enum class EncryptionType : u8 {
+ Auto = 0,
+ None = 1,
+ };
+
+ enum DecryptionKey {
+ DecryptionKey_AesXts = 0,
+ DecryptionKey_AesXts1 = DecryptionKey_AesXts,
+ DecryptionKey_AesXts2 = 1,
+ DecryptionKey_AesCtr = 2,
+ DecryptionKey_AesCtrEx = 3,
+ DecryptionKey_AesCtrHw = 4,
+ DecryptionKey_Count,
+ };
+
+ struct FsInfo {
+ u32 start_sector;
+ u32 end_sector;
+ u32 hash_sectors;
+ u32 reserved;
+ };
+ static_assert(sizeof(FsInfo) == 0x10);
+ static_assert(std::is_trivial_v<FsInfo>);
+
+ static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0');
+ static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1');
+ static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2');
+ static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3');
+
+ static constexpr u32 Magic = Magic3;
+
+ static constexpr std::size_t Size = 1_KiB;
+ static constexpr s32 FsCountMax = 4;
+ static constexpr std::size_t HeaderSignCount = 2;
+ static constexpr std::size_t HeaderSignSize = 0x100;
+ static constexpr std::size_t EncryptedKeyAreaSize = 0x100;
+ static constexpr std::size_t SectorSize = 0x200;
+ static constexpr std::size_t SectorShift = 9;
+ static constexpr std::size_t RightsIdSize = 0x10;
+ static constexpr std::size_t XtsBlockSize = 0x200;
+ static constexpr std::size_t CtrBlockSize = 0x10;
+
+ static_assert(SectorSize == (1 << SectorShift));
+
+ // Data members.
+ std::array<u8, HeaderSignSize> header_sign_1;
+ std::array<u8, HeaderSignSize> header_sign_2;
+ u32 magic;
+ DistributionType distribution_type;
+ ContentType content_type;
+ u8 key_generation;
+ u8 key_index;
+ u64 content_size;
+ u64 program_id;
+ u32 content_index;
+ u32 sdk_addon_version;
+ u8 key_generation_2;
+ u8 header1_signature_key_generation;
+ std::array<u8, 2> reserved_222;
+ std::array<u32, 3> reserved_224;
+ std::array<u8, RightsIdSize> rights_id;
+ std::array<FsInfo, FsCountMax> fs_info;
+ std::array<Hash, FsCountMax> fs_header_hash;
+ std::array<u8, EncryptedKeyAreaSize> encrypted_key_area;
+
+ static constexpr u64 SectorToByte(u32 sector) {
+ return static_cast<u64>(sector) << SectorShift;
+ }
+
+ static constexpr u32 ByteToSector(u64 byte) {
+ return static_cast<u32>(byte >> SectorShift);
+ }
+
+ u8 GetProperKeyGeneration() const;
+};
+static_assert(sizeof(NcaHeader) == NcaHeader::Size);
+static_assert(std::is_trivial_v<NcaHeader>);
+
+struct NcaBucketInfo {
+ static constexpr size_t HeaderSize = 0x10;
+ Int64 offset;
+ Int64 size;
+ std::array<u8, HeaderSize> header;
+};
+static_assert(std::is_trivial_v<NcaBucketInfo>);
+
+struct NcaPatchInfo {
+ static constexpr size_t Size = 0x40;
+ static constexpr size_t Offset = 0x100;
+
+ Int64 indirect_offset;
+ Int64 indirect_size;
+ std::array<u8, NcaBucketInfo::HeaderSize> indirect_header;
+ Int64 aes_ctr_ex_offset;
+ Int64 aes_ctr_ex_size;
+ std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header;
+
+ bool HasIndirectTable() const;
+ bool HasAesCtrExTable() const;
+};
+static_assert(std::is_trivial_v<NcaPatchInfo>);
+
+union NcaAesCtrUpperIv {
+ u64 value;
+ struct {
+ u32 generation;
+ u32 secure_value;
+ } part;
+};
+static_assert(std::is_trivial_v<NcaAesCtrUpperIv>);
+
+struct NcaSparseInfo {
+ NcaBucketInfo bucket;
+ Int64 physical_offset;
+ u16 generation;
+ std::array<u8, 6> reserved;
+
+ s64 GetPhysicalSize() const {
+ return this->bucket.offset + this->bucket.size;
+ }
+
+ u32 GetGeneration() const {
+ return static_cast<u32>(this->generation) << 16;
+ }
+
+ const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const {
+ NcaAesCtrUpperIv sparse_upper_iv = upper_iv;
+ sparse_upper_iv.part.generation = this->GetGeneration();
+ return sparse_upper_iv;
+ }
+};
+static_assert(std::is_trivial_v<NcaSparseInfo>);
+
+struct NcaCompressionInfo {
+ NcaBucketInfo bucket;
+ std::array<u8, 8> resreved;
+};
+static_assert(std::is_trivial_v<NcaCompressionInfo>);
+
+struct NcaMetaDataHashDataInfo {
+ Int64 offset;
+ Int64 size;
+ Hash hash;
+};
+static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>);
+
+struct NcaFsHeader {
+ static constexpr size_t Size = 0x200;
+ static constexpr size_t HashDataOffset = 0x8;
+
+ struct Region {
+ Int64 offset;
+ Int64 size;
+ };
+ static_assert(std::is_trivial_v<Region>);
+
+ enum class FsType : u8 {
+ RomFs = 0,
+ PartitionFs = 1,
+ };
+
+ enum class EncryptionType : u8 {
+ Auto = 0,
+ None = 1,
+ AesXts = 2,
+ AesCtr = 3,
+ AesCtrEx = 4,
+ AesCtrSkipLayerHash = 5,
+ AesCtrExSkipLayerHash = 6,
+ };
+
+ enum class HashType : u8 {
+ Auto = 0,
+ None = 1,
+ HierarchicalSha256Hash = 2,
+ HierarchicalIntegrityHash = 3,
+ AutoSha3 = 4,
+ HierarchicalSha3256Hash = 5,
+ HierarchicalIntegritySha3Hash = 6,
+ };
+
+ enum class MetaDataHashType : u8 {
+ None = 0,
+ HierarchicalIntegrity = 1,
+ };
+
+ union HashData {
+ struct HierarchicalSha256Data {
+ static constexpr size_t HashLayerCountMax = 5;
+ static const size_t MasterHashOffset;
+
+ Hash fs_data_master_hash;
+ s32 hash_block_size;
+ s32 hash_layer_count;
+ std::array<Region, HashLayerCountMax> hash_layer_region;
+ } hierarchical_sha256_data;
+ static_assert(std::is_trivial_v<HierarchicalSha256Data>);
+
+ struct IntegrityMetaInfo {
+ static const size_t MasterHashOffset;
+
+ u32 magic;
+ u32 version;
+ u32 master_hash_size;
+
+ struct LevelHashInfo {
+ u32 max_layers;
+
+ struct HierarchicalIntegrityVerificationLevelInformation {
+ static constexpr size_t IntegrityMaxLayerCount = 7;
+ Int64 offset;
+ Int64 size;
+ s32 block_order;
+ std::array<u8, 4> reserved;
+ };
+ std::array<
+ HierarchicalIntegrityVerificationLevelInformation,
+ HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1>
+ info;
+
+ struct SignatureSalt {
+ static constexpr size_t Size = 0x20;
+ std::array<u8, Size> value;
+ };
+ SignatureSalt seed;
+ } level_hash_info;
+
+ Hash master_hash;
+ } integrity_meta_info;
+ static_assert(std::is_trivial_v<IntegrityMetaInfo>);
+
+ std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding;
+ };
+
+ u16 version;
+ FsType fs_type;
+ HashType hash_type;
+ EncryptionType encryption_type;
+ MetaDataHashType meta_data_hash_type;
+ std::array<u8, 2> reserved;
+ HashData hash_data;
+ NcaPatchInfo patch_info;
+ NcaAesCtrUpperIv aes_ctr_upper_iv;
+ NcaSparseInfo sparse_info;
+ NcaCompressionInfo compression_info;
+ NcaMetaDataHashDataInfo meta_data_hash_data_info;
+ std::array<u8, 0x30> pad;
+
+ bool IsSkipLayerHashEncryption() const {
+ return this->encryption_type == EncryptionType::AesCtrSkipLayerHash ||
+ this->encryption_type == EncryptionType::AesCtrExSkipLayerHash;
+ }
+
+ Result GetHashTargetOffset(s64* out) const {
+ switch (this->hash_type) {
+ case HashType::HierarchicalIntegrityHash:
+ case HashType::HierarchicalIntegritySha3Hash:
+ *out = this->hash_data.integrity_meta_info.level_hash_info
+ .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2]
+ .offset;
+ R_SUCCEED();
+ case HashType::HierarchicalSha256Hash:
+ case HashType::HierarchicalSha3256Hash:
+ *out =
+ this->hash_data.hierarchical_sha256_data
+ .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count -
+ 1]
+ .offset;
+ R_SUCCEED();
+ default:
+ R_THROW(ResultInvalidNcaFsHeader);
+ }
+ }
+};
+static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size);
+static_assert(std::is_trivial_v<NcaFsHeader>);
+static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset);
+
+inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset =
+ offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash);
+inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset =
+ offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash);
+
+struct NcaMetaDataHashData {
+ s64 layer_info_offset;
+ NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info;
+};
+static_assert(sizeof(NcaMetaDataHashData) ==
+ sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64));
+static_assert(std::is_trivial_v<NcaMetaDataHashData>);
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+namespace {
+
+constexpr inline u32 SdkAddonVersionMin = 0x000B0000;
+constexpr inline size_t Aes128KeySize = 0x10;
+constexpr const std::array<u8, Aes128KeySize> ZeroKey{};
+
+constexpr Result CheckNcaMagic(u32 magic) {
+ // Verify the magic is not a deprecated one.
+ R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion);
+ R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion);
+ R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion);
+
+ // Verify the magic is the current one.
+ R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature);
+
+ R_SUCCEED();
+}
+
+} // namespace
+
+NcaReader::NcaReader()
+ : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false),
+ m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto),
+ m_get_decompressor() {
+ std::memset(std::addressof(m_header), 0, sizeof(m_header));
+ std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys));
+ std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key));
+}
+
+NcaReader::~NcaReader() {}
+
+Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg,
+ const NcaCompressionConfiguration& compression_cfg) {
+ // Validate preconditions.
+ ASSERT(base_storage != nullptr);
+ ASSERT(m_body_storage == nullptr);
+
+ // Create the work header storage storage.
+ VirtualFile work_header_storage;
+
+ // We need to be able to generate keys.
+ R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument);
+
+ // Generate keys for header.
+ using AesXtsStorageForNcaHeader = AesXtsStorage;
+
+ constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount>
+ HeaderKeyTypeValues = {
+ static_cast<s32>(KeyType::NcaHeaderKey1),
+ static_cast<s32>(KeyType::NcaHeaderKey2),
+ };
+
+ std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>,
+ NcaCryptoConfiguration::HeaderEncryptionKeyCount>
+ header_decryption_keys;
+ for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) {
+ crypto_cfg.generate_key(header_decryption_keys[i].data(),
+ AesXtsStorageForNcaHeader::KeySize,
+ crypto_cfg.header_encrypted_encryption_keys[i].data(),
+ AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]);
+ }
+
+ // Create the header storage.
+ std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {};
+ work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>(
+ base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(),
+ AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize,
+ NcaHeader::XtsBlockSize);
+
+ // Check that we successfully created the storage.
+ R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA);
+
+ // Read the header.
+ work_header_storage->ReadObject(std::addressof(m_header), 0);
+
+ // Validate the magic.
+ if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) {
+ // Try to use a plaintext header.
+ base_storage->ReadObject(std::addressof(m_header), 0);
+ R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result);
+
+ // Configure to use the plaintext header.
+ auto base_storage_size = base_storage->GetSize();
+ work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0);
+ R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA);
+
+ // Set encryption type as plaintext.
+ m_header_encryption_type = NcaHeader::EncryptionType::None;
+ }
+
+ // Verify the header sign1.
+ if (crypto_cfg.verify_sign1 != nullptr) {
+ const u8* sig = m_header.header_sign_1.data();
+ const size_t sig_size = NcaHeader::HeaderSignSize;
+ const u8* msg =
+ static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic)));
+ const size_t msg_size =
+ NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount;
+
+ m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1(
+ sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation);
+
+ if (!m_is_header_sign1_signature_valid) {
+ LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1");
+ }
+ }
+
+ // Validate the sdk version.
+ R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion);
+
+ // Validate the key index.
+ R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount ||
+ m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey,
+ ResultInvalidNcaKeyIndex);
+
+ // Check if we have a rights id.
+ constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{};
+ if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) {
+ // If we don't, then we don't have an external key, so we need to generate decryption keys.
+ crypto_cfg.generate_key(
+ m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize,
+ m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize,
+ Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
+ crypto_cfg.generate_key(
+ m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize,
+ m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize,
+ Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
+ crypto_cfg.generate_key(
+ m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize,
+ m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize,
+ Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
+ crypto_cfg.generate_key(
+ m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize,
+ m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize,
+ Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
+
+ // Copy the hardware speed emulation key.
+ std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(),
+ m_header.encrypted_key_area.data() +
+ NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize,
+ Aes128KeySize);
+ }
+
+ // Clear the external decryption key.
+ std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size());
+
+ // Set software key availability.
+ m_is_available_sw_key = crypto_cfg.is_available_sw_key;
+
+ // Set our decompressor function getter.
+ m_get_decompressor = compression_cfg.get_decompressor;
+
+ // Set our storages.
+ m_header_storage = std::move(work_header_storage);
+ m_body_storage = std::move(base_storage);
+
+ R_SUCCEED();
+}
+
+VirtualFile NcaReader::GetSharedBodyStorage() {
+ ASSERT(m_body_storage != nullptr);
+ return m_body_storage;
+}
+
+u32 NcaReader::GetMagic() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.magic;
+}
+
+NcaHeader::DistributionType NcaReader::GetDistributionType() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.distribution_type;
+}
+
+NcaHeader::ContentType NcaReader::GetContentType() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.content_type;
+}
+
+u8 NcaReader::GetHeaderSign1KeyGeneration() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.header1_signature_key_generation;
+}
+
+u8 NcaReader::GetKeyGeneration() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.GetProperKeyGeneration();
+}
+
+u8 NcaReader::GetKeyIndex() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.key_index;
+}
+
+u64 NcaReader::GetContentSize() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.content_size;
+}
+
+u64 NcaReader::GetProgramId() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.program_id;
+}
+
+u32 NcaReader::GetContentIndex() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.content_index;
+}
+
+u32 NcaReader::GetSdkAddonVersion() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.sdk_addon_version;
+}
+
+void NcaReader::GetRightsId(u8* dst, size_t dst_size) const {
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size >= NcaHeader::RightsIdSize);
+
+ std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize);
+}
+
+bool NcaReader::HasFsInfo(s32 index) const {
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0;
+}
+
+s32 NcaReader::GetFsCount() const {
+ ASSERT(m_body_storage != nullptr);
+ for (s32 i = 0; i < NcaHeader::FsCountMax; i++) {
+ if (!this->HasFsInfo(i)) {
+ return i;
+ }
+ }
+ return NcaHeader::FsCountMax;
+}
+
+const Hash& NcaReader::GetFsHeaderHash(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return m_header.fs_header_hash[index];
+}
+
+void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ ASSERT(dst != nullptr);
+ std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst));
+}
+
+void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ ASSERT(dst != nullptr);
+ std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst));
+}
+
+u64 NcaReader::GetFsOffset(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector);
+}
+
+u64 NcaReader::GetFsEndOffset(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector);
+}
+
+u64 NcaReader::GetFsSize(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector -
+ m_header.fs_info[index].start_sector);
+}
+
+void NcaReader::GetEncryptedKey(void* dst, size_t size) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(dst != nullptr);
+ ASSERT(size >= NcaHeader::EncryptedKeyAreaSize);
+
+ std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize);
+}
+
+const void* NcaReader::GetDecryptionKey(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count);
+ return m_decryption_keys[index].data();
+}
+
+bool NcaReader::HasValidInternalKey() const {
+ for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) {
+ if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize,
+ Aes128KeySize) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool NcaReader::HasInternalDecryptionKeyForAesHw() const {
+ return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw),
+ Aes128KeySize) != 0;
+}
+
+bool NcaReader::IsSoftwareAesPrioritized() const {
+ return m_is_software_aes_prioritized;
+}
+
+void NcaReader::PrioritizeSoftwareAes() {
+ m_is_software_aes_prioritized = true;
+}
+
+bool NcaReader::IsAvailableSwKey() const {
+ return m_is_available_sw_key;
+}
+
+bool NcaReader::HasExternalDecryptionKey() const {
+ return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0;
+}
+
+const void* NcaReader::GetExternalDecryptionKey() const {
+ return m_external_decryption_key.data();
+}
+
+void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) {
+ ASSERT(src != nullptr);
+ ASSERT(size == sizeof(m_external_decryption_key));
+
+ std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key));
+}
+
+void NcaReader::GetRawData(void* dst, size_t dst_size) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size >= sizeof(NcaHeader));
+
+ std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader));
+}
+
+GetDecompressorFunction NcaReader::GetDecompressor() const {
+ ASSERT(m_get_decompressor != nullptr);
+ return m_get_decompressor;
+}
+
+NcaHeader::EncryptionType NcaReader::GetEncryptionType() const {
+ return m_header_encryption_type;
+}
+
+Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const {
+ ASSERT(dst != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+
+ const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index;
+ m_header_storage->ReadObject(dst, offset);
+
+ R_SUCCEED();
+}
+
+bool NcaReader::GetHeaderSign1Valid() const {
+ return m_is_header_sign1_signature_valid;
+}
+
+void NcaReader::GetHeaderSign2(void* dst, size_t size) const {
+ ASSERT(dst != nullptr);
+ ASSERT(size == NcaHeader::HeaderSignSize);
+
+ std::memcpy(dst, m_header.header_sign_2.data(), size);
+}
+
+Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) {
+ // Reset ourselves to uninitialized.
+ m_fs_index = -1;
+
+ // Read the header.
+ R_TRY(reader.ReadHeader(std::addressof(m_data), index));
+
+ // Set our index.
+ m_fs_index = index;
+ R_SUCCEED();
+}
+
+void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const {
+ ASSERT(this->IsInitialized());
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size >= sizeof(NcaFsHeader));
+
+ std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader));
+}
+
+NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() {
+ ASSERT(this->IsInitialized());
+ return m_data.hash_data;
+}
+
+const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const {
+ ASSERT(this->IsInitialized());
+ return m_data.hash_data;
+}
+
+u16 NcaFsHeaderReader::GetVersion() const {
+ ASSERT(this->IsInitialized());
+ return m_data.version;
+}
+
+s32 NcaFsHeaderReader::GetFsIndex() const {
+ ASSERT(this->IsInitialized());
+ return m_fs_index;
+}
+
+NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.fs_type;
+}
+
+NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.hash_type;
+}
+
+NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.encryption_type;
+}
+
+NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.patch_info;
+}
+
+const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.patch_info;
+}
+
+const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const {
+ ASSERT(this->IsInitialized());
+ return m_data.aes_ctr_upper_iv;
+}
+
+bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const {
+ ASSERT(this->IsInitialized());
+ return m_data.IsSkipLayerHashEncryption();
+}
+
+Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const {
+ ASSERT(out != nullptr);
+ ASSERT(this->IsInitialized());
+
+ R_RETURN(m_data.GetHashTargetOffset(out));
+}
+
+bool NcaFsHeaderReader::ExistsSparseLayer() const {
+ ASSERT(this->IsInitialized());
+ return m_data.sparse_info.generation != 0;
+}
+
+NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.sparse_info;
+}
+
+const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.sparse_info;
+}
+
+bool NcaFsHeaderReader::ExistsCompressionLayer() const {
+ ASSERT(this->IsInitialized());
+ return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0;
+}
+
+NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.compression_info;
+}
+
+const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.compression_info;
+}
+
+bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable();
+}
+
+NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info;
+}
+
+const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info;
+}
+
+NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_type;
+}
+
+bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer();
+}
+
+NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info;
+}
+
+const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info;
+}
+
+NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_type;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+
+namespace FileSys {
+
+namespace {
+
+constexpr size_t HeapBlockSize = BufferPoolAlignment;
+static_assert(HeapBlockSize == 4_KiB);
+
+// A heap block is 4KiB. An order is a power of two.
+// This gives blocks of the order 32KiB, 512KiB, 4MiB.
+constexpr s32 HeapOrderMax = 7;
+constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3;
+
+constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax);
+constexpr size_t HeapAllocatableSizeMaxForLarge =
+ HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge);
+
+} // namespace
+
+size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) {
+ return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax;
+}
+
+void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) {
+ // Ensure preconditions.
+ ASSERT(m_buffer == nullptr);
+
+ // Check that we can allocate this size.
+ ASSERT(required_size <= GetAllocatableSizeMaxCore(large));
+
+ const size_t target_size =
+ std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large));
+
+ // Dummy implementation for allocate.
+ if (target_size > 0) {
+ m_buffer =
+ reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize}));
+ m_size = target_size;
+
+ // Ensure postconditions.
+ ASSERT(m_buffer != nullptr);
+ }
+}
+
+void PooledBuffer::Shrink(size_t ideal_size) {
+ ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true));
+
+ // Shrinking to zero means that we have no buffer.
+ if (ideal_size == 0) {
+ ::operator delete(m_buffer, std::align_val_t{HeapBlockSize});
+ m_buffer = nullptr;
+ m_size = ideal_size;
+ }
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/literals.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+constexpr inline size_t BufferPoolAlignment = 4_KiB;
+constexpr inline size_t BufferPoolWorkSize = 320;
+
+class PooledBuffer {
+ YUZU_NON_COPYABLE(PooledBuffer);
+
+public:
+ // Constructor/Destructor.
+ constexpr PooledBuffer() : m_buffer(), m_size() {}
+
+ PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() {
+ this->Allocate(ideal_size, required_size);
+ }
+
+ ~PooledBuffer() {
+ this->Deallocate();
+ }
+
+ // Move and assignment.
+ explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) {
+ rhs.m_buffer = nullptr;
+ rhs.m_size = 0;
+ }
+
+ PooledBuffer& operator=(PooledBuffer&& rhs) {
+ PooledBuffer(std::move(rhs)).Swap(*this);
+ return *this;
+ }
+
+ // Allocation API.
+ void Allocate(size_t ideal_size, size_t required_size) {
+ return this->AllocateCore(ideal_size, required_size, false);
+ }
+
+ void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) {
+ return this->AllocateCore(ideal_size, required_size, true);
+ }
+
+ void Shrink(size_t ideal_size);
+
+ void Deallocate() {
+ // Shrink the buffer to empty.
+ this->Shrink(0);
+ ASSERT(m_buffer == nullptr);
+ }
+
+ char* GetBuffer() const {
+ ASSERT(m_buffer != nullptr);
+ return m_buffer;
+ }
+
+ size_t GetSize() const {
+ ASSERT(m_buffer != nullptr);
+ return m_size;
+ }
+
+public:
+ static size_t GetAllocatableSizeMax() {
+ return GetAllocatableSizeMaxCore(false);
+ }
+ static size_t GetAllocatableParticularlyLargeSizeMax() {
+ return GetAllocatableSizeMaxCore(true);
+ }
+
+private:
+ static size_t GetAllocatableSizeMaxCore(bool large);
+
+private:
+ void Swap(PooledBuffer& rhs) {
+ std::swap(m_buffer, rhs.m_buffer);
+ std::swap(m_size, rhs.m_size);
+ }
+
+ void AllocateCore(size_t ideal_size, size_t required_size, bool large);
+
+private:
+ char* m_buffer;
+ size_t m_size;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_sparse_storage.h"
+
+namespace FileSys {
+
+size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Validate preconditions.
+ ASSERT(this->IsInitialized());
+ ASSERT(buffer != nullptr);
+
+ // Allow zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ SparseStorage* self = const_cast<SparseStorage*>(this);
+
+ if (self->GetEntryTable().IsEmpty()) {
+ BucketTree::Offsets table_offsets;
+ ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets))));
+ ASSERT(table_offsets.IsInclude(offset, size));
+
+ std::memset(buffer, 0, size);
+ } else {
+ self->OperatePerEntry<false, true>(
+ offset, size,
+ [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
+ storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
+ static_cast<size_t>(cur_size), data_offset);
+ R_SUCCEED();
+ });
+ }
+
+ return size;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
+
+namespace FileSys {
+
+class SparseStorage : public IndirectStorage {
+ YUZU_NON_COPYABLE(SparseStorage);
+ YUZU_NON_MOVEABLE(SparseStorage);
+
+private:
+ class ZeroStorage : public IReadOnlyStorage {
+ public:
+ ZeroStorage() {}
+ virtual ~ZeroStorage() {}
+
+ virtual size_t GetSize() const override {
+ return std::numeric_limits<size_t>::max();
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ ASSERT(buffer != nullptr || size == 0);
+
+ if (size > 0) {
+ std::memset(buffer, 0, size);
+ }
+
+ return size;
+ }
+ };
+
+public:
+ SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {}
+ virtual ~SparseStorage() {}
+
+ using IndirectStorage::Initialize;
+
+ void Initialize(s64 end_offset) {
+ this->GetEntryTable().Initialize(NodeSize, end_offset);
+ this->SetZeroStorage();
+ }
+
+ void SetDataStorage(VirtualFile storage) {
+ ASSERT(this->IsInitialized());
+
+ this->SetStorage(0, storage);
+ this->SetZeroStorage();
+ }
+
+ template <typename T>
+ void SetDataStorage(T storage, s64 offset, s64 size) {
+ ASSERT(this->IsInitialized());
+
+ this->SetStorage(0, storage, offset, size);
+ this->SetZeroStorage();
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+
+private:
+ void SetZeroStorage() {
+ return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max());
+ }
+
+private:
+ VirtualFile m_zero_storage;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fs_i_storage.h"
+
+namespace FileSys {
+
+class RegionSwitchStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(RegionSwitchStorage);
+ YUZU_NON_MOVEABLE(RegionSwitchStorage);
+
+public:
+ struct Region {
+ s64 offset;
+ s64 size;
+ };
+
+public:
+ RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r)
+ : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)),
+ m_region(r) {}
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Process until we're done.
+ size_t processed = 0;
+ while (processed < size) {
+ // Process on the appropriate storage.
+ s64 cur_size = 0;
+ if (this->CheckRegions(std::addressof(cur_size), offset + processed,
+ size - processed)) {
+ m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed);
+ } else {
+ m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed);
+ }
+
+ // Advance.
+ processed += cur_size;
+ }
+
+ return size;
+ }
+
+ virtual size_t GetSize() const override {
+ return m_inside_region_storage->GetSize();
+ }
+
+private:
+ bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const {
+ // Check if our region contains the access.
+ if (m_region.offset <= offset) {
+ if (offset < m_region.offset + m_region.size) {
+ if (m_region.offset + m_region.size <= offset + size) {
+ *out_current_size = m_region.offset + m_region.size - offset;
+ } else {
+ *out_current_size = size;
+ }
+ return true;
+ } else {
+ *out_current_size = size;
+ return false;
+ }
+ } else {
+ if (m_region.offset <= offset + size) {
+ *out_current_size = m_region.offset - offset;
+ } else {
+ *out_current_size = size;
+ }
+ return false;
+ }
+ }
+
+private:
+ VirtualFile m_inside_region_storage;
+ VirtualFile m_outside_region_storage;
+ Region m_region;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_utility.h"
+
+namespace FileSys {
+
+void AddCounter(void* counter_, size_t counter_size, u64 value) {
+ u8* counter = static_cast<u8*>(counter_);
+ u64 remaining = value;
+ u8 carry = 0;
+
+ for (size_t i = 0; i < counter_size; i++) {
+ auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry;
+ carry = static_cast<u8>(sum >> (sizeof(u8) * 8));
+ auto sum8 = static_cast<u8>(sum & 0xFF);
+
+ counter[counter_size - 1 - i] = sum8;
+
+ remaining >>= (sizeof(u8) * 8);
+ if (carry == 0 && remaining == 0) {
+ break;
+ }
+ }
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+
+namespace FileSys {
+
+void AddCounter(void* counter, size_t counter_size, u64 value);
+
+}
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) {
void IPSwitchCompiler::ParseFlag(const std::string& line) {
if (StartsWith(line, "@flag offset_shift ")) {
// Offset Shift Flag
- offset_shift = std::stoll(line.substr(19), nullptr, 0);
+ offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
} else if (StartsWith(line, "@little-endian")) {
// Set values to read as little endian
is_little_endian = true;
@@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
if (patch_line.length() < 11)
break;
- auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
+ auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
offset += static_cast<unsigned long>(offset_shift);
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_,
CNMT::~CNMT() = default;
+const CNMTHeader& CNMT::GetHeader() const {
+ return header;
+}
+
u64 CNMT::GetTitleID() const {
return header.title_id;
}
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:
std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_);
~CNMT();
+ const CNMTHeader& GetHeader() const;
u64 GetTitleID() const;
u32 GetTitleVersion() const;
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 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <algorithm>
-#include <array>
-#include <cstddef>
-#include <cstring>
-
-#include "common/assert.h"
-#include "core/crypto/aes_util.h"
-#include "core/file_sys/nca_patch.h"
-
-namespace FileSys {
-namespace {
-template <bool Subsection, typename BlockType, typename BucketType>
-std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block,
- const BucketType& buckets) {
- if constexpr (Subsection) {
- const auto& last_bucket = buckets[block.number_buckets - 1];
- if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) {
- return {block.number_buckets - 1, last_bucket.number_entries};
- }
- } else {
- ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
- }
-
- std::size_t bucket_id = std::count_if(
- block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
- [&offset](u64 base_offset) { return base_offset <= offset; });
-
- const auto& bucket = buckets[bucket_id];
-
- if (bucket.number_entries == 1) {
- return {bucket_id, 0};
- }
-
- std::size_t low = 0;
- std::size_t mid = 0;
- std::size_t high = bucket.number_entries - 1;
- while (low <= high) {
- mid = (low + high) / 2;
- if (bucket.entries[mid].address_patch > offset) {
- high = mid - 1;
- } else {
- if (mid == bucket.number_entries - 1 ||
- bucket.entries[mid + 1].address_patch > offset) {
- return {bucket_id, mid};
- }
-
- low = mid + 1;
- }
- }
- ASSERT_MSG(false, "Offset could not be found in BKTR block.");
- return {0, 0};
-}
-} // Anonymous namespace
-
-BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
- std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
- std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
- Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
- std::array<u8, 8> section_ctr_)
- : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
- subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
- base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
- encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
- section_ctr(section_ctr_) {
- for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) {
- relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0});
- }
-
- for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) {
- subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch,
- {0},
- subsection_buckets[i + 1].entries[0].ctr});
- }
-
- relocation_buckets.back().entries.push_back({relocation.size, 0, 0});
-}
-
-BKTR::~BKTR() = default;
-
-std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
- // Read out of bounds.
- if (offset >= relocation.size) {
- return 0;
- }
-
- const auto relocation_entry = GetRelocationEntry(offset);
- const auto section_offset =
- offset - relocation_entry.address_patch + relocation_entry.address_source;
- const auto bktr_read = relocation_entry.from_patch;
-
- const auto next_relocation = GetNextRelocationEntry(offset);
-
- if (offset + length > next_relocation.address_patch) {
- const u64 partition = next_relocation.address_patch - offset;
- return Read(data, partition, offset) +
- Read(data + partition, length - partition, offset + partition);
- }
-
- if (!bktr_read) {
- ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative.");
- return base_romfs->Read(data, length, section_offset - ivfc_offset);
- }
-
- if (!encrypted) {
- return bktr_romfs->Read(data, length, section_offset);
- }
-
- const auto subsection_entry = GetSubsectionEntry(section_offset);
- Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
-
- // Calculate AES IV
- std::array<u8, 16> iv{};
- auto subsection_ctr = subsection_entry.ctr;
- auto offset_iv = section_offset + base_offset;
- for (std::size_t i = 0; i < section_ctr.size(); ++i) {
- iv[i] = section_ctr[0x8 - i - 1];
- }
- offset_iv >>= 4;
- for (std::size_t i = 0; i < sizeof(u64); ++i) {
- iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF);
- offset_iv >>= 8;
- }
- for (std::size_t i = 0; i < sizeof(u32); ++i) {
- iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF);
- subsection_ctr >>= 8;
- }
- cipher.SetIV(iv);
-
- const auto next_subsection = GetNextSubsectionEntry(section_offset);
-
- if (section_offset + length > next_subsection.address_patch) {
- const u64 partition = next_subsection.address_patch - section_offset;
- return Read(data, partition, offset) +
- Read(data + partition, length - partition, offset + partition);
- }
-
- const auto block_offset = section_offset & 0xF;
- if (block_offset != 0) {
- auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF);
- cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt);
- if (length + block_offset < 0x10) {
- std::memcpy(data, block.data() + block_offset, std::min(length, block.size()));
- return std::min(length, block.size());
- }
-
- const auto read = 0x10 - block_offset;
- std::memcpy(data, block.data() + block_offset, read);
- return read + Read(data + read, length - read, offset + read);
- }
-
- const auto raw_read = bktr_romfs->Read(data, length, section_offset);
- cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt);
- return raw_read;
-}
-
-RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
- const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
- return relocation_buckets[res.first].entries[res.second];
-}
-
-RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const {
- const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
- const auto bucket = relocation_buckets[res.first];
- if (res.second + 1 < bucket.entries.size())
- return bucket.entries[res.second + 1];
- return relocation_buckets[res.first + 1].entries[0];
-}
-
-SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const {
- const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
- return subsection_buckets[res.first].entries[res.second];
-}
-
-SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const {
- const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
- const auto bucket = subsection_buckets[res.first];
- if (res.second + 1 < bucket.entries.size())
- return bucket.entries[res.second + 1];
- return subsection_buckets[res.first + 1].entries[0];
-}
-
-std::string BKTR::GetName() const {
- return base_romfs->GetName();
-}
-
-std::size_t BKTR::GetSize() const {
- return relocation.size;
-}
-
-bool BKTR::Resize(std::size_t new_size) {
- return false;
-}
-
-VirtualDir BKTR::GetContainingDirectory() const {
- return base_romfs->GetContainingDirectory();
-}
-
-bool BKTR::IsWritable() const {
- return false;
-}
-
-bool BKTR::IsReadable() const {
- return true;
-}
-
-std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) {
- return 0;
-}
-
-bool BKTR::Rename(std::string_view name) {
- return base_romfs->Rename(name);
-}
-
-} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <memory>
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "core/crypto/key_manager.h"
-
-namespace FileSys {
-
-#pragma pack(push, 1)
-struct RelocationEntry {
- u64_le address_patch;
- u64_le address_source;
- u32 from_patch;
-};
-#pragma pack(pop)
-static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
-
-struct RelocationBucketRaw {
- INSERT_PADDING_BYTES(4);
- u32_le number_entries;
- u64_le end_offset;
- std::array<RelocationEntry, 0x332> relocation_entries;
- INSERT_PADDING_BYTES(8);
-};
-static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
-
-// Vector version of RelocationBucketRaw
-struct RelocationBucket {
- u32 number_entries;
- u64 end_offset;
- std::vector<RelocationEntry> entries;
-};
-
-struct RelocationBlock {
- INSERT_PADDING_BYTES(4);
- u32_le number_buckets;
- u64_le size;
- std::array<u64, 0x7FE> base_offsets;
-};
-static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
-
-struct SubsectionEntry {
- u64_le address_patch;
- INSERT_PADDING_BYTES(0x4);
- u32_le ctr;
-};
-static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
-
-struct SubsectionBucketRaw {
- INSERT_PADDING_BYTES(4);
- u32_le number_entries;
- u64_le end_offset;
- std::array<SubsectionEntry, 0x3FF> subsection_entries;
-};
-static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
-
-// Vector version of SubsectionBucketRaw
-struct SubsectionBucket {
- u32 number_entries;
- u64 end_offset;
- std::vector<SubsectionEntry> entries;
-};
-
-struct SubsectionBlock {
- INSERT_PADDING_BYTES(4);
- u32_le number_buckets;
- u64_le size;
- std::array<u64, 0x7FE> base_offsets;
-};
-static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
-
-inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) {
- return {raw.number_entries,
- raw.end_offset,
- {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}};
-}
-
-inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) {
- return {raw.number_entries,
- raw.end_offset,
- {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}};
-}
-
-class BKTR : public VfsFile {
-public:
- BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation,
- std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection,
- std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted,
- Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr);
- ~BKTR() override;
-
- std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
-
- std::string GetName() const override;
-
- std::size_t GetSize() const override;
-
- bool Resize(std::size_t new_size) override;
-
- VirtualDir GetContainingDirectory() const override;
-
- bool IsWritable() const override;
-
- bool IsReadable() const override;
-
- std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
-
- bool Rename(std::string_view name) override;
-
-private:
- RelocationEntry GetRelocationEntry(u64 offset) const;
- RelocationEntry GetNextRelocationEntry(u64 offset) const;
-
- SubsectionEntry GetSubsectionEntry(u64 offset) const;
- SubsectionEntry GetNextSubsectionEntry(u64 offset) const;
-
- RelocationBlock relocation;
- std::vector<RelocationBucket> relocation_buckets;
- SubsectionBlock subsection;
- std::vector<SubsectionBucket> subsection_buckets;
-
- // Should be the raw base romfs, decrypted.
- VirtualFile base_romfs;
- // Should be the raw BKTR romfs, (located at media_offset with size media_size).
- VirtualFile bktr_romfs;
-
- bool encrypted;
- Core::Crypto::Key128 key;
-
- // Base offset into NCA, used for IV calculation.
- u64 base_offset;
- // Distance between IVFC start and RomFS start, used for base reads
- u64 ivfc_offset;
- std::array<u8, 8> section_ctr;
-};
-
-} // namespace FileSys
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 2527ae606..2422cb51b 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -47,6 +47,7 @@ PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
// Actually read in now...
std::vector<u8> file_data = file->ReadBytes(metadata_size);
const std::size_t total_size = file_data.size();
+ file_data.push_back(0);
if (total_size != metadata_size) {
status = Loader::ResultStatus::ErrorIncorrectPFSFileSize;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index d3286b352..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 {
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
- if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
- update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
exefs = update->GetExeFS();
@@ -295,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
return out;
}
-bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
+bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
const auto build_id_raw = Common::HexToString(build_id_);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
- LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
+ LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
@@ -353,16 +352,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
const Service::FileSystem::FileSystemController& fs_controller) {
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
- if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
+ if ((type != ContentRecordType::Program && type != ContentRecordType::Data &&
+ type != ContentRecordType::HtmlDocument) ||
(load_dir == nullptr && sdmc_load_dir == nullptr)) {
return;
}
- auto extracted = ExtractRomFS(romfs);
- if (extracted == nullptr) {
- return;
- }
-
const auto& disabled = Settings::values.disabled_addons[title_id];
std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
@@ -387,6 +382,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext");
if (ext_dir != nullptr)
layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir));
+
+ if (type == ContentRecordType::HtmlDocument) {
+ auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html");
+ if (manual_dir != nullptr)
+ layers.push_back(std::make_shared<CachedVfsDirectory>(manual_dir));
+ }
}
// 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
return;
}
+ auto extracted = ExtractRomFS(romfs);
+ if (extracted == nullptr) {
+ return;
+ }
+
layers.push_back(std::move(extracted));
auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
@@ -412,39 +418,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
romfs = std::move(packed);
}
-VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
- VirtualFile update_raw, bool apply_layeredfs) const {
+VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs,
+ ContentRecordType type, VirtualFile packed_update_raw,
+ bool apply_layeredfs) const {
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
title_id, static_cast<u8>(type));
-
if (type == ContentRecordType::Program || type == ContentRecordType::Data) {
LOG_INFO(Loader, "{}", log_string);
} else {
LOG_DEBUG(Loader, "{}", log_string);
}
- if (romfs == nullptr) {
- return romfs;
+ if (base_romfs == nullptr) {
+ return base_romfs;
}
+ auto romfs = base_romfs;
+
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- const auto update = content_provider.GetEntryRaw(update_tid, type);
+ const auto update_raw = content_provider.GetEntryRaw(update_tid, type);
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
- if (!update_disabled && update != nullptr) {
- const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
+ if (!update_disabled && update_raw != nullptr && base_nca != nullptr) {
+ const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
+ const auto version =
+ FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0));
}
- } else if (!update_disabled && update_raw != nullptr) {
- const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
+ } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
+ const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
@@ -608,7 +618,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
return {};
}
- const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
+ const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control);
if (romfs == nullptr) {
return {};
}
@@ -626,8 +636,8 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
// Get language code from settings
- const auto language_code =
- Service::Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue());
+ const auto language_code = Service::Set::GetLanguageCodeFromIndex(
+ static_cast<u32>(Settings::values.language_index.GetValue()));
// Convert to application language and get priority list
const auto application_language =
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:
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
// Used to prevent expensive copies in NSO loader.
- [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
+ [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
// Creates a CheatList object with all
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
@@ -61,9 +61,9 @@ public:
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
- [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
+ [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs,
ContentRecordType type = ContentRecordType::Program,
- VirtualFile update_raw = nullptr,
+ VirtualFile packed_update_raw = nullptr,
bool apply_layeredfs = true) const;
// 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 @@
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
@@ -416,9 +417,9 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
if (file == nullptr)
continue;
- const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0);
+ const auto nca = std::make_shared<NCA>(parser(file, id));
if (nca->GetStatus() != Loader::ResultStatus::Success ||
- nca->GetType() != NCAContentType::Meta) {
+ nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) {
continue;
}
@@ -500,7 +501,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
- return std::make_unique<NCA>(raw, nullptr, 0);
+ return std::make_unique<NCA>(raw);
}
template <typename T>
@@ -606,9 +607,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
const auto result = RemoveExistingEntry(title_id);
// Install Metadata File
- const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data);
- if (res != InstallResult::Success) {
- return res;
+ const auto meta_result = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data);
+ if (meta_result != InstallResult::Success) {
+ return meta_result;
}
// Install all the other NCAs
@@ -621,9 +622,19 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
if (nca == nullptr) {
return InstallResult::ErrorCopyFailed;
}
- const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id);
- if (res2 != InstallResult::Success) {
- return res2;
+ if (nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
+ nca->GetTitleId() != title_id) {
+ // Create fake cnmt for patch to multiprogram application
+ const auto sub_nca_result =
+ InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy);
+ if (sub_nca_result != InstallResult::Success) {
+ return sub_nca_result;
+ }
+ continue;
+ }
+ const auto nca_result = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id);
+ if (nca_result != InstallResult::Success) {
+ return nca_result;
}
}
@@ -662,7 +673,34 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
}
+InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header,
+ const ContentRecord& base_record,
+ bool overwrite_if_exists, const VfsCopyFunction& copy) {
+ const CNMTHeader header{
+ .title_id = nca.GetTitleId(),
+ .title_version = base_header.title_version,
+ .type = base_header.type,
+ .reserved = {},
+ .table_offset = 0x10,
+ .number_content_entries = 1,
+ .number_meta_entries = 0,
+ .attributes = 0,
+ .reserved2 = {},
+ .is_committed = 0,
+ .required_download_system_version = 0,
+ .reserved3 = {},
+ };
+ const OptionalHeader opt_header{0, 0};
+ const CNMT new_cnmt(header, opt_header, {base_record}, {});
+ if (!RawInstallYuzuMeta(new_cnmt)) {
+ return InstallResult::ErrorMetaFailed;
+ }
+ return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id);
+}
+
bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
+ bool removed_data = false;
+
const auto delete_nca = [this](const NcaID& id) {
const auto path = GetRelativePathFromNcaID(id, false, true, false);
@@ -706,11 +744,20 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
const auto deleted_html = delete_nca(html_id);
const auto deleted_legal = delete_nca(legal_id);
- return deleted_meta && (deleted_meta || deleted_program || deleted_data ||
- deleted_control || deleted_html || deleted_legal);
+ removed_data |= (deleted_meta || deleted_program || deleted_data || deleted_control ||
+ deleted_html || deleted_legal);
}
- return false;
+ // If patch entries for any program exist in yuzu meta, remove them
+ for (u8 i = 0; i < 0x10; i++) {
+ const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta");
+ const auto filename = GetCNMTName(TitleType::Update, title_id + i);
+ if (meta_dir->GetFile(filename)) {
+ removed_data |= meta_dir->DeleteFile(filename);
+ }
+ }
+
+ return removed_data;
}
InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
@@ -964,7 +1011,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord
const auto res = GetEntryRaw(title_id, type);
if (res == nullptr)
return nullptr;
- return std::make_unique<NCA>(res, nullptr, 0);
+ return std::make_unique<NCA>(res);
}
std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index bd7f53eaf..64815a845 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -24,6 +24,7 @@ enum class NCAContentType : u8;
enum class TitleType : u8;
struct ContentRecord;
+struct CNMTHeader;
struct MetaRecord;
class RegisteredCache;
@@ -169,6 +170,10 @@ public:
InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
+ InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header,
+ const ContentRecord& base_record, bool overwrite_if_exists = false,
+ const VfsCopyFunction& copy = &VfsRawCopy);
+
// Removes an existing entry based on title id
bool RemoveExistingEntry(u64 title_id) const;
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index ae7a3511b..1bc07dae5 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -26,60 +26,52 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi
}
updatable = app_loader.IsRomFSUpdatable();
- ivfc_offset = app_loader.ReadRomFSIVFCOffset();
}
RomFSFactory::~RomFSFactory() = default;
void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) {
- update_raw = std::move(update_raw_file);
+ packed_update_raw = std::move(update_raw_file);
}
-ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
+VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
if (!updatable) {
return file;
}
+ const auto type = ContentRecordType::Program;
+ const auto nca = content_provider.GetEntry(current_process_title_id, type);
const PatchManager patch_manager{current_process_title_id, filesystem_controller,
content_provider};
- return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw);
+ return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw);
}
-ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
+VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
auto nca = content_provider.GetEntry(title_id, type);
if (nca == nullptr) {
- // TODO: Find the right error code to use here
- return ResultUnknown;
+ return nullptr;
}
const PatchManager patch_manager{title_id, filesystem_controller, content_provider};
- return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type);
+ return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type);
}
-ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFSWithProgramIndex(
- u64 title_id, u8 program_index, ContentRecordType type) const {
+VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
+ ContentRecordType type) const {
const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index);
return OpenPatchedRomFS(res_title_id, type);
}
-ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
- ContentRecordType type) const {
+VirtualFile RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) const {
const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type);
if (res == nullptr) {
- // TODO(DarkLordZach): Find the right error code to use here
- return ResultUnknown;
- }
-
- const auto romfs = res->GetRomFS();
- if (romfs == nullptr) {
- // TODO(DarkLordZach): Find the right error code to use here
- return ResultUnknown;
+ return nullptr;
}
- return romfs;
+ return res->GetRomFS();
}
std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage,
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 14936031f..e4809bc94 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -40,23 +40,22 @@ public:
Service::FileSystem::FileSystemController& controller);
~RomFSFactory();
- void SetPackedUpdate(VirtualFile update_raw_file);
- [[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
- [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFS(u64 title_id,
- ContentRecordType type) const;
- [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFSWithProgramIndex(
- u64 title_id, u8 program_index, ContentRecordType type) const;
- [[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage,
- ContentRecordType type) const;
-
-private:
+ void SetPackedUpdate(VirtualFile packed_update_raw);
+ [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const;
+ [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const;
+ [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
+ ContentRecordType type) const;
+ [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const;
[[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage,
ContentRecordType type) const;
+private:
VirtualFile file;
- VirtualFile update_raw;
+ VirtualFile packed_update_raw;
+
+ VirtualFile base;
+
bool updatable;
- u64 ivfc_offset;
ContentProvider& content_provider;
Service::FileSystem::FileSystemController& filesystem_controller;
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 70b36f170..a4d060007 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -108,26 +108,16 @@ SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_director
SaveDataFactory::~SaveDataFactory() = default;
-ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
- const SaveDataAttribute& meta) const {
+VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
PrintSaveDataAttributeWarnings(meta);
const auto save_directory =
GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
- auto out = dir->CreateDirectoryRelative(save_directory);
-
- // Return an error if the save data doesn't actually exist.
- if (out == nullptr) {
- // TODO(DarkLordZach): Find out correct error code.
- return ResultUnknown;
- }
-
- return out;
+ return dir->CreateDirectoryRelative(save_directory);
}
-ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
- const SaveDataAttribute& meta) const {
+VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
const auto save_directory =
GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -138,12 +128,6 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
return Create(space, meta);
}
- // Return an error if the save data doesn't actually exist.
- if (out == nullptr) {
- // TODO(Subv): Find out correct error code.
- return ResultUnknown;
- }
-
return out;
}
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index d3633ef03..45c7c81fb 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -89,8 +89,8 @@ public:
explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_);
~SaveDataFactory();
- ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
- ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
+ VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
+ VirtualDir Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index 1df022c9e..d5158cd64 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -23,7 +23,7 @@ SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_)
SDMCFactory::~SDMCFactory() = default;
-ResultVal<VirtualDir> SDMCFactory::Open() const {
+VirtualDir SDMCFactory::Open() const {
return sd_dir;
}
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 3aebfb25e..a445fdb16 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -18,7 +18,7 @@ public:
explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_);
~SDMCFactory();
- ResultVal<VirtualDir> Open() const;
+ VirtualDir Open() const;
VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
VirtualDir GetSDMCContentDirectory() const;
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
return nullptr;
}
-std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const {
- if (extracted)
- LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
- std::vector<Core::Crypto::Key128> out;
- for (const auto& ticket_file : ticket_files) {
- if (ticket_file == nullptr ||
- ticket_file->GetSize() <
- Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
- continue;
- }
-
- out.emplace_back();
- ticket_file->Read(out.back().data(), out.back().size(),
- Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
- }
- return out;
-}
-
std::vector<VirtualFile> NSP::GetFiles() const {
return pfs->GetFiles();
}
@@ -208,22 +190,11 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) {
continue;
}
- if (ticket_file->GetSize() <
- Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
+ auto ticket = Core::Crypto::Ticket::Read(ticket_file);
+ if (!keys.AddTicket(ticket)) {
+ LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName());
continue;
}
-
- Core::Crypto::Key128 key{};
- ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
-
- // We get the name without the extension in order to create the rights ID.
- std::string name_only(ticket_file->GetName());
- name_only.erase(name_only.size() - 4);
-
- const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
- u128 rights_id;
- std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
- keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
@@ -249,7 +220,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
}
const auto nca = std::make_shared<NCA>(outer_file);
- if (nca->GetStatus() != Loader::ResultStatus::Success) {
+ if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) {
program_status[nca->GetTitleId()] = nca->GetStatus();
continue;
}
@@ -280,7 +251,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
continue;
}
- auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0);
+ auto next_nca = std::make_shared<NCA>(std::move(next_file));
if (next_nca->GetType() == NCAContentType::Program) {
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:
TitleType title_type = TitleType::Application) const;
VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
TitleType title_type = TitleType::Application) const;
- std::vector<Core::Crypto::Key128> GetTitlekey() const;
std::vector<VirtualFile> GetFiles() const override;
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 @@
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/frontend/applets/controller.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -62,7 +64,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac
controller->Connect(true);
}
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
- !Settings::values.use_docked_mode.GetValue()) {
+ !Settings::IsDockedMode()) {
// We should *never* reach here under any normal circumstances.
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
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 @@
#include "common/assert.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/frontend/framebuffer_layout.h"
namespace Layout {
@@ -49,7 +50,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
}
FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
+ const bool is_docked = Settings::IsDockedMode();
const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width;
const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height;
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 {
return NpadIdType::Player1;
}
+void HIDCore::SetLastActiveController(NpadIdType npad_id) {
+ last_active_controller = npad_id;
+}
+
+NpadIdType HIDCore::GetLastActiveController() const {
+ return last_active_controller;
+}
+
void HIDCore::EnableAllControllerConfiguration() {
player_1->EnableConfiguration();
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:
/// Returns the first disconnected npad id
NpadIdType GetFirstDisconnectedNpadId() const;
+ /// Sets the npad id of the last active controller
+ void SetLastActiveController(NpadIdType npad_id);
+
+ /// Returns the npad id of the last controller that pushed a button
+ NpadIdType GetLastActiveController() const;
+
/// Sets all emulated controllers into configuring mode.
void EnableAllControllerConfiguration();
@@ -77,6 +83,7 @@ private:
std::unique_ptr<EmulatedConsole> console;
std::unique_ptr<EmulatedDevices> devices;
NpadStyleTag supported_style_tag{NpadStyleSet::All};
+ NpadIdType last_active_controller{NpadIdType::Handheld};
};
} // 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 {
Tight = 2,
};
+// This is nn::settings::system::TouchScreenMode
+enum class TouchScreenMode : u32 {
+ Stylus = 0,
+ Standard = 1,
+};
+
+// This is nn::hid::TouchScreenModeForNx
+enum class TouchScreenModeForNx : u8 {
+ UseSystemSetting,
+ Finger,
+ Heat2,
+};
+
// This is nn::hid::NpadStyleTag
struct NpadStyleTag {
union {
@@ -334,6 +347,14 @@ struct TouchState {
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
+// This is nn::hid::TouchScreenConfigurationForNx
+struct TouchScreenConfigurationForNx {
+ TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
+ INSERT_PADDING_BYTES(0xF);
+};
+static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10,
+ "TouchScreenConfigurationForNx is an invalid size");
+
struct NpadColor {
u8 r{};
u8 g{};
@@ -662,6 +683,11 @@ struct MouseState {
};
static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
+struct UniquePadId {
+ u64 id;
+};
+static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size");
+
/// Converts a NpadIdType to an array index.
constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
switch (npad_id_type) {
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
index 49bdc671e..4cfdf4558 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
@@ -35,13 +35,27 @@ namespace {
using namespace Common::Literals;
u32 GetMemorySizeForInit() {
- return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemorySize_8GB
- : Smc::MemorySize_4GB;
+ switch (Settings::values.memory_layout_mode.GetValue()) {
+ case Settings::MemoryLayout::Memory_4Gb:
+ return Smc::MemorySize_4GB;
+ case Settings::MemoryLayout::Memory_6Gb:
+ return Smc::MemorySize_6GB;
+ case Settings::MemoryLayout::Memory_8Gb:
+ return Smc::MemorySize_8GB;
+ }
+ return Smc::MemorySize_4GB;
}
Smc::MemoryArrangement GetMemoryArrangeForInit() {
- return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemoryArrangement_8GB
- : Smc::MemoryArrangement_4GB;
+ switch (Settings::values.memory_layout_mode.GetValue()) {
+ case Settings::MemoryLayout::Memory_4Gb:
+ return Smc::MemoryArrangement_4GB;
+ case Settings::MemoryLayout::Memory_6Gb:
+ return Smc::MemoryArrangement_6GB;
+ case Settings::MemoryLayout::Memory_8Gb:
+ return Smc::MemoryArrangement_8GB;
+ }
+ return Smc::MemoryArrangement_4GB;
}
} // namespace
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) {
const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize;
const size_t num_pages = 1;
const size_t size = num_pages * PageSize;
- R_UNLESS(num_pages != 0, ResultInvalidSize);
R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress);
R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress);
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:
void Initialize();
void Finalize();
- s64 GetCount() const {
- return GetTick();
- }
-
- void RegisterTask(KTimerTask* task, s64 time_from_now) {
- this->RegisterAbsoluteTask(task, GetTick() + time_from_now);
- }
+ s64 GetTick() const;
void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) {
KScopedDisableDispatch dd{m_kernel};
@@ -42,7 +36,6 @@ private:
void EnableInterrupt(s64 wakeup_time);
void DisableInterrupt();
bool GetInterruptEnabled();
- s64 GetTick() const;
void DoTask();
private:
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 02b5cada4..9bfc85b34 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -768,7 +768,7 @@ Result KPageTable::UnmapProcessMemory(KProcessAddress dst_addr, size_t size,
m_memory_block_slab_manager, num_allocator_blocks);
R_TRY(allocator_result);
- CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
+ R_TRY(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
// Apply the memory block update.
m_memory_block_manager.Update(std::addressof(allocator), dst_addr, num_pages,
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 44c7cb22f..4a099286b 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -38,7 +38,7 @@ namespace {
*/
void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority,
KProcessAddress stack_top) {
- const KProcessAddress entry_point = owner_process.GetPageTable().GetCodeRegionStart();
+ const KProcessAddress entry_point = owner_process.GetEntryPoint();
ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1));
KThread* thread = KThread::Create(system.Kernel());
@@ -81,7 +81,8 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
process->m_capabilities.InitializeForMetadatalessProcess();
process->m_is_initialized = true;
- std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr)));
+ std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
+ : static_cast<u32>(std::time(nullptr)));
std::uniform_int_distribution<u64> distribution;
std::generate(process->m_random_entropy.begin(), process->m_random_entropy.end(),
[&] { return distribution(rng); });
@@ -95,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
process->m_is_suspended = false;
process->m_schedule_count = 0;
process->m_is_handle_table_initialized = false;
+ process->m_is_hbl = false;
// Open a reference to the resource limit.
process->m_resource_limit->Open();
@@ -350,12 +352,29 @@ Result KProcess::SetActivity(ProcessActivity activity) {
R_SUCCEED();
}
-Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) {
+Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
+ bool is_hbl) {
m_program_id = metadata.GetTitleID();
m_ideal_core = metadata.GetMainThreadCore();
m_is_64bit_process = metadata.Is64BitProgram();
m_system_resource_size = metadata.GetSystemResourceSize();
m_image_size = code_size;
+ m_is_hbl = is_hbl;
+
+ if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
+ // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
+ // However, some (buggy) programs/libraries like skyline incorrectly depend on the
+ // existence of ASLR pages before the entry point, so we will adjust the load address
+ // to point to about 2GiB into the ASLR region.
+ m_code_address = 0x8000'0000;
+ } else {
+ // All other processes can be mapped at the beginning of the code region.
+ if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) {
+ m_code_address = 0x800'0000;
+ } else {
+ m_code_address = 0x20'0000;
+ }
+ }
KScopedResourceReservation memory_reservation(
m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size);
@@ -367,15 +386,15 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
// Initialize process address space
if (const Result result{m_page_table.InitializeForProcess(
metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application,
- 0x8000000, code_size, std::addressof(m_kernel.GetAppSystemResource()), m_resource_limit,
- m_kernel.System().ApplicationMemory())};
+ this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()),
+ m_resource_limit, m_kernel.System().ApplicationMemory())};
result.IsError()) {
R_RETURN(result);
}
// Map process code region
- if (const Result result{m_page_table.MapProcessCode(m_page_table.GetCodeRegionStart(),
- code_size / PageSize, KMemoryState::Code,
+ if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize,
+ KMemoryState::Code,
KMemoryPermission::None)};
result.IsError()) {
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:
return m_program_id;
}
+ KProcessAddress GetEntryPoint() const {
+ return m_code_address;
+ }
+
/// Gets the resource limit descriptor for this process
KResourceLimit* GetResourceLimit() const;
@@ -334,7 +338,8 @@ public:
* @returns ResultSuccess if all relevant metadata was able to be
* loaded and parsed. Otherwise, an error code is returned.
*/
- Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size);
+ Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
+ bool is_hbl);
/**
* Starts the main application thread for this process.
@@ -364,6 +369,10 @@ public:
return GetProcessId();
}
+ bool IsHbl() const {
+ return m_is_hbl;
+ }
+
bool IsSignaled() const override;
void DoWorkerTaskImpl();
@@ -485,6 +494,9 @@ private:
/// Address indicating the location of the process' dedicated TLS region.
KProcessAddress m_plr_address = 0;
+ /// Address indicating the location of the process's entry point.
+ KProcessAddress m_code_address = 0;
+
/// Random values for svcGetInfo RandomEntropy
std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{};
@@ -518,6 +530,7 @@ private:
bool m_is_immortal{};
bool m_is_handle_table_initialized{};
bool m_is_initialized{};
+ bool m_is_hbl{};
std::atomic<u16> m_num_running_threads{};
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 @@
#include "common/overflow.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/svc_results.h"
@@ -15,9 +16,7 @@ KResourceLimit::KResourceLimit(KernelCore& kernel)
: KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {}
KResourceLimit::~KResourceLimit() = default;
-void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) {
- m_core_timing = core_timing;
-}
+void KResourceLimit::Initialize() {}
void KResourceLimit::Finalize() {}
@@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
}
bool KResourceLimit::Reserve(LimitableResource which, s64 value) {
- return Reserve(which, value, m_core_timing->GetGlobalTimeNs().count() + DefaultTimeout);
+ return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout);
}
bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) {
@@ -117,7 +116,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) {
}
if (m_current_hints[index] + value <= m_limit_values[index] &&
- (timeout < 0 || m_core_timing->GetGlobalTimeNs().count() < timeout)) {
+ (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) {
m_waiter_count++;
m_cond_var.Wait(std::addressof(m_lock), timeout, false);
m_waiter_count--;
@@ -154,7 +153,7 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) {
KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) {
auto* resource_limit = KResourceLimit::Create(system.Kernel());
- resource_limit->Initialize(std::addressof(system.CoreTiming()));
+ resource_limit->Initialize();
// Initialize default resource limit values.
// 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:
explicit KResourceLimit(KernelCore& kernel);
~KResourceLimit() override;
- void Initialize(const Core::Timing::CoreTiming* core_timing);
+ void Initialize();
void Finalize() override;
s64 GetLimitValue(LimitableResource which) const;
@@ -57,7 +57,6 @@ private:
mutable KLightLock m_lock;
s32 m_waiter_count{};
KLightConditionVariable m_cond_var;
- const Core::Timing::CoreTiming* m_core_timing{};
};
KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size);
diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
index c485022f5..b62415da7 100644
--- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
+++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
@@ -28,7 +28,7 @@ public:
~KScopedSchedulerLockAndSleep() {
// Register the sleep.
if (m_timeout_tick > 0) {
- m_timer->RegisterTask(m_thread, m_timeout_tick);
+ m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick);
}
// 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 {
void InitializeSystemResourceLimit(KernelCore& kernel,
const Core::Timing::CoreTiming& core_timing) {
system_resource_limit = KResourceLimit::Create(system.Kernel());
- system_resource_limit->Initialize(&core_timing);
+ system_resource_limit->Initialize();
KResourceLimit::Register(kernel, system_resource_limit);
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 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/kernel.h"
@@ -52,7 +53,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ
if (timeout_ns > 0) {
const s64 offset_tick(timeout_ns);
if (offset_tick > 0) {
- timeout = offset_tick + 2;
+ timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2;
if (timeout <= 0) {
timeout = std::numeric_limits<s64>::max();
}
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 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/kernel.h"
@@ -25,7 +26,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u
if (timeout_ns > 0) {
const s64 offset_tick(timeout_ns);
if (offset_tick > 0) {
- timeout = offset_tick + 2;
+ timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2;
if (timeout <= 0) {
timeout = std::numeric_limits<s64>::max();
}
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) {
std::string str(len, '\0');
GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size());
- LOG_DEBUG(Debug_Emulated, "{}", str);
+ LOG_INFO(Debug_Emulated, "{}", str);
R_SUCCEED();
}
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 @@
#include "core/core.h"
#include "core/debugger/debugger.h"
+#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_types.h"
@@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
}
- if (system.DebuggerEnabled()) {
+ const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl();
+ const bool should_break = is_hbl || !notification_only;
+
+ if (system.DebuggerEnabled() && should_break) {
auto* thread = system.Kernel().GetCurrentEmuThread();
system.GetDebugger().NotifyThreadStopped(thread);
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 @@
#include "common/scratch_buffer.h"
#include "core/core.h"
#include "core/hle/kernel/k_client_session.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/svc.h"
@@ -82,12 +83,29 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad
R_TRY(session->SendReply());
}
+ // Convert the timeout from nanoseconds to ticks.
+ // NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
+ s64 timeout;
+ if (timeout_ns > 0) {
+ const s64 offset_tick(timeout_ns);
+ if (offset_tick > 0) {
+ timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2;
+ if (timeout <= 0) {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = timeout_ns;
+ }
+
// Wait for a message.
while (true) {
// Wait for an object.
s32 index;
Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(),
- num_handles, timeout_ns);
+ num_handles, timeout);
if (result == ResultTimedOut) {
R_RETURN(result);
}
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) {
SCOPE_EXIT({ resource_limit->Close(); });
// Initialize the resource limit.
- resource_limit->Initialize(std::addressof(system.CoreTiming()));
+ resource_limit->Initialize();
// Register the limit.
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 @@
#include "common/scope_exit.h"
#include "common/scratch_buffer.h"
#include "core/core.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/svc.h"
@@ -83,9 +84,20 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha
}
});
+ // Convert the timeout from nanoseconds to ticks.
+ s64 timeout;
+ if (timeout_ns > 0) {
+ u64 ticks = kernel.HardwareTimer().GetTick();
+ ticks += timeout_ns;
+ ticks += 2;
+
+ timeout = ticks;
+ } else {
+ timeout = timeout_ns;
+ }
+
// Wait on the objects.
- Result res =
- KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns);
+ Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout);
R_SUCCEED_IF(res == ResultSessionClosed);
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 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_thread.h"
@@ -42,9 +43,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u
R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority);
// Reserve a new thread from the process resource limit (waiting up to 100ms).
- KScopedResourceReservation thread_reservation(
- std::addressof(process), LimitableResource::ThreadCountMax, 1,
- system.CoreTiming().GetGlobalTimeNs().count() + 100000000);
+ KScopedResourceReservation thread_reservation(std::addressof(process),
+ LimitableResource::ThreadCountMax, 1,
+ kernel.HardwareTimer().GetTick() + 100000000);
R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached);
// Create the thread.
@@ -102,20 +103,31 @@ void ExitThread(Core::System& system) {
}
/// Sleep the current thread
-void SleepThread(Core::System& system, s64 nanoseconds) {
+void SleepThread(Core::System& system, s64 ns) {
auto& kernel = system.Kernel();
- const auto yield_type = static_cast<Svc::YieldType>(nanoseconds);
+ const auto yield_type = static_cast<Svc::YieldType>(ns);
- LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
+ LOG_TRACE(Kernel_SVC, "called nanoseconds={}", ns);
// When the input tick is positive, sleep.
- if (nanoseconds > 0) {
+ if (ns > 0) {
// Convert the timeout from nanoseconds to ticks.
// NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
+ s64 timeout;
+
+ const s64 offset_tick(ns);
+ if (offset_tick > 0) {
+ timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2;
+ if (timeout <= 0) {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = std::numeric_limits<s64>::max();
+ }
// Sleep.
// NOTE: Nintendo does not check the result of this sleep.
- static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds));
+ static_cast<void>(GetCurrentThread(kernel).Sleep(timeout));
} else if (yield_type == Svc::YieldType::WithoutCoreMigration) {
KScheduler::YieldWithoutCoreMigration(kernel);
} else if (yield_type == Svc::YieldType::WithCoreMigration) {
@@ -124,7 +136,6 @@ void SleepThread(Core::System& system, s64 nanoseconds) {
KScheduler::YieldToAnyThread(kernel);
} else {
// Nintendo does nothing at all if an otherwise invalid value is passed.
- ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
}
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 240f06689..dd0b27f47 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -62,7 +62,7 @@ enum class ErrorModule : u32 {
XCD = 108,
TMP451 = 109,
NIFM = 110,
- Hwopus = 111,
+ HwOpus = 111,
LSM6DS3 = 112,
Bluetooth = 113,
VI = 114,
@@ -283,159 +283,6 @@ private:
u32 description_end;
};
-/**
- * This is an optional value type. It holds a `Result` and, if that code is ResultSuccess, it
- * also holds a result of type `T`. If the code is an error code (not ResultSuccess), then trying
- * to access the inner value with operator* is undefined behavior and will assert with Unwrap().
- * Users of this class must be cognizant to check the status of the ResultVal with operator bool(),
- * Code(), Succeeded() or Failed() prior to accessing the inner value.
- *
- * An example of how it could be used:
- * \code
- * ResultVal<int> Frobnicate(float strength) {
- * if (strength < 0.f || strength > 1.0f) {
- * // Can't frobnicate too weakly or too strongly
- * return Result{ErrorModule::Common, 1};
- * } else {
- * // Frobnicated! Give caller a cookie
- * return 42;
- * }
- * }
- * \endcode
- *
- * \code
- * auto frob_result = Frobnicate(0.75f);
- * if (frob_result) {
- * // Frobbed ok
- * printf("My cookie is %d\n", *frob_result);
- * } else {
- * printf("Guess I overdid it. :( Error code: %ux\n", frob_result.Code().raw);
- * }
- * \endcode
- */
-template <typename T>
-class ResultVal {
-public:
- constexpr ResultVal() : expected{} {}
-
- constexpr ResultVal(Result code) : expected{Common::Unexpected(code)} {}
-
- constexpr ResultVal(ResultRange range) : expected{Common::Unexpected(range)} {}
-
- template <typename U>
- constexpr ResultVal(U&& val) : expected{std::forward<U>(val)} {}
-
- template <typename... Args>
- constexpr ResultVal(Args&&... args) : expected{std::in_place, std::forward<Args>(args)...} {}
-
- ~ResultVal() = default;
-
- constexpr ResultVal(const ResultVal&) = default;
- constexpr ResultVal(ResultVal&&) = default;
-
- ResultVal& operator=(const ResultVal&) = default;
- ResultVal& operator=(ResultVal&&) = default;
-
- [[nodiscard]] constexpr explicit operator bool() const noexcept {
- return expected.has_value();
- }
-
- [[nodiscard]] constexpr Result Code() const {
- return expected.has_value() ? ResultSuccess : expected.error();
- }
-
- [[nodiscard]] constexpr bool Succeeded() const {
- return expected.has_value();
- }
-
- [[nodiscard]] constexpr bool Failed() const {
- return !expected.has_value();
- }
-
- [[nodiscard]] constexpr T* operator->() {
- return std::addressof(expected.value());
- }
-
- [[nodiscard]] constexpr const T* operator->() const {
- return std::addressof(expected.value());
- }
-
- [[nodiscard]] constexpr T& operator*() & {
- return *expected;
- }
-
- [[nodiscard]] constexpr const T& operator*() const& {
- return *expected;
- }
-
- [[nodiscard]] constexpr T&& operator*() && {
- return *expected;
- }
-
- [[nodiscard]] constexpr const T&& operator*() const&& {
- return *expected;
- }
-
- [[nodiscard]] constexpr T& Unwrap() & {
- ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
- return expected.value();
- }
-
- [[nodiscard]] constexpr const T& Unwrap() const& {
- ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
- return expected.value();
- }
-
- [[nodiscard]] constexpr T&& Unwrap() && {
- ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
- return std::move(expected.value());
- }
-
- [[nodiscard]] constexpr const T&& Unwrap() const&& {
- ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
- return std::move(expected.value());
- }
-
- template <typename U>
- [[nodiscard]] constexpr T ValueOr(U&& v) const& {
- return expected.value_or(v);
- }
-
- template <typename U>
- [[nodiscard]] constexpr T ValueOr(U&& v) && {
- return expected.value_or(v);
- }
-
-private:
- // TODO (Morph): Replace this with C++23 std::expected.
- Common::Expected<T, Result> expected;
-};
-
-/**
- * Check for the success of `source` (which must evaluate to a ResultVal). If it succeeds, unwraps
- * the contained value and assigns it to `target`, which can be either an l-value expression or a
- * variable declaration. If it fails the return code is returned from the current function. Thus it
- * can be used to cascade errors out, achieving something akin to exception handling.
- */
-#define CASCADE_RESULT(target, source) \
- auto CONCAT2(check_result_L, __LINE__) = source; \
- if (CONCAT2(check_result_L, __LINE__).Failed()) { \
- return CONCAT2(check_result_L, __LINE__).Code(); \
- } \
- target = std::move(*CONCAT2(check_result_L, __LINE__))
-
-/**
- * Analogous to CASCADE_RESULT, but for a bare Result. The code will be propagated if
- * non-success, or discarded otherwise.
- */
-#define CASCADE_CODE(source) \
- do { \
- auto CONCAT2(check_result_L, __LINE__) = source; \
- if (CONCAT2(check_result_L, __LINE__).IsError()) { \
- return CONCAT2(check_result_L, __LINE__); \
- } \
- } while (false)
-
#define R_SUCCEEDED(res) (static_cast<Result>(res).IsSuccess())
#define R_FAILED(res) (static_cast<Result>(res).IsFailure())
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 2632cd3ef..b971401e6 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -765,15 +765,16 @@ Result Module::Interface::InitializeApplicationInfoBase() {
// TODO(ogniK): This should be changed to reflect the target process for when we have multiple
// processes emulated. As we don't actually have pid support we should assume we're just using
// our own process
- const auto launch_property =
- system.GetARPManager().GetLaunchProperty(system.GetApplicationProcessProgramID());
+ Glue::ApplicationLaunchProperty launch_property{};
+ const auto result = system.GetARPManager().GetLaunchProperty(
+ &launch_property, system.GetApplicationProcessProgramID());
- if (launch_property.Failed()) {
+ if (result != ResultSuccess) {
LOG_ERROR(Service_ACC, "Failed to get launch property");
return Account::ResultInvalidApplication;
}
- switch (launch_property->base_game_storage_id) {
+ switch (launch_property.base_game_storage_id) {
case FileSys::StorageId::GameCard:
application_info.application_type = ApplicationType::GameCard;
break;
@@ -785,7 +786,7 @@ Result Module::Interface::InitializeApplicationInfoBase() {
break;
default:
LOG_ERROR(Service_ACC, "Invalid game storage ID! storage_id={}",
- launch_property->base_game_storage_id);
+ launch_property.base_game_storage_id);
return Account::ResultInvalidApplication;
}
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 4f400d341..8ffdd19e7 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -6,6 +6,7 @@
#include <cinttypes>
#include <cstring>
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -45,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3};
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
enum class LaunchParameterKind : u32 {
- ApplicationSpecific = 1,
+ UserChannel = 1,
AccountPreselectedUser = 2,
};
@@ -340,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) {
void ISelfController::LockExit(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- system.SetExitLock(true);
+ system.SetExitLocked(true);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -349,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) {
void ISelfController::UnlockExit(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- system.SetExitLock(false);
+ system.SetExitLocked(false);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
+
+ if (system.GetExitRequested()) {
+ system.Exit();
+ }
}
void ISelfController::EnterFatalSection(HLERequestContext& ctx) {
@@ -833,7 +838,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight));
} else {
@@ -921,7 +926,7 @@ void IStorage::Open(HLERequestContext& ctx) {
}
void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) {
- const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()};
+ const bool use_docked_mode{Settings::IsDockedMode()};
LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
IPC::ResponseBuilder rb{ctx, 3};
@@ -1317,6 +1322,50 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
rb.PushIpcInterface<IStorage>(system, std::move(memory));
}
+ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
+ : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "PopInData"},
+ {1, nullptr, "PushOutData"},
+ {2, nullptr, "PopInteractiveInData"},
+ {3, nullptr, "PushInteractiveOutData"},
+ {5, nullptr, "GetPopInDataEvent"},
+ {6, nullptr, "GetPopInteractiveInDataEvent"},
+ {10, nullptr, "ExitProcessAndReturn"},
+ {11, nullptr, "GetLibraryAppletInfo"},
+ {12, nullptr, "GetMainAppletIdentityInfo"},
+ {13, nullptr, "CanUseApplicationCore"},
+ {14, nullptr, "GetCallerAppletIdentityInfo"},
+ {15, nullptr, "GetMainAppletApplicationControlProperty"},
+ {16, nullptr, "GetMainAppletStorageId"},
+ {17, nullptr, "GetCallerAppletIdentityInfoStack"},
+ {18, nullptr, "GetNextReturnDestinationAppletIdentityInfo"},
+ {19, nullptr, "GetDesirableKeyboardLayout"},
+ {20, nullptr, "PopExtraStorage"},
+ {25, nullptr, "GetPopExtraStorageEvent"},
+ {30, nullptr, "UnpopInData"},
+ {31, nullptr, "UnpopExtraStorage"},
+ {40, nullptr, "GetIndirectLayerProducerHandle"},
+ {50, nullptr, "ReportVisibleError"},
+ {51, nullptr, "ReportVisibleErrorWithErrorContext"},
+ {60, nullptr, "GetMainAppletApplicationDesiredLanguage"},
+ {70, nullptr, "GetCurrentApplicationId"},
+ {80, nullptr, "RequestExitToSelf"},
+ {90, nullptr, "CreateApplicationAndPushAndRequestToLaunch"},
+ {100, nullptr, "CreateGameMovieTrimmer"},
+ {101, nullptr, "ReserveResourceForMovieOperation"},
+ {102, nullptr, "UnreserveResourceForMovieOperation"},
+ {110, nullptr, "GetMainAppletAvailableUsers"},
+ {120, nullptr, "GetLaunchStorageInfoForDebug"},
+ {130, nullptr, "GetGpuErrorDetectedSystemEvent"},
+ {140, nullptr, "SetApplicationMemoryReservation"},
+ {150, nullptr, "ShouldSetGpuTimeSliceManually"},
+ };
+ RegisterHandlers(functions);
+}
+
+ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
+
IApplicationFunctions::IApplicationFunctions(Core::System& system_)
: ServiceFramework{system_, "IApplicationFunctions"}, service_context{system,
"IApplicationFunctions"} {
@@ -1337,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
{26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
{27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
- {28, nullptr, "GetSaveDataSizeMax"},
+ {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
{29, nullptr, "GetCacheStorageMax"},
{30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
{31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
@@ -1469,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto kind = rp.PopEnum<LaunchParameterKind>();
- LOG_DEBUG(Service_AM, "called, kind={:08X}", kind);
-
- if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
- const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) {
- return system.GetFileSystemController().GetBCATDirectory(tid);
- });
- const auto build_id_full = system.GetApplicationProcessBuildID();
- u64 build_id{};
- std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
-
- auto data =
- backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id});
- if (data.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IStorage>(system, std::move(*data));
- launch_popped_application_specific = true;
+ LOG_INFO(Service_AM, "called, kind={:08X}", kind);
+
+ if (kind == LaunchParameterKind::UserChannel) {
+ auto channel = system.GetUserChannel();
+ if (channel.empty()) {
+ LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(AM::ResultNoDataInChannel);
return;
}
+
+ auto data = channel.back();
+ channel.pop_back();
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IStorage>(system, std::move(data));
} else if (kind == LaunchParameterKind::AccountPreselectedUser &&
!launch_popped_account_preselect) {
+ // TODO: Verify this is hw-accurate
LaunchParameterAccountPreselectedUser params{};
params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
@@ -1501,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
params.current_user = *uuid;
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-
rb.Push(ResultSuccess);
std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
@@ -1509,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
rb.PushIpcInterface<IStorage>(system, std::move(buffer));
launch_popped_account_preselect = true;
- return;
+ } else {
+ LOG_ERROR(Service_AM, "Unknown launch parameter kind.");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(AM::ResultNoDataInChannel);
}
-
- LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(AM::ResultNoDataInChannel);
}
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) {
@@ -1534,11 +1580,13 @@ void IApplicationFunctions::EnsureSaveData(HLERequestContext& ctx) {
attribute.title_id = system.GetApplicationProcessProgramID();
attribute.user_id = user_id;
attribute.type = FileSys::SaveDataType::SaveData;
+
+ FileSys::VirtualDir save_data{};
const auto res = system.GetFileSystemController().CreateSaveData(
- FileSys::SaveDataSpaceId::NandUser, attribute);
+ &save_data, FileSys::SaveDataSpaceId::NandUser, attribute);
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(res.Code());
+ rb.Push(res);
rb.Push<u64>(0);
}
@@ -1623,26 +1671,30 @@ void IApplicationFunctions::GetDesiredLanguage(HLERequestContext& ctx) {
auto app_man = ns_am2->GetApplicationManagerInterface();
// Get desired application language
- const auto res_lang = app_man->GetApplicationDesiredLanguage(supported_languages);
- if (res_lang.Failed()) {
+ u8 desired_language{};
+ const auto res_lang =
+ app_man->GetApplicationDesiredLanguage(&desired_language, supported_languages);
+ if (res_lang != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res_lang.Code());
+ rb.Push(res_lang);
return;
}
// Convert to settings language code.
- const auto res_code = app_man->ConvertApplicationLanguageToLanguageCode(*res_lang);
- if (res_code.Failed()) {
+ u64 language_code{};
+ const auto res_code =
+ app_man->ConvertApplicationLanguageToLanguageCode(&language_code, desired_language);
+ if (res_code != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res_code.Code());
+ rb.Push(res_code);
return;
}
- LOG_DEBUG(Service_AM, "got desired_language={:016X}", *res_code);
+ LOG_DEBUG(Service_AM, "got desired_language={:016X}", language_code);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push(*res_code);
+ rb.Push(language_code);
}
void IApplicationFunctions::IsGamePlayRecordingSupported(HLERequestContext& ctx) {
@@ -1769,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
rb.PushRaw(resp);
}
+void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ constexpr u64 size_max_normal = 0xFFFFFFF;
+ constexpr u64 size_max_journal = 0xFFFFFFF;
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.Push(size_max_normal);
+ rb.Push(size_max_journal);
+}
+
void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -1800,14 +1864,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) {
}
void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called");
+
+ system.GetUserChannel().clear();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::RequestParser rp{ctx};
+ const auto storage = rp.PopIpcInterface<IStorage>().lock();
+ if (storage) {
+ system.GetUserChannel().push_back(storage->GetData());
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index d4fd163da..f86841c60 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -22,30 +22,6 @@ class Nvnflinger;
namespace Service::AM {
-// This is nn::settings::Language
-enum SystemLanguage {
- Japanese = 0,
- English = 1, // en-US
- French = 2,
- German = 3,
- Italian = 4,
- Spanish = 5,
- Chinese = 6,
- Korean = 7,
- Dutch = 8,
- Portuguese = 9,
- Russian = 10,
- Taiwanese = 11,
- BritishEnglish = 12, // en-GB
- CanadianFrench = 13,
- LatinAmericanSpanish = 14, // es-419
- // 4.0.0+
- SimplifiedChinese = 15,
- TraditionalChinese = 16,
- // 10.1.0+
- BrazilianPortuguese = 17,
-};
-
class AppletMessageQueue {
public:
// This is nn::am::AppletMessage
@@ -314,6 +290,12 @@ private:
void CreateHandleStorage(HLERequestContext& ctx);
};
+class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletSelfAccessor> {
+public:
+ explicit ILibraryAppletSelfAccessor(Core::System& system_);
+ ~ILibraryAppletSelfAccessor() override;
+};
+
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
public:
explicit IApplicationFunctions(Core::System& system_);
@@ -334,6 +316,7 @@ private:
void ExtendSaveData(HLERequestContext& ctx);
void GetSaveDataSize(HLERequestContext& ctx);
void CreateCacheStorage(HLERequestContext& ctx);
+ void GetSaveDataSizeMax(HLERequestContext& ctx);
void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
void BeginBlockingHomeButton(HLERequestContext& ctx);
@@ -357,7 +340,6 @@ private:
KernelHelpers::ServiceContext service_context;
- bool launch_popped_application_specific = false;
bool launch_popped_account_preselect = false;
s32 previous_program_index{-1};
Kernel::KEvent* gpu_error_detected_event;
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index 2764f7ceb..ee9d99a54 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -26,8 +26,10 @@ public:
{4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"},
{10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
- {20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"},
+ {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
{21, nullptr, "GetAppletCommonFunctions"},
+ {22, nullptr, "GetHomeMenuFunctions"},
+ {23, nullptr, "GetGlobalStateController"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
@@ -100,12 +102,12 @@ private:
rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
- void GetApplicationFunctions(HLERequestContext& ctx) {
+ void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IApplicationFunctions>(system);
+ rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system);
}
Nvnflinger::Nvnflinger& nvnflinger;
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..ff77830d2 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.cpp
+++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp
@@ -7,7 +7,9 @@
#include "core/frontend/applets/mii_edit.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_mii_edit.h"
+#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/sm/sm.h"
namespace Service::AM::Applets {
@@ -56,6 +58,12 @@ void MiiEdit::Initialize() {
sizeof(MiiEditAppletInputV4));
break;
}
+
+ manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager();
+ if (manager == nullptr) {
+ manager = std::make_shared<Mii::MiiManager>();
+ }
+ manager->Initialize(metadata);
}
bool MiiEdit::TransactionComplete() const {
@@ -78,22 +86,49 @@ void MiiEdit::Execute() {
// This is a default stub for each of the MiiEdit applet modes.
switch (applet_input_common.applet_mode) {
case MiiEditAppletMode::ShowMiiEdit:
- case MiiEditAppletMode::AppendMii:
case MiiEditAppletMode::AppendMiiImage:
case MiiEditAppletMode::UpdateMiiImage:
MiiEditOutput(MiiEditResult::Success, 0);
break;
- case MiiEditAppletMode::CreateMii:
- case MiiEditAppletMode::EditMii: {
- Service::Mii::MiiManager mii_manager;
+ case MiiEditAppletMode::AppendMii: {
+ Mii::StoreData store_data{};
+ store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
+ store_data.SetNickname({u'y', u'u', u'z', u'u'});
+ store_data.SetChecksum();
+ const auto result = manager->AddOrReplace(metadata, store_data);
+
+ if (result.IsError()) {
+ MiiEditOutput(MiiEditResult::Cancel, 0);
+ break;
+ }
+
+ s32 index = manager->FindIndex(store_data.GetCreateId(), false);
+
+ if (index == -1) {
+ MiiEditOutput(MiiEditResult::Cancel, 0);
+ break;
+ }
+
+ MiiEditOutput(MiiEditResult::Success, index);
+ break;
+ }
+ case MiiEditAppletMode::CreateMii: {
+ Mii::CharInfo char_info{};
+ manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All);
- const MiiEditCharInfo char_info{
- .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
- ? applet_input_v4.char_info.mii_info
- : mii_manager.BuildDefault(0)},
+ const MiiEditCharInfo edit_char_info{
+ .mii_info{char_info},
+ };
+
+ MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
+ break;
+ }
+ case MiiEditAppletMode::EditMii: {
+ const MiiEditCharInfo edit_char_info{
+ .mii_info{applet_input_v4.char_info.mii_info},
};
- MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info);
+ MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
break;
}
default:
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h
index 3f46fae1b..7ff34af49 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.h
+++ b/src/core/hle/service/am/applets/applet_mii_edit.h
@@ -11,6 +11,11 @@ namespace Core {
class System;
} // namespace Core
+namespace Service::Mii {
+struct DatabaseSessionMetadata;
+class MiiManager;
+} // namespace Service::Mii
+
namespace Service::AM::Applets {
class MiiEdit final : public Applet {
@@ -40,6 +45,8 @@ private:
MiiEditAppletInputV4 applet_input_v4{};
bool is_complete{false};
+ std::shared_ptr<Mii::MiiManager> manager = nullptr;
+ Mii::DatabaseSessionMetadata metadata{};
};
} // namespace Service::AM::Applets
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 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "core/hle/service/mii/types.h"
+#include "common/uuid.h"
+#include "core/hle/service/mii/types/char_info.h"
namespace Service::AM::Applets {
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,
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
- return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type);
+ return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type);
}
}
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 38c2138e8..7075ab800 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -22,6 +22,8 @@
namespace Service::AOC {
+constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400};
+
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return FileSys::GetBaseTitleID(title_id) == base;
}
@@ -54,8 +56,8 @@ public:
{0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
{1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
{2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
- {3, nullptr, "PopPurchasedProductInfo"},
- {4, nullptr, "PopPurchasedProductInfoWithUid"},
+ {3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"},
+ {4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"},
};
// clang-format on
@@ -101,6 +103,20 @@ private:
rb.PushCopyObjects(purchased_event->GetReadableEvent());
}
+ void PopPurchasedProductInfo(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AOC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultNoPurchasedProductInfoAvailable);
+ }
+
+ void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AOC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultNoPurchasedProductInfoAvailable);
+ }
+
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* purchased_event;
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 @@
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core_timing.h"
#include "core/hle/service/apm/apm_controller.h"
@@ -67,8 +68,7 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
}
PerformanceMode Controller::GetCurrentPerformanceMode() const {
- return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost
- : PerformanceMode::Normal;
+ return Settings::IsDockedMode() ? PerformanceMode::Boost : PerformanceMode::Normal;
}
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp
index 7ad93be6b..66dd64fd1 100644
--- a/src/core/hle/service/audio/audctl.cpp
+++ b/src/core/hle/service/audio/audctl.cpp
@@ -22,13 +22,13 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} {
{9, nullptr, "GetAudioOutputMode"},
{10, nullptr, "SetAudioOutputMode"},
{11, nullptr, "SetForceMutePolicy"},
- {12, nullptr, "GetForceMutePolicy"},
- {13, nullptr, "GetOutputModeSetting"},
+ {12, &AudCtl::GetForceMutePolicy, "GetForceMutePolicy"},
+ {13, &AudCtl::GetOutputModeSetting, "GetOutputModeSetting"},
{14, nullptr, "SetOutputModeSetting"},
{15, nullptr, "SetOutputTarget"},
{16, nullptr, "SetInputTargetForceEnabled"},
{17, nullptr, "SetHeadphoneOutputLevelMode"},
- {18, nullptr, "GetHeadphoneOutputLevelMode"},
+ {18, &AudCtl::GetHeadphoneOutputLevelMode, "GetHeadphoneOutputLevelMode"},
{19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"},
{20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"},
{21, nullptr, "GetAudioOutputTargetForPlayReport"},
@@ -41,7 +41,7 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} {
{28, nullptr, "GetAudioOutputChannelCountForPlayReport"},
{29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
{30, nullptr, "SetSpeakerAutoMuteEnabled"},
- {31, nullptr, "IsSpeakerAutoMuteEnabled"},
+ {31, &AudCtl::IsSpeakerAutoMuteEnabled, "IsSpeakerAutoMuteEnabled"},
{32, nullptr, "GetActiveOutputTarget"},
{33, nullptr, "GetTargetDeviceInfo"},
{34, nullptr, "AcquireTargetNotification"},
@@ -96,4 +96,42 @@ void AudCtl::GetTargetVolumeMax(HLERequestContext& ctx) {
rb.Push(target_max_volume);
}
+void AudCtl::GetForceMutePolicy(HLERequestContext& ctx) {
+ LOG_WARNING(Audio, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(ForceMutePolicy::Disable);
+}
+
+void AudCtl::GetOutputModeSetting(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto value = rp.Pop<u32>();
+
+ LOG_WARNING(Audio, "(STUBBED) called, value={}", value);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(AudioOutputMode::PcmAuto);
+}
+
+void AudCtl::GetHeadphoneOutputLevelMode(HLERequestContext& ctx) {
+ LOG_WARNING(Audio, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(HeadphoneOutputLevelMode::Normal);
+}
+
+void AudCtl::IsSpeakerAutoMuteEnabled(HLERequestContext& ctx) {
+ const bool is_speaker_auto_mute_enabled = false;
+
+ LOG_WARNING(Audio, "(STUBBED) called, is_speaker_auto_mute_enabled={}",
+ is_speaker_auto_mute_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_speaker_auto_mute_enabled);
+}
+
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h
index 8e31ac237..d57abb383 100644
--- a/src/core/hle/service/audio/audctl.h
+++ b/src/core/hle/service/audio/audctl.h
@@ -17,8 +17,30 @@ public:
~AudCtl() override;
private:
+ enum class AudioOutputMode {
+ Invalid,
+ Pcm1ch,
+ Pcm2ch,
+ Pcm6ch,
+ PcmAuto,
+ };
+
+ enum class ForceMutePolicy {
+ Disable,
+ SpeakerMuteOnHeadphoneUnplugged,
+ };
+
+ enum class HeadphoneOutputLevelMode {
+ Normal,
+ HighPower,
+ };
+
void GetTargetVolumeMin(HLERequestContext& ctx);
void GetTargetVolumeMax(HLERequestContext& ctx);
+ void GetForceMutePolicy(HLERequestContext& ctx);
+ void GetOutputModeSetting(HLERequestContext& ctx);
+ void GetHeadphoneOutputLevelMode(HLERequestContext& ctx);
+ void IsSpeakerAutoMuteEnabled(HLERequestContext& ctx);
};
} // namespace Service::Audio
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_)
AudInU::~AudInU() = default;
void AudInU::ListAudioIns(HLERequestContext& ctx) {
- using namespace AudioCore::AudioRenderer;
+ using namespace AudioCore::Renderer;
LOG_DEBUG(Service_Audio, "called");
@@ -240,7 +240,7 @@ void AudInU::ListAudioIns(HLERequestContext& ctx) {
}
void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) {
- using namespace AudioCore::AudioRenderer;
+ using namespace AudioCore::Renderer;
LOG_DEBUG(Service_Audio, "called");
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_)
AudOutU::~AudOutU() = default;
void AudOutU::ListAudioOuts(HLERequestContext& ctx) {
- using namespace AudioCore::AudioRenderer;
+ using namespace AudioCore::Renderer;
std::scoped_lock l{impl->mutex};
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 003870176..2f09cade5 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -26,7 +26,7 @@
#include "core/hle/service/ipc_helpers.h"
#include "core/memory.h"
-using namespace AudioCore::AudioRenderer;
+using namespace AudioCore::Renderer;
namespace Service::Audio {
@@ -441,10 +441,11 @@ void AudRenU::OpenAudioRenderer(HLERequestContext& ctx) {
AudioCore::AudioRendererParameterInternal params;
rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params);
- auto transfer_memory_handle = ctx.GetCopyHandle(0);
- auto process_handle = ctx.GetCopyHandle(1);
+ rp.Skip(1, false);
auto transfer_memory_size = rp.Pop<u64>();
auto applet_resource_user_id = rp.Pop<u64>();
+ auto transfer_memory_handle = ctx.GetCopyHandle(0);
+ auto process_handle = ctx.GetCopyHandle(1);
if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) {
LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!");
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:
void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx);
KernelHelpers::ServiceContext service_context;
- std::unique_ptr<AudioCore::AudioRenderer::Manager> impl;
+ std::unique_ptr<AudioCore::Renderer::Manager> impl;
u32 num_audio_devices{0};
};
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};
constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536};
constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537};
+constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7};
+constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8};
+constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6};
+constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5};
+constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17};
+constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4};
+constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
+constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
+constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
+constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
+constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};
+
} // 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 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <chrono>
-#include <cstring>
#include <memory>
#include <vector>
-#include <opus.h>
-#include <opus_multistream.h>
-
+#include "audio_core/opus/decoder.h"
+#include "audio_core/opus/parameters.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scratch_buffer.h"
+#include "core/core.h"
#include "core/hle/service/audio/hwopus.h"
#include "core/hle/service/ipc_helpers.h"
namespace Service::Audio {
-namespace {
-struct OpusDeleter {
- void operator()(OpusMSDecoder* ptr) const {
- opus_multistream_decoder_destroy(ptr);
+using namespace AudioCore::OpusDecoder;
+
+class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> {
+public:
+ explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus)
+ : ServiceFramework{system_, "IHardwareOpusDecoder"},
+ impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"},
+ {1, &IHardwareOpusDecoder::SetContext, "SetContext"},
+ {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"},
+ {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"},
+ {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
+ {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"},
+ {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"},
+ {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
+ {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"},
+ {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
}
-};
-using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
+ Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size) {
+ return impl->Initialize(params, transfer_memory, transfer_memory_size);
+ }
-struct OpusPacketHeader {
- // Packet size in bytes.
- u32_be size;
- // Indicates the final range of the codec's entropy coder.
- u32_be final_range;
-};
-static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
+ Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size) {
+ return impl->Initialize(params, transfer_memory, transfer_memory_size);
+ }
-class OpusDecoderState {
-public:
- /// Describes extra behavior that may be asked of the decoding context.
- enum class ExtraBehavior {
- /// No extra behavior.
- None,
+private:
+ void DecodeInterleavedOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
- /// Resets the decoder context back to a freshly initialized state.
- ResetContext,
- };
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
- enum class PerfTime {
- Disabled,
- Enabled,
- };
+ u32 size{};
+ u32 sample_count{};
+ auto result =
+ impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false);
+
+ LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
+
+ ctx.WriteBuffer(output_data);
- explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_)
- : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {}
-
- // Decodes interleaved Opus packets. Optionally allows reporting time taken to
- // perform the decoding, as well as any relevant extra behavior.
- void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time,
- ExtraBehavior extra_behavior) {
- if (perf_time == PerfTime::Disabled) {
- DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
- } else {
- u64 performance = 0;
- DecodeInterleavedHelper(ctx, &performance, extra_behavior);
- }
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
}
-private:
- void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance,
- ExtraBehavior extra_behavior) {
- u32 consumed = 0;
- u32 sample_count = 0;
- samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>());
-
- if (extra_behavior == ExtraBehavior::ResetContext) {
- ResetDecoderContext();
- }
-
- if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
- LOG_ERROR(Audio, "Failed to decode opus data");
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(ogniK): Use correct error code
- rb.Push(ResultUnknown);
- return;
- }
-
- const u32 param_size = performance != nullptr ? 6 : 4;
- IPC::ResponseBuilder rb{ctx, param_size};
- rb.Push(ResultSuccess);
- rb.Push<u32>(consumed);
- rb.Push<u32>(sample_count);
- if (performance) {
- rb.Push<u64>(*performance);
- }
- ctx.WriteBuffer(samples);
+ void SetContext(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_DEBUG(Service_Audio, "called");
+
+ auto input_data{ctx.ReadBuffer(0)};
+ auto result = impl->SetContext(input_data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
- bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input,
- std::span<opus_int16> output, u64* out_performance_time) const {
- const auto start_time = std::chrono::steady_clock::now();
- const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
- if (sizeof(OpusPacketHeader) > input.size()) {
- LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
- sizeof(OpusPacketHeader), input.size());
- return false;
- }
-
- OpusPacketHeader hdr{};
- std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
- if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
- LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
- sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
- return false;
- }
-
- const auto frame = input.data() + sizeof(OpusPacketHeader);
- const auto decoded_sample_count = opus_packet_get_nb_samples(
- frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
- static_cast<opus_int32>(sample_rate));
- if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
- LOG_ERROR(
- Audio,
- "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}",
- decoded_sample_count * channel_count * sizeof(u16), raw_output_sz);
- return false;
- }
-
- const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
- const auto out_sample_count =
- opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
- if (out_sample_count < 0) {
- LOG_ERROR(Audio,
- "Incorrect sample count received from opus_decode, "
- "output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
- out_sample_count, frame_size, static_cast<u32>(hdr.size));
- return false;
- }
-
- const auto end_time = std::chrono::steady_clock::now() - start_time;
- sample_count = out_sample_count;
- consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
- if (out_performance_time != nullptr) {
- *out_performance_time =
- std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
- }
-
- return true;
+ void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count,
+ input_data, output_data, false);
+
+ LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
}
- void ResetDecoderContext() {
- ASSERT(decoder != nullptr);
+ void SetContextForMultiStream(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_DEBUG(Service_Audio, "called");
+
+ auto input_data{ctx.ReadBuffer(0)};
+ auto result = impl->SetContext(input_data);
- opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
- OpusDecoderPtr decoder;
- u32 sample_rate;
- u32 channel_count;
- Common::ScratchBuffer<opus_int16> samples;
-};
+ void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
-class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
-public:
- explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_)
- : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{
- std::move(decoder_state_)} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
- {1, nullptr, "SetContext"},
- {2, nullptr, "DecodeInterleavedForMultiStreamOld"},
- {3, nullptr, "SetContextForMultiStream"},
- {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
- {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
- {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
- {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
- {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
- {9, nullptr, "DecodeInterleavedForMultiStream"},
- };
- // clang-format on
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
- RegisterHandlers(functions);
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
+ output_data, false);
+
+ LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
+ sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
}
-private:
- void DecodeInterleavedOld(HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
+ void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
+ input_data, output_data, false);
- decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
- OpusDecoderState::ExtraBehavior::None);
+ LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
+ sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
}
- void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
+ void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto reset{rp.Pop<bool>()};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
+ output_data, reset);
+
+ LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
+ reset, size, sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
+ }
+
+ void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto reset{rp.Pop<bool>()};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
+ input_data, output_data, reset);
+
+ LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
+ reset, size, sample_count, time_taken);
- decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
- OpusDecoderState::ExtraBehavior::None);
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
}
void DecodeInterleaved(HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
+ IPC::RequestParser rp{ctx};
+
+ auto reset{rp.Pop<bool>()};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
+ output_data, reset);
+
+ LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
+ reset, size, sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
+ }
+ void DecodeInterleavedForMultiStream(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
- : OpusDecoderState::ExtraBehavior::None;
- decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
+ auto reset{rp.Pop<bool>()};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
+ input_data, output_data, reset);
+
+ LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
+ reset, size, sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
}
- OpusDecoderState decoder_state;
+ std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl;
+ Common::ScratchBuffer<u8> output_data;
};
-std::size_t WorkerBufferSize(u32 channel_count) {
- ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
- constexpr int num_streams = 1;
- const int num_stereo_streams = channel_count == 2 ? 1 : 0;
- return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
-}
+void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
-// Creates the mapping table that maps the input channels to the particular
-// output channels. In the stereo case, we map the left and right input channels
-// to the left and right output channels respectively.
-//
-// However, in the monophonic case, we only map the one available channel
-// to the sole output channel. We specify 255 for the would-be right channel
-// as this is a special value defined by Opus to indicate to the decoder to
-// ignore that channel.
-std::array<u8, 2> CreateMappingTable(u32 channel_count) {
- if (channel_count == 2) {
- return {{0, 1}};
- }
+ auto params = rp.PopRaw<OpusParameters>();
+ auto transfer_memory_size{rp.Pop<u32>()};
+ auto transfer_memory_handle{ctx.GetCopyHandle(0)};
+ auto transfer_memory{
+ system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ transfer_memory_handle)};
- return {{0, 255}};
+ LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
+ params.sample_rate, params.channel_count, transfer_memory_size);
+
+ auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
+
+ OpusParametersEx ex{
+ .sample_rate = params.sample_rate,
+ .channel_count = params.channel_count,
+ .use_large_frame_size = false,
+ };
+ auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(result);
+ rb.PushIpcInterface(decoder);
}
-} // Anonymous namespace
void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto sample_rate = rp.Pop<u32>();
- const auto channel_count = rp.Pop<u32>();
+ auto params = rp.PopRaw<OpusParameters>();
- LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count);
+ u64 size{};
+ auto result = impl.GetWorkBufferSize(params, size);
- ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
- sample_rate == 12000 || sample_rate == 8000,
- "Invalid sample rate");
- ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
+ LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}",
+ params.sample_rate, params.channel_count, size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+}
+
+void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
- const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count));
- LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz);
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParameters params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParameters));
+
+ auto transfer_memory_size{rp.Pop<u32>()};
+ auto transfer_memory_handle{ctx.GetCopyHandle(0)};
+ auto transfer_memory{
+ system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ transfer_memory_handle)};
+
+ LOG_DEBUG(Service_Audio,
+ "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
+ "transfer_memory_size 0x{:X}",
+ params.sample_rate, params.channel_count, params.total_stream_count,
+ params.stereo_stream_count, transfer_memory_size);
+
+ auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
+
+ OpusMultiStreamParametersEx ex{
+ .sample_rate = params.sample_rate,
+ .channel_count = params.channel_count,
+ .total_stream_count = params.total_stream_count,
+ .stereo_stream_count = params.stereo_stream_count,
+ .use_large_frame_size = false,
+ .mappings{},
+ };
+ std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings));
+ auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(worker_buffer_sz);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(result);
+ rb.PushIpcInterface(decoder);
}
-void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
- GetWorkBufferSize(ctx);
+void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParameters params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParameters));
+
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeForMultiStream(params, size);
+
+ LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
}
-void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
- OpusMultiStreamParametersEx param;
- std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
+void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
- const auto sample_rate = param.sample_rate;
- const auto channel_count = param.channel_count;
- const auto number_streams = param.number_streams;
- const auto number_stereo_streams = param.number_stereo_streams;
+ auto params = rp.PopRaw<OpusParametersEx>();
+ auto transfer_memory_size{rp.Pop<u32>()};
+ auto transfer_memory_handle{ctx.GetCopyHandle(0)};
+ auto transfer_memory{
+ system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ transfer_memory_handle)};
- LOG_DEBUG(
- Audio,
- "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
- sample_rate, channel_count, number_streams, number_stereo_streams);
+ LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
+ params.sample_rate, params.channel_count, transfer_memory_size);
- ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
- sample_rate == 12000 || sample_rate == 8000,
- "Invalid sample rate");
+ auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
- const u32 worker_buffer_sz =
- static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams));
+ auto result =
+ decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(worker_buffer_sz);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(result);
+ rb.PushIpcInterface(decoder);
}
-void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) {
+void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto sample_rate = rp.Pop<u32>();
- const auto channel_count = rp.Pop<u32>();
- const auto buffer_sz = rp.Pop<u32>();
-
- LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
- channel_count, buffer_sz);
-
- ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
- sample_rate == 12000 || sample_rate == 8000,
- "Invalid sample rate");
- ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
-
- const std::size_t worker_sz = WorkerBufferSize(channel_count);
- ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
-
- const int num_stereo_streams = channel_count == 2 ? 1 : 0;
- const auto mapping_table = CreateMappingTable(channel_count);
-
- int error = 0;
- OpusDecoderPtr decoder{
- opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
- num_stereo_streams, mapping_table.data(), &error)};
- if (error != OPUS_OK || decoder == nullptr) {
- LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(ogniK): Use correct error code
- rb.Push(ResultUnknown);
- return;
- }
+ auto params = rp.PopRaw<OpusParametersEx>();
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IHardwareOpusDecoderManager>(
- system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeEx(params, size);
+
+ LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
}
-void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
+void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto sample_rate = rp.Pop<u32>();
- const auto channel_count = rp.Pop<u32>();
- LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count);
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParametersEx params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
- ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
- sample_rate == 12000 || sample_rate == 8000,
- "Invalid sample rate");
- ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
+ auto transfer_memory_size{rp.Pop<u32>()};
+ auto transfer_memory_handle{ctx.GetCopyHandle(0)};
+ auto transfer_memory{
+ system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ transfer_memory_handle)};
- const int num_stereo_streams = channel_count == 2 ? 1 : 0;
- const auto mapping_table = CreateMappingTable(channel_count);
+ LOG_DEBUG(Service_Audio,
+ "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
+ "use_large_frame_size {}"
+ "transfer_memory_size 0x{:X}",
+ params.sample_rate, params.channel_count, params.total_stream_count,
+ params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size);
- int error = 0;
- OpusDecoderPtr decoder{
- opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
- num_stereo_streams, mapping_table.data(), &error)};
- if (error != OPUS_OK || decoder == nullptr) {
- LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(ogniK): Use correct error code
- rb.Push(ResultUnknown);
- return;
- }
+ auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
+
+ auto result =
+ decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IHardwareOpusDecoderManager>(
- system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
+ rb.Push(result);
+ rb.PushIpcInterface(decoder);
+}
+
+void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParametersEx params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
+
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size);
+
+ LOG_DEBUG(Service_Audio,
+ "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
+ "use_large_frame_size {} -- returned size 0x{:X}",
+ params.sample_rate, params.channel_count, params.total_stream_count,
+ params.stereo_stream_count, params.use_large_frame_size, size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+}
+
+void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto params = rp.PopRaw<OpusParametersEx>();
+
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeExEx(params, size);
+
+ LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+}
+
+void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParametersEx params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
+
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size);
+
+ LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
}
-HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
+HwOpus::HwOpus(Core::System& system_)
+ : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} {
static const FunctionInfo functions[] = {
{0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"},
{1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
- {2, nullptr, "OpenOpusDecoderForMultiStream"},
- {3, nullptr, "GetWorkBufferSizeForMultiStream"},
+ {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"},
+ {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"},
{4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
{5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
- {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},
+ {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx,
+ "OpenHardwareOpusDecoderForMultiStreamEx"},
{7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
- {8, nullptr, "GetWorkBufferSizeExEx"},
- {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"},
+ {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"},
+ {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"},
};
RegisterHandlers(functions);
}
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 @@
#pragma once
+#include "audio_core/opus/decoder_manager.h"
#include "core/hle/service/service.h"
namespace Core {
@@ -11,16 +12,6 @@ class System;
namespace Service::Audio {
-struct OpusMultiStreamParametersEx {
- u32 sample_rate;
- u32 channel_count;
- u32 number_streams;
- u32 number_stereo_streams;
- u32 use_large_frame_size;
- u32 padding;
- std::array<u32, 64> channel_mappings;
-};
-
class HwOpus final : public ServiceFramework<HwOpus> {
public:
explicit HwOpus(Core::System& system_);
@@ -28,10 +19,18 @@ public:
private:
void OpenHardwareOpusDecoder(HLERequestContext& ctx);
- void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
void GetWorkBufferSize(HLERequestContext& ctx);
+ void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx);
+ void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx);
+ void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
void GetWorkBufferSizeEx(HLERequestContext& ctx);
+ void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx);
void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx);
+ void GetWorkBufferSizeExEx(HLERequestContext& ctx);
+ void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx);
+
+ Core::System& system;
+ AudioCore::OpusDecoder::OpusDecoderManager impl;
};
} // 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:
}
void ImportTicket(HLERequestContext& ctx) {
- const auto ticket = ctx.ReadBuffer();
+ const auto raw_ticket = ctx.ReadBuffer();
[[maybe_unused]] const auto cert = ctx.ReadBuffer(1);
- if (ticket.size() < sizeof(Core::Crypto::Ticket)) {
+ if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) {
LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
return;
}
- Core::Crypto::Ticket raw{};
- std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket));
-
- if (!keys.AddTicketPersonalized(raw)) {
+ Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket);
+ if (!keys.AddTicket(ticket)) {
LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index dfcdd3ada..508db7360 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -4,6 +4,7 @@
#include <utility>
#include "common/assert.h"
+#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
#include "core/core.h"
@@ -57,8 +58,8 @@ Result VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size
return FileSys::ERROR_PATH_NOT_FOUND;
}
- const auto entry_type = GetEntryType(path);
- if (entry_type.Code() == ResultSuccess) {
+ FileSys::EntryType entry_type{};
+ if (GetEntryType(&entry_type, path) == ResultSuccess) {
return FileSys::ERROR_PATH_ALREADY_EXISTS;
}
@@ -154,10 +155,18 @@ Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
std::string src_path(Common::FS::SanitizePath(src_path_));
std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = backing->GetFileRelative(src_path);
+ auto dst = backing->GetFileRelative(dest_path);
if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
- if (src == nullptr)
+ if (src == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
+ }
+
+ if (dst && Common::FS::Exists(dst->GetFullPath())) {
+ LOG_ERROR(Service_FS, "File at new_path={} already exists", dst->GetFullPath());
+ return FileSys::ERROR_PATH_ALREADY_EXISTS;
+ }
+
if (!src->Rename(Common::FS::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this
return ResultUnknown;
@@ -210,8 +219,8 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
return ResultUnknown;
}
-ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_,
- FileSys::Mode mode) const {
+Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
+ const std::string& path_, FileSys::Mode mode) const {
const std::string path(Common::FS::SanitizePath(path_));
std::string_view npath = path;
while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) {
@@ -224,50 +233,68 @@ ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::
}
if (mode == FileSys::Mode::Append) {
- return std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize());
+ *out_file = std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize());
+ } else {
+ *out_file = file;
}
- return file;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) {
+Result VfsDirectoryServiceWrapper::OpenDirectory(FileSys::VirtualDir* out_directory,
+ const std::string& path_) {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, path);
if (dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return FileSys::ERROR_PATH_NOT_FOUND;
}
- return dir;
+ *out_directory = dir;
+ return ResultSuccess;
}
-ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
- const std::string& path_) const {
+Result VfsDirectoryServiceWrapper::GetEntryType(FileSys::EntryType* out_entry_type,
+ const std::string& path_) const {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
- if (dir == nullptr)
+ if (dir == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
+ }
+
auto filename = Common::FS::GetFilename(path);
// TODO(Subv): Some games use the '/' path, find out what this means.
- if (filename.empty())
- return FileSys::EntryType::Directory;
+ if (filename.empty()) {
+ *out_entry_type = FileSys::EntryType::Directory;
+ return ResultSuccess;
+ }
+
+ if (dir->GetFile(filename) != nullptr) {
+ *out_entry_type = FileSys::EntryType::File;
+ return ResultSuccess;
+ }
+
+ if (dir->GetSubdirectory(filename) != nullptr) {
+ *out_entry_type = FileSys::EntryType::Directory;
+ return ResultSuccess;
+ }
- if (dir->GetFile(filename) != nullptr)
- return FileSys::EntryType::File;
- if (dir->GetSubdirectory(filename) != nullptr)
- return FileSys::EntryType::Directory;
return FileSys::ERROR_PATH_NOT_FOUND;
}
-ResultVal<FileSys::FileTimeStampRaw> VfsDirectoryServiceWrapper::GetFileTimeStampRaw(
- const std::string& path) const {
+Result VfsDirectoryServiceWrapper::GetFileTimeStampRaw(
+ FileSys::FileTimeStampRaw* out_file_time_stamp_raw, const std::string& path) const {
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (dir == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
- if (GetEntryType(path).Failed()) {
+
+ FileSys::EntryType entry_type;
+ if (GetEntryType(&entry_type, path) != ResultSuccess) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
- return dir->GetFileTimeStamp(Common::FS::GetFilename(path));
+
+ *out_file_time_stamp_raw = dir->GetFileTimeStamp(Common::FS::GetFilename(path));
+ return ResultSuccess;
}
FileSystemController::FileSystemController(Core::System& system_) : system{system_} {}
@@ -310,57 +337,59 @@ void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) {
romfs_factory->SetPackedUpdate(std::move(update_raw));
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() const {
+FileSys::VirtualFile FileSystemController::OpenRomFSCurrentProcess() const {
LOG_TRACE(Service_FS, "Opening RomFS for current process");
if (romfs_factory == nullptr) {
- // TODO(bunnei): Find a better error code for this
- return ResultUnknown;
+ return nullptr;
}
return romfs_factory->OpenCurrentProcess(system.GetApplicationProcessProgramID());
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFS(
- u64 title_id, FileSys::ContentRecordType type) const {
+FileSys::VirtualFile FileSystemController::OpenPatchedRomFS(u64 title_id,
+ FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id);
if (romfs_factory == nullptr) {
- // TODO: Find a better error code for this
- return ResultUnknown;
+ return nullptr;
}
return romfs_factory->OpenPatchedRomFS(title_id, type);
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFSWithProgramIndex(
+FileSys::VirtualFile FileSystemController::OpenPatchedRomFSWithProgramIndex(
u64 title_id, u8 program_index, FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id,
program_index);
if (romfs_factory == nullptr) {
- // TODO: Find a better error code for this
- return ResultUnknown;
+ return nullptr;
}
return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type);
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
- u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
+FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
title_id, storage_id, type);
if (romfs_factory == nullptr) {
- // TODO(bunnei): Find a better error code for this
- return ResultUnknown;
+ return nullptr;
}
return romfs_factory->Open(title_id, storage_id, type);
}
-ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const {
+std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca(
+ u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
+ return romfs_factory->GetEntry(title_id, storage_id, type);
+}
+
+Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data,
+ FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& save_struct) const {
LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space,
save_struct.DebugInfo());
@@ -368,11 +397,18 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->Create(space, save_struct);
+ auto save_data = save_data_factory->Create(space, save_struct);
+ if (save_data == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data = save_data;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const {
+Result FileSystemController::OpenSaveData(FileSys::VirtualDir* out_save_data,
+ FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& attribute) const {
LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", space,
attribute.DebugInfo());
@@ -380,32 +416,50 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->Open(space, attribute);
+ auto save_data = save_data_factory->Open(space, attribute);
+ if (save_data == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data = save_data;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace(
- FileSys::SaveDataSpaceId space) const {
+Result FileSystemController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
+ FileSys::SaveDataSpaceId space) const {
LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", space);
if (save_data_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->GetSaveDataSpaceDirectory(space);
+ auto save_data_space = save_data_factory->GetSaveDataSpaceDirectory(space);
+ if (save_data_space == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data_space = save_data_space;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const {
+Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const {
LOG_TRACE(Service_FS, "Opening SDMC");
if (sdmc_factory == nullptr) {
return FileSys::ERROR_SD_CARD_NOT_FOUND;
}
- return sdmc_factory->Open();
+ auto sdmc = sdmc_factory->Open();
+ if (sdmc == nullptr) {
+ return FileSys::ERROR_SD_CARD_NOT_FOUND;
+ }
+
+ *out_sdmc = sdmc;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition(
- FileSys::BisPartitionId id) const {
+Result FileSystemController::OpenBISPartition(FileSys::VirtualDir* out_bis_partition,
+ FileSys::BisPartitionId id) const {
LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id);
if (bis_factory == nullptr) {
@@ -417,11 +471,12 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition(
return FileSys::ERROR_INVALID_ARGUMENT;
}
- return part;
+ *out_bis_partition = part;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
- FileSys::BisPartitionId id) const {
+Result FileSystemController::OpenBISPartitionStorage(
+ FileSys::VirtualFile* out_bis_partition_storage, FileSys::BisPartitionId id) const {
LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id);
if (bis_factory == nullptr) {
@@ -433,7 +488,8 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
return FileSys::ERROR_INVALID_ARGUMENT;
}
- return part;
+ *out_bis_partition_storage = part;
+ return ResultSuccess;
}
u64 FileSystemController::GetFreeSpaceSize(FileSys::StorageId id) const {
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index a5c1c9d3e..e7e7c4c28 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -15,6 +15,7 @@ class System;
namespace FileSys {
class BISFactory;
+class NCA;
class RegisteredCache;
class RegisteredCacheUnion;
class PlaceholderCache;
@@ -64,21 +65,26 @@ public:
Result RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
void SetPackedUpdate(FileSys::VirtualFile update_raw);
- ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const;
- ResultVal<FileSys::VirtualFile> OpenPatchedRomFS(u64 title_id,
- FileSys::ContentRecordType type) const;
- ResultVal<FileSys::VirtualFile> OpenPatchedRomFSWithProgramIndex(
- u64 title_id, u8 program_index, FileSys::ContentRecordType type) const;
- ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::VirtualFile OpenRomFSCurrentProcess() const;
+ FileSys::VirtualFile OpenPatchedRomFS(u64 title_id, FileSys::ContentRecordType type) const;
+ FileSys::VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
+ FileSys::ContentRecordType type) const;
+ FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::ContentRecordType type) const;
+ std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type) const;
- ResultVal<FileSys::VirtualDir> CreateSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const;
- ResultVal<FileSys::VirtualDir> OpenSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const;
- ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const;
- ResultVal<FileSys::VirtualDir> OpenSDMC() const;
- ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const;
- ResultVal<FileSys::VirtualFile> OpenBISPartitionStorage(FileSys::BisPartitionId id) const;
+
+ Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& save_struct) const;
+ Result OpenSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& save_struct) const;
+ Result OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
+ FileSys::SaveDataSpaceId space) const;
+ Result OpenSDMC(FileSys::VirtualDir* out_sdmc) const;
+ Result OpenBISPartition(FileSys::VirtualDir* out_bis_partition,
+ FileSys::BisPartitionId id) const;
+ Result OpenBISPartitionStorage(FileSys::VirtualFile* out_bis_partition_storage,
+ FileSys::BisPartitionId id) const;
u64 GetFreeSpaceSize(FileSys::StorageId id) const;
u64 GetTotalSpaceSize(FileSys::StorageId id) const;
@@ -224,26 +230,28 @@ public:
* @param mode Mode to open the file with
* @return Opened file, or error code
*/
- ResultVal<FileSys::VirtualFile> OpenFile(const std::string& path, FileSys::Mode mode) const;
+ Result OpenFile(FileSys::VirtualFile* out_file, const std::string& path,
+ FileSys::Mode mode) const;
/**
* Open a directory specified by its path
* @param path Path relative to the archive
* @return Opened directory, or error code
*/
- ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path);
+ Result OpenDirectory(FileSys::VirtualDir* out_directory, const std::string& path);
/**
* Get the type of the specified path
* @return The type of the specified path or error code
*/
- ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const;
+ Result GetEntryType(FileSys::EntryType* out_entry_type, const std::string& path) const;
/**
* Get the timestamp of the specified path
* @return The timestamp of the specified path or error code
*/
- ResultVal<FileSys::FileTimeStampRaw> GetFileTimeStampRaw(const std::string& path) const;
+ Result GetFileTimeStampRaw(FileSys::FileTimeStampRaw* out_time_stamp_raw,
+ const std::string& path) const;
private:
FileSys::VirtualDir backing;
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 427dbc8b3..6e4d26b1e 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -419,14 +419,15 @@ public:
LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode);
- auto result = backend.OpenFile(name, mode);
- if (result.Failed()) {
+ FileSys::VirtualFile vfs_file{};
+ auto result = backend.OpenFile(&vfs_file, name, mode);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
- auto file = std::make_shared<IFile>(system, result.Unwrap());
+ auto file = std::make_shared<IFile>(system, vfs_file);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -444,14 +445,15 @@ public:
LOG_DEBUG(Service_FS, "called. directory={}, filter={}", name, filter_flags);
- auto result = backend.OpenDirectory(name);
- if (result.Failed()) {
+ FileSys::VirtualDir vfs_dir{};
+ auto result = backend.OpenDirectory(&vfs_dir, name);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
- auto directory = std::make_shared<IDirectory>(system, result.Unwrap());
+ auto directory = std::make_shared<IDirectory>(system, vfs_dir);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -464,16 +466,17 @@ public:
LOG_DEBUG(Service_FS, "called. file={}", name);
- auto result = backend.GetEntryType(name);
- if (result.Failed()) {
+ FileSys::EntryType vfs_entry_type{};
+ auto result = backend.GetEntryType(&vfs_entry_type, name);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(*result));
+ rb.Push<u32>(static_cast<u32>(vfs_entry_type));
}
void Commit(HLERequestContext& ctx) {
@@ -505,16 +508,17 @@ public:
LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", name);
- auto result = backend.GetFileTimeStampRaw(name);
- if (result.Failed()) {
+ FileSys::FileTimeStampRaw vfs_timestamp{};
+ auto result = backend.GetFileTimeStampRaw(&vfs_timestamp, name);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(ResultSuccess);
- rb.PushRaw(*result);
+ rb.PushRaw(vfs_timestamp);
}
private:
@@ -572,14 +576,15 @@ private:
}
void FindAllSaves(FileSys::SaveDataSpaceId space) {
- const auto save_root = fsc.OpenSaveDataSpace(space);
+ FileSys::VirtualDir save_root{};
+ const auto result = fsc.OpenSaveDataSpace(&save_root, space);
- if (save_root.Failed() || *save_root == nullptr) {
+ if (result != ResultSuccess || save_root == nullptr) {
LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space);
return;
}
- for (const auto& type : (*save_root)->GetSubdirectories()) {
+ for (const auto& type : save_root->GetSubdirectories()) {
if (type->GetName() == "save") {
for (const auto& save_id : type->GetSubdirectories()) {
for (const auto& user_id : save_id->GetSubdirectories()) {
@@ -837,9 +842,11 @@ void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) {
void FSP_SRV::OpenSdCardFileSystem(HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
- auto filesystem =
- std::make_shared<IFileSystem>(system, fsc.OpenSDMC().Unwrap(),
- SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
+ FileSys::VirtualDir sdmc_dir{};
+ fsc.OpenSDMC(&sdmc_dir);
+
+ auto filesystem = std::make_shared<IFileSystem>(
+ system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -856,7 +863,8 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(),
uid[1], uid[0]);
- fsc.CreateSaveData(FileSys::SaveDataSpaceId::NandUser, save_struct);
+ FileSys::VirtualDir save_data_dir{};
+ fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser, save_struct);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -874,8 +882,9 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
LOG_INFO(Service_FS, "called.");
- auto dir = fsc.OpenSaveData(parameters.space_id, parameters.attribute);
- if (dir.Failed()) {
+ FileSys::VirtualDir dir{};
+ auto result = fsc.OpenSaveData(&dir, parameters.space_id, parameters.attribute);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
return;
@@ -899,8 +908,8 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
ASSERT(false);
}
- auto filesystem = std::make_shared<IFileSystem>(system, std::move(dir.Unwrap()),
- SizeGetter::FromStorageId(fsc, id));
+ auto filesystem =
+ std::make_shared<IFileSystem>(system, std::move(dir), SizeGetter::FromStorageId(fsc, id));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -970,7 +979,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) {
if (!romfs) {
auto current_romfs = fsc.OpenRomFSCurrentProcess();
- if (current_romfs.Failed()) {
+ if (!current_romfs) {
// TODO (bunnei): Find the right error code to use here
LOG_CRITICAL(Service_FS, "no file system interface available!");
IPC::ResponseBuilder rb{ctx, 2};
@@ -978,7 +987,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) {
return;
}
- romfs = current_romfs.Unwrap();
+ romfs = current_romfs;
}
auto storage = std::make_shared<IStorage>(system, romfs);
@@ -999,7 +1008,7 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
- if (data.Failed()) {
+ if (!data) {
const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
if (archive != nullptr) {
@@ -1020,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
const FileSys::PatchManager pm{title_id, fsc, content_provider};
+ auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data);
auto storage = std::make_shared<IStorage>(
- system, pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
+ system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -1051,7 +1061,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
fsc.OpenPatchedRomFSWithProgramIndex(system.GetApplicationProcessProgramID(), program_index,
FileSys::ContentRecordType::Program);
- if (patched_romfs.Failed()) {
+ if (!patched_romfs) {
// TODO: Find the right error code to use here
LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index);
@@ -1060,7 +1070,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
return;
}
- auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs.Unwrap()));
+ auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
index ed6fcb5f6..6f1151b03 100644
--- a/src/core/hle/service/glue/arp.cpp
+++ b/src/core/hle/service/glue/arp.cpp
@@ -65,18 +65,19 @@ void ARP_R::GetApplicationLaunchProperty(HLERequestContext& ctx) {
return;
}
- const auto res = manager.GetLaunchProperty(*title_id);
+ ApplicationLaunchProperty launch_property{};
+ const auto res = manager.GetLaunchProperty(&launch_property, *title_id);
- if (res.Failed()) {
+ if (res != ResultSuccess) {
LOG_ERROR(Service_ARP, "Failed to get launch property!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
return;
}
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- rb.PushRaw(*res);
+ rb.PushRaw(launch_property);
}
void ARP_R::GetApplicationLaunchPropertyWithApplicationId(HLERequestContext& ctx) {
@@ -85,18 +86,19 @@ void ARP_R::GetApplicationLaunchPropertyWithApplicationId(HLERequestContext& ctx
LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
- const auto res = manager.GetLaunchProperty(title_id);
+ ApplicationLaunchProperty launch_property{};
+ const auto res = manager.GetLaunchProperty(&launch_property, title_id);
- if (res.Failed()) {
+ if (res != ResultSuccess) {
LOG_ERROR(Service_ARP, "Failed to get launch property!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
return;
}
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- rb.PushRaw(*res);
+ rb.PushRaw(launch_property);
}
void ARP_R::GetApplicationControlProperty(HLERequestContext& ctx) {
@@ -113,16 +115,17 @@ void ARP_R::GetApplicationControlProperty(HLERequestContext& ctx) {
return;
}
- const auto res = manager.GetControlProperty(*title_id);
+ std::vector<u8> nacp_data;
+ const auto res = manager.GetControlProperty(&nacp_data, *title_id);
- if (res.Failed()) {
+ if (res != ResultSuccess) {
LOG_ERROR(Service_ARP, "Failed to get control property!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
return;
}
- ctx.WriteBuffer(*res);
+ ctx.WriteBuffer(nacp_data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -134,16 +137,17 @@ void ARP_R::GetApplicationControlPropertyWithApplicationId(HLERequestContext& ct
LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
- const auto res = manager.GetControlProperty(title_id);
+ std::vector<u8> nacp_data;
+ const auto res = manager.GetControlProperty(&nacp_data, title_id);
- if (res.Failed()) {
+ if (res != ResultSuccess) {
LOG_ERROR(Service_ARP, "Failed to get control property!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
return;
}
- ctx.WriteBuffer(*res);
+ ctx.WriteBuffer(nacp_data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/glue/glue_manager.cpp b/src/core/hle/service/glue/glue_manager.cpp
index 4bf67921b..22f001704 100644
--- a/src/core/hle/service/glue/glue_manager.cpp
+++ b/src/core/hle/service/glue/glue_manager.cpp
@@ -15,7 +15,8 @@ ARPManager::ARPManager() = default;
ARPManager::~ARPManager() = default;
-ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const {
+Result ARPManager::GetLaunchProperty(ApplicationLaunchProperty* out_launch_property,
+ u64 title_id) const {
if (title_id == 0) {
return Glue::ResultInvalidProcessId;
}
@@ -25,10 +26,11 @@ ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id)
return Glue::ResultProcessIdNotRegistered;
}
- return iter->second.launch;
+ *out_launch_property = iter->second.launch;
+ return ResultSuccess;
}
-ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const {
+Result ARPManager::GetControlProperty(std::vector<u8>* out_control_property, u64 title_id) const {
if (title_id == 0) {
return Glue::ResultInvalidProcessId;
}
@@ -38,7 +40,8 @@ ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const {
return Glue::ResultProcessIdNotRegistered;
}
- return iter->second.control;
+ *out_control_property = iter->second.control;
+ return ResultSuccess;
}
Result ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch,
diff --git a/src/core/hle/service/glue/glue_manager.h b/src/core/hle/service/glue/glue_manager.h
index 1cf53d9d9..216aa34c1 100644
--- a/src/core/hle/service/glue/glue_manager.h
+++ b/src/core/hle/service/glue/glue_manager.h
@@ -32,12 +32,12 @@ public:
// Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was
// previously registered, otherwise ResultProcessIdNotRegistered if it was never registered or
// ResultInvalidProcessId if the title ID is 0.
- ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const;
+ Result GetLaunchProperty(ApplicationLaunchProperty* out_launch_property, u64 title_id) const;
// Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to
// the provided title ID if it was previously registered, otherwise ResultProcessIdNotRegistered
// if it was never registered or ResultInvalidProcessId if the title ID is 0.
- ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const;
+ Result GetControlProperty(std::vector<u8>* out_control_property, u64 title_id) const;
// Adds a new entry to the internal database with the provided parameters, returning
// ResultProcessIdNotRegistered if attempting to re-register a title ID without an intermediate
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()
};
// Hack: There is no touch in docked but games still allow it
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
gesture.points[id] = {
.x = static_cast<s32>(active_x * Layout::ScreenDocked::Width),
.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) {
shared_memory->system_properties.use_minus.Assign(1);
shared_memory->system_properties.is_charging_joy_dual.Assign(
battery_level.dual.is_charging);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController;
+ shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController;
shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::Handheld:
@@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->system_properties.is_charging_joy_right.Assign(
battery_level.right.is_charging);
shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
- shared_memory->applet_nfc_xcd.applet_footer.type =
- AppletFooterUiType::HandheldJoyConLeftJoyConRight;
+ shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::JoyconDual:
@@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyDual;
shared_memory->fullkey_color.fullkey = body_colors.left;
shared_memory->battery_level_dual = battery_level.left.battery_level;
shared_memory->system_properties.is_charging_joy_dual.Assign(
battery_level.left.is_charging);
} else if (controller.is_dual_left_connected) {
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly;
shared_memory->fullkey_color.fullkey = body_colors.left;
shared_memory->battery_level_dual = battery_level.left.battery_level;
shared_memory->system_properties.is_charging_joy_dual.Assign(
battery_level.left.is_charging);
} else {
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly;
shared_memory->fullkey_color.fullkey = body_colors.right;
shared_memory->battery_level_dual = battery_level.right.battery_level;
shared_memory->system_properties.is_charging_joy_dual.Assign(
@@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->system_properties.use_minus.Assign(1);
shared_memory->system_properties.is_charging_joy_left.Assign(
battery_level.left.is_charging);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal;
shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::JoyconRight:
@@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->system_properties.use_plus.Assign(1);
shared_memory->system_properties.is_charging_joy_right.Assign(
battery_level.right.is_charging);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal;
shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::GameCube:
@@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
case Core::HID::NpadStyleIndex::SNES:
shared_memory->style_tag.lucia.Assign(1);
shared_memory->device_type.fullkey.Assign(1);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia;
+ shared_memory->applet_footer_type = AppletFooterUiType::Lucia;
break;
case Core::HID::NpadStyleIndex::N64:
shared_memory->style_tag.lagoon.Assign(1);
shared_memory->device_type.fullkey.Assign(1);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon;
+ shared_memory->applet_footer_type = AppletFooterUiType::Lagon;
break;
case Core::HID::NpadStyleIndex::SegaGenesis:
shared_memory->style_tag.lager.Assign(1);
@@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
std::scoped_lock lock{mutex};
auto& controller = GetControllerFromNpadIdType(npad_id);
const auto controller_type = controller.device->GetNpadStyleIndex();
+
+ if (!controller.device->IsConnected() && controller.is_connected) {
+ DisconnectNpad(npad_id);
+ return;
+ }
if (!controller.device->IsConnected()) {
return;
}
+ if (controller.device->IsConnected() && !controller.is_connected) {
+ InitNewlyAddedController(npad_id);
+ }
// This function is unique to yuzu for the turbo buttons and motion to work properly
controller.device->StatusUpdate();
@@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
pad_entry.npad_buttons.l.Assign(button_state.zl);
pad_entry.npad_buttons.r.Assign(button_state.zr);
}
+
+ if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) {
+ hid_core.SetLastActiveController(npad_id);
+ }
}
void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
@@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
// Once SetSupportedStyleSet is called controllers are fully initialized
is_controller_initialized = true;
-
- // Connect all active controllers
- for (auto& controller : controller_data) {
- const auto& device = controller.device;
- if (device->IsConnected()) {
- AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
- }
- }
}
Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
@@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
.left = {},
.right = {},
};
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None;
+ shared_memory->applet_footer_type = AppletFooterUiType::None;
controller.is_dual_left_connected = true;
controller.is_dual_right_connected = true;
@@ -1508,6 +1511,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() {
return static_cast<Core::HID::NpadButton>(press_state.exchange(0));
}
+void Controller_NPad::ApplyNpadSystemCommonPolicy() {
+ Core::HID::NpadStyleTag styletag{};
+ styletag.fullkey.Assign(1);
+ styletag.handheld.Assign(1);
+ styletag.joycon_dual.Assign(1);
+ styletag.system_ext.Assign(1);
+ styletag.system.Assign(1);
+ SetSupportedStyleSet(styletag);
+
+ SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual);
+
+ supported_npad_id_types.clear();
+ supported_npad_id_types.resize(10);
+ supported_npad_id_types[0] = Core::HID::NpadIdType::Player1;
+ supported_npad_id_types[1] = Core::HID::NpadIdType::Player2;
+ supported_npad_id_types[2] = Core::HID::NpadIdType::Player3;
+ supported_npad_id_types[3] = Core::HID::NpadIdType::Player4;
+ supported_npad_id_types[4] = Core::HID::NpadIdType::Player5;
+ supported_npad_id_types[5] = Core::HID::NpadIdType::Player6;
+ supported_npad_id_types[6] = Core::HID::NpadIdType::Player7;
+ supported_npad_id_types[7] = Core::HID::NpadIdType::Player8;
+ supported_npad_id_types[8] = Core::HID::NpadIdType::Other;
+ supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
+}
+
bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const {
if (controller == Core::HID::NpadStyleIndex::Handheld) {
const bool support_handheld =
@@ -1518,7 +1546,7 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller
return false;
}
// Handheld shouldn't be supported in docked mode
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
return false;
}
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:
// Specifically for cheat engine and other features.
Core::HID::NpadButton GetAndResetPressState();
+ void ApplyNpadSystemCommonPolicy();
+
static bool IsNpadIdValid(Core::HID::NpadIdType npad_id);
static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
static Result VerifyValidSixAxisSensorHandle(
@@ -360,7 +362,7 @@ private:
enum class AppletFooterUiType : u8 {
None = 0,
HandheldNone = 1,
- HandheldJoyConLeftOnly = 1,
+ HandheldJoyConLeftOnly = 2,
HandheldJoyConRightOnly = 3,
HandheldJoyConLeftJoyConRight = 4,
JoyDual = 5,
@@ -382,13 +384,6 @@ private:
Lagon = 21,
};
- struct AppletFooterUi {
- AppletFooterUiAttributes attributes{};
- AppletFooterUiType type{AppletFooterUiType::None};
- INSERT_PADDING_BYTES(0x5B); // Reserved
- };
- static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
-
// This is nn::hid::NpadLarkType
enum class NpadLarkType : u32 {
Invalid,
@@ -419,13 +414,6 @@ private:
U,
};
- struct AppletNfcXcd {
- union {
- AppletFooterUi applet_footer{};
- Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo;
- };
- };
-
// This is nn::hid::detail::NpadInternalState
struct NpadInternalState {
Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None};
@@ -452,7 +440,9 @@ private:
Core::HID::NpadBatteryLevel battery_level_dual{};
Core::HID::NpadBatteryLevel battery_level_left{};
Core::HID::NpadBatteryLevel battery_level_right{};
- AppletNfcXcd applet_nfc_xcd{};
+ AppletFooterUiAttributes applet_footer_attributes{};
+ AppletFooterUiType applet_footer_type{AppletFooterUiType::None};
+ INSERT_PADDING_BYTES(0x5B); // Reserved
INSERT_PADDING_BYTES(0x20); // Unknown
Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{};
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;
namespace Service::HID {
class Controller_Touchscreen final : public ControllerBase {
public:
- // This is nn::hid::TouchScreenModeForNx
- enum class TouchScreenModeForNx : u8 {
- UseSystemSetting,
- Finger,
- Heat2,
- };
-
- // This is nn::hid::TouchScreenConfigurationForNx
- struct TouchScreenConfigurationForNx {
- TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
- INSERT_PADDING_BYTES_NOINIT(0x7);
- INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved
- };
- static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17,
- "TouchScreenConfigurationForNx is an invalid size");
-
explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_Touchscreen() override;
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() {
return applet_resource;
}
-Hid::Hid(Core::System& system_)
- : ServiceFramework{system_, "hid"}, service_context{system_, service_name} {
+Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
+ : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{
+ system_,
+ service_name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Hid::CreateAppletResource, "CreateAppletResource"},
@@ -2368,7 +2370,7 @@ void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) {
void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()};
+ const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}",
@@ -2543,7 +2545,9 @@ public:
class HidSys final : public ServiceFramework<HidSys> {
public:
- explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} {
+ explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
+ : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"},
+ applet_resource{applet_resource_} {
// clang-format off
static const FunctionInfo functions[] = {
{31, nullptr, "SendKeyboardLockKeyEvent"},
@@ -2568,7 +2572,7 @@ public:
{303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"},
{304, nullptr, "EnableAssigningSingleOnSlSrPress"},
{305, nullptr, "DisableAssigningSingleOnSlSrPress"},
- {306, nullptr, "GetLastActiveNpad"},
+ {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"},
{307, nullptr, "GetNpadSystemExtStyle"},
{308, nullptr, "ApplyNpadSystemCommonPolicyFull"},
{309, nullptr, "GetNpadFullKeyGripColor"},
@@ -2624,7 +2628,7 @@ public:
{700, nullptr, "ActivateUniquePad"},
{702, nullptr, "AcquireUniquePadConnectionEventHandle"},
{703, nullptr, "GetUniquePadIds"},
- {751, nullptr, "AcquireJoyDetachOnBluetoothOffEventHandle"},
+ {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"},
{800, nullptr, "ListSixAxisSensorHandles"},
{801, nullptr, "IsSixAxisSensorUserCalibrationSupported"},
{802, nullptr, "ResetSixAxisSensorCalibrationValues"},
@@ -2650,7 +2654,7 @@ public:
{830, nullptr, "SetNotificationLedPattern"},
{831, nullptr, "SetNotificationLedPatternWithTimeout"},
{832, nullptr, "PrepareHidsForNotificationWake"},
- {850, nullptr, "IsUsbFullKeyControllerEnabled"},
+ {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
{851, nullptr, "EnableUsbFullKeyController"},
{852, nullptr, "IsUsbConnected"},
{870, nullptr, "IsHandheldButtonPressedOnConsoleMode"},
@@ -2682,7 +2686,7 @@ public:
{1150, nullptr, "SetTouchScreenMagnification"},
{1151, nullptr, "GetTouchScreenFirmwareVersion"},
{1152, nullptr, "SetTouchScreenDefaultConfiguration"},
- {1153, nullptr, "GetTouchScreenDefaultConfiguration"},
+ {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"},
{1154, nullptr, "IsFirmwareAvailableForNotification"},
{1155, nullptr, "SetForceHandheldStyleVibration"},
{1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"},
@@ -2749,37 +2753,102 @@ public:
// clang-format on
RegisterHandlers(functions);
+
+ joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent");
}
private:
void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
- // We already do this for homebrew so we can just stub it out
LOG_WARNING(Service_HID, "called");
+ GetAppletResource()
+ ->GetController<Controller_NPad>(HidController::NPad)
+ .ApplyNpadSystemCommonPolicy();
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
+ void GetLastActiveNpad(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(system.HIDCore().GetLastActiveController());
+ }
+
void GetUniquePadsFromNpad(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
- const s64 total_entries = 0;
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type);
+ const std::vector<Core::HID::UniquePadId> unique_pads{};
+
+ ctx.WriteBuffer(unique_pads);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(unique_pads.size()));
+ }
+
+ void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(joy_detach_event->GetReadableEvent());
+ }
+
+ void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
+ const bool is_enabled = false;
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled);
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(total_entries);
+ rb.Push(is_enabled);
}
+
+ void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ Core::HID::TouchScreenConfigurationForNx touchscreen_config{
+ .mode = Core::HID::TouchScreenModeForNx::Finger,
+ };
+
+ if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 &&
+ touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) {
+ touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(touchscreen_config);
+ }
+
+ std::shared_ptr<IAppletResource> GetAppletResource() {
+ if (applet_resource == nullptr) {
+ applet_resource = std::make_shared<IAppletResource>(system, service_context);
+ }
+
+ return applet_resource;
+ }
+
+ Kernel::KEvent* joy_detach_event;
+ KernelHelpers::ServiceContext service_context;
+ std::shared_ptr<IAppletResource> applet_resource;
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
+ std::shared_ptr<IAppletResource> applet_resource;
- server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system));
+ server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource));
server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system));
server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system));
- server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system));
+ server_manager->RegisterNamedService("hid:sys",
+ std::make_shared<HidSys>(system, applet_resource));
server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system));
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:
class Hid final : public ServiceFramework<Hid> {
public:
- explicit Hid(Core::System& system_);
+ explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_);
~Hid() override;
std::shared_ptr<IAppletResource> GetAppletResource();
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 055c0a2db..c73035c77 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -357,7 +357,8 @@ public:
return ResultSuccess;
}
- ResultVal<VAddr> MapProcessCodeMemory(Kernel::KProcess* process, VAddr base_addr, u64 size) {
+ Result MapProcessCodeMemory(VAddr* out_map_location, Kernel::KProcess* process, VAddr base_addr,
+ u64 size) {
auto& page_table{process->GetPageTable()};
VAddr addr{};
@@ -372,20 +373,21 @@ public:
R_TRY(result);
if (ValidateRegionForMap(page_table, addr, size)) {
- return addr;
+ *out_map_location = addr;
+ return ResultSuccess;
}
}
return ERROR_INSUFFICIENT_ADDRESS_SPACE;
}
- ResultVal<VAddr> MapNro(Kernel::KProcess* process, VAddr nro_addr, std::size_t nro_size,
- VAddr bss_addr, std::size_t bss_size, std::size_t size) {
+ Result MapNro(VAddr* out_map_location, Kernel::KProcess* process, VAddr nro_addr,
+ std::size_t nro_size, VAddr bss_addr, std::size_t bss_size, std::size_t size) {
for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
auto& page_table{process->GetPageTable()};
VAddr addr{};
- CASCADE_RESULT(addr, MapProcessCodeMemory(process, nro_addr, nro_size));
+ R_TRY(MapProcessCodeMemory(&addr, process, nro_addr, nro_size));
if (bss_size) {
auto block_guard = detail::ScopeExit([&] {
@@ -411,7 +413,8 @@ public:
}
if (ValidateRegionForMap(page_table, addr, size)) {
- return addr;
+ *out_map_location = addr;
+ return ResultSuccess;
}
}
@@ -437,9 +440,9 @@ public:
CopyCode(nro_addr + nro_header.segment_headers[DATA_INDEX].memory_offset, data_start,
nro_header.segment_headers[DATA_INDEX].memory_size);
- CASCADE_CODE(process->GetPageTable().SetProcessMemoryPermission(
+ R_TRY(process->GetPageTable().SetProcessMemoryPermission(
text_start, ro_start - text_start, Kernel::Svc::MemoryPermission::ReadExecute));
- CASCADE_CODE(process->GetPageTable().SetProcessMemoryPermission(
+ R_TRY(process->GetPageTable().SetProcessMemoryPermission(
ro_start, data_start - ro_start, Kernel::Svc::MemoryPermission::Read));
return process->GetPageTable().SetProcessMemoryPermission(
@@ -542,31 +545,32 @@ public:
}
// Map memory for the NRO
- const auto map_result{MapNro(system.ApplicationProcess(), nro_address, nro_size,
- bss_address, bss_size, nro_size + bss_size)};
- if (map_result.Failed()) {
+ VAddr map_location{};
+ const auto map_result{MapNro(&map_location, system.ApplicationProcess(), nro_address,
+ nro_size, bss_address, bss_size, nro_size + bss_size)};
+ if (map_result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(map_result.Code());
+ rb.Push(map_result);
}
// Load the NRO into the mapped memory
if (const auto result{
- LoadNro(system.ApplicationProcess(), header, nro_address, *map_result)};
+ LoadNro(system.ApplicationProcess(), header, nro_address, map_location)};
result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(map_result.Code());
+ rb.Push(result);
}
// Track the loaded NRO
- nro.insert_or_assign(*map_result,
- NROInfo{hash, *map_result, nro_size, bss_address, bss_size,
+ nro.insert_or_assign(map_location,
+ NROInfo{hash, map_location, nro_size, bss_address, bss_size,
header.segment_headers[TEXT_INDEX].memory_size,
header.segment_headers[RO_INDEX].memory_size,
header.segment_headers[DATA_INDEX].memory_size, nro_address});
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push(*map_result);
+ rb.Push(map_location);
}
Result UnmapNro(const NROInfo& info) {
@@ -574,19 +578,19 @@ public:
auto& page_table{system.ApplicationProcess()->GetPageTable()};
if (info.bss_size != 0) {
- CASCADE_CODE(page_table.UnmapCodeMemory(
+ R_TRY(page_table.UnmapCodeMemory(
info.nro_address + info.text_size + info.ro_size + info.data_size, info.bss_address,
info.bss_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
}
- CASCADE_CODE(page_table.UnmapCodeMemory(
+ R_TRY(page_table.UnmapCodeMemory(
info.nro_address + info.text_size + info.ro_size,
info.src_addr + info.text_size + info.ro_size, info.data_size,
Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
- CASCADE_CODE(page_table.UnmapCodeMemory(
+ R_TRY(page_table.UnmapCodeMemory(
info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size,
Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
- CASCADE_CODE(page_table.UnmapCodeMemory(
+ R_TRY(page_table.UnmapCodeMemory(
info.nro_address, info.src_addr, info.text_size,
Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
return ResultSuccess;
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index 5c7adf97d..c28eed926 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -7,17 +7,21 @@
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/store_data.h"
+#include "core/hle/service/mii/types/ver3_store_data.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::Mii {
-constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
-
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public:
- explicit IDatabaseService(Core::System& system_)
- : ServiceFramework{system_, "IDatabaseService"} {
+ explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager,
+ bool is_system_)
+ : ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{
+ is_system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDatabaseService::IsUpdated, "IsUpdated"},
@@ -28,142 +32,134 @@ public:
{5, &IDatabaseService::UpdateLatest, "UpdateLatest"},
{6, &IDatabaseService::BuildRandom, "BuildRandom"},
{7, &IDatabaseService::BuildDefault, "BuildDefault"},
- {8, nullptr, "Get2"},
- {9, nullptr, "Get3"},
- {10, nullptr, "UpdateLatest1"},
- {11, nullptr, "FindIndex"},
- {12, nullptr, "Move"},
- {13, nullptr, "AddOrReplace"},
- {14, nullptr, "Delete"},
- {15, nullptr, "DestroyFile"},
- {16, nullptr, "DeleteFile"},
- {17, nullptr, "Format"},
+ {8, &IDatabaseService::Get2, "Get2"},
+ {9, &IDatabaseService::Get3, "Get3"},
+ {10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"},
+ {11, &IDatabaseService::FindIndex, "FindIndex"},
+ {12, &IDatabaseService::Move, "Move"},
+ {13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
+ {14, &IDatabaseService::Delete, "Delete"},
+ {15, &IDatabaseService::DestroyFile, "DestroyFile"},
+ {16, &IDatabaseService::DeleteFile, "DeleteFile"},
+ {17, &IDatabaseService::Format, "Format"},
{18, nullptr, "Import"},
{19, nullptr, "Export"},
- {20, nullptr, "IsBrokenDatabaseWithClearFlag"},
+ {20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"},
{21, &IDatabaseService::GetIndex, "GetIndex"},
{22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
{23, &IDatabaseService::Convert, "Convert"},
- {24, nullptr, "ConvertCoreDataToCharInfo"},
- {25, nullptr, "ConvertCharInfoToCoreData"},
- {26, nullptr, "Append"},
+ {24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"},
+ {25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"},
+ {26, &IDatabaseService::Append, "Append"},
};
// clang-format on
RegisterHandlers(functions);
- }
-private:
- template <typename T>
- std::vector<u8> SerializeArray(const std::vector<T>& values) {
- std::vector<u8> out(values.size() * sizeof(T));
- std::size_t offset{};
- for (const auto& value : values) {
- std::memcpy(out.data() + offset, &value, sizeof(T));
- offset += sizeof(T);
- }
- return out;
+ manager->Initialize(metadata);
}
+private:
void IsUpdated(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+ const bool is_updated = manager->IsUpdated(metadata, source_flag);
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter));
+ rb.Push<u8>(is_updated);
}
void IsFullDatabase(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called");
+ const bool is_full_database = manager->IsFullDatabase();
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(manager.IsFullDatabase());
+ rb.Push<u8>(is_full_database);
}
void GetCount(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
- LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+ const u32 mii_count = manager->GetCount(metadata, source_flag);
+
+ LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(manager.GetCount(source_flag));
+ rb.Push(mii_count);
}
void Get(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
+ const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
- LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+ u32 mii_count{};
+ std::vector<CharInfoElement> char_info_elements(output_size);
+ const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag);
- const auto result{manager.GetDefault(source_flag)};
- if (result.Failed()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
- return;
+ if (mii_count != 0) {
+ ctx.WriteBuffer(char_info_elements);
}
- if (result->size() > 0) {
- ctx.WriteBuffer(SerializeArray(*result));
- }
+ LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
+ output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(result->size()));
+ rb.Push(result);
+ rb.Push(mii_count);
}
void Get1(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
+ const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
- LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
-
- const auto result{manager.GetDefault(source_flag)};
- if (result.Failed()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
- return;
- }
+ u32 mii_count{};
+ std::vector<CharInfo> char_info(output_size);
+ const auto result = manager->Get(metadata, char_info, mii_count, source_flag);
- std::vector<CharInfo> values;
- for (const auto& element : *result) {
- values.emplace_back(element.info);
+ if (mii_count != 0) {
+ ctx.WriteBuffer(char_info);
}
- ctx.WriteBuffer(SerializeArray(values));
+ LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
+ output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(result->size()));
+ rb.Push(result);
+ rb.Push(mii_count);
}
void UpdateLatest(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto info{rp.PopRaw<CharInfo>()};
+ const auto char_info{rp.PopRaw<CharInfo>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
- LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+ LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
- const auto result{manager.UpdateLatest(info, source_flag)};
- if (result.Failed()) {
+ CharInfo new_char_info{};
+ const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag);
+ if (result.IsFailure()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(*result);
+ rb.PushRaw(new_char_info);
}
void BuildRandom(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
-
const auto age{rp.PopRaw<Age>()};
const auto gender{rp.PopRaw<Gender>()};
const auto race{rp.PopRaw<Race>()};
@@ -172,28 +168,28 @@ private:
if (age > Age::All) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "invalid age={}", age);
+ rb.Push(ResultInvalidArgument);
return;
}
if (gender > Gender::All) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "invalid gender={}", gender);
+ rb.Push(ResultInvalidArgument);
return;
}
if (race > Race::All) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "invalid race={}", race);
+ rb.Push(ResultInvalidArgument);
return;
}
+ CharInfo char_info{};
+ manager->BuildRandom(char_info, age, gender, race);
+
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race));
+ rb.PushRaw(char_info);
}
void BuildDefault(HLERequestContext& ctx) {
@@ -203,16 +199,249 @@ private:
LOG_DEBUG(Service_Mii, "called with index={}", index);
if (index > 5) {
- LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
- index);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
+ rb.Push(ResultInvalidArgument);
return;
}
+ CharInfo char_info{};
+ manager->BuildDefault(char_info, index);
+
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(manager.BuildDefault(index));
+ rb.PushRaw(char_info);
+ }
+
+ void Get2(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
+ const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()};
+
+ u32 mii_count{};
+ std::vector<StoreDataElement> store_data_elements(output_size);
+ const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag);
+
+ if (mii_count != 0) {
+ ctx.WriteBuffer(store_data_elements);
+ }
+
+ LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
+ output_size, mii_count);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(mii_count);
+ }
+
+ void Get3(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
+ const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()};
+
+ u32 mii_count{};
+ std::vector<StoreData> store_data(output_size);
+ const auto result = manager->Get(metadata, store_data, mii_count, source_flag);
+
+ if (mii_count != 0) {
+ ctx.WriteBuffer(store_data);
+ }
+
+ LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
+ output_size, mii_count);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(mii_count);
+ }
+
+ void UpdateLatest1(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto store_data{rp.PopRaw<StoreData>()};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
+
+ LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
+
+ Result result = ResultSuccess;
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ StoreData new_store_data{};
+ if (result.IsSuccess()) {
+ result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag);
+ }
+
+ if (result.IsFailure()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)};
+ rb.Push(ResultSuccess);
+ rb.PushRaw<StoreData>(new_store_data);
+ }
+
+ void FindIndex(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto create_id{rp.PopRaw<Common::UUID>()};
+ const auto is_special{rp.PopRaw<bool>()};
+
+ LOG_INFO(Service_Mii, "called with create_id={}, is_special={}",
+ create_id.FormattedString(), is_special);
+
+ const s32 index = manager->FindIndex(create_id, is_special);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(index);
+ }
+
+ void Move(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto create_id{rp.PopRaw<Common::UUID>()};
+ const auto new_index{rp.PopRaw<s32>()};
+
+ LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(),
+ new_index);
+
+ Result result = ResultSuccess;
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ if (result.IsSuccess()) {
+ const u32 count = manager->GetCount(metadata, SourceFlag::Database);
+ if (new_index < 0 || new_index >= static_cast<s32>(count)) {
+ result = ResultInvalidArgument;
+ }
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->Move(metadata, new_index, create_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void AddOrReplace(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto store_data{rp.PopRaw<StoreData>()};
+
+ LOG_INFO(Service_Mii, "called");
+
+ Result result = ResultSuccess;
+
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->AddOrReplace(metadata, store_data);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void Delete(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto create_id{rp.PopRaw<Common::UUID>()};
+
+ LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString());
+
+ Result result = ResultSuccess;
+
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->Delete(metadata, create_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void DestroyFile(HLERequestContext& ctx) {
+ // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
+ const bool is_db_test_mode_enabled = false;
+
+ LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
+
+ Result result = ResultSuccess;
+
+ if (!is_db_test_mode_enabled) {
+ result = ResultTestModeOnly;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->DestroyFile(metadata);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void DeleteFile(HLERequestContext& ctx) {
+ // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
+ const bool is_db_test_mode_enabled = false;
+
+ LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
+
+ Result result = ResultSuccess;
+
+ if (!is_db_test_mode_enabled) {
+ result = ResultTestModeOnly;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->DeleteFile();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void Format(HLERequestContext& ctx) {
+ // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
+ const bool is_db_test_mode_enabled = false;
+
+ LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
+
+ Result result = ResultSuccess;
+
+ if (!is_db_test_mode_enabled) {
+ result = ResultTestModeOnly;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->Format(metadata);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Mii, "called");
+
+ bool is_broken_with_clear_flag = false;
+ Result result = ResultSuccess;
+
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ if (result.IsSuccess()) {
+ is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push<u8>(is_broken_with_clear_flag);
}
void GetIndex(HLERequestContext& ctx) {
@@ -221,19 +450,21 @@ private:
LOG_DEBUG(Service_Mii, "called");
- u32 index{};
+ s32 index{};
+ const auto result = manager->GetIndex(metadata, info, index);
+
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(manager.GetIndex(info, index));
+ rb.Push(result);
rb.Push(index);
}
void SetInterfaceVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- current_interface_version = rp.PopRaw<u32>();
+ const auto interface_version{rp.PopRaw<u32>()};
- LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version);
+ LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
- UNIMPLEMENTED_IF(current_interface_version != 1);
+ manager->SetInterfaceVersion(metadata, interface_version);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -241,57 +472,101 @@ private:
void Convert(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
-
const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
LOG_INFO(Service_Mii, "called");
+ CharInfo char_info{};
+ const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3);
+
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
- rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3));
+ rb.Push(result);
+ rb.PushRaw<CharInfo>(char_info);
}
- constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
- return current_interface_version >= interface_version;
+ void ConvertCoreDataToCharInfo(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto core_data{rp.PopRaw<CoreData>()};
+
+ LOG_INFO(Service_Mii, "called");
+
+ CharInfo char_info{};
+ const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data);
+
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
+ rb.Push(result);
+ rb.PushRaw<CharInfo>(char_info);
}
- MiiManager manager;
+ void ConvertCharInfoToCoreData(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto char_info{rp.PopRaw<CharInfo>()};
- u32 current_interface_version{};
- u64 current_update_counter{};
-};
+ LOG_INFO(Service_Mii, "called");
-class MiiDBModule final : public ServiceFramework<MiiDBModule> {
-public:
- explicit MiiDBModule(Core::System& system_, const char* name_)
- : ServiceFramework{system_, name_} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
- };
- // clang-format on
+ CoreData core_data{};
+ const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info);
- RegisterHandlers(functions);
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)};
+ rb.Push(result);
+ rb.PushRaw<CoreData>(core_data);
}
-private:
- void GetDatabaseService(HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDatabaseService>(system);
+ void Append(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto char_info{rp.PopRaw<CharInfo>()};
- LOG_DEBUG(Service_Mii, "called");
+ LOG_INFO(Service_Mii, "called");
+
+ const auto result = manager->Append(metadata, char_info);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
+
+ std::shared_ptr<MiiManager> manager = nullptr;
+ DatabaseSessionMetadata metadata{};
+ bool is_system{};
};
+MiiDBModule::MiiDBModule(Core::System& system_, const char* name_,
+ std::shared_ptr<MiiManager> mii_manager, bool is_system_)
+ : ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ if (manager == nullptr) {
+ manager = std::make_shared<MiiManager>();
+ }
+}
+
+MiiDBModule::~MiiDBModule() = default;
+
+void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IDatabaseService>(system, manager, is_system);
+
+ LOG_DEBUG(Service_Mii, "called");
+}
+
+std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() {
+ return manager;
+}
+
class MiiImg final : public ServiceFramework<MiiImg> {
public:
explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Initialize"},
+ {0, &MiiImg::Initialize, "Initialize"},
{10, nullptr, "Reload"},
- {11, nullptr, "GetCount"},
+ {11, &MiiImg::GetCount, "GetCount"},
{12, nullptr, "IsEmpty"},
{13, nullptr, "IsFull"},
{14, nullptr, "GetAttribute"},
@@ -308,13 +583,32 @@ public:
RegisterHandlers(functions);
}
+
+private:
+ void Initialize(HLERequestContext& ctx) {
+ LOG_INFO(Service_Mii, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void GetCount(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Mii, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(0);
+ }
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
+ std::shared_ptr<MiiManager> manager = nullptr;
- server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e"));
- server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u"));
+ server_manager->RegisterNamedService(
+ "mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true));
+ server_manager->RegisterNamedService(
+ "mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false));
server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h
index ed4e3f62b..9aa4426f6 100644
--- a/src/core/hle/service/mii/mii.h
+++ b/src/core/hle/service/mii/mii.h
@@ -3,11 +3,29 @@
#pragma once
+#include "core/hle/service/service.h"
+
namespace Core {
class System;
}
namespace Service::Mii {
+class MiiManager;
+
+class MiiDBModule final : public ServiceFramework<MiiDBModule> {
+public:
+ explicit MiiDBModule(Core::System& system_, const char* name_,
+ std::shared_ptr<MiiManager> mii_manager, bool is_system_);
+ ~MiiDBModule() override;
+
+ std::shared_ptr<MiiManager> GetMiiManager();
+
+private:
+ void GetDatabaseService(HLERequestContext& ctx);
+
+ std::shared_ptr<MiiManager> manager = nullptr;
+ bool is_system{};
+};
void LoopProcess(Core::System& system);
diff --git a/src/core/hle/service/mii/mii_database.cpp b/src/core/hle/service/mii/mii_database.cpp
new file mode 100644
index 000000000..3803e58e2
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database.cpp
@@ -0,0 +1,142 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/mii/mii_database.h"
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/mii_util.h"
+
+namespace Service::Mii {
+
+u8 NintendoFigurineDatabase::GetDatabaseLength() const {
+ return database_length;
+}
+
+bool NintendoFigurineDatabase::IsFull() const {
+ return database_length >= MaxDatabaseLength;
+}
+
+StoreData NintendoFigurineDatabase::Get(std::size_t index) const {
+ StoreData store_data = miis.at(index);
+
+ // This hack is to make external database dumps compatible
+ store_data.SetDeviceChecksum();
+
+ return store_data;
+}
+
+u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const {
+ if (magic == MiiMagic) {
+ return GetDatabaseLength();
+ }
+
+ u32 mii_count{};
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ const auto& store_data = Get(index);
+ if (!store_data.IsSpecial()) {
+ mii_count++;
+ }
+ }
+
+ return mii_count;
+}
+
+bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index,
+ const Common::UUID& create_id) const {
+ for (std::size_t index = 0; index < database_length; ++index) {
+ if (miis[index].GetCreateId() == create_id) {
+ out_index = static_cast<u32>(index);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) {
+ if (current_index == new_index) {
+ return ResultNotUpdated;
+ }
+
+ const StoreData store_data = miis[current_index];
+
+ if (new_index > current_index) {
+ // Shift left
+ const u32 index_diff = new_index - current_index;
+ for (std::size_t i = 0; i < index_diff; i++) {
+ miis[current_index + i] = miis[current_index + i + 1];
+ }
+ } else {
+ // Shift right
+ const u32 index_diff = current_index - new_index;
+ for (std::size_t i = 0; i < index_diff; i++) {
+ miis[current_index - i] = miis[current_index - i - 1];
+ }
+ }
+
+ miis[new_index] = store_data;
+ crc = GenerateDatabaseCrc();
+ return ResultSuccess;
+}
+
+void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) {
+ miis[index] = store_data;
+ crc = GenerateDatabaseCrc();
+}
+
+void NintendoFigurineDatabase::Add(const StoreData& store_data) {
+ miis[database_length] = store_data;
+ database_length++;
+ crc = GenerateDatabaseCrc();
+}
+
+void NintendoFigurineDatabase::Delete(u32 index) {
+ // Shift left
+ const s32 new_database_size = database_length - 1;
+ if (static_cast<s32>(index) < new_database_size) {
+ for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) {
+ miis[i] = miis[i + 1];
+ }
+ }
+
+ database_length = static_cast<u8>(new_database_size);
+ crc = GenerateDatabaseCrc();
+}
+
+void NintendoFigurineDatabase::CleanDatabase() {
+ miis = {};
+ version = 1;
+ magic = DatabaseMagic;
+ database_length = 0;
+ crc = GenerateDatabaseCrc();
+}
+
+void NintendoFigurineDatabase::CorruptCrc() {
+ crc = GenerateDatabaseCrc();
+ crc = ~crc;
+}
+
+Result NintendoFigurineDatabase::CheckIntegrity() {
+ if (magic != DatabaseMagic) {
+ return ResultInvalidDatabaseSignature;
+ }
+
+ if (version != 1) {
+ return ResultInvalidDatabaseVersion;
+ }
+
+ if (crc != GenerateDatabaseCrc()) {
+ return ResultInvalidDatabaseChecksum;
+ }
+
+ if (database_length >= MaxDatabaseLength) {
+ return ResultInvalidDatabaseLength;
+ }
+
+ return ResultSuccess;
+}
+
+u16 NintendoFigurineDatabase::GenerateDatabaseCrc() {
+ return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc));
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h
new file mode 100644
index 000000000..3bd240f93
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database.h
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+#include "core/hle/service/mii/types/store_data.h"
+
+namespace Service::Mii {
+
+constexpr std::size_t MaxDatabaseLength{100};
+constexpr u32 MiiMagic{0xa523b78f};
+constexpr u32 DatabaseMagic{0x4244464e}; // NFDB
+
+class NintendoFigurineDatabase {
+public:
+ /// Returns the total mii count.
+ u8 GetDatabaseLength() const;
+
+ /// Returns true if database is full.
+ bool IsFull() const;
+
+ /// Returns the mii of the specified index.
+ StoreData Get(std::size_t index) const;
+
+ /// Returns the total mii count. Ignoring special mii.
+ u32 GetCount(const DatabaseSessionMetadata& metadata) const;
+
+ /// Returns the index of a mii. If the mii isn't found returns false.
+ bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const;
+
+ /// Moves the location of a specific mii.
+ Result Move(u32 current_index, u32 new_index);
+
+ /// Replaces mii with new data.
+ void Replace(u32 index, const StoreData& store_data);
+
+ /// Adds a new mii to the end of the database.
+ void Add(const StoreData& store_data);
+
+ /// Removes mii from database and shifts left the remainding data.
+ void Delete(u32 index);
+
+ /// Deletes all contents with a fresh database
+ void CleanDatabase();
+
+ /// Intentionally sets a bad checksum
+ void CorruptCrc();
+
+ /// Returns success if database is valid otherwise returns the corresponding error code.
+ Result CheckIntegrity();
+
+private:
+ /// Returns the checksum of the database
+ u16 GenerateDatabaseCrc();
+
+ u32 magic{}; // 'NFDB'
+ std::array<StoreData, MaxDatabaseLength> miis{};
+ u8 version{};
+ u8 database_length{};
+ u16 crc{};
+};
+static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98,
+ "NintendoFigurineDatabase has incorrect size.");
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp
new file mode 100644
index 000000000..c39898594
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.cpp
@@ -0,0 +1,420 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+
+#include "core/hle/service/mii/mii_database_manager.h"
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/store_data.h"
+
+namespace Service::Mii {
+const char* DbFileName = "MiiDatabase.dat";
+
+DatabaseManager::DatabaseManager() {}
+
+Result DatabaseManager::MountSaveData() {
+ if (!is_save_data_mounted) {
+ system_save_dir =
+ Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030";
+ if (is_test_db) {
+ system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
+ "system/save/8000000000000031";
+ }
+
+ // mount point should be "mii:"
+
+ if (!Common::FS::CreateDirs(system_save_dir)) {
+ return ResultUnknown;
+ }
+ }
+
+ is_save_data_mounted = true;
+ return ResultSuccess;
+}
+
+Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) {
+ is_database_broken = false;
+ if (!is_save_data_mounted) {
+ return ResultInvalidArgument;
+ }
+
+ database.CleanDatabase();
+ update_counter++;
+ metadata.update_counter = update_counter;
+
+ const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+
+ if (!db_file.IsOpen()) {
+ return SaveDatabase();
+ }
+
+ if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) {
+ is_database_broken = true;
+ }
+
+ if (db_file.Read(database) != 1) {
+ is_database_broken = true;
+ }
+
+ if (is_database_broken) {
+ // Dragons happen here for simplicity just clean the database
+ LOG_ERROR(Service_Mii, "Mii database is corrupted");
+ database.CleanDatabase();
+ return ResultUnknown;
+ }
+
+ const auto result = database.CheckIntegrity();
+
+ if (result.IsError()) {
+ LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw);
+ database.CleanDatabase();
+ return ResultSuccess;
+ }
+
+ LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}",
+ database.GetDatabaseLength());
+ return ResultSuccess;
+}
+
+bool DatabaseManager::IsFullDatabase() const {
+ return database.GetDatabaseLength() == MaxDatabaseLength;
+}
+
+bool DatabaseManager::IsModified() const {
+ return is_moddified;
+}
+
+u64 DatabaseManager::GetUpdateCounter() const {
+ return update_counter;
+}
+
+u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const {
+ const u32 database_size = database.GetDatabaseLength();
+ if (metadata.magic == MiiMagic) {
+ return database_size;
+ }
+
+ // Special mii can't be used. Skip those.
+
+ u32 mii_count{};
+ for (std::size_t index = 0; index < database_size; ++index) {
+ const auto& store_data = database.Get(index);
+ if (store_data.IsSpecial()) {
+ continue;
+ }
+ mii_count++;
+ }
+
+ return mii_count;
+}
+
+void DatabaseManager::Get(StoreData& out_store_data, std::size_t index,
+ const DatabaseSessionMetadata& metadata) const {
+ if (metadata.magic == MiiMagic) {
+ out_store_data = database.Get(index);
+ return;
+ }
+
+ // The index refeers to the mii index without special mii.
+ // Search on the database until we find it
+
+ u32 virtual_index = 0;
+ const u32 database_size = database.GetDatabaseLength();
+ for (std::size_t i = 0; i < database_size; ++i) {
+ const auto& store_data = database.Get(i);
+ if (store_data.IsSpecial()) {
+ continue;
+ }
+ if (virtual_index == index) {
+ out_store_data = store_data;
+ return;
+ }
+ virtual_index++;
+ }
+
+ // This function doesn't fail. It returns the first mii instead
+ out_store_data = database.Get(0);
+}
+
+Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id,
+ bool is_special) const {
+ u32 index{};
+ const bool is_found = database.GetIndexByCreatorId(index, create_id);
+
+ if (!is_found) {
+ return ResultNotFound;
+ }
+
+ if (is_special) {
+ out_index = index;
+ return ResultSuccess;
+ }
+
+ if (database.Get(index).IsSpecial()) {
+ return ResultNotFound;
+ }
+
+ out_index = 0;
+
+ if (index < 1) {
+ return ResultSuccess;
+ }
+
+ for (std::size_t i = 0; i <= index; ++i) {
+ if (database.Get(i).IsSpecial()) {
+ continue;
+ }
+ out_index++;
+ }
+ return ResultSuccess;
+}
+
+Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
+ const Common::UUID& create_id) const {
+ u32 index{};
+ const bool is_found = database.GetIndexByCreatorId(index, create_id);
+
+ if (!is_found) {
+ return ResultNotFound;
+ }
+
+ if (metadata.magic == MiiMagic) {
+ out_index = index;
+ return ResultSuccess;
+ }
+
+ if (database.Get(index).IsSpecial()) {
+ return ResultNotFound;
+ }
+
+ out_index = 0;
+
+ if (index < 1) {
+ return ResultSuccess;
+ }
+
+ // The index refeers to the mii index without special mii.
+ // Search on the database until we find it
+
+ for (std::size_t i = 0; i <= index; ++i) {
+ const auto& store_data = database.Get(i);
+ if (store_data.IsSpecial()) {
+ continue;
+ }
+ out_index++;
+ }
+ return ResultSuccess;
+}
+
+Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index,
+ const Common::UUID& create_id) const {
+ const auto database_size = database.GetDatabaseLength();
+
+ if (database_size >= 1) {
+ u32 virtual_index{};
+ for (std::size_t i = 0; i < database_size; ++i) {
+ const StoreData& store_data = database.Get(i);
+ if (store_data.IsSpecial()) {
+ continue;
+ }
+ if (virtual_index == new_index) {
+ const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
+ if (!is_found) {
+ return ResultNotFound;
+ }
+ if (store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+ return ResultSuccess;
+ }
+ virtual_index++;
+ }
+ }
+
+ const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
+ if (!is_found) {
+ return ResultNotFound;
+ }
+ const StoreData& store_data = database.Get(out_index);
+ if (store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+ return ResultSuccess;
+}
+
+Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index,
+ const Common::UUID& create_id) {
+ u32 current_index{};
+ if (metadata.magic == MiiMagic) {
+ const bool is_found = database.GetIndexByCreatorId(current_index, create_id);
+ if (!is_found) {
+ return ResultNotFound;
+ }
+ } else {
+ const auto result = FindMoveIndex(current_index, new_index, create_id);
+ if (result.IsError()) {
+ return result;
+ }
+ }
+
+ const auto result = database.Move(current_index, new_index);
+ if (result.IsFailure()) {
+ return result;
+ }
+
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+ return ResultSuccess;
+}
+
+Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata,
+ const StoreData& store_data) {
+ if (store_data.IsValid() != ValidationResult::NoErrors) {
+ return ResultInvalidStoreData;
+ }
+ if (metadata.magic != MiiMagic && store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+
+ u32 index{};
+ const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId());
+ if (is_found) {
+ const StoreData& old_store_data = database.Get(index);
+
+ if (store_data.IsSpecial() != old_store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+
+ database.Replace(index, store_data);
+ } else {
+ if (database.IsFull()) {
+ return ResultDatabaseFull;
+ }
+
+ database.Add(store_data);
+ }
+
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+ return ResultSuccess;
+}
+
+Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
+ u32 index{};
+ const bool is_found = database.GetIndexByCreatorId(index, create_id);
+ if (!is_found) {
+ return ResultNotFound;
+ }
+
+ if (metadata.magic != MiiMagic) {
+ const auto& store_data = database.Get(index);
+ if (store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+ }
+
+ database.Delete(index);
+
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+ return ResultSuccess;
+}
+
+Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
+ if (char_info.Verify() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo2;
+ }
+ if (char_info.GetType() == 1) {
+ return ResultInvalidCharInfoType;
+ }
+
+ u32 index{};
+ StoreData store_data{};
+
+ // Loop until the mii we created is not on the database
+ do {
+ store_data.BuildWithCharInfo(char_info);
+ } while (database.GetIndexByCreatorId(index, store_data.GetCreateId()));
+
+ const Result result = store_data.Restore();
+
+ if (result.IsSuccess() || result == ResultNotUpdated) {
+ return AddOrReplace(metadata, store_data);
+ }
+
+ return result;
+}
+
+Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) {
+ database.CorruptCrc();
+
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+
+ const auto result = SaveDatabase();
+ database.CleanDatabase();
+
+ return result;
+}
+
+Result DatabaseManager::DeleteFile() {
+ const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName);
+ // TODO: Return proper FS error here
+ return result ? ResultSuccess : ResultUnknown;
+}
+
+void DatabaseManager::Format(DatabaseSessionMetadata& metadata) {
+ database.CleanDatabase();
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+}
+
+Result DatabaseManager::SaveDatabase() {
+ // TODO: Replace unknown error codes with proper FS error codes when available
+
+ if (!Common::FS::Exists(system_save_dir / DbFileName)) {
+ if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
+ LOG_ERROR(Service_Mii, "Failed to create mii database");
+ return ResultUnknown;
+ }
+ }
+
+ const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName);
+ if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) {
+ if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) {
+ LOG_ERROR(Service_Mii, "Failed to delete mii database");
+ return ResultUnknown;
+ }
+ if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
+ LOG_ERROR(Service_Mii, "Failed to create mii database");
+ return ResultUnknown;
+ }
+ }
+
+ const Common::FS::IOFile db_file{system_save_dir / DbFileName,
+ Common::FS::FileAccessMode::ReadWrite,
+ Common::FS::FileType::BinaryFile};
+
+ if (db_file.Write(database) != 1) {
+ LOG_ERROR(Service_Mii, "Failed to save mii database");
+ return ResultUnknown;
+ }
+
+ is_moddified = false;
+ return ResultSuccess;
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h
new file mode 100644
index 000000000..52c32be82
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.h
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/fs/fs.h"
+#include "core/hle/result.h"
+#include "core/hle/service/mii/mii_database.h"
+
+namespace Service::Mii {
+class CharInfo;
+class StoreData;
+
+class DatabaseManager {
+public:
+ DatabaseManager();
+ Result MountSaveData();
+ Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken);
+
+ bool IsFullDatabase() const;
+ bool IsModified() const;
+ u64 GetUpdateCounter() const;
+
+ void Get(StoreData& out_store_data, std::size_t index,
+ const DatabaseSessionMetadata& metadata) const;
+ u32 GetCount(const DatabaseSessionMetadata& metadata) const;
+
+ Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const;
+ Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
+ const Common::UUID& create_id) const;
+ Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const;
+
+ Result Move(DatabaseSessionMetadata& metadata, u32 current_index,
+ const Common::UUID& create_id);
+ Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data);
+ Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
+ Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
+
+ Result DestroyFile(DatabaseSessionMetadata& metadata);
+ Result DeleteFile();
+ void Format(DatabaseSessionMetadata& metadata);
+
+ Result SaveDatabase();
+
+private:
+ // This is the global value of
+ // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
+ bool is_test_db{};
+
+ bool is_moddified{};
+ bool is_save_data_mounted{};
+ u64 update_counter{};
+ NintendoFigurineDatabase database{};
+
+ std::filesystem::path system_save_dir{};
+};
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index c920650f5..dcfd6b2e2 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -1,698 +1,486 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <cstring>
-#include <random>
-
-#include "common/assert.h"
#include "common/logging/log.h"
-#include "common/string_util.h"
-
-#include "core/hle/service/acc/profile_manager.h"
+#include "core/hle/service/mii/mii_database_manager.h"
#include "core/hle/service/mii/mii_manager.h"
-#include "core/hle/service/mii/raw_data.h"
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/core_data.h"
+#include "core/hle/service/mii/types/raw_data.h"
+#include "core/hle/service/mii/types/store_data.h"
+#include "core/hle/service/mii/types/ver3_store_data.h"
namespace Service::Mii {
+constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
-namespace {
+MiiManager::MiiManager() {}
-constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
+Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) {
+ database_manager.MountSaveData();
+ database_manager.Initialize(metadata, is_broken_with_clear_flag);
+ return ResultSuccess;
+}
-constexpr std::size_t BaseMiiCount{2};
-constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
+void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
+ StoreData store_data{};
+ store_data.BuildDefault(index);
+ out_char_info.SetFromStoreData(store_data);
+}
-constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'};
-constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7};
-constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13};
-constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23};
-constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0};
-constexpr std::array<u8, 62> EyeRotateLookup{
- {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
- 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
- 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
- 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}};
-constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07,
- 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06,
- 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}};
-
-template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
-std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
- std::array<T, DestArraySize> out{};
- std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
- return out;
+void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const {
+ StoreData store_data{};
+ store_data.BuildBase(gender);
+ out_char_info.SetFromStoreData(store_data);
}
-CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
- MiiStoreBitFields bf;
- std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
-
- return {
- .uuid = data.data.uuid,
- .name = ResizeArray<char16_t, 10, 11>(data.data.name),
- .font_region = static_cast<u8>(bf.font_region.Value()),
- .favorite_color = static_cast<u8>(bf.favorite_color.Value()),
- .gender = static_cast<u8>(bf.gender.Value()),
- .height = static_cast<u8>(bf.height.Value()),
- .build = static_cast<u8>(bf.build.Value()),
- .type = static_cast<u8>(bf.type.Value()),
- .region_move = static_cast<u8>(bf.region_move.Value()),
- .faceline_type = static_cast<u8>(bf.faceline_type.Value()),
- .faceline_color = static_cast<u8>(bf.faceline_color.Value()),
- .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()),
- .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()),
- .hair_type = static_cast<u8>(bf.hair_type.Value()),
- .hair_color = static_cast<u8>(bf.hair_color.Value()),
- .hair_flip = static_cast<u8>(bf.hair_flip.Value()),
- .eye_type = static_cast<u8>(bf.eye_type.Value()),
- .eye_color = static_cast<u8>(bf.eye_color.Value()),
- .eye_scale = static_cast<u8>(bf.eye_scale.Value()),
- .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()),
- .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()),
- .eye_x = static_cast<u8>(bf.eye_x.Value()),
- .eye_y = static_cast<u8>(bf.eye_y.Value()),
- .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()),
- .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()),
- .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()),
- .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()),
- .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()),
- .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()),
- .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3),
- .nose_type = static_cast<u8>(bf.nose_type.Value()),
- .nose_scale = static_cast<u8>(bf.nose_scale.Value()),
- .nose_y = static_cast<u8>(bf.nose_y.Value()),
- .mouth_type = static_cast<u8>(bf.mouth_type.Value()),
- .mouth_color = static_cast<u8>(bf.mouth_color.Value()),
- .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()),
- .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()),
- .mouth_y = static_cast<u8>(bf.mouth_y.Value()),
- .beard_color = static_cast<u8>(bf.beard_color.Value()),
- .beard_type = static_cast<u8>(bf.beard_type.Value()),
- .mustache_type = static_cast<u8>(bf.mustache_type.Value()),
- .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()),
- .mustache_y = static_cast<u8>(bf.mustache_y.Value()),
- .glasses_type = static_cast<u8>(bf.glasses_type.Value()),
- .glasses_color = static_cast<u8>(bf.glasses_color.Value()),
- .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()),
- .glasses_y = static_cast<u8>(bf.glasses_y.Value()),
- .mole_type = static_cast<u8>(bf.mole_type.Value()),
- .mole_scale = static_cast<u8>(bf.mole_scale.Value()),
- .mole_x = static_cast<u8>(bf.mole_x.Value()),
- .mole_y = static_cast<u8>(bf.mole_y.Value()),
- .padding = 0,
- };
+void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const {
+ StoreData store_data{};
+ store_data.BuildRandom(age, gender, race);
+ out_char_info.SetFromStoreData(store_data);
}
-u16 GenerateCrc16(const void* data, std::size_t size) {
- s32 crc{};
- for (std::size_t i = 0; i < size; i++) {
- crc ^= static_cast<const u8*>(data)[i] << 8;
- for (std::size_t j = 0; j < 8; j++) {
- crc <<= 1;
- if ((crc & 0x10000) != 0) {
- crc = (crc ^ 0x1021) & 0xFFFF;
- }
- }
- }
- return Common::swap16(static_cast<u16>(crc));
+bool MiiManager::IsFullDatabase() const {
+ return database_manager.IsFullDatabase();
}
-template <typename T>
-T GetRandomValue(T min, T max) {
- std::random_device device;
- std::mt19937 gen(device());
- std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max));
- return static_cast<T>(distribution(gen));
+void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const {
+ metadata.interface_version = version;
}
-template <typename T>
-T GetRandomValue(T max) {
- return GetRandomValue<T>({}, max);
+bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return false;
+ }
+
+ const u64 metadata_update_counter = metadata.update_counter;
+ const u64 database_update_counter = database_manager.GetUpdateCounter();
+ metadata.update_counter = database_update_counter;
+ return metadata_update_counter != database_update_counter;
}
-MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) {
- MiiStoreBitFields bf{};
-
- if (gender == Gender::All) {
- gender = GetRandomValue<Gender>(Gender::Maximum);
- }
-
- bf.gender.Assign(gender);
- bf.favorite_color.Assign(GetRandomValue<u8>(11));
- bf.region_move.Assign(0);
- bf.font_region.Assign(FontRegion::Standard);
- bf.type.Assign(0);
- bf.height.Assign(64);
- bf.build.Assign(64);
-
- if (age == Age::All) {
- const auto temp{GetRandomValue<int>(10)};
- if (temp >= 8) {
- age = Age::Old;
- } else if (temp >= 4) {
- age = Age::Normal;
- } else {
- age = Age::Young;
- }
+u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
+ u32 mii_count{};
+ if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
+ mii_count += DefaultMiiCount;
+ }
+ if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
+ mii_count += database_manager.GetCount(metadata);
}
+ return mii_count;
+}
- if (race == Race::All) {
- const auto temp{GetRandomValue<int>(10)};
- if (temp >= 8) {
- race = Race::Black;
- } else if (temp >= 4) {
- race = Race::White;
- } else {
- race = Race::Asian;
- }
+Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index,
+ const Common::UUID& create_id) {
+ const auto result = database_manager.Move(metadata, index, create_id);
+
+ if (result.IsFailure()) {
+ return result;
}
- u32 axis_y{};
- if (gender == Gender::Female && age == Age::Young) {
- axis_y = GetRandomValue<u32>(3);
- }
-
- const std::size_t index{3 * static_cast<std::size_t>(age) +
- 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
-
- const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)};
- const auto faceline_color_info{RawData::RandomMiiFacelineColor.at(
- 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
- const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
- const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
- const auto hair_type_info{RawData::RandomMiiHairType.at(index)};
- const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
- static_cast<std::size_t>(age))};
- const auto eye_type_info{RawData::RandomMiiEyeType.at(index)};
- const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
- const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
- const auto nose_type_info{RawData::RandomMiiNoseType.at(index)};
- const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)};
- const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
-
- bf.faceline_type.Assign(
- faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
- bf.faceline_color.Assign(
- faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
- bf.faceline_wrinkle.Assign(
- faceline_wrinkle_info
- .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
- bf.faceline_makeup.Assign(
- faceline_makeup_info
- .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
-
- bf.hair_type.Assign(
- hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]);
- bf.hair_color.Assign(
- HairColorLookup[hair_color_info
- .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]);
- bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum));
-
- bf.eye_type.Assign(
- eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]);
-
- const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
- const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
- const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
- const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]};
-
- bf.eye_color.Assign(
- EyeColorLookup[eye_color_info
- .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]);
- bf.eye_scale.Assign(4);
- bf.eye_aspect.Assign(3);
- bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate);
- bf.eye_x.Assign(2);
- bf.eye_y.Assign(axis_y + 12);
-
- bf.eyebrow_type.Assign(
- eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
-
- const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
- const auto eyebrow_y{race == Race::Asian ? 9 : 10};
- const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6};
- const auto eyebrow_rotate{
- 32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]};
-
- bf.eyebrow_color.Assign(bf.hair_color);
- bf.eyebrow_scale.Assign(4);
- bf.eyebrow_aspect.Assign(3);
- bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate);
- bf.eyebrow_x.Assign(2);
- bf.eyebrow_y.Assign(axis_y + eyebrow_y);
-
- const auto nose_scale{gender == Gender::Female ? 3 : 4};
-
- bf.nose_type.Assign(
- nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]);
- bf.nose_scale.Assign(nose_scale);
- bf.nose_y.Assign(axis_y + 9);
-
- const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0};
-
- bf.mouth_type.Assign(
- mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
- bf.mouth_color.Assign(MouthColorLookup[mouth_color]);
- bf.mouth_scale.Assign(4);
- bf.mouth_aspect.Assign(3);
- bf.mouth_y.Assign(axis_y + 13);
-
- bf.beard_color.Assign(bf.hair_color);
- bf.mustache_scale.Assign(4);
-
- if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) {
- const auto mustache_and_beard_flag{
- GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)};
-
- auto beard_type{BeardType::None};
- auto mustache_type{MustacheType::None};
-
- if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
- BeardAndMustacheFlag::Beard) {
- beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5);
- }
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
+ }
- if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
- BeardAndMustacheFlag::Mustache) {
- mustache_type =
- GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5);
- }
+ return database_manager.SaveDatabase();
+}
- bf.mustache_type.Assign(mustache_type);
- bf.beard_type.Assign(beard_type);
- bf.mustache_y.Assign(10);
- } else {
- bf.mustache_type.Assign(MustacheType::None);
- bf.beard_type.Assign(BeardType::None);
- bf.mustache_y.Assign(axis_y + 10);
- }
-
- const auto glasses_type_start{GetRandomValue<std::size_t>(100)};
- u8 glasses_type{};
- while (glasses_type_start < glasses_type_info.values[glasses_type]) {
- if (++glasses_type >= glasses_type_info.values_count) {
- ASSERT(false);
- break;
- }
+Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) {
+ const auto result = database_manager.AddOrReplace(metadata, store_data);
+
+ if (result.IsFailure()) {
+ return result;
+ }
+
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
}
- bf.glasses_type.Assign(glasses_type);
- bf.glasses_color.Assign(GlassesColorLookup[0]);
- bf.glasses_scale.Assign(4);
- bf.glasses_y.Assign(axis_y + 10);
+ return database_manager.SaveDatabase();
+}
- bf.mole_type.Assign(0);
- bf.mole_scale.Assign(4);
- bf.mole_x.Assign(2);
- bf.mole_y.Assign(20);
+Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
+ const auto result = database_manager.Delete(metadata, create_id);
- return {DefaultMiiName, bf, user_id};
+ if (result.IsFailure()) {
+ return result;
+ }
+
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
+ }
+
+ return database_manager.SaveDatabase();
}
-MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) {
- MiiStoreBitFields bf{};
-
- bf.font_region.Assign(info.font_region);
- bf.favorite_color.Assign(info.favorite_color);
- bf.gender.Assign(info.gender);
- bf.height.Assign(info.height);
- bf.build.Assign(info.weight);
- bf.type.Assign(info.type);
- bf.region_move.Assign(info.region);
- bf.faceline_type.Assign(info.face_type);
- bf.faceline_color.Assign(info.face_color);
- bf.faceline_wrinkle.Assign(info.face_wrinkle);
- bf.faceline_makeup.Assign(info.face_makeup);
- bf.hair_type.Assign(info.hair_type);
- bf.hair_color.Assign(HairColorLookup[info.hair_color]);
- bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip));
- bf.eye_type.Assign(info.eye_type);
- bf.eye_color.Assign(EyeColorLookup[info.eye_color]);
- bf.eye_scale.Assign(info.eye_scale);
- bf.eye_aspect.Assign(info.eye_aspect);
- bf.eye_rotate.Assign(info.eye_rotate);
- bf.eye_x.Assign(info.eye_x);
- bf.eye_y.Assign(info.eye_y);
- bf.eyebrow_type.Assign(info.eyebrow_type);
- bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]);
- bf.eyebrow_scale.Assign(info.eyebrow_scale);
- bf.eyebrow_aspect.Assign(info.eyebrow_aspect);
- bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
- bf.eyebrow_x.Assign(info.eyebrow_x);
- bf.eyebrow_y.Assign(info.eyebrow_y - 3);
- bf.nose_type.Assign(info.nose_type);
- bf.nose_scale.Assign(info.nose_scale);
- bf.nose_y.Assign(info.nose_y);
- bf.mouth_type.Assign(info.mouth_type);
- bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]);
- bf.mouth_scale.Assign(info.mouth_scale);
- bf.mouth_aspect.Assign(info.mouth_aspect);
- bf.mouth_y.Assign(info.mouth_y);
- bf.beard_color.Assign(HairColorLookup[info.beard_color]);
- bf.beard_type.Assign(static_cast<BeardType>(info.beard_type));
- bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type));
- bf.mustache_scale.Assign(info.mustache_scale);
- bf.mustache_y.Assign(info.mustache_y);
- bf.glasses_type.Assign(info.glasses_type);
- bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]);
- bf.glasses_scale.Assign(info.glasses_scale);
- bf.glasses_y.Assign(info.glasses_y);
- bf.mole_type.Assign(info.mole_type);
- bf.mole_scale.Assign(info.mole_scale);
- bf.mole_x.Assign(info.mole_x);
- bf.mole_y.Assign(info.mole_y);
-
- return {DefaultMiiName, bf, user_id};
+s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const {
+ s32 index{};
+ const auto result = database_manager.FindIndex(index, create_id, is_special);
+ if (result.IsError()) {
+ index = -1;
+ }
+ return index;
}
-} // namespace
+Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
+ s32& out_index) const {
+ if (char_info.Verify() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
+ }
+
+ s32 index{};
+ const bool is_special = metadata.magic == MiiMagic;
+ const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special);
-MiiStoreData::MiiStoreData() = default;
+ if (result.IsError()) {
+ index = -1;
+ }
-MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields,
- const Common::UUID& user_id) {
- data.name = name;
- data.uuid = Common::UUID::MakeRandomRFC4122V4();
+ if (index == -1) {
+ return ResultNotFound;
+ }
- std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields));
- data_crc = GenerateCrc16(data.data.data(), sizeof(data));
- device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID));
+ out_index = index;
+ return ResultSuccess;
}
-MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {}
+Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
+ const auto result = database_manager.Append(metadata, char_info);
-bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) {
- if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
- return false;
+ if (result.IsError()) {
+ return ResultNotFound;
}
- const bool result{current_update_counter != update_counter};
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
+ }
- current_update_counter = update_counter;
+ return database_manager.SaveDatabase();
+}
- return result;
+bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) {
+ const bool is_broken = is_broken_with_clear_flag;
+ if (is_broken_with_clear_flag) {
+ is_broken_with_clear_flag = false;
+ database_manager.Format(metadata);
+ database_manager.SaveDatabase();
+ }
+ return is_broken;
}
-bool MiiManager::IsFullDatabase() const {
- // TODO(bunnei): We don't implement the Mii database, so it cannot be full
- return false;
+Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) {
+ is_broken_with_clear_flag = true;
+ return database_manager.DestroyFile(metadata);
}
-u32 MiiManager::GetCount(SourceFlag source_flag) const {
- std::size_t count{};
- if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
- // TODO(bunnei): We don't implement the Mii database, but when we do, update this
- count += 0;
- }
- if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
- count += (DefaultMiiCount - BaseMiiCount);
+Result MiiManager::DeleteFile() {
+ return database_manager.DeleteFile();
+}
+
+Result MiiManager::Format(DatabaseSessionMetadata& metadata) {
+ database_manager.Format(metadata);
+
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
}
- return static_cast<u32>(count);
+ return database_manager.SaveDatabase();
}
-ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info,
- SourceFlag source_flag) {
- if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
- return ERROR_CANNOT_FIND_ENTRY;
+Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
+ if (!mii_v3.IsValid()) {
+ return ResultInvalidCharInfo;
}
- // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
- return ERROR_CANNOT_FIND_ENTRY;
-}
+ StoreData store_data{};
+ mii_v3.BuildToStoreData(store_data);
+ const auto name = store_data.GetNickname();
+ if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
+ store_data.SetInvalidName();
+ }
-CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
- return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id));
+ out_char_info.SetFromStoreData(store_data);
+ return ResultSuccess;
}
-CharInfo MiiManager::BuildDefault(std::size_t index) {
- return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
+Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info,
+ const CoreData& core_data) const {
+ if (core_data.IsValid() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
+ }
+
+ StoreData store_data{};
+ store_data.BuildWithCoreData(core_data);
+ const auto name = store_data.GetNickname();
+ if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
+ store_data.SetInvalidName();
+ }
+
+ out_char_info.SetFromStoreData(store_data);
+ return ResultSuccess;
}
-CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
- Service::Mii::MiiManager manager;
- auto mii = manager.BuildDefault(0);
+Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data,
+ const CharInfo& char_info) const {
+ if (char_info.Verify() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
+ }
- if (!ValidateV3Info(mii_v3)) {
- return mii;
+ out_core_data.BuildFromCharInfo(char_info);
+ const auto name = out_core_data.GetNickname();
+ if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) {
+ out_core_data.SetNickname(out_core_data.GetInvalidNickname());
}
- // TODO: We are ignoring a bunch of data from the mii_v3
+ return ResultSuccess;
+}
- mii.gender = static_cast<u8>(mii_v3.mii_information.gender);
- mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color);
- mii.height = mii_v3.height;
- mii.build = mii_v3.build;
+Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
+ const CharInfo& char_info, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return ResultNotFound;
+ }
- // Copy name until string terminator
- mii.name = {};
- for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
- mii.name[index] = mii_v3.mii_name[index];
- if (mii.name[index] == 0) {
- break;
+ if (metadata.IsInterfaceVersionSupported(1)) {
+ if (char_info.Verify() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
}
}
- mii.font_region = mii_v3.region_information.character_set;
+ u32 index{};
+ Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId());
- mii.faceline_type = mii_v3.appearance_bits1.face_shape;
- mii.faceline_color = mii_v3.appearance_bits1.skin_color;
- mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
- mii.faceline_make = mii_v3.appearance_bits2.makeup;
+ if (result.IsError()) {
+ return result;
+ }
+
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
+
+ if (store_data.GetType() != char_info.GetType()) {
+ return ResultNotFound;
+ }
- mii.hair_type = mii_v3.hair_style;
- mii.hair_color = mii_v3.appearance_bits3.hair_color;
- mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
+ out_char_info.SetFromStoreData(store_data);
- mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
- mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
- mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
- mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
- mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
- mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
- mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
+ if (char_info == out_char_info) {
+ return ResultNotUpdated;
+ }
- mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
- mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
- mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
- mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
- mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
- mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
- mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
+ return ResultSuccess;
+}
- mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
- mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
- mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
+Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
+ const StoreData& store_data, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return ResultNotFound;
+ }
- mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
- mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
- mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
- mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
- mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
+ if (metadata.IsInterfaceVersionSupported(1)) {
+ if (store_data.IsValid() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
+ }
+ }
- mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
- mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
- mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
+ u32 index{};
+ Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId());
- mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
- mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
+ if (result.IsError()) {
+ return result;
+ }
- mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
- mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
- mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
- mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
+ database_manager.Get(out_store_data, index, metadata);
- mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
- mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
- mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
- mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
+ if (out_store_data.GetType() != store_data.GetType()) {
+ return ResultNotFound;
+ }
- // TODO: Validate mii data
+ if (store_data == out_store_data) {
+ return ResultNotUpdated;
+ }
- return mii;
+ return ResultSuccess;
}
-Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const {
- Service::Mii::MiiManager manager;
- Ver3StoreData mii_v3{};
-
- // TODO: We are ignoring a bunch of data from the mii_v3
+Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
+ std::span<CharInfoElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return BuildDefault(out_elements, out_count, source_flag);
+ }
- mii_v3.version = 1;
- mii_v3.mii_information.gender.Assign(mii.gender);
- mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
- mii_v3.height = mii.height;
- mii_v3.build = mii.build;
+ const auto mii_count = database_manager.GetCount(metadata);
- // Copy name until string terminator
- mii_v3.mii_name = {};
- for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
- mii_v3.mii_name[index] = mii.name[index];
- if (mii_v3.mii_name[index] == 0) {
- break;
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
}
- }
- mii_v3.region_information.character_set.Assign(mii.font_region);
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
- mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
- mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
- mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
+ out_elements[out_count].source = Source::Database;
+ out_elements[out_count].char_info.SetFromStoreData(store_data);
+ out_count++;
+ }
- mii_v3.hair_style = mii.hair_type;
- mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
+ // Include default Mii at the end of the list
+ return BuildDefault(out_elements, out_count, source_flag);
+}
- mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
- mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
- mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
- mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
- mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
- mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
+Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
+ u32& out_count, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return BuildDefault(out_char_info, out_count, source_flag);
+ }
- mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
- mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
- mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
- mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
- mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
- mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
+ const auto mii_count = database_manager.GetCount(metadata);
- mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
- mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
- mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
- mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
- mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
- mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
- mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
- mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
- mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
- mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
+ out_char_info[out_count].SetFromStoreData(store_data);
+ out_count++;
+ }
- mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
+ // Include default Mii at the end of the list
+ return BuildDefault(out_char_info, out_count, source_flag);
+}
- mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
- mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
+Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
+ std::span<StoreDataElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return BuildDefault(out_elements, out_count, source_flag);
+ }
- mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
- mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
- mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
- mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
+ const auto mii_count = database_manager.GetCount(metadata);
- // These types are converted to V3 from a table
- mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]);
- mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]);
- mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]);
- mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]);
- mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]);
- mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]);
- mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]);
- mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]);
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
- mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16));
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
- // TODO: Validate mii_v3 data
+ out_elements[out_count].store_data = store_data;
+ out_elements[out_count].source = Source::Database;
+ out_count++;
+ }
- return mii_v3;
+ // Include default Mii at the end of the list
+ return BuildDefault(out_elements, out_count, source_flag);
}
-NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const {
- return {
- .faceline_color = static_cast<u8>(mii.faceline_color & 0xf),
- .hair_color = static_cast<u8>(mii.hair_color & 0x7f),
- .eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
- .eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
- .mouth_color = static_cast<u8>(mii.mouth_color & 0x7f),
- .beard_color = static_cast<u8>(mii.beard_color & 0x7f),
- .glass_color = static_cast<u8>(mii.glasses_color & 0x7f),
- .glass_type = static_cast<u8>(mii.glasses_type & 0x1f),
- };
+Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
+ u32& out_count, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return BuildDefault(out_store_data, out_count, source_flag);
+ }
+
+ const auto mii_count = database_manager.GetCount(metadata);
+
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ if (out_store_data.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
+
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
+
+ out_store_data[out_count] = store_data;
+ out_count++;
+ }
+
+ // Include default Mii at the end of the list
+ return BuildDefault(out_store_data, out_count, source_flag);
}
+Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
+ return ResultSuccess;
+ }
-bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
- bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
-
- is_valid = is_valid && (mii_v3.mii_name[0] != 0);
-
- is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
- is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
- is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
- is_valid = is_valid && (mii_v3.height < 128);
- is_valid = is_valid && (mii_v3.build < 128);
-
- is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
- is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
- is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
- is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
-
- is_valid = is_valid && (mii_v3.hair_style < 132);
- is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
-
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
-
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
-
- is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
- is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
- is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
-
- is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
- is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
- is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
- is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
- is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
-
- is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
- is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
- is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
-
- is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
- is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
-
- is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
- is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
- is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
- is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
-
- is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
- is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
- is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
- is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
-
- return is_valid;
+ StoreData store_data{};
+
+ for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
+ if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
+
+ store_data.BuildDefault(static_cast<u32>(index));
+
+ out_elements[out_count].source = Source::Default;
+ out_elements[out_count].char_info.SetFromStoreData(store_data);
+ out_count++;
+ }
+
+ return ResultSuccess;
}
-ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
- std::vector<MiiInfoElement> result;
+Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
+ return ResultSuccess;
+ }
+
+ StoreData store_data{};
+
+ for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
+ if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
+
+ store_data.BuildDefault(static_cast<u32>(index));
+
+ out_char_info[out_count].SetFromStoreData(store_data);
+ out_count++;
+ }
+
+ return ResultSuccess;
+}
+Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
- return result;
+ return ResultSuccess;
}
- for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) {
- result.emplace_back(BuildDefault(index), Source::Default);
+ for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
+ if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
+
+ out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index));
+ out_elements[out_count].source = Source::Default;
+ out_count++;
}
- return result;
+ return ResultSuccess;
}
-Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) {
- constexpr u32 INVALID_INDEX{0xFFFFFFFF};
+Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
+ return ResultSuccess;
+ }
+
+ for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
+ if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
- index = INVALID_INDEX;
+ out_char_info[out_count].BuildDefault(static_cast<u32>(index));
+ out_count++;
+ }
- // TODO(bunnei): We don't implement the Mii database, so we can't have an index
- return ERROR_CANNOT_FIND_ENTRY;
+ return ResultSuccess;
}
} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index 5525fcd1c..48d8e8bb7 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -3,39 +3,85 @@
#pragma once
-#include <vector>
+#include <span>
#include "core/hle/result.h"
-#include "core/hle/service/mii/types.h"
+#include "core/hle/service/mii/mii_database_manager.h"
+#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii {
+class CharInfo;
+class CoreData;
+class StoreData;
+class Ver3StoreData;
-// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
-// with providing an easy interface for HLE emulation of the mii service.
+struct CharInfoElement;
+struct StoreDataElement;
+
+// The Mii manager is responsible for handling mii operations along with providing an easy interface
+// for HLE emulation of the mii service.
class MiiManager {
public:
MiiManager();
+ Result Initialize(DatabaseSessionMetadata& metadata);
+
+ // Auto generated mii
+ void BuildDefault(CharInfo& out_char_info, u32 index) const;
+ void BuildBase(CharInfo& out_char_info, Gender gender) const;
+ void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
- bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter);
+ // Database operations
bool IsFullDatabase() const;
- u32 GetCount(SourceFlag source_flag) const;
- ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
- CharInfo BuildRandom(Age age, Gender gender, Race race);
- CharInfo BuildDefault(std::size_t index);
- CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
- bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
- ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
- Result GetIndex(const CharInfo& info, u32& index);
+ void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const;
+ bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
+ u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
+ Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id);
+ Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data);
+ Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
+ s32 FindIndex(const Common::UUID& create_id, bool is_special) const;
+ Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
+ s32& out_index) const;
+ Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
+
+ // Test database operations
+ bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata);
+ Result DestroyFile(DatabaseSessionMetadata& metadata);
+ Result DeleteFile();
+ Result Format(DatabaseSessionMetadata& metadata);
- // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData
- Ver3StoreData BuildFromStoreData(const CharInfo& mii) const;
+ // Mii conversions
+ Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
+ Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const;
+ Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const;
+ Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
+ const CharInfo& char_info, SourceFlag source_flag) const;
+ Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
+ const StoreData& store_data, SourceFlag source_flag) const;
- // This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData
- NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const;
+ // Overloaded getters
+ Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
+ u32& out_count, SourceFlag source_flag) const;
+ Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
+ u32& out_count, SourceFlag source_flag) const;
+ Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements,
+ u32& out_count, SourceFlag source_flag) const;
+ Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
+ u32& out_count, SourceFlag source_flag) const;
private:
- const Common::UUID user_id{};
- u64 update_counter{};
+ Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const;
+ Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
+ SourceFlag source_flag) const;
+ Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count,
+ SourceFlag source_flag) const;
+ Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
+ SourceFlag source_flag) const;
+
+ DatabaseManager database_manager{};
+
+ // This should be a global value
+ bool is_broken_with_clear_flag{};
};
}; // namespace Service::Mii
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..e2c36e556
--- /dev/null
+++ b/src/core/hle/service/mii/mii_result.h
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Mii {
+
+constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1};
+constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2};
+constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
+constexpr Result ResultNotFound{ErrorModule::Mii, 4};
+constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
+constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
+constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101};
+constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103};
+constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104};
+constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105};
+constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107};
+constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
+constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
+constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
+constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204};
+constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205};
+
+}; // 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..f43efd83c
--- /dev/null
+++ b/src/core/hle/service/mii/mii_types.h
@@ -0,0 +1,692 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <type_traits>
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/uuid.h"
+
+namespace Service::Mii {
+
+constexpr std::size_t MaxNameSize = 10;
+constexpr u8 MaxHeight = 127;
+constexpr u8 MaxBuild = 127;
+constexpr u8 MaxType = 1;
+constexpr u8 MaxRegionMove = 3;
+constexpr u8 MaxEyeScale = 7;
+constexpr u8 MaxEyeAspect = 6;
+constexpr u8 MaxEyeRotate = 7;
+constexpr u8 MaxEyeX = 12;
+constexpr u8 MaxEyeY = 18;
+constexpr u8 MaxEyebrowScale = 8;
+constexpr u8 MaxEyebrowAspect = 6;
+constexpr u8 MaxEyebrowRotate = 11;
+constexpr u8 MaxEyebrowX = 12;
+constexpr u8 MaxEyebrowY = 15;
+constexpr u8 MaxNoseScale = 8;
+constexpr u8 MaxNoseY = 18;
+constexpr u8 MaxMouthScale = 8;
+constexpr u8 MaxMoutAspect = 6;
+constexpr u8 MaxMouthY = 18;
+constexpr u8 MaxMustacheScale = 8;
+constexpr u8 MaxMustacheY = 16;
+constexpr u8 MaxGlassScale = 7;
+constexpr u8 MaxGlassY = 20;
+constexpr u8 MaxMoleScale = 8;
+constexpr u8 MaxMoleX = 16;
+constexpr u8 MaxMoleY = 30;
+constexpr u8 MaxVer3CommonColor = 7;
+constexpr u8 MaxVer3GlassType = 8;
+
+enum class Age : u8 {
+ Young,
+ Normal,
+ Old,
+ All, // Default
+
+ Max = All,
+};
+
+enum class Gender : u8 {
+ Male,
+ Female,
+ All, // Default
+
+ Max = Female,
+};
+
+enum class Race : u8 {
+ Black,
+ White,
+ Asian,
+ All, // Default
+
+ Max = All,
+};
+
+enum class HairType : u8 {
+ NormalLong, // Default
+ NormalShort,
+ NormalMedium,
+ NormalExtraLong,
+ NormalLongBottom,
+ NormalTwoPeaks,
+ PartingLong,
+ FrontLock,
+ PartingShort,
+ PartingExtraLongCurved,
+ PartingExtraLong,
+ PartingMiddleLong,
+ PartingSquared,
+ PartingLongBottom,
+ PeaksTop,
+ PeaksSquared,
+ PartingPeaks,
+ PeaksLongBottom,
+ Peaks,
+ PeaksRounded,
+ PeaksSide,
+ PeaksMedium,
+ PeaksLong,
+ PeaksRoundedLong,
+ PartingFrontPeaks,
+ PartingLongFront,
+ PartingLongRounded,
+ PartingFrontPeaksLong,
+ PartingExtraLongRounded,
+ LongRounded,
+ NormalUnknown1,
+ NormalUnknown2,
+ NormalUnknown3,
+ NormalUnknown4,
+ NormalUnknown5,
+ NormalUnknown6,
+ DreadLocks,
+ PlatedMats,
+ Caps,
+ Afro,
+ PlatedMatsLong,
+ Beanie,
+ Short,
+ ShortTopLongSide,
+ ShortUnknown1,
+ ShortUnknown2,
+ MilitaryParting,
+ Military,
+ ShortUnknown3,
+ ShortUnknown4,
+ ShortUnknown5,
+ ShortUnknown6,
+ NoneTop,
+ None,
+ LongUnknown1,
+ LongUnknown2,
+ LongUnknown3,
+ LongUnknown4,
+ LongUnknown5,
+ LongUnknown6,
+ LongUnknown7,
+ LongUnknown8,
+ LongUnknown9,
+ LongUnknown10,
+ LongUnknown11,
+ LongUnknown12,
+ LongUnknown13,
+ LongUnknown14,
+ LongUnknown15,
+ LongUnknown16,
+ LongUnknown17,
+ LongUnknown18,
+ LongUnknown19,
+ LongUnknown20,
+ LongUnknown21,
+ LongUnknown22,
+ LongUnknown23,
+ LongUnknown24,
+ LongUnknown25,
+ LongUnknown26,
+ LongUnknown27,
+ LongUnknown28,
+ LongUnknown29,
+ LongUnknown30,
+ LongUnknown31,
+ LongUnknown32,
+ LongUnknown33,
+ LongUnknown34,
+ LongUnknown35,
+ LongUnknown36,
+ LongUnknown37,
+ LongUnknown38,
+ LongUnknown39,
+ LongUnknown40,
+ LongUnknown41,
+ LongUnknown42,
+ LongUnknown43,
+ LongUnknown44,
+ LongUnknown45,
+ LongUnknown46,
+ LongUnknown47,
+ LongUnknown48,
+ LongUnknown49,
+ LongUnknown50,
+ LongUnknown51,
+ LongUnknown52,
+ LongUnknown53,
+ LongUnknown54,
+ LongUnknown55,
+ LongUnknown56,
+ LongUnknown57,
+ LongUnknown58,
+ LongUnknown59,
+ LongUnknown60,
+ LongUnknown61,
+ LongUnknown62,
+ LongUnknown63,
+ LongUnknown64,
+ LongUnknown65,
+ LongUnknown66,
+ TwoMediumFrontStrandsOneLongBackPonyTail,
+ TwoFrontStrandsLongBackPonyTail,
+ PartingFrontTwoLongBackPonyTails,
+ TwoFrontStrandsOneLongBackPonyTail,
+ LongBackPonyTail,
+ LongFrontTwoLongBackPonyTails,
+ StrandsTwoShortSidedPonyTails,
+ TwoMediumSidedPonyTails,
+ ShortFrontTwoBackPonyTails,
+ TwoShortSidedPonyTails,
+ TwoLongSidedPonyTails,
+ LongFrontTwoBackPonyTails,
+
+ Max = LongFrontTwoBackPonyTails,
+};
+
+enum class MoleType : u8 {
+ None, // Default
+ OneDot,
+
+ Max = OneDot,
+};
+
+enum class HairFlip : u8 {
+ Left, // Default
+ Right,
+
+ Max = Right,
+};
+
+enum class CommonColor : u8 {
+ // For simplicity common colors aren't listed
+ Max = 99,
+ Count = 100,
+};
+
+enum class FavoriteColor : u8 {
+ Red, // Default
+ Orange,
+ Yellow,
+ LimeGreen,
+ Green,
+ Blue,
+ LightBlue,
+ Pink,
+ Purple,
+ Brown,
+ White,
+ Black,
+
+ Max = Black,
+};
+
+enum class EyeType : u8 {
+ Normal, // Default
+ NormalLash,
+ WhiteLash,
+ WhiteNoBottom,
+ OvalAngledWhite,
+ AngryWhite,
+ DotLashType1,
+ Line,
+ DotLine,
+ OvalWhite,
+ RoundedWhite,
+ NormalShadow,
+ CircleWhite,
+ Circle,
+ CircleWhiteStroke,
+ NormalOvalNoBottom,
+ NormalOvalLarge,
+ NormalRoundedNoBottom,
+ SmallLash,
+ Small,
+ TwoSmall,
+ NormalLongLash,
+ WhiteTwoLashes,
+ WhiteThreeLashes,
+ DotAngry,
+ DotAngled,
+ Oval,
+ SmallWhite,
+ WhiteAngledNoBottom,
+ WhiteAngledNoLeft,
+ SmallWhiteTwoLashes,
+ LeafWhiteLash,
+ WhiteLargeNoBottom,
+ Dot,
+ DotLashType2,
+ DotThreeLashes,
+ WhiteOvalTop,
+ WhiteOvalBottom,
+ WhiteOvalBottomFlat,
+ WhiteOvalTwoLashes,
+ WhiteOvalThreeLashes,
+ WhiteOvalNoBottomTwoLashes,
+ DotWhite,
+ WhiteOvalTopFlat,
+ WhiteThinLeaf,
+ StarThreeLashes,
+ LineTwoLashes,
+ CrowsFeet,
+ WhiteNoBottomFlat,
+ WhiteNoBottomRounded,
+ WhiteSmallBottomLine,
+ WhiteNoBottomLash,
+ WhiteNoPartialBottomLash,
+ WhiteOvalBottomLine,
+ WhiteNoBottomLashTopLine,
+ WhiteNoPartialBottomTwoLashes,
+ NormalTopLine,
+ WhiteOvalLash,
+ RoundTired,
+ WhiteLarge,
+
+ Max = WhiteLarge,
+};
+
+enum class MouthType : u8 {
+ Neutral, // Default
+ NeutralLips,
+ Smile,
+ SmileStroke,
+ SmileTeeth,
+ LipsSmall,
+ LipsLarge,
+ Wave,
+ WaveAngrySmall,
+ NeutralStrokeLarge,
+ TeethSurprised,
+ LipsExtraLarge,
+ LipsUp,
+ NeutralDown,
+ Surprised,
+ TeethMiddle,
+ NeutralStroke,
+ LipsExtraSmall,
+ Malicious,
+ LipsDual,
+ NeutralComma,
+ NeutralUp,
+ TeethLarge,
+ WaveAngry,
+ LipsSexy,
+ SmileInverted,
+ LipsSexyOutline,
+ SmileRounded,
+ LipsTeeth,
+ NeutralOpen,
+ TeethRounded,
+ WaveAngrySmallInverted,
+ NeutralCommaInverted,
+ TeethFull,
+ SmileDownLine,
+ Kiss,
+
+ Max = Kiss,
+};
+
+enum class FontRegion : u8 {
+ Standard, // Default
+ China,
+ Korea,
+ Taiwan,
+
+ Max = Taiwan,
+};
+
+enum class FacelineType : u8 {
+ Sharp, // Default
+ Rounded,
+ SharpRounded,
+ SharpRoundedSmall,
+ Large,
+ LargeRounded,
+ SharpSmall,
+ Flat,
+ Bump,
+ Angular,
+ FlatRounded,
+ AngularSmall,
+
+ Max = AngularSmall,
+};
+
+enum class FacelineColor : u8 {
+ Beige, // Default
+ WarmBeige,
+ Natural,
+ Honey,
+ Chestnut,
+ Porcelain,
+ Ivory,
+ WarmIvory,
+ Almond,
+ Espresso,
+
+ Max = Espresso,
+ Count = Max + 1,
+};
+
+enum class FacelineWrinkle : u8 {
+ None, // Default
+ TearTroughs,
+ FacialPain,
+ Cheeks,
+ Folds,
+ UnderTheEyes,
+ SplitChin,
+ Chin,
+ BrowDroop,
+ MouthFrown,
+ CrowsFeet,
+ FoldsCrowsFrown,
+
+ Max = FoldsCrowsFrown,
+};
+
+enum class FacelineMake : u8 {
+ None, // Default
+ CheekPorcelain,
+ CheekNatural,
+ EyeShadowBlue,
+ CheekBlushPorcelain,
+ CheekBlushNatural,
+ CheekPorcelainEyeShadowBlue,
+ CheekPorcelainEyeShadowNatural,
+ CheekBlushPorcelainEyeShadowEspresso,
+ Freckles,
+ LionsManeBeard,
+ StubbleBeard,
+
+ Max = StubbleBeard,
+};
+
+enum class EyebrowType : u8 {
+ FlatAngledLarge, // Default
+ LowArchRoundedThin,
+ SoftAngledLarge,
+ MediumArchRoundedThin,
+ RoundedMedium,
+ LowArchMedium,
+ RoundedThin,
+ UpThin,
+ MediumArchRoundedMedium,
+ RoundedLarge,
+ UpLarge,
+ FlatAngledLargeInverted,
+ MediumArchFlat,
+ AngledThin,
+ HorizontalLarge,
+ HighArchFlat,
+ Flat,
+ MediumArchLarge,
+ LowArchThin,
+ RoundedThinInverted,
+ HighArchLarge,
+ Hairy,
+ Dotted,
+ None,
+
+ Max = None,
+};
+
+enum class NoseType : u8 {
+ Normal, // Default
+ Rounded,
+ Dot,
+ Arrow,
+ Roman,
+ Triangle,
+ Button,
+ RoundedInverted,
+ Potato,
+ Grecian,
+ Snub,
+ Aquiline,
+ ArrowLeft,
+ RoundedLarge,
+ Hooked,
+ Fat,
+ Droopy,
+ ArrowLarge,
+
+ Max = ArrowLarge,
+};
+
+enum class BeardType : u8 {
+ None,
+ Goatee,
+ GoateeLong,
+ LionsManeLong,
+ LionsMane,
+ Full,
+
+ Min = Goatee,
+ Max = Full,
+};
+
+enum class MustacheType : u8 {
+ None,
+ Walrus,
+ Pencil,
+ Horseshoe,
+ Normal,
+ Toothbrush,
+
+ Min = Walrus,
+ Max = Toothbrush,
+};
+
+enum class GlassType : u8 {
+ None,
+ Oval,
+ Wayfarer,
+ Rectangle,
+ TopRimless,
+ Rounded,
+ Oversized,
+ CatEye,
+ Square,
+ BottomRimless,
+ SemiOpaqueRounded,
+ SemiOpaqueCatEye,
+ SemiOpaqueOval,
+ SemiOpaqueRectangle,
+ SemiOpaqueAviator,
+ OpaqueRounded,
+ OpaqueCatEye,
+ OpaqueOval,
+ OpaqueRectangle,
+ OpaqueAviator,
+
+ Max = OpaqueAviator,
+ Count = Max + 1,
+};
+
+enum class BeardAndMustacheFlag : u32 {
+ Beard = 1,
+ Mustache,
+ All = Beard | Mustache,
+};
+DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
+
+enum class Source : u32 {
+ Database = 0,
+ Default = 1,
+ Account = 2,
+ Friend = 3,
+};
+
+enum class SourceFlag : u32 {
+ None = 0,
+ Database = 1 << 0,
+ Default = 1 << 1,
+};
+DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
+
+enum class ValidationResult : u32 {
+ NoErrors = 0x0,
+ InvalidBeardColor = 0x1,
+ InvalidBeardType = 0x2,
+ InvalidBuild = 0x3,
+ InvalidEyeAspect = 0x4,
+ InvalidEyeColor = 0x5,
+ InvalidEyeRotate = 0x6,
+ InvalidEyeScale = 0x7,
+ InvalidEyeType = 0x8,
+ InvalidEyeX = 0x9,
+ InvalidEyeY = 0xa,
+ InvalidEyebrowAspect = 0xb,
+ InvalidEyebrowColor = 0xc,
+ InvalidEyebrowRotate = 0xd,
+ InvalidEyebrowScale = 0xe,
+ InvalidEyebrowType = 0xf,
+ InvalidEyebrowX = 0x10,
+ InvalidEyebrowY = 0x11,
+ InvalidFacelineColor = 0x12,
+ InvalidFacelineMake = 0x13,
+ InvalidFacelineWrinkle = 0x14,
+ InvalidFacelineType = 0x15,
+ InvalidColor = 0x16,
+ InvalidFont = 0x17,
+ InvalidGender = 0x18,
+ InvalidGlassColor = 0x19,
+ InvalidGlassScale = 0x1a,
+ InvalidGlassType = 0x1b,
+ InvalidGlassY = 0x1c,
+ InvalidHairColor = 0x1d,
+ InvalidHairFlip = 0x1e,
+ InvalidHairType = 0x1f,
+ InvalidHeight = 0x20,
+ InvalidMoleScale = 0x21,
+ InvalidMoleType = 0x22,
+ InvalidMoleX = 0x23,
+ InvalidMoleY = 0x24,
+ InvalidMouthAspect = 0x25,
+ InvalidMouthColor = 0x26,
+ InvalidMouthScale = 0x27,
+ InvalidMouthType = 0x28,
+ InvalidMouthY = 0x29,
+ InvalidMustacheScale = 0x2a,
+ InvalidMustacheType = 0x2b,
+ InvalidMustacheY = 0x2c,
+ InvalidNoseScale = 0x2e,
+ InvalidNoseType = 0x2f,
+ InvalidNoseY = 0x30,
+ InvalidRegionMove = 0x31,
+ InvalidCreateId = 0x32,
+ InvalidName = 0x33,
+ InvalidChecksum = 0x34,
+ InvalidType = 0x35,
+};
+
+struct Nickname {
+ std::array<char16_t, MaxNameSize> data{};
+
+ // Checks for null or dirty strings
+ bool IsValid() const {
+ if (data[0] == 0) {
+ return false;
+ }
+
+ std::size_t index = 1;
+ while (data[index] != 0) {
+ index++;
+ }
+ while (index < MaxNameSize && data[index] == 0) {
+ index++;
+ }
+ return index == MaxNameSize;
+ }
+};
+static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size");
+
+struct DefaultMii {
+ u32 face_type{};
+ u32 face_color{};
+ u32 face_wrinkle{};
+ u32 face_makeup{};
+ u32 hair_type{};
+ u32 hair_color{};
+ u32 hair_flip{};
+ u32 eye_type{};
+ u32 eye_color{};
+ u32 eye_scale{};
+ u32 eye_aspect{};
+ u32 eye_rotate{};
+ u32 eye_x{};
+ u32 eye_y{};
+ u32 eyebrow_type{};
+ u32 eyebrow_color{};
+ u32 eyebrow_scale{};
+ u32 eyebrow_aspect{};
+ u32 eyebrow_rotate{};
+ u32 eyebrow_x{};
+ u32 eyebrow_y{};
+ u32 nose_type{};
+ u32 nose_scale{};
+ u32 nose_y{};
+ u32 mouth_type{};
+ u32 mouth_color{};
+ u32 mouth_scale{};
+ u32 mouth_aspect{};
+ u32 mouth_y{};
+ u32 mustache_type{};
+ u32 beard_type{};
+ u32 beard_color{};
+ u32 mustache_scale{};
+ u32 mustache_y{};
+ u32 glasses_type{};
+ u32 glasses_color{};
+ u32 glasses_scale{};
+ u32 glasses_y{};
+ u32 mole_type{};
+ u32 mole_scale{};
+ u32 mole_x{};
+ u32 mole_y{};
+ u32 height{};
+ u32 weight{};
+ u32 gender{};
+ u32 favorite_color{};
+ u32 region_move{};
+ u32 font_region{};
+ u32 type{};
+ Nickname nickname;
+};
+static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size.");
+
+struct DatabaseSessionMetadata {
+ u32 interface_version;
+ u32 magic;
+ u64 update_counter;
+
+ bool IsInterfaceVersionSupported(u32 version) const {
+ return version <= interface_version;
+ }
+};
+
+} // 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..3534fa31d
--- /dev/null
+++ b/src/core/hle/service/mii/mii_util.h
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <random>
+#include <span>
+
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "common/uuid.h"
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii {
+class MiiUtil {
+public:
+ static u16 CalculateCrc16(const void* data, std::size_t size) {
+ s32 crc{};
+ for (std::size_t i = 0; i < size; i++) {
+ crc ^= static_cast<const u8*>(data)[i] << 8;
+ for (std::size_t j = 0; j < 8; j++) {
+ crc <<= 1;
+ if ((crc & 0x10000) != 0) {
+ crc = (crc ^ 0x1021) & 0xFFFF;
+ }
+ }
+ }
+ return Common::swap16(static_cast<u16>(crc));
+ }
+
+ static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) {
+ constexpr u16 magic{0x1021};
+ s32 crc{};
+
+ for (std::size_t i = 0; i < uuid.uuid.size(); i++) {
+ for (std::size_t j = 0; j < 8; j++) {
+ crc <<= 1;
+ if ((crc & 0x10000) != 0) {
+ crc = crc ^ magic;
+ }
+ }
+ crc ^= uuid.uuid[i];
+ }
+
+ // As much as this looks wrong this is what N's does
+
+ for (std::size_t i = 0; i < data_size * 8; i++) {
+ crc <<= 1;
+ if ((crc & 0x10000) != 0) {
+ crc = crc ^ magic;
+ }
+ }
+
+ return Common::swap16(static_cast<u16>(crc));
+ }
+
+ static Common::UUID MakeCreateId() {
+ return Common::UUID::MakeRandomRFC4122V4();
+ }
+
+ static Common::UUID GetDeviceId() {
+ // This should be nn::settings::detail::GetMiiAuthorId()
+ return Common::UUID::MakeDefault();
+ }
+
+ template <typename T>
+ static T GetRandomValue(T min, T max) {
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<u64> distribution(static_cast<u64>(min),
+ static_cast<u64>(max));
+ return static_cast<T>(distribution(gen));
+ }
+
+ template <typename T>
+ static T GetRandomValue(T max) {
+ return GetRandomValue<T>({}, max);
+ }
+
+ static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) {
+ // TODO: This function needs to check against the font tables
+ return true;
+ }
+};
+} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-
-#include "core/hle/service/mii/types.h"
-
-namespace Service::Mii::RawData {
-
-extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline;
-extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType;
-extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType;
-extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType;
-extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType;
-
-} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <type_traits>
-
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/uuid.h"
-
-namespace Service::Mii {
-
-enum class Age : u32 {
- Young,
- Normal,
- Old,
- All,
-};
-
-enum class BeardType : u32 {
- None,
- Beard1,
- Beard2,
- Beard3,
- Beard4,
- Beard5,
-};
-
-enum class BeardAndMustacheFlag : u32 {
- Beard = 1,
- Mustache,
- All = Beard | Mustache,
-};
-DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
-
-enum class FontRegion : u32 {
- Standard,
- China,
- Korea,
- Taiwan,
-};
-
-enum class Gender : u32 {
- Male,
- Female,
- All,
- Maximum = Female,
-};
-
-enum class HairFlip : u32 {
- Left,
- Right,
- Maximum = Right,
-};
-
-enum class MustacheType : u32 {
- None,
- Mustache1,
- Mustache2,
- Mustache3,
- Mustache4,
- Mustache5,
-};
-
-enum class Race : u32 {
- Black,
- White,
- Asian,
- All,
-};
-
-enum class Source : u32 {
- Database = 0,
- Default = 1,
- Account = 2,
- Friend = 3,
-};
-
-enum class SourceFlag : u32 {
- None = 0,
- Database = 1 << 0,
- Default = 1 << 1,
-};
-DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
-
-// nn::mii::CharInfo
-struct CharInfo {
- Common::UUID uuid;
- std::array<char16_t, 11> name;
- u8 font_region;
- u8 favorite_color;
- u8 gender;
- u8 height;
- u8 build;
- u8 type;
- u8 region_move;
- u8 faceline_type;
- u8 faceline_color;
- u8 faceline_wrinkle;
- u8 faceline_make;
- u8 hair_type;
- u8 hair_color;
- u8 hair_flip;
- u8 eye_type;
- u8 eye_color;
- u8 eye_scale;
- u8 eye_aspect;
- u8 eye_rotate;
- u8 eye_x;
- u8 eye_y;
- u8 eyebrow_type;
- u8 eyebrow_color;
- u8 eyebrow_scale;
- u8 eyebrow_aspect;
- u8 eyebrow_rotate;
- u8 eyebrow_x;
- u8 eyebrow_y;
- u8 nose_type;
- u8 nose_scale;
- u8 nose_y;
- u8 mouth_type;
- u8 mouth_color;
- u8 mouth_scale;
- u8 mouth_aspect;
- u8 mouth_y;
- u8 beard_color;
- u8 beard_type;
- u8 mustache_type;
- u8 mustache_scale;
- u8 mustache_y;
- u8 glasses_type;
- u8 glasses_color;
- u8 glasses_scale;
- u8 glasses_y;
- u8 mole_type;
- u8 mole_scale;
- u8 mole_x;
- u8 mole_y;
- u8 padding;
-};
-static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
-static_assert(std::has_unique_object_representations_v<CharInfo>,
- "All bits of CharInfo must contribute to its value.");
-
-#pragma pack(push, 4)
-
-struct MiiInfoElement {
- MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
-
- CharInfo info{};
- Source source{};
-};
-static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
-
-struct MiiStoreBitFields {
- union {
- u32 word_0{};
-
- BitField<0, 8, u32> hair_type;
- BitField<8, 7, u32> height;
- BitField<15, 1, u32> mole_type;
- BitField<16, 7, u32> build;
- BitField<23, 1, HairFlip> hair_flip;
- BitField<24, 7, u32> hair_color;
- BitField<31, 1, u32> type;
- };
-
- union {
- u32 word_1{};
-
- BitField<0, 7, u32> eye_color;
- BitField<7, 1, Gender> gender;
- BitField<8, 7, u32> eyebrow_color;
- BitField<16, 7, u32> mouth_color;
- BitField<24, 7, u32> beard_color;
- };
-
- union {
- u32 word_2{};
-
- BitField<0, 7, u32> glasses_color;
- BitField<8, 6, u32> eye_type;
- BitField<14, 2, u32> region_move;
- BitField<16, 6, u32> mouth_type;
- BitField<22, 2, FontRegion> font_region;
- BitField<24, 5, u32> eye_y;
- BitField<29, 3, u32> glasses_scale;
- };
-
- union {
- u32 word_3{};
-
- BitField<0, 5, u32> eyebrow_type;
- BitField<5, 3, MustacheType> mustache_type;
- BitField<8, 5, u32> nose_type;
- BitField<13, 3, BeardType> beard_type;
- BitField<16, 5, u32> nose_y;
- BitField<21, 3, u32> mouth_aspect;
- BitField<24, 5, u32> mouth_y;
- BitField<29, 3, u32> eyebrow_aspect;
- };
-
- union {
- u32 word_4{};
-
- BitField<0, 5, u32> mustache_y;
- BitField<5, 3, u32> eye_rotate;
- BitField<8, 5, u32> glasses_y;
- BitField<13, 3, u32> eye_aspect;
- BitField<16, 5, u32> mole_x;
- BitField<21, 3, u32> eye_scale;
- BitField<24, 5, u32> mole_y;
- };
-
- union {
- u32 word_5{};
-
- BitField<0, 5, u32> glasses_type;
- BitField<8, 4, u32> favorite_color;
- BitField<12, 4, u32> faceline_type;
- BitField<16, 4, u32> faceline_color;
- BitField<20, 4, u32> faceline_wrinkle;
- BitField<24, 4, u32> faceline_makeup;
- BitField<28, 4, u32> eye_x;
- };
-
- union {
- u32 word_6{};
-
- BitField<0, 4, u32> eyebrow_scale;
- BitField<4, 4, u32> eyebrow_rotate;
- BitField<8, 4, u32> eyebrow_x;
- BitField<12, 4, u32> eyebrow_y;
- BitField<16, 4, u32> nose_scale;
- BitField<20, 4, u32> mouth_scale;
- BitField<24, 4, u32> mustache_scale;
- BitField<28, 4, u32> mole_scale;
- };
-};
-static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
-static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
- "MiiStoreBitFields is not trivially copyable.");
-
-// This is nn::mii::Ver3StoreData
-// Based on citra HLE::Applets::MiiData and PretendoNetwork.
-// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
-// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
-struct Ver3StoreData {
- u8 version;
- union {
- u8 raw;
-
- BitField<0, 1, u8> allow_copying;
- BitField<1, 1, u8> profanity_flag;
- BitField<2, 2, u8> region_lock;
- BitField<4, 2, u8> character_set;
- } region_information;
- u16_be mii_id;
- u64_be system_id;
- u32_be specialness_and_creation_date;
- std::array<u8, 0x6> creator_mac;
- u16_be padding;
- union {
- u16 raw;
-
- BitField<0, 1, u16> gender;
- BitField<1, 4, u16> birth_month;
- BitField<5, 5, u16> birth_day;
- BitField<10, 4, u16> favorite_color;
- BitField<14, 1, u16> favorite;
- } mii_information;
- std::array<char16_t, 0xA> mii_name;
- u8 height;
- u8 build;
- union {
- u8 raw;
-
- BitField<0, 1, u8> disable_sharing;
- BitField<1, 4, u8> face_shape;
- BitField<5, 3, u8> skin_color;
- } appearance_bits1;
- union {
- u8 raw;
-
- BitField<0, 4, u8> wrinkles;
- BitField<4, 4, u8> makeup;
- } appearance_bits2;
- u8 hair_style;
- union {
- u8 raw;
-
- BitField<0, 3, u8> hair_color;
- BitField<3, 1, u8> flip_hair;
- } appearance_bits3;
- union {
- u32 raw;
-
- BitField<0, 6, u32> eye_type;
- BitField<6, 3, u32> eye_color;
- BitField<9, 4, u32> eye_scale;
- BitField<13, 3, u32> eye_vertical_stretch;
- BitField<16, 5, u32> eye_rotation;
- BitField<21, 4, u32> eye_spacing;
- BitField<25, 5, u32> eye_y_position;
- } appearance_bits4;
- union {
- u32 raw;
-
- BitField<0, 5, u32> eyebrow_style;
- BitField<5, 3, u32> eyebrow_color;
- BitField<8, 4, u32> eyebrow_scale;
- BitField<12, 3, u32> eyebrow_yscale;
- BitField<16, 4, u32> eyebrow_rotation;
- BitField<21, 4, u32> eyebrow_spacing;
- BitField<25, 5, u32> eyebrow_y_position;
- } appearance_bits5;
- union {
- u16 raw;
-
- BitField<0, 5, u16> nose_type;
- BitField<5, 4, u16> nose_scale;
- BitField<9, 5, u16> nose_y_position;
- } appearance_bits6;
- union {
- u16 raw;
-
- BitField<0, 6, u16> mouth_type;
- BitField<6, 3, u16> mouth_color;
- BitField<9, 4, u16> mouth_scale;
- BitField<13, 3, u16> mouth_horizontal_stretch;
- } appearance_bits7;
- union {
- u8 raw;
-
- BitField<0, 5, u8> mouth_y_position;
- BitField<5, 3, u8> mustache_type;
- } appearance_bits8;
- u8 allow_copying;
- union {
- u16 raw;
-
- BitField<0, 3, u16> bear_type;
- BitField<3, 3, u16> facial_hair_color;
- BitField<6, 4, u16> mustache_scale;
- BitField<10, 5, u16> mustache_y_position;
- } appearance_bits9;
- union {
- u16 raw;
-
- BitField<0, 4, u16> glasses_type;
- BitField<4, 3, u16> glasses_color;
- BitField<7, 4, u16> glasses_scale;
- BitField<11, 5, u16> glasses_y_position;
- } appearance_bits10;
- union {
- u16 raw;
-
- BitField<0, 1, u16> mole_enabled;
- BitField<1, 4, u16> mole_scale;
- BitField<5, 5, u16> mole_x_position;
- BitField<10, 5, u16> mole_y_position;
- } appearance_bits11;
-
- std::array<u16_le, 0xA> author_name;
- INSERT_PADDING_BYTES(0x2);
- u16_be crc;
-};
-static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
-
-struct NfpStoreDataExtension {
- u8 faceline_color;
- u8 hair_color;
- u8 eye_color;
- u8 eyebrow_color;
- u8 mouth_color;
- u8 beard_color;
- u8 glass_color;
- u8 glass_type;
-};
-static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
-
-constexpr std::array<u8, 0x10> Ver3FacelineColorTable{
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
-};
-
-constexpr std::array<u8, 100> Ver3HairColorTable{
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0,
- 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
- 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4,
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5,
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7,
- 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4,
-};
-
-constexpr std::array<u8, 100> Ver3EyeColorTable{
- 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4,
- 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
- 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4,
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
- 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2,
- 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
-};
-
-constexpr std::array<u8, 100> Ver3MouthlineColorTable{
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
- 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
- 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
- 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
- 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3,
- 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3,
-};
-
-constexpr std::array<u8, 100> Ver3GlassColorTable{
- 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3,
- 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
- 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
- 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5,
- 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4,
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
-};
-
-constexpr std::array<u8, 20> Ver3GlassTypeTable{
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1,
- 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7,
-};
-
-struct MiiStoreData {
- using Name = std::array<char16_t, 10>;
-
- MiiStoreData();
- MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields,
- const Common::UUID& user_id);
-
- // This corresponds to the above structure MiiStoreBitFields. I did it like this because the
- // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
- // not suitable for our uses.
- struct {
- std::array<u8, 0x1C> data{};
- static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
-
- Name name{};
- Common::UUID uuid{};
- } data;
-
- u16 data_crc{};
- u16 device_crc{};
-};
-static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
-
-struct MiiStoreDataElement {
- MiiStoreData data{};
- Source source{};
-};
-static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
-
-struct MiiDatabase {
- u32 magic{}; // 'NFDB'
- std::array<MiiStoreData, 0x64> miis{};
- INSERT_PADDING_BYTES(1);
- u8 count{};
- u16 crc{};
-};
-static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
-
-struct RandomMiiValues {
- std::array<u8, 0xbc> values{};
-};
-static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
-
-struct RandomMiiData4 {
- Gender gender{};
- Age age{};
- Race race{};
- u32 values_count{};
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
-
-struct RandomMiiData3 {
- u32 arg_1;
- u32 arg_2;
- u32 values_count;
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
-
-struct RandomMiiData2 {
- u32 arg_1;
- u32 values_count;
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
-
-struct DefaultMii {
- u32 face_type{};
- u32 face_color{};
- u32 face_wrinkle{};
- u32 face_makeup{};
- u32 hair_type{};
- u32 hair_color{};
- u32 hair_flip{};
- u32 eye_type{};
- u32 eye_color{};
- u32 eye_scale{};
- u32 eye_aspect{};
- u32 eye_rotate{};
- u32 eye_x{};
- u32 eye_y{};
- u32 eyebrow_type{};
- u32 eyebrow_color{};
- u32 eyebrow_scale{};
- u32 eyebrow_aspect{};
- u32 eyebrow_rotate{};
- u32 eyebrow_x{};
- u32 eyebrow_y{};
- u32 nose_type{};
- u32 nose_scale{};
- u32 nose_y{};
- u32 mouth_type{};
- u32 mouth_color{};
- u32 mouth_scale{};
- u32 mouth_aspect{};
- u32 mouth_y{};
- u32 mustache_type{};
- u32 beard_type{};
- u32 beard_color{};
- u32 mustache_scale{};
- u32 mustache_y{};
- u32 glasses_type{};
- u32 glasses_color{};
- u32 glasses_scale{};
- u32 glasses_y{};
- u32 mole_type{};
- u32 mole_scale{};
- u32 mole_x{};
- u32 mole_y{};
- u32 height{};
- u32 weight{};
- Gender gender{};
- u32 favorite_color{};
- u32 region{};
- FontRegion font_region{};
- u32 type{};
- INSERT_PADDING_WORDS(5);
-};
-static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size.");
-
-#pragma pack(pop)
-
-} // 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..c82186c73
--- /dev/null
+++ b/src/core/hle/service/mii/types/char_info.cpp
@@ -0,0 +1,482 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/store_data.h"
+
+namespace Service::Mii {
+
+void CharInfo::SetFromStoreData(const StoreData& store_data) {
+ name = store_data.GetNickname();
+ null_terminator = '\0';
+ create_id = store_data.GetCreateId();
+ font_region = store_data.GetFontRegion();
+ favorite_color = store_data.GetFavoriteColor();
+ gender = store_data.GetGender();
+ height = store_data.GetHeight();
+ build = store_data.GetBuild();
+ type = store_data.GetType();
+ region_move = store_data.GetRegionMove();
+ faceline_type = store_data.GetFacelineType();
+ faceline_color = store_data.GetFacelineColor();
+ faceline_wrinkle = store_data.GetFacelineWrinkle();
+ faceline_make = store_data.GetFacelineMake();
+ hair_type = store_data.GetHairType();
+ hair_color = store_data.GetHairColor();
+ hair_flip = store_data.GetHairFlip();
+ eye_type = store_data.GetEyeType();
+ eye_color = store_data.GetEyeColor();
+ eye_scale = store_data.GetEyeScale();
+ eye_aspect = store_data.GetEyeAspect();
+ eye_rotate = store_data.GetEyeRotate();
+ eye_x = store_data.GetEyeX();
+ eye_y = store_data.GetEyeY();
+ eyebrow_type = store_data.GetEyebrowType();
+ eyebrow_color = store_data.GetEyebrowColor();
+ eyebrow_scale = store_data.GetEyebrowScale();
+ eyebrow_aspect = store_data.GetEyebrowAspect();
+ eyebrow_rotate = store_data.GetEyebrowRotate();
+ eyebrow_x = store_data.GetEyebrowX();
+ eyebrow_y = store_data.GetEyebrowY();
+ nose_type = store_data.GetNoseType();
+ nose_scale = store_data.GetNoseScale();
+ nose_y = store_data.GetNoseY();
+ mouth_type = store_data.GetMouthType();
+ mouth_color = store_data.GetMouthColor();
+ mouth_scale = store_data.GetMouthScale();
+ mouth_aspect = store_data.GetMouthAspect();
+ mouth_y = store_data.GetMouthY();
+ beard_color = store_data.GetBeardColor();
+ beard_type = store_data.GetBeardType();
+ mustache_type = store_data.GetMustacheType();
+ mustache_scale = store_data.GetMustacheScale();
+ mustache_y = store_data.GetMustacheY();
+ glass_type = store_data.GetGlassType();
+ glass_color = store_data.GetGlassColor();
+ glass_scale = store_data.GetGlassScale();
+ glass_y = store_data.GetGlassY();
+ mole_type = store_data.GetMoleType();
+ mole_scale = store_data.GetMoleScale();
+ mole_x = store_data.GetMoleX();
+ mole_y = store_data.GetMoleY();
+ padding = '\0';
+}
+
+ValidationResult CharInfo::Verify() const {
+ if (!create_id.IsValid()) {
+ return ValidationResult::InvalidCreateId;
+ }
+ if (!name.IsValid()) {
+ return ValidationResult::InvalidName;
+ }
+ if (font_region > FontRegion::Max) {
+ return ValidationResult::InvalidFont;
+ }
+ if (favorite_color > FavoriteColor::Max) {
+ return ValidationResult::InvalidColor;
+ }
+ if (gender > Gender::Max) {
+ return ValidationResult::InvalidGender;
+ }
+ if (height > MaxHeight) {
+ return ValidationResult::InvalidHeight;
+ }
+ if (build > MaxBuild) {
+ return ValidationResult::InvalidBuild;
+ }
+ if (type > MaxType) {
+ return ValidationResult::InvalidType;
+ }
+ if (region_move > MaxRegionMove) {
+ return ValidationResult::InvalidRegionMove;
+ }
+ if (faceline_type > FacelineType::Max) {
+ return ValidationResult::InvalidFacelineType;
+ }
+ if (faceline_color > FacelineColor::Max) {
+ return ValidationResult::InvalidFacelineColor;
+ }
+ if (faceline_wrinkle > FacelineWrinkle::Max) {
+ return ValidationResult::InvalidFacelineWrinkle;
+ }
+ if (faceline_make > FacelineMake::Max) {
+ return ValidationResult::InvalidFacelineMake;
+ }
+ if (hair_type > HairType::Max) {
+ return ValidationResult::InvalidHairType;
+ }
+ if (hair_color > CommonColor::Max) {
+ return ValidationResult::InvalidHairColor;
+ }
+ if (hair_flip > HairFlip::Max) {
+ return ValidationResult::InvalidHairFlip;
+ }
+ if (eye_type > EyeType::Max) {
+ return ValidationResult::InvalidEyeType;
+ }
+ if (eye_color > CommonColor::Max) {
+ return ValidationResult::InvalidEyeColor;
+ }
+ if (eye_scale > MaxEyeScale) {
+ return ValidationResult::InvalidEyeScale;
+ }
+ if (eye_aspect > MaxEyeAspect) {
+ return ValidationResult::InvalidEyeAspect;
+ }
+ if (eye_rotate > MaxEyeX) {
+ return ValidationResult::InvalidEyeRotate;
+ }
+ if (eye_x > MaxEyeX) {
+ return ValidationResult::InvalidEyeX;
+ }
+ if (eye_y > MaxEyeY) {
+ return ValidationResult::InvalidEyeY;
+ }
+ if (eyebrow_type > EyebrowType::Max) {
+ return ValidationResult::InvalidEyebrowType;
+ }
+ if (eyebrow_color > CommonColor::Max) {
+ return ValidationResult::InvalidEyebrowColor;
+ }
+ if (eyebrow_scale > MaxEyebrowScale) {
+ return ValidationResult::InvalidEyebrowScale;
+ }
+ if (eyebrow_aspect > MaxEyebrowAspect) {
+ return ValidationResult::InvalidEyebrowAspect;
+ }
+ if (eyebrow_rotate > MaxEyebrowRotate) {
+ return ValidationResult::InvalidEyebrowRotate;
+ }
+ if (eyebrow_x > MaxEyebrowX) {
+ return ValidationResult::InvalidEyebrowX;
+ }
+ if (eyebrow_y - 3 > MaxEyebrowY) {
+ return ValidationResult::InvalidEyebrowY;
+ }
+ if (nose_type > NoseType::Max) {
+ return ValidationResult::InvalidNoseType;
+ }
+ if (nose_scale > MaxNoseScale) {
+ return ValidationResult::InvalidNoseScale;
+ }
+ if (nose_y > MaxNoseY) {
+ return ValidationResult::InvalidNoseY;
+ }
+ if (mouth_type > MouthType::Max) {
+ return ValidationResult::InvalidMouthType;
+ }
+ if (mouth_color > CommonColor::Max) {
+ return ValidationResult::InvalidMouthColor;
+ }
+ if (mouth_scale > MaxMouthScale) {
+ return ValidationResult::InvalidMouthScale;
+ }
+ if (mouth_aspect > MaxMoutAspect) {
+ return ValidationResult::InvalidMouthAspect;
+ }
+ if (mouth_y > MaxMouthY) {
+ return ValidationResult::InvalidMoleY;
+ }
+ if (beard_color > CommonColor::Max) {
+ return ValidationResult::InvalidBeardColor;
+ }
+ if (beard_type > BeardType::Max) {
+ return ValidationResult::InvalidBeardType;
+ }
+ if (mustache_type > MustacheType::Max) {
+ return ValidationResult::InvalidMustacheType;
+ }
+ if (mustache_scale > MaxMustacheScale) {
+ return ValidationResult::InvalidMustacheScale;
+ }
+ if (mustache_y > MaxMustacheY) {
+ return ValidationResult::InvalidMustacheY;
+ }
+ if (glass_type > GlassType::Max) {
+ return ValidationResult::InvalidGlassType;
+ }
+ if (glass_color > CommonColor::Max) {
+ return ValidationResult::InvalidGlassColor;
+ }
+ if (glass_scale > MaxGlassScale) {
+ return ValidationResult::InvalidGlassScale;
+ }
+ if (glass_y > MaxGlassY) {
+ return ValidationResult::InvalidGlassY;
+ }
+ if (mole_type > MoleType::Max) {
+ return ValidationResult::InvalidMoleType;
+ }
+ if (mole_scale > MaxMoleScale) {
+ return ValidationResult::InvalidMoleScale;
+ }
+ if (mole_x > MaxMoleX) {
+ return ValidationResult::InvalidMoleX;
+ }
+ if (mole_y > MaxMoleY) {
+ return ValidationResult::InvalidMoleY;
+ }
+ return ValidationResult::NoErrors;
+}
+
+Common::UUID CharInfo::GetCreateId() const {
+ return create_id;
+}
+
+Nickname CharInfo::GetNickname() const {
+ return name;
+}
+
+FontRegion CharInfo::GetFontRegion() const {
+ return font_region;
+}
+
+FavoriteColor CharInfo::GetFavoriteColor() const {
+ return favorite_color;
+}
+
+Gender CharInfo::GetGender() const {
+ return gender;
+}
+
+u8 CharInfo::GetHeight() const {
+ return height;
+}
+
+u8 CharInfo::GetBuild() const {
+ return build;
+}
+
+u8 CharInfo::GetType() const {
+ return type;
+}
+
+u8 CharInfo::GetRegionMove() const {
+ return region_move;
+}
+
+FacelineType CharInfo::GetFacelineType() const {
+ return faceline_type;
+}
+
+FacelineColor CharInfo::GetFacelineColor() const {
+ return faceline_color;
+}
+
+FacelineWrinkle CharInfo::GetFacelineWrinkle() const {
+ return faceline_wrinkle;
+}
+
+FacelineMake CharInfo::GetFacelineMake() const {
+ return faceline_make;
+}
+
+HairType CharInfo::GetHairType() const {
+ return hair_type;
+}
+
+CommonColor CharInfo::GetHairColor() const {
+ return hair_color;
+}
+
+HairFlip CharInfo::GetHairFlip() const {
+ return hair_flip;
+}
+
+EyeType CharInfo::GetEyeType() const {
+ return eye_type;
+}
+
+CommonColor CharInfo::GetEyeColor() const {
+ return eye_color;
+}
+
+u8 CharInfo::GetEyeScale() const {
+ return eye_scale;
+}
+
+u8 CharInfo::GetEyeAspect() const {
+ return eye_aspect;
+}
+
+u8 CharInfo::GetEyeRotate() const {
+ return eye_rotate;
+}
+
+u8 CharInfo::GetEyeX() const {
+ return eye_x;
+}
+
+u8 CharInfo::GetEyeY() const {
+ return eye_y;
+}
+
+EyebrowType CharInfo::GetEyebrowType() const {
+ return eyebrow_type;
+}
+
+CommonColor CharInfo::GetEyebrowColor() const {
+ return eyebrow_color;
+}
+
+u8 CharInfo::GetEyebrowScale() const {
+ return eyebrow_scale;
+}
+
+u8 CharInfo::GetEyebrowAspect() const {
+ return eyebrow_aspect;
+}
+
+u8 CharInfo::GetEyebrowRotate() const {
+ return eyebrow_rotate;
+}
+
+u8 CharInfo::GetEyebrowX() const {
+ return eyebrow_x;
+}
+
+u8 CharInfo::GetEyebrowY() const {
+ return eyebrow_y;
+}
+
+NoseType CharInfo::GetNoseType() const {
+ return nose_type;
+}
+
+u8 CharInfo::GetNoseScale() const {
+ return nose_scale;
+}
+
+u8 CharInfo::GetNoseY() const {
+ return nose_y;
+}
+
+MouthType CharInfo::GetMouthType() const {
+ return mouth_type;
+}
+
+CommonColor CharInfo::GetMouthColor() const {
+ return mouth_color;
+}
+
+u8 CharInfo::GetMouthScale() const {
+ return mouth_scale;
+}
+
+u8 CharInfo::GetMouthAspect() const {
+ return mouth_aspect;
+}
+
+u8 CharInfo::GetMouthY() const {
+ return mouth_y;
+}
+
+CommonColor CharInfo::GetBeardColor() const {
+ return beard_color;
+}
+
+BeardType CharInfo::GetBeardType() const {
+ return beard_type;
+}
+
+MustacheType CharInfo::GetMustacheType() const {
+ return mustache_type;
+}
+
+u8 CharInfo::GetMustacheScale() const {
+ return mustache_scale;
+}
+
+u8 CharInfo::GetMustacheY() const {
+ return mustache_y;
+}
+
+GlassType CharInfo::GetGlassType() const {
+ return glass_type;
+}
+
+CommonColor CharInfo::GetGlassColor() const {
+ return glass_color;
+}
+
+u8 CharInfo::GetGlassScale() const {
+ return glass_scale;
+}
+
+u8 CharInfo::GetGlassY() const {
+ return glass_y;
+}
+
+MoleType CharInfo::GetMoleType() const {
+ return mole_type;
+}
+
+u8 CharInfo::GetMoleScale() const {
+ return mole_scale;
+}
+
+u8 CharInfo::GetMoleX() const {
+ return mole_x;
+}
+
+u8 CharInfo::GetMoleY() const {
+ return mole_y;
+}
+
+bool CharInfo::operator==(const CharInfo& info) {
+ bool is_identical = info.Verify() == ValidationResult::NoErrors;
+ is_identical &= name.data == info.GetNickname().data;
+ is_identical &= create_id == info.GetCreateId();
+ is_identical &= font_region == info.GetFontRegion();
+ is_identical &= favorite_color == info.GetFavoriteColor();
+ is_identical &= gender == info.GetGender();
+ is_identical &= height == info.GetHeight();
+ is_identical &= build == info.GetBuild();
+ is_identical &= type == info.GetType();
+ is_identical &= region_move == info.GetRegionMove();
+ is_identical &= faceline_type == info.GetFacelineType();
+ is_identical &= faceline_color == info.GetFacelineColor();
+ is_identical &= faceline_wrinkle == info.GetFacelineWrinkle();
+ is_identical &= faceline_make == info.GetFacelineMake();
+ is_identical &= hair_type == info.GetHairType();
+ is_identical &= hair_color == info.GetHairColor();
+ is_identical &= hair_flip == info.GetHairFlip();
+ is_identical &= eye_type == info.GetEyeType();
+ is_identical &= eye_color == info.GetEyeColor();
+ is_identical &= eye_scale == info.GetEyeScale();
+ is_identical &= eye_aspect == info.GetEyeAspect();
+ is_identical &= eye_rotate == info.GetEyeRotate();
+ is_identical &= eye_x == info.GetEyeX();
+ is_identical &= eye_y == info.GetEyeY();
+ is_identical &= eyebrow_type == info.GetEyebrowType();
+ is_identical &= eyebrow_color == info.GetEyebrowColor();
+ is_identical &= eyebrow_scale == info.GetEyebrowScale();
+ is_identical &= eyebrow_aspect == info.GetEyebrowAspect();
+ is_identical &= eyebrow_rotate == info.GetEyebrowRotate();
+ is_identical &= eyebrow_x == info.GetEyebrowX();
+ is_identical &= eyebrow_y == info.GetEyebrowY();
+ is_identical &= nose_type == info.GetNoseType();
+ is_identical &= nose_scale == info.GetNoseScale();
+ is_identical &= nose_y == info.GetNoseY();
+ is_identical &= mouth_type == info.GetMouthType();
+ is_identical &= mouth_color == info.GetMouthColor();
+ is_identical &= mouth_scale == info.GetMouthScale();
+ is_identical &= mouth_aspect == info.GetMouthAspect();
+ is_identical &= mouth_y == info.GetMouthY();
+ is_identical &= beard_color == info.GetBeardColor();
+ is_identical &= beard_type == info.GetBeardType();
+ is_identical &= mustache_type == info.GetMustacheType();
+ is_identical &= mustache_scale == info.GetMustacheScale();
+ is_identical &= mustache_y == info.GetMustacheY();
+ is_identical &= glass_type == info.GetGlassType();
+ is_identical &= glass_color == info.GetGlassColor();
+ is_identical &= glass_scale == info.GetGlassScale();
+ is_identical &= glass_y == info.GetGlassY();
+ is_identical &= mole_type == info.GetMoleType();
+ is_identical &= mole_scale == info.GetMoleScale();
+ is_identical &= mole_x == info.GetMoleX();
+ is_identical &= mole_y == info.GetMoleY();
+ return is_identical;
+}
+
+} // 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..d0c457fd5
--- /dev/null
+++ b/src/core/hle/service/mii/types/char_info.h
@@ -0,0 +1,137 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii {
+class StoreData;
+
+// This is nn::mii::detail::CharInfoRaw
+class CharInfo {
+public:
+ void SetFromStoreData(const StoreData& store_data_raw);
+
+ ValidationResult Verify() const;
+
+ Common::UUID GetCreateId() const;
+ Nickname GetNickname() const;
+ FontRegion GetFontRegion() const;
+ FavoriteColor GetFavoriteColor() const;
+ Gender GetGender() const;
+ u8 GetHeight() const;
+ u8 GetBuild() const;
+ u8 GetType() const;
+ u8 GetRegionMove() const;
+ FacelineType GetFacelineType() const;
+ FacelineColor GetFacelineColor() const;
+ FacelineWrinkle GetFacelineWrinkle() const;
+ FacelineMake GetFacelineMake() const;
+ HairType GetHairType() const;
+ CommonColor GetHairColor() const;
+ HairFlip GetHairFlip() const;
+ EyeType GetEyeType() const;
+ CommonColor GetEyeColor() const;
+ u8 GetEyeScale() const;
+ u8 GetEyeAspect() const;
+ u8 GetEyeRotate() const;
+ u8 GetEyeX() const;
+ u8 GetEyeY() const;
+ EyebrowType GetEyebrowType() const;
+ CommonColor GetEyebrowColor() const;
+ u8 GetEyebrowScale() const;
+ u8 GetEyebrowAspect() const;
+ u8 GetEyebrowRotate() const;
+ u8 GetEyebrowX() const;
+ u8 GetEyebrowY() const;
+ NoseType GetNoseType() const;
+ u8 GetNoseScale() const;
+ u8 GetNoseY() const;
+ MouthType GetMouthType() const;
+ CommonColor GetMouthColor() const;
+ u8 GetMouthScale() const;
+ u8 GetMouthAspect() const;
+ u8 GetMouthY() const;
+ CommonColor GetBeardColor() const;
+ BeardType GetBeardType() const;
+ MustacheType GetMustacheType() const;
+ u8 GetMustacheScale() const;
+ u8 GetMustacheY() const;
+ GlassType GetGlassType() const;
+ CommonColor GetGlassColor() const;
+ u8 GetGlassScale() const;
+ u8 GetGlassY() const;
+ MoleType GetMoleType() const;
+ u8 GetMoleScale() const;
+ u8 GetMoleX() const;
+ u8 GetMoleY() const;
+
+ bool operator==(const CharInfo& info);
+
+private:
+ Common::UUID create_id{};
+ Nickname name{};
+ u16 null_terminator{};
+ FontRegion font_region{};
+ FavoriteColor favorite_color{};
+ Gender gender{};
+ u8 height{};
+ u8 build{};
+ u8 type{};
+ u8 region_move{};
+ FacelineType faceline_type{};
+ FacelineColor faceline_color{};
+ FacelineWrinkle faceline_wrinkle{};
+ FacelineMake faceline_make{};
+ HairType hair_type{};
+ CommonColor hair_color{};
+ HairFlip hair_flip{};
+ EyeType eye_type{};
+ CommonColor eye_color{};
+ u8 eye_scale{};
+ u8 eye_aspect{};
+ u8 eye_rotate{};
+ u8 eye_x{};
+ u8 eye_y{};
+ EyebrowType eyebrow_type{};
+ CommonColor eyebrow_color{};
+ u8 eyebrow_scale{};
+ u8 eyebrow_aspect{};
+ u8 eyebrow_rotate{};
+ u8 eyebrow_x{};
+ u8 eyebrow_y{};
+ NoseType nose_type{};
+ u8 nose_scale{};
+ u8 nose_y{};
+ MouthType mouth_type{};
+ CommonColor mouth_color{};
+ u8 mouth_scale{};
+ u8 mouth_aspect{};
+ u8 mouth_y{};
+ CommonColor beard_color{};
+ BeardType beard_type{};
+ MustacheType mustache_type{};
+ u8 mustache_scale{};
+ u8 mustache_y{};
+ GlassType glass_type{};
+ CommonColor glass_color{};
+ u8 glass_scale{};
+ u8 glass_y{};
+ MoleType mole_type{};
+ u8 mole_scale{};
+ u8 mole_x{};
+ u8 mole_y{};
+ u8 padding{};
+};
+static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
+static_assert(std::has_unique_object_representations_v<CharInfo>,
+ "All bits of CharInfo must contribute to its value.");
+
+struct CharInfoElement {
+ CharInfo char_info{};
+ Source source{};
+};
+static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size.");
+
+}; // 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..1068031e3
--- /dev/null
+++ b/src/core/hle/service/mii/types/core_data.cpp
@@ -0,0 +1,805 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/core_data.h"
+#include "core/hle/service/mii/types/raw_data.h"
+
+namespace Service::Mii {
+
+void CoreData::SetDefault() {
+ data = {};
+ name = GetDefaultNickname();
+}
+
+void CoreData::BuildRandom(Age age, Gender gender, Race race) {
+ if (gender == Gender::All) {
+ gender = MiiUtil::GetRandomValue(Gender::Max);
+ }
+
+ if (age == Age::All) {
+ const auto random{MiiUtil::GetRandomValue<int>(10)};
+ if (random >= 8) {
+ age = Age::Old;
+ } else if (random >= 4) {
+ age = Age::Normal;
+ } else {
+ age = Age::Young;
+ }
+ }
+
+ if (race == Race::All) {
+ const auto random{MiiUtil::GetRandomValue<int>(10)};
+ if (random >= 8) {
+ race = Race::Black;
+ } else if (random >= 4) {
+ race = Race::White;
+ } else {
+ race = Race::Asian;
+ }
+ }
+
+ SetGender(gender);
+ SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max));
+ SetRegionMove(0);
+ SetFontRegion(FontRegion::Standard);
+ SetType(0);
+ SetHeight(64);
+ SetBuild(64);
+
+ u32 axis_y{};
+ if (gender == Gender::Female && age == Age::Young) {
+ axis_y = MiiUtil::GetRandomValue<u32>(3);
+ }
+
+ const std::size_t index{3 * static_cast<std::size_t>(age) +
+ 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
+
+ const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)};
+ const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at(
+ 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
+ const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
+ const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
+ const auto& hair_type_info{RawData::RandomMiiHairType.at(index)};
+ const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
+ static_cast<std::size_t>(age))};
+ const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)};
+ const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
+ const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
+ const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)};
+ const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)};
+ const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
+
+ data.faceline_type.Assign(
+ faceline_type_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
+ data.faceline_color.Assign(
+ faceline_color_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
+ data.faceline_wrinkle.Assign(
+ faceline_wrinkle_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
+ data.faceline_makeup.Assign(
+ faceline_makeup_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
+
+ data.hair_type.Assign(
+ hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]);
+ SetHairColor(RawData::GetHairColorFromVer3(
+ hair_color_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)]));
+ SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max));
+
+ data.eye_type.Assign(
+ eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]);
+
+ const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
+ const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
+ const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
+ const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]};
+
+ SetEyeColor(RawData::GetEyeColorFromVer3(
+ eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)]));
+ SetEyeScale(4);
+ SetEyeAspect(3);
+ SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate));
+ SetEyeX(2);
+ SetEyeY(static_cast<u8>(axis_y + 12));
+
+ data.eyebrow_type.Assign(
+ eyebrow_type_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
+
+ const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
+ const auto eyebrow_y{race == Race::Asian ? 9 : 10};
+ const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6};
+ const auto eyebrow_rotate{
+ 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]};
+
+ SetEyebrowColor(GetHairColor());
+ SetEyebrowScale(4);
+ SetEyebrowAspect(3);
+ SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate));
+ SetEyebrowX(2);
+ SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y));
+
+ data.nose_type.Assign(
+ nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]);
+ SetNoseScale(gender == Gender::Female ? 3 : 4);
+ SetNoseY(static_cast<u8>(axis_y + 9));
+
+ const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0};
+
+ data.mouth_type.Assign(
+ mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
+ SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color));
+ SetMouthScale(4);
+ SetMouthAspect(3);
+ SetMouthY(static_cast<u8>(axis_y + 13));
+
+ SetBeardColor(GetHairColor());
+ SetMustacheScale(4);
+
+ if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) {
+ const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)};
+
+ auto beard_type{BeardType::None};
+ auto mustache_type{MustacheType::None};
+
+ if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
+ BeardAndMustacheFlag::Beard) {
+ beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max);
+ }
+
+ if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
+ BeardAndMustacheFlag::Mustache) {
+ mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max);
+ }
+
+ SetMustacheType(mustache_type);
+ SetBeardType(beard_type);
+ SetMustacheY(10);
+ } else {
+ SetMustacheType(MustacheType::None);
+ SetBeardType(BeardType::None);
+ SetMustacheY(static_cast<u8>(axis_y + 10));
+ }
+
+ const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)};
+ u8 glasses_type{};
+ while (glasses_type_start < glasses_type_info.values[glasses_type]) {
+ if (++glasses_type >= glasses_type_info.values_count) {
+ glasses_type = 0;
+ break;
+ }
+ }
+
+ SetGlassType(static_cast<GlassType>(glasses_type));
+ SetGlassColor(RawData::GetGlassColorFromVer3(0));
+ SetGlassScale(4);
+ SetGlassY(static_cast<u8>(axis_y + 10));
+
+ SetMoleType(MoleType::None);
+ SetMoleScale(4);
+ SetMoleX(2);
+ SetMoleY(20);
+}
+
+void CoreData::BuildFromCharInfo(const CharInfo& char_info) {
+ name = char_info.GetNickname();
+ SetFontRegion(char_info.GetFontRegion());
+ SetFavoriteColor(char_info.GetFavoriteColor());
+ SetGender(char_info.GetGender());
+ SetHeight(char_info.GetHeight());
+ SetBuild(char_info.GetBuild());
+ SetType(char_info.GetType());
+ SetRegionMove(char_info.GetRegionMove());
+ SetFacelineType(char_info.GetFacelineType());
+ SetFacelineColor(char_info.GetFacelineColor());
+ SetFacelineWrinkle(char_info.GetFacelineWrinkle());
+ SetFacelineMake(char_info.GetFacelineMake());
+ SetHairType(char_info.GetHairType());
+ SetHairColor(char_info.GetHairColor());
+ SetHairFlip(char_info.GetHairFlip());
+ SetEyeType(char_info.GetEyeType());
+ SetEyeColor(char_info.GetEyeColor());
+ SetEyeScale(char_info.GetEyeScale());
+ SetEyeAspect(char_info.GetEyeAspect());
+ SetEyeRotate(char_info.GetEyeRotate());
+ SetEyeX(char_info.GetEyeX());
+ SetEyeY(char_info.GetEyeY());
+ SetEyebrowType(char_info.GetEyebrowType());
+ SetEyebrowColor(char_info.GetEyebrowColor());
+ SetEyebrowScale(char_info.GetEyebrowScale());
+ SetEyebrowAspect(char_info.GetEyebrowAspect());
+ SetEyebrowRotate(char_info.GetEyebrowRotate());
+ SetEyebrowX(char_info.GetEyebrowX());
+ SetEyebrowY(char_info.GetEyebrowY() - 3);
+ SetNoseType(char_info.GetNoseType());
+ SetNoseScale(char_info.GetNoseScale());
+ SetNoseY(char_info.GetNoseY());
+ SetMouthType(char_info.GetMouthType());
+ SetMouthColor(char_info.GetMouthColor());
+ SetMouthScale(char_info.GetMouthScale());
+ SetMouthAspect(char_info.GetMouthAspect());
+ SetMouthY(char_info.GetMouthY());
+ SetBeardColor(char_info.GetBeardColor());
+ SetBeardType(char_info.GetBeardType());
+ SetMustacheType(char_info.GetMustacheType());
+ SetMustacheScale(char_info.GetMustacheScale());
+ SetMustacheY(char_info.GetMustacheY());
+ SetGlassType(char_info.GetGlassType());
+ SetGlassColor(char_info.GetGlassColor());
+ SetGlassScale(char_info.GetGlassScale());
+ SetGlassY(char_info.GetGlassY());
+ SetMoleType(char_info.GetMoleType());
+ SetMoleScale(char_info.GetMoleScale());
+ SetMoleX(char_info.GetMoleX());
+ SetMoleY(char_info.GetMoleY());
+}
+
+ValidationResult CoreData::IsValid() const {
+ if (!name.IsValid()) {
+ return ValidationResult::InvalidName;
+ }
+ if (GetFontRegion() > FontRegion::Max) {
+ return ValidationResult::InvalidFont;
+ }
+ if (GetFavoriteColor() > FavoriteColor::Max) {
+ return ValidationResult::InvalidColor;
+ }
+ if (GetGender() > Gender::Max) {
+ return ValidationResult::InvalidGender;
+ }
+ if (GetHeight() > MaxHeight) {
+ return ValidationResult::InvalidHeight;
+ }
+ if (GetBuild() > MaxBuild) {
+ return ValidationResult::InvalidBuild;
+ }
+ if (GetType() > MaxType) {
+ return ValidationResult::InvalidType;
+ }
+ if (GetRegionMove() > MaxRegionMove) {
+ return ValidationResult::InvalidRegionMove;
+ }
+ if (GetFacelineType() > FacelineType::Max) {
+ return ValidationResult::InvalidFacelineType;
+ }
+ if (GetFacelineColor() > FacelineColor::Max) {
+ return ValidationResult::InvalidFacelineColor;
+ }
+ if (GetFacelineWrinkle() > FacelineWrinkle::Max) {
+ return ValidationResult::InvalidFacelineWrinkle;
+ }
+ if (GetFacelineMake() > FacelineMake::Max) {
+ return ValidationResult::InvalidFacelineMake;
+ }
+ if (GetHairType() > HairType::Max) {
+ return ValidationResult::InvalidHairType;
+ }
+ if (GetHairColor() > CommonColor::Max) {
+ return ValidationResult::InvalidHairColor;
+ }
+ if (GetHairFlip() > HairFlip::Max) {
+ return ValidationResult::InvalidHairFlip;
+ }
+ if (GetEyeType() > EyeType::Max) {
+ return ValidationResult::InvalidEyeType;
+ }
+ if (GetEyeColor() > CommonColor::Max) {
+ return ValidationResult::InvalidEyeColor;
+ }
+ if (GetEyeScale() > MaxEyeScale) {
+ return ValidationResult::InvalidEyeScale;
+ }
+ if (GetEyeAspect() > MaxEyeAspect) {
+ return ValidationResult::InvalidEyeAspect;
+ }
+ if (GetEyeRotate() > MaxEyeRotate) {
+ return ValidationResult::InvalidEyeRotate;
+ }
+ if (GetEyeX() > MaxEyeX) {
+ return ValidationResult::InvalidEyeX;
+ }
+ if (GetEyeY() > MaxEyeY) {
+ return ValidationResult::InvalidEyeY;
+ }
+ if (GetEyebrowType() > EyebrowType::Max) {
+ return ValidationResult::InvalidEyebrowType;
+ }
+ if (GetEyebrowColor() > CommonColor::Max) {
+ return ValidationResult::InvalidEyebrowColor;
+ }
+ if (GetEyebrowScale() > MaxEyebrowScale) {
+ return ValidationResult::InvalidEyebrowScale;
+ }
+ if (GetEyebrowAspect() > MaxEyebrowAspect) {
+ return ValidationResult::InvalidEyebrowAspect;
+ }
+ if (GetEyebrowRotate() > MaxEyebrowRotate) {
+ return ValidationResult::InvalidEyebrowRotate;
+ }
+ if (GetEyebrowX() > MaxEyebrowX) {
+ return ValidationResult::InvalidEyebrowX;
+ }
+ if (GetEyebrowY() > MaxEyebrowY) {
+ return ValidationResult::InvalidEyebrowY;
+ }
+ if (GetNoseType() > NoseType::Max) {
+ return ValidationResult::InvalidNoseType;
+ }
+ if (GetNoseScale() > MaxNoseScale) {
+ return ValidationResult::InvalidNoseScale;
+ }
+ if (GetNoseY() > MaxNoseY) {
+ return ValidationResult::InvalidNoseY;
+ }
+ if (GetMouthType() > MouthType::Max) {
+ return ValidationResult::InvalidMouthType;
+ }
+ if (GetMouthColor() > CommonColor::Max) {
+ return ValidationResult::InvalidMouthColor;
+ }
+ if (GetMouthScale() > MaxMouthScale) {
+ return ValidationResult::InvalidMouthScale;
+ }
+ if (GetMouthAspect() > MaxMoutAspect) {
+ return ValidationResult::InvalidMouthAspect;
+ }
+ if (GetMouthY() > MaxMouthY) {
+ return ValidationResult::InvalidMouthY;
+ }
+ if (GetBeardColor() > CommonColor::Max) {
+ return ValidationResult::InvalidBeardColor;
+ }
+ if (GetBeardType() > BeardType::Max) {
+ return ValidationResult::InvalidBeardType;
+ }
+ if (GetMustacheType() > MustacheType::Max) {
+ return ValidationResult::InvalidMustacheType;
+ }
+ if (GetMustacheScale() > MaxMustacheScale) {
+ return ValidationResult::InvalidMustacheScale;
+ }
+ if (GetMustacheY() > MaxMustacheY) {
+ return ValidationResult::InvalidMustacheY;
+ }
+ if (GetGlassType() > GlassType::Max) {
+ return ValidationResult::InvalidGlassType;
+ }
+ if (GetGlassColor() > CommonColor::Max) {
+ return ValidationResult::InvalidGlassColor;
+ }
+ if (GetGlassScale() > MaxGlassScale) {
+ return ValidationResult::InvalidGlassScale;
+ }
+ if (GetGlassY() > MaxGlassY) {
+ return ValidationResult::InvalidGlassY;
+ }
+ if (GetMoleType() > MoleType::Max) {
+ return ValidationResult::InvalidMoleType;
+ }
+ if (GetMoleScale() > MaxMoleScale) {
+ return ValidationResult::InvalidMoleScale;
+ }
+ if (GetMoleX() > MaxMoleX) {
+ return ValidationResult::InvalidMoleX;
+ }
+ if (GetMoleY() > MaxMoleY) {
+ return ValidationResult::InvalidMoleY;
+ }
+ return ValidationResult::NoErrors;
+}
+
+void CoreData::SetFontRegion(FontRegion value) {
+ data.font_region.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetFavoriteColor(FavoriteColor value) {
+ data.favorite_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetGender(Gender value) {
+ data.gender.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetHeight(u8 value) {
+ data.height.Assign(value);
+}
+
+void CoreData::SetBuild(u8 value) {
+ data.build.Assign(value);
+}
+
+void CoreData::SetType(u8 value) {
+ data.type.Assign(value);
+}
+
+void CoreData::SetRegionMove(u8 value) {
+ data.region_move.Assign(value);
+}
+
+void CoreData::SetFacelineType(FacelineType value) {
+ data.faceline_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetFacelineColor(FacelineColor value) {
+ data.faceline_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetFacelineWrinkle(FacelineWrinkle value) {
+ data.faceline_wrinkle.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetFacelineMake(FacelineMake value) {
+ data.faceline_makeup.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetHairType(HairType value) {
+ data.hair_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetHairColor(CommonColor value) {
+ data.hair_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetHairFlip(HairFlip value) {
+ data.hair_flip.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyeType(EyeType value) {
+ data.eye_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyeColor(CommonColor value) {
+ data.eye_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyeScale(u8 value) {
+ data.eye_scale.Assign(value);
+}
+
+void CoreData::SetEyeAspect(u8 value) {
+ data.eye_aspect.Assign(value);
+}
+
+void CoreData::SetEyeRotate(u8 value) {
+ data.eye_rotate.Assign(value);
+}
+
+void CoreData::SetEyeX(u8 value) {
+ data.eye_x.Assign(value);
+}
+
+void CoreData::SetEyeY(u8 value) {
+ data.eye_y.Assign(value);
+}
+
+void CoreData::SetEyebrowType(EyebrowType value) {
+ data.eyebrow_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyebrowColor(CommonColor value) {
+ data.eyebrow_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyebrowScale(u8 value) {
+ data.eyebrow_scale.Assign(value);
+}
+
+void CoreData::SetEyebrowAspect(u8 value) {
+ data.eyebrow_aspect.Assign(value);
+}
+
+void CoreData::SetEyebrowRotate(u8 value) {
+ data.eyebrow_rotate.Assign(value);
+}
+
+void CoreData::SetEyebrowX(u8 value) {
+ data.eyebrow_x.Assign(value);
+}
+
+void CoreData::SetEyebrowY(u8 value) {
+ data.eyebrow_y.Assign(value);
+}
+
+void CoreData::SetNoseType(NoseType value) {
+ data.nose_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetNoseScale(u8 value) {
+ data.nose_scale.Assign(value);
+}
+
+void CoreData::SetNoseY(u8 value) {
+ data.nose_y.Assign(value);
+}
+
+void CoreData::SetMouthType(MouthType value) {
+ data.mouth_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMouthColor(CommonColor value) {
+ data.mouth_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMouthScale(u8 value) {
+ data.mouth_scale.Assign(value);
+}
+
+void CoreData::SetMouthAspect(u8 value) {
+ data.mouth_aspect.Assign(value);
+}
+
+void CoreData::SetMouthY(u8 value) {
+ data.mouth_y.Assign(value);
+}
+
+void CoreData::SetBeardColor(CommonColor value) {
+ data.beard_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetBeardType(BeardType value) {
+ data.beard_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMustacheType(MustacheType value) {
+ data.mustache_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMustacheScale(u8 value) {
+ data.mustache_scale.Assign(value);
+}
+
+void CoreData::SetMustacheY(u8 value) {
+ data.mustache_y.Assign(value);
+}
+
+void CoreData::SetGlassType(GlassType value) {
+ data.glasses_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetGlassColor(CommonColor value) {
+ data.glasses_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetGlassScale(u8 value) {
+ data.glasses_scale.Assign(value);
+}
+
+void CoreData::SetGlassY(u8 value) {
+ data.glasses_y.Assign(value);
+}
+
+void CoreData::SetMoleType(MoleType value) {
+ data.mole_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMoleScale(u8 value) {
+ data.mole_scale.Assign(value);
+}
+
+void CoreData::SetMoleX(u8 value) {
+ data.mole_x.Assign(value);
+}
+
+void CoreData::SetMoleY(u8 value) {
+ data.mole_y.Assign(value);
+}
+
+void CoreData::SetNickname(Nickname nickname) {
+ name = nickname;
+}
+
+FontRegion CoreData::GetFontRegion() const {
+ return static_cast<FontRegion>(data.font_region.Value());
+}
+
+FavoriteColor CoreData::GetFavoriteColor() const {
+ return static_cast<FavoriteColor>(data.favorite_color.Value());
+}
+
+Gender CoreData::GetGender() const {
+ return static_cast<Gender>(data.gender.Value());
+}
+
+u8 CoreData::GetHeight() const {
+ return static_cast<u8>(data.height.Value());
+}
+
+u8 CoreData::GetBuild() const {
+ return static_cast<u8>(data.build.Value());
+}
+
+u8 CoreData::GetType() const {
+ return static_cast<u8>(data.type.Value());
+}
+
+u8 CoreData::GetRegionMove() const {
+ return static_cast<u8>(data.region_move.Value());
+}
+
+FacelineType CoreData::GetFacelineType() const {
+ return static_cast<FacelineType>(data.faceline_type.Value());
+}
+
+FacelineColor CoreData::GetFacelineColor() const {
+ return static_cast<FacelineColor>(data.faceline_color.Value());
+}
+
+FacelineWrinkle CoreData::GetFacelineWrinkle() const {
+ return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value());
+}
+
+FacelineMake CoreData::GetFacelineMake() const {
+ return static_cast<FacelineMake>(data.faceline_makeup.Value());
+}
+
+HairType CoreData::GetHairType() const {
+ return static_cast<HairType>(data.hair_type.Value());
+}
+
+CommonColor CoreData::GetHairColor() const {
+ return static_cast<CommonColor>(data.hair_color.Value());
+}
+
+HairFlip CoreData::GetHairFlip() const {
+ return static_cast<HairFlip>(data.hair_flip.Value());
+}
+
+EyeType CoreData::GetEyeType() const {
+ return static_cast<EyeType>(data.eye_type.Value());
+}
+
+CommonColor CoreData::GetEyeColor() const {
+ return static_cast<CommonColor>(data.eye_color.Value());
+}
+
+u8 CoreData::GetEyeScale() const {
+ return static_cast<u8>(data.eye_scale.Value());
+}
+
+u8 CoreData::GetEyeAspect() const {
+ return static_cast<u8>(data.eye_aspect.Value());
+}
+
+u8 CoreData::GetEyeRotate() const {
+ return static_cast<u8>(data.eye_rotate.Value());
+}
+
+u8 CoreData::GetEyeX() const {
+ return static_cast<u8>(data.eye_x.Value());
+}
+
+u8 CoreData::GetEyeY() const {
+ return static_cast<u8>(data.eye_y.Value());
+}
+
+EyebrowType CoreData::GetEyebrowType() const {
+ return static_cast<EyebrowType>(data.eyebrow_type.Value());
+}
+
+CommonColor CoreData::GetEyebrowColor() const {
+ return static_cast<CommonColor>(data.eyebrow_color.Value());
+}
+
+u8 CoreData::GetEyebrowScale() const {
+ return static_cast<u8>(data.eyebrow_scale.Value());
+}
+
+u8 CoreData::GetEyebrowAspect() const {
+ return static_cast<u8>(data.eyebrow_aspect.Value());
+}
+
+u8 CoreData::GetEyebrowRotate() const {
+ return static_cast<u8>(data.eyebrow_rotate.Value());
+}
+
+u8 CoreData::GetEyebrowX() const {
+ return static_cast<u8>(data.eyebrow_x.Value());
+}
+
+u8 CoreData::GetEyebrowY() const {
+ return static_cast<u8>(data.eyebrow_y.Value());
+}
+
+NoseType CoreData::GetNoseType() const {
+ return static_cast<NoseType>(data.nose_type.Value());
+}
+
+u8 CoreData::GetNoseScale() const {
+ return static_cast<u8>(data.nose_scale.Value());
+}
+
+u8 CoreData::GetNoseY() const {
+ return static_cast<u8>(data.nose_y.Value());
+}
+
+MouthType CoreData::GetMouthType() const {
+ return static_cast<MouthType>(data.mouth_type.Value());
+}
+
+CommonColor CoreData::GetMouthColor() const {
+ return static_cast<CommonColor>(data.mouth_color.Value());
+}
+
+u8 CoreData::GetMouthScale() const {
+ return static_cast<u8>(data.mouth_scale.Value());
+}
+
+u8 CoreData::GetMouthAspect() const {
+ return static_cast<u8>(data.mouth_aspect.Value());
+}
+
+u8 CoreData::GetMouthY() const {
+ return static_cast<u8>(data.mouth_y.Value());
+}
+
+CommonColor CoreData::GetBeardColor() const {
+ return static_cast<CommonColor>(data.beard_color.Value());
+}
+
+BeardType CoreData::GetBeardType() const {
+ return static_cast<BeardType>(data.beard_type.Value());
+}
+
+MustacheType CoreData::GetMustacheType() const {
+ return static_cast<MustacheType>(data.mustache_type.Value());
+}
+
+u8 CoreData::GetMustacheScale() const {
+ return static_cast<u8>(data.mustache_scale.Value());
+}
+
+u8 CoreData::GetMustacheY() const {
+ return static_cast<u8>(data.mustache_y.Value());
+}
+
+GlassType CoreData::GetGlassType() const {
+ return static_cast<GlassType>(data.glasses_type.Value());
+}
+
+CommonColor CoreData::GetGlassColor() const {
+ return static_cast<CommonColor>(data.glasses_color.Value());
+}
+
+u8 CoreData::GetGlassScale() const {
+ return static_cast<u8>(data.glasses_scale.Value());
+}
+
+u8 CoreData::GetGlassY() const {
+ return static_cast<u8>(data.glasses_y.Value());
+}
+
+MoleType CoreData::GetMoleType() const {
+ return static_cast<MoleType>(data.mole_type.Value());
+}
+
+u8 CoreData::GetMoleScale() const {
+ return static_cast<u8>(data.mole_scale.Value());
+}
+
+u8 CoreData::GetMoleX() const {
+ return static_cast<u8>(data.mole_x.Value());
+}
+
+u8 CoreData::GetMoleY() const {
+ return static_cast<u8>(data.mole_y.Value());
+}
+
+Nickname CoreData::GetNickname() const {
+ return name;
+}
+
+Nickname CoreData::GetDefaultNickname() const {
+ return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'};
+}
+
+Nickname CoreData::GetInvalidNickname() const {
+ return {u'?', u'?', u'?'};
+}
+
+} // 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..8897e4f3b
--- /dev/null
+++ b/src/core/hle/service/mii/types/core_data.h
@@ -0,0 +1,219 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii {
+class CharInfo;
+
+struct StoreDataBitFields {
+ union {
+ u32 word_0{};
+
+ BitField<0, 8, u32> hair_type;
+ BitField<8, 7, u32> height;
+ BitField<15, 1, u32> mole_type;
+ BitField<16, 7, u32> build;
+ BitField<23, 1, u32> hair_flip;
+ BitField<24, 7, u32> hair_color;
+ BitField<31, 1, u32> type;
+ };
+
+ union {
+ u32 word_1{};
+
+ BitField<0, 7, u32> eye_color;
+ BitField<7, 1, u32> gender;
+ BitField<8, 7, u32> eyebrow_color;
+ BitField<16, 7, u32> mouth_color;
+ BitField<24, 7, u32> beard_color;
+ };
+
+ union {
+ u32 word_2{};
+
+ BitField<0, 7, u32> glasses_color;
+ BitField<8, 6, u32> eye_type;
+ BitField<14, 2, u32> region_move;
+ BitField<16, 6, u32> mouth_type;
+ BitField<22, 2, u32> font_region;
+ BitField<24, 5, u32> eye_y;
+ BitField<29, 3, u32> glasses_scale;
+ };
+
+ union {
+ u32 word_3{};
+
+ BitField<0, 5, u32> eyebrow_type;
+ BitField<5, 3, u32> mustache_type;
+ BitField<8, 5, u32> nose_type;
+ BitField<13, 3, u32> beard_type;
+ BitField<16, 5, u32> nose_y;
+ BitField<21, 3, u32> mouth_aspect;
+ BitField<24, 5, u32> mouth_y;
+ BitField<29, 3, u32> eyebrow_aspect;
+ };
+
+ union {
+ u32 word_4{};
+
+ BitField<0, 5, u32> mustache_y;
+ BitField<5, 3, u32> eye_rotate;
+ BitField<8, 5, u32> glasses_y;
+ BitField<13, 3, u32> eye_aspect;
+ BitField<16, 5, u32> mole_x;
+ BitField<21, 3, u32> eye_scale;
+ BitField<24, 5, u32> mole_y;
+ };
+
+ union {
+ u32 word_5{};
+
+ BitField<0, 5, u32> glasses_type;
+ BitField<8, 4, u32> favorite_color;
+ BitField<12, 4, u32> faceline_type;
+ BitField<16, 4, u32> faceline_color;
+ BitField<20, 4, u32> faceline_wrinkle;
+ BitField<24, 4, u32> faceline_makeup;
+ BitField<28, 4, u32> eye_x;
+ };
+
+ union {
+ u32 word_6{};
+
+ BitField<0, 4, u32> eyebrow_scale;
+ BitField<4, 4, u32> eyebrow_rotate;
+ BitField<8, 4, u32> eyebrow_x;
+ BitField<12, 4, u32> eyebrow_y;
+ BitField<16, 4, u32> nose_scale;
+ BitField<20, 4, u32> mouth_scale;
+ BitField<24, 4, u32> mustache_scale;
+ BitField<28, 4, u32> mole_scale;
+ };
+};
+static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size.");
+static_assert(std::is_trivially_copyable_v<StoreDataBitFields>,
+ "StoreDataBitFields is not trivially copyable.");
+
+class CoreData {
+public:
+ void SetDefault();
+ void BuildRandom(Age age, Gender gender, Race race);
+ void BuildFromCharInfo(const CharInfo& char_info);
+
+ ValidationResult IsValid() const;
+
+ void SetFontRegion(FontRegion value);
+ void SetFavoriteColor(FavoriteColor value);
+ void SetGender(Gender value);
+ void SetHeight(u8 value);
+ void SetBuild(u8 value);
+ void SetType(u8 value);
+ void SetRegionMove(u8 value);
+ void SetFacelineType(FacelineType value);
+ void SetFacelineColor(FacelineColor value);
+ void SetFacelineWrinkle(FacelineWrinkle value);
+ void SetFacelineMake(FacelineMake value);
+ void SetHairType(HairType value);
+ void SetHairColor(CommonColor value);
+ void SetHairFlip(HairFlip value);
+ void SetEyeType(EyeType value);
+ void SetEyeColor(CommonColor value);
+ void SetEyeScale(u8 value);
+ void SetEyeAspect(u8 value);
+ void SetEyeRotate(u8 value);
+ void SetEyeX(u8 value);
+ void SetEyeY(u8 value);
+ void SetEyebrowType(EyebrowType value);
+ void SetEyebrowColor(CommonColor value);
+ void SetEyebrowScale(u8 value);
+ void SetEyebrowAspect(u8 value);
+ void SetEyebrowRotate(u8 value);
+ void SetEyebrowX(u8 value);
+ void SetEyebrowY(u8 value);
+ void SetNoseType(NoseType value);
+ void SetNoseScale(u8 value);
+ void SetNoseY(u8 value);
+ void SetMouthType(MouthType value);
+ void SetMouthColor(CommonColor value);
+ void SetMouthScale(u8 value);
+ void SetMouthAspect(u8 value);
+ void SetMouthY(u8 value);
+ void SetBeardColor(CommonColor value);
+ void SetBeardType(BeardType value);
+ void SetMustacheType(MustacheType value);
+ void SetMustacheScale(u8 value);
+ void SetMustacheY(u8 value);
+ void SetGlassType(GlassType value);
+ void SetGlassColor(CommonColor value);
+ void SetGlassScale(u8 value);
+ void SetGlassY(u8 value);
+ void SetMoleType(MoleType value);
+ void SetMoleScale(u8 value);
+ void SetMoleX(u8 value);
+ void SetMoleY(u8 value);
+ void SetNickname(Nickname nickname);
+
+ FontRegion GetFontRegion() const;
+ FavoriteColor GetFavoriteColor() const;
+ Gender GetGender() const;
+ u8 GetHeight() const;
+ u8 GetBuild() const;
+ u8 GetType() const;
+ u8 GetRegionMove() const;
+ FacelineType GetFacelineType() const;
+ FacelineColor GetFacelineColor() const;
+ FacelineWrinkle GetFacelineWrinkle() const;
+ FacelineMake GetFacelineMake() const;
+ HairType GetHairType() const;
+ CommonColor GetHairColor() const;
+ HairFlip GetHairFlip() const;
+ EyeType GetEyeType() const;
+ CommonColor GetEyeColor() const;
+ u8 GetEyeScale() const;
+ u8 GetEyeAspect() const;
+ u8 GetEyeRotate() const;
+ u8 GetEyeX() const;
+ u8 GetEyeY() const;
+ EyebrowType GetEyebrowType() const;
+ CommonColor GetEyebrowColor() const;
+ u8 GetEyebrowScale() const;
+ u8 GetEyebrowAspect() const;
+ u8 GetEyebrowRotate() const;
+ u8 GetEyebrowX() const;
+ u8 GetEyebrowY() const;
+ NoseType GetNoseType() const;
+ u8 GetNoseScale() const;
+ u8 GetNoseY() const;
+ MouthType GetMouthType() const;
+ CommonColor GetMouthColor() const;
+ u8 GetMouthScale() const;
+ u8 GetMouthAspect() const;
+ u8 GetMouthY() const;
+ CommonColor GetBeardColor() const;
+ BeardType GetBeardType() const;
+ MustacheType GetMustacheType() const;
+ u8 GetMustacheScale() const;
+ u8 GetMustacheY() const;
+ GlassType GetGlassType() const;
+ CommonColor GetGlassColor() const;
+ u8 GetGlassScale() const;
+ u8 GetGlassY() const;
+ MoleType GetMoleType() const;
+ u8 GetMoleScale() const;
+ u8 GetMoleX() const;
+ u8 GetMoleY() const;
+ Nickname GetNickname() const;
+ Nickname GetDefaultNickname() const;
+ Nickname GetInvalidNickname() const;
+
+private:
+ StoreDataBitFields data{};
+ Nickname name{};
+};
+static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
+static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable.");
+
+}; // 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..0e1a07fd7 100644
--- a/src/core/hle/service/mii/raw_data.cpp
+++ b/src/core/hle/service/mii/types/raw_data.cpp
@@ -1,11 +1,88 @@
// SPDX-FileCopyrightText: Ryujinx Team and Contributors
// SPDX-License-Identifier: MIT
-#include "core/hle/service/mii/raw_data.h"
+#include "core/hle/service/mii/types/raw_data.h"
namespace Service::Mii::RawData {
-const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
+constexpr std::array<u8, static_cast<u8>(FacelineColor::Count)> FromVer3FacelineColorTable{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
+};
+
+constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3HairColorTable{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0,
+ 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7,
+ 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4,
+};
+
+constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3EyeColorTable{
+ 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4,
+ 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
+ 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2,
+ 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
+};
+
+constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3MouthlineColorTable{
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
+ 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
+ 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
+ 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
+ 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3,
+ 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3,
+};
+
+constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3GlassColorTable{
+ 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3,
+ 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
+ 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5,
+ 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
+};
+
+constexpr std::array<u8, static_cast<u8>(GlassType::Count)> FromVer3GlassTypeTable{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1,
+ 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7,
+};
+
+constexpr std::array<u8, 8> Ver3FacelineColorTable{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
+};
+
+constexpr std::array<u8, 8> Ver3HairColorTable{
+ 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+};
+
+constexpr std::array<u8, 6> Ver3EyeColorTable{
+ 0x8, 0x9, 0xa, 0xb, 0xc, 0xd,
+};
+
+constexpr std::array<u8, 5> Ver3MouthColorTable{
+ 0x13, 0x14, 0x15, 0x16, 0x17,
+};
+
+constexpr std::array<u8, 7> Ver3GlassColorTable{
+ 0x8, 0xe, 0xf, 0x10, 0x11, 0x12, 0x0,
+};
+
+const std::array<u8, 62> EyeRotateLookup{
+ 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
+ 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
+ 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
+};
+
+const std::array<u8, 24> EyebrowRotateLookup{
+ 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08,
+ 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05,
+};
+
+const std::array<Service::Mii::DefaultMii, 2> BaseMii{
Service::Mii::DefaultMii{
.face_type = 0,
.face_color = 0,
@@ -51,11 +128,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Male,
+ .gender = 0,
.favorite_color = 0,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -102,12 +180,16 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Female,
+ .gender = 1,
.favorite_color = 0,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
+};
+
+const std::array<Service::Mii::DefaultMii, 6> DefaultMii{
Service::Mii::DefaultMii{
.face_type = 0,
.face_color = 4,
@@ -153,11 +235,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Male,
+ .gender = 0,
.favorite_color = 4,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -204,11 +287,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Male,
+ .gender = 0,
.favorite_color = 5,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -255,11 +339,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Male,
+ .gender = 0,
.favorite_color = 0,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -306,11 +391,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Female,
+ .gender = 1,
.favorite_color = 2,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -357,11 +443,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Female,
+ .gender = 1,
.favorite_color = 6,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -408,176 +495,177 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Female,
+ .gender = 1,
.favorite_color = 7,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiFaceline{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 13,
.values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 13,
.values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
};
-const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{
- Service::Mii::RandomMiiData3{
+const std::array<RandomMiiData3, 6> RandomMiiFacelineColor{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 0,
.values_count = 10,
.values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 1,
.values_count = 10,
.values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 2,
.values_count = 10,
.values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 0,
.values_count = 10,
.values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 1,
.values_count = 10,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 2,
.values_count = 10,
@@ -585,407 +673,407 @@ const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiHairType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 30,
.values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45,
47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 31,
.values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47,
48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 31,
.values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47,
48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 38,
.values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50,
51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 39,
.values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 39,
.values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 18,
.values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 19,
.values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 19,
.values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 39,
.values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 42,
.values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50,
61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 42,
.values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50,
61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 44,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50,
58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 44,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58,
60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 44,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58,
60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 24,
.values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14,
16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 27,
.values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17,
18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 27,
.values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17,
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{
};
const std::array<RandomMiiData3, 9> RandomMiiHairColor{
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 0,
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 1,
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 2,
.values_count = 20,
.values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 0,
.values_count = 20,
.values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 1,
.values_count = 20,
.values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 2,
.values_count = 20,
.values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 2,
.arg_2 = 0,
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 2,
.arg_2 = 1,
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 2,
.arg_2 = 2,
.values_count = 20,
@@ -1049,598 +1137,642 @@ const std::array<RandomMiiData3, 9> RandomMiiHairColor{
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiEyeType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 26,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27,
29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 26,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27,
29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 27,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27,
29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 35,
.values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29,
31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 35,
.values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29,
31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 35,
.values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29,
31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 30,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21,
22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 30,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21,
22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 30,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22,
26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 39,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27,
28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 39,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27,
28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 40,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26,
27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 46,
.values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37,
38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 46,
.values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37,
38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 46,
.values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18,
19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37,
38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 34,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23,
24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 34,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23,
24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 35,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24,
25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
},
};
-const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{
- Service::Mii::RandomMiiData2{
+const std::array<RandomMiiData2, 3> RandomMiiEyeColor{
+ RandomMiiData2{
.arg_1 = 0,
.values_count = 10,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
},
- Service::Mii::RandomMiiData2{
+ RandomMiiData2{
.arg_1 = 1,
.values_count = 10,
.values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5},
},
- Service::Mii::RandomMiiData2{
+ RandomMiiData2{
.arg_1 = 2,
.values_count = 10,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiEyebrowType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 23,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 23,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 23,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 21,
.values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 21,
.values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 21,
.values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 9,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 9,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 9,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 11,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 11,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 11,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 9,
.values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 9,
.values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 9,
.values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiNoseType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 11,
.values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 11,
.values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 11,
.values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 15,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 15,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 8,
.values = {0, 1, 3, 4, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 8,
.values = {0, 1, 3, 4, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 8,
.values = {0, 1, 3, 4, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 11,
.values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 10,
.values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 11,
.values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 10,
.values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiMouthType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 25,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18,
19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 27,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17,
18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 28,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17,
18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 24,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 26,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 26,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 24,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 26,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 26,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15,
17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 26,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 26,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15,
17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 26,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 24,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14,
15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
},
};
-const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{
- Service::Mii::RandomMiiData2{
+const std::array<RandomMiiData2, 3> RandomMiiGlassType{
+ RandomMiiData2{
.arg_1 = 0,
- .values_count = 9,
- .values = {90, 94, 96, 100, 0, 0, 0, 0, 0},
+ .values_count = 4,
+ .values = {90, 94, 96, 100},
},
- Service::Mii::RandomMiiData2{
+ RandomMiiData2{
.arg_1 = 1,
- .values_count = 9,
- .values = {83, 86, 90, 93, 94, 96, 98, 100, 0},
+ .values_count = 8,
+ .values = {83, 86, 90, 93, 94, 96, 98, 100},
},
- Service::Mii::RandomMiiData2{
+ RandomMiiData2{
.arg_1 = 2,
- .values_count = 9,
- .values = {78, 83, 0, 93, 0, 0, 98, 100, 0},
+ .values_count = 8,
+ .values = {78, 83, 0, 93, 0, 0, 98, 100},
},
};
+u8 FromVer3GetFacelineColor(u8 color) {
+ return FromVer3FacelineColorTable[color];
+}
+
+u8 FromVer3GetHairColor(u8 color) {
+ return FromVer3HairColorTable[color];
+}
+
+u8 FromVer3GetEyeColor(u8 color) {
+ return FromVer3EyeColorTable[color];
+}
+
+u8 FromVer3GetMouthlineColor(u8 color) {
+ return FromVer3MouthlineColorTable[color];
+}
+
+u8 FromVer3GetGlassColor(u8 color) {
+ return FromVer3GlassColorTable[color];
+}
+
+u8 FromVer3GetGlassType(u8 type) {
+ return FromVer3GlassTypeTable[type];
+}
+
+FacelineColor GetFacelineColorFromVer3(u32 color) {
+ return static_cast<FacelineColor>(Ver3FacelineColorTable[color]);
+}
+
+CommonColor GetHairColorFromVer3(u32 color) {
+ return static_cast<CommonColor>(Ver3HairColorTable[color]);
+}
+
+CommonColor GetEyeColorFromVer3(u32 color) {
+ return static_cast<CommonColor>(Ver3EyeColorTable[color]);
+}
+
+CommonColor GetMouthColorFromVer3(u32 color) {
+ return static_cast<CommonColor>(Ver3MouthColorTable[color]);
+}
+
+CommonColor GetGlassColorFromVer3(u32 color) {
+ return static_cast<CommonColor>(Ver3GlassColorTable[color]);
+}
+
} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii::RawData {
+
+struct RandomMiiValues {
+ std::array<u8, 188> values{};
+};
+static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
+
+struct RandomMiiData4 {
+ u32 gender{};
+ u32 age{};
+ u32 race{};
+ u32 values_count{};
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
+
+struct RandomMiiData3 {
+ u32 arg_1;
+ u32 arg_2;
+ u32 values_count;
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
+
+struct RandomMiiData2 {
+ u32 arg_1;
+ u32 values_count;
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
+
+extern const std::array<Service::Mii::DefaultMii, 2> BaseMii;
+extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii;
+
+extern const std::array<u8, 62> EyeRotateLookup;
+extern const std::array<u8, 24> EyebrowRotateLookup;
+
+extern const std::array<RandomMiiData4, 18> RandomMiiFaceline;
+extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor;
+extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle;
+extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup;
+extern const std::array<RandomMiiData4, 18> RandomMiiHairType;
+extern const std::array<RandomMiiData3, 9> RandomMiiHairColor;
+extern const std::array<RandomMiiData4, 18> RandomMiiEyeType;
+extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor;
+extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType;
+extern const std::array<RandomMiiData4, 18> RandomMiiNoseType;
+extern const std::array<RandomMiiData4, 18> RandomMiiMouthType;
+extern const std::array<RandomMiiData2, 3> RandomMiiGlassType;
+
+u8 FromVer3GetFacelineColor(u8 color);
+u8 FromVer3GetHairColor(u8 color);
+u8 FromVer3GetEyeColor(u8 color);
+u8 FromVer3GetMouthlineColor(u8 color);
+u8 FromVer3GetGlassColor(u8 color);
+u8 FromVer3GetGlassType(u8 type);
+
+FacelineColor GetFacelineColorFromVer3(u32 color);
+CommonColor GetHairColorFromVer3(u32 color);
+CommonColor GetEyeColorFromVer3(u32 color);
+CommonColor GetMouthColorFromVer3(u32 color);
+CommonColor GetGlassColorFromVer3(u32 color);
+
+} // 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..127221fdb
--- /dev/null
+++ b/src/core/hle/service/mii/types/store_data.cpp
@@ -0,0 +1,676 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/raw_data.h"
+#include "core/hle/service/mii/types/store_data.h"
+
+namespace Service::Mii {
+
+void StoreData::BuildDefault(u32 mii_index) {
+ const auto& default_mii = RawData::DefaultMii[mii_index];
+ core_data.SetDefault();
+
+ core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
+ core_data.SetFacelineColor(
+ RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
+ core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
+ core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
+
+ core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
+ core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
+ core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
+ core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
+ core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
+ core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
+ core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
+ core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
+ core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
+ core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
+
+ core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
+ core_data.SetEyebrowColor(
+ RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
+ core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
+ core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
+ core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
+ core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
+ core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
+
+ core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
+ core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
+ core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
+
+ core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
+ core_data.SetMouthColor(
+ RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
+ core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
+ core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
+ core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
+
+ core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
+ core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
+ core_data.SetBeardColor(
+ RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
+ core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
+ core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
+
+ core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
+ core_data.SetGlassColor(
+ RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
+ core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
+ core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
+
+ core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
+ core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
+ core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
+ core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
+
+ core_data.SetHeight(static_cast<u8>(default_mii.height));
+ core_data.SetBuild(static_cast<u8>(default_mii.weight));
+ core_data.SetGender(static_cast<Gender>(default_mii.gender));
+ core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
+ core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
+ core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
+ core_data.SetType(static_cast<u8>(default_mii.type));
+ core_data.SetNickname(default_mii.nickname);
+
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+void StoreData::BuildBase(Gender gender) {
+ const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0];
+ core_data.SetDefault();
+
+ core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
+ core_data.SetFacelineColor(
+ RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
+ core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
+ core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
+
+ core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
+ core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
+ core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
+ core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
+ core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
+ core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
+ core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
+ core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
+ core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
+ core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
+
+ core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
+ core_data.SetEyebrowColor(
+ RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
+ core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
+ core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
+ core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
+ core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
+ core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
+
+ core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
+ core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
+ core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
+
+ core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
+ core_data.SetMouthColor(
+ RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
+ core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
+ core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
+ core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
+
+ core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
+ core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
+ core_data.SetBeardColor(
+ RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
+ core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
+ core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
+
+ core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
+ core_data.SetGlassColor(
+ RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
+ core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
+ core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
+
+ core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
+ core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
+ core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
+ core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
+
+ core_data.SetHeight(static_cast<u8>(default_mii.height));
+ core_data.SetBuild(static_cast<u8>(default_mii.weight));
+ core_data.SetGender(static_cast<Gender>(default_mii.gender));
+ core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
+ core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
+ core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
+ core_data.SetType(static_cast<u8>(default_mii.type));
+ core_data.SetNickname(default_mii.nickname);
+
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+void StoreData::BuildRandom(Age age, Gender gender, Race race) {
+ core_data.BuildRandom(age, gender, race);
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+void StoreData::BuildWithCharInfo(const CharInfo& char_info) {
+ core_data.BuildFromCharInfo(char_info);
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+void StoreData::BuildWithCoreData(const CoreData& in_core_data) {
+ core_data = in_core_data;
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+Result StoreData::Restore() {
+ // TODO: Implement this
+ return ResultNotUpdated;
+}
+
+ValidationResult StoreData::IsValid() const {
+ if (core_data.IsValid() != ValidationResult::NoErrors) {
+ return core_data.IsValid();
+ }
+ if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) {
+ return ValidationResult::InvalidChecksum;
+ }
+ const auto device_id = MiiUtil::GetDeviceId();
+ if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) {
+ return ValidationResult::InvalidChecksum;
+ }
+ return ValidationResult::NoErrors;
+}
+
+bool StoreData::IsSpecial() const {
+ return GetType() == 1;
+}
+
+void StoreData::SetFontRegion(FontRegion value) {
+ core_data.SetFontRegion(value);
+}
+
+void StoreData::SetFavoriteColor(FavoriteColor value) {
+ core_data.SetFavoriteColor(value);
+}
+
+void StoreData::SetGender(Gender value) {
+ core_data.SetGender(value);
+}
+
+void StoreData::SetHeight(u8 value) {
+ core_data.SetHeight(value);
+}
+
+void StoreData::SetBuild(u8 value) {
+ core_data.SetBuild(value);
+}
+
+void StoreData::SetType(u8 value) {
+ core_data.SetType(value);
+}
+
+void StoreData::SetRegionMove(u8 value) {
+ core_data.SetRegionMove(value);
+}
+
+void StoreData::SetFacelineType(FacelineType value) {
+ core_data.SetFacelineType(value);
+}
+
+void StoreData::SetFacelineColor(FacelineColor value) {
+ core_data.SetFacelineColor(value);
+}
+
+void StoreData::SetFacelineWrinkle(FacelineWrinkle value) {
+ core_data.SetFacelineWrinkle(value);
+}
+
+void StoreData::SetFacelineMake(FacelineMake value) {
+ core_data.SetFacelineMake(value);
+}
+
+void StoreData::SetHairType(HairType value) {
+ core_data.SetHairType(value);
+}
+
+void StoreData::SetHairColor(CommonColor value) {
+ core_data.SetHairColor(value);
+}
+
+void StoreData::SetHairFlip(HairFlip value) {
+ core_data.SetHairFlip(value);
+}
+
+void StoreData::SetEyeType(EyeType value) {
+ core_data.SetEyeType(value);
+}
+
+void StoreData::SetEyeColor(CommonColor value) {
+ core_data.SetEyeColor(value);
+}
+
+void StoreData::SetEyeScale(u8 value) {
+ core_data.SetEyeScale(value);
+}
+
+void StoreData::SetEyeAspect(u8 value) {
+ core_data.SetEyeAspect(value);
+}
+
+void StoreData::SetEyeRotate(u8 value) {
+ core_data.SetEyeRotate(value);
+}
+
+void StoreData::SetEyeX(u8 value) {
+ core_data.SetEyeX(value);
+}
+
+void StoreData::SetEyeY(u8 value) {
+ core_data.SetEyeY(value);
+}
+
+void StoreData::SetEyebrowType(EyebrowType value) {
+ core_data.SetEyebrowType(value);
+}
+
+void StoreData::SetEyebrowColor(CommonColor value) {
+ core_data.SetEyebrowColor(value);
+}
+
+void StoreData::SetEyebrowScale(u8 value) {
+ core_data.SetEyebrowScale(value);
+}
+
+void StoreData::SetEyebrowAspect(u8 value) {
+ core_data.SetEyebrowAspect(value);
+}
+
+void StoreData::SetEyebrowRotate(u8 value) {
+ core_data.SetEyebrowRotate(value);
+}
+
+void StoreData::SetEyebrowX(u8 value) {
+ core_data.SetEyebrowX(value);
+}
+
+void StoreData::SetEyebrowY(u8 value) {
+ core_data.SetEyebrowY(value);
+}
+
+void StoreData::SetNoseType(NoseType value) {
+ core_data.SetNoseType(value);
+}
+
+void StoreData::SetNoseScale(u8 value) {
+ core_data.SetNoseScale(value);
+}
+
+void StoreData::SetNoseY(u8 value) {
+ core_data.SetNoseY(value);
+}
+
+void StoreData::SetMouthType(MouthType value) {
+ core_data.SetMouthType(value);
+}
+
+void StoreData::SetMouthColor(CommonColor value) {
+ core_data.SetMouthColor(value);
+}
+
+void StoreData::SetMouthScale(u8 value) {
+ core_data.SetMouthScale(value);
+}
+
+void StoreData::SetMouthAspect(u8 value) {
+ core_data.SetMouthAspect(value);
+}
+
+void StoreData::SetMouthY(u8 value) {
+ core_data.SetMouthY(value);
+}
+
+void StoreData::SetBeardColor(CommonColor value) {
+ core_data.SetBeardColor(value);
+}
+
+void StoreData::SetBeardType(BeardType value) {
+ core_data.SetBeardType(value);
+}
+
+void StoreData::SetMustacheType(MustacheType value) {
+ core_data.SetMustacheType(value);
+}
+
+void StoreData::SetMustacheScale(u8 value) {
+ core_data.SetMustacheScale(value);
+}
+
+void StoreData::SetMustacheY(u8 value) {
+ core_data.SetMustacheY(value);
+}
+
+void StoreData::SetGlassType(GlassType value) {
+ core_data.SetGlassType(value);
+}
+
+void StoreData::SetGlassColor(CommonColor value) {
+ core_data.SetGlassColor(value);
+}
+
+void StoreData::SetGlassScale(u8 value) {
+ core_data.SetGlassScale(value);
+}
+
+void StoreData::SetGlassY(u8 value) {
+ core_data.SetGlassY(value);
+}
+
+void StoreData::SetMoleType(MoleType value) {
+ core_data.SetMoleType(value);
+}
+
+void StoreData::SetMoleScale(u8 value) {
+ core_data.SetMoleScale(value);
+}
+
+void StoreData::SetMoleX(u8 value) {
+ core_data.SetMoleX(value);
+}
+
+void StoreData::SetMoleY(u8 value) {
+ core_data.SetMoleY(value);
+}
+
+void StoreData::SetNickname(Nickname value) {
+ core_data.SetNickname(value);
+}
+
+void StoreData::SetInvalidName() {
+ const auto& invalid_name = core_data.GetInvalidNickname();
+ core_data.SetNickname(invalid_name);
+ SetChecksum();
+}
+
+void StoreData::SetChecksum() {
+ SetDataChecksum();
+ SetDeviceChecksum();
+}
+
+void StoreData::SetDataChecksum() {
+ data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID));
+}
+
+void StoreData::SetDeviceChecksum() {
+ const auto device_id = MiiUtil::GetDeviceId();
+ device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData));
+}
+
+Common::UUID StoreData::GetCreateId() const {
+ return create_id;
+}
+
+FontRegion StoreData::GetFontRegion() const {
+ return static_cast<FontRegion>(core_data.GetFontRegion());
+}
+
+FavoriteColor StoreData::GetFavoriteColor() const {
+ return core_data.GetFavoriteColor();
+}
+
+Gender StoreData::GetGender() const {
+ return core_data.GetGender();
+}
+
+u8 StoreData::GetHeight() const {
+ return core_data.GetHeight();
+}
+
+u8 StoreData::GetBuild() const {
+ return core_data.GetBuild();
+}
+
+u8 StoreData::GetType() const {
+ return core_data.GetType();
+}
+
+u8 StoreData::GetRegionMove() const {
+ return core_data.GetRegionMove();
+}
+
+FacelineType StoreData::GetFacelineType() const {
+ return core_data.GetFacelineType();
+}
+
+FacelineColor StoreData::GetFacelineColor() const {
+ return core_data.GetFacelineColor();
+}
+
+FacelineWrinkle StoreData::GetFacelineWrinkle() const {
+ return core_data.GetFacelineWrinkle();
+}
+
+FacelineMake StoreData::GetFacelineMake() const {
+ return core_data.GetFacelineMake();
+}
+
+HairType StoreData::GetHairType() const {
+ return core_data.GetHairType();
+}
+
+CommonColor StoreData::GetHairColor() const {
+ return core_data.GetHairColor();
+}
+
+HairFlip StoreData::GetHairFlip() const {
+ return core_data.GetHairFlip();
+}
+
+EyeType StoreData::GetEyeType() const {
+ return core_data.GetEyeType();
+}
+
+CommonColor StoreData::GetEyeColor() const {
+ return core_data.GetEyeColor();
+}
+
+u8 StoreData::GetEyeScale() const {
+ return core_data.GetEyeScale();
+}
+
+u8 StoreData::GetEyeAspect() const {
+ return core_data.GetEyeAspect();
+}
+
+u8 StoreData::GetEyeRotate() const {
+ return core_data.GetEyeRotate();
+}
+
+u8 StoreData::GetEyeX() const {
+ return core_data.GetEyeX();
+}
+
+u8 StoreData::GetEyeY() const {
+ return core_data.GetEyeY();
+}
+
+EyebrowType StoreData::GetEyebrowType() const {
+ return core_data.GetEyebrowType();
+}
+
+CommonColor StoreData::GetEyebrowColor() const {
+ return core_data.GetEyebrowColor();
+}
+
+u8 StoreData::GetEyebrowScale() const {
+ return core_data.GetEyebrowScale();
+}
+
+u8 StoreData::GetEyebrowAspect() const {
+ return core_data.GetEyebrowAspect();
+}
+
+u8 StoreData::GetEyebrowRotate() const {
+ return core_data.GetEyebrowRotate();
+}
+
+u8 StoreData::GetEyebrowX() const {
+ return core_data.GetEyebrowX();
+}
+
+u8 StoreData::GetEyebrowY() const {
+ return core_data.GetEyebrowY();
+}
+
+NoseType StoreData::GetNoseType() const {
+ return core_data.GetNoseType();
+}
+
+u8 StoreData::GetNoseScale() const {
+ return core_data.GetNoseScale();
+}
+
+u8 StoreData::GetNoseY() const {
+ return core_data.GetNoseY();
+}
+
+MouthType StoreData::GetMouthType() const {
+ return core_data.GetMouthType();
+}
+
+CommonColor StoreData::GetMouthColor() const {
+ return core_data.GetMouthColor();
+}
+
+u8 StoreData::GetMouthScale() const {
+ return core_data.GetMouthScale();
+}
+
+u8 StoreData::GetMouthAspect() const {
+ return core_data.GetMouthAspect();
+}
+
+u8 StoreData::GetMouthY() const {
+ return core_data.GetMouthY();
+}
+
+CommonColor StoreData::GetBeardColor() const {
+ return core_data.GetBeardColor();
+}
+
+BeardType StoreData::GetBeardType() const {
+ return core_data.GetBeardType();
+}
+
+MustacheType StoreData::GetMustacheType() const {
+ return core_data.GetMustacheType();
+}
+
+u8 StoreData::GetMustacheScale() const {
+ return core_data.GetMustacheScale();
+}
+
+u8 StoreData::GetMustacheY() const {
+ return core_data.GetMustacheY();
+}
+
+GlassType StoreData::GetGlassType() const {
+ return core_data.GetGlassType();
+}
+
+CommonColor StoreData::GetGlassColor() const {
+ return core_data.GetGlassColor();
+}
+
+u8 StoreData::GetGlassScale() const {
+ return core_data.GetGlassScale();
+}
+
+u8 StoreData::GetGlassY() const {
+ return core_data.GetGlassY();
+}
+
+MoleType StoreData::GetMoleType() const {
+ return core_data.GetMoleType();
+}
+
+u8 StoreData::GetMoleScale() const {
+ return core_data.GetMoleScale();
+}
+
+u8 StoreData::GetMoleX() const {
+ return core_data.GetMoleX();
+}
+
+u8 StoreData::GetMoleY() const {
+ return core_data.GetMoleY();
+}
+
+Nickname StoreData::GetNickname() const {
+ return core_data.GetNickname();
+}
+
+bool StoreData::operator==(const StoreData& data) {
+ bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors;
+ is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
+ is_identical &= GetCreateId() == data.GetCreateId();
+ is_identical &= GetFontRegion() == data.GetFontRegion();
+ is_identical &= GetFavoriteColor() == data.GetFavoriteColor();
+ is_identical &= GetGender() == data.GetGender();
+ is_identical &= GetHeight() == data.GetHeight();
+ is_identical &= GetBuild() == data.GetBuild();
+ is_identical &= GetType() == data.GetType();
+ is_identical &= GetRegionMove() == data.GetRegionMove();
+ is_identical &= GetFacelineType() == data.GetFacelineType();
+ is_identical &= GetFacelineColor() == data.GetFacelineColor();
+ is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle();
+ is_identical &= GetFacelineMake() == data.GetFacelineMake();
+ is_identical &= GetHairType() == data.GetHairType();
+ is_identical &= GetHairColor() == data.GetHairColor();
+ is_identical &= GetHairFlip() == data.GetHairFlip();
+ is_identical &= GetEyeType() == data.GetEyeType();
+ is_identical &= GetEyeColor() == data.GetEyeColor();
+ is_identical &= GetEyeScale() == data.GetEyeScale();
+ is_identical &= GetEyeAspect() == data.GetEyeAspect();
+ is_identical &= GetEyeRotate() == data.GetEyeRotate();
+ is_identical &= GetEyeX() == data.GetEyeX();
+ is_identical &= GetEyeY() == data.GetEyeY();
+ is_identical &= GetEyebrowType() == data.GetEyebrowType();
+ is_identical &= GetEyebrowColor() == data.GetEyebrowColor();
+ is_identical &= GetEyebrowScale() == data.GetEyebrowScale();
+ is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect();
+ is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate();
+ is_identical &= GetEyebrowX() == data.GetEyebrowX();
+ is_identical &= GetEyebrowY() == data.GetEyebrowY();
+ is_identical &= GetNoseType() == data.GetNoseType();
+ is_identical &= GetNoseScale() == data.GetNoseScale();
+ is_identical &= GetNoseY() == data.GetNoseY();
+ is_identical &= GetMouthType() == data.GetMouthType();
+ is_identical &= GetMouthColor() == data.GetMouthColor();
+ is_identical &= GetMouthScale() == data.GetMouthScale();
+ is_identical &= GetMouthAspect() == data.GetMouthAspect();
+ is_identical &= GetMouthY() == data.GetMouthY();
+ is_identical &= GetBeardColor() == data.GetBeardColor();
+ is_identical &= GetBeardType() == data.GetBeardType();
+ is_identical &= GetMustacheType() == data.GetMustacheType();
+ is_identical &= GetMustacheScale() == data.GetMustacheScale();
+ is_identical &= GetMustacheY() == data.GetMustacheY();
+ is_identical &= GetGlassType() == data.GetGlassType();
+ is_identical &= GetGlassColor() == data.GetGlassColor();
+ is_identical &= GetGlassScale() == data.GetGlassScale();
+ is_identical &= GetGlassY() == data.GetGlassY();
+ is_identical &= GetMoleType() == data.GetMoleType();
+ is_identical &= GetMoleScale() == data.GetMoleScale();
+ is_identical &= GetMoleX() == data.GetMoleX();
+ is_identical &= data.GetMoleY() == data.GetMoleY();
+ return is_identical;
+}
+
+} // 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..ed5dfb949
--- /dev/null
+++ b/src/core/hle/service/mii/types/store_data.h
@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+#include "core/hle/service/mii/mii_types.h"
+#include "core/hle/service/mii/types/core_data.h"
+
+namespace Service::Mii {
+
+class StoreData {
+public:
+ void BuildDefault(u32 mii_index);
+ void BuildBase(Gender gender);
+ void BuildRandom(Age age, Gender gender, Race race);
+ void BuildWithCharInfo(const CharInfo& char_info);
+ void BuildWithCoreData(const CoreData& in_core_data);
+ Result Restore();
+
+ ValidationResult IsValid() const;
+
+ bool IsSpecial() const;
+
+ void SetFontRegion(FontRegion value);
+ void SetFavoriteColor(FavoriteColor value);
+ void SetGender(Gender value);
+ void SetHeight(u8 value);
+ void SetBuild(u8 value);
+ void SetType(u8 value);
+ void SetRegionMove(u8 value);
+ void SetFacelineType(FacelineType value);
+ void SetFacelineColor(FacelineColor value);
+ void SetFacelineWrinkle(FacelineWrinkle value);
+ void SetFacelineMake(FacelineMake value);
+ void SetHairType(HairType value);
+ void SetHairColor(CommonColor value);
+ void SetHairFlip(HairFlip value);
+ void SetEyeType(EyeType value);
+ void SetEyeColor(CommonColor value);
+ void SetEyeScale(u8 value);
+ void SetEyeAspect(u8 value);
+ void SetEyeRotate(u8 value);
+ void SetEyeX(u8 value);
+ void SetEyeY(u8 value);
+ void SetEyebrowType(EyebrowType value);
+ void SetEyebrowColor(CommonColor value);
+ void SetEyebrowScale(u8 value);
+ void SetEyebrowAspect(u8 value);
+ void SetEyebrowRotate(u8 value);
+ void SetEyebrowX(u8 value);
+ void SetEyebrowY(u8 value);
+ void SetNoseType(NoseType value);
+ void SetNoseScale(u8 value);
+ void SetNoseY(u8 value);
+ void SetMouthType(MouthType value);
+ void SetMouthColor(CommonColor value);
+ void SetMouthScale(u8 value);
+ void SetMouthAspect(u8 value);
+ void SetMouthY(u8 value);
+ void SetBeardColor(CommonColor value);
+ void SetBeardType(BeardType value);
+ void SetMustacheType(MustacheType value);
+ void SetMustacheScale(u8 value);
+ void SetMustacheY(u8 value);
+ void SetGlassType(GlassType value);
+ void SetGlassColor(CommonColor value);
+ void SetGlassScale(u8 value);
+ void SetGlassY(u8 value);
+ void SetMoleType(MoleType value);
+ void SetMoleScale(u8 value);
+ void SetMoleX(u8 value);
+ void SetMoleY(u8 value);
+ void SetNickname(Nickname nickname);
+ void SetInvalidName();
+ void SetChecksum();
+ void SetDataChecksum();
+ void SetDeviceChecksum();
+
+ Common::UUID GetCreateId() const;
+ FontRegion GetFontRegion() const;
+ FavoriteColor GetFavoriteColor() const;
+ Gender GetGender() const;
+ u8 GetHeight() const;
+ u8 GetBuild() const;
+ u8 GetType() const;
+ u8 GetRegionMove() const;
+ FacelineType GetFacelineType() const;
+ FacelineColor GetFacelineColor() const;
+ FacelineWrinkle GetFacelineWrinkle() const;
+ FacelineMake GetFacelineMake() const;
+ HairType GetHairType() const;
+ CommonColor GetHairColor() const;
+ HairFlip GetHairFlip() const;
+ EyeType GetEyeType() const;
+ CommonColor GetEyeColor() const;
+ u8 GetEyeScale() const;
+ u8 GetEyeAspect() const;
+ u8 GetEyeRotate() const;
+ u8 GetEyeX() const;
+ u8 GetEyeY() const;
+ EyebrowType GetEyebrowType() const;
+ CommonColor GetEyebrowColor() const;
+ u8 GetEyebrowScale() const;
+ u8 GetEyebrowAspect() const;
+ u8 GetEyebrowRotate() const;
+ u8 GetEyebrowX() const;
+ u8 GetEyebrowY() const;
+ NoseType GetNoseType() const;
+ u8 GetNoseScale() const;
+ u8 GetNoseY() const;
+ MouthType GetMouthType() const;
+ CommonColor GetMouthColor() const;
+ u8 GetMouthScale() const;
+ u8 GetMouthAspect() const;
+ u8 GetMouthY() const;
+ CommonColor GetBeardColor() const;
+ BeardType GetBeardType() const;
+ MustacheType GetMustacheType() const;
+ u8 GetMustacheScale() const;
+ u8 GetMustacheY() const;
+ GlassType GetGlassType() const;
+ CommonColor GetGlassColor() const;
+ u8 GetGlassScale() const;
+ u8 GetGlassY() const;
+ MoleType GetMoleType() const;
+ u8 GetMoleScale() const;
+ u8 GetMoleX() const;
+ u8 GetMoleY() const;
+ Nickname GetNickname() const;
+
+ bool operator==(const StoreData& data);
+
+private:
+ CoreData core_data{};
+ Common::UUID create_id{};
+ u16 data_crc{};
+ u16 device_crc{};
+};
+static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
+static_assert(std::is_trivially_copyable_v<StoreData>,
+ "StoreData type must be trivially copyable.");
+
+struct StoreDataElement {
+ StoreData store_data{};
+ Source source{};
+};
+static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size.");
+
+}; // 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..a019cc9f7
--- /dev/null
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -0,0 +1,241 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/raw_data.h"
+#include "core/hle/service/mii/types/store_data.h"
+#include "core/hle/service/mii/types/ver3_store_data.h"
+
+namespace Service::Mii {
+
+void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
+ faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf;
+ hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f;
+ eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f;
+ eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f;
+ mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f;
+ beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f;
+ glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f;
+ glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f;
+}
+
+void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
+ out_store_data.BuildBase(Gender::Male);
+
+ out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value()));
+ out_store_data.SetFavoriteColor(
+ static_cast<FavoriteColor>(mii_information.favorite_color.Value()));
+ out_store_data.SetHeight(height);
+ out_store_data.SetBuild(build);
+
+ out_store_data.SetNickname(mii_name);
+ out_store_data.SetFontRegion(
+ static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value())));
+
+ out_store_data.SetFacelineType(
+ static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
+ out_store_data.SetFacelineColor(
+ RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value()));
+ out_store_data.SetFacelineWrinkle(
+ static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value()));
+ out_store_data.SetFacelineMake(
+ static_cast<FacelineMake>(appearance_bits2.faceline_make.Value()));
+
+ out_store_data.SetHairType(static_cast<HairType>(hair_type));
+ out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value()));
+ out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value()));
+
+ out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value()));
+ out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value()));
+ out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value()));
+ out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value()));
+ out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value()));
+ out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value()));
+ out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value()));
+
+ out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value()));
+ out_store_data.SetEyebrowColor(
+ RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value()));
+ out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value()));
+ out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value()));
+ out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value()));
+ out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value()));
+ out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3));
+
+ out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value()));
+ out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value()));
+ out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value()));
+
+ out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value()));
+ out_store_data.SetMouthColor(
+ RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value()));
+ out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value()));
+ out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value()));
+ out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value()));
+
+ out_store_data.SetMustacheType(
+ static_cast<MustacheType>(appearance_bits8.mustache_type.Value()));
+ out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value()));
+ out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value()));
+
+ out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value()));
+ out_store_data.SetBeardColor(
+ RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value()));
+
+ // Glass type is compatible as it is. It doesn't need a table
+ out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value()));
+ out_store_data.SetGlassColor(
+ RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value()));
+ out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value()));
+ out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value()));
+
+ out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value()));
+ out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value()));
+ out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value()));
+ out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value()));
+
+ out_store_data.SetChecksum();
+}
+
+void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
+ version = 1;
+ mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
+ mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
+ height = store_data.GetHeight();
+ build = store_data.GetBuild();
+
+ mii_name = store_data.GetNickname();
+ region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion()));
+
+ appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType()));
+ appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle()));
+ appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake()));
+
+ hair_type = static_cast<u8>(store_data.GetHairType());
+ appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip()));
+
+ appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType()));
+ appearance_bits4.eye_scale.Assign(store_data.GetEyeScale());
+ appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect());
+ appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate());
+ appearance_bits4.eye_x.Assign(store_data.GetEyeX());
+ appearance_bits4.eye_y.Assign(store_data.GetEyeY());
+
+ appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType()));
+ appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale());
+ appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect());
+ appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate());
+ appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX());
+ appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY());
+
+ appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType()));
+ appearance_bits6.nose_scale.Assign(store_data.GetNoseScale());
+ appearance_bits6.nose_y.Assign(store_data.GetNoseY());
+
+ appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType()));
+ appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale());
+ appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect());
+ appearance_bits8.mouth_y.Assign(store_data.GetMouthY());
+
+ appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType()));
+ appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale());
+ appearance_bits9.mustache_y.Assign(store_data.GetMustacheY());
+
+ appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType()));
+
+ appearance_bits10.glass_scale.Assign(store_data.GetGlassScale());
+ appearance_bits10.glass_y.Assign(store_data.GetGlassY());
+
+ appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType()));
+ appearance_bits11.mole_scale.Assign(store_data.GetMoleScale());
+ appearance_bits11.mole_x.Assign(store_data.GetMoleX());
+ appearance_bits11.mole_y.Assign(store_data.GetMoleY());
+
+ // These types are converted to V3 from a table
+ appearance_bits1.faceline_color.Assign(
+ RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor())));
+ appearance_bits3.hair_color.Assign(
+ RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor())));
+ appearance_bits4.eye_color.Assign(
+ RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor())));
+ appearance_bits5.eyebrow_color.Assign(
+ RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor())));
+ appearance_bits7.mouth_color.Assign(
+ RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor())));
+ appearance_bits9.beard_color.Assign(
+ RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor())));
+ appearance_bits10.glass_color.Assign(
+ RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor())));
+ appearance_bits10.glass_type.Assign(
+ RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType())));
+
+ crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16));
+}
+
+u32 Ver3StoreData::IsValid() const {
+ bool is_valid = version == 0 || version == 3;
+
+ is_valid = is_valid && (mii_name.data[0] != '\0');
+
+ is_valid = is_valid && (mii_information.birth_month < 13);
+ is_valid = is_valid && (mii_information.birth_day < 32);
+ is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max));
+ is_valid = is_valid && (height <= MaxHeight);
+ is_valid = is_valid && (build <= MaxBuild);
+
+ is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max));
+ is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2);
+ is_valid =
+ is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max));
+ is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max));
+
+ is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max));
+ is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor);
+
+ is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max));
+ is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2);
+ is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale);
+ is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect);
+ is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate);
+ is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX);
+ is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY);
+
+ is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max));
+ is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor);
+ is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale);
+ is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect);
+ is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate);
+ is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX);
+ is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY);
+
+ is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max));
+ is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale);
+ is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY);
+
+ is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max));
+ is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3);
+ is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale);
+ is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect);
+ is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY);
+
+ is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max));
+ is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale);
+ is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY);
+
+ is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
+ is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
+
+ is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType);
+ is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2);
+ is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale);
+ is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY);
+
+ is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
+ is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);
+ is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX);
+ is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY);
+
+ return is_valid;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii {
+class StoreData;
+
+// This is nn::mii::Ver3StoreData
+// Based on citra HLE::Applets::MiiData and PretendoNetwork.
+// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
+// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
+
+struct NfpStoreDataExtension {
+ void SetFromStoreData(const StoreData& store_data);
+
+ u8 faceline_color;
+ u8 hair_color;
+ u8 eye_color;
+ u8 eyebrow_color;
+ u8 mouth_color;
+ u8 beard_color;
+ u8 glass_color;
+ u8 glass_type;
+};
+static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
+
+#pragma pack(push, 4)
+class Ver3StoreData {
+public:
+ void BuildToStoreData(StoreData& out_store_data) const;
+ void BuildFromStoreData(const StoreData& store_data);
+
+ u32 IsValid() const;
+
+ u8 version;
+ union {
+ u8 raw;
+
+ BitField<0, 1, u8> allow_copying;
+ BitField<1, 1, u8> profanity_flag;
+ BitField<2, 2, u8> region_lock;
+ BitField<4, 2, u8> font_region;
+ } region_information;
+ u16_be mii_id;
+ u64_be system_id;
+ u32_be specialness_and_creation_date;
+ std::array<u8, 6> creator_mac;
+ u16_be padding;
+ union {
+ u16 raw;
+
+ BitField<0, 1, u16> gender;
+ BitField<1, 4, u16> birth_month;
+ BitField<5, 5, u16> birth_day;
+ BitField<10, 4, u16> favorite_color;
+ BitField<14, 1, u16> favorite;
+ } mii_information;
+ Nickname mii_name;
+ u8 height;
+ u8 build;
+ union {
+ u8 raw;
+
+ BitField<0, 1, u8> disable_sharing;
+ BitField<1, 4, u8> faceline_type;
+ BitField<5, 3, u8> faceline_color;
+ } appearance_bits1;
+ union {
+ u8 raw;
+
+ BitField<0, 4, u8> faceline_wrinkle;
+ BitField<4, 4, u8> faceline_make;
+ } appearance_bits2;
+ u8 hair_type;
+ union {
+ u8 raw;
+
+ BitField<0, 3, u8> hair_color;
+ BitField<3, 1, u8> hair_flip;
+ } appearance_bits3;
+ union {
+ u32 raw;
+
+ BitField<0, 6, u32> eye_type;
+ BitField<6, 3, u32> eye_color;
+ BitField<9, 4, u32> eye_scale;
+ BitField<13, 3, u32> eye_aspect;
+ BitField<16, 5, u32> eye_rotate;
+ BitField<21, 4, u32> eye_x;
+ BitField<25, 5, u32> eye_y;
+ } appearance_bits4;
+ union {
+ u32 raw;
+
+ BitField<0, 5, u32> eyebrow_type;
+ BitField<5, 3, u32> eyebrow_color;
+ BitField<8, 4, u32> eyebrow_scale;
+ BitField<12, 3, u32> eyebrow_aspect;
+ BitField<16, 4, u32> eyebrow_rotate;
+ BitField<21, 4, u32> eyebrow_x;
+ BitField<25, 5, u32> eyebrow_y;
+ } appearance_bits5;
+ union {
+ u16 raw;
+
+ BitField<0, 5, u16> nose_type;
+ BitField<5, 4, u16> nose_scale;
+ BitField<9, 5, u16> nose_y;
+ } appearance_bits6;
+ union {
+ u16 raw;
+
+ BitField<0, 6, u16> mouth_type;
+ BitField<6, 3, u16> mouth_color;
+ BitField<9, 4, u16> mouth_scale;
+ BitField<13, 3, u16> mouth_aspect;
+ } appearance_bits7;
+ union {
+ u8 raw;
+
+ BitField<0, 5, u8> mouth_y;
+ BitField<5, 3, u8> mustache_type;
+ } appearance_bits8;
+ u8 allow_copying;
+ union {
+ u16 raw;
+
+ BitField<0, 3, u16> beard_type;
+ BitField<3, 3, u16> beard_color;
+ BitField<6, 4, u16> mustache_scale;
+ BitField<10, 5, u16> mustache_y;
+ } appearance_bits9;
+ union {
+ u16 raw;
+
+ BitField<0, 4, u16> glass_type;
+ BitField<4, 3, u16> glass_color;
+ BitField<7, 4, u16> glass_scale;
+ BitField<11, 5, u16> glass_y;
+ } appearance_bits10;
+ union {
+ u16 raw;
+
+ BitField<0, 1, u16> mole_type;
+ BitField<1, 4, u16> mole_scale;
+ BitField<5, 5, u16> mole_x;
+ BitField<10, 5, u16> mole_y;
+ } appearance_bits11;
+
+ Nickname author_name;
+ INSERT_PADDING_BYTES(0x2);
+ u16_be crc;
+};
+static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
+#pragma pack(pop)
+
+}; // 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..05951d8cb 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -28,7 +28,6 @@
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii_manager.h"
-#include "core/hle/service/mii/types.h"
#include "core/hle/service/nfc/common/amiibo_crypto.h"
#include "core/hle/service/nfc/common/device.h"
#include "core/hle/service/nfc/mifare_result.h"
@@ -440,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
+
return ResultSuccess;
}
@@ -681,12 +681,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const {
return ResultRegistrationIsNotInitialized;
}
- Service::Mii::MiiManager manager;
+ Mii::CharInfo char_info{};
+ Mii::StoreData store_data{};
+ tag_data.owner_mii.BuildToStoreData(store_data);
+ char_info.SetFromStoreData(store_data);
+
const auto& settings = tag_data.settings;
// TODO: Validate this data
register_info = {
- .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
+ .mii_char_info = char_info,
.creation_date = settings.init_date.GetWriteDate(),
.amiibo_name = GetAmiiboName(settings),
.font_region = settings.settings.font_region,
@@ -713,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info
return ResultRegistrationIsNotInitialized;
}
- Service::Mii::MiiManager manager;
+ Mii::StoreData store_data{};
const auto& settings = tag_data.settings;
+ tag_data.owner_mii.BuildToStoreData(store_data);
// TODO: Validate and complete this data
register_info = {
- .mii_store_data = {},
+ .mii_store_data = store_data,
.creation_date = settings.init_date.GetWriteDate(),
.amiibo_name = GetAmiiboName(settings),
.font_region = settings.settings.font_region,
@@ -825,8 +830,11 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
return ResultWrongDeviceState;
}
- Service::Mii::MiiManager manager;
- const auto mii = manager.BuildDefault(0);
+ Service::Mii::StoreData store_data{};
+ Service::Mii::NfpStoreDataExtension extension{};
+ store_data.BuildBase(Mii::Gender::Male);
+ extension.SetFromStoreData(store_data);
+
auto& settings = tag_data.settings;
if (tag_data.settings.settings.amiibo_initialized == 0) {
@@ -835,8 +843,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
}
SetAmiiboName(settings, register_info.amiibo_name);
- tag_data.owner_mii = manager.BuildFromStoreData(mii);
- tag_data.mii_extension = manager.SetFromStoreData(mii);
+ tag_data.owner_mii.BuildFromStoreData(store_data);
+ tag_data.mii_extension = extension;
tag_data.unknown = 0;
tag_data.unknown2 = {};
settings.country_code_id = 0;
@@ -868,17 +876,19 @@ Result NfcDevice::RestoreAmiibo() {
}
Result NfcDevice::Format() {
- auto result1 = DeleteApplicationArea();
- auto result2 = DeleteRegisterInfo();
+ Result result = ResultSuccess;
- if (result1.IsError()) {
- return result1;
+ if (device_state == DeviceState::TagFound) {
+ result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
}
- if (result2.IsError()) {
- return result2;
+ if (result.IsError()) {
+ return result;
}
+ DeleteApplicationArea();
+ DeleteRegisterInfo();
+
return Flush();
}
@@ -1364,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
// Convert from utf16 to utf8
const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
- memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
+ memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1);
return amiibo_name;
}
@@ -1453,7 +1463,7 @@ void NfcDevice::UpdateRegisterInfoCrc() {
void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
const NFP::EncryptedNTAG215File& encrypted_file) const {
- Service::Mii::MiiManager manager;
+ Service::Mii::StoreData store_data{};
auto& settings = stubbed_tag_data.settings;
stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
@@ -1467,7 +1477,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
settings.settings.font_region.Assign(0);
settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
- stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
+ store_data.BuildBase(Mii::Gender::Male);
+ stubbed_tag_data.owner_mii.BuildFromStoreData(store_data);
// Admin info
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 @@
#include <array>
#include "common/swap.h"
-#include "core/hle/service/mii/types.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/store_data.h"
+#include "core/hle/service/mii/types/ver3_store_data.h"
#include "core/hle/service/nfc/nfc_types.h"
namespace Service::NFP {
@@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
// This is nn::nfp::RegisterInfoPrivate
struct RegisterInfoPrivate {
- Service::Mii::MiiStoreData mii_store_data;
+ Service::Mii::StoreData mii_store_data;
WriteDate creation_date;
AmiiboName amiibo_name;
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 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/ngc/ngc.h"
+#include "core/hle/service/server_manager.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NGC {
+
+class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
+public:
+ explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &NgctServiceImpl::Match, "Match"},
+ {1, &NgctServiceImpl::Filter, "Filter"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Match(HLERequestContext& ctx) {
+ const auto buffer = ctx.ReadBuffer();
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(buffer.data()), buffer.size());
+
+ LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ // Return false since we don't censor anything
+ rb.Push(false);
+ }
+
+ void Filter(HLERequestContext& ctx) {
+ const auto buffer = ctx.ReadBuffer();
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(buffer.data()), buffer.size());
+
+ LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
+
+ // Return the same string since we don't censor anything
+ ctx.WriteBuffer(buffer);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+};
+
+class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
+public:
+ explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"},
+ {1, &NgcServiceImpl::Check, "Check"},
+ {2, &NgcServiceImpl::Mask, "Mask"},
+ {3, &NgcServiceImpl::Reload, "Reload"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ static constexpr u32 NgcContentVersion = 1;
+
+ // This is nn::ngc::detail::ProfanityFilterOption
+ struct ProfanityFilterOption {
+ INSERT_PADDING_BYTES_NOINIT(0x20);
+ };
+ static_assert(sizeof(ProfanityFilterOption) == 0x20,
+ "ProfanityFilterOption has incorrect size");
+
+ void GetContentVersion(HLERequestContext& ctx) {
+ LOG_INFO(Service_NGC, "(STUBBED) called");
+
+ // This calls nn::ngc::ProfanityFilter::GetContentVersion
+ const u32 version = NgcContentVersion;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(version);
+ }
+
+ void Check(HLERequestContext& ctx) {
+ LOG_INFO(Service_NGC, "(STUBBED) called");
+
+ struct InputParameters {
+ u32 flags;
+ ProfanityFilterOption option;
+ };
+
+ IPC::RequestParser rp{ctx};
+ [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
+ [[maybe_unused]] const auto input = ctx.ReadBuffer(0);
+
+ // This calls nn::ngc::ProfanityFilter::CheckProfanityWords
+ const u32 out_flags = 0;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(out_flags);
+ }
+
+ void Mask(HLERequestContext& ctx) {
+ LOG_INFO(Service_NGC, "(STUBBED) called");
+
+ struct InputParameters {
+ u32 flags;
+ ProfanityFilterOption option;
+ };
+
+ IPC::RequestParser rp{ctx};
+ [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
+ const auto input = ctx.ReadBuffer(0);
+
+ // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText
+ const u32 out_flags = 0;
+ ctx.WriteBuffer(input);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(out_flags);
+ }
+
+ void Reload(HLERequestContext& ctx) {
+ LOG_INFO(Service_NGC, "(STUBBED) called");
+
+ // This reloads the database.
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+};
+
+void LoopProcess(Core::System& system) {
+ auto server_manager = std::make_unique<ServerManager>(system);
+
+ server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
+ server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
+ ServerManager::RunServer(std::move(server_manager));
+}
+
+} // 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 {
class System;
}
-namespace Service::NGCT {
+namespace Service::NGC {
void LoopProcess(Core::System& system);
-} // namespace Service::NGCT
+} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/string_util.h"
-#include "core/core.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/ngct/ngct.h"
-#include "core/hle/service/server_manager.h"
-#include "core/hle/service/service.h"
-
-namespace Service::NGCT {
-
-class IService final : public ServiceFramework<IService> {
-public:
- explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IService::Match, "Match"},
- {1, &IService::Filter, "Filter"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void Match(HLERequestContext& ctx) {
- const auto buffer = ctx.ReadBuffer();
- const auto text = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(buffer.data()), buffer.size());
-
- LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- // Return false since we don't censor anything
- rb.Push(false);
- }
-
- void Filter(HLERequestContext& ctx) {
- const auto buffer = ctx.ReadBuffer();
- const auto text = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(buffer.data()), buffer.size());
-
- LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
-
- // Return the same string since we don't censor anything
- ctx.WriteBuffer(buffer);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-};
-
-void LoopProcess(Core::System& system) {
- auto server_manager = std::make_unique<ServerManager>(system);
-
- server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system));
- ServerManager::RunServer(std::move(server_manager));
-}
-
-} // namespace Service::NGCT
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 376067a95..6e0baf0be 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -392,24 +392,25 @@ void IApplicationManagerInterface::GetApplicationDesiredLanguage(HLERequestConte
IPC::RequestParser rp{ctx};
const auto supported_languages = rp.Pop<u32>();
- const auto res = GetApplicationDesiredLanguage(supported_languages);
- if (res.Succeeded()) {
+ u8 desired_language{};
+ const auto res = GetApplicationDesiredLanguage(&desired_language, supported_languages);
+ if (res == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(*res);
+ rb.Push<u32>(desired_language);
} else {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
}
}
-ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
- const u32 supported_languages) {
+Result IApplicationManagerInterface::GetApplicationDesiredLanguage(u8* out_desired_language,
+ const u32 supported_languages) {
LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages);
// Get language code from settings
const auto language_code =
- Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue());
+ Set::GetLanguageCodeFromIndex(static_cast<s32>(Settings::values.language_index.GetValue()));
// Convert to application language, get priority list
const auto application_language = ConvertToApplicationLanguage(language_code);
@@ -430,7 +431,8 @@ ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
for (const auto lang : *priority_list) {
const auto supported_flag = GetSupportedLanguageFlag(lang);
if (supported_languages == 0 || (supported_languages & supported_flag) == supported_flag) {
- return static_cast<u8>(lang);
+ *out_desired_language = static_cast<u8>(lang);
+ return ResultSuccess;
}
}
@@ -444,19 +446,20 @@ void IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
IPC::RequestParser rp{ctx};
const auto application_language = rp.Pop<u8>();
- const auto res = ConvertApplicationLanguageToLanguageCode(application_language);
- if (res.Succeeded()) {
+ u64 language_code{};
+ const auto res = ConvertApplicationLanguageToLanguageCode(&language_code, application_language);
+ if (res == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push(*res);
+ rb.Push(language_code);
} else {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
}
}
-ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
- u8 application_language) {
+Result IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
+ u64* out_language_code, u8 application_language) {
const auto language_code =
ConvertToLanguageCode(static_cast<ApplicationLanguage>(application_language));
if (language_code == std::nullopt) {
@@ -464,7 +467,8 @@ ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguag
return Service::NS::ResultApplicationLanguageNotFound;
}
- return static_cast<u64>(*language_code);
+ *out_language_code = static_cast<u64>(*language_code);
+ return ResultSuccess;
}
IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_)
@@ -618,12 +622,13 @@ void IReadOnlyApplicationControlDataInterface::GetApplicationControlData(HLERequ
static_assert(sizeof(RequestParameters) == 0x10, "RequestParameters has incorrect size.");
IPC::RequestParser rp{ctx};
+ std::vector<u8> nacp_data{};
const auto parameters{rp.PopRaw<RequestParameters>()};
- const auto nacp_data{system.GetARPManager().GetControlProperty(parameters.application_id)};
- const auto result = nacp_data ? ResultSuccess : ResultUnknown;
+ const auto result =
+ system.GetARPManager().GetControlProperty(&nacp_data, parameters.application_id);
- if (nacp_data) {
- ctx.WriteBuffer(nacp_data->data(), nacp_data->size());
+ if (result == ResultSuccess) {
+ ctx.WriteBuffer(nacp_data.data(), nacp_data.size());
}
IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 203388e1f..175dad780 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -28,8 +28,9 @@ public:
explicit IApplicationManagerInterface(Core::System& system_);
~IApplicationManagerInterface() override;
- ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages);
- ResultVal<u64> ConvertApplicationLanguageToLanguageCode(u8 application_language);
+ Result GetApplicationDesiredLanguage(u8* out_desired_language, u32 supported_languages);
+ Result ConvertApplicationLanguageToLanguageCode(u64* out_language_code,
+ u8 application_language);
private:
void GetApplicationControlData(HLERequestContext& ctx);
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) {
u32 address{};
auto& smmu_allocator = host1x.Allocator();
auto& smmu_memory_manager = host1x.MemoryManager();
- while (!(address =
- smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) {
+ while ((address = smmu_allocator.Allocate(
+ static_cast<u32>(handle_description->aligned_size))) == 0) {
// Free handles until the allocation succeeds
std::scoped_lock queueLock(unmap_queue_lock);
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) {
if (!mapping->fixed) {
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+ u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
+ u64 aligned_size{Common::AlignUp(mapping->size, page_size)};
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
- static_cast<u32>(mapping->size >> page_size_bits));
+ static_cast<u32>(aligned_size >> page_size_bits));
}
// 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,
case NativeWindowScalingMode::ScaleToWindow:
case NativeWindowScalingMode::ScaleCrop:
case NativeWindowScalingMode::NoScaleCrop:
+ case NativeWindowScalingMode::PreserveAspectRatio:
break;
default:
LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode);
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 5f55cd31e..21f31f7a0 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -183,7 +183,7 @@ std::optional<u32> Nvnflinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
return layer->GetBinderId();
}
-ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) {
+Result Nvnflinger::FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
@@ -191,7 +191,7 @@ ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) {
return VI::ResultNotFound;
}
- return display->GetVSyncEvent();
+ return display->GetVSyncEvent(out_vsync_event);
}
VI::Display* Nvnflinger::FindDisplay(u64 display_id) {
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index ef236303a..f478c2bc6 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -82,7 +82,7 @@ public:
///
/// If an invalid display ID is provided, then VI::ResultNotFound is returned.
/// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned.
- [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id);
+ [[nodiscard]] Result FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id);
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
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 {
ScaleToWindow = 1,
ScaleCrop = 2,
NoScaleCrop = 3,
+ PreserveAspectRatio = 4,
};
/// Transform parameter for QueueBuffer
diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp
index 14ba67b4c..889f27c31 100644
--- a/src/core/hle/service/olsc/olsc.cpp
+++ b/src/core/hle/service/olsc/olsc.cpp
@@ -8,15 +8,16 @@
namespace Service::OLSC {
-class OLSC final : public ServiceFramework<OLSC> {
+class IOlscServiceForApplication final : public ServiceFramework<IOlscServiceForApplication> {
public:
- explicit OLSC(Core::System& system_) : ServiceFramework{system_, "olsc:u"} {
+ explicit IOlscServiceForApplication(Core::System& system_)
+ : ServiceFramework{system_, "olsc:u"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &OLSC::Initialize, "Initialize"},
+ {0, &IOlscServiceForApplication::Initialize, "Initialize"},
{10, nullptr, "VerifySaveDataBackupLicenseAsync"},
- {13, &OLSC::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"},
- {14, &OLSC::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"},
+ {13, &IOlscServiceForApplication::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"},
+ {14, &IOlscServiceForApplication::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"},
{15, nullptr, "SetCustomData"},
{16, nullptr, "DeleteSaveDataBackupSetting"},
{18, nullptr, "GetSaveDataBackupInfoCache"},
@@ -72,10 +73,155 @@ private:
bool initialized{};
};
+class INativeHandleHolder final : public ServiceFramework<INativeHandleHolder> {
+public:
+ explicit INativeHandleHolder(Core::System& system_)
+ : ServiceFramework{system_, "INativeHandleHolder"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetNativeHandle"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class ITransferTaskListController final : public ServiceFramework<ITransferTaskListController> {
+public:
+ explicit ITransferTaskListController(Core::System& system_)
+ : ServiceFramework{system_, "ITransferTaskListController"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "Unknown0"},
+ {1, nullptr, "Unknown1"},
+ {2, nullptr, "Unknown2"},
+ {3, nullptr, "Unknown3"},
+ {4, nullptr, "Unknown4"},
+ {5, &ITransferTaskListController::GetNativeHandleHolder , "GetNativeHandleHolder"},
+ {6, nullptr, "Unknown6"},
+ {7, nullptr, "Unknown7"},
+ {8, nullptr, "GetRemoteStorageController"},
+ {9, &ITransferTaskListController::GetNativeHandleHolder, "GetNativeHandleHolder2"},
+ {10, nullptr, "Unknown10"},
+ {11, nullptr, "Unknown11"},
+ {12, nullptr, "Unknown12"},
+ {13, nullptr, "Unknown13"},
+ {14, nullptr, "Unknown14"},
+ {15, nullptr, "Unknown15"},
+ {16, nullptr, "Unknown16"},
+ {17, nullptr, "Unknown17"},
+ {18, nullptr, "Unknown18"},
+ {19, nullptr, "Unknown19"},
+ {20, nullptr, "Unknown20"},
+ {21, nullptr, "Unknown21"},
+ {22, nullptr, "Unknown22"},
+ {23, nullptr, "Unknown23"},
+ {24, nullptr, "Unknown24"},
+ {25, nullptr, "Unknown25"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetNativeHandleHolder(HLERequestContext& ctx) {
+ LOG_INFO(Service_OLSC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<INativeHandleHolder>(system);
+ }
+};
+
+class IOlscServiceForSystemService final : public ServiceFramework<IOlscServiceForSystemService> {
+public:
+ explicit IOlscServiceForSystemService(Core::System& system_)
+ : ServiceFramework{system_, "olsc:s"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IOlscServiceForSystemService::OpenTransferTaskListController, "OpenTransferTaskListController"},
+ {1, nullptr, "OpenRemoteStorageController"},
+ {2, nullptr, "OpenDaemonController"},
+ {10, nullptr, "Unknown10"},
+ {11, nullptr, "Unknown11"},
+ {12, nullptr, "Unknown12"},
+ {13, nullptr, "Unknown13"},
+ {100, nullptr, "ListLastTransferTaskErrorInfo"},
+ {101, nullptr, "GetLastErrorInfoCount"},
+ {102, nullptr, "RemoveLastErrorInfoOld"},
+ {103, nullptr, "GetLastErrorInfo"},
+ {104, nullptr, "GetLastErrorEventHolder"},
+ {105, nullptr, "GetLastTransferTaskErrorInfo"},
+ {200, nullptr, "GetDataTransferPolicyInfo"},
+ {201, nullptr, "RemoveDataTransferPolicyInfo"},
+ {202, nullptr, "UpdateDataTransferPolicyOld"},
+ {203, nullptr, "UpdateDataTransferPolicy"},
+ {204, nullptr, "CleanupDataTransferPolicyInfo"},
+ {205, nullptr, "RequestDataTransferPolicy"},
+ {300, nullptr, "GetAutoTransferSeriesInfo"},
+ {301, nullptr, "UpdateAutoTransferSeriesInfo"},
+ {400, nullptr, "CleanupSaveDataArchiveInfoType1"},
+ {900, nullptr, "CleanupTransferTask"},
+ {902, nullptr, "CleanupSeriesInfoType0"},
+ {903, nullptr, "CleanupSaveDataArchiveInfoType0"},
+ {904, nullptr, "CleanupApplicationAutoTransferSetting"},
+ {905, nullptr, "CleanupErrorHistory"},
+ {906, nullptr, "SetLastError"},
+ {907, nullptr, "AddSaveDataArchiveInfoType0"},
+ {908, nullptr, "RemoveSeriesInfoType0"},
+ {909, nullptr, "GetSeriesInfoType0"},
+ {910, nullptr, "RemoveLastErrorInfo"},
+ {911, nullptr, "CleanupSeriesInfoType1"},
+ {912, nullptr, "RemoveSeriesInfoType1"},
+ {913, nullptr, "GetSeriesInfoType1"},
+ {1000, nullptr, "UpdateIssueOld"},
+ {1010, nullptr, "Unknown1010"},
+ {1011, nullptr, "ListIssueInfoOld"},
+ {1012, nullptr, "GetIssueOld"},
+ {1013, nullptr, "GetIssue2Old"},
+ {1014, nullptr, "GetIssue3Old"},
+ {1020, nullptr, "RepairIssueOld"},
+ {1021, nullptr, "RepairIssueWithUserIdOld"},
+ {1022, nullptr, "RepairIssue2Old"},
+ {1023, nullptr, "RepairIssue3Old"},
+ {1024, nullptr, "Unknown1024"},
+ {1100, nullptr, "UpdateIssue"},
+ {1110, nullptr, "Unknown1110"},
+ {1111, nullptr, "ListIssueInfo"},
+ {1112, nullptr, "GetIssue"},
+ {1113, nullptr, "GetIssue2"},
+ {1114, nullptr, "GetIssue3"},
+ {1120, nullptr, "RepairIssue"},
+ {1121, nullptr, "RepairIssueWithUserId"},
+ {1122, nullptr, "RepairIssue2"},
+ {1123, nullptr, "RepairIssue3"},
+ {1124, nullptr, "Unknown1124"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void OpenTransferTaskListController(HLERequestContext& ctx) {
+ LOG_INFO(Service_OLSC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<ITransferTaskListController>(system);
+ }
+};
+
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- server_manager->RegisterNamedService("olsc:u", std::make_shared<OLSC>(system));
+ server_manager->RegisterNamedService("olsc:u",
+ std::make_shared<IOlscServiceForApplication>(system));
+ server_manager->RegisterNamedService("olsc:s",
+ std::make_shared<IOlscServiceForSystemService>(system));
+
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index f966c5c8b..5db1703d1 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.cpp
@@ -6,6 +6,7 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/pctl/pctl.h"
#include "core/hle/service/pctl/pctl_module.h"
#include "core/hle/service/server_manager.h"
@@ -24,7 +25,8 @@ constexpr Result ResultNoRestrictionEnabled{ErrorModule::PCTL, 181};
class IParentalControlService final : public ServiceFramework<IParentalControlService> {
public:
explicit IParentalControlService(Core::System& system_, Capability capability_)
- : ServiceFramework{system_, "IParentalControlService"}, capability{capability_} {
+ : ServiceFramework{system_, "IParentalControlService"}, capability{capability_},
+ service_context{system_, "IParentalControlService"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &IParentalControlService::Initialize, "Initialize"},
@@ -33,7 +35,7 @@ public:
{1003, nullptr, "ConfirmResumeApplicationPermission"},
{1004, nullptr, "ConfirmSnsPostPermission"},
{1005, nullptr, "ConfirmSystemSettingsPermission"},
- {1006, nullptr, "IsRestrictionTemporaryUnlocked"},
+ {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"},
{1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
{1008, nullptr, "EnterRestrictedSystemSettings"},
{1009, nullptr, "LeaveRestrictedSystemSettings"},
@@ -47,14 +49,14 @@ public:
{1017, &IParentalControlService::EndFreeCommunication, "EndFreeCommunication"},
{1018, &IParentalControlService::IsFreeCommunicationAvailable, "IsFreeCommunicationAvailable"},
{1031, &IParentalControlService::IsRestrictionEnabled, "IsRestrictionEnabled"},
- {1032, nullptr, "GetSafetyLevel"},
+ {1032, &IParentalControlService::GetSafetyLevel, "GetSafetyLevel"},
{1033, nullptr, "SetSafetyLevel"},
{1034, nullptr, "GetSafetyLevelSettings"},
- {1035, nullptr, "GetCurrentSettings"},
+ {1035, &IParentalControlService::GetCurrentSettings, "GetCurrentSettings"},
{1036, nullptr, "SetCustomSafetyLevelSettings"},
{1037, nullptr, "GetDefaultRatingOrganization"},
{1038, nullptr, "SetDefaultRatingOrganization"},
- {1039, nullptr, "GetFreeCommunicationApplicationListCount"},
+ {1039, &IParentalControlService::GetFreeCommunicationApplicationListCount, "GetFreeCommunicationApplicationListCount"},
{1042, nullptr, "AddToFreeCommunicationApplicationList"},
{1043, nullptr, "DeleteSettings"},
{1044, nullptr, "GetFreeCommunicationApplicationList"},
@@ -76,7 +78,7 @@ public:
{1206, nullptr, "GetPinCodeLength"},
{1207, nullptr, "GetPinCodeChangedEvent"},
{1208, nullptr, "GetPinCode"},
- {1403, nullptr, "IsPairingActive"},
+ {1403, &IParentalControlService::IsPairingActive, "IsPairingActive"},
{1406, nullptr, "GetSettingsLastUpdated"},
{1411, nullptr, "GetPairingAccountInfo"},
{1421, nullptr, "GetAccountNickname"},
@@ -84,18 +86,18 @@ public:
{1425, nullptr, "RequestPostEvents"},
{1426, nullptr, "GetPostEventInterval"},
{1427, nullptr, "SetPostEventInterval"},
- {1432, nullptr, "GetSynchronizationEvent"},
+ {1432, &IParentalControlService::GetSynchronizationEvent, "GetSynchronizationEvent"},
{1451, nullptr, "StartPlayTimer"},
{1452, nullptr, "StopPlayTimer"},
{1453, nullptr, "IsPlayTimerEnabled"},
{1454, nullptr, "GetPlayTimerRemainingTime"},
{1455, nullptr, "IsRestrictedByPlayTimer"},
- {1456, nullptr, "GetPlayTimerSettings"},
- {1457, nullptr, "GetPlayTimerEventToRequestSuspension"},
- {1458, nullptr, "IsPlayTimerAlarmDisabled"},
+ {1456, &IParentalControlService::GetPlayTimerSettings, "GetPlayTimerSettings"},
+ {1457, &IParentalControlService::GetPlayTimerEventToRequestSuspension, "GetPlayTimerEventToRequestSuspension"},
+ {1458, &IParentalControlService::IsPlayTimerAlarmDisabled, "IsPlayTimerAlarmDisabled"},
{1471, nullptr, "NotifyWrongPinCodeInputManyTimes"},
{1472, nullptr, "CancelNetworkRequest"},
- {1473, nullptr, "GetUnlinkedEvent"},
+ {1473, &IParentalControlService::GetUnlinkedEvent, "GetUnlinkedEvent"},
{1474, nullptr, "ClearUnlinkedEvent"},
{1601, nullptr, "DisableAllFeatures"},
{1602, nullptr, "PostEnableAllFeatures"},
@@ -131,6 +133,12 @@ public:
};
// clang-format on
RegisterHandlers(functions);
+
+ synchronization_event =
+ service_context.CreateEvent("IParentalControlService::SynchronizationEvent");
+ unlinked_event = service_context.CreateEvent("IParentalControlService::UnlinkedEvent");
+ request_suspension_event =
+ service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent");
}
private:
@@ -228,6 +236,17 @@ private:
states.free_communication = true;
}
+ void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
+ const bool is_temporary_unlocked = false;
+
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, is_temporary_unlocked={}",
+ is_temporary_unlocked);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_temporary_unlocked);
+ }
+
void ConfirmStereoVisionPermission(HLERequestContext& ctx) {
LOG_DEBUG(Service_PCTL, "called");
states.stereo_vision = true;
@@ -268,6 +287,34 @@ private:
rb.Push(pin_code[0] != '\0');
}
+ void GetSafetyLevel(HLERequestContext& ctx) {
+ const u32 safety_level = 0;
+
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, safety_level={}", safety_level);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(safety_level);
+ }
+
+ void GetCurrentSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_PCTL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(restriction_settings);
+ }
+
+ void GetFreeCommunicationApplicationListCount(HLERequestContext& ctx) {
+ const u32 count = 4;
+
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, count={}", count);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(count);
+ }
+
void ConfirmStereoVisionRestrictionConfigurable(HLERequestContext& ctx) {
LOG_DEBUG(Service_PCTL, "called");
@@ -300,6 +347,61 @@ private:
}
}
+ void IsPairingActive(HLERequestContext& ctx) {
+ const bool is_pairing_active = false;
+
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, is_pairing_active={}", is_pairing_active);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_pairing_active);
+ }
+
+ void GetSynchronizationEvent(HLERequestContext& ctx) {
+ LOG_INFO(Service_PCTL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(synchronization_event->GetReadableEvent());
+ }
+
+ void GetPlayTimerSettings(HLERequestContext& ctx) {
+ LOG_WARNING(Service_PCTL, "(STUBBED) called");
+
+ const PlayTimerSettings timer_settings{};
+
+ IPC::ResponseBuilder rb{ctx, 15};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(timer_settings);
+ }
+
+ void GetPlayTimerEventToRequestSuspension(HLERequestContext& ctx) {
+ LOG_INFO(Service_PCTL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(request_suspension_event->GetReadableEvent());
+ }
+
+ void IsPlayTimerAlarmDisabled(HLERequestContext& ctx) {
+ const bool is_play_timer_alarm_disabled = false;
+
+ LOG_INFO(Service_PCTL, "called, is_play_timer_alarm_disabled={}",
+ is_play_timer_alarm_disabled);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_play_timer_alarm_disabled);
+ }
+
+ void GetUnlinkedEvent(HLERequestContext& ctx) {
+ LOG_INFO(Service_PCTL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(unlinked_event->GetReadableEvent());
+ }
+
void SetStereoVisionRestriction(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto can_use = rp.Pop<bool>();
@@ -364,10 +466,30 @@ private:
bool disabled{};
};
+ // This is nn::pctl::RestrictionSettings
+ struct RestrictionSettings {
+ u8 rating_age;
+ bool sns_post_restriction;
+ bool free_communication_restriction;
+ };
+ static_assert(sizeof(RestrictionSettings) == 0x3, "RestrictionSettings has incorrect size.");
+
+ // This is nn::pctl::PlayTimerSettings
+ struct PlayTimerSettings {
+ std::array<u32, 13> settings;
+ };
+ static_assert(sizeof(PlayTimerSettings) == 0x34, "PlayTimerSettings has incorrect size.");
+
States states{};
ParentalControlSettings settings{};
+ RestrictionSettings restriction_settings{};
std::array<char, 8> pin_code{};
Capability capability{};
+
+ Kernel::KEvent* synchronization_event;
+ Kernel::KEvent* unlinked_event;
+ Kernel::KEvent* request_suspension_event;
+ KernelHelpers::ServiceContext service_context;
};
void Module::Interface::CreateService(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp
index d1e99b184..e2e399534 100644
--- a/src/core/hle/service/server_manager.cpp
+++ b/src/core/hle/service/server_manager.cpp
@@ -102,16 +102,17 @@ Result ServerManager::RegisterNamedService(const std::string& service_name,
m_system.ServiceManager().RegisterService(service_name, max_sessions, handler)));
// Get the registered port.
- auto port = m_system.ServiceManager().GetServicePort(service_name);
- ASSERT(port.Succeeded());
+ Kernel::KPort* port{};
+ ASSERT(
+ R_SUCCEEDED(m_system.ServiceManager().GetServicePort(std::addressof(port), service_name)));
// Open a new reference to the server port.
- (*port)->GetServerPort().Open();
+ port->GetServerPort().Open();
// Begin tracking the server port.
{
std::scoped_lock ll{m_list_mutex};
- m_ports.emplace(std::addressof((*port)->GetServerPort()), std::move(handler));
+ m_ports.emplace(std::addressof(port->GetServerPort()), std::move(handler));
}
// Signal the wakeup event.
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 @@
#include "core/hle/service/ncm/ncm.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfp/nfp.h"
-#include "core/hle/service/ngct/ngct.h"
+#include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nim/nim.h"
#include "core/hle/service/npns/npns.h"
@@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
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:
using HandlerFnP = void (Self::*)(HLERequestContext&);
/// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
- [[nodiscard]] std::scoped_lock<std::mutex> LockService() {
- return std::scoped_lock{lock_service};
+ [[nodiscard]] virtual std::unique_lock<std::mutex> LockService() {
+ return std::unique_lock{lock_service};
}
/// System context that the service operates under.
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index f5788b481..2082b8ef7 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -11,66 +11,6 @@
namespace Service::Set {
namespace {
-constexpr std::array<LanguageCode, 18> available_language_codes = {{
- LanguageCode::JA,
- LanguageCode::EN_US,
- LanguageCode::FR,
- LanguageCode::DE,
- LanguageCode::IT,
- LanguageCode::ES,
- LanguageCode::ZH_CN,
- LanguageCode::KO,
- LanguageCode::NL,
- LanguageCode::PT,
- LanguageCode::RU,
- LanguageCode::ZH_TW,
- LanguageCode::EN_GB,
- LanguageCode::FR_CA,
- LanguageCode::ES_419,
- LanguageCode::ZH_HANS,
- LanguageCode::ZH_HANT,
- LanguageCode::PT_BR,
-}};
-
-enum class KeyboardLayout : u64 {
- Japanese = 0,
- EnglishUs = 1,
- EnglishUsInternational = 2,
- EnglishUk = 3,
- French = 4,
- FrenchCa = 5,
- Spanish = 6,
- SpanishLatin = 7,
- German = 8,
- Italian = 9,
- Portuguese = 10,
- Russian = 11,
- Korean = 12,
- ChineseSimplified = 13,
- ChineseTraditional = 14,
-};
-
-constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 18> language_to_layout{{
- {LanguageCode::JA, KeyboardLayout::Japanese},
- {LanguageCode::EN_US, KeyboardLayout::EnglishUs},
- {LanguageCode::FR, KeyboardLayout::French},
- {LanguageCode::DE, KeyboardLayout::German},
- {LanguageCode::IT, KeyboardLayout::Italian},
- {LanguageCode::ES, KeyboardLayout::Spanish},
- {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified},
- {LanguageCode::KO, KeyboardLayout::Korean},
- {LanguageCode::NL, KeyboardLayout::EnglishUsInternational},
- {LanguageCode::PT, KeyboardLayout::Portuguese},
- {LanguageCode::RU, KeyboardLayout::Russian},
- {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional},
- {LanguageCode::EN_GB, KeyboardLayout::EnglishUk},
- {LanguageCode::FR_CA, KeyboardLayout::FrenchCa},
- {LanguageCode::ES_419, KeyboardLayout::SpanishLatin},
- {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified},
- {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional},
- {LanguageCode::PT_BR, KeyboardLayout::Portuguese},
-}};
-
constexpr std::size_t PRE_4_0_0_MAX_ENTRIES = 0xF;
constexpr std::size_t POST_4_0_0_MAX_ENTRIES = 0x40;
@@ -93,7 +33,8 @@ void GetAvailableLanguageCodesImpl(HLERequestContext& ctx, std::size_t max_entri
}
void GetKeyCodeMapImpl(HLERequestContext& ctx) {
- const auto language_code = available_language_codes[Settings::values.language_index.GetValue()];
+ const auto language_code =
+ available_language_codes[static_cast<s32>(Settings::values.language_index.GetValue())];
const auto key_code =
std::find_if(language_to_layout.cbegin(), language_to_layout.cend(),
[=](const auto& element) { return element.first == language_code; });
@@ -162,7 +103,7 @@ void SET::GetQuestFlag(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(Settings::values.quest_flag.GetValue()));
+ rb.Push(static_cast<s32>(Settings::values.quest_flag.GetValue()));
}
void SET::GetLanguageCode(HLERequestContext& ctx) {
@@ -170,7 +111,8 @@ void SET::GetLanguageCode(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.PushEnum(available_language_codes[Settings::values.language_index.GetValue()]);
+ rb.PushEnum(
+ available_language_codes[static_cast<s32>(Settings::values.language_index.GetValue())]);
}
void SET::GetRegionCode(HLERequestContext& ctx) {
@@ -178,7 +120,7 @@ void SET::GetRegionCode(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(Settings::values.region_index.GetValue());
+ rb.Push(static_cast<u32>(Settings::values.region_index.GetValue()));
}
void SET::GetKeyCodeMap(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 7fd3a7654..b61a3560d 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -32,6 +32,67 @@ enum class LanguageCode : u64 {
ZH_HANT = 0x00746E61482D687A,
PT_BR = 0x00000052422D7470,
};
+
+enum class KeyboardLayout : u64 {
+ Japanese = 0,
+ EnglishUs = 1,
+ EnglishUsInternational = 2,
+ EnglishUk = 3,
+ French = 4,
+ FrenchCa = 5,
+ Spanish = 6,
+ SpanishLatin = 7,
+ German = 8,
+ Italian = 9,
+ Portuguese = 10,
+ Russian = 11,
+ Korean = 12,
+ ChineseSimplified = 13,
+ ChineseTraditional = 14,
+};
+
+constexpr std::array<LanguageCode, 18> available_language_codes = {{
+ LanguageCode::JA,
+ LanguageCode::EN_US,
+ LanguageCode::FR,
+ LanguageCode::DE,
+ LanguageCode::IT,
+ LanguageCode::ES,
+ LanguageCode::ZH_CN,
+ LanguageCode::KO,
+ LanguageCode::NL,
+ LanguageCode::PT,
+ LanguageCode::RU,
+ LanguageCode::ZH_TW,
+ LanguageCode::EN_GB,
+ LanguageCode::FR_CA,
+ LanguageCode::ES_419,
+ LanguageCode::ZH_HANS,
+ LanguageCode::ZH_HANT,
+ LanguageCode::PT_BR,
+}};
+
+static constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 18> language_to_layout{{
+ {LanguageCode::JA, KeyboardLayout::Japanese},
+ {LanguageCode::EN_US, KeyboardLayout::EnglishUs},
+ {LanguageCode::FR, KeyboardLayout::French},
+ {LanguageCode::DE, KeyboardLayout::German},
+ {LanguageCode::IT, KeyboardLayout::Italian},
+ {LanguageCode::ES, KeyboardLayout::Spanish},
+ {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified},
+ {LanguageCode::KO, KeyboardLayout::Korean},
+ {LanguageCode::NL, KeyboardLayout::EnglishUsInternational},
+ {LanguageCode::PT, KeyboardLayout::Portuguese},
+ {LanguageCode::RU, KeyboardLayout::Russian},
+ {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional},
+ {LanguageCode::EN_GB, KeyboardLayout::EnglishUk},
+ {LanguageCode::FR_CA, KeyboardLayout::FrenchCa},
+ {LanguageCode::ES_419, KeyboardLayout::SpanishLatin},
+ {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified},
+ {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional},
+ {LanguageCode::PT_BR, KeyboardLayout::Portuguese},
+}};
+
LanguageCode GetLanguageCodeFromIndex(std::size_t idx);
class SET final : public ServiceFramework<SET> {
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 2e38d1cfc..165b97dad 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -4,10 +4,12 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/string_util.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/system_archive/system_version.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/set/set.h"
#include "core/hle/service/set/set_sys.h"
namespace Service::Set {
@@ -73,6 +75,16 @@ void GetFirmwareVersionImpl(HLERequestContext& ctx, GetFirmwareVersionType type)
}
} // Anonymous namespace
+void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ language_code_setting = rp.PopEnum<LanguageCode>();
+
+ LOG_INFO(Service_SET, "called, language_code={}", language_code_setting);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version1);
@@ -83,21 +95,113 @@ void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version2);
}
+void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(account_settings);
+}
+
+void SET_SYS::SetAccountSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ account_settings = rp.PopRaw<AccountSettings>();
+
+ LOG_INFO(Service_SET, "called, account_settings_flags={}", account_settings.flags);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetEulaVersions(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ ctx.WriteBuffer(eula_versions);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(eula_versions.size()));
+}
+
+void SET_SYS::SetEulaVersions(HLERequestContext& ctx) {
+ const auto elements = ctx.GetReadBufferNumElements<EulaVersion>();
+ const auto buffer_data = ctx.ReadBuffer();
+
+ LOG_INFO(Service_SET, "called, elements={}", elements);
+
+ eula_versions.resize(elements);
+ for (std::size_t index = 0; index < elements; index++) {
+ const std::size_t start_index = index * sizeof(EulaVersion);
+ memcpy(eula_versions.data() + start_index, buffer_data.data() + start_index,
+ sizeof(EulaVersion));
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void SET_SYS::GetColorSetId(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
IPC::ResponseBuilder rb{ctx, 3};
-
rb.Push(ResultSuccess);
rb.PushEnum(color_set);
}
void SET_SYS::SetColorSetId(HLERequestContext& ctx) {
- LOG_DEBUG(Service_SET, "called");
-
IPC::RequestParser rp{ctx};
color_set = rp.PopEnum<ColorSet>();
+ LOG_DEBUG(Service_SET, "called, color_set={}", color_set);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetNotificationSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 8};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(notification_settings);
+}
+
+void SET_SYS::SetNotificationSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ notification_settings = rp.PopRaw<NotificationSettings>();
+
+ LOG_INFO(Service_SET, "called, flags={}, volume={}, head_time={}:{}, tailt_time={}:{}",
+ notification_settings.flags.raw, notification_settings.volume,
+ notification_settings.start_time.hour, notification_settings.start_time.minute,
+ notification_settings.stop_time.hour, notification_settings.stop_time.minute);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetAccountNotificationSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ ctx.WriteBuffer(account_notifications);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(account_notifications.size()));
+}
+
+void SET_SYS::SetAccountNotificationSettings(HLERequestContext& ctx) {
+ const auto elements = ctx.GetReadBufferNumElements<AccountNotificationSettings>();
+ const auto buffer_data = ctx.ReadBuffer();
+
+ LOG_INFO(Service_SET, "called, elements={}", elements);
+
+ account_notifications.resize(elements);
+ for (std::size_t index = 0; index < elements; index++) {
+ const std::size_t start_index = index * sizeof(AccountNotificationSettings);
+ memcpy(account_notifications.data() + start_index, buffer_data.data() + start_index,
+ sizeof(AccountNotificationSettings));
+ }
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
@@ -177,17 +281,218 @@ void SET_SYS::GetSettingsItemValue(HLERequestContext& ctx) {
rb.Push(response);
}
+void SET_SYS::GetTvSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 10};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(tv_settings);
+}
+
+void SET_SYS::SetTvSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ tv_settings = rp.PopRaw<TvSettings>();
+
+ LOG_INFO(Service_SET,
+ "called, flags={}, cmu_mode={}, constrast_ratio={}, hdmi_content_type={}, "
+ "rgb_range={}, tv_gama={}, tv_resolution={}, tv_underscan={}",
+ tv_settings.flags.raw, tv_settings.cmu_mode, tv_settings.constrast_ratio,
+ tv_settings.hdmi_content_type, tv_settings.rgb_range, tv_settings.tv_gama,
+ tv_settings.tv_resolution, tv_settings.tv_underscan);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetQuestFlag(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(QuestFlag::Retail);
+}
+
+void SET_SYS::SetRegionCode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ region_code = rp.PopEnum<RegionCode>();
+
+ LOG_INFO(Service_SET, "called, region_code={}", region_code);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetPrimaryAlbumStorage(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(PrimaryAlbumStorage::SdCard);
+}
+
+void SET_SYS::GetSleepSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(sleep_settings);
+}
+
+void SET_SYS::SetSleepSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ sleep_settings = rp.PopRaw<SleepSettings>();
+
+ LOG_INFO(Service_SET, "called, flags={}, handheld_sleep_plan={}, console_sleep_plan={}",
+ sleep_settings.flags.raw, sleep_settings.handheld_sleep_plan,
+ sleep_settings.console_sleep_plan);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetInitialLaunchSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+ IPC::ResponseBuilder rb{ctx, 10};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(launch_settings);
+}
+
+void SET_SYS::SetInitialLaunchSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ launch_settings = rp.PopRaw<InitialLaunchSettings>();
+
+ LOG_INFO(Service_SET, "called, flags={}, timestamp={}", launch_settings.flags.raw,
+ launch_settings.timestamp.time_point);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void SET_SYS::GetDeviceNickName(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
+
+ ctx.WriteBuffer(::Settings::values.device_name.GetValue());
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
- ctx.WriteBuffer(::Settings::values.device_name.GetValue());
+}
+
+void SET_SYS::SetDeviceNickName(HLERequestContext& ctx) {
+ const std::string device_name = Common::StringFromBuffer(ctx.ReadBuffer());
+
+ LOG_INFO(Service_SET, "called, device_name={}", device_name);
+
+ ::Settings::values.device_name = device_name;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetProductModel(HLERequestContext& ctx) {
+ const u32 product_model = 1;
+
+ LOG_WARNING(Service_SET, "(STUBBED) called, product_model={}", product_model);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(product_model);
+}
+
+void SET_SYS::GetMiiAuthorId(HLERequestContext& ctx) {
+ const auto author_id = Common::UUID::MakeDefault();
+
+ LOG_WARNING(Service_SET, "(STUBBED) called, author_id={}", author_id.FormattedString());
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(author_id);
+}
+
+void SET_SYS::GetAutoUpdateEnableFlag(HLERequestContext& ctx) {
+ u8 auto_update_flag{};
+
+ LOG_WARNING(Service_SET, "(STUBBED) called, auto_update_flag={}", auto_update_flag);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(auto_update_flag);
+}
+
+void SET_SYS::GetBatteryPercentageFlag(HLERequestContext& ctx) {
+ u8 battery_percentage_flag{1};
+
+ LOG_WARNING(Service_SET, "(STUBBED) called, battery_percentage_flag={}",
+ battery_percentage_flag);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(battery_percentage_flag);
+}
+
+void SET_SYS::GetErrorReportSharePermission(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(ErrorReportSharePermission::Denied);
+}
+
+void SET_SYS::GetAppletLaunchFlags(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called, applet_launch_flag={}", applet_launch_flag);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(applet_launch_flag);
+}
+
+void SET_SYS::SetAppletLaunchFlags(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ applet_launch_flag = rp.Pop<u32>();
+
+ LOG_INFO(Service_SET, "called, applet_launch_flag={}", applet_launch_flag);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetKeyboardLayout(HLERequestContext& ctx) {
+ const auto language_code =
+ available_language_codes[static_cast<s32>(::Settings::values.language_index.GetValue())];
+ const auto key_code =
+ std::find_if(language_to_layout.cbegin(), language_to_layout.cend(),
+ [=](const auto& element) { return element.first == language_code; });
+
+ KeyboardLayout selected_keyboard_layout = KeyboardLayout::EnglishUs;
+ if (key_code != language_to_layout.end()) {
+ selected_keyboard_layout = key_code->second;
+ }
+
+ LOG_INFO(Service_SET, "called, selected_keyboard_layout={}", selected_keyboard_layout);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(selected_keyboard_layout));
+}
+
+void SET_SYS::GetChineseTraditionalInputMethod(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(ChineseTraditionalInputMethod::Unknown0);
+}
+
+void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(false);
}
SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "SetLanguageCode"},
+ {0, &SET_SYS::SetLanguageCode, "SetLanguageCode"},
{1, nullptr, "SetNetworkSettings"},
{2, nullptr, "GetNetworkSettings"},
{3, &SET_SYS::GetFirmwareVersion, "GetFirmwareVersion"},
@@ -203,35 +508,35 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{14, nullptr, "SetExternalSteadyClockSourceId"},
{15, nullptr, "GetUserSystemClockContext"},
{16, nullptr, "SetUserSystemClockContext"},
- {17, nullptr, "GetAccountSettings"},
- {18, nullptr, "SetAccountSettings"},
+ {17, &SET_SYS::GetAccountSettings, "GetAccountSettings"},
+ {18, &SET_SYS::SetAccountSettings, "SetAccountSettings"},
{19, nullptr, "GetAudioVolume"},
{20, nullptr, "SetAudioVolume"},
- {21, nullptr, "GetEulaVersions"},
- {22, nullptr, "SetEulaVersions"},
+ {21, &SET_SYS::GetEulaVersions, "GetEulaVersions"},
+ {22, &SET_SYS::SetEulaVersions, "SetEulaVersions"},
{23, &SET_SYS::GetColorSetId, "GetColorSetId"},
{24, &SET_SYS::SetColorSetId, "SetColorSetId"},
{25, nullptr, "GetConsoleInformationUploadFlag"},
{26, nullptr, "SetConsoleInformationUploadFlag"},
{27, nullptr, "GetAutomaticApplicationDownloadFlag"},
{28, nullptr, "SetAutomaticApplicationDownloadFlag"},
- {29, nullptr, "GetNotificationSettings"},
- {30, nullptr, "SetNotificationSettings"},
- {31, nullptr, "GetAccountNotificationSettings"},
- {32, nullptr, "SetAccountNotificationSettings"},
+ {29, &SET_SYS::GetNotificationSettings, "GetNotificationSettings"},
+ {30, &SET_SYS::SetNotificationSettings, "SetNotificationSettings"},
+ {31, &SET_SYS::GetAccountNotificationSettings, "GetAccountNotificationSettings"},
+ {32, &SET_SYS::SetAccountNotificationSettings, "SetAccountNotificationSettings"},
{35, nullptr, "GetVibrationMasterVolume"},
{36, nullptr, "SetVibrationMasterVolume"},
{37, &SET_SYS::GetSettingsItemValueSize, "GetSettingsItemValueSize"},
{38, &SET_SYS::GetSettingsItemValue, "GetSettingsItemValue"},
- {39, nullptr, "GetTvSettings"},
- {40, nullptr, "SetTvSettings"},
+ {39, &SET_SYS::GetTvSettings, "GetTvSettings"},
+ {40, &SET_SYS::SetTvSettings, "SetTvSettings"},
{41, nullptr, "GetEdid"},
{42, nullptr, "SetEdid"},
{43, nullptr, "GetAudioOutputMode"},
{44, nullptr, "SetAudioOutputMode"},
{45, nullptr, "IsForceMuteOnHeadphoneRemoved"},
{46, nullptr, "SetForceMuteOnHeadphoneRemoved"},
- {47, nullptr, "GetQuestFlag"},
+ {47, &SET_SYS::GetQuestFlag, "GetQuestFlag"},
{48, nullptr, "SetQuestFlag"},
{49, nullptr, "GetDataDeletionSettings"},
{50, nullptr, "SetDataDeletionSettings"},
@@ -241,13 +546,13 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{54, nullptr, "SetDeviceTimeZoneLocationName"},
{55, nullptr, "GetWirelessCertificationFileSize"},
{56, nullptr, "GetWirelessCertificationFile"},
- {57, nullptr, "SetRegionCode"},
+ {57, &SET_SYS::SetRegionCode, "SetRegionCode"},
{58, nullptr, "GetNetworkSystemClockContext"},
{59, nullptr, "SetNetworkSystemClockContext"},
{60, nullptr, "IsUserSystemClockAutomaticCorrectionEnabled"},
{61, nullptr, "SetUserSystemClockAutomaticCorrectionEnabled"},
{62, nullptr, "GetDebugModeFlag"},
- {63, nullptr, "GetPrimaryAlbumStorage"},
+ {63, &SET_SYS::GetPrimaryAlbumStorage, "GetPrimaryAlbumStorage"},
{64, nullptr, "SetPrimaryAlbumStorage"},
{65, nullptr, "GetUsb30EnableFlag"},
{66, nullptr, "SetUsb30EnableFlag"},
@@ -255,15 +560,15 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{68, nullptr, "GetSerialNumber"},
{69, nullptr, "GetNfcEnableFlag"},
{70, nullptr, "SetNfcEnableFlag"},
- {71, nullptr, "GetSleepSettings"},
- {72, nullptr, "SetSleepSettings"},
+ {71, &SET_SYS::GetSleepSettings, "GetSleepSettings"},
+ {72, &SET_SYS::SetSleepSettings, "SetSleepSettings"},
{73, nullptr, "GetWirelessLanEnableFlag"},
{74, nullptr, "SetWirelessLanEnableFlag"},
- {75, nullptr, "GetInitialLaunchSettings"},
- {76, nullptr, "SetInitialLaunchSettings"},
+ {75, &SET_SYS::GetInitialLaunchSettings, "GetInitialLaunchSettings"},
+ {76, &SET_SYS::SetInitialLaunchSettings, "SetInitialLaunchSettings"},
{77, &SET_SYS::GetDeviceNickName, "GetDeviceNickName"},
- {78, nullptr, "SetDeviceNickName"},
- {79, nullptr, "GetProductModel"},
+ {78, &SET_SYS::SetDeviceNickName, "SetDeviceNickName"},
+ {79, &SET_SYS::GetProductModel, "GetProductModel"},
{80, nullptr, "GetLdnChannel"},
{81, nullptr, "SetLdnChannel"},
{82, nullptr, "AcquireTelemetryDirtyFlagEventHandle"},
@@ -274,16 +579,16 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{87, nullptr, "SetPtmFuelGaugeParameter"},
{88, nullptr, "GetBluetoothEnableFlag"},
{89, nullptr, "SetBluetoothEnableFlag"},
- {90, nullptr, "GetMiiAuthorId"},
+ {90, &SET_SYS::GetMiiAuthorId, "GetMiiAuthorId"},
{91, nullptr, "SetShutdownRtcValue"},
{92, nullptr, "GetShutdownRtcValue"},
{93, nullptr, "AcquireFatalDirtyFlagEventHandle"},
{94, nullptr, "GetFatalDirtyFlags"},
- {95, nullptr, "GetAutoUpdateEnableFlag"},
+ {95, &SET_SYS::GetAutoUpdateEnableFlag, "GetAutoUpdateEnableFlag"},
{96, nullptr, "SetAutoUpdateEnableFlag"},
{97, nullptr, "GetNxControllerSettings"},
{98, nullptr, "SetNxControllerSettings"},
- {99, nullptr, "GetBatteryPercentageFlag"},
+ {99, &SET_SYS::GetBatteryPercentageFlag, "GetBatteryPercentageFlag"},
{100, nullptr, "SetBatteryPercentageFlag"},
{101, nullptr, "GetExternalRtcResetFlag"},
{102, nullptr, "SetExternalRtcResetFlag"},
@@ -308,10 +613,10 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{121, nullptr, "SetPushNotificationActivityModeOnSleep"},
{122, nullptr, "GetServiceDiscoveryControlSettings"},
{123, nullptr, "SetServiceDiscoveryControlSettings"},
- {124, nullptr, "GetErrorReportSharePermission"},
+ {124, &SET_SYS::GetErrorReportSharePermission, "GetErrorReportSharePermission"},
{125, nullptr, "SetErrorReportSharePermission"},
- {126, nullptr, "GetAppletLaunchFlags"},
- {127, nullptr, "SetAppletLaunchFlags"},
+ {126, &SET_SYS::GetAppletLaunchFlags, "GetAppletLaunchFlags"},
+ {127, &SET_SYS::SetAppletLaunchFlags, "SetAppletLaunchFlags"},
{128, nullptr, "GetConsoleSixAxisSensorAccelerationBias"},
{129, nullptr, "SetConsoleSixAxisSensorAccelerationBias"},
{130, nullptr, "GetConsoleSixAxisSensorAngularVelocityBias"},
@@ -320,7 +625,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{133, nullptr, "SetConsoleSixAxisSensorAccelerationGain"},
{134, nullptr, "GetConsoleSixAxisSensorAngularVelocityGain"},
{135, nullptr, "SetConsoleSixAxisSensorAngularVelocityGain"},
- {136, nullptr, "GetKeyboardLayout"},
+ {136, &SET_SYS::GetKeyboardLayout, "GetKeyboardLayout"},
{137, nullptr, "SetKeyboardLayout"},
{138, nullptr, "GetWebInspectorFlag"},
{139, nullptr, "GetAllowedSslHosts"},
@@ -354,7 +659,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{167, nullptr, "SetUsb30DeviceEnableFlag"},
{168, nullptr, "GetThemeId"},
{169, nullptr, "SetThemeId"},
- {170, nullptr, "GetChineseTraditionalInputMethod"},
+ {170, &SET_SYS::GetChineseTraditionalInputMethod, "GetChineseTraditionalInputMethod"},
{171, nullptr, "SetChineseTraditionalInputMethod"},
{172, nullptr, "GetPtmCycleCountReliability"},
{173, nullptr, "SetPtmCycleCountReliability"},
@@ -385,12 +690,16 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{198, nullptr, "SetButtonConfigRegisteredSettingsEmbedded"},
{199, nullptr, "GetButtonConfigRegisteredSettings"},
{200, nullptr, "SetButtonConfigRegisteredSettings"},
- {201, nullptr, "GetFieldTestingFlag"},
+ {201, &SET_SYS::GetFieldTestingFlag, "GetFieldTestingFlag"},
{202, nullptr, "SetFieldTestingFlag"},
{203, nullptr, "GetPanelCrcMode"},
{204, nullptr, "SetPanelCrcMode"},
{205, nullptr, "GetNxControllerSettingsEx"},
{206, nullptr, "SetNxControllerSettingsEx"},
+ {207, nullptr, "GetHearingProtectionSafeguardFlag"},
+ {208, nullptr, "SetHearingProtectionSafeguardFlag"},
+ {209, nullptr, "GetHearingProtectionSafeguardRemainingTime"},
+ {210, nullptr, "SetHearingProtectionSafeguardRemainingTime"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 1efbcc97a..c7dba2a9e 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -3,7 +3,9 @@
#pragma once
+#include "common/uuid.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/time/clock_types.h"
namespace Core {
class System;
@@ -23,15 +25,331 @@ private:
BasicBlack = 1,
};
- void GetSettingsItemValueSize(HLERequestContext& ctx);
- void GetSettingsItemValue(HLERequestContext& ctx);
+ /// Indicates the current console is a retail or kiosk unit
+ enum class QuestFlag : u8 {
+ Retail = 0,
+ Kiosk = 1,
+ };
+
+ /// This is nn::settings::system::TvResolution
+ enum class TvResolution : u32 {
+ Auto,
+ Resolution1080p,
+ Resolution720p,
+ Resolution480p,
+ };
+
+ /// This is nn::settings::system::HdmiContentType
+ enum class HdmiContentType : u32 {
+ None,
+ Graphics,
+ Cinema,
+ Photo,
+ Game,
+ };
+
+ /// This is nn::settings::system::RgbRange
+ enum class RgbRange : u32 {
+ Auto,
+ Full,
+ Limited,
+ };
+
+ /// This is nn::settings::system::CmuMode
+ enum class CmuMode : u32 {
+ None,
+ ColorInvert,
+ HighContrast,
+ GrayScale,
+ };
+
+ /// This is nn::settings::system::PrimaryAlbumStorage
+ enum class PrimaryAlbumStorage : u32 {
+ Nand,
+ SdCard,
+ };
+
+ /// This is nn::settings::system::NotificationVolume
+ enum class NotificationVolume : u32 {
+ Mute,
+ Low,
+ High,
+ };
+
+ /// This is nn::settings::system::ChineseTraditionalInputMethod
+ enum class ChineseTraditionalInputMethod : u32 {
+ Unknown0 = 0,
+ Unknown1 = 1,
+ Unknown2 = 2,
+ };
+
+ /// This is nn::settings::system::ErrorReportSharePermission
+ enum class ErrorReportSharePermission : u32 {
+ NotConfirmed,
+ Granted,
+ Denied,
+ };
+
+ /// This is nn::settings::system::FriendPresenceOverlayPermission
+ enum class FriendPresenceOverlayPermission : u8 {
+ NotConfirmed,
+ NoDisplay,
+ FavoriteFriends,
+ Friends,
+ };
+
+ /// This is nn::settings::system::HandheldSleepPlan
+ enum class HandheldSleepPlan : u32 {
+ Sleep1Min,
+ Sleep3Min,
+ Sleep5Min,
+ Sleep10Min,
+ Sleep30Min,
+ Never,
+ };
+
+ /// This is nn::settings::system::ConsoleSleepPlan
+ enum class ConsoleSleepPlan : u32 {
+ Sleep1Hour,
+ Sleep2Hour,
+ Sleep3Hour,
+ Sleep6Hour,
+ Sleep12Hour,
+ Never,
+ };
+
+ /// This is nn::settings::system::RegionCode
+ enum class RegionCode : u32 {
+ Japan,
+ Usa,
+ Europe,
+ Australia,
+ HongKongTaiwanKorea,
+ China,
+ };
+
+ /// This is nn::settings::system::EulaVersionClockType
+ enum class EulaVersionClockType : u32 {
+ NetworkSystemClock,
+ SteadyClock,
+ };
+
+ /// This is nn::settings::system::SleepFlag
+ struct SleepFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> SleepsWhilePlayingMedia;
+ BitField<1, 1, u32> WakesAtPowerStateChange;
+ };
+ };
+ static_assert(sizeof(SleepFlag) == 4, "TvFlag is an invalid size");
+
+ /// This is nn::settings::system::TvFlag
+ struct TvFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> Allows4k;
+ BitField<1, 1, u32> Allows3d;
+ BitField<2, 1, u32> AllowsCec;
+ BitField<3, 1, u32> PreventsScreenBurnIn;
+ };
+ };
+ static_assert(sizeof(TvFlag) == 4, "TvFlag is an invalid size");
+
+ /// This is nn::settings::system::InitialLaunchFlag
+ struct InitialLaunchFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> InitialLaunchCompletionFlag;
+ BitField<8, 1, u32> InitialLaunchUserAdditionFlag;
+ BitField<16, 1, u32> InitialLaunchTimestampFlag;
+ };
+ };
+ static_assert(sizeof(InitialLaunchFlag) == 4, "InitialLaunchFlag is an invalid size");
+
+ /// This is nn::settings::system::NotificationFlag
+ struct NotificationFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> RingtoneFlag;
+ BitField<1, 1, u32> DownloadCompletionFlag;
+ BitField<8, 1, u32> EnablesNews;
+ BitField<9, 1, u32> IncomingLampFlag;
+ };
+ };
+ static_assert(sizeof(NotificationFlag) == 4, "NotificationFlag is an invalid size");
+
+ /// This is nn::settings::system::AccountNotificationFlag
+ struct AccountNotificationFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> FriendOnlineFlag;
+ BitField<1, 1, u32> FriendRequestFlag;
+ BitField<8, 1, u32> CoralInvitationFlag;
+ };
+ };
+ static_assert(sizeof(AccountNotificationFlag) == 4,
+ "AccountNotificationFlag is an invalid size");
+
+ /// This is nn::settings::system::TvSettings
+ struct TvSettings {
+ TvFlag flags;
+ TvResolution tv_resolution;
+ HdmiContentType hdmi_content_type;
+ RgbRange rgb_range;
+ CmuMode cmu_mode;
+ u32 tv_underscan;
+ f32 tv_gama;
+ f32 constrast_ratio;
+ };
+ static_assert(sizeof(TvSettings) == 0x20, "TvSettings is an invalid size");
+
+ /// This is nn::settings::system::NotificationTime
+ struct NotificationTime {
+ u32 hour;
+ u32 minute;
+ };
+ static_assert(sizeof(NotificationTime) == 0x8, "NotificationTime is an invalid size");
+
+ /// This is nn::settings::system::NotificationSettings
+ struct NotificationSettings {
+ NotificationFlag flags;
+ NotificationVolume volume;
+ NotificationTime start_time;
+ NotificationTime stop_time;
+ };
+ static_assert(sizeof(NotificationSettings) == 0x18, "NotificationSettings is an invalid size");
+
+ /// This is nn::settings::system::AccountSettings
+ struct AccountSettings {
+ u32 flags;
+ };
+ static_assert(sizeof(AccountSettings) == 0x4, "AccountSettings is an invalid size");
+
+ /// This is nn::settings::system::AccountNotificationSettings
+ struct AccountNotificationSettings {
+ Common::UUID uid;
+ AccountNotificationFlag flags;
+ FriendPresenceOverlayPermission friend_presence_permission;
+ FriendPresenceOverlayPermission friend_invitation_permission;
+ INSERT_PADDING_BYTES(0x2);
+ };
+ static_assert(sizeof(AccountNotificationSettings) == 0x18,
+ "AccountNotificationSettings is an invalid size");
+
+ /// This is nn::settings::system::InitialLaunchSettings
+ struct SleepSettings {
+ SleepFlag flags;
+ HandheldSleepPlan handheld_sleep_plan;
+ ConsoleSleepPlan console_sleep_plan;
+ };
+ static_assert(sizeof(SleepSettings) == 0xc, "SleepSettings is incorrect size");
+
+ /// This is nn::settings::system::InitialLaunchSettings
+ struct InitialLaunchSettings {
+ InitialLaunchFlag flags;
+ INSERT_PADDING_BYTES(0x4);
+ Time::Clock::SteadyClockTimePoint timestamp;
+ };
+ static_assert(sizeof(InitialLaunchSettings) == 0x20, "InitialLaunchSettings is incorrect size");
+
+ /// This is nn::settings::system::InitialLaunchSettings
+ struct EulaVersion {
+ u32 version;
+ RegionCode region_code;
+ EulaVersionClockType clock_type;
+ INSERT_PADDING_BYTES(0x4);
+ s64 posix_time;
+ Time::Clock::SteadyClockTimePoint timestamp;
+ };
+ static_assert(sizeof(EulaVersion) == 0x30, "EulaVersion is incorrect size");
+
+ void SetLanguageCode(HLERequestContext& ctx);
void GetFirmwareVersion(HLERequestContext& ctx);
void GetFirmwareVersion2(HLERequestContext& ctx);
+ void GetAccountSettings(HLERequestContext& ctx);
+ void SetAccountSettings(HLERequestContext& ctx);
+ void GetEulaVersions(HLERequestContext& ctx);
+ void SetEulaVersions(HLERequestContext& ctx);
void GetColorSetId(HLERequestContext& ctx);
void SetColorSetId(HLERequestContext& ctx);
+ void GetNotificationSettings(HLERequestContext& ctx);
+ void SetNotificationSettings(HLERequestContext& ctx);
+ void GetAccountNotificationSettings(HLERequestContext& ctx);
+ void SetAccountNotificationSettings(HLERequestContext& ctx);
+ void GetSettingsItemValueSize(HLERequestContext& ctx);
+ void GetSettingsItemValue(HLERequestContext& ctx);
+ void GetTvSettings(HLERequestContext& ctx);
+ void SetTvSettings(HLERequestContext& ctx);
+ void GetQuestFlag(HLERequestContext& ctx);
+ void SetRegionCode(HLERequestContext& ctx);
+ void GetPrimaryAlbumStorage(HLERequestContext& ctx);
+ void GetSleepSettings(HLERequestContext& ctx);
+ void SetSleepSettings(HLERequestContext& ctx);
+ void GetInitialLaunchSettings(HLERequestContext& ctx);
+ void SetInitialLaunchSettings(HLERequestContext& ctx);
void GetDeviceNickName(HLERequestContext& ctx);
+ void SetDeviceNickName(HLERequestContext& ctx);
+ void GetProductModel(HLERequestContext& ctx);
+ void GetMiiAuthorId(HLERequestContext& ctx);
+ void GetAutoUpdateEnableFlag(HLERequestContext& ctx);
+ void GetBatteryPercentageFlag(HLERequestContext& ctx);
+ void GetErrorReportSharePermission(HLERequestContext& ctx);
+ void GetAppletLaunchFlags(HLERequestContext& ctx);
+ void SetAppletLaunchFlags(HLERequestContext& ctx);
+ void GetKeyboardLayout(HLERequestContext& ctx);
+ void GetChineseTraditionalInputMethod(HLERequestContext& ctx);
+ void GetFieldTestingFlag(HLERequestContext& ctx);
+
+ AccountSettings account_settings{
+ .flags = {},
+ };
ColorSet color_set = ColorSet::BasicWhite;
+
+ NotificationSettings notification_settings{
+ .flags = {0x300},
+ .volume = NotificationVolume::High,
+ .start_time = {.hour = 9, .minute = 0},
+ .stop_time = {.hour = 21, .minute = 0},
+ };
+
+ std::vector<AccountNotificationSettings> account_notifications{};
+
+ TvSettings tv_settings{
+ .flags = {0xc},
+ .tv_resolution = TvResolution::Auto,
+ .hdmi_content_type = HdmiContentType::Game,
+ .rgb_range = RgbRange::Auto,
+ .cmu_mode = CmuMode::None,
+ .tv_underscan = {},
+ .tv_gama = 1.0f,
+ .constrast_ratio = 0.5f,
+ };
+
+ InitialLaunchSettings launch_settings{
+ .flags = {0x10001},
+ .timestamp = {},
+ };
+
+ SleepSettings sleep_settings{
+ .flags = {0x3},
+ .handheld_sleep_plan = HandheldSleepPlan::Sleep10Min,
+ .console_sleep_plan = ConsoleSleepPlan::Sleep1Hour,
+ };
+
+ u32 applet_launch_flag{};
+
+ std::vector<EulaVersion> eula_versions{};
+
+ RegionCode region_code;
+
+ LanguageCode language_code_setting;
};
} // namespace Service::Set
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 1608fa24c..9ab718e0a 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -52,8 +52,7 @@ static Result ValidateServiceName(const std::string& name) {
Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
SessionRequestHandlerPtr handler) {
-
- CASCADE_CODE(ValidateServiceName(name));
+ R_TRY(ValidateServiceName(name));
std::scoped_lock lk{lock};
if (registered_services.find(name) != registered_services.end()) {
@@ -77,7 +76,7 @@ Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
}
Result ServiceManager::UnregisterService(const std::string& name) {
- CASCADE_CODE(ValidateServiceName(name));
+ R_TRY(ValidateServiceName(name));
std::scoped_lock lk{lock};
const auto iter = registered_services.find(name);
@@ -92,8 +91,8 @@ Result ServiceManager::UnregisterService(const std::string& name) {
return ResultSuccess;
}
-ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name) {
- CASCADE_CODE(ValidateServiceName(name));
+Result ServiceManager::GetServicePort(Kernel::KPort** out_port, const std::string& name) {
+ R_TRY(ValidateServiceName(name));
std::scoped_lock lk{lock};
auto it = service_ports.find(name);
@@ -102,7 +101,8 @@ ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name
return Service::SM::ResultNotRegistered;
}
- return it->second;
+ *out_port = it->second;
+ return ResultSuccess;
}
/**
@@ -122,32 +122,34 @@ void SM::Initialize(HLERequestContext& ctx) {
}
void SM::GetService(HLERequestContext& ctx) {
- auto result = GetServiceImpl(ctx);
+ Kernel::KClientSession* client_session{};
+ auto result = GetServiceImpl(&client_session, ctx);
if (ctx.GetIsDeferred()) {
// Don't overwrite the command buffer.
return;
}
- if (result.Succeeded()) {
+ if (result == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
- rb.Push(result.Code());
- rb.PushMoveObjects(result.Unwrap());
+ rb.Push(result);
+ rb.PushMoveObjects(client_session);
} else {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
}
}
void SM::GetServiceTipc(HLERequestContext& ctx) {
- auto result = GetServiceImpl(ctx);
+ Kernel::KClientSession* client_session{};
+ auto result = GetServiceImpl(&client_session, ctx);
if (ctx.GetIsDeferred()) {
// Don't overwrite the command buffer.
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
- rb.Push(result.Code());
- rb.PushMoveObjects(result.Succeeded() ? result.Unwrap() : nullptr);
+ rb.Push(result);
+ rb.PushMoveObjects(result == ResultSuccess ? client_session : nullptr);
}
static std::string PopServiceName(IPC::RequestParser& rp) {
@@ -161,7 +163,7 @@ static std::string PopServiceName(IPC::RequestParser& rp) {
return result;
}
-ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) {
+Result SM::GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx) {
if (!ctx.GetManager()->GetIsInitializedForSm()) {
return Service::SM::ResultInvalidClient;
}
@@ -170,18 +172,18 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) {
std::string name(PopServiceName(rp));
// Find the named port.
- auto port_result = service_manager.GetServicePort(name);
- if (port_result.Code() == Service::SM::ResultInvalidServiceName) {
+ Kernel::KPort* port{};
+ auto port_result = service_manager.GetServicePort(&port, name);
+ if (port_result == Service::SM::ResultInvalidServiceName) {
LOG_ERROR(Service_SM, "Invalid service name '{}'", name);
return Service::SM::ResultInvalidServiceName;
}
- if (port_result.Failed()) {
+ if (port_result != ResultSuccess) {
LOG_INFO(Service_SM, "Waiting for service {} to become available", name);
ctx.SetIsDeferred();
return Service::SM::ResultNotRegistered;
}
- auto& port = port_result.Unwrap();
// Create a new session.
Kernel::KClientSession* session{};
@@ -192,7 +194,8 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) {
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId());
- return session;
+ *out_client_session = session;
+ return ResultSuccess;
}
void SM::RegisterService(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 6697f4007..14bfaf8c2 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -42,7 +42,7 @@ private:
void RegisterService(HLERequestContext& ctx);
void UnregisterService(HLERequestContext& ctx);
- ResultVal<Kernel::KClientSession*> GetServiceImpl(HLERequestContext& ctx);
+ Result GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx);
ServiceManager& service_manager;
Kernel::KernelCore& kernel;
@@ -55,7 +55,7 @@ public:
Result RegisterService(std::string name, u32 max_sessions, SessionRequestHandlerPtr handler);
Result UnregisterService(const std::string& name);
- ResultVal<Kernel::KPort*> GetServicePort(const std::string& name);
+ Result GetServicePort(Kernel::KPort** out_port, const std::string& name);
template <Common::DerivedFrom<SessionRequestHandler> T>
std::shared_ptr<T> GetService(const std::string& service_name) const {
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) {
}
void BSD::Select(HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_DEBUG(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -1029,6 +1029,11 @@ BSD::~BSD() {
}
}
+std::unique_lock<std::mutex> BSD::LockService() {
+ // Do not lock socket IClient instances.
+ return {};
+}
+
BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} {
// clang-format off
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:
// Callback identifier for the OnProxyPacketReceived event.
Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received;
+
+protected:
+ virtual std::unique_lock<std::mutex> LockService() override;
};
class BSDCFG final : public ServiceFramework<BSDCFG> {
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
index 5dfcaabb1..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 {
Dp,
};
+// This is nn::nsd::EnvironmentIdentifier
+struct EnvironmentIdentifier {
+ std::array<u8, 8> identifier;
+};
+static_assert(sizeof(EnvironmentIdentifier) == 0x8);
+
NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
@@ -54,7 +60,7 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
RegisterHandlers(functions);
}
-static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) {
+static std::string ResolveImpl(const std::string& fqdn_in) {
// The real implementation makes various substitutions.
// For now we just return the string as-is, which is good enough when not
// connecting to real Nintendo servers.
@@ -64,13 +70,10 @@ static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) {
static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) {
const auto res = ResolveImpl(fqdn_in);
- if (res.Failed()) {
- return res.Code();
- }
- if (res->size() >= fqdn_out.size()) {
+ if (res.size() >= fqdn_out.size()) {
return ResultOverflow;
}
- std::memcpy(fqdn_out.data(), res->c_str(), res->size() + 1);
+ std::memcpy(fqdn_out.data(), res.c_str(), res.size() + 1);
return ResultSuccess;
}
@@ -104,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) {
}
void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) {
- const std::string environment_identifier = "lp1";
- ctx.WriteBuffer(environment_identifier);
+ constexpr EnvironmentIdentifier lp1 = {
+ .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}};
+ ctx.WriteBuffer(lp1);
IPC::ResponseBuilder rb{ctx, 2};
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
const std::string host = Common::StringFromBuffer(host_buffer);
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
+ // Prevent resolution of Nintendo servers
+ if (host.find("srv.nintendo.net") != std::string::npos) {
+ LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
+ return {0, GetAddrInfoError::AGAIN};
+ }
+
auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt);
if (!res.has_value()) {
return {0, Translate(res.error())};
@@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
const auto host_buffer = ctx.ReadBuffer(0);
const std::string host = Common::StringFromBuffer(host_buffer);
+ // Prevent resolution of Nintendo servers
+ if (host.find("srv.nintendo.net") != std::string::npos) {
+ LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
+ return {0, GetAddrInfoError::AGAIN};
+ }
+
std::optional<std::string> service = std::nullopt;
if (ctx.CanReadBuffer(1)) {
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 {
AGAIN = 11,
INVAL = 22,
MFILE = 24,
+ PIPE = 32,
MSGSIZE = 90,
+ CONNABORTED = 103,
CONNRESET = 104,
NOTCONN = 107,
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) {
return Errno::INVAL;
case Network::Errno::MFILE:
return Errno::MFILE;
+ case Network::Errno::PIPE:
+ return Errno::PIPE;
case Network::Errno::NOTCONN:
return Errno::NOTCONN;
case Network::Errno::TIMEDOUT:
return Errno::TIMEDOUT;
+ case Network::Errno::CONNABORTED:
+ return Errno::CONNABORTED;
case Network::Errno::CONNRESET:
return Errno::CONNRESET;
case Network::Errno::INPROGRESS:
diff --git a/src/core/hle/service/spl/spl_module.cpp b/src/core/hle/service/spl/spl_module.cpp
index 0227d4393..549e6f4fa 100644
--- a/src/core/hle/service/spl/spl_module.cpp
+++ b/src/core/hle/service/spl/spl_module.cpp
@@ -19,7 +19,8 @@ namespace Service::SPL {
Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
const char* name)
: ServiceFramework{system_, name}, module{std::move(module_)},
- rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {}
+ rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
+ : static_cast<u32>(std::time(nullptr))) {}
Module::Interface::~Interface() = default;
@@ -29,10 +30,10 @@ void Module::Interface::GetConfig(HLERequestContext& ctx) {
// This should call svcCallSecureMonitor with the appropriate args.
// Since we do not have it implemented yet, we will use this for now.
- const auto smc_result = GetConfigImpl(config_item);
- const auto result_code = smc_result.Code();
+ u64 smc_result{};
+ const auto result_code = GetConfigImpl(&smc_result, config_item);
- if (smc_result.Failed()) {
+ if (result_code != ResultSuccess) {
LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item,
result_code.raw);
@@ -41,11 +42,11 @@ void Module::Interface::GetConfig(HLERequestContext& ctx) {
}
LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item,
- result_code.raw, *smc_result);
+ result_code.raw, smc_result);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(result_code);
- rb.Push(*smc_result);
+ rb.Push(smc_result);
}
void Module::Interface::ModularExponentiate(HLERequestContext& ctx) {
@@ -98,7 +99,7 @@ void Module::Interface::GetBootReason(HLERequestContext& ctx) {
rb.Push(ResultSecureMonitorNotImplemented);
}
-ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const {
+Result Module::Interface::GetConfigImpl(u64* out_config, ConfigItem config_item) const {
switch (config_item) {
case ConfigItem::DisableProgramVerification:
case ConfigItem::DramId:
@@ -120,40 +121,50 @@ ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const {
return ResultSecureMonitorNotImplemented;
case ConfigItem::ExosphereApiVersion:
// Get information about the current exosphere version.
- return (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) |
- (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) |
- (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) |
- (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware()));
+ *out_config = (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) |
+ (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) |
+ (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) |
+ (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware()));
+ return ResultSuccess;
case ConfigItem::ExosphereNeedsReboot:
// We are executing, so we aren't in the process of rebooting.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereNeedsShutdown:
// We are executing, so we aren't in the process of shutting down.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereGitCommitHash:
// Get information about the current exosphere git commit hash.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereHasRcmBugPatch:
// Get information about whether this unit has the RCM bug patched.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereBlankProdInfo:
// Get whether this unit should simulate a "blanked" PRODINFO.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereAllowCalWrites:
// Get whether this unit should allow writing to the calibration partition.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereEmummcType:
// Get what kind of emummc this unit has active.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExospherePayloadAddress:
// Gets the physical address of the reboot payload buffer, if one exists.
return ResultSecureMonitorNotInitialized;
case ConfigItem::ExosphereLogConfiguration:
// Get the log configuration.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereForceEnableUsb30:
// Get whether usb 3.0 should be force-enabled.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
default:
return ResultSecureMonitorInvalidArgument;
}
diff --git a/src/core/hle/service/spl/spl_module.h b/src/core/hle/service/spl/spl_module.h
index e074e115d..06dcffa6c 100644
--- a/src/core/hle/service/spl/spl_module.h
+++ b/src/core/hle/service/spl/spl_module.h
@@ -35,7 +35,7 @@ public:
std::shared_ptr<Module> module;
private:
- ResultVal<u64> GetConfigImpl(ConfigItem config_item) const;
+ Result GetConfigImpl(u64* out_config, ConfigItem config_item) const;
std::mt19937 rng;
};
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index 9c96f9763..6c8427b0d 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -4,6 +4,7 @@
#include "common/string_util.h"
#include "core/core.h"
+#include "core/hle/result.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
@@ -138,15 +139,14 @@ private:
bool do_not_close_socket = false;
bool get_server_cert_chain = false;
std::shared_ptr<Network::SocketBase> socket;
- bool did_set_host_name = false;
bool did_handshake = false;
- ResultVal<s32> SetSocketDescriptorImpl(s32 fd) {
+ Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
LOG_DEBUG(Service_SSL, "called, fd={}", fd);
ASSERT(!did_handshake);
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
- s32 ret_fd;
+
// Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor
if (do_not_close_socket) {
auto res = bsd->DuplicateSocketImpl(fd);
@@ -156,9 +156,9 @@ private:
}
fd = *res;
fd_to_close = fd;
- ret_fd = fd;
+ *out_fd = fd;
} else {
- ret_fd = -1;
+ *out_fd = -1;
}
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(fd);
if (!sock.has_value()) {
@@ -167,17 +167,13 @@ private:
}
socket = std::move(*sock);
backend->SetSocket(socket);
- return ret_fd;
+ return ResultSuccess;
}
Result SetHostNameImpl(const std::string& hostname) {
LOG_DEBUG(Service_SSL, "called. hostname={}", hostname);
ASSERT(!did_handshake);
- Result res = backend->SetHostName(hostname);
- if (res == ResultSuccess) {
- did_set_host_name = true;
- }
- return res;
+ return backend->SetHostName(hostname);
}
Result SetVerifyOptionImpl(u32 option) {
@@ -207,9 +203,6 @@ private:
Result DoHandshakeImpl() {
ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; });
- ASSERT_OR_EXECUTE_MSG(
- did_set_host_name, { return ResultInternalError; },
- "Expected SetHostName before DoHandshake");
Result res = backend->DoHandshake();
did_handshake = res.IsSuccess();
return res;
@@ -247,34 +240,36 @@ private:
return ret;
}
- ResultVal<std::vector<u8>> ReadImpl(size_t size) {
+ Result ReadImpl(std::vector<u8>* out_data, size_t size) {
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
- std::vector<u8> res(size);
- ResultVal<size_t> actual = backend->Read(res);
- if (actual.Failed()) {
- return actual.Code();
+ size_t actual_size{};
+ Result res = backend->Read(&actual_size, *out_data);
+ if (res != ResultSuccess) {
+ return res;
}
- res.resize(*actual);
+ out_data->resize(actual_size);
return res;
}
- ResultVal<size_t> WriteImpl(std::span<const u8> data) {
+ Result WriteImpl(size_t* out_size, std::span<const u8> data) {
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
- return backend->Write(data);
+ return backend->Write(out_size, data);
}
- ResultVal<s32> PendingImpl() {
+ Result PendingImpl(s32* out_pending) {
LOG_WARNING(Service_SSL, "(STUBBED) called.");
- return 0;
+ *out_pending = 0;
+ return ResultSuccess;
}
void SetSocketDescriptor(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const s32 fd = rp.Pop<s32>();
- const ResultVal<s32> res = SetSocketDescriptorImpl(fd);
+ const s32 in_fd = rp.Pop<s32>();
+ s32 out_fd{-1};
+ const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(res.Code());
- rb.Push<s32>(res.ValueOr(-1));
+ rb.Push(res);
+ rb.Push<s32>(out_fd);
}
void SetHostName(HLERequestContext& ctx) {
@@ -313,14 +308,15 @@ private:
};
static_assert(sizeof(OutputParameters) == 0x8);
- const Result res = DoHandshakeImpl();
+ Result res = DoHandshakeImpl();
OutputParameters out{};
if (res == ResultSuccess) {
- auto certs = backend->GetServerCerts();
- if (certs.Succeeded()) {
- const std::vector<u8> certs_buf = SerializeServerCerts(*certs);
+ std::vector<std::vector<u8>> certs;
+ res = backend->GetServerCerts(&certs);
+ if (res == ResultSuccess) {
+ const std::vector<u8> certs_buf = SerializeServerCerts(certs);
ctx.WriteBuffer(certs_buf);
- out.certs_count = static_cast<u32>(certs->size());
+ out.certs_count = static_cast<u32>(certs.size());
out.certs_size = static_cast<u32>(certs_buf.size());
}
}
@@ -330,29 +326,32 @@ private:
}
void Read(HLERequestContext& ctx) {
- const ResultVal<std::vector<u8>> res = ReadImpl(ctx.GetWriteBufferSize());
+ std::vector<u8> output_bytes;
+ const Result res = ReadImpl(&output_bytes, ctx.GetWriteBufferSize());
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(res.Code());
- if (res.Succeeded()) {
- rb.Push(static_cast<u32>(res->size()));
- ctx.WriteBuffer(*res);
+ rb.Push(res);
+ if (res == ResultSuccess) {
+ rb.Push(static_cast<u32>(output_bytes.size()));
+ ctx.WriteBuffer(output_bytes);
} else {
rb.Push(static_cast<u32>(0));
}
}
void Write(HLERequestContext& ctx) {
- const ResultVal<size_t> res = WriteImpl(ctx.ReadBuffer());
+ size_t write_size{0};
+ const Result res = WriteImpl(&write_size, ctx.ReadBuffer());
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(res.Code());
- rb.Push(static_cast<u32>(res.ValueOr(0)));
+ rb.Push(res);
+ rb.Push(static_cast<u32>(write_size));
}
void Pending(HLERequestContext& ctx) {
- const ResultVal<s32> res = PendingImpl();
+ s32 pending_size{0};
+ const Result res = PendingImpl(&pending_size);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(res.Code());
- rb.Push<s32>(res.ValueOr(0));
+ rb.Push(res);
+ rb.Push<s32>(pending_size);
}
void SetSessionCacheMode(HLERequestContext& ctx) {
@@ -438,13 +437,14 @@ private:
void CreateConnection(HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "called");
- auto backend_res = CreateSSLConnectionBackend();
+ std::unique_ptr<SSLConnectionBackend> backend;
+ const Result res = CreateSSLConnectionBackend(&backend);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(backend_res.Code());
- if (backend_res.Succeeded()) {
+ rb.Push(res);
+ if (res == ResultSuccess) {
rb.PushIpcInterface<ISslConnection>(system, ssl_version, shared_data,
- std::move(*backend_res));
+ std::move(backend));
}
}
diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h
index 409f4367c..a2ec8e694 100644
--- a/src/core/hle/service/ssl/ssl_backend.h
+++ b/src/core/hle/service/ssl/ssl_backend.h
@@ -35,11 +35,11 @@ public:
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
virtual Result SetHostName(const std::string& hostname) = 0;
virtual Result DoHandshake() = 0;
- virtual ResultVal<size_t> Read(std::span<u8> data) = 0;
- virtual ResultVal<size_t> Write(std::span<const u8> data) = 0;
- virtual ResultVal<std::vector<std::vector<u8>>> GetServerCerts() = 0;
+ virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
+ virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
+ virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
};
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend();
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
} // namespace Service::SSL
diff --git a/src/core/hle/service/ssl/ssl_backend_none.cpp b/src/core/hle/service/ssl/ssl_backend_none.cpp
index 2f4f23c42..a7fafd0a3 100644
--- a/src/core/hle/service/ssl/ssl_backend_none.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_none.cpp
@@ -7,7 +7,7 @@
namespace Service::SSL {
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
LOG_ERROR(Service_SSL,
"Can't create SSL connection because no SSL backend is available on this platform");
return ResultInternalError;
diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp
index 6ca869dbf..5714e6f3c 100644
--- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp
@@ -105,31 +105,30 @@ public:
return ResultInternalError;
}
}
- return HandleReturn("SSL_do_handshake", 0, ret).Code();
+ return HandleReturn("SSL_do_handshake", 0, ret);
}
- ResultVal<size_t> Read(std::span<u8> data) override {
- size_t actual;
- const int ret = SSL_read_ex(ssl, data.data(), data.size(), &actual);
- return HandleReturn("SSL_read_ex", actual, ret);
+ Result Read(size_t* out_size, std::span<u8> data) override {
+ const int ret = SSL_read_ex(ssl, data.data(), data.size(), out_size);
+ return HandleReturn("SSL_read_ex", out_size, ret);
}
- ResultVal<size_t> Write(std::span<const u8> data) override {
- size_t actual;
- const int ret = SSL_write_ex(ssl, data.data(), data.size(), &actual);
- return HandleReturn("SSL_write_ex", actual, ret);
+ Result Write(size_t* out_size, std::span<const u8> data) override {
+ const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
+ return HandleReturn("SSL_write_ex", out_size, ret);
}
- ResultVal<size_t> HandleReturn(const char* what, size_t actual, int ret) {
+ Result HandleReturn(const char* what, size_t* actual, int ret) {
const int ssl_err = SSL_get_error(ssl, ret);
CheckOpenSSLErrors();
switch (ssl_err) {
case SSL_ERROR_NONE:
- return actual;
+ return ResultSuccess;
case SSL_ERROR_ZERO_RETURN:
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what);
// DoHandshake special-cases this, but for Read and Write:
- return size_t(0);
+ *actual = 0;
+ return ResultSuccess;
case SSL_ERROR_WANT_READ:
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what);
return ResultWouldBlock;
@@ -139,20 +138,20 @@ public:
default:
if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) {
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what);
- return size_t(0);
+ *actual = 0;
+ return ResultSuccess;
}
LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err);
return ResultInternalError;
}
}
- ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
+ Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
if (!chain) {
LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr");
return ResultInternalError;
}
- std::vector<std::vector<u8>> ret;
int count = sk_X509_num(chain);
ASSERT(count >= 0);
for (int i = 0; i < count; i++) {
@@ -161,16 +160,15 @@ public:
unsigned char* buf = nullptr;
int len = i2d_X509(x509, &buf);
ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; });
- ret.emplace_back(buf, buf + len);
+ out_certs->emplace_back(buf, buf + len);
OPENSSL_free(buf);
}
- return ret;
+ return ResultSuccess;
}
~SSLConnectionBackendOpenSSL() {
- // these are null-tolerant:
+ // this is null-tolerant:
SSL_free(ssl);
- BIO_free(bio);
}
static void KeyLogCallback(const SSL* ssl, const char* line) {
@@ -253,13 +251,13 @@ public:
std::shared_ptr<Network::SocketBase> socket;
};
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendOpenSSL>();
- const Result res = conn->Init();
- if (res.IsFailure()) {
- return res;
- }
- return conn;
+
+ R_TRY(conn->Init());
+
+ *out_backend = std::move(conn);
+ return ResultSuccess;
}
namespace {
diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp
index d8074339a..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;
static void OneTimeInit() {
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
schannel_cred.dwFlags =
- SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
- SCH_CRED_AUTO_CRED_VALIDATION | // validate certs
- SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
+ SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
+ SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names
+ SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
// ^ I'm assuming that nobody would want to connect Yuzu to a
// service that requires some OS-provided corporate client
// certificate, and presenting one to some arbitrary server
@@ -227,16 +227,15 @@ public:
ciphertext_read_buf.size());
}
- const SECURITY_STATUS ret =
- InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt : nullptr,
- // Caller ensured we have set a hostname:
- const_cast<char*>(hostname.value().c_str()), req,
- 0, // Reserved1
- 0, // TargetDataRep not used with Schannel
- initial_call_done ? &input_desc : nullptr,
- 0, // Reserved2
- initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
- nullptr); // ptsExpiry
+ char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr;
+ const SECURITY_STATUS ret = InitializeSecurityContextA(
+ &cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req,
+ 0, // Reserved1
+ 0, // TargetDataRep not used with Schannel
+ initial_call_done ? &input_desc : nullptr,
+ 0, // Reserved2
+ initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
+ nullptr); // ptsExpiry
if (output_buffers[0].pvBuffer) {
const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
@@ -299,21 +298,22 @@ public:
return ResultSuccess;
}
- ResultVal<size_t> Read(std::span<u8> data) override {
+ Result Read(size_t* out_size, std::span<u8> data) override {
+ *out_size = 0;
if (handshake_state != HandshakeState::Connected) {
LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake");
return ResultInternalError;
}
if (data.size() == 0 || got_read_eof) {
- return size_t(0);
+ return ResultSuccess;
}
while (1) {
if (!cleartext_read_buf.empty()) {
- const size_t read_size = std::min(cleartext_read_buf.size(), data.size());
- std::memcpy(data.data(), cleartext_read_buf.data(), read_size);
+ *out_size = std::min(cleartext_read_buf.size(), data.size());
+ std::memcpy(data.data(), cleartext_read_buf.data(), *out_size);
cleartext_read_buf.erase(cleartext_read_buf.begin(),
- cleartext_read_buf.begin() + read_size);
- return read_size;
+ cleartext_read_buf.begin() + *out_size);
+ return ResultSuccess;
}
if (!ciphertext_read_buf.empty()) {
SecBuffer empty{
@@ -366,7 +366,8 @@ public:
case SEC_I_CONTEXT_EXPIRED:
// Server hung up by sending close_notify.
got_read_eof = true;
- return size_t(0);
+ *out_size = 0;
+ return ResultSuccess;
default:
LOG_ERROR(Service_SSL, "DecryptMessage failed: {}",
Common::NativeErrorToString(ret));
@@ -379,18 +380,21 @@ public:
}
if (ciphertext_read_buf.empty()) {
got_read_eof = true;
- return size_t(0);
+ *out_size = 0;
+ return ResultSuccess;
}
}
}
- ResultVal<size_t> Write(std::span<const u8> data) override {
+ Result Write(size_t* out_size, std::span<const u8> data) override {
+ *out_size = 0;
+
if (handshake_state != HandshakeState::Connected) {
LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake");
return ResultInternalError;
}
if (data.size() == 0) {
- return size_t(0);
+ return ResultSuccess;
}
data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage));
if (!cleartext_write_buf.empty()) {
@@ -402,7 +406,7 @@ public:
LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer");
return ResultInternalError;
}
- return WriteAlreadyEncryptedData();
+ return WriteAlreadyEncryptedData(out_size);
} else {
cleartext_write_buf.assign(data.begin(), data.end());
}
@@ -448,21 +452,21 @@ public:
tmp_data_buf.end());
ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(),
trailer_buf.end());
- return WriteAlreadyEncryptedData();
+ return WriteAlreadyEncryptedData(out_size);
}
- ResultVal<size_t> WriteAlreadyEncryptedData() {
+ Result WriteAlreadyEncryptedData(size_t* out_size) {
const Result r = FlushCiphertextWriteBuf();
if (r != ResultSuccess) {
return r;
}
// write buf is empty
- const size_t cleartext_bytes_written = cleartext_write_buf.size();
+ *out_size = cleartext_write_buf.size();
cleartext_write_buf.clear();
- return cleartext_bytes_written;
+ return ResultSuccess;
}
- ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
+ Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
PCCERT_CONTEXT returned_cert = nullptr;
const SECURITY_STATUS ret =
QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert);
@@ -473,16 +477,16 @@ public:
return ResultInternalError;
}
PCCERT_CONTEXT some_cert = nullptr;
- std::vector<std::vector<u8>> certs;
- while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) {
- certs.emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
- static_cast<u8*>(some_cert->pbCertEncoded) +
- some_cert->cbCertEncoded);
+ while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) !=
+ nullptr) {
+ out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
+ static_cast<u8*>(some_cert->pbCertEncoded) +
+ some_cert->cbCertEncoded);
}
- std::reverse(certs.begin(),
- certs.end()); // Windows returns certs in reverse order from what we want
+ std::reverse(out_certs->begin(),
+ out_certs->end()); // Windows returns certs in reverse order from what we want
CertFreeCertificateContext(returned_cert);
- return certs;
+ return ResultSuccess;
}
~SSLConnectionBackendSchannel() {
@@ -532,13 +536,13 @@ public:
size_t read_buf_fill_size = 0;
};
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendSchannel>();
- const Result res = conn->Init();
- if (res.IsFailure()) {
- return res;
- }
- return conn;
+
+ R_TRY(conn->Init());
+
+ *out_backend = std::move(conn);
+ return ResultSuccess;
}
} // namespace Service::SSL
diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
index b3083cbad..c48914f64 100644
--- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
@@ -100,27 +100,23 @@ public:
Result DoHandshake() override {
OSStatus status = SSLHandshake(context);
- return HandleReturn("SSLHandshake", 0, status).Code();
+ return HandleReturn("SSLHandshake", 0, status);
}
- ResultVal<size_t> Read(std::span<u8> data) override {
- size_t actual;
- OSStatus status = SSLRead(context, data.data(), data.size(), &actual);
- ;
- return HandleReturn("SSLRead", actual, status);
+ Result Read(size_t* out_size, std::span<u8> data) override {
+ OSStatus status = SSLRead(context, data.data(), data.size(), out_size);
+ return HandleReturn("SSLRead", out_size, status);
}
- ResultVal<size_t> Write(std::span<const u8> data) override {
- size_t actual;
- OSStatus status = SSLWrite(context, data.data(), data.size(), &actual);
- ;
- return HandleReturn("SSLWrite", actual, status);
+ Result Write(size_t* out_size, std::span<const u8> data) override {
+ OSStatus status = SSLWrite(context, data.data(), data.size(), out_size);
+ return HandleReturn("SSLWrite", out_size, status);
}
- ResultVal<size_t> HandleReturn(const char* what, size_t actual, OSStatus status) {
+ Result HandleReturn(const char* what, size_t* actual, OSStatus status) {
switch (status) {
case 0:
- return actual;
+ return ResultSuccess;
case errSSLWouldBlock:
return ResultWouldBlock;
default: {
@@ -136,22 +132,21 @@ public:
}
}
- ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
+ Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
CFReleaser<SecTrustRef> trust;
OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
if (status) {
LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
return ResultInternalError;
}
- std::vector<std::vector<u8>> ret;
for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
const u8* ptr = CFDataGetBytePtr(data);
- ret.emplace_back(ptr, ptr + CFDataGetLength(data));
+ out_certs->emplace_back(ptr, ptr + CFDataGetLength(data));
}
- return ret;
+ return ResultSuccess;
}
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
@@ -210,13 +205,13 @@ private:
std::shared_ptr<Network::SocketBase> socket;
};
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
- const Result res = conn->Init();
- if (res.IsFailure()) {
- return res;
- }
- return conn;
+
+ R_TRY(conn->Init());
+
+ *out_backend = std::move(conn);
+ return ResultSuccess;
}
} // namespace Service::SSL
diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp
index 5d60be67a..1b96de37a 100644
--- a/src/core/hle/service/time/time_zone_content_manager.cpp
+++ b/src/core/hle/service/time/time_zone_content_manager.cpp
@@ -3,6 +3,7 @@
#include <chrono>
#include <sstream>
+#include <utility>
#include "common/logging/log.h"
#include "common/settings.h"
@@ -46,14 +47,14 @@ static FileSys::VirtualDir GetTimeZoneBinary(Core::System& system) {
return FileSys::ExtractRomFS(romfs);
}
-static std::vector<std::string> BuildLocationNameCache(Core::System& system) {
- const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)};
- if (!extracted_romfs) {
+static std::vector<std::string> BuildLocationNameCache(
+ const FileSys::VirtualDir& time_zone_binary) {
+ if (!time_zone_binary) {
LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid);
return {};
}
- const FileSys::VirtualFile binary_list{extracted_romfs->GetFile("binaryList.txt")};
+ const FileSys::VirtualFile binary_list{time_zone_binary->GetFile("binaryList.txt")};
if (!binary_list) {
LOG_ERROR(Service_Time, "{:016X} has no file binaryList.txt!", time_zone_binary_titleid);
return {};
@@ -73,10 +74,12 @@ static std::vector<std::string> BuildLocationNameCache(Core::System& system) {
}
TimeZoneContentManager::TimeZoneContentManager(Core::System& system_)
- : system{system_}, location_name_cache{BuildLocationNameCache(system)} {}
+ : system{system_}, time_zone_binary{GetTimeZoneBinary(system)},
+ location_name_cache{BuildLocationNameCache(time_zone_binary)} {}
void TimeZoneContentManager::Initialize(TimeManager& time_manager) {
- const auto timezone_setting = Settings::GetTimeZoneString();
+ const auto timezone_setting =
+ Settings::GetTimeZoneString(Settings::values.time_zone_index.GetValue());
if (FileSys::VirtualFile vfs_file;
GetTimeZoneInfoFile(timezone_setting, vfs_file) == ResultSuccess) {
@@ -111,13 +114,12 @@ Result TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_n
return ERROR_TIME_NOT_FOUND;
}
- const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)};
- if (!extracted_romfs) {
+ if (!time_zone_binary) {
LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid);
return ERROR_TIME_NOT_FOUND;
}
- const FileSys::VirtualDir zoneinfo_dir{extracted_romfs->GetSubdirectory("zoneinfo")};
+ const FileSys::VirtualDir zoneinfo_dir{time_zone_binary->GetSubdirectory("zoneinfo")};
if (!zoneinfo_dir) {
LOG_ERROR(Service_Time, "{:016X} has no directory zoneinfo!", time_zone_binary_titleid);
return ERROR_TIME_NOT_FOUND;
diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h
index 3d94b6428..a6f9698bc 100644
--- a/src/core/hle/service/time/time_zone_content_manager.h
+++ b/src/core/hle/service/time/time_zone_content_manager.h
@@ -6,6 +6,7 @@
#include <string>
#include <vector>
+#include "core/file_sys/vfs_types.h"
#include "core/hle/service/time/time_zone_manager.h"
namespace Core {
@@ -41,6 +42,7 @@ private:
Core::System& system;
TimeZoneManager time_zone_manager;
+ const FileSys::VirtualDir time_zone_binary;
const std::vector<std::string> location_name_cache;
};
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index 69af2868a..f0b5eff8a 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -58,14 +58,15 @@ const Layer& Display::GetLayer(std::size_t index) const {
return *layers.at(index);
}
-ResultVal<Kernel::KReadableEvent*> Display::GetVSyncEvent() {
+Result Display::GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event) {
if (got_vsync_event) {
return ResultPermissionDenied;
}
got_vsync_event = true;
- return GetVSyncEventUnchecked();
+ *out_vsync_event = GetVSyncEventUnchecked();
+ return ResultSuccess;
}
Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() {
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 3f31d1f32..101cbce20 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -85,7 +85,7 @@ public:
* @returns The internal Vsync event if it has not yet been retrieved,
* VI::ResultPermissionDenied otherwise.
*/
- [[nodiscard]] ResultVal<Kernel::KReadableEvent*> GetVSyncEvent();
+ [[nodiscard]] Result GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event);
/// Gets the internal vsync event.
Kernel::KReadableEvent* GetVSyncEventUnchecked();
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 1b193f00c..2eb978379 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -217,7 +217,7 @@ private:
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight));
} else {
@@ -683,9 +683,9 @@ private:
LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
- const auto vsync_event = nv_flinger.FindVsyncEvent(display_id);
- if (vsync_event.Failed()) {
- const auto result = vsync_event.Code();
+ Kernel::KReadableEvent* vsync_event{};
+ const auto result = nv_flinger.FindVsyncEvent(&vsync_event, display_id);
+ if (result != ResultSuccess) {
if (result == ResultNotFound) {
LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
}
@@ -697,7 +697,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(*vsync_event);
+ rb.PushCopyObjects(vsync_event);
}
void ConvertScalingMode(HLERequestContext& ctx) {
@@ -705,15 +705,16 @@ private:
const auto mode = rp.PopEnum<NintendoScaleMode>();
LOG_DEBUG(Service_VI, "called mode={}", mode);
- const auto converted_mode = ConvertScalingModeImpl(mode);
+ ConvertedScaleMode converted_mode{};
+ const auto result = ConvertScalingModeImpl(&converted_mode, mode);
- if (converted_mode.Succeeded()) {
+ if (result == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.PushEnum(*converted_mode);
+ rb.PushEnum(converted_mode);
} else {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(converted_mode.Code());
+ rb.Push(result);
}
}
@@ -760,18 +761,24 @@ private:
rb.Push(alignment);
}
- static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) {
+ static Result ConvertScalingModeImpl(ConvertedScaleMode* out_scaling_mode,
+ NintendoScaleMode mode) {
switch (mode) {
case NintendoScaleMode::None:
- return ConvertedScaleMode::None;
+ *out_scaling_mode = ConvertedScaleMode::None;
+ return ResultSuccess;
case NintendoScaleMode::Freeze:
- return ConvertedScaleMode::Freeze;
+ *out_scaling_mode = ConvertedScaleMode::Freeze;
+ return ResultSuccess;
case NintendoScaleMode::ScaleToWindow:
- return ConvertedScaleMode::ScaleToWindow;
+ *out_scaling_mode = ConvertedScaleMode::ScaleToWindow;
+ return ResultSuccess;
case NintendoScaleMode::ScaleAndCrop:
- return ConvertedScaleMode::ScaleAndCrop;
+ *out_scaling_mode = ConvertedScaleMode::ScaleAndCrop;
+ return ResultSuccess;
case NintendoScaleMode::PreserveAspectRatio:
- return ConvertedScaleMode::PreserveAspectRatio;
+ *out_scaling_mode = ConvertedScaleMode::PreserveAspectRatio;
+ return ResultSuccess;
default:
LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode);
return ResultOperationFailed;
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 {
namespace {
+enum class CallType {
+ Send,
+ Other,
+};
+
#ifdef _WIN32
using socklen_t = int;
+SOCKET interrupt_socket = static_cast<SOCKET>(-1);
+
+void InterruptSocketOperations() {
+ closesocket(interrupt_socket);
+}
+
+void AcknowledgeInterrupt() {
+ interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+}
+
void Initialize() {
WSADATA wsa_data;
(void)WSAStartup(MAKEWORD(2, 2), &wsa_data);
+
+ AcknowledgeInterrupt();
}
void Finalize() {
+ InterruptSocketOperations();
WSACleanup();
}
+SOCKET GetInterruptSocket() {
+ return interrupt_socket;
+}
+
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result;
@@ -96,7 +118,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) {
return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
}
-Errno TranslateNativeError(int e) {
+Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
switch (e) {
case 0:
return Errno::SUCCESS;
@@ -112,6 +134,14 @@ Errno TranslateNativeError(int e) {
return Errno::AGAIN;
case WSAECONNREFUSED:
return Errno::CONNREFUSED;
+ case WSAECONNABORTED:
+ if (call_type == CallType::Send) {
+ // Winsock yields WSAECONNABORTED from `send` in situations where Unix
+ // systems, and actual Switches, yield EPIPE.
+ return Errno::PIPE;
+ } else {
+ return Errno::CONNABORTED;
+ }
case WSAECONNRESET:
return Errno::CONNRESET;
case WSAEHOSTUNREACH:
@@ -144,9 +174,42 @@ constexpr int SD_RECEIVE = SHUT_RD;
constexpr int SD_SEND = SHUT_WR;
constexpr int SD_BOTH = SHUT_RDWR;
-void Initialize() {}
+int interrupt_pipe_fd[2] = {-1, -1};
-void Finalize() {}
+void Initialize() {
+ if (pipe(interrupt_pipe_fd) != 0) {
+ LOG_ERROR(Network, "Failed to create interrupt pipe!");
+ }
+ int flags = fcntl(interrupt_pipe_fd[0], F_GETFL);
+ ASSERT_MSG(fcntl(interrupt_pipe_fd[0], F_SETFL, flags | O_NONBLOCK) == 0,
+ "Failed to set nonblocking state for interrupt pipe");
+}
+
+void Finalize() {
+ if (interrupt_pipe_fd[0] >= 0) {
+ close(interrupt_pipe_fd[0]);
+ }
+ if (interrupt_pipe_fd[1] >= 0) {
+ close(interrupt_pipe_fd[1]);
+ }
+}
+
+void InterruptSocketOperations() {
+ u8 value = 0;
+ ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1);
+}
+
+void AcknowledgeInterrupt() {
+ u8 value = 0;
+ ssize_t ret = read(interrupt_pipe_fd[0], &value, sizeof(value));
+ if (ret != 1 && errno != EAGAIN && errno != EWOULDBLOCK) {
+ LOG_ERROR(Network, "Failed to acknowledge interrupt on shutdown");
+ }
+}
+
+SOCKET GetInterruptSocket() {
+ return interrupt_pipe_fd[0];
+}
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result;
@@ -198,7 +261,7 @@ bool EnableNonBlock(int fd, bool enable) {
return fcntl(fd, F_SETFL, flags) == 0;
}
-Errno TranslateNativeError(int e) {
+Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
switch (e) {
case 0:
return Errno::SUCCESS;
@@ -208,6 +271,10 @@ Errno TranslateNativeError(int e) {
return Errno::INVAL;
case EMFILE:
return Errno::MFILE;
+ case EPIPE:
+ return Errno::PIPE;
+ case ECONNABORTED:
+ return Errno::CONNABORTED;
case ENOTCONN:
return Errno::NOTCONN;
case EAGAIN:
@@ -236,13 +303,13 @@ Errno TranslateNativeError(int e) {
#endif
-Errno GetAndLogLastError() {
+Errno GetAndLogLastError(CallType call_type = CallType::Other) {
#ifdef _WIN32
int e = WSAGetLastError();
#else
int e = errno;
#endif
- const Errno err = TranslateNativeError(e);
+ const Errno err = TranslateNativeError(e, call_type);
if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) {
// These happen during normal operation, so only log them at debug level.
LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
@@ -473,10 +540,24 @@ NetworkInstance::~NetworkInstance() {
Finalize();
}
+void CancelPendingSocketOperations() {
+ InterruptSocketOperations();
+}
+
+void RestartSocketOperations() {
+ AcknowledgeInterrupt();
+}
+
std::optional<IPv4Address> GetHostIPv4Address() {
const auto network_interface = Network::GetSelectedNetworkInterface();
if (!network_interface.has_value()) {
- LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface");
+ // Only print the error once to avoid log spam
+ static bool print_error = true;
+ if (print_error) {
+ LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface");
+ print_error = false;
+ }
+
return {};
}
@@ -537,7 +618,14 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
return result;
});
- const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout);
+ host_pollfds.push_back(WSAPOLLFD{
+ .fd = GetInterruptSocket(),
+ .events = POLLIN,
+ .revents = 0,
+ });
+
+ const int result =
+ WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), timeout);
if (result == 0) {
ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(),
[](WSAPOLLFD fd) { return fd.revents == 0; }));
@@ -604,6 +692,24 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
+
+ std::vector<WSAPOLLFD> host_pollfds{
+ WSAPOLLFD{fd, POLLIN, 0},
+ WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
+ };
+
+ while (true) {
+ const int pollres =
+ WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), -1);
+ if (host_pollfds[1].revents != 0) {
+ // Interrupt signaled before a client could be accepted, break
+ return {AcceptResult{}, Errno::AGAIN};
+ }
+ if (pollres > 0) {
+ break;
+ }
+ }
+
const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen);
if (new_socket == INVALID_SOCKET) {
@@ -725,13 +831,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) {
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
ASSERT(flags == 0);
+ int native_flags = 0;
+#if YUZU_UNIX
+ native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE
+#endif
const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
- static_cast<int>(message.size()), 0);
+ static_cast<int>(message.size()), native_flags);
if (result != SOCKET_ERROR) {
return {static_cast<s32>(result), Errno::SUCCESS};
}
- return {-1, GetAndLogLastError()};
+ return {-1, GetAndLogLastError(CallType::Send)};
}
std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
@@ -753,7 +863,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
return {static_cast<s32>(result), Errno::SUCCESS};
}
- return {-1, GetAndLogLastError()};
+ return {-1, GetAndLogLastError(CallType::Send)};
}
Errno Socket::Close() {
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
index badcb8369..b7b7d773a 100644
--- a/src/core/internal_network/network.h
+++ b/src/core/internal_network/network.h
@@ -33,10 +33,12 @@ enum class Errno {
BADF,
INVAL,
MFILE,
+ PIPE,
NOTCONN,
AGAIN,
CONNREFUSED,
CONNRESET,
+ CONNABORTED,
HOSTUNREACH,
NETDOWN,
NETUNREACH,
@@ -94,6 +96,9 @@ public:
~NetworkInstance();
};
+void CancelPendingSocketOperations();
+void RestartSocketOperations();
+
#ifdef _WIN32
constexpr IPv4Address TranslateIPv4(in_addr addr) {
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() {
});
if (res == network_interfaces.end()) {
- LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
+ // Only print the error once to avoid log spam
+ static bool print_error = true;
+ if (print_error) {
+ LOG_ERROR(Network, "Couldn't find selected interface \"{}\"",
+ selected_network_interface);
+ print_error = false;
+ }
+
return std::nullopt;
}
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 {
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
bool override_update_)
- : AppLoader(std::move(file_)), override_update(override_update_) {
+ : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
const auto file_dir = file->GetContainingDirectory();
// Title ID
@@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
}
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
- FileSys::VirtualDir directory, bool override_update_)
+ FileSys::VirtualDir directory, bool override_update_, bool is_hbl_)
: AppLoader(directory->GetFile("main")), dir(std::move(directory)),
- override_update(override_update_) {}
+ override_update(override_update_), is_hbl(is_hbl_) {}
FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) {
if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) {
@@ -147,13 +147,13 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
// Setup the process code layout
- if (process.LoadFromMetadata(metadata, code_size).IsError()) {
+ if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) {
return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
}
// Load NSO modules
modules.clear();
- const VAddr base_address{GetInteger(process.GetPageTable().GetCodeRegionStart())};
+ const VAddr base_address{GetInteger(process.GetEntryPoint())};
VAddr next_load_addr{base_address};
const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
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:
// Overload to accept exefs directory. Must contain 'main' and 'main.npdm'
explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory,
- bool override_update_ = false);
+ bool override_update_ = false,
+ bool is_hbl_ = false);
/**
* Identifies whether or not the given file is a deconstructed ROM directory.
@@ -62,6 +63,7 @@ private:
std::string name;
u64 title_id{};
bool override_update;
+ bool is_hbl;
Modules modules;
};
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,
codeset.DataSegment().size += kip->GetBSSSize();
// Setup the process code layout
- if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size())
+ if (process
+ .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
.IsError()) {
return {ResultStatus::ErrorNotInitialized, {}};
}
codeset.memory = std::move(program_image);
- const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart());
+ const VAddr base_address = GetInteger(process.GetEntryPoint());
process.LoadModule(std::move(codeset), base_address);
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) {
return "unknown";
}
-constexpr std::array<const char*, 66> RESULT_MESSAGES{
+constexpr std::array<const char*, 68> RESULT_MESSAGES{
"The operation completed successfully.",
"The loader requested to load is already loaded.",
"The operation is not implemented.",
@@ -135,7 +135,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
"The titlekey and/or titlekek is incorrect or the section header is invalid.",
"The XCI file is missing a Program-type NCA.",
"The NCA file is not an application.",
- "The ExeFS partition could not be found.",
+ "The Program-type NCA contains no executable. An update may be required.",
"The XCI file has a bad header.",
"The XCI file is missing a partition.",
"The file could not be found or does not exist.",
@@ -169,12 +169,14 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
"The BKTR-type NCA has a bad Subsection block.",
"The BKTR-type NCA has a bad Relocation bucket.",
"The BKTR-type NCA has a bad Subsection bucket.",
- "The BKTR-type NCA is missing the base RomFS.",
+ "Game updates cannot be loaded directly. Load the base game instead.",
"The NSP or XCI does not contain an update in addition to the base game.",
"The KIP file has a bad header.",
"The KIP BLZ decompression of the section failed unexpectedly.",
"The INI file has a bad header.",
"The INI file contains more than the maximum allowable number of KIP files.",
+ "Integrity verification could not be performed for this file.",
+ "Integrity verification failed.",
};
std::string GetResultStatusString(ResultStatus status) {
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 7a2a52fd4..b4828f7cd 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -3,6 +3,7 @@
#pragma once
+#include <functional>
#include <iosfwd>
#include <memory>
#include <optional>
@@ -79,8 +80,6 @@ enum class ResultStatus : u16 {
ErrorBadPFSHeader,
ErrorIncorrectPFSFileSize,
ErrorBadNCAHeader,
- ErrorCompressedNCA,
- ErrorSparseNCA,
ErrorMissingProductionKeyFile,
ErrorMissingHeaderKey,
ErrorIncorrectHeaderKey,
@@ -134,6 +133,8 @@ enum class ResultStatus : u16 {
ErrorBLZDecompressionFailed,
ErrorBadINIHeader,
ErrorINITooManyKIPs,
+ ErrorIntegrityVerificationNotImplemented,
+ ErrorIntegrityVerificationFailed,
};
std::string GetResultStatusString(ResultStatus status);
@@ -172,6 +173,13 @@ public:
virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0;
/**
+ * Try to verify the integrity of the file.
+ */
+ virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
+ return ResultStatus::ErrorIntegrityVerificationNotImplemented;
+ }
+
+ /**
* Get the code (typically .code section) of the application
*
* @param[out] buffer Reference to buffer to store data
@@ -276,16 +284,6 @@ public:
}
/**
- * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS)
- * data. Needed for BKTR patching.
- *
- * @return IVFC offset for RomFS.
- */
- virtual u64 ReadRomFSIVFCOffset() const {
- return 0;
- }
-
- /**
* Get the title of the application
*
* @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) {
return nca_loader->ReadRomFS(dir);
}
-u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
- return nca_loader->ReadRomFSIVFCOffset();
-}
-
ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
return nca_loader->ReadProgramId(out_program_id);
}
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:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
- u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadProgramId(u64& out_program_id) override;
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 @@
#include <utility>
+#include "common/hex_util.h"
+#include "common/scope_exit.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs_factory.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h"
+#include "mbedtls/sha256.h"
namespace Loader {
@@ -43,9 +48,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
return {ResultStatus::ErrorNCANotProgram, {}};
}
- const auto exefs = nca->GetExeFS();
+ auto exefs = nca->GetExeFS();
if (exefs == nullptr) {
- return {ResultStatus::ErrorNoExeFS, {}};
+ LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update");
+
+ // This NCA may be a sparse base of an installed title.
+ // Try to fetch the ExeFS from the installed update.
+ const auto& installed = system.GetContentProvider();
+ const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()),
+ FileSys::ContentRecordType::Program);
+
+ if (update_nca) {
+ exefs = update_nca->GetExeFS();
+ }
+
+ if (exefs == nullptr) {
+ return {ResultStatus::ErrorNoExeFS, {}};
+ }
}
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
@@ -64,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
return load_result;
}
+ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
+ using namespace Common::Literals;
+
+ constexpr size_t NcaFileNameWithHashLength = 36;
+ constexpr size_t NcaFileNameHashLength = 32;
+ constexpr size_t NcaSha256HashLength = 32;
+ constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2;
+
+ // Get the file name.
+ const auto name = file->GetName();
+
+ // We won't try to verify meta NCAs.
+ if (name.ends_with(".cnmt.nca")) {
+ return ResultStatus::Success;
+ }
+
+ // Check if we can verify this file. NCAs should be named after their hashes.
+ if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) {
+ LOG_WARNING(Loader, "Unable to validate NCA with name {}", name);
+ return ResultStatus::ErrorIntegrityVerificationNotImplemented;
+ }
+
+ // Get the expected truncated hash of the NCA.
+ const auto input_hash =
+ Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false);
+
+ // Declare buffer to read into.
+ std::vector<u8> buffer(4_MiB);
+
+ // Initialize sha256 verification context.
+ mbedtls_sha256_context ctx;
+ mbedtls_sha256_init(&ctx);
+ mbedtls_sha256_starts_ret(&ctx, 0);
+
+ // Ensure we maintain a clean state on exit.
+ SCOPE_EXIT({ mbedtls_sha256_free(&ctx); });
+
+ // Declare counters.
+ const size_t total_size = file->GetSize();
+ size_t processed_size = 0;
+
+ // Begin iterating the file.
+ while (processed_size < total_size) {
+ // Refill the buffer.
+ const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size);
+ const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size);
+
+ // Update the hash function with the buffer contents.
+ mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size);
+
+ // Update counters.
+ processed_size += read_size;
+
+ // Call the progress function.
+ if (!progress_callback(processed_size, total_size)) {
+ return ResultStatus::ErrorIntegrityVerificationFailed;
+ }
+ }
+
+ // Finalize context and compute the output hash.
+ std::array<u8, NcaSha256HashLength> output_hash;
+ mbedtls_sha256_finish_ret(&ctx, output_hash.data());
+
+ // Compare to expected.
+ if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) {
+ LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name);
+ return ResultStatus::ErrorIntegrityVerificationFailed;
+ }
+
+ // File verified.
+ return ResultStatus::Success;
+}
+
ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
if (nca == nullptr) {
return ResultStatus::ErrorNotInitialized;
@@ -77,14 +169,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
return ResultStatus::Success;
}
-u64 AppLoader_NCA::ReadRomFSIVFCOffset() const {
- if (nca == nullptr) {
- return 0;
- }
-
- return nca->GetBaseIVFCOffset();
-}
-
ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) {
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:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
+ ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
+
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
- u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadProgramId(u64& out_program_id) override;
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)
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
// Setup the process code layout
- if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size())
+ if (process
+ .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
.IsError()) {
return false;
}
// Load codeset for current process
codeset.memory = std::move(program_image);
- process.LoadModule(std::move(codeset), process.GetPageTable().GetCodeRegionStart());
+ process.LoadModule(std::move(codeset), process.GetEntryPoint());
return true;
}
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::
}
// Apply patches if necessary
- if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
+ const auto name = nso_file.GetName();
+ if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
program_image.size());
- pi_header = pm->PatchNSO(pi_header, nso_file.GetName());
+ pi_header = pm->PatchNSO(pi_header, name);
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
}
@@ -167,7 +168,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::S
modules.clear();
// Load module
- const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart());
+ const VAddr base_address = GetInteger(process.GetEntryPoint());
if (!LoadModule(process, system, *file, base_address, true, true)) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
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_,
}
if (nsp->IsExtractedType()) {
- secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
+ secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(
+ nsp->GetExeFS(), false, file->GetName() == "hbl.nsp");
} else {
const auto control_nca =
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
@@ -117,12 +118,44 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
return result;
}
-ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
- return secondary_loader->ReadRomFS(out_file);
+ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
+ // Extracted-type NSPs can't be verified.
+ if (nsp->IsExtractedType()) {
+ return ResultStatus::ErrorIntegrityVerificationNotImplemented;
+ }
+
+ // Get list of all NCAs.
+ const auto ncas = nsp->GetNCAsCollapsed();
+
+ size_t total_size = 0;
+ size_t processed_size = 0;
+
+ // Loop over NCAs, collecting the total size to verify.
+ for (const auto& nca : ncas) {
+ total_size += nca->GetBaseFile()->GetSize();
+ }
+
+ // Loop over NCAs again, verifying each.
+ for (const auto& nca : ncas) {
+ AppLoader_NCA loader_nca(nca->GetBaseFile());
+
+ const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) {
+ return progress_callback(processed_size + nca_processed_size, total_size);
+ };
+
+ const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback);
+ if (verification_result != ResultStatus::Success) {
+ return verification_result;
+ }
+
+ processed_size += nca->GetBaseFile()->GetSize();
+ }
+
+ return ResultStatus::Success;
}
-u64 AppLoader_NSP::ReadRomFSIVFCOffset() const {
- return secondary_loader->ReadRomFSIVFCOffset();
+ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
+ return secondary_loader->ReadRomFS(out_file);
}
ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 003cc345c..7ce436c67 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -45,8 +45,9 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
+ ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
+
ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override;
- u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
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
return result;
}
-ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
- return nca_loader->ReadRomFS(out_file);
+ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
+ // Verify secure partition, as it is the only thing we can process.
+ auto secure_partition = xci->GetSecurePartitionNSP();
+
+ // Get list of all NCAs.
+ const auto ncas = secure_partition->GetNCAsCollapsed();
+
+ size_t total_size = 0;
+ size_t processed_size = 0;
+
+ // Loop over NCAs, collecting the total size to verify.
+ for (const auto& nca : ncas) {
+ total_size += nca->GetBaseFile()->GetSize();
+ }
+
+ // Loop over NCAs again, verifying each.
+ for (const auto& nca : ncas) {
+ AppLoader_NCA loader_nca(nca->GetBaseFile());
+
+ const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) {
+ return progress_callback(processed_size + nca_processed_size, total_size);
+ };
+
+ const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback);
+ if (verification_result != ResultStatus::Success) {
+ return verification_result;
+ }
+
+ processed_size += nca->GetBaseFile()->GetSize();
+ }
+
+ return ResultStatus::Success;
}
-u64 AppLoader_XCI::ReadRomFSIVFCOffset() const {
- return nca_loader->ReadRomFSIVFCOffset();
+ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
+ return nca_loader->ReadRomFS(out_file);
}
ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 2affb6c6e..b02e136d3 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -45,8 +45,9 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
+ ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
+
ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override;
- u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 513bc4edb..fa5273402 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -24,6 +24,16 @@
namespace Core::Memory {
+namespace {
+
+bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr,
+ const std::size_t size) {
+ const Common::ProcessAddress max_addr = 1ULL << table.GetAddressSpaceBits();
+ return addr + size >= addr && addr + size <= max_addr;
+}
+
+} // namespace
+
// Implementation class used to keep the specifics of the memory subsystem hidden
// from outside classes. This also allows modification to the internals of the memory
// subsystem without needing to rebuild all files that make use of the memory interface.
@@ -191,6 +201,11 @@ struct Memory::Impl {
std::size_t page_offset = addr & YUZU_PAGEMASK;
bool user_accessible = true;
+ if (!AddressSpaceContains(page_table, addr, size)) [[unlikely]] {
+ on_unmapped(size, addr);
+ return false;
+ }
+
while (remaining_size) {
const std::size_t copy_amount =
std::min(static_cast<std::size_t>(YUZU_PAGESIZE) - page_offset, remaining_size);
@@ -421,7 +436,7 @@ struct Memory::Impl {
}
void MarkRegionDebug(u64 vaddr, u64 size, bool debug) {
- if (vaddr == 0) {
+ if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) {
return;
}
@@ -478,7 +493,7 @@ struct Memory::Impl {
}
void RasterizerMarkRegionCached(u64 vaddr, u64 size, bool cached) {
- if (vaddr == 0) {
+ if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) {
return;
}
@@ -615,7 +630,7 @@ struct Memory::Impl {
// AARCH64 masks the upper 16 bit of all memory accesses
vaddr = vaddr & 0xffffffffffffULL;
- if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
+ if (!AddressSpaceContains(*current_page_table, vaddr, 1)) [[unlikely]] {
on_unmapped();
return nullptr;
}
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 {
public:
GuestMemory() = delete;
- explicit GuestMemory(M& memory_, u64 addr_, std::size_t size_,
+ explicit GuestMemory(M& memory, u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr)
- : memory{memory_}, addr{addr_}, size{size_} {
+ : m_memory{memory}, m_addr{addr}, m_size{size} {
static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write);
if constexpr (FLAGS & GuestMemoryFlags::Read) {
Read(addr, size, backup);
@@ -521,89 +521,97 @@ public:
~GuestMemory() = default;
T* data() noexcept {
- return data_span.data();
+ return m_data_span.data();
}
const T* data() const noexcept {
- return data_span.data();
+ return m_data_span.data();
+ }
+
+ size_t size() const noexcept {
+ return m_size;
+ }
+
+ size_t size_bytes() const noexcept {
+ return this->size() * sizeof(T);
}
[[nodiscard]] T* begin() noexcept {
- return data();
+ return this->data();
}
[[nodiscard]] const T* begin() const noexcept {
- return data();
+ return this->data();
}
[[nodiscard]] T* end() noexcept {
- return data() + size;
+ return this->data() + this->size();
}
[[nodiscard]] const T* end() const noexcept {
- return data() + size;
+ return this->data() + this->size();
}
T& operator[](size_t index) noexcept {
- return data_span[index];
+ return m_data_span[index];
}
const T& operator[](size_t index) const noexcept {
- return data_span[index];
+ return m_data_span[index];
}
- void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept {
- addr = addr_;
- size = size_;
- addr_changed = true;
+ void SetAddressAndSize(u64 addr, std::size_t size) noexcept {
+ m_addr = addr;
+ m_size = size;
+ m_addr_changed = true;
}
- std::span<T> Read(u64 addr_, std::size_t size_,
+ std::span<T> Read(u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr) noexcept {
- addr = addr_;
- size = size_;
- if (size == 0) {
- is_data_copy = true;
+ m_addr = addr;
+ m_size = size;
+ if (m_size == 0) {
+ m_is_data_copy = true;
return {};
}
- if (TrySetSpan()) {
+ if (this->TrySetSpan()) {
if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- memory.FlushRegion(addr, size * sizeof(T));
+ m_memory.FlushRegion(m_addr, this->size_bytes());
}
} else {
if (backup) {
- backup->resize_destructive(size);
- data_span = *backup;
+ backup->resize_destructive(this->size());
+ m_data_span = *backup;
} else {
- data_copy.resize(size);
- data_span = std::span(data_copy);
+ m_data_copy.resize(this->size());
+ m_data_span = std::span(m_data_copy);
}
- is_data_copy = true;
- span_valid = true;
+ m_is_data_copy = true;
+ m_span_valid = true;
if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- memory.ReadBlock(addr, data_span.data(), size * sizeof(T));
+ m_memory.ReadBlock(m_addr, this->data(), this->size_bytes());
} else {
- memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T));
+ m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes());
}
}
- return data_span;
+ return m_data_span;
}
void Write(std::span<T> write_data) noexcept {
if constexpr (FLAGS & GuestMemoryFlags::Cached) {
- memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T));
+ m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes());
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- memory.WriteBlock(addr, write_data.data(), size * sizeof(T));
+ m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes());
} else {
- memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T));
+ m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes());
}
}
bool TrySetSpan() noexcept {
- if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) {
- data_span = {reinterpret_cast<T*>(ptr), size};
- span_valid = true;
+ if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) {
+ m_data_span = {reinterpret_cast<T*>(ptr), this->size()};
+ m_span_valid = true;
return true;
}
return false;
@@ -611,36 +619,36 @@ public:
protected:
bool IsDataCopy() const noexcept {
- return is_data_copy;
+ return m_is_data_copy;
}
bool AddressChanged() const noexcept {
- return addr_changed;
+ return m_addr_changed;
}
- M& memory;
- u64 addr;
- size_t size;
- std::span<T> data_span{};
- std::vector<T> data_copy;
- bool span_valid{false};
- bool is_data_copy{false};
- bool addr_changed{false};
+ M& m_memory;
+ u64 m_addr{};
+ size_t m_size{};
+ std::span<T> m_data_span{};
+ std::vector<T> m_data_copy{};
+ bool m_span_valid{false};
+ bool m_is_data_copy{false};
+ bool m_addr_changed{false};
};
template <typename M, typename T, GuestMemoryFlags FLAGS>
class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> {
public:
GuestMemoryScoped() = delete;
- explicit GuestMemoryScoped(M& memory_, u64 addr_, std::size_t size_,
+ explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr)
- : GuestMemory<M, T, FLAGS>(memory_, addr_, size_, backup) {
+ : GuestMemory<M, T, FLAGS>(memory, addr, size, backup) {
if constexpr (!(FLAGS & GuestMemoryFlags::Read)) {
if (!this->TrySetSpan()) {
if (backup) {
- this->data_span = *backup;
- this->span_valid = true;
- this->is_data_copy = true;
+ this->m_data_span = *backup;
+ this->m_span_valid = true;
+ this->m_is_data_copy = true;
}
}
}
@@ -648,24 +656,21 @@ public:
~GuestMemoryScoped() {
if constexpr (FLAGS & GuestMemoryFlags::Write) {
- if (this->size == 0) [[unlikely]] {
+ if (this->size() == 0) [[unlikely]] {
return;
}
if (this->AddressChanged() || this->IsDataCopy()) {
- ASSERT(this->span_valid);
+ ASSERT(this->m_span_valid);
if constexpr (FLAGS & GuestMemoryFlags::Cached) {
- this->memory.WriteBlockCached(this->addr, this->data_span.data(),
- this->size * sizeof(T));
+ this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes());
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- this->memory.WriteBlock(this->addr, this->data_span.data(),
- this->size * sizeof(T));
+ this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes());
} else {
- this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(),
- this->size * sizeof(T));
+ this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes());
}
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- this->memory.InvalidateRegion(this->addr, this->size * sizeof(T));
+ this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes());
}
}
}
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 {
return {};
}
- const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10));
+ const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
value;
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) {
arm.SaveContext(context);
return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
- GetInteger(process->GetPageTable().GetCodeRegionStart()),
- context.sp, context.pc, context.pstate, context.cpu_registers);
+ GetInteger(process->GetEntryPoint()), context.sp, context.pc,
+ context.pstate, context.cpu_registers);
}
json GetBacktraceData(Core::System& system) {
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 7a2f3c90a..c26179e03 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -14,6 +14,7 @@
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h"
@@ -61,13 +62,13 @@ static const char* TranslateRenderer(Settings::RendererBackend backend) {
return "Unknown";
}
-static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) {
+static const char* TranslateGPUAccuracyLevel(Settings::GpuAccuracy backend) {
switch (backend) {
- case Settings::GPUAccuracy::Normal:
+ case Settings::GpuAccuracy::Normal:
return "Normal";
- case Settings::GPUAccuracy::High:
+ case Settings::GpuAccuracy::High:
return "High";
- case Settings::GPUAccuracy::Extreme:
+ case Settings::GpuAccuracy::Extreme:
return "Extreme";
}
return "Unknown";
@@ -77,9 +78,9 @@ static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) {
switch (backend) {
case Settings::NvdecEmulation::Off:
return "Off";
- case Settings::NvdecEmulation::CPU:
+ case Settings::NvdecEmulation::Cpu:
return "CPU";
- case Settings::NvdecEmulation::GPU:
+ case Settings::NvdecEmulation::Gpu:
return "GPU";
}
return "Unknown";
@@ -91,14 +92,26 @@ static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) {
return "Immediate";
case Settings::VSyncMode::Mailbox:
return "Mailbox";
- case Settings::VSyncMode::FIFO:
+ case Settings::VSyncMode::Fifo:
return "FIFO";
- case Settings::VSyncMode::FIFORelaxed:
+ case Settings::VSyncMode::FifoRelaxed:
return "FIFO Relaxed";
}
return "Unknown";
}
+static constexpr const char* TranslateASTCDecodeMode(Settings::AstcDecodeMode mode) {
+ switch (mode) {
+ case Settings::AstcDecodeMode::Cpu:
+ return "CPU";
+ case Settings::AstcDecodeMode::Gpu:
+ return "GPU";
+ case Settings::AstcDecodeMode::CpuAsynchronous:
+ return "CPU Asynchronous";
+ }
+ return "Unknown";
+}
+
u64 GetTelemetryId() {
u64 telemetry_id{};
const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id";
@@ -240,7 +253,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
// Log user configuration information
constexpr auto field_type = Telemetry::FieldType::UserConfig;
- AddField(field_type, "Audio_SinkId", Settings::values.sink_id.GetValue());
+ AddField(field_type, "Audio_SinkId",
+ Settings::CanonicalizeEnum(Settings::values.sink_id.GetValue()));
AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
AddField(field_type, "Renderer_Backend",
TranslateRenderer(Settings::values.renderer_backend.GetValue()));
@@ -254,14 +268,15 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
Settings::values.use_asynchronous_gpu_emulation.GetValue());
AddField(field_type, "Renderer_NvdecEmulation",
TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue()));
- AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue());
+ AddField(field_type, "Renderer_AccelerateASTC",
+ TranslateASTCDecodeMode(Settings::values.accelerate_astc.GetValue()));
AddField(field_type, "Renderer_UseVsync",
TranslateVSyncMode(Settings::values.vsync_mode.GetValue()));
AddField(field_type, "Renderer_ShaderBackend",
static_cast<u32>(Settings::values.shader_backend.GetValue()));
AddField(field_type, "Renderer_UseAsynchronousShaders",
Settings::values.use_asynchronous_shaders.GetValue());
- AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue());
+ AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode());
}
bool TelemetrySession::SubmitTestcase() {
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp
new file mode 100644
index 000000000..947fa6cb3
--- /dev/null
+++ b/src/core/tools/renderdoc.cpp
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <renderdoc_app.h>
+
+#include "common/assert.h"
+#include "common/dynamic_library.h"
+#include "core/tools/renderdoc.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+namespace Tools {
+
+RenderdocAPI::RenderdocAPI() {
+#ifdef WIN32
+ if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
+ const auto RENDERDOC_GetAPI =
+ reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI"));
+ const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
+ ASSERT(ret == 1);
+ }
+#else
+#ifdef ANDROID
+ static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
+#else
+ static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
+#endif
+ if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
+ const auto RENDERDOC_GetAPI =
+ reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
+ const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
+ ASSERT(ret == 1);
+ }
+#endif
+}
+
+RenderdocAPI::~RenderdocAPI() = default;
+
+void RenderdocAPI::ToggleCapture() {
+ if (!rdoc_api) [[unlikely]] {
+ return;
+ }
+ if (!is_capturing) {
+ rdoc_api->StartFrameCapture(NULL, NULL);
+ } else {
+ rdoc_api->EndFrameCapture(NULL, NULL);
+ }
+ is_capturing = !is_capturing;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+struct RENDERDOC_API_1_6_0;
+
+namespace Tools {
+
+class RenderdocAPI {
+public:
+ explicit RenderdocAPI();
+ ~RenderdocAPI();
+
+ void ToggleCapture();
+
+private:
+ RENDERDOC_API_1_6_0* rdoc_api{};
+ bool is_capturing{false};
+};
+
+} // namespace Tools