summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt33
-rw-r--r--src/android/app/build.gradle.kts26
-rw-r--r--src/android/app/src/main/AndroidManifest.xml10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt45
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt60
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt22
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt52
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt31
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt103
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt (renamed from src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt)6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt20
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt61
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt154
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt325
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt247
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt37
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt55
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt232
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt90
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt57
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt282
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt166
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt541
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt58
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt255
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt196
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt235
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt192
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt153
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt67
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt85
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt64
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt327
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt49
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt88
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt33
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt35
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt48
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt2
-rw-r--r--src/android/app/src/main/jni/config.cpp38
-rw-r--r--src/android/app/src/main/jni/config.h24
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp14
-rw-r--r--src/android/app/src/main/jni/id_cache.h2
-rw-r--r--src/android/app/src/main/jni/native.cpp113
-rw-r--r--src/android/app/src/main/jni/native_config.cpp237
-rw-r--r--src/android/app/src/main/jni/uisettings.cpp10
-rw-r--r--src/android/app/src/main/jni/uisettings.h29
-rw-r--r--src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml16
-rw-r--r--src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_settings_fragment_in.xml16
-rw-r--r--src/android/app/src/main/res/anim/anim_settings_fragment_out.xml10
-rw-r--r--src/android/app/src/main/res/animator/menu_slide_in_from_start.xml20
-rw-r--r--src/android/app/src/main/res/animator/menu_slide_out_to_start.xml21
-rw-r--r--src/android/app/src/main/res/drawable/shortcut.xml11
-rw-r--r--src/android/app/src/main/res/layout-w600dp/fragment_setup.xml60
-rw-r--r--src/android/app/src/main/res/layout-w600dp/page_setup.xml69
-rw-r--r--src/android/app/src/main/res/layout/activity_settings.xml52
-rw-r--r--src/android/app/src/main/res/layout/card_home_option.xml17
-rw-r--r--src/android/app/src/main/res/layout/dialog_slider.xml13
-rw-r--r--src/android/app/src/main/res/layout/fragment_emulation.xml83
-rw-r--r--src/android/app/src/main/res/layout/fragment_settings.xml39
-rw-r--r--src/android/app/src/main/res/layout/fragment_settings_search.xml120
-rw-r--r--src/android/app/src/main/res/layout/fragment_setup.xml62
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting.xml62
-rw-r--r--src/android/app/src/main/res/layout/page_setup.xml30
-rw-r--r--src/android/app/src/main/res/menu/menu_settings.xml11
-rw-r--r--src/android/app/src/main/res/navigation/emulation_navigation.xml21
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml21
-rw-r--r--src/android/app/src/main/res/navigation/settings_navigation.xml32
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml1
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml1
-rw-r--r--src/android/app/src/main/res/values/arrays.xml10
-rw-r--r--src/android/app/src/main/res/values/dimens.xml1
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
-rw-r--r--src/audio_core/CMakeLists.txt31
-rw-r--r--src/audio_core/adsp/adsp.cpp27
-rw-r--r--src/audio_core/adsp/adsp.h53
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp218
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h109
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/command_buffer.h23
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp (renamed from src/audio_core/renderer/adsp/command_list_processor.cpp)29
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/command_list_processor.h (renamed from src/audio_core/renderer/adsp/command_list_processor.h)21
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.cpp107
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.h38
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.cpp269
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.h92
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp111
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h39
-rw-r--r--src/audio_core/adsp/apps/opus/shared_memory.h17
-rw-r--r--src/audio_core/adsp/mailbox.h60
-rw-r--r--src/audio_core/audio_core.cpp4
-rw-r--r--src/audio_core/audio_core.h6
-rw-r--r--src/audio_core/audio_event.cpp1
-rw-r--r--src/audio_core/audio_in_manager.cpp2
-rw-r--r--src/audio_core/audio_in_manager.h4
-rw-r--r--src/audio_core/audio_out_manager.cpp2
-rw-r--r--src/audio_core/audio_out_manager.h3
-rw-r--r--src/audio_core/audio_render_manager.cpp4
-rw-r--r--src/audio_core/audio_render_manager.h4
-rw-r--r--src/audio_core/common/audio_renderer_parameter.h6
-rw-r--r--src/audio_core/opus/decoder.cpp179
-rw-r--r--src/audio_core/opus/decoder.h53
-rw-r--r--src/audio_core/opus/decoder_manager.cpp102
-rw-r--r--src/audio_core/opus/decoder_manager.h38
-rw-r--r--src/audio_core/opus/hardware_opus.cpp241
-rw-r--r--src/audio_core/opus/hardware_opus.h45
-rw-r--r--src/audio_core/opus/parameters.h54
-rw-r--r--src/audio_core/renderer/adsp/adsp.cpp117
-rw-r--r--src/audio_core/renderer/adsp/adsp.h171
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp225
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h204
-rw-r--r--src/audio_core/renderer/adsp/command_buffer.h21
-rw-r--r--src/audio_core/renderer/audio_device.cpp4
-rw-r--r--src/audio_core/renderer/audio_device.h4
-rw-r--r--src/audio_core/renderer/audio_renderer.cpp4
-rw-r--r--src/audio_core/renderer/audio_renderer.h6
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp4
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.h8
-rw-r--r--src/audio_core/renderer/behavior/info_updater.cpp4
-rw-r--r--src/audio_core/renderer/behavior/info_updater.h4
-rw-r--r--src/audio_core/renderer/command/command_buffer.cpp4
-rw-r--r--src/audio_core/renderer/command/command_buffer.h4
-rw-r--r--src/audio_core/renderer/command/command_generator.cpp4
-rw-r--r--src/audio_core/renderer/command/command_generator.h4
-rw-r--r--src/audio_core/renderer/command/command_list_header.h4
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.cpp8
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.h4
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.cpp24
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.h23
-rw-r--r--src/audio_core/renderer/command/data_source/decode.cpp110
-rw-r--r--src/audio_core/renderer/command/data_source/decode.h4
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.cpp28
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.h23
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.cpp28
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.h23
-rw-r--r--src/audio_core/renderer/command/effect/aux_.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/aux_.h13
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.cpp14
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.h13
-rw-r--r--src/audio_core/renderer/command/effect/capture.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/capture.h13
-rw-r--r--src/audio_core/renderer/command/effect/compressor.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/compressor.h13
-rw-r--r--src/audio_core/renderer/command/effect/delay.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/delay.h13
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.h13
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.cpp22
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.h19
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp14
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h13
-rw-r--r--src/audio_core/renderer/command/effect/reverb.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/reverb.h13
-rw-r--r--src/audio_core/renderer/command/icommand.h17
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.cpp14
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.h13
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.cpp14
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.h13
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp14
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.h13
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.cpp14
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.h13
-rw-r--r--src/audio_core/renderer/command/mix/mix.cpp12
-rw-r--r--src/audio_core/renderer/command/mix/mix.h13
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.cpp13
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.h13
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp13
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.h13
-rw-r--r--src/audio_core/renderer/command/mix/volume.cpp12
-rw-r--r--src/audio_core/renderer/command/mix/volume.h13
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.cpp13
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.h13
-rw-r--r--src/audio_core/renderer/command/performance/performance.cpp20
-rw-r--r--src/audio_core/renderer/command/performance/performance.h13
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp14
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h13
-rw-r--r--src/audio_core/renderer/command/resample/resample.cpp4
-rw-r--r--src/audio_core/renderer/command/resample/resample.h4
-rw-r--r--src/audio_core/renderer/command/resample/upsample.cpp12
-rw-r--r--src/audio_core/renderer/command/resample/upsample.h13
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.cpp14
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.h13
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp12
-rw-r--r--src/audio_core/renderer/command/sink/device.h13
-rw-r--r--src/audio_core/renderer/effect/aux_.cpp4
-rw-r--r--src/audio_core/renderer/effect/aux_.h4
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.cpp4
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.h4
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.cpp4
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.h4
-rw-r--r--src/audio_core/renderer/effect/capture.cpp4
-rw-r--r--src/audio_core/renderer/effect/capture.h4
-rw-r--r--src/audio_core/renderer/effect/compressor.cpp4
-rw-r--r--src/audio_core/renderer/effect/compressor.h4
-rw-r--r--src/audio_core/renderer/effect/delay.cpp4
-rw-r--r--src/audio_core/renderer/effect/delay.h4
-rw-r--r--src/audio_core/renderer/effect/effect_context.cpp4
-rw-r--r--src/audio_core/renderer/effect/effect_context.h4
-rw-r--r--src/audio_core/renderer/effect/effect_info_base.h4
-rw-r--r--src/audio_core/renderer/effect/effect_reset.h4
-rw-r--r--src/audio_core/renderer/effect/effect_result_state.h4
-rw-r--r--src/audio_core/renderer/effect/i3dl2.cpp4
-rw-r--r--src/audio_core/renderer/effect/i3dl2.h4
-rw-r--r--src/audio_core/renderer/effect/light_limiter.cpp4
-rw-r--r--src/audio_core/renderer/effect/light_limiter.h4
-rw-r--r--src/audio_core/renderer/effect/reverb.cpp4
-rw-r--r--src/audio_core/renderer/effect/reverb.h4
-rw-r--r--src/audio_core/renderer/memory/address_info.h4
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.cpp4
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.h4
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.cpp4
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.h4
-rw-r--r--src/audio_core/renderer/mix/mix_context.cpp4
-rw-r--r--src/audio_core/renderer/mix/mix_context.h4
-rw-r--r--src/audio_core/renderer/mix/mix_info.cpp4
-rw-r--r--src/audio_core/renderer/mix/mix_info.h4
-rw-r--r--src/audio_core/renderer/nodes/bit_array.h4
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.cpp4
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.h4
-rw-r--r--src/audio_core/renderer/nodes/node_states.cpp4
-rw-r--r--src/audio_core/renderer/nodes/node_states.h4
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.cpp4
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.h4
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.cpp4
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.h4
-rw-r--r--src/audio_core/renderer/performance/performance_detail.h4
-rw-r--r--src/audio_core/renderer/performance/performance_entry.h4
-rw-r--r--src/audio_core/renderer/performance/performance_entry_addresses.h4
-rw-r--r--src/audio_core/renderer/performance/performance_frame_header.h4
-rw-r--r--src/audio_core/renderer/performance/performance_manager.cpp4
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h4
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.cpp4
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.h4
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.cpp4
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.h4
-rw-r--r--src/audio_core/renderer/sink/sink_context.cpp4
-rw-r--r--src/audio_core/renderer/sink/sink_context.h4
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.cpp4
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.h4
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.cpp4
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.h4
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.cpp4
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.h4
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.cpp4
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.h4
-rw-r--r--src/audio_core/renderer/system.cpp54
-rw-r--r--src/audio_core/renderer/system.h16
-rw-r--r--src/audio_core/renderer/system_manager.cpp30
-rw-r--r--src/audio_core/renderer/system_manager.h21
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_info.h4
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.cpp4
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.h4
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_state.h4
-rw-r--r--src/audio_core/renderer/voice/voice_channel_resource.h4
-rw-r--r--src/audio_core/renderer/voice/voice_context.cpp4
-rw-r--r--src/audio_core/renderer/voice/voice_context.h4
-rw-r--r--src/audio_core/renderer/voice/voice_info.cpp4
-rw-r--r--src/audio_core/renderer/voice/voice_info.h4
-rw-r--r--src/audio_core/renderer/voice/voice_state.h4
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp47
-rw-r--r--src/audio_core/sink/cubeb_sink.h7
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp40
-rw-r--r--src/audio_core/sink/sdl2_sink.h7
-rw-r--r--src/audio_core/sink/sink_details.cpp45
-rw-r--r--src/common/CMakeLists.txt11
-rw-r--r--src/common/alignment.h37
-rw-r--r--src/common/bounded_threadsafe_queue.h4
-rw-r--r--src/common/fs/fs.cpp15
-rw-r--r--src/common/fs/path_util.cpp6
-rw-r--r--src/common/logging/filter.cpp2
-rw-r--r--src/common/logging/types.h2
-rw-r--r--src/common/lz4_compression.cpp6
-rw-r--r--src/common/lz4_compression.h2
-rw-r--r--src/common/polyfill_thread.h20
-rw-r--r--src/common/settings.cpp17
-rw-r--r--src/common/settings.h13
-rw-r--r--src/common/settings_common.cpp3
-rw-r--r--src/common/settings_common.h13
-rw-r--r--src/common/settings_enums.h18
-rw-r--r--src/common/settings_setting.h43
-rw-r--r--src/common/swap.h5
-rw-r--r--src/core/CMakeLists.txt70
-rw-r--r--src/core/core.cpp44
-rw-r--r--src/core/core.h20
-rw-r--r--src/core/crypto/key_manager.cpp234
-rw-r--r--src/core/crypto/key_manager.h59
-rw-r--r--src/core/debugger/gdbstub.cpp17
-rw-r--r--src/core/file_sys/card_image.cpp54
-rw-r--r--src/core/file_sys/card_image.h1
-rw-r--r--src/core/file_sys/content_archive.cpp586
-rw-r--r--src/core/file_sys/content_archive.h66
-rw-r--r--src/core/file_sys/errors.h70
-rw-r--r--src/core/file_sys/fssystem/fs_i_storage.h58
-rw-r--r--src/core/file_sys/fssystem/fs_types.h46
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp251
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h114
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp129
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h43
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp112
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_xts_storage.h42
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h146
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp204
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h21
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree.cpp598
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree.h416
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h170
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h110
-rw-r--r--src/core/file_sys/fssystem/fssystem_compressed_storage.h963
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_common.h43
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_configuration.cpp36
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_configuration.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp65
-rw-r--r--src/core/file_sys/fssystem/fssystem_crypto_configuration.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp127
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h164
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp80
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h44
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.cpp119
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.h294
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp30
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h42
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp91
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h65
-rw-r--r--src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h61
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp1351
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h364
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_header.cpp20
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_header.h338
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_reader.cpp531
-rw-r--r--src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp61
-rw-r--r--src/core/file_sys/fssystem/fssystem_pooled_buffer.h95
-rw-r--r--src/core/file_sys/fssystem/fssystem_sparse_storage.cpp39
-rw-r--r--src/core/file_sys/fssystem/fssystem_sparse_storage.h72
-rw-r--r--src/core/file_sys/fssystem/fssystem_switch_storage.h80
-rw-r--r--src/core/file_sys/fssystem/fssystem_utility.cpp27
-rw-r--r--src/core/file_sys/fssystem/fssystem_utility.h12
-rw-r--r--src/core/file_sys/ips_layer.cpp4
-rw-r--r--src/core/file_sys/nca_metadata.cpp4
-rw-r--r--src/core/file_sys/nca_metadata.h1
-rw-r--r--src/core/file_sys/nca_patch.cpp217
-rw-r--r--src/core/file_sys/nca_patch.h145
-rw-r--r--src/core/file_sys/patch_manager.cpp52
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/file_sys/registered_cache.cpp73
-rw-r--r--src/core/file_sys/registered_cache.h5
-rw-r--r--src/core/file_sys/romfs_factory.cpp9
-rw-r--r--src/core/file_sys/romfs_factory.h11
-rw-r--r--src/core/file_sys/submission_package.cpp39
-rw-r--r--src/core/file_sys/submission_package.h1
-rw-r--r--src/core/frontend/applets/controller.cpp4
-rw-r--r--src/core/frontend/framebuffer_layout.cpp3
-rw-r--r--src/core/hid/hid_core.cpp8
-rw-r--r--src/core/hid/hid_core.h7
-rw-r--r--src/core/hid/hid_types.h26
-rw-r--r--src/core/hle/kernel/k_capabilities.cpp1
-rw-r--r--src/core/hle/kernel/k_hardware_timer.h9
-rw-r--r--src/core/hle/kernel/k_process.cpp30
-rw-r--r--src/core/hle/kernel/k_process.h15
-rw-r--r--src/core/hle/kernel/k_resource_limit.cpp11
-rw-r--r--src/core/hle/kernel/k_resource_limit.h3
-rw-r--r--src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h2
-rw-r--r--src/core/hle/kernel/kernel.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_address_arbiter.cpp3
-rw-r--r--src/core/hle/kernel/svc/svc_condition_variable.cpp3
-rw-r--r--src/core/hle/kernel/svc/svc_debug_string.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_exception.cpp6
-rw-r--r--src/core/hle/kernel/svc/svc_ipc.cpp20
-rw-r--r--src/core/hle/kernel/svc/svc_resource_limit.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_synchronization.cpp16
-rw-r--r--src/core/hle/kernel/svc/svc_thread.cpp29
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/am/am.cpp84
-rw-r--r--src/core/hle/service/am/am.h2
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp11
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit_types.h3
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.cpp2
-rw-r--r--src/core/hle/service/apm/apm_controller.cpp4
-rw-r--r--src/core/hle/service/audio/audin_u.cpp4
-rw-r--r--src/core/hle/service/audio/audout_u.cpp2
-rw-r--r--src/core/hle/service/audio/audren_u.cpp2
-rw-r--r--src/core/hle/service/audio/audren_u.h2
-rw-r--r--src/core/hle/service/audio/errors.h12
-rw-r--r--src/core/hle/service/audio/hwopus.cpp697
-rw-r--r--src/core/hle/service/audio/hwopus.h21
-rw-r--r--src/core/hle/service/es/es.cpp10
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp16
-rw-r--r--src/core/hle/service/filesystem/filesystem.h3
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp68
-rw-r--r--src/core/hle/service/hid/controllers/npad.h22
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h16
-rw-r--r--src/core/hle/service/hid/hid.cpp95
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/mii/mii.cpp139
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp717
-rw-r--r--src/core/hle/service/mii/mii_manager.h42
-rw-r--r--src/core/hle/service/mii/mii_result.h20
-rw-r--r--src/core/hle/service/mii/mii_types.h691
-rw-r--r--src/core/hle/service/mii/mii_util.h59
-rw-r--r--src/core/hle/service/mii/raw_data.h26
-rw-r--r--src/core/hle/service/mii/types.h553
-rw-r--r--src/core/hle/service/mii/types/char_info.cpp482
-rw-r--r--src/core/hle/service/mii/types/char_info.h137
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp601
-rw-r--r--src/core/hle/service/mii/types/core_data.h216
-rw-r--r--src/core/hle/service/mii/types/raw_data.cpp (renamed from src/core/hle/service/mii/raw_data.cpp)1400
-rw-r--r--src/core/hle/service/mii/types/raw_data.h73
-rw-r--r--src/core/hle/service/mii/types/store_data.cpp643
-rw-r--r--src/core/hle/service/mii/types/store_data.h145
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp241
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.h160
-rw-r--r--src/core/hle/service/nfc/common/device.cpp39
-rw-r--r--src/core/hle/service/nfp/nfp_types.h6
-rw-r--r--src/core/hle/service/ngc/ngc.cpp150
-rw-r--r--src/core/hle/service/ngc/ngc.h (renamed from src/core/hle/service/ngct/ngct.h)4
-rw-r--r--src/core/hle/service/ngct/ngct.cpp62
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.cpp4
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp1
-rw-r--r--src/core/hle/service/nvnflinger/window.h1
-rw-r--r--src/core/hle/service/service.cpp4
-rw-r--r--src/core/hle/service/service.h4
-rw-r--r--src/core/hle/service/sockets/bsd.cpp7
-rw-r--r--src/core/hle/service/sockets/bsd.h3
-rw-r--r--src/core/hle/service/sockets/nsd.cpp11
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp12
-rw-r--r--src/core/hle/service/sockets/sockets.h2
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp4
-rw-r--r--src/core/hle/service/ssl/ssl.cpp10
-rw-r--r--src/core/hle/service/ssl/ssl_backend_openssl.cpp3
-rw-r--r--src/core/hle/service/ssl/ssl_backend_schannel.cpp28
-rw-r--r--src/core/hle/service/ssl/ssl_backend_securetransport.cpp2
-rw-r--r--src/core/hle/service/vi/vi.cpp2
-rw-r--r--src/core/internal_network/network.cpp132
-rw-r--r--src/core/internal_network/network.h5
-rw-r--r--src/core/internal_network/network_interface.cpp9
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp10
-rw-r--r--src/core/loader/deconstructed_rom_directory.h4
-rw-r--r--src/core/loader/kip.cpp5
-rw-r--r--src/core/loader/loader.cpp8
-rw-r--r--src/core/loader/loader.h22
-rw-r--r--src/core/loader/nax.cpp4
-rw-r--r--src/core/loader/nax.h1
-rw-r--r--src/core/loader/nca.cpp104
-rw-r--r--src/core/loader/nca.h3
-rw-r--r--src/core/loader/nro.cpp5
-rw-r--r--src/core/loader/nso.cpp7
-rw-r--r--src/core/loader/nsp.cpp43
-rw-r--r--src/core/loader/nsp.h3
-rw-r--r--src/core/loader/xci.cpp38
-rw-r--r--src/core/loader/xci.h3
-rw-r--r--src/core/memory.h125
-rw-r--r--src/core/memory/cheat_engine.cpp2
-rw-r--r--src/core/reporter.cpp4
-rw-r--r--src/core/telemetry_session.cpp3
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
-rw-r--r--src/dedicated_room/yuzu_room.cpp6
-rw-r--r--src/input_common/CMakeLists.txt2
-rw-r--r--src/input_common/input_poller.cpp10
-rw-r--r--src/network/room.cpp2
-rw-r--r--src/shader_recompiler/CMakeLists.txt2
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_image.cpp23
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_image.cpp8
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp39
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp10
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h1
-rw-r--r--src/shader_recompiler/frontend/ir/modifiers.h1
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp2
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp6
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp3
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp4
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp1
-rw-r--r--src/shader_recompiler/ir_opt/constant_propagation_pass.cpp227
-rw-r--r--src/tests/common/ring_buffer.cpp2
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h10
-rw-r--r--src/video_core/dma_pusher.cpp34
-rw-r--r--src/video_core/dma_pusher.h5
-rw-r--r--src/video_core/engines/engine_interface.h8
-rw-r--r--src/video_core/engines/engine_upload.h8
-rw-r--r--src/video_core/engines/kepler_compute.cpp20
-rw-r--r--src/video_core/engines/kepler_compute.h17
-rw-r--r--src/video_core/engines/maxwell_3d.cpp6
-rw-r--r--src/video_core/engines/maxwell_dma.cpp1
-rw-r--r--src/video_core/engines/puller.cpp15
-rw-r--r--src/video_core/host1x/codecs/codec.cpp3
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt1
-rw-r--r--src/video_core/host_shaders/astc_decoder.comp988
-rw-r--r--src/video_core/host_shaders/vulkan_depthstencil_clear.frag12
-rw-r--r--src/video_core/macro/macro.cpp24
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.h2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp18
-rw-r--r--src/video_core/renderer_opengl/util_shaders.cpp1
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp79
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h19
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp2
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp18
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp11
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp29
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp51
-rw-r--r--src/video_core/texture_cache/texture_cache.h15
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h6
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.cpp27
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.h2
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp187
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h9
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.cpp16
-rw-r--r--src/video_core/vulkan_common/vulkan_library.cpp16
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp1
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h5
-rw-r--r--src/web_service/verify_user_jwt.cpp1
-rw-r--r--src/yuzu/CMakeLists.txt12
-rw-r--r--src/yuzu/applets/qt_amiibo_settings.cpp3
-rw-r--r--src/yuzu/applets/qt_controller.cpp14
-rw-r--r--src/yuzu/bootmanager.cpp16
-rw-r--r--src/yuzu/configuration/config.cpp12
-rw-r--r--src/yuzu/configuration/config.h3
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui99
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp8
-rw-r--r--src/yuzu/configuration/configure_dialog.h2
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp72
-rw-r--r--src/yuzu/configuration/configure_graphics.h17
-rw-r--r--src/yuzu/configuration/configure_input.cpp14
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp10
-rw-r--r--src/yuzu/configuration/configure_system.cpp5
-rw-r--r--src/yuzu/configuration/configure_ui.cpp95
-rw-r--r--src/yuzu/configuration/configure_ui.h8
-rw-r--r--src/yuzu/configuration/configure_ui.ui37
-rw-r--r--src/yuzu/configuration/shared_translation.cpp5
-rw-r--r--src/yuzu/configuration/shared_widget.cpp234
-rw-r--r--src/yuzu/configuration/shared_widget.h27
-rw-r--r--src/yuzu/game_list.cpp17
-rw-r--r--src/yuzu/game_list.h4
-rw-r--r--src/yuzu/game_list_worker.cpp23
-rw-r--r--src/yuzu/hotkeys.h4
-rw-r--r--src/yuzu/main.cpp450
-rw-r--r--src/yuzu/main.h17
-rw-r--r--src/yuzu/main.ui6
-rw-r--r--src/yuzu/uisettings.cpp16
-rw-r--r--src/yuzu/uisettings.h5
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp6
601 files changed, 23288 insertions, 9342 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2da983cad..d7f68618c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -24,7 +24,7 @@ if (MSVC)
24 # Ensure that projects build with Unicode support. 24 # Ensure that projects build with Unicode support.
25 add_definitions(-DUNICODE -D_UNICODE) 25 add_definitions(-DUNICODE -D_UNICODE)
26 26
27 # /W3 - Level 3 warnings 27 # /W4 - Level 4 warnings
28 # /MP - Multi-threaded compilation 28 # /MP - Multi-threaded compilation
29 # /Zi - Output debugging information 29 # /Zi - Output debugging information
30 # /Zm - Specifies the precompiled header memory allocation limit 30 # /Zm - Specifies the precompiled header memory allocation limit
@@ -61,7 +61,7 @@ if (MSVC)
61 /external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers 61 /external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers
62 62
63 # Warnings 63 # Warnings
64 /W3 64 /W4
65 /WX 65 /WX
66 66
67 /we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled 67 /we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled
@@ -84,12 +84,17 @@ if (MSVC)
84 84
85 /wd4100 # 'identifier': unreferenced formal parameter 85 /wd4100 # 'identifier': unreferenced formal parameter
86 /wd4324 # 'struct_name': structure was padded due to __declspec(align()) 86 /wd4324 # 'struct_name': structure was padded due to __declspec(align())
87 /wd4201 # nonstandard extension used : nameless struct/union
88 /wd4702 # unreachable code (when used with LTO)
87 ) 89 )
88 90
89 if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) 91 if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS)
90 # when caching, we need to use /Z7 to downgrade debug info to use an older but more cacheable format 92 # when caching, we need to use /Z7 to downgrade debug info to use an older but more cacheable format
91 # Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21 93 # Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21
92 add_compile_options(/Z7) 94 add_compile_options(/Z7)
95 # Avoid D9025 warning
96 string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
97 string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
93 else() 98 else()
94 add_compile_options(/Zi) 99 add_compile_options(/Zi)
95 endif() 100 endif()
@@ -105,6 +110,8 @@ if (MSVC)
105 set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) 110 set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
106else() 111else()
107 add_compile_options( 112 add_compile_options(
113 -fwrapv
114
108 -Werror=all 115 -Werror=all
109 -Werror=extra 116 -Werror=extra
110 -Werror=missing-declarations 117 -Werror=missing-declarations
@@ -114,19 +121,21 @@ else()
114 -Wno-attributes 121 -Wno-attributes
115 -Wno-invalid-offsetof 122 -Wno-invalid-offsetof
116 -Wno-unused-parameter 123 -Wno-unused-parameter
117
118 $<$<CXX_COMPILER_ID:Clang>:-Wno-braced-scalar-init>
119 $<$<CXX_COMPILER_ID:Clang>:-Wno-unused-private-field>
120 $<$<CXX_COMPILER_ID:Clang>:-Werror=shadow-uncaptured-local>
121 $<$<CXX_COMPILER_ID:Clang>:-Werror=implicit-fallthrough>
122 $<$<CXX_COMPILER_ID:Clang>:-Werror=type-limits>
123 $<$<CXX_COMPILER_ID:AppleClang>:-Wno-braced-scalar-init>
124 $<$<CXX_COMPILER_ID:AppleClang>:-Wno-unused-private-field>
125 ) 124 )
126 125
126 if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang
127 add_compile_options(
128 -Wno-braced-scalar-init
129 -Wno-unused-private-field
130 -Wno-nullability-completeness
131 -Werror=shadow-uncaptured-local
132 -Werror=implicit-fallthrough
133 -Werror=type-limits
134 )
135 endif()
136
127 if (ARCHITECTURE_x86_64) 137 if (ARCHITECTURE_x86_64)
128 add_compile_options("-mcx16") 138 add_compile_options("-mcx16")
129 add_compile_options("-fwrapv")
130 endif() 139 endif()
131 140
132 if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) 141 if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
@@ -134,7 +143,7 @@ else()
134 endif() 143 endif()
135 144
136 # GCC bugs 145 # GCC bugs
137 if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 146 if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
138 # These diagnostics would be great if they worked, but are just completely broken 147 # These diagnostics would be great if they worked, but are just completely broken
139 # and produce bogus errors on external libraries like fmt. 148 # and produce bogus errors on external libraries like fmt.
140 add_compile_options( 149 add_compile_options(
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 9a47e2bd8..431f899b3 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -77,13 +77,30 @@ android {
77 buildConfigField("String", "BRANCH", "\"${getBranch()}\"") 77 buildConfigField("String", "BRANCH", "\"${getBranch()}\"")
78 } 78 }
79 79
80 val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
81 if (keystoreFile != null) {
82 signingConfigs {
83 create("release") {
84 storeFile = file(keystoreFile)
85 storePassword = System.getenv("ANDROID_KEYSTORE_PASS")
86 keyAlias = System.getenv("ANDROID_KEY_ALIAS")
87 keyPassword = System.getenv("ANDROID_KEYSTORE_PASS")
88 }
89 }
90 }
91
80 // Define build types, which are orthogonal to product flavors. 92 // Define build types, which are orthogonal to product flavors.
81 buildTypes { 93 buildTypes {
82 94
83 // Signed by release key, allowing for upload to Play Store. 95 // Signed by release key, allowing for upload to Play Store.
84 release { 96 release {
97 signingConfig = if (keystoreFile != null) {
98 signingConfigs.getByName("release")
99 } else {
100 signingConfigs.getByName("debug")
101 }
102
85 resValue("string", "app_name_suffixed", "yuzu") 103 resValue("string", "app_name_suffixed", "yuzu")
86 signingConfig = signingConfigs.getByName("debug")
87 isMinifyEnabled = true 104 isMinifyEnabled = true
88 isDebuggable = false 105 isDebuggable = false
89 proguardFiles( 106 proguardFiles(
@@ -95,6 +112,7 @@ android {
95 // builds a release build that doesn't need signing 112 // builds a release build that doesn't need signing
96 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build. 113 // Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
97 register("relWithDebInfo") { 114 register("relWithDebInfo") {
115 isDefault = true
98 resValue("string", "app_name_suffixed", "yuzu Debug Release") 116 resValue("string", "app_name_suffixed", "yuzu Debug Release")
99 signingConfig = signingConfigs.getByName("debug") 117 signingConfig = signingConfigs.getByName("debug")
100 isMinifyEnabled = true 118 isMinifyEnabled = true
@@ -122,6 +140,7 @@ android {
122 flavorDimensions.add("version") 140 flavorDimensions.add("version")
123 productFlavors { 141 productFlavors {
124 create("mainline") { 142 create("mainline") {
143 isDefault = true
125 dimension = "version" 144 dimension = "version"
126 buildConfigField("Boolean", "PREMIUM", "false") 145 buildConfigField("Boolean", "PREMIUM", "false")
127 } 146 }
@@ -160,6 +179,11 @@ android {
160 } 179 }
161} 180}
162 181
182tasks.create<Delete>("ktlintReset") {
183 delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
184}
185
186tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
163tasks.getByPath("preBuild").dependsOn("ktlintCheck") 187tasks.getByPath("preBuild").dependsOn("ktlintCheck")
164 188
165ktlint { 189ktlint {
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 6184f3eb6..832c08e15 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -25,6 +25,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
25 android:hasFragileUserData="false" 25 android:hasFragileUserData="false"
26 android:supportsRtl="true" 26 android:supportsRtl="true"
27 android:isGame="true" 27 android:isGame="true"
28 android:appCategory="game"
28 android:localeConfig="@xml/locales_config" 29 android:localeConfig="@xml/locales_config"
29 android:banner="@drawable/tv_banner" 30 android:banner="@drawable/tv_banner"
30 android:extractNativeLibs="true" 31 android:extractNativeLibs="true"
@@ -55,7 +56,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
55 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" 56 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
56 android:theme="@style/Theme.Yuzu.Main" 57 android:theme="@style/Theme.Yuzu.Main"
57 android:launchMode="singleTop" 58 android:launchMode="singleTop"
58 android:screenOrientation="userLandscape"
59 android:supportsPictureInPicture="true" 59 android:supportsPictureInPicture="true"
60 android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" 60 android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
61 android:exported="true"> 61 android:exported="true">
@@ -66,6 +66,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
66 <data android:mimeType="application/octet-stream" /> 66 <data android:mimeType="application/octet-stream" />
67 </intent-filter> 67 </intent-filter>
68 68
69 <intent-filter>
70 <action android:name="android.intent.action.VIEW" />
71 <category android:name="android.intent.category.DEFAULT" />
72 <data
73 android:mimeType="application/octet-stream"
74 android:scheme="content"/>
75 </intent-filter>
76
69 <meta-data 77 <meta-data
70 android:name="android.nfc.action.TECH_DISCOVERED" 78 android:name="android.nfc.action.TECH_DISCOVERED"
71 android:resource="@xml/nfc_tech_filter" /> 79 android:resource="@xml/nfc_tech_filter" />
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 9c32e044c..21f67f32a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -22,9 +22,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil.exists
22import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize 22import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
23import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory 23import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
24import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri 24import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
25import org.yuzu.yuzu_emu.utils.Log.error 25import org.yuzu.yuzu_emu.utils.Log
26import org.yuzu.yuzu_emu.utils.Log.verbose
27import org.yuzu.yuzu_emu.utils.Log.warning
28import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 26import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
29 27
30/** 28/**
@@ -219,10 +217,6 @@ object NativeLibrary {
219 217
220 external fun reloadSettings() 218 external fun reloadSettings()
221 219
222 external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
223
224 external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
225
226 external fun initGameIni(gameID: String?) 220 external fun initGameIni(gameID: String?)
227 221
228 /** 222 /**
@@ -314,21 +308,6 @@ object NativeLibrary {
314 external fun isPaused(): Boolean 308 external fun isPaused(): Boolean
315 309
316 /** 310 /**
317 * Mutes emulation sound
318 */
319 external fun muteAudio(): Boolean
320
321 /**
322 * Unmutes emulation sound
323 */
324 external fun unmuteAudio(): Boolean
325
326 /**
327 * Returns true if emulation audio is muted.
328 */
329 external fun isMuted(): Boolean
330
331 /**
332 * Returns the performance stats for the current game 311 * Returns the performance stats for the current game
333 */ 312 */
334 external fun getPerfStats(): DoubleArray 313 external fun getPerfStats(): DoubleArray
@@ -413,14 +392,17 @@ object NativeLibrary {
413 details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } 392 details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
414 ) 393 )
415 } 394 }
395
416 CoreError.ErrorSavestate -> { 396 CoreError.ErrorSavestate -> {
417 title = emulationActivity.getString(R.string.save_load_error) 397 title = emulationActivity.getString(R.string.save_load_error)
418 message = details 398 message = details
419 } 399 }
400
420 CoreError.ErrorUnknown -> { 401 CoreError.ErrorUnknown -> {
421 title = emulationActivity.getString(R.string.fatal_error) 402 title = emulationActivity.getString(R.string.fatal_error)
422 message = emulationActivity.getString(R.string.fatal_error_message) 403 message = emulationActivity.getString(R.string.fatal_error_message)
423 } 404 }
405
424 else -> { 406 else -> {
425 return true 407 return true
426 } 408 }
@@ -454,6 +436,7 @@ object NativeLibrary {
454 captionId = R.string.loader_error_video_core 436 captionId = R.string.loader_error_video_core
455 descriptionId = R.string.loader_error_video_core_description 437 descriptionId = R.string.loader_error_video_core_description
456 } 438 }
439
457 else -> { 440 else -> {
458 captionId = R.string.loader_error_encrypted 441 captionId = R.string.loader_error_encrypted
459 descriptionId = R.string.loader_error_encrypted_roms_description 442 descriptionId = R.string.loader_error_encrypted_roms_description
@@ -465,7 +448,7 @@ object NativeLibrary {
465 448
466 val emulationActivity = sEmulationActivity.get() 449 val emulationActivity = sEmulationActivity.get()
467 if (emulationActivity == null) { 450 if (emulationActivity == null) {
468 warning("[NativeLibrary] EmulationActivity is null, can't exit.") 451 Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.")
469 return 452 return
470 } 453 }
471 454
@@ -490,15 +473,27 @@ object NativeLibrary {
490 } 473 }
491 474
492 fun setEmulationActivity(emulationActivity: EmulationActivity?) { 475 fun setEmulationActivity(emulationActivity: EmulationActivity?) {
493 verbose("[NativeLibrary] Registering EmulationActivity.") 476 Log.verbose("[NativeLibrary] Registering EmulationActivity.")
494 sEmulationActivity = WeakReference(emulationActivity) 477 sEmulationActivity = WeakReference(emulationActivity)
495 } 478 }
496 479
497 fun clearEmulationActivity() { 480 fun clearEmulationActivity() {
498 verbose("[NativeLibrary] Unregistering EmulationActivity.") 481 Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
499 sEmulationActivity.clear() 482 sEmulationActivity.clear()
500 } 483 }
501 484
485 @Keep
486 @JvmStatic
487 fun onEmulationStarted() {
488 sEmulationActivity.get()!!.onEmulationStarted()
489 }
490
491 @Keep
492 @JvmStatic
493 fun onEmulationStopped(status: Int) {
494 sEmulationActivity.get()!!.onEmulationStopped(status)
495 }
496
502 /** 497 /**
503 * Logs the Yuzu version, Android version and, CPU. 498 * Logs the Yuzu version, Android version and, CPU.
504 */ 499 */
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 04ab6a220..9561748cb 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -46,7 +46,7 @@ class YuzuApplication : Application() {
46 super.onCreate() 46 super.onCreate()
47 application = this 47 application = this
48 documentsTree = DocumentsTree() 48 documentsTree = DocumentsTree()
49 DirectoryInitialization.start(applicationContext) 49 DirectoryInitialization.start()
50 GpuDriverHelper.initializeDriverParameters(applicationContext) 50 GpuDriverHelper.initializeDriverParameters(applicationContext)
51 NativeLibrary.logDeviceInfo() 51 NativeLibrary.logDeviceInfo()
52 52
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 7461fb093..d4ae39661 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -42,7 +42,7 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
42import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting 42import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
43import org.yuzu.yuzu_emu.features.settings.model.IntSetting 43import org.yuzu.yuzu_emu.features.settings.model.IntSetting
44import org.yuzu.yuzu_emu.features.settings.model.Settings 44import org.yuzu.yuzu_emu.features.settings.model.Settings
45import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel 45import org.yuzu.yuzu_emu.model.EmulationViewModel
46import org.yuzu.yuzu_emu.model.Game 46import org.yuzu.yuzu_emu.model.Game
47import org.yuzu.yuzu_emu.utils.ControllerMappingHelper 47import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
48import org.yuzu.yuzu_emu.utils.ForegroundService 48import org.yuzu.yuzu_emu.utils.ForegroundService
@@ -72,18 +72,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
72 private val actionMute = "ACTION_EMULATOR_MUTE" 72 private val actionMute = "ACTION_EMULATOR_MUTE"
73 private val actionUnmute = "ACTION_EMULATOR_UNMUTE" 73 private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
74 74
75 private val settingsViewModel: SettingsViewModel by viewModels() 75 private val emulationViewModel: EmulationViewModel by viewModels()
76 76
77 override fun onDestroy() { 77 override fun onDestroy() {
78 stopForegroundService(this) 78 stopForegroundService(this)
79 emulationViewModel.clear()
79 super.onDestroy() 80 super.onDestroy()
80 } 81 }
81 82
82 override fun onCreate(savedInstanceState: Bundle?) { 83 override fun onCreate(savedInstanceState: Bundle?) {
83 ThemeHelper.setTheme(this) 84 ThemeHelper.setTheme(this)
84 85
85 settingsViewModel.settings.loadSettings()
86
87 super.onCreate(savedInstanceState) 86 super.onCreate(savedInstanceState)
88 87
89 binding = ActivityEmulationBinding.inflate(layoutInflater) 88 binding = ActivityEmulationBinding.inflate(layoutInflater)
@@ -91,9 +90,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
91 90
92 val navHostFragment = 91 val navHostFragment =
93 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment 92 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
94 val navController = navHostFragment.navController 93 navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
95 navController
96 .setGraph(R.navigation.emulation_navigation, intent.extras)
97 94
98 isActivityRecreated = savedInstanceState != null 95 isActivityRecreated = savedInstanceState != null
99 96
@@ -335,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
335 pictureInPictureActions.add(pauseRemoteAction) 332 pictureInPictureActions.add(pauseRemoteAction)
336 } 333 }
337 334
338 if (NativeLibrary.isMuted()) { 335 if (BooleanSetting.AUDIO_MUTED.boolean) {
339 val unmuteIcon = Icon.createWithResource( 336 val unmuteIcon = Icon.createWithResource(
340 this@EmulationActivity, 337 this@EmulationActivity,
341 R.drawable.ic_pip_unmute 338 R.drawable.ic_pip_unmute
@@ -392,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
392 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() 389 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
393 } 390 }
394 if (intent.action == actionUnmute) { 391 if (intent.action == actionUnmute) {
395 if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() 392 if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
396 } else if (intent.action == actionMute) { 393 } else if (intent.action == actionMute) {
397 if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio() 394 if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
398 } 395 }
399 buildPictureInPictureParams() 396 buildPictureInPictureParams()
400 } 397 }
@@ -420,7 +417,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
420 } catch (ignored: Exception) { 417 } catch (ignored: Exception) {
421 } 418 }
422 // Always resume audio, since there is no UI button 419 // Always resume audio, since there is no UI button
423 if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() 420 if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
421 }
422 }
423
424 fun onEmulationStarted() {
425 emulationViewModel.setEmulationStarted(true)
426 }
427
428 fun onEmulationStopped(status: Int) {
429 if (status == 0) {
430 finish()
424 } 431 }
425 } 432 }
426 433
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index e91277d35..f9f88a1d2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -3,8 +3,9 @@
3 3
4package org.yuzu.yuzu_emu.adapters 4package org.yuzu.yuzu_emu.adapters
5 5
6import android.content.Intent
6import android.graphics.Bitmap 7import android.graphics.Bitmap
7import android.graphics.BitmapFactory 8import android.graphics.drawable.LayerDrawable
8import android.net.Uri 9import android.net.Uri
9import android.text.TextUtils 10import android.text.TextUtils
10import android.view.LayoutInflater 11import android.view.LayoutInflater
@@ -13,25 +14,29 @@ import android.view.ViewGroup
13import android.widget.ImageView 14import android.widget.ImageView
14import android.widget.Toast 15import android.widget.Toast
15import androidx.appcompat.app.AppCompatActivity 16import androidx.appcompat.app.AppCompatActivity
17import androidx.core.content.pm.ShortcutInfoCompat
18import androidx.core.content.pm.ShortcutManagerCompat
19import androidx.core.content.res.ResourcesCompat
20import androidx.core.graphics.drawable.IconCompat
21import androidx.core.graphics.drawable.toBitmap
22import androidx.core.graphics.drawable.toDrawable
16import androidx.documentfile.provider.DocumentFile 23import androidx.documentfile.provider.DocumentFile
17import androidx.lifecycle.ViewModelProvider 24import androidx.lifecycle.ViewModelProvider
18import androidx.lifecycle.lifecycleScope
19import androidx.navigation.findNavController 25import androidx.navigation.findNavController
20import androidx.preference.PreferenceManager 26import androidx.preference.PreferenceManager
21import androidx.recyclerview.widget.AsyncDifferConfig 27import androidx.recyclerview.widget.AsyncDifferConfig
22import androidx.recyclerview.widget.DiffUtil 28import androidx.recyclerview.widget.DiffUtil
23import androidx.recyclerview.widget.ListAdapter 29import androidx.recyclerview.widget.ListAdapter
24import androidx.recyclerview.widget.RecyclerView 30import androidx.recyclerview.widget.RecyclerView
25import coil.load
26import kotlinx.coroutines.launch
27import org.yuzu.yuzu_emu.HomeNavigationDirections 31import org.yuzu.yuzu_emu.HomeNavigationDirections
28import org.yuzu.yuzu_emu.NativeLibrary
29import org.yuzu.yuzu_emu.R 32import org.yuzu.yuzu_emu.R
30import org.yuzu.yuzu_emu.YuzuApplication 33import org.yuzu.yuzu_emu.YuzuApplication
34import org.yuzu.yuzu_emu.activities.EmulationActivity
31import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder 35import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
32import org.yuzu.yuzu_emu.databinding.CardGameBinding 36import org.yuzu.yuzu_emu.databinding.CardGameBinding
33import org.yuzu.yuzu_emu.model.Game 37import org.yuzu.yuzu_emu.model.Game
34import org.yuzu.yuzu_emu.model.GamesViewModel 38import org.yuzu.yuzu_emu.model.GamesViewModel
39import org.yuzu.yuzu_emu.utils.GameIconUtils
35 40
36class GameAdapter(private val activity: AppCompatActivity) : 41class GameAdapter(private val activity: AppCompatActivity) :
37 ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), 42 ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
@@ -82,6 +87,34 @@ class GameAdapter(private val activity: AppCompatActivity) :
82 ) 87 )
83 .apply() 88 .apply()
84 89
90 val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
91 action = Intent.ACTION_VIEW
92 data = Uri.parse(holder.game.path)
93 }
94
95 val layerDrawable = ResourcesCompat.getDrawable(
96 YuzuApplication.appContext.resources,
97 R.drawable.shortcut,
98 null
99 ) as LayerDrawable
100 layerDrawable.setDrawableByLayerId(
101 R.id.shortcut_foreground,
102 GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
103 )
104 val inset = YuzuApplication.appContext.resources
105 .getDimensionPixelSize(R.dimen.icon_inset)
106 layerDrawable.setLayerInset(1, inset, inset, inset, inset)
107 val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
108 .setShortLabel(holder.game.title)
109 .setIcon(
110 IconCompat.createWithAdaptiveBitmap(
111 layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
112 )
113 )
114 .setIntent(openIntent)
115 .build()
116 ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
117
85 val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) 118 val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
86 view.findNavController().navigate(action) 119 view.findNavController().navigate(action)
87 } 120 }
@@ -98,12 +131,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
98 this.game = game 131 this.game = game
99 132
100 binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP 133 binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
101 activity.lifecycleScope.launch { 134 GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
102 val bitmap = decodeGameIcon(game.path)
103 binding.imageGameScreen.load(bitmap) {
104 error(R.drawable.default_icon)
105 }
106 }
107 135
108 binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ") 136 binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
109 137
@@ -126,14 +154,4 @@ class GameAdapter(private val activity: AppCompatActivity) :
126 return oldItem == newItem 154 return oldItem == newItem
127 } 155 }
128 } 156 }
129
130 private fun decodeGameIcon(uri: String): Bitmap? {
131 val data = NativeLibrary.getIcon(uri)
132 return BitmapFactory.decodeByteArray(
133 data,
134 0,
135 data.size,
136 BitmapFactory.Options()
137 )
138 }
139} 157}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index aadc445f9..1675627a1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -3,19 +3,29 @@
3 3
4package org.yuzu.yuzu_emu.adapters 4package org.yuzu.yuzu_emu.adapters
5 5
6import android.text.TextUtils
6import android.view.LayoutInflater 7import android.view.LayoutInflater
7import android.view.View 8import android.view.View
8import android.view.ViewGroup 9import android.view.ViewGroup
9import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
10import androidx.core.content.ContextCompat 11import androidx.core.content.ContextCompat
11import androidx.core.content.res.ResourcesCompat 12import androidx.core.content.res.ResourcesCompat
13import androidx.lifecycle.Lifecycle
14import androidx.lifecycle.LifecycleOwner
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
12import androidx.recyclerview.widget.RecyclerView 17import androidx.recyclerview.widget.RecyclerView
18import kotlinx.coroutines.launch
13import org.yuzu.yuzu_emu.R 19import org.yuzu.yuzu_emu.R
14import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding 20import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
15import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 21import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
16import org.yuzu.yuzu_emu.model.HomeSetting 22import org.yuzu.yuzu_emu.model.HomeSetting
17 23
18class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) : 24class HomeSettingAdapter(
25 private val activity: AppCompatActivity,
26 private val viewLifecycle: LifecycleOwner,
27 var options: List<HomeSetting>
28) :
19 RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), 29 RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
20 View.OnClickListener { 30 View.OnClickListener {
21 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { 31 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
@@ -39,8 +49,8 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
39 holder.option.onClick.invoke() 49 holder.option.onClick.invoke()
40 } else { 50 } else {
41 MessageDialogFragment.newInstance( 51 MessageDialogFragment.newInstance(
42 holder.option.disabledTitleId, 52 titleId = holder.option.disabledTitleId,
43 holder.option.disabledMessageId 53 descriptionId = holder.option.disabledMessageId
44 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) 54 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
45 } 55 }
46 } 56 }
@@ -79,6 +89,26 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
79 binding.optionDescription.alpha = 0.5f 89 binding.optionDescription.alpha = 0.5f
80 binding.optionIcon.alpha = 0.5f 90 binding.optionIcon.alpha = 0.5f
81 } 91 }
92
93 viewLifecycle.lifecycleScope.launch {
94 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
95 option.details.collect { updateOptionDetails(it) }
96 }
97 }
98 binding.optionDetail.postDelayed(
99 {
100 binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
101 binding.optionDetail.isSelected = true
102 },
103 3000
104 )
105 }
106
107 private fun updateOptionDetails(detailString: String) {
108 if (detailString.isNotEmpty()) {
109 binding.optionDetail.text = detailString
110 binding.optionDetail.visibility = View.VISIBLE
111 }
82 } 112 }
83 } 113 }
84} 114}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
index 7006651d0..bc6ff1364 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
@@ -49,6 +49,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List
49 val context = YuzuApplication.appContext 49 val context = YuzuApplication.appContext
50 binding.textSettingName.text = context.getString(license.titleId) 50 binding.textSettingName.text = context.getString(license.titleId)
51 binding.textSettingDescription.text = context.getString(license.descriptionId) 51 binding.textSettingDescription.text = context.getString(license.descriptionId)
52 binding.textSettingValue.visibility = View.GONE
52 } 53 }
53 } 54 }
54} 55}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
index 481ddd5a5..6b46d359e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
@@ -5,13 +5,19 @@ package org.yuzu.yuzu_emu.adapters
5 5
6import android.text.Html 6import android.text.Html
7import android.view.LayoutInflater 7import android.view.LayoutInflater
8import android.view.View
8import android.view.ViewGroup 9import android.view.ViewGroup
9import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
10import androidx.core.content.res.ResourcesCompat 11import androidx.core.content.res.ResourcesCompat
12import androidx.lifecycle.ViewModelProvider
11import androidx.recyclerview.widget.RecyclerView 13import androidx.recyclerview.widget.RecyclerView
12import com.google.android.material.button.MaterialButton 14import com.google.android.material.button.MaterialButton
13import org.yuzu.yuzu_emu.databinding.PageSetupBinding 15import org.yuzu.yuzu_emu.databinding.PageSetupBinding
16import org.yuzu.yuzu_emu.model.HomeViewModel
17import org.yuzu.yuzu_emu.model.SetupCallback
14import org.yuzu.yuzu_emu.model.SetupPage 18import org.yuzu.yuzu_emu.model.SetupPage
19import org.yuzu.yuzu_emu.model.StepState
20import org.yuzu.yuzu_emu.utils.ViewUtils
15 21
16class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : 22class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
17 RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { 23 RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
@@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
26 holder.bind(pages[position]) 32 holder.bind(pages[position])
27 33
28 inner class SetupPageViewHolder(val binding: PageSetupBinding) : 34 inner class SetupPageViewHolder(val binding: PageSetupBinding) :
29 RecyclerView.ViewHolder(binding.root) { 35 RecyclerView.ViewHolder(binding.root), SetupCallback {
30 lateinit var page: SetupPage 36 lateinit var page: SetupPage
31 37
32 init { 38 init {
@@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
35 41
36 fun bind(page: SetupPage) { 42 fun bind(page: SetupPage) {
37 this.page = page 43 this.page = page
44
45 if (page.stepCompleted.invoke() == StepState.COMPLETE) {
46 binding.buttonAction.visibility = View.INVISIBLE
47 binding.textConfirmation.visibility = View.VISIBLE
48 }
49
38 binding.icon.setImageDrawable( 50 binding.icon.setImageDrawable(
39 ResourcesCompat.getDrawable( 51 ResourcesCompat.getDrawable(
40 activity.resources, 52 activity.resources,
@@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
62 MaterialButton.ICON_GRAVITY_END 74 MaterialButton.ICON_GRAVITY_END
63 } 75 }
64 setOnClickListener { 76 setOnClickListener {
65 page.buttonAction.invoke() 77 page.buttonAction.invoke(this@SetupPageViewHolder)
66 } 78 }
67 } 79 }
68 } 80 }
81
82 override fun onStepCompleted() {
83 ViewUtils.hideView(binding.buttonAction, 200)
84 ViewUtils.showView(binding.textConfirmation, 200)
85 ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true)
86 }
69 } 87 }
70} 88}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt
index a18efef19..6f4b5b13f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt
@@ -4,43 +4,43 @@
4package org.yuzu.yuzu_emu.disk_shader_cache 4package org.yuzu.yuzu_emu.disk_shader_cache
5 5
6import androidx.annotation.Keep 6import androidx.annotation.Keep
7import androidx.lifecycle.ViewModelProvider
7import org.yuzu.yuzu_emu.NativeLibrary 8import org.yuzu.yuzu_emu.NativeLibrary
8import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
9import org.yuzu.yuzu_emu.disk_shader_cache.ui.ShaderProgressDialogFragment 10import org.yuzu.yuzu_emu.activities.EmulationActivity
11import org.yuzu.yuzu_emu.model.EmulationViewModel
12import org.yuzu.yuzu_emu.utils.Log
10 13
11@Keep 14@Keep
12object DiskShaderCacheProgress { 15object DiskShaderCacheProgress {
13 val finishLock = Object() 16 private lateinit var emulationViewModel: EmulationViewModel
14 private lateinit var fragment: ShaderProgressDialogFragment
15 17
16 private fun prepareDialog() { 18 private fun prepareViewModel() {
17 val emulationActivity = NativeLibrary.sEmulationActivity.get()!! 19 emulationViewModel =
18 emulationActivity.runOnUiThread { 20 ViewModelProvider(
19 fragment = ShaderProgressDialogFragment.newInstance( 21 NativeLibrary.sEmulationActivity.get() as EmulationActivity
20 emulationActivity.getString(R.string.loading), 22 )[EmulationViewModel::class.java]
21 emulationActivity.getString(R.string.preparing_shaders)
22 )
23 fragment.show(
24 emulationActivity.supportFragmentManager,
25 ShaderProgressDialogFragment.TAG
26 )
27 }
28 synchronized(finishLock) { finishLock.wait() }
29 } 23 }
30 24
31 @JvmStatic 25 @JvmStatic
32 fun loadProgress(stage: Int, progress: Int, max: Int) { 26 fun loadProgress(stage: Int, progress: Int, max: Int) {
33 val emulationActivity = NativeLibrary.sEmulationActivity.get() 27 val emulationActivity = NativeLibrary.sEmulationActivity.get()
34 ?: error("[DiskShaderCacheProgress] EmulationActivity not present") 28 if (emulationActivity == null) {
35 29 Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
36 when (LoadCallbackStage.values()[stage]) { 30 return
37 LoadCallbackStage.Prepare -> prepareDialog() 31 }
38 LoadCallbackStage.Build -> fragment.onUpdateProgress( 32
39 emulationActivity.getString(R.string.building_shaders), 33 emulationActivity.runOnUiThread {
40 progress, 34 when (LoadCallbackStage.values()[stage]) {
41 max 35 LoadCallbackStage.Prepare -> prepareViewModel()
42 ) 36 LoadCallbackStage.Build -> emulationViewModel.updateProgress(
43 LoadCallbackStage.Complete -> fragment.dismiss() 37 emulationActivity.getString(R.string.building_shaders),
38 progress,
39 max
40 )
41
42 LoadCallbackStage.Complete -> {}
43 }
44 } 44 }
45 } 45 }
46 46
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt
deleted file mode 100644
index bf6f0366d..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt
+++ /dev/null
@@ -1,31 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.disk_shader_cache
5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel
9
10class ShaderProgressViewModel : ViewModel() {
11 private val _progress = MutableLiveData(0)
12 val progress: LiveData<Int> get() = _progress
13
14 private val _max = MutableLiveData(0)
15 val max: LiveData<Int> get() = _max
16
17 private val _message = MutableLiveData("")
18 val message: LiveData<String> get() = _message
19
20 fun setProgress(progress: Int) {
21 _progress.postValue(progress)
22 }
23
24 fun setMax(max: Int) {
25 _max.postValue(max)
26 }
27
28 fun setMessage(msg: String) {
29 _message.postValue(msg)
30 }
31}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt
deleted file mode 100644
index 8a8e0a6e8..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt
+++ /dev/null
@@ -1,103 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.disk_shader_cache.ui
5
6import android.app.Dialog
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import androidx.appcompat.app.AlertDialog
12import androidx.fragment.app.DialogFragment
13import androidx.lifecycle.ViewModelProvider
14import com.google.android.material.dialog.MaterialAlertDialogBuilder
15import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
16import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress
17import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel
18
19class ShaderProgressDialogFragment : DialogFragment() {
20 private var _binding: DialogProgressBarBinding? = null
21 private val binding get() = _binding!!
22
23 private lateinit var alertDialog: AlertDialog
24
25 private lateinit var shaderProgressViewModel: ShaderProgressViewModel
26
27 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
28 _binding = DialogProgressBarBinding.inflate(layoutInflater)
29 shaderProgressViewModel =
30 ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java]
31
32 val title = requireArguments().getString(TITLE)
33 val message = requireArguments().getString(MESSAGE)
34
35 isCancelable = false
36 alertDialog = MaterialAlertDialogBuilder(requireActivity())
37 .setView(binding.root)
38 .setTitle(title)
39 .setMessage(message)
40 .create()
41 return alertDialog
42 }
43
44 override fun onCreateView(
45 inflater: LayoutInflater,
46 container: ViewGroup?,
47 savedInstanceState: Bundle?
48 ): View {
49 return binding.root
50 }
51
52 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
53 super.onViewCreated(view, savedInstanceState)
54 shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress ->
55 binding.progressBar.progress = progress
56 setUpdateText()
57 }
58 shaderProgressViewModel.max.observe(viewLifecycleOwner) { max ->
59 binding.progressBar.max = max
60 setUpdateText()
61 }
62 shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
63 alertDialog.setMessage(msg)
64 }
65 synchronized(DiskShaderCacheProgress.finishLock) {
66 DiskShaderCacheProgress.finishLock.notifyAll()
67 }
68 }
69
70 override fun onDestroyView() {
71 super.onDestroyView()
72 _binding = null
73 }
74
75 fun onUpdateProgress(msg: String, progress: Int, max: Int) {
76 shaderProgressViewModel.setProgress(progress)
77 shaderProgressViewModel.setMax(max)
78 shaderProgressViewModel.setMessage(msg)
79 }
80
81 private fun setUpdateText() {
82 binding.progressText.text = String.format(
83 "%d/%d",
84 shaderProgressViewModel.progress.value,
85 shaderProgressViewModel.max.value
86 )
87 }
88
89 companion object {
90 const val TAG = "ProgressDialogFragment"
91 const val TITLE = "title"
92 const val MESSAGE = "message"
93
94 fun newInstance(title: String, message: String): ShaderProgressDialogFragment {
95 val frag = ShaderProgressDialogFragment()
96 val args = Bundle()
97 args.putString(TITLE, title)
98 args.putString(MESSAGE, message)
99 frag.arguments = args
100 return frag
101 }
102 }
103}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
index a6e9833ee..aeda8d222 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
@@ -4,5 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6interface AbstractBooleanSetting : AbstractSetting { 6interface AbstractBooleanSetting : AbstractSetting {
7 var boolean: Boolean 7 val boolean: Boolean
8
9 fun setBoolean(value: Boolean)
8} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt
index bd9233d62..606519ad8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt
@@ -3,8 +3,8 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import androidx.lifecycle.ViewModel 6interface AbstractByteSetting : AbstractSetting {
7 val byte: Byte
7 8
8class SettingsViewModel : ViewModel() { 9 fun setByte(value: Byte)
9 val settings = Settings()
10} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
index 6fe4bc263..974925eed 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
@@ -4,5 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6interface AbstractFloatSetting : AbstractSetting { 6interface AbstractFloatSetting : AbstractSetting {
7 var float: Float 7 val float: Float
8
9 fun setFloat(value: Float)
8} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
index 892b7dcfe..89b285b10 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
@@ -4,5 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6interface AbstractIntSetting : AbstractSetting { 6interface AbstractIntSetting : AbstractSetting {
7 var int: Int 7 val int: Int
8
9 fun setInt(value: Int)
8} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
new file mode 100644
index 000000000..4873942db
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
@@ -0,0 +1,10 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractLongSetting : AbstractSetting {
7 val long: Long
8
9 fun setLong(value: Long)
10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
index 258580209..8b6d29fe5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
@@ -3,10 +3,22 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6interface AbstractSetting { 8interface AbstractSetting {
7 val key: String? 9 val key: String
8 val section: String? 10 val category: Settings.Category
9 val isRuntimeEditable: Boolean
10 val valueAsString: String
11 val defaultValue: Any 11 val defaultValue: Any
12 val androidDefault: Any?
13 get() = null
14 val valueAsString: String
15 get() = ""
16
17 val isRuntimeModifiable: Boolean
18 get() = NativeConfig.getIsRuntimeModifiable(key)
19
20 val pairedSettingKey: String
21 get() = NativeConfig.getPairedSettingKey(key)
22
23 fun reset()
12} 24}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
new file mode 100644
index 000000000..91407ccbb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
@@ -0,0 +1,10 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6interface AbstractShortSetting : AbstractSetting {
7 val short: Short
8
9 fun setShort(value: Short)
10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
index 0d02c5997..c8935cc48 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
@@ -4,5 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6interface AbstractStringSetting : AbstractSetting { 6interface AbstractStringSetting : AbstractSetting {
7 var string: String 7 val string: String
8
9 fun setString(value: String)
8} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index d41933766..8476ce867 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -3,41 +3,38 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6enum class BooleanSetting( 8enum class BooleanSetting(
7 override val key: String, 9 override val key: String,
8 override val section: String, 10 override val category: Settings.Category,
9 override val defaultValue: Boolean 11 override val androidDefault: Boolean? = null
10) : AbstractBooleanSetting { 12) : AbstractBooleanSetting {
11 CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), 13 AUDIO_MUTED("audio_muted", Settings.Category.Audio),
12 FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), 14 CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
13 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), 15 FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
14 PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), 16 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
15 USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); 17 RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
16 18 USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false),
17 override var boolean: Boolean = defaultValue 19 RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
20 RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
21 RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
22 RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false),
23 RENDERER_DEBUG("debug", Settings.Category.Renderer),
24 PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
25 USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
26
27 override val boolean: Boolean
28 get() = NativeConfig.getBoolean(key, false)
29
30 override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
31
32 override val defaultValue: Boolean by lazy {
33 androidDefault ?: NativeConfig.getBoolean(key, true)
34 }
18 35
19 override val valueAsString: String 36 override val valueAsString: String
20 get() = boolean.toString() 37 get() = if (boolean) "1" else "0"
21 38
22 override val isRuntimeEditable: Boolean 39 override fun reset() = NativeConfig.setBoolean(key, defaultValue)
23 get() {
24 for (setting in NOT_RUNTIME_EDITABLE) {
25 if (setting == this) {
26 return false
27 }
28 }
29 return true
30 }
31
32 companion object {
33 private val NOT_RUNTIME_EDITABLE = listOf(
34 PICTURE_IN_PICTURE,
35 USE_CUSTOM_RTC
36 )
37
38 fun from(key: String): BooleanSetting? =
39 BooleanSetting.values().firstOrNull { it.key == key }
40
41 fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
42 }
43} 40}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
new file mode 100644
index 000000000..6ec0a765e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
8enum class ByteSetting(
9 override val key: String,
10 override val category: Settings.Category
11) : AbstractByteSetting {
12 AUDIO_VOLUME("volume", Settings.Category.Audio);
13
14 override val byte: Byte
15 get() = NativeConfig.getByte(key, false)
16
17 override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
18
19 override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
20
21 override val valueAsString: String
22 get() = byte.toString()
23
24 override fun reset() = NativeConfig.setByte(key, defaultValue)
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
index e5545a916..0181d06f2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
@@ -3,34 +3,24 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6enum class FloatSetting( 8enum class FloatSetting(
7 override val key: String, 9 override val key: String,
8 override val section: String, 10 override val category: Settings.Category
9 override val defaultValue: Float
10) : AbstractFloatSetting { 11) : AbstractFloatSetting {
11 // No float settings currently exist 12 // No float settings currently exist
12 EMPTY_SETTING("", "", 0f); 13 EMPTY_SETTING("", Settings.Category.UiGeneral);
13
14 override var float: Float = defaultValue
15 14
16 override val valueAsString: String 15 override val float: Float
17 get() = float.toString() 16 get() = NativeConfig.getFloat(key, false)
18 17
19 override val isRuntimeEditable: Boolean 18 override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
20 get() {
21 for (setting in NOT_RUNTIME_EDITABLE) {
22 if (setting == this) {
23 return false
24 }
25 }
26 return true
27 }
28 19
29 companion object { 20 override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
30 private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
31 21
32 fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key } 22 override val valueAsString: String
23 get() = float.toString()
33 24
34 fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue } 25 override fun reset() = NativeConfig.setFloat(key, defaultValue)
35 }
36} 26}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index 4427a7d9d..151362124 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -3,139 +3,37 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6enum class IntSetting( 8enum class IntSetting(
7 override val key: String, 9 override val key: String,
8 override val section: String, 10 override val category: Settings.Category,
9 override val defaultValue: Int 11 override val androidDefault: Int? = null
10) : AbstractIntSetting { 12) : AbstractIntSetting {
11 RENDERER_USE_SPEED_LIMIT( 13 CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
12 "use_speed_limit", 14 REGION_INDEX("region_index", Settings.Category.System),
13 Settings.SECTION_RENDERER, 15 LANGUAGE_INDEX("language_index", Settings.Category.System),
14 1 16 RENDERER_BACKEND("backend", Settings.Category.Renderer),
15 ), 17 RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0),
16 USE_DOCKED_MODE( 18 RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
17 "use_docked_mode", 19 RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
18 Settings.SECTION_SYSTEM, 20 RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
19 0 21 RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
20 ), 22 RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
21 RENDERER_USE_DISK_SHADER_CACHE( 23 RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
22 "use_disk_shader_cache", 24 AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
23 Settings.SECTION_RENDERER, 25
24 1 26 override val int: Int
25 ), 27 get() = NativeConfig.getInt(key, false)
26 RENDERER_FORCE_MAX_CLOCK( 28
27 "force_max_clock", 29 override fun setInt(value: Int) = NativeConfig.setInt(key, value)
28 Settings.SECTION_RENDERER, 30
29 0 31 override val defaultValue: Int by lazy {
30 ), 32 androidDefault ?: NativeConfig.getInt(key, true)
31 RENDERER_ASYNCHRONOUS_SHADERS( 33 }
32 "use_asynchronous_shaders",
33 Settings.SECTION_RENDERER,
34 0
35 ),
36 RENDERER_REACTIVE_FLUSHING(
37 "use_reactive_flushing",
38 Settings.SECTION_RENDERER,
39 0
40 ),
41 RENDERER_DEBUG(
42 "debug",
43 Settings.SECTION_RENDERER,
44 0
45 ),
46 RENDERER_SPEED_LIMIT(
47 "speed_limit",
48 Settings.SECTION_RENDERER,
49 100
50 ),
51 CPU_ACCURACY(
52 "cpu_accuracy",
53 Settings.SECTION_CPU,
54 0
55 ),
56 REGION_INDEX(
57 "region_index",
58 Settings.SECTION_SYSTEM,
59 -1
60 ),
61 LANGUAGE_INDEX(
62 "language_index",
63 Settings.SECTION_SYSTEM,
64 1
65 ),
66 RENDERER_BACKEND(
67 "backend",
68 Settings.SECTION_RENDERER,
69 1
70 ),
71 RENDERER_ACCURACY(
72 "gpu_accuracy",
73 Settings.SECTION_RENDERER,
74 0
75 ),
76 RENDERER_RESOLUTION(
77 "resolution_setup",
78 Settings.SECTION_RENDERER,
79 2
80 ),
81 RENDERER_VSYNC(
82 "use_vsync",
83 Settings.SECTION_RENDERER,
84 0
85 ),
86 RENDERER_SCALING_FILTER(
87 "scaling_filter",
88 Settings.SECTION_RENDERER,
89 1
90 ),
91 RENDERER_ANTI_ALIASING(
92 "anti_aliasing",
93 Settings.SECTION_RENDERER,
94 0
95 ),
96 RENDERER_SCREEN_LAYOUT(
97 "screen_layout",
98 Settings.SECTION_RENDERER,
99 Settings.LayoutOption_MobileLandscape
100 ),
101 RENDERER_ASPECT_RATIO(
102 "aspect_ratio",
103 Settings.SECTION_RENDERER,
104 0
105 ),
106 AUDIO_VOLUME(
107 "volume",
108 Settings.SECTION_AUDIO,
109 100
110 );
111
112 override var int: Int = defaultValue
113 34
114 override val valueAsString: String 35 override val valueAsString: String
115 get() = int.toString() 36 get() = int.toString()
116 37
117 override val isRuntimeEditable: Boolean 38 override fun reset() = NativeConfig.setInt(key, defaultValue)
118 get() {
119 for (setting in NOT_RUNTIME_EDITABLE) {
120 if (setting == this) {
121 return false
122 }
123 }
124 return true
125 }
126
127 companion object {
128 private val NOT_RUNTIME_EDITABLE = listOf(
129 RENDERER_USE_DISK_SHADER_CACHE,
130 RENDERER_ASYNCHRONOUS_SHADERS,
131 RENDERER_DEBUG,
132 RENDERER_BACKEND,
133 RENDERER_RESOLUTION,
134 RENDERER_VSYNC
135 )
136
137 fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
138
139 fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
140 }
141} 39}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
new file mode 100644
index 000000000..c526fc4cf
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
8enum class LongSetting(
9 override val key: String,
10 override val category: Settings.Category
11) : AbstractLongSetting {
12 CUSTOM_RTC("custom_rtc", Settings.Category.System);
13
14 override val long: Long
15 get() = NativeConfig.getLong(key, false)
16
17 override fun setLong(value: Long) = NativeConfig.setLong(key, value)
18
19 override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
20
21 override val valueAsString: String
22 get() = long.toString()
23
24 override fun reset() = NativeConfig.setLong(key, defaultValue)
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
deleted file mode 100644
index 474f598a9..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
+++ /dev/null
@@ -1,37 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6/**
7 * A semantically-related group of Settings objects. These Settings are
8 * internally stored as a HashMap.
9 */
10class SettingSection(val name: String) {
11 val settings = HashMap<String, AbstractSetting>()
12
13 /**
14 * Convenience method; inserts a value directly into the backing HashMap.
15 *
16 * @param setting The Setting to be inserted.
17 */
18 fun putSetting(setting: AbstractSetting) {
19 settings[setting.key!!] = setting
20 }
21
22 /**
23 * Convenience method; gets a value directly from the backing HashMap.
24 *
25 * @param key Used to retrieve the Setting.
26 * @return A Setting object (you should probably cast this before using)
27 */
28 fun getSetting(key: String): AbstractSetting? {
29 return settings[key]
30 }
31
32 fun mergeSection(settingSection: SettingSection) {
33 for (setting in settingSection.settings.values) {
34 putSetting(setting)
35 }
36 }
37}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index a6251bafd..08e2a973d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -4,195 +4,162 @@
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import android.text.TextUtils 6import android.text.TextUtils
7import java.util.* 7import android.widget.Toast
8import org.yuzu.yuzu_emu.R 8import org.yuzu.yuzu_emu.R
9import org.yuzu.yuzu_emu.YuzuApplication 9import org.yuzu.yuzu_emu.YuzuApplication
10import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
11import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 10import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
12 11
13class Settings { 12object Settings {
14 private var gameId: String? = null 13 private val context get() = YuzuApplication.appContext
15 14
16 var isLoaded = false 15 fun saveSettings(gameId: String = "") {
17
18 /**
19 * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
20 * when getting a key not already in the map
21 */
22 class SettingsSectionMap : HashMap<String, SettingSection?>() {
23 override operator fun get(key: String): SettingSection? {
24 if (!super.containsKey(key)) {
25 val section = SettingSection(key)
26 super.put(key, section)
27 return section
28 }
29 return super.get(key)
30 }
31 }
32
33 var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
34
35 fun getSection(sectionName: String): SettingSection? {
36 return sections[sectionName]
37 }
38
39 val isEmpty: Boolean
40 get() = sections.isEmpty()
41
42 fun loadSettings(view: SettingsActivityView? = null) {
43 sections = SettingsSectionMap()
44 loadYuzuSettings(view)
45 if (!TextUtils.isEmpty(gameId)) {
46 loadCustomGameSettings(gameId!!, view)
47 }
48 isLoaded = true
49 }
50
51 private fun loadYuzuSettings(view: SettingsActivityView?) {
52 for ((fileName) in configFileSectionsMap) {
53 sections.putAll(SettingsFile.readFile(fileName, view))
54 }
55 }
56
57 private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
58 // Custom game settings
59 mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
60 }
61
62 private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
63 for ((key, updatedSection) in updatedSections) {
64 if (sections.containsKey(key)) {
65 val originalSection = sections[key]
66 originalSection!!.mergeSection(updatedSection!!)
67 } else {
68 sections[key] = updatedSection
69 }
70 }
71 }
72
73 fun loadSettings(gameId: String, view: SettingsActivityView) {
74 this.gameId = gameId
75 loadSettings(view)
76 }
77
78 fun saveSettings(view: SettingsActivityView) {
79 if (TextUtils.isEmpty(gameId)) { 16 if (TextUtils.isEmpty(gameId)) {
80 view.showToastMessage( 17 Toast.makeText(
81 YuzuApplication.appContext.getString(R.string.ini_saved), 18 context,
82 false 19 context.getString(R.string.ini_saved),
83 ) 20 Toast.LENGTH_SHORT
84 21 ).show()
85 for ((fileName, sectionNames) in configFileSectionsMap) { 22 SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
86 val iniSections = TreeMap<String, SettingSection>()
87 for (section in sectionNames) {
88 iniSections[section] = sections[section]!!
89 }
90
91 SettingsFile.saveFile(fileName, iniSections, view)
92 }
93 } else { 23 } else {
94 // Custom game settings 24 // TODO: Save custom game settings
95 view.showToastMessage( 25 Toast.makeText(
96 YuzuApplication.appContext.getString(R.string.gameid_saved, gameId), 26 context,
97 false 27 context.getString(R.string.gameid_saved, gameId),
98 ) 28 Toast.LENGTH_SHORT
99 29 ).show()
100 SettingsFile.saveCustomGameSettings(gameId, sections)
101 } 30 }
102 } 31 }
103 32
104 companion object { 33 enum class Category {
105 const val SECTION_GENERAL = "General" 34 Android,
106 const val SECTION_SYSTEM = "System" 35 Audio,
107 const val SECTION_RENDERER = "Renderer" 36 Core,
108 const val SECTION_AUDIO = "Audio" 37 Cpu,
109 const val SECTION_CPU = "Cpu" 38 CpuDebug,
110 const val SECTION_THEME = "Theme" 39 CpuUnsafe,
111 const val SECTION_DEBUG = "Debug" 40 Renderer,
112 41 RendererAdvanced,
113 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" 42 RendererDebug,
114 43 System,
115 const val PREF_OVERLAY_VERSION = "OverlayVersion" 44 SystemAudio,
116 const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" 45 DataStorage,
117 const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" 46 Debugging,
118 const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" 47 DebuggingGraphics,
119 val overlayLayoutPrefs = listOf( 48 Miscellaneous,
120 PREF_LANDSCAPE_OVERLAY_VERSION, 49 Network,
121 PREF_PORTRAIT_OVERLAY_VERSION, 50 WebService,
122 PREF_FOLDABLE_OVERLAY_VERSION 51 AddOns,
123 ) 52 Controls,
124 53 Ui,
125 const val PREF_CONTROL_SCALE = "controlScale" 54 UiGeneral,
126 const val PREF_CONTROL_OPACITY = "controlOpacity" 55 UiLayout,
127 const val PREF_TOUCH_ENABLED = "isTouchEnabled" 56 UiGameList,
128 const val PREF_BUTTON_A = "buttonToggle0" 57 Screenshots,
129 const val PREF_BUTTON_B = "buttonToggle1" 58 Shortcuts,
130 const val PREF_BUTTON_X = "buttonToggle2" 59 Multiplayer,
131 const val PREF_BUTTON_Y = "buttonToggle3" 60 Services,
132 const val PREF_BUTTON_L = "buttonToggle4" 61 Paths,
133 const val PREF_BUTTON_R = "buttonToggle5" 62 MaxEnum
134 const val PREF_BUTTON_ZL = "buttonToggle6" 63 }
135 const val PREF_BUTTON_ZR = "buttonToggle7"
136 const val PREF_BUTTON_PLUS = "buttonToggle8"
137 const val PREF_BUTTON_MINUS = "buttonToggle9"
138 const val PREF_BUTTON_DPAD = "buttonToggle10"
139 const val PREF_STICK_L = "buttonToggle11"
140 const val PREF_STICK_R = "buttonToggle12"
141 const val PREF_BUTTON_STICK_L = "buttonToggle13"
142 const val PREF_BUTTON_STICK_R = "buttonToggle14"
143 const val PREF_BUTTON_HOME = "buttonToggle15"
144 const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
145
146 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
147 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
148 const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
149 const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
150 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
151
152 const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
153 const val PREF_THEME = "Theme"
154 const val PREF_THEME_MODE = "ThemeMode"
155 const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
156
157 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
158
159 val overlayPreferences = listOf(
160 PREF_OVERLAY_VERSION,
161 PREF_CONTROL_SCALE,
162 PREF_CONTROL_OPACITY,
163 PREF_TOUCH_ENABLED,
164 PREF_BUTTON_A,
165 PREF_BUTTON_B,
166 PREF_BUTTON_X,
167 PREF_BUTTON_Y,
168 PREF_BUTTON_L,
169 PREF_BUTTON_R,
170 PREF_BUTTON_ZL,
171 PREF_BUTTON_ZR,
172 PREF_BUTTON_PLUS,
173 PREF_BUTTON_MINUS,
174 PREF_BUTTON_DPAD,
175 PREF_STICK_L,
176 PREF_STICK_R,
177 PREF_BUTTON_HOME,
178 PREF_BUTTON_SCREENSHOT,
179 PREF_BUTTON_STICK_L,
180 PREF_BUTTON_STICK_R
181 )
182
183 const val LayoutOption_Unspecified = 0
184 const val LayoutOption_MobilePortrait = 4
185 const val LayoutOption_MobileLandscape = 5
186 64
187 init { 65 val settingsList = listOf<AbstractSetting>(
188 configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] = 66 *BooleanSetting.values(),
189 listOf( 67 *ByteSetting.values(),
190 SECTION_GENERAL, 68 *ShortSetting.values(),
191 SECTION_SYSTEM, 69 *IntSetting.values(),
192 SECTION_RENDERER, 70 *FloatSetting.values(),
193 SECTION_AUDIO, 71 *LongSetting.values(),
194 SECTION_CPU 72 *StringSetting.values()
195 ) 73 )
196 } 74
75 const val SECTION_GENERAL = "General"
76 const val SECTION_SYSTEM = "System"
77 const val SECTION_RENDERER = "Renderer"
78 const val SECTION_AUDIO = "Audio"
79 const val SECTION_CPU = "Cpu"
80 const val SECTION_THEME = "Theme"
81 const val SECTION_DEBUG = "Debug"
82
83 enum class MenuTag(val titleId: Int) {
84 SECTION_ROOT(R.string.advanced_settings),
85 SECTION_GENERAL(R.string.preferences_general),
86 SECTION_SYSTEM(R.string.preferences_system),
87 SECTION_RENDERER(R.string.preferences_graphics),
88 SECTION_AUDIO(R.string.preferences_audio),
89 SECTION_CPU(R.string.cpu),
90 SECTION_THEME(R.string.preferences_theme),
91 SECTION_DEBUG(R.string.preferences_debug);
197 } 92 }
93
94 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
95
96 const val PREF_OVERLAY_VERSION = "OverlayVersion"
97 const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
98 const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
99 const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
100 val overlayLayoutPrefs = listOf(
101 PREF_LANDSCAPE_OVERLAY_VERSION,
102 PREF_PORTRAIT_OVERLAY_VERSION,
103 PREF_FOLDABLE_OVERLAY_VERSION
104 )
105
106 const val PREF_CONTROL_SCALE = "controlScale"
107 const val PREF_CONTROL_OPACITY = "controlOpacity"
108 const val PREF_TOUCH_ENABLED = "isTouchEnabled"
109 const val PREF_BUTTON_A = "buttonToggle0"
110 const val PREF_BUTTON_B = "buttonToggle1"
111 const val PREF_BUTTON_X = "buttonToggle2"
112 const val PREF_BUTTON_Y = "buttonToggle3"
113 const val PREF_BUTTON_L = "buttonToggle4"
114 const val PREF_BUTTON_R = "buttonToggle5"
115 const val PREF_BUTTON_ZL = "buttonToggle6"
116 const val PREF_BUTTON_ZR = "buttonToggle7"
117 const val PREF_BUTTON_PLUS = "buttonToggle8"
118 const val PREF_BUTTON_MINUS = "buttonToggle9"
119 const val PREF_BUTTON_DPAD = "buttonToggle10"
120 const val PREF_STICK_L = "buttonToggle11"
121 const val PREF_STICK_R = "buttonToggle12"
122 const val PREF_BUTTON_STICK_L = "buttonToggle13"
123 const val PREF_BUTTON_STICK_R = "buttonToggle14"
124 const val PREF_BUTTON_HOME = "buttonToggle15"
125 const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
126
127 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
128 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
129 const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
130 const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
131 const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
132
133 const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
134 const val PREF_THEME = "Theme"
135 const val PREF_THEME_MODE = "ThemeMode"
136 const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
137
138 val overlayPreferences = listOf(
139 PREF_OVERLAY_VERSION,
140 PREF_CONTROL_SCALE,
141 PREF_CONTROL_OPACITY,
142 PREF_TOUCH_ENABLED,
143 PREF_BUTTON_A,
144 PREF_BUTTON_B,
145 PREF_BUTTON_X,
146 PREF_BUTTON_Y,
147 PREF_BUTTON_L,
148 PREF_BUTTON_R,
149 PREF_BUTTON_ZL,
150 PREF_BUTTON_ZR,
151 PREF_BUTTON_PLUS,
152 PREF_BUTTON_MINUS,
153 PREF_BUTTON_DPAD,
154 PREF_STICK_L,
155 PREF_STICK_R,
156 PREF_BUTTON_HOME,
157 PREF_BUTTON_SCREENSHOT,
158 PREF_BUTTON_STICK_L,
159 PREF_BUTTON_STICK_R
160 )
161
162 const val LayoutOption_Unspecified = 0
163 const val LayoutOption_MobilePortrait = 4
164 const val LayoutOption_MobileLandscape = 5
198} 165}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
new file mode 100644
index 000000000..c9a0c664c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.model
5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
8enum class ShortSetting(
9 override val key: String,
10 override val category: Settings.Category
11) : AbstractShortSetting {
12 RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
13
14 override val short: Short
15 get() = NativeConfig.getShort(key, false)
16
17 override fun setShort(value: Short) = NativeConfig.setShort(key, value)
18
19 override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
20
21 override val valueAsString: String
22 get() = short.toString()
23
24 override fun reset() = NativeConfig.setShort(key, defaultValue)
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
index 6621289fd..9bb3e66d4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
@@ -3,36 +3,24 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model 4package org.yuzu.yuzu_emu.features.settings.model
5 5
6import org.yuzu.yuzu_emu.utils.NativeConfig
7
6enum class StringSetting( 8enum class StringSetting(
7 override val key: String, 9 override val key: String,
8 override val section: String, 10 override val category: Settings.Category
9 override val defaultValue: String
10) : AbstractStringSetting { 11) : AbstractStringSetting {
11 AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), 12 // No string settings currently exist
12 CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); 13 EMPTY_SETTING("", Settings.Category.UiGeneral);
14
15 override val string: String
16 get() = NativeConfig.getString(key, false)
17
18 override fun setString(value: String) = NativeConfig.setString(key, value)
13 19
14 override var string: String = defaultValue 20 override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
15 21
16 override val valueAsString: String 22 override val valueAsString: String
17 get() = string 23 get() = string
18 24
19 override val isRuntimeEditable: Boolean 25 override fun reset() = NativeConfig.setString(key, defaultValue)
20 get() {
21 for (setting in NOT_RUNTIME_EDITABLE) {
22 if (setting == this) {
23 return false
24 }
25 }
26 return true
27 }
28
29 companion object {
30 private val NOT_RUNTIME_EDITABLE = listOf(
31 CUSTOM_RTC
32 )
33
34 fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
35
36 fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
37 }
38} 26}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
index bc0bf7788..8bc164197 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
@@ -3,29 +3,16 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 6import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
8 7
9class DateTimeSetting( 8class DateTimeSetting(
10 setting: AbstractSetting?, 9 private val longSetting: AbstractLongSetting,
11 titleId: Int, 10 titleId: Int,
12 descriptionId: Int, 11 descriptionId: Int
13 val key: String? = null, 12) : SettingsItem(longSetting, titleId, descriptionId) {
14 private val defaultValue: String? = null
15) : SettingsItem(setting, titleId, descriptionId) {
16 override val type = TYPE_DATETIME_SETTING 13 override val type = TYPE_DATETIME_SETTING
17 14
18 val value: String 15 var value: Long
19 get() = if (setting != null) { 16 get() = longSetting.long
20 val setting = setting as AbstractStringSetting 17 set(value) = (setting as AbstractLongSetting).setLong(value)
21 setting.string
22 } else {
23 defaultValue!!
24 }
25
26 fun setSelectedValue(datetime: String): AbstractStringSetting {
27 val stringSetting = setting as AbstractStringSetting
28 stringSetting.string = datetime
29 return stringSetting
30 }
31} 18}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
index a67001311..d31ce1c31 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
@@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6class HeaderSetting( 6class HeaderSetting(
7 titleId: Int 7 titleId: Int
8) : SettingsItem(null, titleId, 0) { 8) : SettingsItem(emptySetting, titleId, 0) {
9 override val type = TYPE_HEADER 9 override val type = TYPE_HEADER
10} 10}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
index caaab50d8..522cc49df 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
@@ -8,6 +8,6 @@ class RunnableSetting(
8 descriptionId: Int, 8 descriptionId: Int,
9 val isRuntimeRunnable: Boolean, 9 val isRuntimeRunnable: Boolean,
10 val runnable: () -> Unit 10 val runnable: () -> Unit
11) : SettingsItem(null, titleId, descriptionId) { 11) : SettingsItem(emptySetting, titleId, descriptionId) {
12 override val type = TYPE_RUNNABLE 12 override val type = TYPE_RUNNABLE
13} 13}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 07520849e..b3b3fc209 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -4,7 +4,15 @@
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.NativeLibrary 6import org.yuzu.yuzu_emu.NativeLibrary
7import org.yuzu.yuzu_emu.R
8import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 9import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
10import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
11import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
12import org.yuzu.yuzu_emu.features.settings.model.IntSetting
13import org.yuzu.yuzu_emu.features.settings.model.LongSetting
14import org.yuzu.yuzu_emu.features.settings.model.Settings
15import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
8 16
9/** 17/**
10 * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. 18 * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -14,7 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
14 * file.) 22 * file.)
15 */ 23 */
16abstract class SettingsItem( 24abstract class SettingsItem(
17 var setting: AbstractSetting?, 25 val setting: AbstractSetting,
18 val nameId: Int, 26 val nameId: Int,
19 val descriptionId: Int 27 val descriptionId: Int
20) { 28) {
@@ -23,7 +31,7 @@ abstract class SettingsItem(
23 val isEditable: Boolean 31 val isEditable: Boolean
24 get() { 32 get() {
25 if (!NativeLibrary.isRunning()) return true 33 if (!NativeLibrary.isRunning()) return true
26 return setting?.isRuntimeEditable ?: false 34 return setting.isRuntimeModifiable
27 } 35 }
28 36
29 companion object { 37 companion object {
@@ -35,5 +43,240 @@ abstract class SettingsItem(
35 const val TYPE_STRING_SINGLE_CHOICE = 5 43 const val TYPE_STRING_SINGLE_CHOICE = 5
36 const val TYPE_DATETIME_SETTING = 6 44 const val TYPE_DATETIME_SETTING = 6
37 const val TYPE_RUNNABLE = 7 45 const val TYPE_RUNNABLE = 7
46
47 const val FASTMEM_COMBINED = "fastmem_combined"
48
49 val emptySetting = object : AbstractSetting {
50 override val key: String = ""
51 override val category: Settings.Category = Settings.Category.Ui
52 override val defaultValue: Any = false
53 override fun reset() {}
54 }
55
56 // Extension for putting SettingsItems into a hashmap without repeating yourself
57 fun HashMap<String, SettingsItem>.put(item: SettingsItem) {
58 put(item.setting.key, item)
59 }
60
61 // List of all general
62 val settingsItems = HashMap<String, SettingsItem>().apply {
63 put(
64 SwitchSetting(
65 BooleanSetting.RENDERER_USE_SPEED_LIMIT,
66 R.string.frame_limit_enable,
67 R.string.frame_limit_enable_description
68 )
69 )
70 put(
71 SliderSetting(
72 ShortSetting.RENDERER_SPEED_LIMIT,
73 R.string.frame_limit_slider,
74 R.string.frame_limit_slider_description,
75 1,
76 200,
77 "%"
78 )
79 )
80 put(
81 SingleChoiceSetting(
82 IntSetting.CPU_ACCURACY,
83 R.string.cpu_accuracy,
84 0,
85 R.array.cpuAccuracyNames,
86 R.array.cpuAccuracyValues
87 )
88 )
89 put(
90 SwitchSetting(
91 BooleanSetting.PICTURE_IN_PICTURE,
92 R.string.picture_in_picture,
93 R.string.picture_in_picture_description
94 )
95 )
96 put(
97 SwitchSetting(
98 BooleanSetting.USE_DOCKED_MODE,
99 R.string.use_docked_mode,
100 R.string.use_docked_mode_description
101 )
102 )
103 put(
104 SingleChoiceSetting(
105 IntSetting.REGION_INDEX,
106 R.string.emulated_region,
107 0,
108 R.array.regionNames,
109 R.array.regionValues
110 )
111 )
112 put(
113 SingleChoiceSetting(
114 IntSetting.LANGUAGE_INDEX,
115 R.string.emulated_language,
116 0,
117 R.array.languageNames,
118 R.array.languageValues
119 )
120 )
121 put(
122 SwitchSetting(
123 BooleanSetting.USE_CUSTOM_RTC,
124 R.string.use_custom_rtc,
125 R.string.use_custom_rtc_description
126 )
127 )
128 put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
129 put(
130 SingleChoiceSetting(
131 IntSetting.RENDERER_ACCURACY,
132 R.string.renderer_accuracy,
133 0,
134 R.array.rendererAccuracyNames,
135 R.array.rendererAccuracyValues
136 )
137 )
138 put(
139 SingleChoiceSetting(
140 IntSetting.RENDERER_RESOLUTION,
141 R.string.renderer_resolution,
142 0,
143 R.array.rendererResolutionNames,
144 R.array.rendererResolutionValues
145 )
146 )
147 put(
148 SingleChoiceSetting(
149 IntSetting.RENDERER_VSYNC,
150 R.string.renderer_vsync,
151 0,
152 R.array.rendererVSyncNames,
153 R.array.rendererVSyncValues
154 )
155 )
156 put(
157 SingleChoiceSetting(
158 IntSetting.RENDERER_SCALING_FILTER,
159 R.string.renderer_scaling_filter,
160 0,
161 R.array.rendererScalingFilterNames,
162 R.array.rendererScalingFilterValues
163 )
164 )
165 put(
166 SingleChoiceSetting(
167 IntSetting.RENDERER_ANTI_ALIASING,
168 R.string.renderer_anti_aliasing,
169 0,
170 R.array.rendererAntiAliasingNames,
171 R.array.rendererAntiAliasingValues
172 )
173 )
174 put(
175 SingleChoiceSetting(
176 IntSetting.RENDERER_SCREEN_LAYOUT,
177 R.string.renderer_screen_layout,
178 0,
179 R.array.rendererScreenLayoutNames,
180 R.array.rendererScreenLayoutValues
181 )
182 )
183 put(
184 SingleChoiceSetting(
185 IntSetting.RENDERER_ASPECT_RATIO,
186 R.string.renderer_aspect_ratio,
187 0,
188 R.array.rendererAspectRatioNames,
189 R.array.rendererAspectRatioValues
190 )
191 )
192 put(
193 SwitchSetting(
194 BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
195 R.string.use_disk_shader_cache,
196 R.string.use_disk_shader_cache_description
197 )
198 )
199 put(
200 SwitchSetting(
201 BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
202 R.string.renderer_force_max_clock,
203 R.string.renderer_force_max_clock_description
204 )
205 )
206 put(
207 SwitchSetting(
208 BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
209 R.string.renderer_asynchronous_shaders,
210 R.string.renderer_asynchronous_shaders_description
211 )
212 )
213 put(
214 SwitchSetting(
215 BooleanSetting.RENDERER_REACTIVE_FLUSHING,
216 R.string.renderer_reactive_flushing,
217 R.string.renderer_reactive_flushing_description
218 )
219 )
220 put(
221 SingleChoiceSetting(
222 IntSetting.AUDIO_OUTPUT_ENGINE,
223 R.string.audio_output_engine,
224 0,
225 R.array.outputEngineEntries,
226 R.array.outputEngineValues
227 )
228 )
229 put(
230 SliderSetting(
231 ByteSetting.AUDIO_VOLUME,
232 R.string.audio_volume,
233 R.string.audio_volume_description,
234 0,
235 100,
236 "%"
237 )
238 )
239 put(
240 SingleChoiceSetting(
241 IntSetting.RENDERER_BACKEND,
242 R.string.renderer_api,
243 0,
244 R.array.rendererApiNames,
245 R.array.rendererApiValues
246 )
247 )
248 put(
249 SwitchSetting(
250 BooleanSetting.RENDERER_DEBUG,
251 R.string.renderer_debug,
252 R.string.renderer_debug_description
253 )
254 )
255 put(
256 SwitchSetting(
257 BooleanSetting.CPU_DEBUG_MODE,
258 R.string.cpu_debug_mode,
259 R.string.cpu_debug_mode_description
260 )
261 )
262
263 val fastmem = object : AbstractBooleanSetting {
264 override val boolean: Boolean
265 get() =
266 BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
267
268 override fun setBoolean(value: Boolean) {
269 BooleanSetting.FASTMEM.setBoolean(value)
270 BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
271 }
272
273 override val key: String = FASTMEM_COMBINED
274 override val category = Settings.Category.Cpu
275 override val isRuntimeModifiable: Boolean = false
276 override val defaultValue: Boolean = true
277 override fun reset() = setBoolean(defaultValue)
278 }
279 put(SwitchSetting(fastmem, R.string.fastmem, 0))
280 }
38 } 281 }
39} 282}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
index 7306ec458..705527a73 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
@@ -4,36 +4,27 @@
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting 6import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7 8
8class SingleChoiceSetting( 9class SingleChoiceSetting(
9 setting: AbstractIntSetting?, 10 setting: AbstractSetting,
10 titleId: Int, 11 titleId: Int,
11 descriptionId: Int, 12 descriptionId: Int,
12 val choicesId: Int, 13 val choicesId: Int,
13 val valuesId: Int, 14 val valuesId: Int
14 val key: String? = null,
15 val defaultValue: Int? = null
16) : SettingsItem(setting, titleId, descriptionId) { 15) : SettingsItem(setting, titleId, descriptionId) {
17 override val type = TYPE_SINGLE_CHOICE 16 override val type = TYPE_SINGLE_CHOICE
18 17
19 val selectedValue: Int 18 var selectedValue: Int
20 get() = if (setting != null) { 19 get() {
21 val setting = setting as AbstractIntSetting 20 return when (setting) {
22 setting.int 21 is AbstractIntSetting -> setting.int
23 } else { 22 else -> -1
24 defaultValue!! 23 }
24 }
25 set(value) {
26 when (setting) {
27 is AbstractIntSetting -> setting.setInt(value)
28 }
25 } 29 }
26
27 /**
28 * Write a value to the backing int. If that int was previously null,
29 * initializes a new one and returns it, so it can be added to the Hashmap.
30 *
31 * @param selection New value of the int.
32 * @return the existing setting with the new value applied.
33 */
34 fun setSelectedValue(selection: Int): AbstractIntSetting {
35 val intSetting = setting as AbstractIntSetting
36 intSetting.int = selection
37 return intSetting
38 }
39} 30}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
index 92d0167ae..c3b5df02c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
@@ -3,60 +3,39 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import kotlin.math.roundToInt 6import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting 7import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
8import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting 8import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
9import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 9import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
10import org.yuzu.yuzu_emu.utils.Log 10import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
11import kotlin.math.roundToInt
11 12
12class SliderSetting( 13class SliderSetting(
13 setting: AbstractSetting?, 14 setting: AbstractSetting,
14 titleId: Int, 15 titleId: Int,
15 descriptionId: Int, 16 descriptionId: Int,
16 val min: Int, 17 val min: Int,
17 val max: Int, 18 val max: Int,
18 val units: String, 19 val units: String
19 val key: String? = null,
20 val defaultValue: Int? = null
21) : SettingsItem(setting, titleId, descriptionId) { 20) : SettingsItem(setting, titleId, descriptionId) {
22 override val type = TYPE_SLIDER 21 override val type = TYPE_SLIDER
23 22
24 val selectedValue: Int 23 var selectedValue: Int
25 get() { 24 get() {
26 val setting = setting ?: return defaultValue!!
27 return when (setting) { 25 return when (setting) {
26 is AbstractByteSetting -> setting.byte.toInt()
27 is AbstractShortSetting -> setting.short.toInt()
28 is AbstractIntSetting -> setting.int 28 is AbstractIntSetting -> setting.int
29 is AbstractFloatSetting -> setting.float.roundToInt() 29 is AbstractFloatSetting -> setting.float.roundToInt()
30 else -> { 30 else -> -1
31 Log.error("[SliderSetting] Error casting setting type.") 31 }
32 -1 32 }
33 } 33 set(value) {
34 when (setting) {
35 is AbstractByteSetting -> setting.setByte(value.toByte())
36 is AbstractShortSetting -> setting.setShort(value.toShort())
37 is AbstractIntSetting -> setting.setInt(value)
38 is AbstractFloatSetting -> setting.setFloat(value.toFloat())
34 } 39 }
35 } 40 }
36
37 /**
38 * Write a value to the backing int. If that int was previously null,
39 * initializes a new one and returns it, so it can be added to the Hashmap.
40 *
41 * @param selection New value of the int.
42 * @return the existing setting with the new value applied.
43 */
44 fun setSelectedValue(selection: Int): AbstractIntSetting {
45 val intSetting = setting as AbstractIntSetting
46 intSetting.int = selection
47 return intSetting
48 }
49
50 /**
51 * Write a value to the backing float. If that float was previously null,
52 * initializes a new one and returns it, so it can be added to the Hashmap.
53 *
54 * @param selection New value of the float.
55 * @return the existing setting with the new value applied.
56 */
57 fun setSelectedValue(selection: Float): AbstractFloatSetting {
58 val floatSetting = setting as AbstractFloatSetting
59 floatSetting.float = selection
60 return floatSetting
61 }
62} 41}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
index 3b6731dcd..871dab4f3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
@@ -3,57 +3,31 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting 6import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
8 7
9class StringSingleChoiceSetting( 8class StringSingleChoiceSetting(
10 setting: AbstractSetting?, 9 private val stringSetting: AbstractStringSetting,
11 titleId: Int, 10 titleId: Int,
12 descriptionId: Int, 11 descriptionId: Int,
13 val choices: Array<String>, 12 val choices: Array<String>,
14 val values: Array<String>?, 13 val values: Array<String>
15 val key: String? = null, 14) : SettingsItem(stringSetting, titleId, descriptionId) {
16 private val defaultValue: String? = null
17) : SettingsItem(setting, titleId, descriptionId) {
18 override val type = TYPE_STRING_SINGLE_CHOICE 15 override val type = TYPE_STRING_SINGLE_CHOICE
19 16
20 fun getValueAt(index: Int): String? { 17 fun getValueAt(index: Int): String =
21 if (values == null) return null 18 if (index >= 0 && index < values.size) values[index] else ""
22 return if (index >= 0 && index < values.size) { 19
23 values[index] 20 var selectedValue: String
24 } else { 21 get() = stringSetting.string
25 "" 22 set(value) = stringSetting.setString(value)
26 }
27 }
28 23
29 val selectedValue: String
30 get() = if (setting != null) {
31 val setting = setting as AbstractStringSetting
32 setting.string
33 } else {
34 defaultValue!!
35 }
36 val selectValueIndex: Int 24 val selectValueIndex: Int
37 get() { 25 get() {
38 val selectedValue = selectedValue 26 for (i in values.indices) {
39 for (i in values!!.indices) {
40 if (values[i] == selectedValue) { 27 if (values[i] == selectedValue) {
41 return i 28 return i
42 } 29 }
43 } 30 }
44 return -1 31 return -1
45 } 32 }
46
47 /**
48 * Write a value to the backing int. If that int was previously null,
49 * initializes a new one and returns it, so it can be added to the Hashmap.
50 *
51 * @param selection New value of the int.
52 * @return the existing setting with the new value applied.
53 */
54 fun setSelectedValue(selection: String): AbstractStringSetting {
55 val stringSetting = setting as AbstractStringSetting
56 stringSetting.string = selection
57 return stringSetting
58 }
59} 33}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 8a9d13a92..b343e527e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -3,10 +3,12 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.Settings
7
6class SubmenuSetting( 8class SubmenuSetting(
7 titleId: Int, 9 titleId: Int,
8 descriptionId: Int, 10 descriptionId: Int,
9 val menuKey: String 11 val menuKey: Settings.MenuTag
10) : SettingsItem(null, titleId, descriptionId) { 12) : SettingsItem(emptySetting, titleId, descriptionId) {
11 override val type = TYPE_SUBMENU 13 override val type = TYPE_SUBMENU
12} 14}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
index 90b198718..416967e64 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
@@ -10,53 +10,22 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
10class SwitchSetting( 10class SwitchSetting(
11 setting: AbstractSetting, 11 setting: AbstractSetting,
12 titleId: Int, 12 titleId: Int,
13 descriptionId: Int, 13 descriptionId: Int
14 val key: String? = null,
15 val defaultValue: Any? = null
16) : SettingsItem(setting, titleId, descriptionId) { 14) : SettingsItem(setting, titleId, descriptionId) {
17 override val type = TYPE_SWITCH 15 override val type = TYPE_SWITCH
18 16
19 val isChecked: Boolean 17 var checked: Boolean
20 get() { 18 get() {
21 if (setting == null) { 19 return when (setting) {
22 return defaultValue as Boolean 20 is AbstractIntSetting -> setting.int == 1
21 is AbstractBooleanSetting -> setting.boolean
22 else -> false
23 } 23 }
24
25 // Try integer setting
26 try {
27 val setting = setting as AbstractIntSetting
28 return setting.int == 1
29 } catch (_: ClassCastException) {
30 }
31
32 // Try boolean setting
33 try {
34 val setting = setting as AbstractBooleanSetting
35 return setting.boolean
36 } catch (_: ClassCastException) {
37 }
38 return defaultValue as Boolean
39 } 24 }
40 25 set(value) {
41 /** 26 when (setting) {
42 * Write a value to the backing boolean. If that boolean was previously null, 27 is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
43 * initializes a new one and returns it, so it can be added to the Hashmap. 28 is AbstractBooleanSetting -> setting.setBoolean(value)
44 * 29 }
45 * @param checked Pretty self explanatory.
46 * @return the existing setting with the new value applied.
47 */
48 fun setChecked(checked: Boolean): AbstractSetting {
49 // Try integer setting
50 try {
51 val setting = setting as AbstractIntSetting
52 setting.int = if (checked) 1 else 0
53 return setting
54 } catch (_: ClassCastException) {
55 } 30 }
56
57 // Try boolean setting
58 val setting = setting as AbstractBooleanSetting
59 setting.boolean = checked
60 return setting
61 }
62} 31}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index a5af5a7ae..4d2f2f604 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -3,42 +3,39 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context
7import android.content.Intent
8import android.os.Bundle 6import android.os.Bundle
9import android.view.Menu
10import android.view.View 7import android.view.View
11import android.view.ViewGroup.MarginLayoutParams 8import android.view.ViewGroup.MarginLayoutParams
12import android.widget.Toast 9import android.widget.Toast
13import androidx.activity.OnBackPressedCallback 10import androidx.activity.OnBackPressedCallback
14import androidx.activity.result.ActivityResultLauncher
15import androidx.activity.viewModels 11import androidx.activity.viewModels
16import androidx.appcompat.app.AppCompatActivity 12import androidx.appcompat.app.AppCompatActivity
17import androidx.core.view.ViewCompat 13import androidx.core.view.ViewCompat
18import androidx.core.view.WindowCompat 14import androidx.core.view.WindowCompat
19import androidx.core.view.WindowInsetsCompat 15import androidx.core.view.WindowInsetsCompat
20import androidx.core.view.updatePadding 16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle
19import androidx.navigation.fragment.NavHostFragment
20import androidx.navigation.navArgs
21import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.flow.collectLatest
23import kotlinx.coroutines.launch
22import java.io.IOException 24import java.io.IOException
23import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
24import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 26import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
25import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
26import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
27import org.yuzu.yuzu_emu.features.settings.model.IntSetting
28import org.yuzu.yuzu_emu.features.settings.model.Settings 27import org.yuzu.yuzu_emu.features.settings.model.Settings
29import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
30import org.yuzu.yuzu_emu.features.settings.model.StringSetting
31import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 28import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
29import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
30import org.yuzu.yuzu_emu.model.SettingsViewModel
32import org.yuzu.yuzu_emu.utils.* 31import org.yuzu.yuzu_emu.utils.*
33 32
34class SettingsActivity : AppCompatActivity(), SettingsActivityView { 33class SettingsActivity : AppCompatActivity() {
35 private val presenter = SettingsActivityPresenter(this)
36
37 private lateinit var binding: ActivitySettingsBinding 34 private lateinit var binding: ActivitySettingsBinding
38 35
39 private val settingsViewModel: SettingsViewModel by viewModels() 36 private val args by navArgs<SettingsActivityArgs>()
40 37
41 override val settings: Settings get() = settingsViewModel.settings 38 private val settingsViewModel: SettingsViewModel by viewModels()
42 39
43 override fun onCreate(savedInstanceState: Bundle?) { 40 override fun onCreate(savedInstanceState: Bundle?) {
44 ThemeHelper.setTheme(this) 41 ThemeHelper.setTheme(this)
@@ -48,16 +45,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
48 binding = ActivitySettingsBinding.inflate(layoutInflater) 45 binding = ActivitySettingsBinding.inflate(layoutInflater)
49 setContentView(binding.root) 46 setContentView(binding.root)
50 47
51 WindowCompat.setDecorFitsSystemWindows(window, false) 48 settingsViewModel.game = args.game
52 49
53 val launcher = intent 50 val navHostFragment =
54 val gameID = launcher.getStringExtra(ARG_GAME_ID) 51 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
55 val menuTag = launcher.getStringExtra(ARG_MENU_TAG) 52 navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras)
56 presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
57 53
58 // Show "Back" button in the action bar for navigation 54 WindowCompat.setDecorFitsSystemWindows(window, false)
59 setSupportActionBar(binding.toolbarSettings) 55
60 supportActionBar!!.setDisplayHomeAsUpEnabled(true) 56 if (savedInstanceState != null) {
57 settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
58 }
61 59
62 if (InsetsHelper.getSystemGestureType(applicationContext) != 60 if (InsetsHelper.getSystemGestureType(applicationContext) !=
63 InsetsHelper.GESTURE_NAVIGATION 61 InsetsHelper.GESTURE_NAVIGATION
@@ -73,6 +71,42 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
73 ) 71 )
74 } 72 }
75 73
74 lifecycleScope.apply {
75 launch {
76 repeatOnLifecycle(Lifecycle.State.CREATED) {
77 settingsViewModel.shouldRecreate.collectLatest {
78 if (it) {
79 settingsViewModel.setShouldRecreate(false)
80 recreate()
81 }
82 }
83 }
84 }
85 launch {
86 repeatOnLifecycle(Lifecycle.State.CREATED) {
87 settingsViewModel.shouldNavigateBack.collectLatest {
88 if (it) {
89 settingsViewModel.setShouldNavigateBack(false)
90 navigateBack()
91 }
92 }
93 }
94 }
95 launch {
96 repeatOnLifecycle(Lifecycle.State.CREATED) {
97 settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
98 if (it) {
99 settingsViewModel.setShouldShowResetSettingsDialog(false)
100 ResetSettingsDialogFragment().show(
101 supportFragmentManager,
102 ResetSettingsDialogFragment.TAG
103 )
104 }
105 }
106 }
107 }
108 }
109
76 onBackPressedDispatcher.addCallback( 110 onBackPressedDispatcher.addCallback(
77 this, 111 this,
78 object : OnBackPressedCallback(true) { 112 object : OnBackPressedCallback(true) {
@@ -83,34 +117,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
83 setInsets() 117 setInsets()
84 } 118 }
85 119
86 override fun onSupportNavigateUp(): Boolean { 120 fun navigateBack() {
87 navigateBack() 121 val navHostFragment =
88 return true 122 supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
89 } 123 if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
90 124 navHostFragment.navController.popBackStack()
91 private fun navigateBack() {
92 if (supportFragmentManager.backStackEntryCount > 0) {
93 supportFragmentManager.popBackStack()
94 } else { 125 } else {
95 finish() 126 finish()
96 } 127 }
97 } 128 }
98 129
99 override fun onCreateOptionsMenu(menu: Menu): Boolean {
100 val inflater = menuInflater
101 inflater.inflate(R.menu.menu_settings, menu)
102 return true
103 }
104
105 override fun onSaveInstanceState(outState: Bundle) { 130 override fun onSaveInstanceState(outState: Bundle) {
106 // Critical: If super method is not called, rotations will be busted. 131 // Critical: If super method is not called, rotations will be busted.
107 super.onSaveInstanceState(outState) 132 super.onSaveInstanceState(outState)
108 presenter.saveState(outState) 133 outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
109 } 134 }
110 135
111 override fun onStart() { 136 override fun onStart() {
112 super.onStart() 137 super.onStart()
113 presenter.onStart() 138 // TODO: Load custom settings contextually
139 if (!DirectoryInitialization.areDirectoriesReady) {
140 DirectoryInitialization.start()
141 }
114 } 142 }
115 143
116 /** 144 /**
@@ -120,143 +148,51 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
120 */ 148 */
121 override fun onStop() { 149 override fun onStop() {
122 super.onStop() 150 super.onStop()
123 presenter.onStop(isFinishing) 151 if (isFinishing && settingsViewModel.shouldSave) {
124 } 152 Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
125 153 Settings.saveSettings()
126 override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
127 if (!addToStack && settingsFragment != null) {
128 return
129 } 154 }
130
131 val transaction = supportFragmentManager.beginTransaction()
132 if (addToStack) {
133 if (areSystemAnimationsEnabled()) {
134 transaction.setCustomAnimations(
135 R.anim.anim_settings_fragment_in,
136 R.anim.anim_settings_fragment_out,
137 0,
138 R.anim.anim_pop_settings_fragment_out
139 )
140 }
141 transaction.addToBackStack(null)
142 }
143 transaction.replace(
144 R.id.frame_content,
145 SettingsFragment.newInstance(menuTag, gameId),
146 FRAGMENT_TAG
147 )
148 transaction.commit()
149 }
150
151 private fun areSystemAnimationsEnabled(): Boolean {
152 val duration = android.provider.Settings.Global.getFloat(
153 contentResolver,
154 android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
155 1f
156 )
157 val transition = android.provider.Settings.Global.getFloat(
158 contentResolver,
159 android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
160 1f
161 )
162 return duration != 0f && transition != 0f
163 } 155 }
164 156
165 override fun onSettingsFileLoaded() { 157 override fun onDestroy() {
166 val fragment: SettingsFragmentView? = settingsFragment 158 settingsViewModel.clear()
167 fragment?.loadSettingsList() 159 super.onDestroy()
168 }
169
170 override fun onSettingsFileNotFound() {
171 val fragment: SettingsFragmentView? = settingsFragment
172 fragment?.loadSettingsList()
173 }
174
175 override fun showToastMessage(message: String, is_long: Boolean) {
176 Toast.makeText(
177 this,
178 message,
179 if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
180 ).show()
181 }
182
183 override fun onSettingChanged() {
184 presenter.onSettingChanged()
185 } 160 }
186 161
187 fun onSettingsReset() { 162 fun onSettingsReset() {
188 // Prevents saving to a non-existent settings file 163 // Prevents saving to a non-existent settings file
189 presenter.onSettingsReset() 164 settingsViewModel.shouldSave = false
190
191 // Reset the static memory representation of each setting
192 BooleanSetting.clear()
193 FloatSetting.clear()
194 IntSetting.clear()
195 StringSetting.clear()
196 165
197 // Delete settings file because the user may have changed values that do not exist in the UI 166 // Delete settings file because the user may have changed values that do not exist in the UI
198 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) 167 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
199 if (!settingsFile.delete()) { 168 if (!settingsFile.delete()) {
200 throw IOException("Failed to delete $settingsFile") 169 throw IOException("Failed to delete $settingsFile")
201 } 170 }
171 Settings.settingsList.forEach { it.reset() }
202 172
203 showToastMessage(getString(R.string.settings_reset), true) 173 Toast.makeText(
174 applicationContext,
175 getString(R.string.settings_reset),
176 Toast.LENGTH_LONG
177 ).show()
204 finish() 178 finish()
205 } 179 }
206 180
207 fun setToolbarTitle(title: String) {
208 binding.toolbarSettingsLayout.title = title
209 }
210
211 private val settingsFragment: SettingsFragment?
212 get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
213
214 private fun setInsets() { 181 private fun setInsets() {
215 ViewCompat.setOnApplyWindowInsetsListener( 182 ViewCompat.setOnApplyWindowInsetsListener(
216 binding.frameContent 183 binding.navigationBarShade
217 ) { view: View, windowInsets: WindowInsetsCompat -> 184 ) { view: View, windowInsets: WindowInsetsCompat ->
218 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 185 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
219 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
220 view.updatePadding(
221 left = barInsets.left + cutoutInsets.left,
222 right = barInsets.right + cutoutInsets.right
223 )
224 186
225 val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams 187 val mlpShade = view.layoutParams as MarginLayoutParams
226 mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
227 mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
228 binding.appbarSettings.layoutParams = mlpAppBar
229
230 val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
231 mlpShade.height = barInsets.bottom 188 mlpShade.height = barInsets.bottom
232 binding.navigationBarShade.layoutParams = mlpShade 189 view.layoutParams = mlpShade
233 190
234 windowInsets 191 windowInsets
235 } 192 }
236 } 193 }
237 194
238 companion object { 195 companion object {
239 private const val ARG_MENU_TAG = "menu_tag" 196 private const val KEY_SHOULD_SAVE = "should_save"
240 private const val ARG_GAME_ID = "game_id"
241 private const val FRAGMENT_TAG = "settings"
242
243 fun launch(context: Context, menuTag: String?, gameId: String?) {
244 val settings = Intent(context, SettingsActivity::class.java)
245 settings.putExtra(ARG_MENU_TAG, menuTag)
246 settings.putExtra(ARG_GAME_ID, gameId)
247 context.startActivity(settings)
248 }
249
250 fun launch(
251 context: Context,
252 launcher: ActivityResultLauncher<Intent>,
253 menuTag: String?,
254 gameId: String?
255 ) {
256 val settings = Intent(context, SettingsActivity::class.java)
257 settings.putExtra(ARG_MENU_TAG, menuTag)
258 settings.putExtra(ARG_GAME_ID, gameId)
259 launcher.launch(settings)
260 }
261 } 197 }
262} 198}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
deleted file mode 100644
index 93e677b21..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
+++ /dev/null
@@ -1,90 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import android.content.Context
7import android.os.Bundle
8import android.text.TextUtils
9import java.io.File
10import org.yuzu.yuzu_emu.NativeLibrary
11import org.yuzu.yuzu_emu.features.settings.model.Settings
12import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
13import org.yuzu.yuzu_emu.utils.DirectoryInitialization
14import org.yuzu.yuzu_emu.utils.Log
15
16class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
17 val settings: Settings get() = activityView.settings
18
19 private var shouldSave = false
20 private lateinit var menuTag: String
21 private lateinit var gameId: String
22
23 fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
24 this.menuTag = menuTag
25 this.gameId = gameId
26 if (savedInstanceState != null) {
27 shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
28 }
29 }
30
31 fun onStart() {
32 prepareDirectoriesIfNeeded()
33 }
34
35 private fun loadSettingsUI() {
36 if (!settings.isLoaded) {
37 if (!TextUtils.isEmpty(gameId)) {
38 settings.loadSettings(gameId, activityView)
39 } else {
40 settings.loadSettings(activityView)
41 }
42 }
43 activityView.showSettingsFragment(menuTag, false, gameId)
44 activityView.onSettingsFileLoaded()
45 }
46
47 private fun prepareDirectoriesIfNeeded() {
48 val configFile =
49 File(
50 "${DirectoryInitialization.userDirectory}/config/" +
51 "${SettingsFile.FILE_NAME_CONFIG}.ini"
52 )
53 if (!configFile.exists()) {
54 Log.error(
55 "${DirectoryInitialization.userDirectory}/config/" +
56 "${SettingsFile.FILE_NAME_CONFIG}.ini"
57 )
58 Log.error("yuzu config file could not be found!")
59 }
60
61 if (!DirectoryInitialization.areDirectoriesReady) {
62 DirectoryInitialization.start(activityView as Context)
63 }
64 loadSettingsUI()
65 }
66
67 fun onStop(finishing: Boolean) {
68 if (finishing && shouldSave) {
69 Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
70 settings.saveSettings(activityView)
71 }
72 NativeLibrary.reloadSettings()
73 }
74
75 fun onSettingChanged() {
76 shouldSave = true
77 }
78
79 fun onSettingsReset() {
80 shouldSave = false
81 }
82
83 fun saveState(outState: Bundle) {
84 outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
85 }
86
87 companion object {
88 private const val KEY_SHOULD_SAVE = "should_save"
89 }
90}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
deleted file mode 100644
index c186fc388..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
+++ /dev/null
@@ -1,57 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import org.yuzu.yuzu_emu.features.settings.model.Settings
7
8/**
9 * Abstraction for the Activity that manages SettingsFragments.
10 */
11interface SettingsActivityView {
12 /**
13 * Show a new SettingsFragment.
14 *
15 * @param menuTag Identifier for the settings group that should be displayed.
16 * @param addToStack Whether or not this fragment should replace a previous one.
17 */
18 fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
19
20 /**
21 * Called by a contained Fragment to get access to the Setting HashMap
22 * loaded from disk, so that each Fragment doesn't need to perform its own
23 * read operation.
24 *
25 * @return A HashMap of Settings.
26 */
27 val settings: Settings
28
29 /**
30 * Called when a load operation completes.
31 */
32 fun onSettingsFileLoaded()
33
34 /**
35 * Called when a load operation fails.
36 */
37 fun onSettingsFileNotFound()
38
39 /**
40 * Display a popup text message on screen.
41 *
42 * @param message The contents of the onscreen message.
43 * @param is_long Whether this should be a long Toast or short one.
44 */
45 fun showToastMessage(message: String, is_long: Boolean)
46
47 /**
48 * End the activity.
49 */
50 fun finish()
51
52 /**
53 * Called by a containing Fragment to tell the Activity that a setting was changed;
54 * unless this has been called, the Activity will not save to disk.
55 */
56 fun onSettingChanged()
57}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index ce0b92c90..a7a029fc1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -4,51 +4,54 @@
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context 6import android.content.Context
7import android.content.DialogInterface
8import android.icu.util.Calendar 7import android.icu.util.Calendar
9import android.icu.util.TimeZone 8import android.icu.util.TimeZone
10import android.text.format.DateFormat 9import android.text.format.DateFormat
11import android.view.LayoutInflater 10import android.view.LayoutInflater
12import android.view.ViewGroup 11import android.view.ViewGroup
13import android.widget.TextView 12import androidx.fragment.app.Fragment
14import androidx.appcompat.app.AlertDialog 13import androidx.lifecycle.Lifecycle
15import androidx.appcompat.app.AppCompatActivity 14import androidx.lifecycle.ViewModelProvider
16import androidx.recyclerview.widget.RecyclerView 15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
17import androidx.navigation.findNavController
18import androidx.recyclerview.widget.AsyncDifferConfig
19import androidx.recyclerview.widget.DiffUtil
20import androidx.recyclerview.widget.ListAdapter
17import com.google.android.material.datepicker.MaterialDatePicker 21import com.google.android.material.datepicker.MaterialDatePicker
18import com.google.android.material.dialog.MaterialAlertDialogBuilder
19import com.google.android.material.slider.Slider
20import com.google.android.material.timepicker.MaterialTimePicker 22import com.google.android.material.timepicker.MaterialTimePicker
21import com.google.android.material.timepicker.TimeFormat 23import com.google.android.material.timepicker.TimeFormat
24import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.DialogSliderBinding 26import org.yuzu.yuzu_emu.SettingsNavigationDirections
24import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding 27import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
25import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding 28import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
26import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding 29import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
27import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
28import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
29import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
30import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
31import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
32import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
33import org.yuzu.yuzu_emu.features.settings.model.view.* 30import org.yuzu.yuzu_emu.features.settings.model.view.*
34import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* 31import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
32import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
33import org.yuzu.yuzu_emu.model.SettingsViewModel
35 34
36class SettingsAdapter( 35class SettingsAdapter(
37 private val fragmentView: SettingsFragmentView, 36 private val fragment: Fragment,
38 private val context: Context 37 private val context: Context
39) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener { 38) : ListAdapter<SettingsItem, SettingViewHolder>(
40 private var settings: ArrayList<SettingsItem>? = null 39 AsyncDifferConfig.Builder(DiffCallback()).build()
41 private var clickedItem: SettingsItem? = null 40) {
42 private var clickedPosition: Int 41 private val settingsViewModel: SettingsViewModel
43 private var dialog: AlertDialog? = null 42 get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
44 private var sliderProgress = 0
45 private var textSliderValue: TextView? = null
46
47 private var defaultCancelListener =
48 DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
49 43
50 init { 44 init {
51 clickedPosition = -1 45 fragment.viewLifecycleOwner.lifecycleScope.launch {
46 fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
47 settingsViewModel.adapterItemChanged.collect {
48 if (it != -1) {
49 notifyItemChanged(it)
50 settingsViewModel.setAdapterItemChanged(-1)
51 }
52 }
53 }
54 }
52 } 55 }
53 56
54 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { 57 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
@@ -90,67 +93,41 @@ class SettingsAdapter(
90 } 93 }
91 94
92 override fun onBindViewHolder(holder: SettingViewHolder, position: Int) { 95 override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
93 holder.bind(getItem(position)) 96 holder.bind(currentList[position])
94 } 97 }
95 98
96 private fun getItem(position: Int): SettingsItem { 99 override fun getItemCount(): Int = currentList.size
97 return settings!![position]
98 }
99
100 override fun getItemCount(): Int {
101 return if (settings != null) {
102 settings!!.size
103 } else {
104 0
105 }
106 }
107 100
108 override fun getItemViewType(position: Int): Int { 101 override fun getItemViewType(position: Int): Int {
109 return getItem(position).type 102 return currentList[position].type
110 }
111
112 fun setSettingsList(settings: ArrayList<SettingsItem>?) {
113 this.settings = settings
114 notifyDataSetChanged()
115 }
116
117 fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
118 val setting = item.setChecked(checked)
119 fragmentView.putSetting(setting)
120 fragmentView.onSettingChanged()
121 } 103 }
122 104
123 private fun onSingleChoiceClick(item: SingleChoiceSetting) { 105 fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
124 clickedItem = item 106 item.checked = checked
125 val value = getSelectionForSingleChoiceValue(item) 107 settingsViewModel.setShouldReloadSettingsList(true)
126 dialog = MaterialAlertDialogBuilder(context) 108 settingsViewModel.shouldSave = true
127 .setTitle(item.nameId)
128 .setSingleChoiceItems(item.choicesId, value, this)
129 .show()
130 } 109 }
131 110
132 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { 111 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
133 clickedPosition = position 112 SettingsDialogFragment.newInstance(
134 onSingleChoiceClick(item) 113 settingsViewModel,
135 } 114 item,
136 115 SettingsItem.TYPE_SINGLE_CHOICE,
137 private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) { 116 position
138 clickedItem = item 117 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
139 dialog = MaterialAlertDialogBuilder(context)
140 .setTitle(item.nameId)
141 .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
142 .show()
143 } 118 }
144 119
145 fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) { 120 fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
146 clickedPosition = position 121 SettingsDialogFragment.newInstance(
147 onStringSingleChoiceClick(item) 122 settingsViewModel,
123 item,
124 SettingsItem.TYPE_STRING_SINGLE_CHOICE,
125 position
126 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
148 } 127 }
149 128
150 fun onDateTimeClick(item: DateTimeSetting, position: Int) { 129 fun onDateTimeClick(item: DateTimeSetting, position: Int) {
151 clickedItem = item 130 val storedTime = item.value * 1000
152 clickedPosition = position
153 val storedTime = java.lang.Long.decode(item.value) * 1000
154 131
155 // Helper to extract hour and minute from epoch time 132 // Helper to extract hour and minute from epoch time
156 val calendar: Calendar = Calendar.getInstance() 133 val calendar: Calendar = Calendar.getInstance()
@@ -158,7 +135,7 @@ class SettingsAdapter(
158 calendar.timeZone = TimeZone.getTimeZone("UTC") 135 calendar.timeZone = TimeZone.getTimeZone("UTC")
159 136
160 var timeFormat: Int = TimeFormat.CLOCK_12H 137 var timeFormat: Int = TimeFormat.CLOCK_12H
161 if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) { 138 if (DateFormat.is24HourFormat(context)) {
162 timeFormat = TimeFormat.CLOCK_24H 139 timeFormat = TimeFormat.CLOCK_24H
163 } 140 }
164 141
@@ -175,7 +152,7 @@ class SettingsAdapter(
175 152
176 datePicker.addOnPositiveButtonClickListener { 153 datePicker.addOnPositiveButtonClickListener {
177 timePicker.show( 154 timePicker.show(
178 (fragmentView.activityView as AppCompatActivity).supportFragmentManager, 155 fragment.childFragmentManager,
179 "TimePicker" 156 "TimePicker"
180 ) 157 )
181 } 158 }
@@ -183,157 +160,50 @@ class SettingsAdapter(
183 var epochTime: Long = datePicker.selection!! / 1000 160 var epochTime: Long = datePicker.selection!! / 1000
184 epochTime += timePicker.hour.toLong() * 60 * 60 161 epochTime += timePicker.hour.toLong() * 60 * 60
185 epochTime += timePicker.minute.toLong() * 60 162 epochTime += timePicker.minute.toLong() * 60
186 val rtcString = epochTime.toString() 163 if (item.value != epochTime) {
187 if (item.value != rtcString) { 164 settingsViewModel.shouldSave = true
188 fragmentView.onSettingChanged() 165 notifyItemChanged(position)
166 item.value = epochTime
189 } 167 }
190 notifyItemChanged(clickedPosition)
191 val setting = item.setSelectedValue(rtcString)
192 fragmentView.putSetting(setting)
193 clickedItem = null
194 } 168 }
195 datePicker.show( 169 datePicker.show(
196 (fragmentView.activityView as AppCompatActivity).supportFragmentManager, 170 fragment.childFragmentManager,
197 "DatePicker" 171 "DatePicker"
198 ) 172 )
199 } 173 }
200 174
201 fun onSliderClick(item: SliderSetting, position: Int) { 175 fun onSliderClick(item: SliderSetting, position: Int) {
202 clickedItem = item 176 SettingsDialogFragment.newInstance(
203 clickedPosition = position 177 settingsViewModel,
204 sliderProgress = item.selectedValue 178 item,
205 179 SettingsItem.TYPE_SLIDER,
206 val inflater = LayoutInflater.from(context) 180 position
207 val sliderBinding = DialogSliderBinding.inflate(inflater) 181 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
208
209 textSliderValue = sliderBinding.textValue
210 textSliderValue!!.text = sliderProgress.toString()
211 sliderBinding.textUnits.text = item.units
212
213 sliderBinding.slider.apply {
214 valueFrom = item.min.toFloat()
215 valueTo = item.max.toFloat()
216 value = sliderProgress.toFloat()
217 addOnChangeListener { _: Slider, value: Float, _: Boolean ->
218 sliderProgress = value.toInt()
219 textSliderValue!!.text = sliderProgress.toString()
220 }
221 }
222
223 dialog = MaterialAlertDialogBuilder(context)
224 .setTitle(item.nameId)
225 .setView(sliderBinding.root)
226 .setPositiveButton(android.R.string.ok, this)
227 .setNegativeButton(android.R.string.cancel, defaultCancelListener)
228 .setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
229 sliderBinding.slider.value = item.defaultValue!!.toFloat()
230 onClick(dialog, which)
231 }
232 .show()
233 } 182 }
234 183
235 fun onSubmenuClick(item: SubmenuSetting) { 184 fun onSubmenuClick(item: SubmenuSetting) {
236 fragmentView.loadSubMenu(item.menuKey) 185 val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
186 fragment.view?.findNavController()?.navigate(action)
237 } 187 }
238 188
239 override fun onClick(dialog: DialogInterface, which: Int) { 189 fun onLongClick(item: SettingsItem, position: Int): Boolean {
240 when (clickedItem) { 190 SettingsDialogFragment.newInstance(
241 is SingleChoiceSetting -> { 191 settingsViewModel,
242 val scSetting = clickedItem as SingleChoiceSetting 192 item,
243 val value = getValueForSingleChoiceSelection(scSetting, which) 193 SettingsDialogFragment.TYPE_RESET_SETTING,
244 if (scSetting.selectedValue != value) { 194 position
245 fragmentView.onSettingChanged() 195 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
246 }
247
248 // Get the backing Setting, which may be null (if for example it was missing from the file)
249 val setting = scSetting.setSelectedValue(value)
250 fragmentView.putSetting(setting)
251 closeDialog()
252 }
253
254 is StringSingleChoiceSetting -> {
255 val scSetting = clickedItem as StringSingleChoiceSetting
256 val value = scSetting.getValueAt(which)
257 if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
258 val setting = scSetting.setSelectedValue(value!!)
259 fragmentView.putSetting(setting)
260 closeDialog()
261 }
262
263 is SliderSetting -> {
264 val sliderSetting = clickedItem as SliderSetting
265 if (sliderSetting.selectedValue != sliderProgress) {
266 fragmentView.onSettingChanged()
267 }
268 if (sliderSetting.setting is FloatSetting) {
269 val value = sliderProgress.toFloat()
270 val setting = sliderSetting.setSelectedValue(value)
271 fragmentView.putSetting(setting)
272 } else {
273 val setting = sliderSetting.setSelectedValue(sliderProgress)
274 fragmentView.putSetting(setting)
275 }
276 closeDialog()
277 }
278 }
279 clickedItem = null
280 sliderProgress = -1
281 }
282
283 fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
284 MaterialAlertDialogBuilder(context)
285 .setMessage(R.string.reset_setting_confirmation)
286 .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
287 when (setting) {
288 is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
289 is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
290 is AbstractIntSetting -> setting.int = setting.defaultValue as Int
291 is AbstractStringSetting -> setting.string = setting.defaultValue as String
292 }
293 notifyItemChanged(position)
294 fragmentView.onSettingChanged()
295 }
296 .setNegativeButton(android.R.string.cancel, null)
297 .show()
298 196
299 return true 197 return true
300 } 198 }
301 199
302 fun closeDialog() { 200 private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
303 if (dialog != null) { 201 override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
304 if (clickedPosition != -1) { 202 return oldItem.setting.key == newItem.setting.key
305 notifyItemChanged(clickedPosition)
306 clickedPosition = -1
307 }
308 dialog!!.dismiss()
309 dialog = null
310 } 203 }
311 }
312 204
313 private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { 205 override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
314 val valuesId = item.valuesId 206 return oldItem.setting.key == newItem.setting.key
315 return if (valuesId > 0) {
316 val valuesArray = context.resources.getIntArray(valuesId)
317 valuesArray[which]
318 } else {
319 which
320 }
321 }
322
323 private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
324 val value = item.selectedValue
325 val valuesId = item.valuesId
326 if (valuesId > 0) {
327 val valuesArray = context.resources.getIntArray(valuesId)
328 for (index in valuesArray.indices) {
329 val current = valuesArray[index]
330 if (current == value) {
331 return index
332 }
333 }
334 } else {
335 return value
336 } 207 }
337 return -1
338 } 208 }
339} 209}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 70a74c4dd..70d8ec14b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -3,40 +3,49 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context 6import android.annotation.SuppressLint
7import android.os.Bundle 7import android.os.Bundle
8import android.view.LayoutInflater 8import android.view.LayoutInflater
9import android.view.View 9import android.view.View
10import android.view.ViewGroup 10import android.view.ViewGroup
11import android.view.ViewGroup.MarginLayoutParams
11import androidx.core.view.ViewCompat 12import androidx.core.view.ViewCompat
12import androidx.core.view.WindowInsetsCompat 13import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding 14import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment 15import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels
17import androidx.lifecycle.Lifecycle
18import androidx.lifecycle.lifecycleScope
19import androidx.lifecycle.repeatOnLifecycle
20import androidx.navigation.findNavController
21import androidx.navigation.fragment.navArgs
15import androidx.recyclerview.widget.LinearLayoutManager 22import androidx.recyclerview.widget.LinearLayoutManager
16import com.google.android.material.divider.MaterialDividerItemDecoration 23import com.google.android.material.divider.MaterialDividerItemDecoration
24import com.google.android.material.transition.MaterialSharedAxis
25import kotlinx.coroutines.flow.collectLatest
26import kotlinx.coroutines.launch
27import org.yuzu.yuzu_emu.R
17import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding 28import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
18import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting 29import org.yuzu.yuzu_emu.features.settings.model.Settings
19import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 30import org.yuzu.yuzu_emu.model.SettingsViewModel
20 31
21class SettingsFragment : Fragment(), SettingsFragmentView { 32class SettingsFragment : Fragment() {
22 override var activityView: SettingsActivityView? = null 33 private lateinit var presenter: SettingsFragmentPresenter
23
24 private val fragmentPresenter = SettingsFragmentPresenter(this)
25 private var settingsAdapter: SettingsAdapter? = null 34 private var settingsAdapter: SettingsAdapter? = null
26 35
27 private var _binding: FragmentSettingsBinding? = null 36 private var _binding: FragmentSettingsBinding? = null
28 private val binding get() = _binding!! 37 private val binding get() = _binding!!
29 38
30 override fun onAttach(context: Context) { 39 private val args by navArgs<SettingsFragmentArgs>()
31 super.onAttach(context) 40
32 activityView = requireActivity() as SettingsActivityView 41 private val settingsViewModel: SettingsViewModel by activityViewModels()
33 }
34 42
35 override fun onCreate(savedInstanceState: Bundle?) { 43 override fun onCreate(savedInstanceState: Bundle?) {
36 super.onCreate(savedInstanceState) 44 super.onCreate(savedInstanceState)
37 val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG) 45 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
38 val gameId = requireArguments().getString(ARGUMENT_GAME_ID) 46 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
39 fragmentPresenter.onCreate(menuTag!!, gameId!!) 47 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
48 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
40 } 49 }
41 50
42 override fun onCreateView( 51 override fun onCreateView(
@@ -48,8 +57,17 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
48 return binding.root 57 return binding.root
49 } 58 }
50 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
51 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
52 settingsAdapter = SettingsAdapter(this, requireActivity()) 63 settingsAdapter = SettingsAdapter(this, requireContext())
64 presenter = SettingsFragmentPresenter(
65 settingsViewModel,
66 settingsAdapter!!,
67 args.menuTag
68 )
69
70 binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
53 val dividerDecoration = MaterialDividerItemDecoration( 71 val dividerDecoration = MaterialDividerItemDecoration(
54 requireContext(), 72 requireContext(),
55 LinearLayoutManager.VERTICAL 73 LinearLayoutManager.VERTICAL
@@ -57,71 +75,89 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
57 dividerDecoration.isLastItemDecorated = false 75 dividerDecoration.isLastItemDecorated = false
58 binding.listSettings.apply { 76 binding.listSettings.apply {
59 adapter = settingsAdapter 77 adapter = settingsAdapter
60 layoutManager = LinearLayoutManager(activity) 78 layoutManager = LinearLayoutManager(requireContext())
61 addItemDecoration(dividerDecoration) 79 addItemDecoration(dividerDecoration)
62 } 80 }
63 fragmentPresenter.onViewCreated()
64 81
65 setInsets() 82 binding.toolbarSettings.setNavigationOnClickListener {
66 } 83 settingsViewModel.setShouldNavigateBack(true)
67
68 override fun onDetach() {
69 super.onDetach()
70 activityView = null
71 if (settingsAdapter != null) {
72 settingsAdapter!!.closeDialog()
73 } 84 }
74 }
75
76 override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
77 settingsAdapter!!.setSettingsList(settingsList)
78 }
79 85
80 override fun loadSettingsList() { 86 viewLifecycleOwner.lifecycleScope.apply {
81 fragmentPresenter.loadSettingsList() 87 launch {
82 } 88 repeatOnLifecycle(Lifecycle.State.CREATED) {
89 settingsViewModel.shouldReloadSettingsList.collectLatest {
90 if (it) {
91 settingsViewModel.setShouldReloadSettingsList(false)
92 presenter.loadSettingsList()
93 }
94 }
95 }
96 }
97 launch {
98 settingsViewModel.isUsingSearch.collectLatest {
99 if (it) {
100 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
101 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
102 } else {
103 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
104 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
105 }
106 }
107 }
108 }
83 109
84 override fun loadSubMenu(menuKey: String) { 110 if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
85 activityView!!.showSettingsFragment( 111 binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
86 menuKey, 112 binding.toolbarSettings.setOnMenuItemClickListener {
87 true, 113 when (it.itemId) {
88 requireArguments().getString(ARGUMENT_GAME_ID)!! 114 R.id.action_search -> {
89 ) 115 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
90 } 116 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
117 view.findNavController()
118 .navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
119 true
120 }
121
122 else -> false
123 }
124 }
125 }
91 126
92 override fun showToastMessage(message: String?, is_long: Boolean) { 127 presenter.onViewCreated()
93 activityView!!.showToastMessage(message!!, is_long)
94 }
95 128
96 override fun putSetting(setting: AbstractSetting) { 129 setInsets()
97 fragmentPresenter.putSetting(setting)
98 } 130 }
99 131
100 override fun onSettingChanged() { 132 override fun onResume() {
101 activityView!!.onSettingChanged() 133 super.onResume()
134 settingsViewModel.setIsUsingSearch(false)
102 } 135 }
103 136
104 private fun setInsets() { 137 private fun setInsets() {
105 ViewCompat.setOnApplyWindowInsetsListener( 138 ViewCompat.setOnApplyWindowInsetsListener(
106 binding.listSettings 139 binding.root
107 ) { view: View, windowInsets: WindowInsetsCompat -> 140 ) { _: View, windowInsets: WindowInsetsCompat ->
108 val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 141 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
109 view.updatePadding(bottom = insets.bottom) 142 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
143
144 val leftInsets = barInsets.left + cutoutInsets.left
145 val rightInsets = barInsets.right + cutoutInsets.right
146
147 val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
148 val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
149 mlpSettingsList.leftMargin = sideMargin + leftInsets
150 mlpSettingsList.rightMargin = sideMargin + rightInsets
151 binding.listSettings.layoutParams = mlpSettingsList
152 binding.listSettings.updatePadding(
153 bottom = barInsets.bottom
154 )
155
156 val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
157 mlpAppBar.leftMargin = leftInsets
158 mlpAppBar.rightMargin = rightInsets
159 binding.appbarSettings.layoutParams = mlpAppBar
110 windowInsets 160 windowInsets
111 } 161 }
112 } 162 }
113
114 companion object {
115 private const val ARGUMENT_MENU_TAG = "menu_tag"
116 private const val ARGUMENT_GAME_ID = "game_id"
117
118 fun newInstance(menuTag: String?, gameId: String?): Fragment {
119 val fragment = SettingsFragment()
120 val arguments = Bundle()
121 arguments.putString(ARGUMENT_MENU_TAG, menuTag)
122 arguments.putString(ARGUMENT_GAME_ID, gameId)
123 fragment.arguments = arguments
124 return fragment
125 }
126 }
127} 163}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 59c1d9d54..766414a6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -3,401 +3,155 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context
6import android.content.SharedPreferences 7import android.content.SharedPreferences
7import android.os.Build 8import android.os.Build
8import android.text.TextUtils 9import android.widget.Toast
9import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
10import org.yuzu.yuzu_emu.R 11import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 12import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting 13import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
13import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting 14import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
14import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
15import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting 15import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
16import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
16import org.yuzu.yuzu_emu.features.settings.model.IntSetting 17import org.yuzu.yuzu_emu.features.settings.model.IntSetting
18import org.yuzu.yuzu_emu.features.settings.model.LongSetting
17import org.yuzu.yuzu_emu.features.settings.model.Settings 19import org.yuzu.yuzu_emu.features.settings.model.Settings
18import org.yuzu.yuzu_emu.features.settings.model.StringSetting 20import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
19import org.yuzu.yuzu_emu.features.settings.model.view.* 21import org.yuzu.yuzu_emu.features.settings.model.view.*
20import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 22import org.yuzu.yuzu_emu.model.SettingsViewModel
21import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment 23import org.yuzu.yuzu_emu.utils.NativeConfig
22import org.yuzu.yuzu_emu.utils.ThemeHelper
23 24
24class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { 25class SettingsFragmentPresenter(
25 private var menuTag: String? = null 26 private val settingsViewModel: SettingsViewModel,
26 private lateinit var gameId: String 27 private val adapter: SettingsAdapter,
27 private var settingsList: ArrayList<SettingsItem>? = null 28 private var menuTag: Settings.MenuTag
29) {
30 private var settingsList = ArrayList<SettingsItem>()
28 31
29 private val settingsActivity get() = fragmentView.activityView as SettingsActivity 32 private val preferences: SharedPreferences
30 private val settings get() = fragmentView.activityView!!.settings 33 get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
31 34
32 private lateinit var preferences: SharedPreferences 35 private val context: Context get() = YuzuApplication.appContext
33 36
34 fun onCreate(menuTag: String, gameId: String) { 37 // Extension for populating settings list based on paired settings
35 this.gameId = gameId 38 fun ArrayList<SettingsItem>.add(key: String) {
36 this.menuTag = menuTag 39 val item = SettingsItem.settingsItems[key]!!
40 val pairedSettingKey = item.setting.pairedSettingKey
41 if (pairedSettingKey.isNotEmpty()) {
42 val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
43 if (!pairedSettingValue) return
44 }
45 add(item)
37 } 46 }
38 47
39 fun onViewCreated() { 48 fun onViewCreated() {
40 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
41 loadSettingsList() 49 loadSettingsList()
42 } 50 }
43 51
44 fun putSetting(setting: AbstractSetting) {
45 if (setting.section == null || setting.key == null) {
46 return
47 }
48
49 val section = settings.getSection(setting.section!!)!!
50 if (section.getSetting(setting.key!!) == null) {
51 section.putSetting(setting)
52 }
53 }
54
55 fun loadSettingsList() { 52 fun loadSettingsList() {
56 if (!TextUtils.isEmpty(gameId)) {
57 settingsActivity.setToolbarTitle("Game Settings: $gameId")
58 }
59 val sl = ArrayList<SettingsItem>() 53 val sl = ArrayList<SettingsItem>()
60 if (menuTag == null) {
61 return
62 }
63 when (menuTag) { 54 when (menuTag) {
64 SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) 55 Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
65 Settings.SECTION_GENERAL -> addGeneralSettings(sl) 56 Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
66 Settings.SECTION_SYSTEM -> addSystemSettings(sl) 57 Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
67 Settings.SECTION_RENDERER -> addGraphicsSettings(sl) 58 Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
68 Settings.SECTION_AUDIO -> addAudioSettings(sl) 59 Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
69 Settings.SECTION_THEME -> addThemeSettings(sl) 60 Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
70 Settings.SECTION_DEBUG -> addDebugSettings(sl) 61 Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
71 else -> { 62 else -> {
72 fragmentView.showToastMessage("Unimplemented menu", false) 63 val context = YuzuApplication.appContext
64 Toast.makeText(
65 context,
66 context.getString(R.string.unimplemented_menu),
67 Toast.LENGTH_SHORT
68 ).show()
73 return 69 return
74 } 70 }
75 } 71 }
76 settingsList = sl 72 settingsList = sl
77 fragmentView.showSettingsList(settingsList!!) 73 adapter.submitList(settingsList)
78 } 74 }
79 75
80 private fun addConfigSettings(sl: ArrayList<SettingsItem>) { 76 private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
81 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
82 sl.apply { 77 sl.apply {
83 add( 78 add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
84 SubmenuSetting( 79 add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
85 R.string.preferences_general, 80 add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
86 0, 81 add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
87 Settings.SECTION_GENERAL 82 add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
88 ) 83 add(
89 ) 84 RunnableSetting(R.string.reset_to_default, 0, false) {
90 add( 85 settingsViewModel.setShouldShowResetSettingsDialog(true)
91 SubmenuSetting(
92 R.string.preferences_system,
93 0,
94 Settings.SECTION_SYSTEM
95 )
96 )
97 add(
98 SubmenuSetting(
99 R.string.preferences_graphics,
100 0,
101 Settings.SECTION_RENDERER
102 )
103 )
104 add(
105 SubmenuSetting(
106 R.string.preferences_audio,
107 0,
108 Settings.SECTION_AUDIO
109 )
110 )
111 add(
112 SubmenuSetting(
113 R.string.preferences_debug,
114 0,
115 Settings.SECTION_DEBUG
116 )
117 )
118 add(
119 RunnableSetting(
120 R.string.reset_to_default,
121 0,
122 false
123 ) {
124 ResetSettingsDialogFragment().show(
125 settingsActivity.supportFragmentManager,
126 ResetSettingsDialogFragment.TAG
127 )
128 } 86 }
129 ) 87 )
130 } 88 }
131 } 89 }
132 90
133 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { 91 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
134 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general))
135 sl.apply { 92 sl.apply {
136 add( 93 add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
137 SwitchSetting( 94 add(ShortSetting.RENDERER_SPEED_LIMIT.key)
138 IntSetting.RENDERER_USE_SPEED_LIMIT, 95 add(IntSetting.CPU_ACCURACY.key)
139 R.string.frame_limit_enable, 96 add(BooleanSetting.PICTURE_IN_PICTURE.key)
140 R.string.frame_limit_enable_description,
141 IntSetting.RENDERER_USE_SPEED_LIMIT.key,
142 IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
143 )
144 )
145 add(
146 SliderSetting(
147 IntSetting.RENDERER_SPEED_LIMIT,
148 R.string.frame_limit_slider,
149 R.string.frame_limit_slider_description,
150 1,
151 200,
152 "%",
153 IntSetting.RENDERER_SPEED_LIMIT.key,
154 IntSetting.RENDERER_SPEED_LIMIT.defaultValue
155 )
156 )
157 add(
158 SingleChoiceSetting(
159 IntSetting.CPU_ACCURACY,
160 R.string.cpu_accuracy,
161 0,
162 R.array.cpuAccuracyNames,
163 R.array.cpuAccuracyValues,
164 IntSetting.CPU_ACCURACY.key,
165 IntSetting.CPU_ACCURACY.defaultValue
166 )
167 )
168 add(
169 SwitchSetting(
170 BooleanSetting.PICTURE_IN_PICTURE,
171 R.string.picture_in_picture,
172 R.string.picture_in_picture_description,
173 BooleanSetting.PICTURE_IN_PICTURE.key,
174 BooleanSetting.PICTURE_IN_PICTURE.defaultValue
175 )
176 )
177 } 97 }
178 } 98 }
179 99
180 private fun addSystemSettings(sl: ArrayList<SettingsItem>) { 100 private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
181 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system))
182 sl.apply { 101 sl.apply {
183 add( 102 add(BooleanSetting.USE_DOCKED_MODE.key)
184 SwitchSetting( 103 add(IntSetting.REGION_INDEX.key)
185 IntSetting.USE_DOCKED_MODE, 104 add(IntSetting.LANGUAGE_INDEX.key)
186 R.string.use_docked_mode, 105 add(BooleanSetting.USE_CUSTOM_RTC.key)
187 R.string.use_docked_mode_description, 106 add(LongSetting.CUSTOM_RTC.key)
188 IntSetting.USE_DOCKED_MODE.key,
189 IntSetting.USE_DOCKED_MODE.defaultValue
190 )
191 )
192 add(
193 SingleChoiceSetting(
194 IntSetting.REGION_INDEX,
195 R.string.emulated_region,
196 0,
197 R.array.regionNames,
198 R.array.regionValues,
199 IntSetting.REGION_INDEX.key,
200 IntSetting.REGION_INDEX.defaultValue
201 )
202 )
203 add(
204 SingleChoiceSetting(
205 IntSetting.LANGUAGE_INDEX,
206 R.string.emulated_language,
207 0,
208 R.array.languageNames,
209 R.array.languageValues,
210 IntSetting.LANGUAGE_INDEX.key,
211 IntSetting.LANGUAGE_INDEX.defaultValue
212 )
213 )
214 add(
215 SwitchSetting(
216 BooleanSetting.USE_CUSTOM_RTC,
217 R.string.use_custom_rtc,
218 R.string.use_custom_rtc_description,
219 BooleanSetting.USE_CUSTOM_RTC.key,
220 BooleanSetting.USE_CUSTOM_RTC.defaultValue
221 )
222 )
223 add(
224 DateTimeSetting(
225 StringSetting.CUSTOM_RTC,
226 R.string.set_custom_rtc,
227 0,
228 StringSetting.CUSTOM_RTC.key,
229 StringSetting.CUSTOM_RTC.defaultValue
230 )
231 )
232 } 107 }
233 } 108 }
234 109
235 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { 110 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
236 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
237 sl.apply { 111 sl.apply {
238 add( 112 add(IntSetting.RENDERER_ACCURACY.key)
239 SingleChoiceSetting( 113 add(IntSetting.RENDERER_RESOLUTION.key)
240 IntSetting.RENDERER_ACCURACY, 114 add(IntSetting.RENDERER_VSYNC.key)
241 R.string.renderer_accuracy, 115 add(IntSetting.RENDERER_SCALING_FILTER.key)
242 0, 116 add(IntSetting.RENDERER_ANTI_ALIASING.key)
243 R.array.rendererAccuracyNames, 117 add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
244 R.array.rendererAccuracyValues, 118 add(IntSetting.RENDERER_ASPECT_RATIO.key)
245 IntSetting.RENDERER_ACCURACY.key, 119 add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
246 IntSetting.RENDERER_ACCURACY.defaultValue 120 add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
247 ) 121 add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
248 ) 122 add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
249 add(
250 SingleChoiceSetting(
251 IntSetting.RENDERER_RESOLUTION,
252 R.string.renderer_resolution,
253 0,
254 R.array.rendererResolutionNames,
255 R.array.rendererResolutionValues,
256 IntSetting.RENDERER_RESOLUTION.key,
257 IntSetting.RENDERER_RESOLUTION.defaultValue
258 )
259 )
260 add(
261 SingleChoiceSetting(
262 IntSetting.RENDERER_VSYNC,
263 R.string.renderer_vsync,
264 0,
265 R.array.rendererVSyncNames,
266 R.array.rendererVSyncValues,
267 IntSetting.RENDERER_VSYNC.key,
268 IntSetting.RENDERER_VSYNC.defaultValue
269 )
270 )
271 add(
272 SingleChoiceSetting(
273 IntSetting.RENDERER_SCALING_FILTER,
274 R.string.renderer_scaling_filter,
275 0,
276 R.array.rendererScalingFilterNames,
277 R.array.rendererScalingFilterValues,
278 IntSetting.RENDERER_SCALING_FILTER.key,
279 IntSetting.RENDERER_SCALING_FILTER.defaultValue
280 )
281 )
282 add(
283 SingleChoiceSetting(
284 IntSetting.RENDERER_ANTI_ALIASING,
285 R.string.renderer_anti_aliasing,
286 0,
287 R.array.rendererAntiAliasingNames,
288 R.array.rendererAntiAliasingValues,
289 IntSetting.RENDERER_ANTI_ALIASING.key,
290 IntSetting.RENDERER_ANTI_ALIASING.defaultValue
291 )
292 )
293 add(
294 SingleChoiceSetting(
295 IntSetting.RENDERER_SCREEN_LAYOUT,
296 R.string.renderer_screen_layout,
297 0,
298 R.array.rendererScreenLayoutNames,
299 R.array.rendererScreenLayoutValues,
300 IntSetting.RENDERER_SCREEN_LAYOUT.key,
301 IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
302 )
303 )
304 add(
305 SingleChoiceSetting(
306 IntSetting.RENDERER_ASPECT_RATIO,
307 R.string.renderer_aspect_ratio,
308 0,
309 R.array.rendererAspectRatioNames,
310 R.array.rendererAspectRatioValues,
311 IntSetting.RENDERER_ASPECT_RATIO.key,
312 IntSetting.RENDERER_ASPECT_RATIO.defaultValue
313 )
314 )
315 add(
316 SwitchSetting(
317 IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
318 R.string.use_disk_shader_cache,
319 R.string.use_disk_shader_cache_description,
320 IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
321 IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
322 )
323 )
324 add(
325 SwitchSetting(
326 IntSetting.RENDERER_FORCE_MAX_CLOCK,
327 R.string.renderer_force_max_clock,
328 R.string.renderer_force_max_clock_description,
329 IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
330 IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
331 )
332 )
333 add(
334 SwitchSetting(
335 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
336 R.string.renderer_asynchronous_shaders,
337 R.string.renderer_asynchronous_shaders_description,
338 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
339 IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
340 )
341 )
342 add(
343 SwitchSetting(
344 IntSetting.RENDERER_REACTIVE_FLUSHING,
345 R.string.renderer_reactive_flushing,
346 R.string.renderer_reactive_flushing_description,
347 IntSetting.RENDERER_REACTIVE_FLUSHING.key,
348 IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
349 )
350 )
351 } 123 }
352 } 124 }
353 125
354 private fun addAudioSettings(sl: ArrayList<SettingsItem>) { 126 private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
355 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
356 sl.apply { 127 sl.apply {
357 add( 128 add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
358 StringSingleChoiceSetting( 129 add(ByteSetting.AUDIO_VOLUME.key)
359 StringSetting.AUDIO_OUTPUT_ENGINE,
360 R.string.audio_output_engine,
361 0,
362 settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
363 settingsActivity.resources.getStringArray(R.array.outputEngineValues),
364 StringSetting.AUDIO_OUTPUT_ENGINE.key,
365 StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
366 )
367 )
368 add(
369 SliderSetting(
370 IntSetting.AUDIO_VOLUME,
371 R.string.audio_volume,
372 R.string.audio_volume_description,
373 0,
374 100,
375 "%",
376 IntSetting.AUDIO_VOLUME.key,
377 IntSetting.AUDIO_VOLUME.defaultValue
378 )
379 )
380 } 130 }
381 } 131 }
382 132
383 private fun addThemeSettings(sl: ArrayList<SettingsItem>) { 133 private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
384 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
385 sl.apply { 134 sl.apply {
386 val theme: AbstractIntSetting = object : AbstractIntSetting { 135 val theme: AbstractIntSetting = object : AbstractIntSetting {
387 override var int: Int 136 override val int: Int
388 get() = preferences.getInt(Settings.PREF_THEME, 0) 137 get() = preferences.getInt(Settings.PREF_THEME, 0)
389 set(value) { 138
390 preferences.edit() 139 override fun setInt(value: Int) {
391 .putInt(Settings.PREF_THEME, value) 140 preferences.edit()
392 .apply() 141 .putInt(Settings.PREF_THEME, value)
393 settingsActivity.recreate() 142 .apply()
394 } 143 settingsViewModel.setShouldRecreate(true)
395 override val key: String? = null 144 }
396 override val section: String? = null 145
397 override val isRuntimeEditable: Boolean = false 146 override val key: String = Settings.PREF_THEME
398 override val valueAsString: String 147 override val category = Settings.Category.UiGeneral
399 get() = preferences.getInt(Settings.PREF_THEME, 0).toString() 148 override val isRuntimeModifiable: Boolean = false
400 override val defaultValue: Any = 0 149 override val defaultValue: Int = 0
150 override fun reset() {
151 preferences.edit()
152 .putInt(Settings.PREF_THEME, defaultValue)
153 .apply()
154 }
401 } 155 }
402 156
403 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 157 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -423,20 +177,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
423 } 177 }
424 178
425 val themeMode: AbstractIntSetting = object : AbstractIntSetting { 179 val themeMode: AbstractIntSetting = object : AbstractIntSetting {
426 override var int: Int 180 override val int: Int
427 get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) 181 get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
428 set(value) { 182
429 preferences.edit() 183 override fun setInt(value: Int) {
430 .putInt(Settings.PREF_THEME_MODE, value) 184 preferences.edit()
431 .apply() 185 .putInt(Settings.PREF_THEME_MODE, value)
432 ThemeHelper.setThemeMode(settingsActivity) 186 .apply()
433 } 187 settingsViewModel.setShouldRecreate(true)
434 override val key: String? = null 188 }
435 override val section: String? = null 189
436 override val isRuntimeEditable: Boolean = false 190 override val key: String = Settings.PREF_THEME_MODE
437 override val valueAsString: String 191 override val category = Settings.Category.UiGeneral
438 get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString() 192 override val isRuntimeModifiable: Boolean = false
439 override val defaultValue: Any = -1 193 override val defaultValue: Int = -1
194 override fun reset() {
195 preferences.edit()
196 .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
197 .apply()
198 settingsViewModel.setShouldRecreate(true)
199 }
440 } 200 }
441 201
442 add( 202 add(
@@ -450,21 +210,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
450 ) 210 )
451 211
452 val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { 212 val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
453 override var boolean: Boolean 213 override val boolean: Boolean
454 get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
455 set(value) {
456 preferences.edit()
457 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
458 .apply()
459 settingsActivity.recreate()
460 }
461 override val key: String? = null
462 override val section: String? = null
463 override val isRuntimeEditable: Boolean = false
464 override val valueAsString: String
465 get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) 214 get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
466 .toString() 215
467 override val defaultValue: Any = false 216 override fun setBoolean(value: Boolean) {
217 preferences.edit()
218 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
219 .apply()
220 settingsViewModel.setShouldRecreate(true)
221 }
222
223 override val key: String = Settings.PREF_BLACK_BACKGROUNDS
224 override val category = Settings.Category.UiGeneral
225 override val isRuntimeModifiable: Boolean = false
226 override val defaultValue: Boolean = false
227 override fun reset() {
228 preferences.edit()
229 .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
230 .apply()
231 settingsViewModel.setShouldRecreate(true)
232 }
468 } 233 }
469 234
470 add( 235 add(
@@ -478,62 +243,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
478 } 243 }
479 244
480 private fun addDebugSettings(sl: ArrayList<SettingsItem>) { 245 private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
481 settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
482 sl.apply { 246 sl.apply {
483 add(HeaderSetting(R.string.gpu)) 247 add(HeaderSetting(R.string.gpu))
484 add( 248 add(IntSetting.RENDERER_BACKEND.key)
485 SingleChoiceSetting( 249 add(BooleanSetting.RENDERER_DEBUG.key)
486 IntSetting.RENDERER_BACKEND,
487 R.string.renderer_api,
488 0,
489 R.array.rendererApiNames,
490 R.array.rendererApiValues,
491 IntSetting.RENDERER_BACKEND.key,
492 IntSetting.RENDERER_BACKEND.defaultValue
493 )
494 )
495 add(
496 SwitchSetting(
497 IntSetting.RENDERER_DEBUG,
498 R.string.renderer_debug,
499 R.string.renderer_debug_description,
500 IntSetting.RENDERER_DEBUG.key,
501 IntSetting.RENDERER_DEBUG.defaultValue
502 )
503 )
504 250
505 add(HeaderSetting(R.string.cpu)) 251 add(HeaderSetting(R.string.cpu))
506 add( 252 add(BooleanSetting.CPU_DEBUG_MODE.key)
507 SwitchSetting( 253 add(SettingsItem.FASTMEM_COMBINED)
508 BooleanSetting.CPU_DEBUG_MODE,
509 R.string.cpu_debug_mode,
510 R.string.cpu_debug_mode_description,
511 BooleanSetting.CPU_DEBUG_MODE.key,
512 BooleanSetting.CPU_DEBUG_MODE.defaultValue
513 )
514 )
515
516 val fastmem = object : AbstractBooleanSetting {
517 override var boolean: Boolean
518 get() =
519 BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
520 set(value) {
521 BooleanSetting.FASTMEM.boolean = value
522 BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
523 }
524 override val key: String? = null
525 override val section: String = Settings.SECTION_CPU
526 override val isRuntimeEditable: Boolean = false
527 override val valueAsString: String = ""
528 override val defaultValue: Any = true
529 }
530 add(
531 SwitchSetting(
532 fastmem,
533 R.string.fastmem,
534 0
535 )
536 )
537 } 254 }
538 } 255 }
539} 256}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
deleted file mode 100644
index 1ebe35eaa..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
+++ /dev/null
@@ -1,58 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.features.settings.ui
5
6import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
7import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
8
9/**
10 * Abstraction for a screen showing a list of settings. Instances of
11 * this type of view will each display a layer of the setting hierarchy.
12 */
13interface SettingsFragmentView {
14 /**
15 * Pass an ArrayList to the View so that it can be displayed on screen.
16 *
17 * @param settingsList The result of converting the HashMap to an ArrayList
18 */
19 fun showSettingsList(settingsList: ArrayList<SettingsItem>)
20
21 /**
22 * Instructs the Fragment to load the settings screen.
23 */
24 fun loadSettingsList()
25
26 /**
27 * @return The Fragment's containing activity.
28 */
29 val activityView: SettingsActivityView?
30
31 /**
32 * Tell the Fragment to tell the containing Activity to show a new
33 * Fragment containing a submenu of settings.
34 *
35 * @param menuKey Identifier for the settings group that should be shown.
36 */
37 fun loadSubMenu(menuKey: String)
38
39 /**
40 * Tell the Fragment to tell the containing activity to display a toast message.
41 *
42 * @param message Text to be shown in the Toast
43 * @param is_long Whether this should be a long Toast or short one.
44 */
45 fun showToastMessage(message: String?, is_long: Boolean)
46
47 /**
48 * Have the fragment add a setting to the HashMap.
49 *
50 * @param setting The (possibly previously missing) new setting.
51 */
52 fun putSetting(setting: AbstractSetting)
53
54 /**
55 * Have the fragment tell the containing Activity that a setting was modified.
56 */
57 fun onSettingChanged()
58}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
index 7955532ee..525f013f8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
@@ -25,12 +25,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
25 binding.textSettingDescription.setText(item.descriptionId) 25 binding.textSettingDescription.setText(item.descriptionId)
26 binding.textSettingDescription.visibility = View.VISIBLE 26 binding.textSettingDescription.visibility = View.VISIBLE
27 } else { 27 } else {
28 val epochTime = setting.value.toLong() 28 binding.textSettingDescription.visibility = View.GONE
29 val instant = Instant.ofEpochMilli(epochTime * 1000)
30 val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
31 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
32 binding.textSettingDescription.text = dateFormatter.format(zonedTime)
33 } 29 }
30
31 binding.textSettingValue.visibility = View.VISIBLE
32 val epochTime = setting.value
33 val instant = Instant.ofEpochMilli(epochTime * 1000)
34 val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
35 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
36 binding.textSettingValue.text = dateFormatter.format(zonedTime)
37
38 setStyle(setting.isEditable, binding)
34 } 39 }
35 40
36 override fun onClick(clicked: View) { 41 override fun onClick(clicked: View) {
@@ -41,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
41 46
42 override fun onLongClick(clicked: View): Boolean { 47 override fun onLongClick(clicked: View): Boolean {
43 if (setting.isEditable) { 48 if (setting.isEditable) {
44 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) 49 return adapter.onLongClick(setting, bindingAdapterPosition)
45 } 50 }
46 return false 51 return false
47 } 52 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
index 5dad5945f..83a2e94f1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
@@ -23,6 +23,9 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
23 } else { 23 } else {
24 binding.textSettingDescription.visibility = View.GONE 24 binding.textSettingDescription.visibility = View.GONE
25 } 25 }
26 binding.textSettingValue.visibility = View.GONE
27
28 setStyle(setting.isEditable, binding)
26 } 29 }
27 30
28 override fun onClick(clicked: View) { 31 override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
index f56460893..0fd1d2eaa 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
@@ -5,6 +5,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5 5
6import android.view.View 6import android.view.View
7import androidx.recyclerview.widget.RecyclerView 7import androidx.recyclerview.widget.RecyclerView
8import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
9import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 10import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter 11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
10 12
@@ -33,4 +35,18 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
33 abstract override fun onClick(clicked: View) 35 abstract override fun onClick(clicked: View)
34 36
35 abstract override fun onLongClick(clicked: View): Boolean 37 abstract override fun onLongClick(clicked: View): Boolean
38
39 fun setStyle(isEditable: Boolean, binding: ListItemSettingBinding) {
40 val opacity = if (isEditable) 1.0f else 0.5f
41 binding.textSettingName.alpha = opacity
42 binding.textSettingDescription.alpha = opacity
43 binding.textSettingValue.alpha = opacity
44 }
45
46 fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
47 binding.switchWidget.isEnabled = isEditable
48 val opacity = if (isEditable) 1.0f else 0.5f
49 binding.textSettingName.alpha = opacity
50 binding.textSettingDescription.alpha = opacity
51 }
36} 52}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index e4e321bd3..80d1b22c1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -17,28 +17,33 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
17 override fun bind(item: SettingsItem) { 17 override fun bind(item: SettingsItem) {
18 setting = item 18 setting = item
19 binding.textSettingName.setText(item.nameId) 19 binding.textSettingName.setText(item.nameId)
20 binding.textSettingDescription.visibility = View.VISIBLE
21 if (item.descriptionId != 0) { 20 if (item.descriptionId != 0) {
22 binding.textSettingDescription.setText(item.descriptionId) 21 binding.textSettingDescription.setText(item.descriptionId)
23 } else if (item is SingleChoiceSetting) { 22 binding.textSettingDescription.visibility = View.VISIBLE
24 val resMgr = binding.textSettingDescription.context.resources 23 } else {
24 binding.textSettingDescription.visibility = View.GONE
25 }
26
27 binding.textSettingValue.visibility = View.VISIBLE
28 if (item is SingleChoiceSetting) {
29 val resMgr = binding.textSettingValue.context.resources
25 val values = resMgr.getIntArray(item.valuesId) 30 val values = resMgr.getIntArray(item.valuesId)
26 for (i in values.indices) { 31 for (i in values.indices) {
27 if (values[i] == item.selectedValue) { 32 if (values[i] == item.selectedValue) {
28 binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] 33 binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
29 return 34 break
30 } 35 }
31 } 36 }
32 } else if (item is StringSingleChoiceSetting) { 37 } else if (item is StringSingleChoiceSetting) {
33 for (i in item.values!!.indices) { 38 for (i in item.values.indices) {
34 if (item.values[i] == item.selectedValue) { 39 if (item.values[i] == item.selectedValue) {
35 binding.textSettingDescription.text = item.choices[i] 40 binding.textSettingValue.text = item.choices[i]
36 return 41 break
37 } 42 }
38 } 43 }
39 } else {
40 binding.textSettingDescription.visibility = View.GONE
41 } 44 }
45
46 setStyle(setting.isEditable, binding)
42 } 47 }
43 48
44 override fun onClick(clicked: View) { 49 override fun onClick(clicked: View) {
@@ -61,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
61 66
62 override fun onLongClick(clicked: View): Boolean { 67 override fun onLongClick(clicked: View): Boolean {
63 if (setting.isEditable) { 68 if (setting.isEditable) {
64 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) 69 return adapter.onLongClick(setting, bindingAdapterPosition)
65 } 70 }
66 return false 71 return false
67 } 72 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index cc3f39aa5..b83c90100 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
@@ -4,6 +4,7 @@
4package org.yuzu.yuzu_emu.features.settings.ui.viewholder 4package org.yuzu.yuzu_emu.features.settings.ui.viewholder
5 5
6import android.view.View 6import android.view.View
7import org.yuzu.yuzu_emu.R
7import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding 8import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
8import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 9import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting 10import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
@@ -22,6 +23,14 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
22 } else { 23 } else {
23 binding.textSettingDescription.visibility = View.GONE 24 binding.textSettingDescription.visibility = View.GONE
24 } 25 }
26 binding.textSettingValue.visibility = View.VISIBLE
27 binding.textSettingValue.text = String.format(
28 binding.textSettingValue.context.getString(R.string.value_with_units),
29 setting.selectedValue,
30 setting.units
31 )
32
33 setStyle(setting.isEditable, binding)
25 } 34 }
26 35
27 override fun onClick(clicked: View) { 36 override fun onClick(clicked: View) {
@@ -32,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
32 41
33 override fun onLongClick(clicked: View): Boolean { 42 override fun onLongClick(clicked: View): Boolean {
34 if (setting.isEditable) { 43 if (setting.isEditable) {
35 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) 44 return adapter.onLongClick(setting, bindingAdapterPosition)
36 } 45 }
37 return false 46 return false
38 } 47 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
index c545b4174..1cf581a9d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
@@ -22,6 +22,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
22 } else { 22 } else {
23 binding.textSettingDescription.visibility = View.GONE 23 binding.textSettingDescription.visibility = View.GONE
24 } 24 }
25 binding.textSettingValue.visibility = View.GONE
25 } 26 }
26 27
27 override fun onClick(clicked: View) { 28 override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index 54f531795..57fdeaa20 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -25,12 +25,14 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
25 binding.textSettingDescription.text = "" 25 binding.textSettingDescription.text = ""
26 binding.textSettingDescription.visibility = View.GONE 26 binding.textSettingDescription.visibility = View.GONE
27 } 27 }
28 binding.switchWidget.isChecked = setting.isChecked 28
29 binding.switchWidget.setOnCheckedChangeListener(null)
30 binding.switchWidget.isChecked = setting.checked
29 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> 31 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
30 adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) 32 adapter.onBooleanClick(item, binding.switchWidget.isChecked)
31 } 33 }
32 34
33 binding.switchWidget.isEnabled = setting.isEditable 35 setStyle(setting.isEditable, binding)
34 } 36 }
35 37
36 override fun onClick(clicked: View) { 38 override fun onClick(clicked: View) {
@@ -41,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
41 43
42 override fun onLongClick(clicked: View): Boolean { 44 override fun onLongClick(clicked: View): Boolean {
43 if (setting.isEditable) { 45 if (setting.isEditable) {
44 return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) 46 return adapter.onLongClick(setting, bindingAdapterPosition)
45 } 47 }
46 return false 48 return false
47 } 49 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
index 70a52df5d..2b04d666a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
@@ -3,18 +3,15 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.utils 4package org.yuzu.yuzu_emu.features.settings.utils
5 5
6import android.widget.Toast
6import java.io.* 7import java.io.*
7import java.util.*
8import org.ini4j.Wini 8import org.ini4j.Wini
9import org.yuzu.yuzu_emu.NativeLibrary
10import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 10import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.* 11import org.yuzu.yuzu_emu.features.settings.model.*
13import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
14import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
15import org.yuzu.yuzu_emu.utils.BiMap
16import org.yuzu.yuzu_emu.utils.DirectoryInitialization 12import org.yuzu.yuzu_emu.utils.DirectoryInitialization
17import org.yuzu.yuzu_emu.utils.Log 13import org.yuzu.yuzu_emu.utils.Log
14import org.yuzu.yuzu_emu.utils.NativeConfig
18 15
19/** 16/**
20 * Contains static methods for interacting with .ini files in which settings are stored. 17 * Contains static methods for interacting with .ini files in which settings are stored.
@@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log
22object SettingsFile { 19object SettingsFile {
23 const val FILE_NAME_CONFIG = "config" 20 const val FILE_NAME_CONFIG = "config"
24 21
25 private var sectionsMap = BiMap<String?, String?>()
26
27 /**
28 * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
29 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
30 * failed.
31 *
32 * @param ini The ini file to load the settings from
33 * @param isCustomGame
34 * @param view The current view.
35 * @return An Observable that emits a HashMap of the file's contents, then completes.
36 */
37 private fun readFile(
38 ini: File?,
39 isCustomGame: Boolean,
40 view: SettingsActivityView? = null
41 ): HashMap<String, SettingSection?> {
42 val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
43 var reader: BufferedReader? = null
44 try {
45 reader = BufferedReader(FileReader(ini))
46 var current: SettingSection? = null
47 var line: String?
48 while (reader.readLine().also { line = it } != null) {
49 if (line!!.startsWith("[") && line!!.endsWith("]")) {
50 current = sectionFromLine(line!!, isCustomGame)
51 sections[current.name] = current
52 } else if (current != null) {
53 val setting = settingFromLine(line!!)
54 if (setting != null) {
55 current.putSetting(setting)
56 }
57 }
58 }
59 } catch (e: FileNotFoundException) {
60 Log.error("[SettingsFile] File not found: " + e.message)
61 view?.onSettingsFileNotFound()
62 } catch (e: IOException) {
63 Log.error("[SettingsFile] Error reading from: " + e.message)
64 view?.onSettingsFileNotFound()
65 } finally {
66 if (reader != null) {
67 try {
68 reader.close()
69 } catch (e: IOException) {
70 Log.error("[SettingsFile] Error closing: " + e.message)
71 }
72 }
73 }
74 return sections
75 }
76
77 fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
78 return readFile(getSettingsFile(fileName), false, view)
79 }
80
81 fun readFile(fileName: String): HashMap<String, SettingSection?> =
82 readFile(getSettingsFile(fileName), false)
83
84 /**
85 * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
86 * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
87 * failed.
88 *
89 * @param gameId the id of the game to load it's settings.
90 * @param view The current view.
91 */
92 fun readCustomGameSettings(
93 gameId: String,
94 view: SettingsActivityView?
95 ): HashMap<String, SettingSection?> {
96 return readFile(getCustomGameSettingsFile(gameId), true, view)
97 }
98
99 /** 22 /**
100 * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error 23 * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
101 * telling why it failed. 24 * telling why it failed.
102 * 25 *
103 * @param fileName The target filename without a path or extension. 26 * @param fileName The target filename without a path or extension.
104 * @param sections The HashMap containing the Settings we want to serialize.
105 * @param view The current view.
106 */ 27 */
107 fun saveFile( 28 fun saveFile(fileName: String) {
108 fileName: String,
109 sections: TreeMap<String, SettingSection>,
110 view: SettingsActivityView
111 ) {
112 val ini = getSettingsFile(fileName) 29 val ini = getSettingsFile(fileName)
113 try { 30 try {
114 val writer = Wini(ini) 31 val wini = Wini(ini)
115 val keySet: Set<String> = sections.keys 32 for (specificCategory in Settings.Category.values()) {
116 for (key in keySet) { 33 val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
117 val section = sections[key] 34 for (setting in Settings.settingsList) {
118 writeSection(writer, section!!) 35 if (setting.key!!.isEmpty()) continue
36
37 val settingCategoryHeader =
38 NativeConfig.getConfigHeader(setting.category.ordinal)
39 val iniSetting: String? = wini.get(categoryHeader, setting.key)
40 if (iniSetting != null || settingCategoryHeader == categoryHeader) {
41 wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
42 }
43 }
119 } 44 }
120 writer.store() 45 wini.store()
121 } catch (e: IOException) { 46 } catch (e: IOException) {
122 Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message) 47 Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
123 view.showToastMessage( 48 val context = YuzuApplication.appContext
124 YuzuApplication.appContext 49 Toast.makeText(
125 .getString(R.string.error_saving, fileName, e.message), 50 context,
126 false 51 context.getString(R.string.error_saving, fileName, e.message),
127 ) 52 Toast.LENGTH_SHORT
128 } 53 ).show()
129 }
130
131 fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
132 val sortedSections: Set<String> = TreeSet(sections.keys)
133 for (sectionKey in sortedSections) {
134 val section = sections[sectionKey]
135 val settings = section!!.settings
136 val sortedKeySet: Set<String> = TreeSet(settings.keys)
137 for (settingKey in sortedKeySet) {
138 val setting = settings[settingKey]
139 NativeLibrary.setUserSetting(
140 gameId,
141 mapSectionNameFromIni(
142 section.name
143 ),
144 setting!!.key,
145 setting.valueAsString
146 )
147 }
148 }
149 }
150
151 private fun mapSectionNameFromIni(generalSectionName: String): String? {
152 return if (sectionsMap.getForward(generalSectionName) != null) {
153 sectionsMap.getForward(generalSectionName)
154 } else {
155 generalSectionName
156 }
157 }
158
159 private fun mapSectionNameToIni(generalSectionName: String): String {
160 return if (sectionsMap.getBackward(generalSectionName) != null) {
161 sectionsMap.getBackward(generalSectionName).toString()
162 } else {
163 generalSectionName
164 }
165 }
166
167 fun getSettingsFile(fileName: String): File {
168 return File(
169 DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
170 )
171 }
172
173 private fun getCustomGameSettingsFile(gameId: String): File {
174 return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
175 }
176
177 private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
178 var sectionName: String = line.substring(1, line.length - 1)
179 if (isCustomGame) {
180 sectionName = mapSectionNameToIni(sectionName)
181 } 54 }
182 return SettingSection(sectionName)
183 } 55 }
184 56
185 /** 57 fun getSettingsFile(fileName: String): File =
186 * For a line of text, determines what type of data is being represented, and returns 58 File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
187 * a Setting object containing this data.
188 *
189 * @param line The line of text being parsed.
190 * @return A typed Setting containing the key/value contained in the line.
191 */
192 private fun settingFromLine(line: String): AbstractSetting? {
193 val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
194 if (splitLine.size != 2) {
195 return null
196 }
197 val key = splitLine[0].trim { it <= ' ' }
198 val value = splitLine[1].trim { it <= ' ' }
199 if (value.isEmpty()) {
200 return null
201 }
202
203 val booleanSetting = BooleanSetting.from(key)
204 if (booleanSetting != null) {
205 booleanSetting.boolean = value.toBoolean()
206 return booleanSetting
207 }
208
209 val intSetting = IntSetting.from(key)
210 if (intSetting != null) {
211 intSetting.int = value.toInt()
212 return intSetting
213 }
214
215 val floatSetting = FloatSetting.from(key)
216 if (floatSetting != null) {
217 floatSetting.float = value.toFloat()
218 return floatSetting
219 }
220
221 val stringSetting = StringSetting.from(key)
222 if (stringSetting != null) {
223 stringSetting.string = value
224 return stringSetting
225 }
226
227 return null
228 }
229
230 /**
231 * Writes the contents of a Section HashMap to disk.
232 *
233 * @param parser A Wini pointed at a file on disk.
234 * @param section A section containing settings to be written to the file.
235 */
236 private fun writeSection(parser: Wini, section: SettingSection) {
237 // Write the section header.
238 val header = section.name
239
240 // Write this section's values.
241 val settings = section.settings
242 val keySet: Set<String> = settings.keys
243 for (key in keySet) {
244 val setting = settings[key]
245 parser.put(header, setting!!.key, setting.valueAsString)
246 }
247
248 BooleanSetting.values().forEach {
249 if (!keySet.contains(it.key)) {
250 parser.put(header, it.key, it.valueAsString)
251 }
252 }
253 IntSetting.values().forEach {
254 if (!keySet.contains(it.key)) {
255 parser.put(header, it.key, it.valueAsString)
256 }
257 }
258 StringSetting.values().forEach {
259 if (!keySet.contains(it.key)) {
260 parser.put(header, it.key, it.valueAsString)
261 }
262 }
263 }
264} 59}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 0e7c1ba88..3e6c157c7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -7,11 +7,11 @@ import android.annotation.SuppressLint
7import android.app.AlertDialog 7import android.app.AlertDialog
8import android.content.Context 8import android.content.Context
9import android.content.DialogInterface 9import android.content.DialogInterface
10import android.content.Intent
11import android.content.SharedPreferences 10import android.content.SharedPreferences
12import android.content.pm.ActivityInfo 11import android.content.pm.ActivityInfo
13import android.content.res.Configuration 12import android.content.res.Configuration
14import android.graphics.Color 13import android.graphics.Color
14import android.net.Uri
15import android.os.Bundle 15import android.os.Bundle
16import android.os.Handler 16import android.os.Handler
17import android.os.Looper 17import android.os.Looper
@@ -19,18 +19,18 @@ import android.util.Rational
19import android.view.* 19import android.view.*
20import android.widget.TextView 20import android.widget.TextView
21import androidx.activity.OnBackPressedCallback 21import androidx.activity.OnBackPressedCallback
22import androidx.activity.result.ActivityResultLauncher
23import androidx.activity.result.contract.ActivityResultContracts
24import androidx.appcompat.widget.PopupMenu 22import androidx.appcompat.widget.PopupMenu
25import androidx.core.content.res.ResourcesCompat 23import androidx.core.content.res.ResourcesCompat
26import androidx.core.graphics.Insets 24import androidx.core.graphics.Insets
27import androidx.core.view.ViewCompat 25import androidx.core.view.ViewCompat
28import androidx.core.view.WindowInsetsCompat 26import androidx.core.view.WindowInsetsCompat
29import androidx.core.view.isVisible 27import androidx.drawerlayout.widget.DrawerLayout
30import androidx.fragment.app.Fragment 28import androidx.fragment.app.Fragment
29import androidx.fragment.app.activityViewModels
31import androidx.lifecycle.Lifecycle 30import androidx.lifecycle.Lifecycle
32import androidx.lifecycle.lifecycleScope 31import androidx.lifecycle.lifecycleScope
33import androidx.lifecycle.repeatOnLifecycle 32import androidx.lifecycle.repeatOnLifecycle
33import androidx.navigation.findNavController
34import androidx.navigation.fragment.navArgs 34import androidx.navigation.fragment.navArgs
35import androidx.preference.PreferenceManager 35import androidx.preference.PreferenceManager
36import androidx.window.layout.FoldingFeature 36import androidx.window.layout.FoldingFeature
@@ -39,7 +39,9 @@ import androidx.window.layout.WindowLayoutInfo
39import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
40import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers 41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.flow.collectLatest
42import kotlinx.coroutines.launch 43import kotlinx.coroutines.launch
44import org.yuzu.yuzu_emu.HomeNavigationDirections
43import org.yuzu.yuzu_emu.NativeLibrary 45import org.yuzu.yuzu_emu.NativeLibrary
44import org.yuzu.yuzu_emu.R 46import org.yuzu.yuzu_emu.R
45import org.yuzu.yuzu_emu.YuzuApplication 47import org.yuzu.yuzu_emu.YuzuApplication
@@ -48,8 +50,8 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
48import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 50import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
49import org.yuzu.yuzu_emu.features.settings.model.IntSetting 51import org.yuzu.yuzu_emu.features.settings.model.IntSetting
50import org.yuzu.yuzu_emu.features.settings.model.Settings 52import org.yuzu.yuzu_emu.features.settings.model.Settings
51import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity 53import org.yuzu.yuzu_emu.model.Game
52import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 54import org.yuzu.yuzu_emu.model.EmulationViewModel
53import org.yuzu.yuzu_emu.overlay.InputOverlay 55import org.yuzu.yuzu_emu.overlay.InputOverlay
54import org.yuzu.yuzu_emu.utils.* 56import org.yuzu.yuzu_emu.utils.*
55 57
@@ -62,11 +64,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
62 private var _binding: FragmentEmulationBinding? = null 64 private var _binding: FragmentEmulationBinding? = null
63 private val binding get() = _binding!! 65 private val binding get() = _binding!!
64 66
65 val args by navArgs<EmulationFragmentArgs>() 67 private val args by navArgs<EmulationFragmentArgs>()
66 68
67 private var isInFoldableLayout = false 69 private lateinit var game: Game
70
71 private val emulationViewModel: EmulationViewModel by activityViewModels()
68 72
69 private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent> 73 private var isInFoldableLayout = false
70 74
71 override fun onAttach(context: Context) { 75 override fun onAttach(context: Context) {
72 super.onAttach(context) 76 super.onAttach(context)
@@ -81,11 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
81 .collect { updateFoldableLayout(context, it) } 85 .collect { updateFoldableLayout(context, it) }
82 } 86 }
83 } 87 }
84
85 onReturnFromSettings = context.activityResultRegistry.register(
86 "SettingsResult",
87 ActivityResultContracts.StartActivityForResult()
88 ) { updateScreenLayout() }
89 } else { 88 } else {
90 throw IllegalStateException("EmulationFragment must have EmulationActivity parent") 89 throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
91 } 90 }
@@ -97,10 +96,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
97 override fun onCreate(savedInstanceState: Bundle?) { 96 override fun onCreate(savedInstanceState: Bundle?) {
98 super.onCreate(savedInstanceState) 97 super.onCreate(savedInstanceState)
99 98
99 val intentUri: Uri? = requireActivity().intent.data
100 var intentGame: Game? = null
101 if (intentUri != null) {
102 intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
103 GameHelper.getGame(requireActivity().intent.data!!, false)
104 } else {
105 null
106 }
107 }
108 game = if (args.game != null) {
109 args.game!!
110 } else {
111 intentGame ?: error("[EmulationFragment] No bootable game present!")
112 }
113
100 // So this fragment doesn't restart on configuration changes; i.e. rotation. 114 // So this fragment doesn't restart on configuration changes; i.e. rotation.
101 retainInstance = true 115 retainInstance = true
102 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 116 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
103 emulationState = EmulationState(args.game.path) 117 emulationState = EmulationState(game.path)
104 } 118 }
105 119
106 /** 120 /**
@@ -115,16 +129,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
115 return binding.root 129 return binding.root
116 } 130 }
117 131
132 // This is using the correct scope, lint is just acting up
133 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
118 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 134 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
119 binding.surfaceEmulation.holder.addCallback(this) 135 binding.surfaceEmulation.holder.addCallback(this)
120 binding.showFpsText.setTextColor(Color.YELLOW) 136 binding.showFpsText.setTextColor(Color.YELLOW)
121 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } 137 binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
122 138
123 // Setup overlay. 139 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
124 updateShowFpsOverlay()
125
126 binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = 140 binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
127 args.game.title 141 game.title
128 binding.inGameMenu.setNavigationItemSelectedListener { 142 binding.inGameMenu.setNavigationItemSelectedListener {
129 when (it.itemId) { 143 when (it.itemId) {
130 R.id.menu_pause_emulation -> { 144 R.id.menu_pause_emulation -> {
@@ -149,12 +163,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
149 } 163 }
150 164
151 R.id.menu_settings -> { 165 R.id.menu_settings -> {
152 SettingsActivity.launch( 166 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
153 requireContext(), 167 null,
154 onReturnFromSettings, 168 Settings.MenuTag.SECTION_ROOT
155 SettingsFile.FILE_NAME_CONFIG,
156 ""
157 ) 169 )
170 binding.root.findNavController().navigate(action)
158 true 171 true
159 } 172 }
160 173
@@ -165,7 +178,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
165 178
166 R.id.menu_exit -> { 179 R.id.menu_exit -> {
167 emulationState.stop() 180 emulationState.stop()
168 requireActivity().finish() 181 emulationViewModel.setIsEmulationStopping(true)
182 binding.drawerLayout.close()
183 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
169 true 184 true
170 } 185 }
171 186
@@ -179,6 +194,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
179 requireActivity(), 194 requireActivity(),
180 object : OnBackPressedCallback(true) { 195 object : OnBackPressedCallback(true) {
181 override fun handleOnBackPressed() { 196 override fun handleOnBackPressed() {
197 if (!NativeLibrary.isRunning()) {
198 return
199 }
200
182 if (binding.drawerLayout.isOpen) { 201 if (binding.drawerLayout.isOpen) {
183 binding.drawerLayout.close() 202 binding.drawerLayout.close()
184 } else { 203 } else {
@@ -188,11 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
188 } 207 }
189 ) 208 )
190 209
191 viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { 210 GameIconUtils.loadGameIcon(game, binding.loadingImage)
192 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { 211 binding.loadingTitle.text = game.title
193 WindowInfoTracker.getOrCreate(requireContext()) 212 binding.loadingTitle.isSelected = true
194 .windowLayoutInfo(requireActivity()) 213 binding.loadingText.isSelected = true
195 .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) } 214
215 viewLifecycleOwner.lifecycleScope.apply {
216 launch {
217 repeatOnLifecycle(Lifecycle.State.STARTED) {
218 WindowInfoTracker.getOrCreate(requireContext())
219 .windowLayoutInfo(requireActivity())
220 .collect {
221 updateFoldableLayout(requireActivity() as EmulationActivity, it)
222 }
223 }
224 }
225 launch {
226 repeatOnLifecycle(Lifecycle.State.CREATED) {
227 emulationViewModel.shaderProgress.collectLatest {
228 if (it > 0 && it != emulationViewModel.totalShaders.value) {
229 binding.loadingProgressIndicator.isIndeterminate = false
230
231 if (it < binding.loadingProgressIndicator.max) {
232 binding.loadingProgressIndicator.progress = it
233 }
234 }
235
236 if (it == emulationViewModel.totalShaders.value) {
237 binding.loadingText.setText(R.string.loading)
238 binding.loadingProgressIndicator.isIndeterminate = true
239 }
240 }
241 }
242 }
243 launch {
244 repeatOnLifecycle(Lifecycle.State.CREATED) {
245 emulationViewModel.totalShaders.collectLatest {
246 binding.loadingProgressIndicator.max = it
247 }
248 }
249 }
250 launch {
251 repeatOnLifecycle(Lifecycle.State.CREATED) {
252 emulationViewModel.shaderMessage.collectLatest {
253 if (it.isNotEmpty()) {
254 binding.loadingText.text = it
255 }
256 }
257 }
258 }
259 launch {
260 repeatOnLifecycle(Lifecycle.State.CREATED) {
261 emulationViewModel.emulationStarted.collectLatest {
262 if (it) {
263 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
264 ViewUtils.showView(binding.surfaceInputOverlay)
265 ViewUtils.hideView(binding.loadingIndicator)
266
267 // Setup overlay
268 updateShowFpsOverlay()
269 }
270 }
271 }
272 }
273 launch {
274 repeatOnLifecycle(Lifecycle.State.CREATED) {
275 emulationViewModel.isEmulationStopping.collectLatest {
276 if (it) {
277 binding.loadingText.setText(R.string.shutting_down)
278 ViewUtils.showView(binding.loadingIndicator)
279 ViewUtils.hideView(binding.inputContainer)
280 ViewUtils.hideView(binding.showFpsText)
281 }
282 }
283 }
196 } 284 }
197 } 285 }
198 } 286 }
@@ -204,11 +292,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
204 binding.drawerLayout.close() 292 binding.drawerLayout.close()
205 } 293 }
206 if (EmulationMenuSettings.showOverlay) { 294 if (EmulationMenuSettings.showOverlay) {
207 binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false } 295 binding.surfaceInputOverlay.post {
296 binding.surfaceInputOverlay.visibility = View.VISIBLE
297 }
208 } 298 }
209 } else { 299 } else {
210 if (EmulationMenuSettings.showOverlay) { 300 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
211 binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true } 301 binding.surfaceInputOverlay.post {
302 binding.surfaceInputOverlay.visibility = View.VISIBLE
303 }
304 } else {
305 binding.surfaceInputOverlay.post {
306 binding.surfaceInputOverlay.visibility = View.INVISIBLE
307 }
212 } 308 }
213 if (!isInFoldableLayout) { 309 if (!isInFoldableLayout) {
214 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 310 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -217,16 +313,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
217 binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE 313 binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
218 } 314 }
219 } 315 }
220 if (!binding.surfaceInputOverlay.isInEditMode) {
221 refreshInputOverlay()
222 }
223 } 316 }
224 } 317 }
225 318
226 override fun onResume() { 319 override fun onResume() {
227 super.onResume() 320 super.onResume()
228 if (!DirectoryInitialization.areDirectoriesReady) { 321 if (!DirectoryInitialization.areDirectoriesReady) {
229 DirectoryInitialization.start(requireContext()) 322 DirectoryInitialization.start()
230 } 323 }
231 324
232 updateScreenLayout() 325 updateScreenLayout()
@@ -251,10 +344,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
251 super.onDetach() 344 super.onDetach()
252 } 345 }
253 346
254 private fun refreshInputOverlay() {
255 binding.surfaceInputOverlay.refreshControls()
256 }
257
258 private fun resetInputOverlay() { 347 private fun resetInputOverlay() {
259 preferences.edit() 348 preferences.edit()
260 .remove(Settings.PREF_CONTROL_SCALE) 349 .remove(Settings.PREF_CONTROL_SCALE)
@@ -272,17 +361,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
272 val FRAMETIME = 2 361 val FRAMETIME = 2
273 val SPEED = 3 362 val SPEED = 3
274 perfStatsUpdater = { 363 perfStatsUpdater = {
275 val perfStats = NativeLibrary.getPerfStats() 364 if (emulationViewModel.emulationStarted.value == true) {
276 if (perfStats[FPS] > 0 && _binding != null) { 365 val perfStats = NativeLibrary.getPerfStats()
277 binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS]) 366 if (perfStats[FPS] > 0 && _binding != null) {
278 } 367 binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
279 368 }
280 if (!emulationState.isStopped) {
281 perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100) 369 perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
282 } 370 }
283 } 371 }
284 perfStatsUpdateHandler.post(perfStatsUpdater!!) 372 perfStatsUpdateHandler.post(perfStatsUpdater!!)
285 binding.showFpsText.text = resources.getString(R.string.emulation_game_loading)
286 binding.showFpsText.visibility = View.VISIBLE 373 binding.showFpsText.visibility = View.VISIBLE
287 } else { 374 } else {
288 if (perfStatsUpdater != null) { 375 if (perfStatsUpdater != null) {
@@ -297,11 +384,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
297 emulationActivity?.let { 384 emulationActivity?.let {
298 it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { 385 it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
299 Settings.LayoutOption_MobileLandscape -> 386 Settings.LayoutOption_MobileLandscape ->
300 ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE 387 ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
301 Settings.LayoutOption_MobilePortrait -> 388 Settings.LayoutOption_MobilePortrait ->
302 ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT 389 ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
303 Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED 390 Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
304 else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE 391 else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
305 } 392 }
306 } 393 }
307 } 394 }
@@ -340,7 +427,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
340 427
341 isInFoldableLayout = true 428 isInFoldableLayout = true
342 binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE 429 binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
343 refreshInputOverlay()
344 } 430 }
345 } 431 }
346 it.isSeparating 432 it.isSeparating
@@ -428,7 +514,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
428 .apply() 514 .apply()
429 } 515 }
430 .setPositiveButton(android.R.string.ok) { _, _ -> 516 .setPositiveButton(android.R.string.ok) { _, _ ->
431 refreshInputOverlay() 517 binding.surfaceInputOverlay.refreshControls()
432 } 518 }
433 .setNegativeButton(android.R.string.cancel, null) 519 .setNegativeButton(android.R.string.cancel, null)
434 .setNeutralButton(R.string.emulation_toggle_all) { _, _ -> } 520 .setNeutralButton(R.string.emulation_toggle_all) { _, _ -> }
@@ -452,7 +538,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
452 R.id.menu_show_overlay -> { 538 R.id.menu_show_overlay -> {
453 it.isChecked = !it.isChecked 539 it.isChecked = !it.isChecked
454 EmulationMenuSettings.showOverlay = it.isChecked 540 EmulationMenuSettings.showOverlay = it.isChecked
455 refreshInputOverlay() 541 binding.surfaceInputOverlay.refreshControls()
456 true 542 true
457 } 543 }
458 544
@@ -558,14 +644,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
558 preferences.edit() 644 preferences.edit()
559 .putInt(Settings.PREF_CONTROL_SCALE, scale) 645 .putInt(Settings.PREF_CONTROL_SCALE, scale)
560 .apply() 646 .apply()
561 refreshInputOverlay() 647 binding.surfaceInputOverlay.refreshControls()
562 } 648 }
563 649
564 private fun setControlOpacity(opacity: Int) { 650 private fun setControlOpacity(opacity: Int) {
565 preferences.edit() 651 preferences.edit()
566 .putInt(Settings.PREF_CONTROL_OPACITY, opacity) 652 .putInt(Settings.PREF_CONTROL_OPACITY, opacity)
567 .apply() 653 .apply()
568 refreshInputOverlay() 654 binding.surfaceInputOverlay.refreshControls()
569 } 655 }
570 656
571 private fun setInsets() { 657 private fun setInsets() {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index c001af892..c119e69c9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -25,18 +25,18 @@ import androidx.core.view.updatePadding
25import androidx.documentfile.provider.DocumentFile 25import androidx.documentfile.provider.DocumentFile
26import androidx.fragment.app.Fragment 26import androidx.fragment.app.Fragment
27import androidx.fragment.app.activityViewModels 27import androidx.fragment.app.activityViewModels
28import androidx.navigation.findNavController
28import androidx.navigation.fragment.findNavController 29import androidx.navigation.fragment.findNavController
29import androidx.recyclerview.widget.LinearLayoutManager 30import androidx.recyclerview.widget.LinearLayoutManager
30import com.google.android.material.dialog.MaterialAlertDialogBuilder 31import com.google.android.material.dialog.MaterialAlertDialogBuilder
31import com.google.android.material.transition.MaterialSharedAxis 32import com.google.android.material.transition.MaterialSharedAxis
32import org.yuzu.yuzu_emu.BuildConfig 33import org.yuzu.yuzu_emu.BuildConfig
34import org.yuzu.yuzu_emu.HomeNavigationDirections
33import org.yuzu.yuzu_emu.R 35import org.yuzu.yuzu_emu.R
34import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter 36import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
35import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
36import org.yuzu.yuzu_emu.features.DocumentProvider 38import org.yuzu.yuzu_emu.features.DocumentProvider
37import org.yuzu.yuzu_emu.features.settings.model.Settings 39import org.yuzu.yuzu_emu.features.settings.model.Settings
38import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
39import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
40import org.yuzu.yuzu_emu.model.HomeSetting 40import org.yuzu.yuzu_emu.model.HomeSetting
41import org.yuzu.yuzu_emu.model.HomeViewModel 41import org.yuzu.yuzu_emu.model.HomeViewModel
42import org.yuzu.yuzu_emu.ui.main.MainActivity 42import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -74,7 +74,13 @@ class HomeSettingsFragment : Fragment() {
74 R.string.advanced_settings, 74 R.string.advanced_settings,
75 R.string.settings_description, 75 R.string.settings_description,
76 R.drawable.ic_settings, 76 R.drawable.ic_settings,
77 { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") } 77 {
78 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
79 null,
80 Settings.MenuTag.SECTION_ROOT
81 )
82 binding.root.findNavController().navigate(action)
83 }
78 ) 84 )
79 ) 85 )
80 add( 86 add(
@@ -90,7 +96,13 @@ class HomeSettingsFragment : Fragment() {
90 R.string.preferences_theme, 96 R.string.preferences_theme,
91 R.string.theme_and_color_description, 97 R.string.theme_and_color_description,
92 R.drawable.ic_palette, 98 R.drawable.ic_palette,
93 { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") } 99 {
100 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
101 null,
102 Settings.MenuTag.SECTION_THEME
103 )
104 binding.root.findNavController().navigate(action)
105 }
94 ) 106 )
95 ) 107 )
96 add( 108 add(
@@ -129,7 +141,11 @@ class HomeSettingsFragment : Fragment() {
129 mainActivity.getGamesDirectory.launch( 141 mainActivity.getGamesDirectory.launch(
130 Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data 142 Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
131 ) 143 )
132 } 144 },
145 { true },
146 0,
147 0,
148 homeViewModel.gamesDir
133 ) 149 )
134 ) 150 )
135 add( 151 add(
@@ -201,7 +217,11 @@ class HomeSettingsFragment : Fragment() {
201 217
202 binding.homeSettingsList.apply { 218 binding.homeSettingsList.apply {
203 layoutManager = LinearLayoutManager(requireContext()) 219 layoutManager = LinearLayoutManager(requireContext())
204 adapter = HomeSettingAdapter(requireActivity() as AppCompatActivity, optionsList) 220 adapter = HomeSettingAdapter(
221 requireActivity() as AppCompatActivity,
222 viewLifecycleOwner,
223 optionsList
224 )
205 } 225 }
206 226
207 setInsets() 227 setInsets()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
index e1495ee8c..f38aeea53 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
@@ -187,8 +187,8 @@ class ImportExportSavesFragment : DialogFragment() {
187 withContext(Dispatchers.Main) { 187 withContext(Dispatchers.Main) {
188 if (!validZip) { 188 if (!validZip) {
189 MessageDialogFragment.newInstance( 189 MessageDialogFragment.newInstance(
190 R.string.save_file_invalid_zip_structure, 190 titleId = R.string.save_file_invalid_zip_structure,
191 R.string.save_file_invalid_zip_structure_description 191 descriptionId = R.string.save_file_invalid_zip_structure_description
192 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG) 192 ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
193 return@withContext 193 return@withContext
194 } 194 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index 739b26f99..18bc34b9f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -5,49 +5,75 @@ package org.yuzu.yuzu_emu.fragments
5 5
6import android.app.Dialog 6import android.app.Dialog
7import android.os.Bundle 7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
8import android.widget.Toast 11import android.widget.Toast
9import androidx.appcompat.app.AppCompatActivity 12import androidx.appcompat.app.AppCompatActivity
10import androidx.fragment.app.DialogFragment 13import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels 14import androidx.fragment.app.activityViewModels
15import androidx.lifecycle.Lifecycle
12import androidx.lifecycle.ViewModelProvider 16import androidx.lifecycle.ViewModelProvider
17import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle
13import com.google.android.material.dialog.MaterialAlertDialogBuilder 19import com.google.android.material.dialog.MaterialAlertDialogBuilder
20import kotlinx.coroutines.launch
14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 21import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
15import org.yuzu.yuzu_emu.model.TaskViewModel 22import org.yuzu.yuzu_emu.model.TaskViewModel
16 23
17class IndeterminateProgressDialogFragment : DialogFragment() { 24class IndeterminateProgressDialogFragment : DialogFragment() {
18 private val taskViewModel: TaskViewModel by activityViewModels() 25 private val taskViewModel: TaskViewModel by activityViewModels()
19 26
27 private lateinit var binding: DialogProgressBarBinding
28
20 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 29 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
21 val titleId = requireArguments().getInt(TITLE) 30 val titleId = requireArguments().getInt(TITLE)
22 31
23 val progressBinding = DialogProgressBarBinding.inflate(layoutInflater) 32 binding = DialogProgressBarBinding.inflate(layoutInflater)
24 progressBinding.progressBar.isIndeterminate = true 33 binding.progressBar.isIndeterminate = true
25 val dialog = MaterialAlertDialogBuilder(requireContext()) 34 val dialog = MaterialAlertDialogBuilder(requireContext())
26 .setTitle(titleId) 35 .setTitle(titleId)
27 .setView(progressBinding.root) 36 .setView(binding.root)
28 .create() 37 .create()
29 dialog.setCanceledOnTouchOutside(false) 38 dialog.setCanceledOnTouchOutside(false)
30 39
31 taskViewModel.isComplete.observe(this) { complete -> 40 if (!taskViewModel.isRunning.value) {
32 if (complete) {
33 dialog.dismiss()
34 when (val result = taskViewModel.result.value) {
35 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
36 is MessageDialogFragment -> result.show(
37 parentFragmentManager,
38 MessageDialogFragment.TAG
39 )
40 }
41 taskViewModel.clear()
42 }
43 }
44
45 if (taskViewModel.isRunning.value == false) {
46 taskViewModel.runTask() 41 taskViewModel.runTask()
47 } 42 }
48 return dialog 43 return dialog
49 } 44 }
50 45
46 override fun onCreateView(
47 inflater: LayoutInflater,
48 container: ViewGroup?,
49 savedInstanceState: Bundle?
50 ): View {
51 return binding.root
52 }
53
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 super.onViewCreated(view, savedInstanceState)
56 viewLifecycleOwner.lifecycleScope.launch {
57 repeatOnLifecycle(Lifecycle.State.CREATED) {
58 taskViewModel.isComplete.collect {
59 if (it) {
60 dismiss()
61 when (val result = taskViewModel.result.value) {
62 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
63 .show()
64
65 is MessageDialogFragment -> result.show(
66 requireActivity().supportFragmentManager,
67 MessageDialogFragment.TAG
68 )
69 }
70 taskViewModel.clear()
71 }
72 }
73 }
74 }
75 }
76
51 companion object { 77 companion object {
52 const val TAG = "IndeterminateProgressDialogFragment" 78 const val TAG = "IndeterminateProgressDialogFragment"
53 79
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
deleted file mode 100644
index b29b627e9..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
+++ /dev/null
@@ -1,62 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.Intent
8import android.net.Uri
9import android.os.Bundle
10import androidx.fragment.app.DialogFragment
11import com.google.android.material.dialog.MaterialAlertDialogBuilder
12import org.yuzu.yuzu_emu.R
13
14class LongMessageDialogFragment : DialogFragment() {
15 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
16 val titleId = requireArguments().getInt(TITLE)
17 val description = requireArguments().getString(DESCRIPTION)
18 val helpLinkId = requireArguments().getInt(HELP_LINK)
19
20 val dialog = MaterialAlertDialogBuilder(requireContext())
21 .setPositiveButton(R.string.close, null)
22 .setTitle(titleId)
23 .setMessage(description)
24
25 if (helpLinkId != 0) {
26 dialog.setNeutralButton(R.string.learn_more) { _, _ ->
27 openLink(getString(helpLinkId))
28 }
29 }
30
31 return dialog.show()
32 }
33
34 private fun openLink(link: String) {
35 val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
36 startActivity(intent)
37 }
38
39 companion object {
40 const val TAG = "LongMessageDialogFragment"
41
42 private const val TITLE = "Title"
43 private const val DESCRIPTION = "Description"
44 private const val HELP_LINK = "Link"
45
46 fun newInstance(
47 titleId: Int,
48 description: String,
49 helpLinkId: Int = 0
50 ): LongMessageDialogFragment {
51 val dialog = LongMessageDialogFragment()
52 val bundle = Bundle()
53 bundle.apply {
54 putInt(TITLE, titleId)
55 putString(DESCRIPTION, description)
56 putInt(HELP_LINK, helpLinkId)
57 }
58 dialog.arguments = bundle
59 return dialog
60 }
61 }
62}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
index 2db38fdc2..7d1c2c8dd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
@@ -13,14 +13,20 @@ import org.yuzu.yuzu_emu.R
13 13
14class MessageDialogFragment : DialogFragment() { 14class MessageDialogFragment : DialogFragment() {
15 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 15 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
16 val titleId = requireArguments().getInt(TITLE) 16 val titleId = requireArguments().getInt(TITLE_ID)
17 val descriptionId = requireArguments().getInt(DESCRIPTION) 17 val titleString = requireArguments().getString(TITLE_STRING)!!
18 val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
19 val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
18 val helpLinkId = requireArguments().getInt(HELP_LINK) 20 val helpLinkId = requireArguments().getInt(HELP_LINK)
19 21
20 val dialog = MaterialAlertDialogBuilder(requireContext()) 22 val dialog = MaterialAlertDialogBuilder(requireContext())
21 .setPositiveButton(R.string.close, null) 23 .setPositiveButton(R.string.close, null)
22 .setTitle(titleId) 24
23 .setMessage(descriptionId) 25 if (titleId != 0) dialog.setTitle(titleId)
26 if (titleString.isNotEmpty()) dialog.setTitle(titleString)
27
28 if (descriptionId != 0) dialog.setMessage(descriptionId)
29 if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString)
24 30
25 if (helpLinkId != 0) { 31 if (helpLinkId != 0) {
26 dialog.setNeutralButton(R.string.learn_more) { _, _ -> 32 dialog.setNeutralButton(R.string.learn_more) { _, _ ->
@@ -39,20 +45,26 @@ class MessageDialogFragment : DialogFragment() {
39 companion object { 45 companion object {
40 const val TAG = "MessageDialogFragment" 46 const val TAG = "MessageDialogFragment"
41 47
42 private const val TITLE = "Title" 48 private const val TITLE_ID = "Title"
43 private const val DESCRIPTION = "Description" 49 private const val TITLE_STRING = "TitleString"
50 private const val DESCRIPTION_ID = "DescriptionId"
51 private const val DESCRIPTION_STRING = "DescriptionString"
44 private const val HELP_LINK = "Link" 52 private const val HELP_LINK = "Link"
45 53
46 fun newInstance( 54 fun newInstance(
47 titleId: Int, 55 titleId: Int = 0,
48 descriptionId: Int, 56 titleString: String = "",
57 descriptionId: Int = 0,
58 descriptionString: String = "",
49 helpLinkId: Int = 0 59 helpLinkId: Int = 0
50 ): MessageDialogFragment { 60 ): MessageDialogFragment {
51 val dialog = MessageDialogFragment() 61 val dialog = MessageDialogFragment()
52 val bundle = Bundle() 62 val bundle = Bundle()
53 bundle.apply { 63 bundle.apply {
54 putInt(TITLE, titleId) 64 putInt(TITLE_ID, titleId)
55 putInt(DESCRIPTION, descriptionId) 65 putString(TITLE_STRING, titleString)
66 putInt(DESCRIPTION_ID, descriptionId)
67 putString(DESCRIPTION_STRING, descriptionString)
56 putInt(HELP_LINK, helpLinkId) 68 putInt(HELP_LINK, helpLinkId)
57 } 69 }
58 dialog.arguments = bundle 70 dialog.arguments = bundle
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index f54dccc69..2dbca76a5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.fragments 4package org.yuzu.yuzu_emu.fragments
5 5
6import android.annotation.SuppressLint
6import android.content.Context 7import android.content.Context
7import android.content.SharedPreferences 8import android.content.SharedPreferences
8import android.os.Bundle 9import android.os.Bundle
@@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
17import androidx.core.widget.doOnTextChanged 18import androidx.core.widget.doOnTextChanged
18import androidx.fragment.app.Fragment 19import androidx.fragment.app.Fragment
19import androidx.fragment.app.activityViewModels 20import androidx.fragment.app.activityViewModels
21import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope
23import androidx.lifecycle.repeatOnLifecycle
20import androidx.preference.PreferenceManager 24import androidx.preference.PreferenceManager
21import info.debatty.java.stringsimilarity.Jaccard 25import info.debatty.java.stringsimilarity.Jaccard
22import info.debatty.java.stringsimilarity.JaroWinkler 26import info.debatty.java.stringsimilarity.JaroWinkler
27import kotlinx.coroutines.launch
23import java.util.Locale 28import java.util.Locale
24import org.yuzu.yuzu_emu.R 29import org.yuzu.yuzu_emu.R
25import org.yuzu.yuzu_emu.YuzuApplication 30import org.yuzu.yuzu_emu.YuzuApplication
@@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
52 return binding.root 57 return binding.root
53 } 58 }
54 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 homeViewModel.setNavigationVisibility(visible = true, animated = false) 63 homeViewModel.setNavigationVisibility(visible = true, animated = false)
57 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 64 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
79 filterAndSearch() 86 filterAndSearch()
80 } 87 }
81 88
82 gamesViewModel.apply { 89 viewLifecycleOwner.lifecycleScope.apply {
83 searchFocused.observe(viewLifecycleOwner) { searchFocused -> 90 launch {
84 if (searchFocused) { 91 repeatOnLifecycle(Lifecycle.State.CREATED) {
85 focusSearch() 92 gamesViewModel.searchFocused.collect {
86 gamesViewModel.setSearchFocused(false) 93 if (it) {
94 focusSearch()
95 gamesViewModel.setSearchFocused(false)
96 }
97 }
87 } 98 }
88 } 99 }
89 100 launch {
90 games.observe(viewLifecycleOwner) { filterAndSearch() } 101 repeatOnLifecycle(Lifecycle.State.CREATED) {
91 searchedGames.observe(viewLifecycleOwner) { 102 gamesViewModel.games.collect { filterAndSearch() }
92 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) 103 }
93 if (it.isEmpty()) { 104 }
94 binding.noResultsView.visibility = View.VISIBLE 105 launch {
95 } else { 106 repeatOnLifecycle(Lifecycle.State.CREATED) {
96 binding.noResultsView.visibility = View.GONE 107 gamesViewModel.searchedGames.collect {
108 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
109 if (it.isEmpty()) {
110 binding.noResultsView.visibility = View.VISIBLE
111 } else {
112 binding.noResultsView.visibility = View.GONE
113 }
114 }
97 } 115 }
98 } 116 }
99 } 117 }
@@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
109 private inner class ScoredGame(val score: Double, val item: Game) 127 private inner class ScoredGame(val score: Double, val item: Game)
110 128
111 private fun filterAndSearch() { 129 private fun filterAndSearch() {
112 val baseList = gamesViewModel.games.value!! 130 val baseList = gamesViewModel.games.value
113 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { 131 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
114 R.id.chip_recently_played -> { 132 R.id.chip_recently_played -> {
115 baseList.filter { 133 baseList.filter {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
new file mode 100644
index 000000000..d18ec6974
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
@@ -0,0 +1,235 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.os.Bundle
9import android.view.LayoutInflater
10import android.view.View
11import android.view.ViewGroup
12import androidx.fragment.app.DialogFragment
13import androidx.fragment.app.activityViewModels
14import androidx.lifecycle.Lifecycle
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
17import com.google.android.material.dialog.MaterialAlertDialogBuilder
18import com.google.android.material.slider.Slider
19import kotlinx.coroutines.launch
20import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
22import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
23import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
24import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
25import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
26import org.yuzu.yuzu_emu.model.SettingsViewModel
27
28class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
29 private var type = 0
30 private var position = 0
31
32 private var defaultCancelListener =
33 DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
34
35 private val settingsViewModel: SettingsViewModel by activityViewModels()
36
37 private lateinit var sliderBinding: DialogSliderBinding
38
39 override fun onCreate(savedInstanceState: Bundle?) {
40 super.onCreate(savedInstanceState)
41 type = requireArguments().getInt(TYPE)
42 position = requireArguments().getInt(POSITION)
43
44 if (settingsViewModel.clickedItem == null) dismiss()
45 }
46
47 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
48 return when (type) {
49 TYPE_RESET_SETTING -> {
50 MaterialAlertDialogBuilder(requireContext())
51 .setMessage(R.string.reset_setting_confirmation)
52 .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
53 settingsViewModel.clickedItem!!.setting.reset()
54 settingsViewModel.setAdapterItemChanged(position)
55 settingsViewModel.shouldSave = true
56 }
57 .setNegativeButton(android.R.string.cancel, null)
58 .create()
59 }
60
61 SettingsItem.TYPE_SINGLE_CHOICE -> {
62 val item = settingsViewModel.clickedItem as SingleChoiceSetting
63 val value = getSelectionForSingleChoiceValue(item)
64 MaterialAlertDialogBuilder(requireContext())
65 .setTitle(item.nameId)
66 .setSingleChoiceItems(item.choicesId, value, this)
67 .create()
68 }
69
70 SettingsItem.TYPE_SLIDER -> {
71 sliderBinding = DialogSliderBinding.inflate(layoutInflater)
72 val item = settingsViewModel.clickedItem as SliderSetting
73
74 settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
75 sliderBinding.slider.apply {
76 valueFrom = item.min.toFloat()
77 valueTo = item.max.toFloat()
78 value = settingsViewModel.sliderProgress.value.toFloat()
79 addOnChangeListener { _: Slider, value: Float, _: Boolean ->
80 settingsViewModel.setSliderTextValue(value, item.units)
81 }
82 }
83
84 MaterialAlertDialogBuilder(requireContext())
85 .setTitle(item.nameId)
86 .setView(sliderBinding.root)
87 .setPositiveButton(android.R.string.ok, this)
88 .setNegativeButton(android.R.string.cancel, defaultCancelListener)
89 .create()
90 }
91
92 SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
93 val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
94 MaterialAlertDialogBuilder(requireContext())
95 .setTitle(item.nameId)
96 .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
97 .create()
98 }
99
100 else -> super.onCreateDialog(savedInstanceState)
101 }
102 }
103
104 override fun onCreateView(
105 inflater: LayoutInflater,
106 container: ViewGroup?,
107 savedInstanceState: Bundle?
108 ): View? {
109 return when (type) {
110 SettingsItem.TYPE_SLIDER -> sliderBinding.root
111 else -> super.onCreateView(inflater, container, savedInstanceState)
112 }
113 }
114
115 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
116 super.onViewCreated(view, savedInstanceState)
117 when (type) {
118 SettingsItem.TYPE_SLIDER -> {
119 viewLifecycleOwner.lifecycleScope.launch {
120 repeatOnLifecycle(Lifecycle.State.CREATED) {
121 settingsViewModel.sliderTextValue.collect {
122 sliderBinding.textValue.text = it
123 }
124 }
125 repeatOnLifecycle(Lifecycle.State.CREATED) {
126 settingsViewModel.sliderProgress.collect {
127 sliderBinding.slider.value = it.toFloat()
128 }
129 }
130 }
131 }
132 }
133 }
134
135 override fun onClick(dialog: DialogInterface, which: Int) {
136 when (settingsViewModel.clickedItem) {
137 is SingleChoiceSetting -> {
138 val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
139 val value = getValueForSingleChoiceSelection(scSetting, which)
140 if (scSetting.selectedValue != value) {
141 settingsViewModel.shouldSave = true
142 }
143 scSetting.selectedValue = value
144 }
145
146 is StringSingleChoiceSetting -> {
147 val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
148 val value = scSetting.getValueAt(which)
149 if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
150 scSetting.selectedValue = value
151 }
152
153 is SliderSetting -> {
154 val sliderSetting = settingsViewModel.clickedItem as SliderSetting
155 if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
156 settingsViewModel.shouldSave = true
157 }
158 sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
159 }
160 }
161 closeDialog()
162 }
163
164 private fun closeDialog() {
165 settingsViewModel.setAdapterItemChanged(position)
166 settingsViewModel.clickedItem = null
167 settingsViewModel.setSliderProgress(-1f)
168 dismiss()
169 }
170
171 private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
172 val valuesId = item.valuesId
173 return if (valuesId > 0) {
174 val valuesArray = requireContext().resources.getIntArray(valuesId)
175 valuesArray[which]
176 } else {
177 which
178 }
179 }
180
181 private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
182 val value = item.selectedValue
183 val valuesId = item.valuesId
184 if (valuesId > 0) {
185 val valuesArray = requireContext().resources.getIntArray(valuesId)
186 for (index in valuesArray.indices) {
187 val current = valuesArray[index]
188 if (current == value) {
189 return index
190 }
191 }
192 } else {
193 return value
194 }
195 return -1
196 }
197
198 companion object {
199 const val TAG = "SettingsDialogFragment"
200
201 const val TYPE_RESET_SETTING = -1
202
203 const val TITLE = "Title"
204 const val TYPE = "Type"
205 const val POSITION = "Position"
206
207 fun newInstance(
208 settingsViewModel: SettingsViewModel,
209 clickedItem: SettingsItem,
210 type: Int,
211 position: Int
212 ): SettingsDialogFragment {
213 when (type) {
214 SettingsItem.TYPE_HEADER,
215 SettingsItem.TYPE_SWITCH,
216 SettingsItem.TYPE_SUBMENU,
217 SettingsItem.TYPE_DATETIME_SETTING,
218 SettingsItem.TYPE_RUNNABLE ->
219 throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
220
221 SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
222 (clickedItem as SliderSetting).selectedValue.toFloat()
223 )
224 }
225 settingsViewModel.clickedItem = clickedItem
226
227 val args = Bundle()
228 args.putInt(TYPE, type)
229 args.putInt(POSITION, position)
230 val fragment = SettingsDialogFragment()
231 fragment.arguments = args
232 return fragment
233 }
234 }
235}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
new file mode 100644
index 000000000..9d0594c6e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -0,0 +1,192 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.content.Context
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import android.view.inputmethod.InputMethodManager
12import androidx.core.view.ViewCompat
13import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding
15import androidx.core.widget.doOnTextChanged
16import androidx.fragment.app.Fragment
17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
21import androidx.recyclerview.widget.LinearLayoutManager
22import com.google.android.material.divider.MaterialDividerItemDecoration
23import com.google.android.material.transition.MaterialSharedAxis
24import info.debatty.java.stringsimilarity.Cosine
25import kotlinx.coroutines.launch
26import org.yuzu.yuzu_emu.R
27import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
28import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
29import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
30import org.yuzu.yuzu_emu.model.SettingsViewModel
31import org.yuzu.yuzu_emu.utils.NativeConfig
32
33class SettingsSearchFragment : Fragment() {
34 private var _binding: FragmentSettingsSearchBinding? = null
35 private val binding get() = _binding!!
36
37 private var settingsAdapter: SettingsAdapter? = null
38
39 private val settingsViewModel: SettingsViewModel by activityViewModels()
40
41 override fun onCreate(savedInstanceState: Bundle?) {
42 super.onCreate(savedInstanceState)
43 enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
44 returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
45 }
46
47 override fun onCreateView(
48 inflater: LayoutInflater,
49 container: ViewGroup?,
50 savedInstanceState: Bundle?
51 ): View {
52 _binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
53 return binding.root
54 }
55
56 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
57 super.onViewCreated(view, savedInstanceState)
58 settingsViewModel.setIsUsingSearch(true)
59
60 if (savedInstanceState != null) {
61 binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
62 }
63
64 settingsAdapter = SettingsAdapter(this, requireContext())
65
66 val dividerDecoration = MaterialDividerItemDecoration(
67 requireContext(),
68 LinearLayoutManager.VERTICAL
69 )
70 dividerDecoration.isLastItemDecorated = false
71 binding.settingsList.apply {
72 adapter = settingsAdapter
73 layoutManager = LinearLayoutManager(requireContext())
74 addItemDecoration(dividerDecoration)
75 }
76
77 focusSearch()
78
79 binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
80 binding.searchBackground.setOnClickListener { focusSearch() }
81 binding.clearButton.setOnClickListener { binding.searchText.setText("") }
82 binding.searchText.doOnTextChanged { _, _, _, _ ->
83 search()
84 binding.settingsList.smoothScrollToPosition(0)
85 }
86 viewLifecycleOwner.lifecycleScope.launch {
87 repeatOnLifecycle(Lifecycle.State.CREATED) {
88 settingsViewModel.shouldReloadSettingsList.collect {
89 if (it) {
90 settingsViewModel.setShouldReloadSettingsList(false)
91 search()
92 }
93 }
94 }
95 }
96
97 search()
98
99 setInsets()
100 }
101
102 override fun onSaveInstanceState(outState: Bundle) {
103 super.onSaveInstanceState(outState)
104 outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
105 }
106
107 private fun search() {
108 val searchTerm = binding.searchText.text.toString().lowercase()
109 binding.clearButton.visibility =
110 if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
111 if (searchTerm.isEmpty()) {
112 binding.noResultsView.visibility = View.VISIBLE
113 settingsAdapter?.submitList(emptyList())
114 return
115 }
116
117 val baseList = SettingsItem.settingsItems
118 val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
119 val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
120 val title = getString(item.value.nameId).lowercase()
121 val similarity = similarityAlgorithm.similarity(searchTerm, title)
122 if (similarity > 0.08) {
123 Pair(similarity, item)
124 } else {
125 null
126 }
127 }.sortedByDescending { it.first }.mapNotNull {
128 val item = it.second.value
129 val pairedSettingKey = item.setting.pairedSettingKey
130 val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
131 val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
132 if (pairedSettingValue) it.second.value else null
133 } else {
134 it.second.value
135 }
136 optionalSetting
137 }
138 settingsAdapter?.submitList(sortedList)
139 binding.noResultsView.visibility =
140 if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
141 }
142
143 private fun focusSearch() {
144 binding.searchText.requestFocus()
145 val imm = requireActivity()
146 .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
147 imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
148 }
149
150 private fun setInsets() =
151 ViewCompat.setOnApplyWindowInsetsListener(
152 binding.root
153 ) { _: View, windowInsets: WindowInsetsCompat ->
154 val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
155 val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
156 val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
157
158 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
159 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
160
161 val leftInsets = barInsets.left + cutoutInsets.left
162 val rightInsets = barInsets.right + cutoutInsets.right
163
164 binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
165 binding.frameSearch.updatePadding(
166 left = leftInsets + sideMargin,
167 top = barInsets.top + topMargin,
168 right = rightInsets + sideMargin
169 )
170 binding.noResultsView.updatePadding(
171 left = leftInsets,
172 right = rightInsets,
173 bottom = barInsets.bottom
174 )
175
176 val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
177 mlpSettingsList.leftMargin = leftInsets + sideMargin
178 mlpSettingsList.rightMargin = rightInsets + sideMargin
179 binding.settingsList.layoutParams = mlpSettingsList
180
181 val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
182 mlpDivider.leftMargin = leftInsets + sideMargin
183 mlpDivider.rightMargin = rightInsets + sideMargin
184 binding.divider.layoutParams = mlpDivider
185
186 windowInsets
187 }
188
189 companion object {
190 const val SEARCH_TEXT = "SearchText"
191 }
192}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index 6c4ddaf6b..fbb2f6e18 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -19,12 +19,17 @@ import androidx.core.content.ContextCompat
19import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowInsetsCompat 20import androidx.core.view.WindowInsetsCompat
21import androidx.core.view.isVisible 21import androidx.core.view.isVisible
22import androidx.core.view.updatePadding
22import androidx.fragment.app.Fragment 23import androidx.fragment.app.Fragment
23import androidx.fragment.app.activityViewModels 24import androidx.fragment.app.activityViewModels
25import androidx.lifecycle.Lifecycle
26import androidx.lifecycle.lifecycleScope
27import androidx.lifecycle.repeatOnLifecycle
24import androidx.navigation.findNavController 28import androidx.navigation.findNavController
25import androidx.preference.PreferenceManager 29import androidx.preference.PreferenceManager
26import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback 30import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
27import com.google.android.material.transition.MaterialFadeThrough 31import com.google.android.material.transition.MaterialFadeThrough
32import kotlinx.coroutines.launch
28import java.io.File 33import java.io.File
29import org.yuzu.yuzu_emu.R 34import org.yuzu.yuzu_emu.R
30import org.yuzu.yuzu_emu.YuzuApplication 35import org.yuzu.yuzu_emu.YuzuApplication
@@ -32,10 +37,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter
32import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding 37import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding
33import org.yuzu.yuzu_emu.features.settings.model.Settings 38import org.yuzu.yuzu_emu.features.settings.model.Settings
34import org.yuzu.yuzu_emu.model.HomeViewModel 39import org.yuzu.yuzu_emu.model.HomeViewModel
40import org.yuzu.yuzu_emu.model.SetupCallback
35import org.yuzu.yuzu_emu.model.SetupPage 41import org.yuzu.yuzu_emu.model.SetupPage
42import org.yuzu.yuzu_emu.model.StepState
36import org.yuzu.yuzu_emu.ui.main.MainActivity 43import org.yuzu.yuzu_emu.ui.main.MainActivity
37import org.yuzu.yuzu_emu.utils.DirectoryInitialization 44import org.yuzu.yuzu_emu.utils.DirectoryInitialization
38import org.yuzu.yuzu_emu.utils.GameHelper 45import org.yuzu.yuzu_emu.utils.GameHelper
46import org.yuzu.yuzu_emu.utils.ViewUtils
39 47
40class SetupFragment : Fragment() { 48class SetupFragment : Fragment() {
41 private var _binding: FragmentSetupBinding? = null 49 private var _binding: FragmentSetupBinding? = null
@@ -112,14 +120,22 @@ class SetupFragment : Fragment() {
112 0, 120 0,
113 false, 121 false,
114 R.string.give_permission, 122 R.string.give_permission,
115 { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }, 123 {
124 notificationCallback = it
125 permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
126 },
116 true, 127 true,
117 R.string.notification_warning, 128 R.string.notification_warning,
118 R.string.notification_warning_description, 129 R.string.notification_warning_description,
119 0, 130 0,
120 { 131 {
121 NotificationManagerCompat.from(requireContext()) 132 if (NotificationManagerCompat.from(requireContext())
122 .areNotificationsEnabled() 133 .areNotificationsEnabled()
134 ) {
135 StepState.COMPLETE
136 } else {
137 StepState.INCOMPLETE
138 }
123 } 139 }
124 ) 140 )
125 ) 141 )
@@ -133,12 +149,22 @@ class SetupFragment : Fragment() {
133 R.drawable.ic_add, 149 R.drawable.ic_add,
134 true, 150 true,
135 R.string.select_keys, 151 R.string.select_keys,
136 { mainActivity.getProdKey.launch(arrayOf("*/*")) }, 152 {
153 keyCallback = it
154 getProdKey.launch(arrayOf("*/*"))
155 },
137 true, 156 true,
138 R.string.install_prod_keys_warning, 157 R.string.install_prod_keys_warning,
139 R.string.install_prod_keys_warning_description, 158 R.string.install_prod_keys_warning_description,
140 R.string.install_prod_keys_warning_help, 159 R.string.install_prod_keys_warning_help,
141 { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() } 160 {
161 val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
162 if (file.exists()) {
163 StepState.COMPLETE
164 } else {
165 StepState.INCOMPLETE
166 }
167 }
142 ) 168 )
143 ) 169 )
144 add( 170 add(
@@ -150,9 +176,8 @@ class SetupFragment : Fragment() {
150 true, 176 true,
151 R.string.add_games, 177 R.string.add_games,
152 { 178 {
153 mainActivity.getGamesDirectory.launch( 179 gamesDirCallback = it
154 Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data 180 getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
155 )
156 }, 181 },
157 true, 182 true,
158 R.string.add_games_warning, 183 R.string.add_games_warning,
@@ -163,7 +188,11 @@ class SetupFragment : Fragment() {
163 PreferenceManager.getDefaultSharedPreferences( 188 PreferenceManager.getDefaultSharedPreferences(
164 YuzuApplication.appContext 189 YuzuApplication.appContext
165 ) 190 )
166 preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() 191 if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
192 StepState.COMPLETE
193 } else {
194 StepState.INCOMPLETE
195 }
167 } 196 }
168 ) 197 )
169 ) 198 )
@@ -181,6 +210,17 @@ class SetupFragment : Fragment() {
181 ) 210 )
182 } 211 }
183 212
213 viewLifecycleOwner.lifecycleScope.launch {
214 repeatOnLifecycle(Lifecycle.State.CREATED) {
215 homeViewModel.shouldPageForward.collect {
216 if (it) {
217 pageForward()
218 homeViewModel.setShouldPageForward(false)
219 }
220 }
221 }
222 }
223
184 binding.viewPager2.apply { 224 binding.viewPager2.apply {
185 adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) 225 adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
186 offscreenPageLimit = 2 226 offscreenPageLimit = 2
@@ -194,15 +234,15 @@ class SetupFragment : Fragment() {
194 super.onPageSelected(position) 234 super.onPageSelected(position)
195 235
196 if (position == 1 && previousPosition == 0) { 236 if (position == 1 && previousPosition == 0) {
197 showView(binding.buttonNext) 237 ViewUtils.showView(binding.buttonNext)
198 showView(binding.buttonBack) 238 ViewUtils.showView(binding.buttonBack)
199 } else if (position == 0 && previousPosition == 1) { 239 } else if (position == 0 && previousPosition == 1) {
200 hideView(binding.buttonBack) 240 ViewUtils.hideView(binding.buttonBack)
201 hideView(binding.buttonNext) 241 ViewUtils.hideView(binding.buttonNext)
202 } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { 242 } else if (position == pages.size - 1 && previousPosition == pages.size - 2) {
203 hideView(binding.buttonNext) 243 ViewUtils.hideView(binding.buttonNext)
204 } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { 244 } else if (position == pages.size - 2 && previousPosition == pages.size - 1) {
205 showView(binding.buttonNext) 245 ViewUtils.showView(binding.buttonNext)
206 } 246 }
207 247
208 previousPosition = position 248 previousPosition = position
@@ -215,7 +255,8 @@ class SetupFragment : Fragment() {
215 255
216 // Checks if the user has completed the task on the current page 256 // Checks if the user has completed the task on the current page
217 if (currentPage.hasWarning) { 257 if (currentPage.hasWarning) {
218 if (currentPage.taskCompleted.invoke()) { 258 val stepState = currentPage.stepCompleted.invoke()
259 if (stepState != StepState.INCOMPLETE) {
219 pageForward() 260 pageForward()
220 return@setOnClickListener 261 return@setOnClickListener
221 } 262 }
@@ -264,9 +305,15 @@ class SetupFragment : Fragment() {
264 _binding = null 305 _binding = null
265 } 306 }
266 307
308 private lateinit var notificationCallback: SetupCallback
309
267 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 310 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
268 private val permissionLauncher = 311 private val permissionLauncher =
269 registerForActivityResult(ActivityResultContracts.RequestPermission()) { 312 registerForActivityResult(ActivityResultContracts.RequestPermission()) {
313 if (it) {
314 notificationCallback.onStepCompleted()
315 }
316
270 if (!it && 317 if (!it &&
271 !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) 318 !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
272 ) { 319 ) {
@@ -277,6 +324,27 @@ class SetupFragment : Fragment() {
277 } 324 }
278 } 325 }
279 326
327 private lateinit var keyCallback: SetupCallback
328
329 val getProdKey =
330 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
331 if (result != null) {
332 if (mainActivity.processKey(result)) {
333 keyCallback.onStepCompleted()
334 }
335 }
336 }
337
338 private lateinit var gamesDirCallback: SetupCallback
339
340 val getGamesDirectory =
341 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
342 if (result != null) {
343 mainActivity.processGamesDir(result)
344 gamesDirCallback.onStepCompleted()
345 }
346 }
347
280 private fun finishSetup() { 348 private fun finishSetup() {
281 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 349 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
282 .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) 350 .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
@@ -284,33 +352,6 @@ class SetupFragment : Fragment() {
284 mainActivity.finishSetup(binding.root.findNavController()) 352 mainActivity.finishSetup(binding.root.findNavController())
285 } 353 }
286 354
287 private fun showView(view: View) {
288 view.apply {
289 alpha = 0f
290 visibility = View.VISIBLE
291 isClickable = true
292 }.animate().apply {
293 duration = 300
294 alpha(1f)
295 }.start()
296 }
297
298 private fun hideView(view: View) {
299 if (view.visibility == View.INVISIBLE) {
300 return
301 }
302
303 view.apply {
304 alpha = 1f
305 isClickable = false
306 }.animate().apply {
307 duration = 300
308 alpha(0f)
309 }.withEndAction {
310 view.visibility = View.INVISIBLE
311 }
312 }
313
314 fun pageForward() { 355 fun pageForward() {
315 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 356 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
316 } 357 }
@@ -326,15 +367,29 @@ class SetupFragment : Fragment() {
326 private fun setInsets() = 367 private fun setInsets() =
327 ViewCompat.setOnApplyWindowInsetsListener( 368 ViewCompat.setOnApplyWindowInsetsListener(
328 binding.root 369 binding.root
329 ) { view: View, windowInsets: WindowInsetsCompat -> 370 ) { _: View, windowInsets: WindowInsetsCompat ->
330 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 371 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
331 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 372 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
332 view.setPadding( 373
333 barInsets.left + cutoutInsets.left, 374 val leftPadding = barInsets.left + cutoutInsets.left
334 barInsets.top + cutoutInsets.top, 375 val topPadding = barInsets.top + cutoutInsets.top
335 barInsets.right + cutoutInsets.right, 376 val rightPadding = barInsets.right + cutoutInsets.right
336 barInsets.bottom + cutoutInsets.bottom 377 val bottomPadding = barInsets.bottom + cutoutInsets.bottom
337 ) 378
379 if (resources.getBoolean(R.bool.small_layout)) {
380 binding.viewPager2
381 .updatePadding(left = leftPadding, top = topPadding, right = rightPadding)
382 binding.constraintButtons
383 .updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding)
384 } else {
385 binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding)
386 binding.constraintButtons
387 .updatePadding(
388 left = leftPadding,
389 right = rightPadding,
390 bottom = bottomPadding
391 )
392 }
338 windowInsets 393 windowInsets
339 } 394 }
340} 395}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
new file mode 100644
index 000000000..f34870c2d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
@@ -0,0 +1,67 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
9
10class EmulationViewModel : ViewModel() {
11 val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
12 private val _emulationStarted = MutableStateFlow(false)
13
14 val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
15 private val _isEmulationStopping = MutableStateFlow(false)
16
17 val shaderProgress: StateFlow<Int> get() = _shaderProgress
18 private val _shaderProgress = MutableStateFlow(0)
19
20 val totalShaders: StateFlow<Int> get() = _totalShaders
21 private val _totalShaders = MutableStateFlow(0)
22
23 val shaderMessage: StateFlow<String> get() = _shaderMessage
24 private val _shaderMessage = MutableStateFlow("")
25
26 fun setEmulationStarted(started: Boolean) {
27 _emulationStarted.value = started
28 }
29
30 fun setIsEmulationStopping(value: Boolean) {
31 _isEmulationStopping.value = value
32 }
33
34 fun setShaderProgress(progress: Int) {
35 _shaderProgress.value = progress
36 }
37
38 fun setTotalShaders(max: Int) {
39 _totalShaders.value = max
40 }
41
42 fun setShaderMessage(msg: String) {
43 _shaderMessage.value = msg
44 }
45
46 fun updateProgress(msg: String, progress: Int, max: Int) {
47 setShaderMessage(msg)
48 setShaderProgress(progress)
49 setTotalShaders(max)
50 }
51
52 fun clear() {
53 setEmulationStarted(false)
54 setIsEmulationStopping(false)
55 setShaderProgress(0)
56 setTotalShaders(0)
57 setShaderMessage("")
58 }
59
60 companion object {
61 const val KEY_EMULATION_STARTED = "EmulationStarted"
62 const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
63 const val KEY_SHADER_PROGRESS = "ShaderProgress"
64 const val KEY_TOTAL_SHADERS = "TotalShaders"
65 const val KEY_SHADER_MESSAGE = "ShaderMessage"
66 }
67}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 1fe42f922..6e09fa81d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.viewModelScope 9import androidx.lifecycle.viewModelScope
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
13import java.util.Locale 11import java.util.Locale
14import kotlinx.coroutines.Dispatchers 12import kotlinx.coroutines.Dispatchers
13import kotlinx.coroutines.flow.MutableStateFlow
14import kotlinx.coroutines.flow.StateFlow
15import kotlinx.coroutines.launch 15import kotlinx.coroutines.launch
16import kotlinx.coroutines.withContext 16import kotlinx.coroutines.withContext
17import kotlinx.serialization.ExperimentalSerializationApi 17import kotlinx.serialization.ExperimentalSerializationApi
@@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
24 24
25@OptIn(ExperimentalSerializationApi::class) 25@OptIn(ExperimentalSerializationApi::class)
26class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
27 private val _games = MutableLiveData<List<Game>>(emptyList()) 27 val games: StateFlow<List<Game>> get() = _games
28 val games: LiveData<List<Game>> get() = _games 28 private val _games = MutableStateFlow(emptyList<Game>())
29 29
30 private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) 30 val searchedGames: StateFlow<List<Game>> get() = _searchedGames
31 val searchedGames: LiveData<List<Game>> get() = _searchedGames 31 private val _searchedGames = MutableStateFlow(emptyList<Game>())
32 32
33 private val _isReloading = MutableLiveData(false) 33 val isReloading: StateFlow<Boolean> get() = _isReloading
34 val isReloading: LiveData<Boolean> get() = _isReloading 34 private val _isReloading = MutableStateFlow(false)
35 35
36 private val _shouldSwapData = MutableLiveData(false) 36 val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
37 val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData 37 private val _shouldSwapData = MutableStateFlow(false)
38 38
39 private val _shouldScrollToTop = MutableLiveData(false) 39 val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
40 val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop 40 private val _shouldScrollToTop = MutableStateFlow(false)
41 41
42 private val _searchFocused = MutableLiveData(false) 42 val searchFocused: StateFlow<Boolean> get() = _searchFocused
43 val searchFocused: LiveData<Boolean> get() = _searchFocused 43 private val _searchFocused = MutableStateFlow(false)
44 44
45 init { 45 init {
46 // Ensure keys are loaded so that ROM metadata can be decrypted. 46 // Ensure keys are loaded so that ROM metadata can be decrypted.
@@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
79 ) 79 )
80 ) 80 )
81 81
82 _games.postValue(sortedList) 82 _games.value = sortedList
83 } 83 }
84 84
85 fun setSearchedGames(games: List<Game>) { 85 fun setSearchedGames(games: List<Game>) {
86 _searchedGames.postValue(games) 86 _searchedGames.value = games
87 } 87 }
88 88
89 fun setShouldSwapData(shouldSwap: Boolean) { 89 fun setShouldSwapData(shouldSwap: Boolean) {
90 _shouldSwapData.postValue(shouldSwap) 90 _shouldSwapData.value = shouldSwap
91 } 91 }
92 92
93 fun setShouldScrollToTop(shouldScroll: Boolean) { 93 fun setShouldScrollToTop(shouldScroll: Boolean) {
94 _shouldScrollToTop.postValue(shouldScroll) 94 _shouldScrollToTop.value = shouldScroll
95 } 95 }
96 96
97 fun setSearchFocused(searchFocused: Boolean) { 97 fun setSearchFocused(searchFocused: Boolean) {
98 _searchFocused.postValue(searchFocused) 98 _searchFocused.value = searchFocused
99 } 99 }
100 100
101 fun reloadGames(directoryChanged: Boolean) { 101 fun reloadGames(directoryChanged: Boolean) {
102 if (isReloading.value == true) { 102 if (isReloading.value) {
103 return 103 return
104 } 104 }
105 _isReloading.postValue(true) 105 _isReloading.value = true
106 106
107 viewModelScope.launch { 107 viewModelScope.launch {
108 withContext(Dispatchers.IO) { 108 withContext(Dispatchers.IO) {
109 NativeLibrary.resetRomMetadata() 109 NativeLibrary.resetRomMetadata()
110 setGames(GameHelper.getGames()) 110 setGames(GameHelper.getGames())
111 _isReloading.postValue(false) 111 _isReloading.value = false
112 112
113 if (directoryChanged) { 113 if (directoryChanged) {
114 setShouldSwapData(true) 114 setShouldSwapData(true)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
index 522d07c37..b32e19373 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
@@ -3,6 +3,9 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import kotlinx.coroutines.flow.MutableStateFlow
7import kotlinx.coroutines.flow.StateFlow
8
6data class HomeSetting( 9data class HomeSetting(
7 val titleId: Int, 10 val titleId: Int,
8 val descriptionId: Int, 11 val descriptionId: Int,
@@ -10,5 +13,6 @@ data class HomeSetting(
10 val onClick: () -> Unit, 13 val onClick: () -> Unit,
11 val isEnabled: () -> Boolean = { true }, 14 val isEnabled: () -> Boolean = { true },
12 val disabledTitleId: Int = 0, 15 val disabledTitleId: Int = 0,
13 val disabledMessageId: Int = 0 16 val disabledMessageId: Int = 0,
17 val details: StateFlow<String> = MutableStateFlow("")
14) 18)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index 263ee7144..756f76721 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -3,34 +3,56 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData 6import android.net.Uri
7import androidx.lifecycle.MutableLiveData 7import androidx.fragment.app.FragmentActivity
8import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
9import androidx.lifecycle.ViewModelProvider
10import androidx.preference.PreferenceManager
11import kotlinx.coroutines.flow.MutableStateFlow
12import kotlinx.coroutines.flow.StateFlow
13import org.yuzu.yuzu_emu.YuzuApplication
14import org.yuzu.yuzu_emu.utils.GameHelper
9 15
10class HomeViewModel : ViewModel() { 16class HomeViewModel : ViewModel() {
11 private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() 17 val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
12 val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible 18 private val _navigationVisible = MutableStateFlow(Pair(false, false))
13 19
14 private val _statusBarShadeVisible = MutableLiveData(true) 20 val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
15 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible 21 private val _statusBarShadeVisible = MutableStateFlow(true)
16 22
17 var navigatedToSetup = false 23 val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
24 private val _shouldPageForward = MutableStateFlow(false)
18 25
19 init { 26 val gamesDir: StateFlow<String> get() = _gamesDir
20 _navigationVisible.value = Pair(false, false) 27 private val _gamesDir = MutableStateFlow(
21 } 28 Uri.parse(
29 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
30 .getString(GameHelper.KEY_GAME_PATH, "")
31 ).path ?: ""
32 )
33
34 var navigatedToSetup = false
22 35
23 fun setNavigationVisibility(visible: Boolean, animated: Boolean) { 36 fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
24 if (_navigationVisible.value?.first == visible) { 37 if (navigationVisible.value.first == visible) {
25 return 38 return
26 } 39 }
27 _navigationVisible.value = Pair(visible, animated) 40 _navigationVisible.value = Pair(visible, animated)
28 } 41 }
29 42
30 fun setStatusBarShadeVisibility(visible: Boolean) { 43 fun setStatusBarShadeVisibility(visible: Boolean) {
31 if (_statusBarShadeVisible.value == visible) { 44 if (statusBarShadeVisible.value == visible) {
32 return 45 return
33 } 46 }
34 _statusBarShadeVisible.value = visible 47 _statusBarShadeVisible.value = visible
35 } 48 }
49
50 fun setShouldPageForward(pageForward: Boolean) {
51 _shouldPageForward.value = pageForward
52 }
53
54 fun setGamesDir(activity: FragmentActivity, dir: String) {
55 ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
56 _gamesDir.value = dir
57 }
36} 58}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
new file mode 100644
index 000000000..53fa7a8de
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -0,0 +1,85 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
9import org.yuzu.yuzu_emu.R
10import org.yuzu.yuzu_emu.YuzuApplication
11import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
12
13class SettingsViewModel : ViewModel() {
14 var game: Game? = null
15
16 var shouldSave = false
17
18 var clickedItem: SettingsItem? = null
19
20 val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
21 private val _shouldRecreate = MutableStateFlow(false)
22
23 val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
24 private val _shouldNavigateBack = MutableStateFlow(false)
25
26 val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
27 private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
28
29 val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
30 private val _shouldReloadSettingsList = MutableStateFlow(false)
31
32 val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
33 private val _isUsingSearch = MutableStateFlow(false)
34
35 val sliderProgress: StateFlow<Int> get() = _sliderProgress
36 private val _sliderProgress = MutableStateFlow(-1)
37
38 val sliderTextValue: StateFlow<String> get() = _sliderTextValue
39 private val _sliderTextValue = MutableStateFlow("")
40
41 val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
42 private val _adapterItemChanged = MutableStateFlow(-1)
43
44 fun setShouldRecreate(value: Boolean) {
45 _shouldRecreate.value = value
46 }
47
48 fun setShouldNavigateBack(value: Boolean) {
49 _shouldNavigateBack.value = value
50 }
51
52 fun setShouldShowResetSettingsDialog(value: Boolean) {
53 _shouldShowResetSettingsDialog.value = value
54 }
55
56 fun setShouldReloadSettingsList(value: Boolean) {
57 _shouldReloadSettingsList.value = value
58 }
59
60 fun setIsUsingSearch(value: Boolean) {
61 _isUsingSearch.value = value
62 }
63
64 fun setSliderTextValue(value: Float, units: String) {
65 _sliderProgress.value = value.toInt()
66 _sliderTextValue.value = String.format(
67 YuzuApplication.appContext.getString(R.string.value_with_units),
68 value.toInt().toString(),
69 units
70 )
71 }
72
73 fun setSliderProgress(value: Float) {
74 _sliderProgress.value = value.toInt()
75 }
76
77 fun setAdapterItemChanged(value: Int) {
78 _adapterItemChanged.value = value
79 }
80
81 fun clear() {
82 game = null
83 shouldSave = false
84 }
85}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
index a0c878e1c..09a128ae6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
@@ -10,10 +10,20 @@ data class SetupPage(
10 val buttonIconId: Int, 10 val buttonIconId: Int,
11 val leftAlignedIcon: Boolean, 11 val leftAlignedIcon: Boolean,
12 val buttonTextId: Int, 12 val buttonTextId: Int,
13 val buttonAction: () -> Unit, 13 val buttonAction: (callback: SetupCallback) -> Unit,
14 val hasWarning: Boolean, 14 val hasWarning: Boolean,
15 val warningTitleId: Int = 0, 15 val warningTitleId: Int = 0,
16 val warningDescriptionId: Int = 0, 16 val warningDescriptionId: Int = 0,
17 val warningHelpLinkId: Int = 0, 17 val warningHelpLinkId: Int = 0,
18 val taskCompleted: () -> Boolean = { true } 18 val stepCompleted: () -> StepState = { StepState.UNDEFINED }
19) 19)
20
21interface SetupCallback {
22 fun onStepCompleted()
23}
24
25enum class StepState {
26 COMPLETE,
27 INCOMPLETE,
28 UNDEFINED
29}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index 27ea725a5..531c2aaf0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -3,29 +3,25 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
9import androidx.lifecycle.viewModelScope 7import androidx.lifecycle.viewModelScope
10import kotlinx.coroutines.Dispatchers 8import kotlinx.coroutines.Dispatchers
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.launch 11import kotlinx.coroutines.launch
12 12
13class TaskViewModel : ViewModel() { 13class TaskViewModel : ViewModel() {
14 private val _result = MutableLiveData<Any>() 14 val result: StateFlow<Any> get() = _result
15 val result: LiveData<Any> = _result 15 private val _result = MutableStateFlow(Any())
16 16
17 private val _isComplete = MutableLiveData<Boolean>() 17 val isComplete: StateFlow<Boolean> get() = _isComplete
18 val isComplete: LiveData<Boolean> = _isComplete 18 private val _isComplete = MutableStateFlow(false)
19 19
20 private val _isRunning = MutableLiveData<Boolean>() 20 val isRunning: StateFlow<Boolean> get() = _isRunning
21 val isRunning: LiveData<Boolean> = _isRunning 21 private val _isRunning = MutableStateFlow(false)
22 22
23 lateinit var task: () -> Any 23 lateinit var task: () -> Any
24 24
25 init {
26 clear()
27 }
28
29 fun clear() { 25 fun clear() {
30 _result.value = Any() 26 _result.value = Any()
31 _isComplete.value = false 27 _isComplete.value = false
@@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
33 } 29 }
34 30
35 fun runTask() { 31 fun runTask() {
36 if (_isRunning.value == true) { 32 if (isRunning.value) {
37 return 33 return
38 } 34 }
39 _isRunning.value = true 35 _isRunning.value = true
40 36
41 viewModelScope.launch(Dispatchers.IO) { 37 viewModelScope.launch(Dispatchers.IO) {
42 val res = task() 38 val res = task()
43 _result.postValue(res) 39 _result.value = res
44 _isComplete.postValue(true) 40 _isComplete.value = true
41 _isRunning.value = false
45 } 42 }
46 } 43 }
47} 44}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
index b0156dca5..805b89b31 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.ui 4package org.yuzu.yuzu_emu.ui
5 5
6import android.annotation.SuppressLint
6import android.os.Bundle 7import android.os.Bundle
7import android.view.LayoutInflater 8import android.view.LayoutInflater
8import android.view.View 9import android.view.View
@@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding 15import androidx.core.view.updatePadding
15import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
17import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
18import com.google.android.material.transition.MaterialFadeThrough 22import com.google.android.material.transition.MaterialFadeThrough
23import kotlinx.coroutines.launch
19import org.yuzu.yuzu_emu.R 24import org.yuzu.yuzu_emu.R
20import org.yuzu.yuzu_emu.adapters.GameAdapter 25import org.yuzu.yuzu_emu.adapters.GameAdapter
21import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding 26import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
44 return binding.root 49 return binding.root
45 } 50 }
46 51
52 // This is using the correct scope, lint is just acting up
53 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 homeViewModel.setNavigationVisibility(visible = true, animated = false) 55 homeViewModel.setNavigationVisibility(visible = true, animated = false)
49 56
@@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
80 if (_binding == null) { 87 if (_binding == null) {
81 return@post 88 return@post
82 } 89 }
83 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! 90 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
84 } 91 }
85 } 92 }
86 93
87 gamesViewModel.apply { 94 viewLifecycleOwner.lifecycleScope.apply {
88 // Watch for when we get updates to any of our games lists 95 launch {
89 isReloading.observe(viewLifecycleOwner) { isReloading -> 96 repeatOnLifecycle(Lifecycle.State.RESUMED) {
90 binding.swipeRefresh.isRefreshing = isReloading 97 gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
98 }
91 } 99 }
92 games.observe(viewLifecycleOwner) { 100 launch {
93 (binding.gridGames.adapter as GameAdapter).submitList(it) 101 repeatOnLifecycle(Lifecycle.State.RESUMED) {
94 if (it.isEmpty()) { 102 gamesViewModel.games.collect {
95 binding.noticeText.visibility = View.VISIBLE 103 (binding.gridGames.adapter as GameAdapter).submitList(it)
96 } else { 104 if (it.isEmpty()) {
97 binding.noticeText.visibility = View.GONE 105 binding.noticeText.visibility = View.VISIBLE
106 } else {
107 binding.noticeText.visibility = View.GONE
108 }
109 }
98 } 110 }
99 } 111 }
100 shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> 112 launch {
101 if (shouldSwapData) { 113 repeatOnLifecycle(Lifecycle.State.RESUMED) {
102 (binding.gridGames.adapter as GameAdapter).submitList( 114 gamesViewModel.shouldSwapData.collect {
103 gamesViewModel.games.value!! 115 if (it) {
104 ) 116 (binding.gridGames.adapter as GameAdapter).submitList(
105 gamesViewModel.setShouldSwapData(false) 117 gamesViewModel.games.value
118 )
119 gamesViewModel.setShouldSwapData(false)
120 }
121 }
106 } 122 }
107 } 123 }
108 124 launch {
109 // Check if the user reselected the games menu item and then scroll to top of the list 125 repeatOnLifecycle(Lifecycle.State.RESUMED) {
110 shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> 126 gamesViewModel.shouldScrollToTop.collect {
111 if (shouldScroll) { 127 if (it) {
112 scrollToTop() 128 scrollToTop()
113 gamesViewModel.setShouldScrollToTop(false) 129 gamesViewModel.setShouldScrollToTop(false)
130 }
131 }
114 } 132 }
115 } 133 }
116 } 134 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index f7d7aed1e..b6b6c6c17 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
19import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowCompat 20import androidx.core.view.WindowCompat
21import androidx.core.view.WindowInsetsCompat 21import androidx.core.view.WindowInsetsCompat
22import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope 23import androidx.lifecycle.lifecycleScope
24import androidx.lifecycle.repeatOnLifecycle
23import androidx.navigation.NavController 25import androidx.navigation.NavController
24import androidx.navigation.fragment.NavHostFragment 26import androidx.navigation.fragment.NavHostFragment
25import androidx.navigation.ui.setupWithNavController 27import androidx.navigation.ui.setupWithNavController
@@ -33,17 +35,14 @@ import java.io.IOException
33import kotlinx.coroutines.Dispatchers 35import kotlinx.coroutines.Dispatchers
34import kotlinx.coroutines.launch 36import kotlinx.coroutines.launch
35import kotlinx.coroutines.withContext 37import kotlinx.coroutines.withContext
38import org.yuzu.yuzu_emu.HomeNavigationDirections
36import org.yuzu.yuzu_emu.NativeLibrary 39import org.yuzu.yuzu_emu.NativeLibrary
37import org.yuzu.yuzu_emu.R 40import org.yuzu.yuzu_emu.R
38import org.yuzu.yuzu_emu.activities.EmulationActivity 41import org.yuzu.yuzu_emu.activities.EmulationActivity
39import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 42import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
40import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 43import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
41import org.yuzu.yuzu_emu.features.settings.model.Settings 44import org.yuzu.yuzu_emu.features.settings.model.Settings
42import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
43import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
44import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
46import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
47import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
48import org.yuzu.yuzu_emu.model.GamesViewModel 47import org.yuzu.yuzu_emu.model.GamesViewModel
49import org.yuzu.yuzu_emu.model.HomeViewModel 48import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
54 53
55 private val homeViewModel: HomeViewModel by viewModels() 54 private val homeViewModel: HomeViewModel by viewModels()
56 private val gamesViewModel: GamesViewModel by viewModels() 55 private val gamesViewModel: GamesViewModel by viewModels()
57 private val settingsViewModel: SettingsViewModel by viewModels()
58 56
59 override var themeId: Int = 0 57 override var themeId: Int = 0
60 58
@@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
62 val splashScreen = installSplashScreen() 60 val splashScreen = installSplashScreen()
63 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } 61 splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
64 62
65 settingsViewModel.settings.loadSettings()
66
67 ThemeHelper.setTheme(this) 63 ThemeHelper.setTheme(this)
68 64
69 super.onCreate(savedInstanceState) 65 super.onCreate(savedInstanceState)
@@ -109,25 +105,33 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
109 when (it.itemId) { 105 when (it.itemId) {
110 R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true) 106 R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
111 R.id.searchFragment -> gamesViewModel.setSearchFocused(true) 107 R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
112 R.id.homeSettingsFragment -> SettingsActivity.launch( 108 R.id.homeSettingsFragment -> {
113 this, 109 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
114 SettingsFile.FILE_NAME_CONFIG, 110 null,
115 "" 111 Settings.MenuTag.SECTION_ROOT
116 ) 112 )
113 navHostFragment.navController.navigate(action)
114 }
117 } 115 }
118 } 116 }
119 117
120 // Prevents navigation from being drawn for a short time on recreation if set to hidden 118 // Prevents navigation from being drawn for a short time on recreation if set to hidden
121 if (!homeViewModel.navigationVisible.value?.first!!) { 119 if (!homeViewModel.navigationVisible.value.first) {
122 binding.navigationView.visibility = View.INVISIBLE 120 binding.navigationView.visibility = View.INVISIBLE
123 binding.statusBarShade.visibility = View.INVISIBLE 121 binding.statusBarShade.visibility = View.INVISIBLE
124 } 122 }
125 123
126 homeViewModel.navigationVisible.observe(this) { 124 lifecycleScope.apply {
127 showNavigation(it.first, it.second) 125 launch {
128 } 126 repeatOnLifecycle(Lifecycle.State.CREATED) {
129 homeViewModel.statusBarShadeVisible.observe(this) { visible -> 127 homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
130 showStatusBarShade(visible) 128 }
129 }
130 launch {
131 repeatOnLifecycle(Lifecycle.State.CREATED) {
132 homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
133 }
134 }
131 } 135 }
132 136
133 // Dismiss previous notifications (should not happen unless a crash occurred) 137 // Dismiss previous notifications (should not happen unless a crash occurred)
@@ -266,73 +270,81 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
266 270
267 val getGamesDirectory = 271 val getGamesDirectory =
268 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> 272 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
269 if (result == null) { 273 if (result != null) {
270 return@registerForActivityResult 274 processGamesDir(result)
271 } 275 }
276 }
272 277
273 contentResolver.takePersistableUriPermission( 278 fun processGamesDir(result: Uri) {
274 result, 279 contentResolver.takePersistableUriPermission(
275 Intent.FLAG_GRANT_READ_URI_PERMISSION 280 result,
276 ) 281 Intent.FLAG_GRANT_READ_URI_PERMISSION
282 )
277 283
278 // When a new directory is picked, we currently will reset the existing games 284 // When a new directory is picked, we currently will reset the existing games
279 // database. This effectively means that only one game directory is supported. 285 // database. This effectively means that only one game directory is supported.
280 PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() 286 PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
281 .putString(GameHelper.KEY_GAME_PATH, result.toString()) 287 .putString(GameHelper.KEY_GAME_PATH, result.toString())
282 .apply() 288 .apply()
283 289
284 Toast.makeText( 290 Toast.makeText(
285 applicationContext, 291 applicationContext,
286 R.string.games_dir_selected, 292 R.string.games_dir_selected,
287 Toast.LENGTH_LONG 293 Toast.LENGTH_LONG
288 ).show() 294 ).show()
289 295
290 gamesViewModel.reloadGames(true) 296 gamesViewModel.reloadGames(true)
291 } 297 homeViewModel.setGamesDir(this, result.path!!)
298 }
292 299
293 val getProdKey = 300 val getProdKey =
294 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 301 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
295 if (result == null) { 302 if (result != null) {
296 return@registerForActivityResult 303 processKey(result)
297 } 304 }
305 }
298 306
299 if (FileUtil.getExtension(result) != "keys") { 307 fun processKey(result: Uri): Boolean {
300 MessageDialogFragment.newInstance( 308 if (FileUtil.getExtension(result) != "keys") {
301 R.string.reading_keys_failure, 309 MessageDialogFragment.newInstance(
302 R.string.install_prod_keys_failure_extension_description 310 titleId = R.string.reading_keys_failure,
303 ).show(supportFragmentManager, MessageDialogFragment.TAG) 311 descriptionId = R.string.install_prod_keys_failure_extension_description
304 return@registerForActivityResult 312 ).show(supportFragmentManager, MessageDialogFragment.TAG)
305 } 313 return false
314 }
306 315
307 contentResolver.takePersistableUriPermission( 316 contentResolver.takePersistableUriPermission(
317 result,
318 Intent.FLAG_GRANT_READ_URI_PERMISSION
319 )
320
321 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
322 if (FileUtil.copyUriToInternalStorage(
323 applicationContext,
308 result, 324 result,
309 Intent.FLAG_GRANT_READ_URI_PERMISSION 325 dstPath,
326 "prod.keys"
310 ) 327 )
311 328 ) {
312 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 329 if (NativeLibrary.reloadKeys()) {
313 if (FileUtil.copyUriToInternalStorage( 330 Toast.makeText(
314 applicationContext, 331 applicationContext,
315 result, 332 R.string.install_keys_success,
316 dstPath, 333 Toast.LENGTH_SHORT
317 "prod.keys" 334 ).show()
318 ) 335 gamesViewModel.reloadGames(true)
319 ) { 336 return true
320 if (NativeLibrary.reloadKeys()) { 337 } else {
321 Toast.makeText( 338 MessageDialogFragment.newInstance(
322 applicationContext, 339 titleId = R.string.invalid_keys_error,
323 R.string.install_keys_success, 340 descriptionId = R.string.install_keys_failure_description,
324 Toast.LENGTH_SHORT 341 helpLinkId = R.string.dumping_keys_quickstart_link
325 ).show() 342 ).show(supportFragmentManager, MessageDialogFragment.TAG)
326 gamesViewModel.reloadGames(true) 343 return false
327 } else {
328 MessageDialogFragment.newInstance(
329 R.string.invalid_keys_error,
330 R.string.install_keys_failure_description,
331 R.string.dumping_keys_quickstart_link
332 ).show(supportFragmentManager, MessageDialogFragment.TAG)
333 }
334 } 344 }
335 } 345 }
346 return false
347 }
336 348
337 val getFirmware = 349 val getFirmware =
338 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 350 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
@@ -364,8 +376,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
364 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 376 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
365 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { 377 messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
366 MessageDialogFragment.newInstance( 378 MessageDialogFragment.newInstance(
367 R.string.firmware_installed_failure, 379 titleId = R.string.firmware_installed_failure,
368 R.string.firmware_installed_failure_description 380 descriptionId = R.string.firmware_installed_failure_description
369 ) 381 )
370 } else { 382 } else {
371 firmwarePath.deleteRecursively() 383 firmwarePath.deleteRecursively()
@@ -395,8 +407,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
395 407
396 if (FileUtil.getExtension(result) != "bin") { 408 if (FileUtil.getExtension(result) != "bin") {
397 MessageDialogFragment.newInstance( 409 MessageDialogFragment.newInstance(
398 R.string.reading_keys_failure, 410 titleId = R.string.reading_keys_failure,
399 R.string.install_amiibo_keys_failure_extension_description 411 descriptionId = R.string.install_amiibo_keys_failure_extension_description
400 ).show(supportFragmentManager, MessageDialogFragment.TAG) 412 ).show(supportFragmentManager, MessageDialogFragment.TAG)
401 return@registerForActivityResult 413 return@registerForActivityResult
402 } 414 }
@@ -422,9 +434,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
422 ).show() 434 ).show()
423 } else { 435 } else {
424 MessageDialogFragment.newInstance( 436 MessageDialogFragment.newInstance(
425 R.string.invalid_keys_error, 437 titleId = R.string.invalid_keys_error,
426 R.string.install_keys_failure_description, 438 descriptionId = R.string.install_keys_failure_description,
427 R.string.dumping_keys_quickstart_link 439 helpLinkId = R.string.dumping_keys_quickstart_link
428 ).show(supportFragmentManager, MessageDialogFragment.TAG) 440 ).show(supportFragmentManager, MessageDialogFragment.TAG)
429 } 441 }
430 } 442 }
@@ -496,96 +508,91 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
496 var errorBaseGame = 0 508 var errorBaseGame = 0
497 var errorExtension = 0 509 var errorExtension = 0
498 var errorOther = 0 510 var errorOther = 0
499 var errorTotal = 0 511 documents.forEach {
500 lifecycleScope.launch { 512 when (NativeLibrary.installFileToNand(it.toString())) {
501 documents.forEach { 513 NativeLibrary.InstallFileToNandResult.Success -> {
502 when (NativeLibrary.installFileToNand(it.toString())) { 514 installSuccess += 1
503 NativeLibrary.InstallFileToNandResult.Success -> {
504 installSuccess += 1
505 }
506
507 NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
508 installOverwrite += 1
509 }
510
511 NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
512 errorBaseGame += 1
513 }
514
515 NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
516 errorExtension += 1
517 }
518
519 else -> {
520 errorOther += 1
521 }
522 } 515 }
523 } 516
524 withContext(Dispatchers.Main) { 517 NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
525 val separator = System.getProperty("line.separator") ?: "\n" 518 installOverwrite += 1
526 val installResult = StringBuilder()
527 if (installSuccess > 0) {
528 installResult.append(
529 getString(
530 R.string.install_game_content_success_install,
531 installSuccess
532 )
533 )
534 installResult.append(separator)
535 } 519 }
536 if (installOverwrite > 0) { 520
537 installResult.append( 521 NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
538 getString( 522 errorBaseGame += 1
539 R.string.install_game_content_success_overwrite,
540 installOverwrite
541 )
542 )
543 installResult.append(separator)
544 } 523 }
545 errorTotal = errorBaseGame + errorExtension + errorOther 524
546 if (errorTotal > 0) { 525 NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
547 installResult.append(separator) 526 errorExtension += 1
548 installResult.append( 527 }
549 getString( 528
550 R.string.install_game_content_failed_count, 529 else -> {
551 errorTotal 530 errorOther += 1
552 )
553 )
554 installResult.append(separator)
555 if (errorBaseGame > 0) {
556 installResult.append(separator)
557 installResult.append(
558 getString(R.string.install_game_content_failure_base)
559 )
560 installResult.append(separator)
561 }
562 if (errorExtension > 0) {
563 installResult.append(separator)
564 installResult.append(
565 getString(R.string.install_game_content_failure_file_extension)
566 )
567 installResult.append(separator)
568 }
569 if (errorOther > 0) {
570 installResult.append(
571 getString(R.string.install_game_content_failure_description)
572 )
573 installResult.append(separator)
574 }
575 LongMessageDialogFragment.newInstance(
576 R.string.install_game_content_failure,
577 installResult.toString().trim(),
578 R.string.install_game_content_help_link
579 ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
580 } else {
581 LongMessageDialogFragment.newInstance(
582 R.string.install_game_content_success,
583 installResult.toString().trim()
584 ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
585 } 531 }
586 } 532 }
587 } 533 }
588 return@newInstance installSuccess + installOverwrite + errorTotal 534
535 val separator = System.getProperty("line.separator") ?: "\n"
536 val installResult = StringBuilder()
537 if (installSuccess > 0) {
538 installResult.append(
539 getString(
540 R.string.install_game_content_success_install,
541 installSuccess
542 )
543 )
544 installResult.append(separator)
545 }
546 if (installOverwrite > 0) {
547 installResult.append(
548 getString(
549 R.string.install_game_content_success_overwrite,
550 installOverwrite
551 )
552 )
553 installResult.append(separator)
554 }
555 val errorTotal: Int = errorBaseGame + errorExtension + errorOther
556 if (errorTotal > 0) {
557 installResult.append(separator)
558 installResult.append(
559 getString(
560 R.string.install_game_content_failed_count,
561 errorTotal
562 )
563 )
564 installResult.append(separator)
565 if (errorBaseGame > 0) {
566 installResult.append(separator)
567 installResult.append(
568 getString(R.string.install_game_content_failure_base)
569 )
570 installResult.append(separator)
571 }
572 if (errorExtension > 0) {
573 installResult.append(separator)
574 installResult.append(
575 getString(R.string.install_game_content_failure_file_extension)
576 )
577 installResult.append(separator)
578 }
579 if (errorOther > 0) {
580 installResult.append(
581 getString(R.string.install_game_content_failure_description)
582 )
583 installResult.append(separator)
584 }
585 return@newInstance MessageDialogFragment.newInstance(
586 titleId = R.string.install_game_content_failure,
587 descriptionString = installResult.toString().trim(),
588 helpLinkId = R.string.install_game_content_help_link
589 )
590 } else {
591 return@newInstance MessageDialogFragment.newInstance(
592 titleId = R.string.install_game_content_success,
593 descriptionString = installResult.toString().trim()
594 )
595 }
589 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) 596 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
590 } 597 }
591 } 598 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
deleted file mode 100644
index 9cfda74ee..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
+++ /dev/null
@@ -1,25 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6class BiMap<K, V> {
7 private val forward: MutableMap<K, V> = HashMap()
8 private val backward: MutableMap<V, K> = HashMap()
9
10 @Synchronized
11 fun add(key: K, value: V) {
12 forward[key] = value
13 backward[value] = key
14 }
15
16 @Synchronized
17 fun getForward(key: K): V? {
18 return forward[key]
19 }
20
21 @Synchronized
22 fun getBackward(key: V): K? {
23 return backward[key]
24 }
25}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index 2ee63697e..3c9f6bad0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -3,18 +3,18 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context
7import java.io.IOException 6import java.io.IOException
8import org.yuzu.yuzu_emu.NativeLibrary 7import org.yuzu.yuzu_emu.NativeLibrary
8import org.yuzu.yuzu_emu.YuzuApplication
9 9
10object DirectoryInitialization { 10object DirectoryInitialization {
11 private var userPath: String? = null 11 private var userPath: String? = null
12 12
13 var areDirectoriesReady: Boolean = false 13 var areDirectoriesReady: Boolean = false
14 14
15 fun start(context: Context) { 15 fun start() {
16 if (!areDirectoriesReady) { 16 if (!areDirectoriesReady) {
17 initializeInternalStorage(context) 17 initializeInternalStorage()
18 NativeLibrary.initializeEmulation() 18 NativeLibrary.initializeEmulation()
19 areDirectoriesReady = true 19 areDirectoriesReady = true
20 } 20 }
@@ -26,9 +26,9 @@ object DirectoryInitialization {
26 return userPath 26 return userPath
27 } 27 }
28 28
29 private fun initializeInternalStorage(context: Context) { 29 private fun initializeInternalStorage() {
30 try { 30 try {
31 userPath = context.getExternalFilesDir(null)!!.canonicalPath 31 userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
32 NativeLibrary.setAppDirectory(userPath!!) 32 NativeLibrary.setAppDirectory(userPath!!)
33 } catch (e: IOException) { 33 } catch (e: IOException) {
34 e.printStackTrace() 34 e.printStackTrace()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index f8e7eeca7..e0ee29c9b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -11,6 +11,7 @@ import kotlinx.serialization.json.Json
11import org.yuzu.yuzu_emu.NativeLibrary 11import org.yuzu.yuzu_emu.NativeLibrary
12import org.yuzu.yuzu_emu.YuzuApplication 12import org.yuzu.yuzu_emu.YuzuApplication
13import org.yuzu.yuzu_emu.model.Game 13import org.yuzu.yuzu_emu.model.Game
14import org.yuzu.yuzu_emu.model.MinimalDocumentFile
14 15
15object GameHelper { 16object GameHelper {
16 const val KEY_GAME_PATH = "game_path" 17 const val KEY_GAME_PATH = "game_path"
@@ -29,15 +30,7 @@ object GameHelper {
29 // Ensure keys are loaded so that ROM metadata can be decrypted. 30 // Ensure keys are loaded so that ROM metadata can be decrypted.
30 NativeLibrary.reloadKeys() 31 NativeLibrary.reloadKeys()
31 32
32 val children = FileUtil.listFiles(context, gamesUri) 33 addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3)
33 for (file in children) {
34 if (!file.isDirectory) {
35 // Check that the file has an extension we care about before trying to read out of it.
36 if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
37 games.add(getGame(file.uri))
38 }
39 }
40 }
41 34
42 // Cache list of games found on disk 35 // Cache list of games found on disk
43 val serializedGames = mutableSetOf<String>() 36 val serializedGames = mutableSetOf<String>()
@@ -52,7 +45,31 @@ object GameHelper {
52 return games.toList() 45 return games.toList()
53 } 46 }
54 47
55 private fun getGame(uri: Uri): Game { 48 private fun addGamesRecursive(
49 games: MutableList<Game>,
50 files: Array<MinimalDocumentFile>,
51 depth: Int
52 ) {
53 if (depth <= 0) {
54 return
55 }
56
57 files.forEach {
58 if (it.isDirectory) {
59 addGamesRecursive(
60 games,
61 FileUtil.listFiles(YuzuApplication.appContext, it.uri),
62 depth - 1
63 )
64 } else {
65 if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
66 games.add(getGame(it.uri, true))
67 }
68 }
69 }
70 }
71
72 fun getGame(uri: Uri, addedToLibrary: Boolean): Game {
56 val filePath = uri.toString() 73 val filePath = uri.toString()
57 var name = NativeLibrary.getTitle(filePath) 74 var name = NativeLibrary.getTitle(filePath)
58 75
@@ -77,11 +94,13 @@ object GameHelper {
77 NativeLibrary.isHomebrew(filePath) 94 NativeLibrary.isHomebrew(filePath)
78 ) 95 )
79 96
80 val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L) 97 if (addedToLibrary) {
81 if (addedTime == 0L) { 98 val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
82 preferences.edit() 99 if (addedTime == 0L) {
83 .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis()) 100 preferences.edit()
84 .apply() 101 .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis())
102 .apply()
103 }
85 } 104 }
86 105
87 return newGame 106 return newGame
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
new file mode 100644
index 000000000..9fe99fab1
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
@@ -0,0 +1,88 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.graphics.Bitmap
7import android.graphics.BitmapFactory
8import android.widget.ImageView
9import androidx.core.graphics.drawable.toBitmap
10import androidx.core.graphics.drawable.toDrawable
11import coil.ImageLoader
12import coil.decode.DataSource
13import coil.executeBlocking
14import coil.fetch.DrawableResult
15import coil.fetch.FetchResult
16import coil.fetch.Fetcher
17import coil.key.Keyer
18import coil.memory.MemoryCache
19import coil.request.ImageRequest
20import coil.request.Options
21import org.yuzu.yuzu_emu.NativeLibrary
22import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.YuzuApplication
24import org.yuzu.yuzu_emu.model.Game
25
26class GameIconFetcher(
27 private val game: Game,
28 private val options: Options
29) : Fetcher {
30 override suspend fun fetch(): FetchResult {
31 return DrawableResult(
32 drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources),
33 isSampled = false,
34 dataSource = DataSource.DISK
35 )
36 }
37
38 private fun decodeGameIcon(uri: String): Bitmap? {
39 val data = NativeLibrary.getIcon(uri)
40 return BitmapFactory.decodeByteArray(
41 data,
42 0,
43 data.size,
44 BitmapFactory.Options()
45 )
46 }
47
48 class Factory : Fetcher.Factory<Game> {
49 override fun create(data: Game, options: Options, imageLoader: ImageLoader): Fetcher =
50 GameIconFetcher(data, options)
51 }
52}
53
54class GameIconKeyer : Keyer<Game> {
55 override fun key(data: Game, options: Options): String = data.path
56}
57
58object GameIconUtils {
59 private val imageLoader = ImageLoader.Builder(YuzuApplication.appContext)
60 .components {
61 add(GameIconKeyer())
62 add(GameIconFetcher.Factory())
63 }
64 .memoryCache {
65 MemoryCache.Builder(YuzuApplication.appContext)
66 .maxSizePercent(0.25)
67 .build()
68 }
69 .build()
70
71 fun loadGameIcon(game: Game, imageView: ImageView) {
72 val request = ImageRequest.Builder(YuzuApplication.appContext)
73 .data(game)
74 .target(imageView)
75 .error(R.drawable.default_icon)
76 .build()
77 imageLoader.enqueue(request)
78 }
79
80 fun getGameIcon(game: Game): Bitmap {
81 val request = ImageRequest.Builder(YuzuApplication.appContext)
82 .data(game)
83 .error(R.drawable.default_icon)
84 .build()
85 return imageLoader.executeBlocking(request)
86 .drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
87 }
88}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
new file mode 100644
index 000000000..9425f8b99
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6object NativeConfig {
7 external fun getBoolean(key: String, getDefault: Boolean): Boolean
8 external fun setBoolean(key: String, value: Boolean)
9
10 external fun getByte(key: String, getDefault: Boolean): Byte
11 external fun setByte(key: String, value: Byte)
12
13 external fun getShort(key: String, getDefault: Boolean): Short
14 external fun setShort(key: String, value: Short)
15
16 external fun getInt(key: String, getDefault: Boolean): Int
17 external fun setInt(key: String, value: Int)
18
19 external fun getFloat(key: String, getDefault: Boolean): Float
20 external fun setFloat(key: String, value: Float)
21
22 external fun getLong(key: String, getDefault: Boolean): Long
23 external fun setLong(key: String, value: Long)
24
25 external fun getString(key: String, getDefault: Boolean): String
26 external fun setString(key: String, value: String)
27
28 external fun getIsRuntimeModifiable(key: String): Boolean
29
30 external fun getConfigHeader(category: Int): String
31
32 external fun getPairedSettingKey(key: String): String
33}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
new file mode 100644
index 000000000..f9a3e4126
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.view.View
7
8object ViewUtils {
9 fun showView(view: View, length: Long = 300) {
10 view.apply {
11 alpha = 0f
12 visibility = View.VISIBLE
13 isClickable = true
14 }.animate().apply {
15 duration = length
16 alpha(1f)
17 }.start()
18 }
19
20 fun hideView(view: View, length: Long = 300) {
21 if (view.visibility == View.INVISIBLE) {
22 return
23 }
24
25 view.apply {
26 alpha = 1f
27 isClickable = false
28 }.animate().apply {
29 duration = length
30 alpha(0f)
31 }.withEndAction {
32 view.visibility = View.INVISIBLE
33 }.start()
34 }
35}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
index 685ccaa76..2f0868c63 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt
@@ -7,7 +7,6 @@ import android.content.Context
7import android.util.AttributeSet 7import android.util.AttributeSet
8import android.util.Rational 8import android.util.Rational
9import android.view.SurfaceView 9import android.view.SurfaceView
10import kotlin.math.roundToInt
11 10
12class FixedRatioSurfaceView @JvmOverloads constructor( 11class FixedRatioSurfaceView @JvmOverloads constructor(
13 context: Context, 12 context: Context,
@@ -22,27 +21,44 @@ class FixedRatioSurfaceView @JvmOverloads constructor(
22 */ 21 */
23 fun setAspectRatio(ratio: Rational?) { 22 fun setAspectRatio(ratio: Rational?) {
24 aspectRatio = ratio?.toFloat() ?: 0f 23 aspectRatio = ratio?.toFloat() ?: 0f
24 requestLayout()
25 } 25 }
26 26
27 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 27 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
28 super.onMeasure(widthMeasureSpec, heightMeasureSpec) 28 val displayWidth: Float = MeasureSpec.getSize(widthMeasureSpec).toFloat()
29 val width = MeasureSpec.getSize(widthMeasureSpec) 29 val displayHeight: Float = MeasureSpec.getSize(heightMeasureSpec).toFloat()
30 val height = MeasureSpec.getSize(heightMeasureSpec)
31 if (aspectRatio != 0f) { 30 if (aspectRatio != 0f) {
32 val newWidth: Int 31 val displayAspect = displayWidth / displayHeight
33 val newHeight: Int 32 if (displayAspect < aspectRatio) {
34 if (height * aspectRatio < width) { 33 // Max out width
35 newWidth = (height * aspectRatio).roundToInt() 34 val halfHeight = displayHeight / 2
36 newHeight = height 35 val surfaceHeight = displayWidth / aspectRatio
36 val newTop: Float = halfHeight - (surfaceHeight / 2)
37 val newBottom: Float = halfHeight + (surfaceHeight / 2)
38 super.onMeasure(
39 widthMeasureSpec,
40 MeasureSpec.makeMeasureSpec(
41 newBottom.toInt() - newTop.toInt(),
42 MeasureSpec.EXACTLY
43 )
44 )
45 return
37 } else { 46 } else {
38 newWidth = width 47 // Max out height
39 newHeight = (width / aspectRatio).roundToInt() 48 val halfWidth = displayWidth / 2
49 val surfaceWidth = displayHeight * aspectRatio
50 val newLeft: Float = halfWidth - (surfaceWidth / 2)
51 val newRight: Float = halfWidth + (surfaceWidth / 2)
52 super.onMeasure(
53 MeasureSpec.makeMeasureSpec(
54 newRight.toInt() - newLeft.toInt(),
55 MeasureSpec.EXACTLY
56 ),
57 heightMeasureSpec
58 )
59 return
40 } 60 }
41 val left = (width - newWidth) / 2
42 val top = (height - newHeight) / 2
43 setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
44 } else {
45 setLeftTopRightBottom(0, 0, width, height)
46 } 61 }
62 super.onMeasure(widthMeasureSpec, heightMeasureSpec)
47 } 63 }
48} 64}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e2ed08e9f..e15d1480b 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -14,6 +14,8 @@ add_library(yuzu-android SHARED
14 id_cache.cpp 14 id_cache.cpp
15 id_cache.h 15 id_cache.h
16 native.cpp 16 native.cpp
17 native_config.cpp
18 uisettings.cpp
17) 19)
18 20
19set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) 21set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 5e1f10f99..81120ab0f 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -11,22 +11,25 @@
11#include "common/fs/path_util.h" 11#include "common/fs/path_util.h"
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "common/settings.h" 13#include "common/settings.h"
14#include "common/settings_enums.h"
14#include "core/hle/service/acc/profile_manager.h" 15#include "core/hle/service/acc/profile_manager.h"
15#include "input_common/main.h" 16#include "input_common/main.h"
16#include "jni/config.h" 17#include "jni/config.h"
17#include "jni/default_ini.h" 18#include "jni/default_ini.h"
19#include "uisettings.h"
18 20
19namespace FS = Common::FS; 21namespace FS = Common::FS;
20 22
21Config::Config(std::optional<std::filesystem::path> config_path) 23Config::Config(const std::string& config_name, ConfigType config_type)
22 : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")}, 24 : type(config_type), global{config_type == ConfigType::GlobalConfig} {
23 config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} { 25 Initialize(config_name);
24 Reload();
25} 26}
26 27
27Config::~Config() = default; 28Config::~Config() = default;
28 29
29bool Config::LoadINI(const std::string& default_contents, bool retry) { 30bool Config::LoadINI(const std::string& default_contents, bool retry) {
31 void(FS::CreateParentDir(config_loc));
32 config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
30 const auto config_loc_str = FS::PathToUTF8String(config_loc); 33 const auto config_loc_str = FS::PathToUTF8String(config_loc);
31 if (config->ParseError() < 0) { 34 if (config->ParseError() < 0) {
32 if (retry) { 35 if (retry) {
@@ -144,7 +147,9 @@ void Config::ReadValues() {
144 Service::Account::MAX_USERS - 1); 147 Service::Account::MAX_USERS - 1);
145 148
146 // Disable docked mode by default on Android 149 // Disable docked mode by default on Android
147 Settings::values.use_docked_mode = config->GetBoolean("System", "use_docked_mode", false); 150 Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false)
151 ? Settings::ConsoleMode::Docked
152 : Settings::ConsoleMode::Handheld);
148 153
149 const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false); 154 const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
150 if (rng_seed_enabled) { 155 if (rng_seed_enabled) {
@@ -277,7 +282,7 @@ void Config::ReadValues() {
277 std::stringstream ss(title_list); 282 std::stringstream ss(title_list);
278 std::string line; 283 std::string line;
279 while (std::getline(ss, line, '|')) { 284 while (std::getline(ss, line, '|')) {
280 const auto title_id = std::stoul(line, nullptr, 16); 285 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
281 const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); 286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
282 287
283 std::stringstream inner_ss(disabled_list); 288 std::stringstream inner_ss(disabled_list);
@@ -298,9 +303,28 @@ void Config::ReadValues() {
298 303
299 // Network 304 // Network
300 ReadSetting("Network", Settings::values.network_interface); 305 ReadSetting("Network", Settings::values.network_interface);
306
307 // Android
308 ReadSetting("Android", AndroidSettings::values.picture_in_picture);
309 ReadSetting("Android", AndroidSettings::values.screen_layout);
301} 310}
302 311
303void Config::Reload() { 312void Config::Initialize(const std::string& config_name) {
313 const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
314 const auto config_file = fmt::format("{}.ini", config_name);
315
316 switch (type) {
317 case ConfigType::GlobalConfig:
318 config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
319 break;
320 case ConfigType::PerGameConfig:
321 config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
322 break;
323 case ConfigType::InputProfile:
324 config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
325 LoadINI(DefaultINI::android_config_file);
326 return;
327 }
304 LoadINI(DefaultINI::android_config_file); 328 LoadINI(DefaultINI::android_config_file);
305 ReadValues(); 329 ReadValues();
306} 330}
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h
index 0d7d6e94d..e1e8f47ed 100644
--- a/src/android/app/src/main/jni/config.h
+++ b/src/android/app/src/main/jni/config.h
@@ -13,25 +13,35 @@
13class INIReader; 13class INIReader;
14 14
15class Config { 15class Config {
16 std::filesystem::path config_loc;
17 std::unique_ptr<INIReader> config;
18
19 bool LoadINI(const std::string& default_contents = "", bool retry = true); 16 bool LoadINI(const std::string& default_contents = "", bool retry = true);
20 void ReadValues();
21 17
22public: 18public:
23 explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt); 19 enum class ConfigType {
20 GlobalConfig,
21 PerGameConfig,
22 InputProfile,
23 };
24
25 explicit Config(const std::string& config_name = "config",
26 ConfigType config_type = ConfigType::GlobalConfig);
24 ~Config(); 27 ~Config();
25 28
26 void Reload(); 29 void Initialize(const std::string& config_name);
27 30
28private: 31private:
29 /** 32 /**
30 * Applies a value read from the sdl2_config to a Setting. 33 * Applies a value read from the config to a Setting.
31 * 34 *
32 * @param group The name of the INI group 35 * @param group The name of the INI group
33 * @param setting The yuzu setting to modify 36 * @param setting The yuzu setting to modify
34 */ 37 */
35 template <typename Type, bool ranged> 38 template <typename Type, bool ranged>
36 void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting); 39 void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
40
41 void ReadValues();
42
43 const ConfigType type;
44 std::unique_ptr<INIReader> config;
45 std::string config_loc;
46 const bool global;
37}; 47};
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index 9cbbf23a3..960abf95a 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -15,6 +15,8 @@ static jclass s_disk_cache_progress_class;
15static jclass s_load_callback_stage_class; 15static jclass s_load_callback_stage_class;
16static jmethodID s_exit_emulation_activity; 16static jmethodID s_exit_emulation_activity;
17static jmethodID s_disk_cache_load_progress; 17static jmethodID s_disk_cache_load_progress;
18static jmethodID s_on_emulation_started;
19static jmethodID s_on_emulation_stopped;
18 20
19static constexpr jint JNI_VERSION = JNI_VERSION_1_6; 21static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
20 22
@@ -59,6 +61,14 @@ jmethodID GetDiskCacheLoadProgress() {
59 return s_disk_cache_load_progress; 61 return s_disk_cache_load_progress;
60} 62}
61 63
64jmethodID GetOnEmulationStarted() {
65 return s_on_emulation_started;
66}
67
68jmethodID GetOnEmulationStopped() {
69 return s_on_emulation_stopped;
70}
71
62} // namespace IDCache 72} // namespace IDCache
63 73
64#ifdef __cplusplus 74#ifdef __cplusplus
@@ -85,6 +95,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
85 env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); 95 env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
86 s_disk_cache_load_progress = 96 s_disk_cache_load_progress =
87 env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); 97 env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
98 s_on_emulation_started =
99 env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
100 s_on_emulation_stopped =
101 env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
88 102
89 // Initialize Android Storage 103 // Initialize Android Storage
90 Common::FS::Android::RegisterCallbacks(env, s_native_library_class); 104 Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
index be535fe1e..b76158928 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -15,5 +15,7 @@ jclass GetDiskCacheProgressClass();
15jclass GetDiskCacheLoadCallbackStageClass(); 15jclass GetDiskCacheLoadCallbackStageClass();
16jmethodID GetExitEmulationActivity(); 16jmethodID GetExitEmulationActivity();
17jmethodID GetDiskCacheLoadProgress(); 17jmethodID GetDiskCacheLoadProgress();
18jmethodID GetOnEmulationStarted();
19jmethodID GetOnEmulationStopped();
18 20
19} // namespace IDCache 21} // namespace IDCache
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index c23b2f19e..f31fe054b 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -30,6 +30,7 @@
30#include "core/cpu_manager.h" 30#include "core/cpu_manager.h"
31#include "core/crypto/key_manager.h" 31#include "core/crypto/key_manager.h"
32#include "core/file_sys/card_image.h" 32#include "core/file_sys/card_image.h"
33#include "core/file_sys/content_archive.h"
33#include "core/file_sys/registered_cache.h" 34#include "core/file_sys/registered_cache.h"
34#include "core/file_sys/submission_package.h" 35#include "core/file_sys/submission_package.h"
35#include "core/file_sys/vfs.h" 36#include "core/file_sys/vfs.h"
@@ -202,12 +203,10 @@ public:
202 } 203 }
203 204
204 bool IsRunning() const { 205 bool IsRunning() const {
205 std::scoped_lock lock(m_mutex);
206 return m_is_running; 206 return m_is_running;
207 } 207 }
208 208
209 bool IsPaused() const { 209 bool IsPaused() const {
210 std::scoped_lock lock(m_mutex);
211 return m_is_running && m_is_paused; 210 return m_is_running && m_is_paused;
212 } 211 }
213 212
@@ -224,17 +223,51 @@ public:
224 m_system.Renderer().NotifySurfaceChanged(); 223 m_system.Renderer().NotifySurfaceChanged();
225 } 224 }
226 225
226 void ConfigureFilesystemProvider(const std::string& filepath) {
227 const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
228 if (!file) {
229 return;
230 }
231
232 auto loader = Loader::GetLoader(m_system, file);
233 if (!loader) {
234 return;
235 }
236
237 const auto file_type = loader->GetFileType();
238 if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
239 return;
240 }
241
242 u64 program_id = 0;
243 const auto res2 = loader->ReadProgramId(program_id);
244 if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
245 m_manual_provider->AddEntry(FileSys::TitleType::Application,
246 FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
247 program_id, file);
248 } else if (res2 == Loader::ResultStatus::Success &&
249 (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
250 const auto nsp = file_type == Loader::FileType::NSP
251 ? std::make_shared<FileSys::NSP>(file)
252 : FileSys::XCI{file}.GetSecurePartitionNSP();
253 for (const auto& title : nsp->GetNCAs()) {
254 for (const auto& entry : title.second) {
255 m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
256 entry.second->GetBaseFile());
257 }
258 }
259 }
260 }
261
227 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { 262 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
228 std::scoped_lock lock(m_mutex); 263 std::scoped_lock lock(m_mutex);
229 264
230 // Loads the configuration.
231 Config{};
232
233 // Create the render window. 265 // Create the render window.
234 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, 266 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
235 m_vulkan_library); 267 m_vulkan_library);
236 268
237 m_system.SetFilesystem(m_vfs); 269 m_system.SetFilesystem(m_vfs);
270 m_system.GetUserChannel().clear();
238 271
239 // Initialize system. 272 // Initialize system.
240 jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); 273 jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
@@ -254,8 +287,14 @@ public:
254 std::move(android_keyboard), // Software Keyboard 287 std::move(android_keyboard), // Software Keyboard
255 nullptr, // Web Browser 288 nullptr, // Web Browser
256 }); 289 });
290
291 // Initialize filesystem.
292 m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
257 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); 293 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
294 m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
295 m_manual_provider.get());
258 m_system.GetFileSystemController().CreateFactories(*m_vfs); 296 m_system.GetFileSystemController().CreateFactories(*m_vfs);
297 ConfigureFilesystemProvider(filepath);
259 298
260 // Initialize account manager 299 // Initialize account manager
261 m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); 300 m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
@@ -288,6 +327,9 @@ public:
288 m_system.ShutdownMainProcess(); 327 m_system.ShutdownMainProcess();
289 m_detached_tasks.WaitForAllTasks(); 328 m_detached_tasks.WaitForAllTasks();
290 m_load_result = Core::SystemResultStatus::ErrorNotInitialized; 329 m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
330 m_window.reset();
331 OnEmulationStopped(Core::SystemResultStatus::Success);
332 return;
291 } 333 }
292 334
293 // Tear down the render window. 335 // Tear down the render window.
@@ -333,6 +375,8 @@ public:
333 m_system.InitializeDebugger(); 375 m_system.InitializeDebugger();
334 } 376 }
335 377
378 OnEmulationStarted();
379
336 while (true) { 380 while (true) {
337 { 381 {
338 [[maybe_unused]] std::unique_lock lock(m_mutex); 382 [[maybe_unused]] std::unique_lock lock(m_mutex);
@@ -377,7 +421,7 @@ public:
377 return false; 421 return false;
378 } 422 }
379 423
380 return !Settings::values.use_docked_mode.GetValue(); 424 return !Settings::IsDockedMode();
381 } 425 }
382 426
383 void SetDeviceType([[maybe_unused]] int index, int type) { 427 void SetDeviceType([[maybe_unused]] int index, int type) {
@@ -468,6 +512,18 @@ private:
468 static_cast<jint>(progress), static_cast<jint>(max)); 512 static_cast<jint>(progress), static_cast<jint>(max));
469 } 513 }
470 514
515 static void OnEmulationStarted() {
516 JNIEnv* env = IDCache::GetEnvForThread();
517 env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
518 IDCache::GetOnEmulationStarted());
519 }
520
521 static void OnEmulationStopped(Core::SystemResultStatus result) {
522 JNIEnv* env = IDCache::GetEnvForThread();
523 env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
524 IDCache::GetOnEmulationStopped(), static_cast<jint>(result));
525 }
526
471private: 527private:
472 static EmulationSession s_instance; 528 static EmulationSession s_instance;
473 529
@@ -485,10 +541,11 @@ private:
485 Core::PerfStatsResults m_perf_stats{}; 541 Core::PerfStatsResults m_perf_stats{};
486 std::shared_ptr<FileSys::VfsFilesystem> m_vfs; 542 std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
487 Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; 543 Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
488 bool m_is_running{}; 544 std::atomic<bool> m_is_running = false;
489 bool m_is_paused{}; 545 std::atomic<bool> m_is_paused = false;
490 SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; 546 SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
491 std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; 547 std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
548 std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
492 549
493 // GPU driver parameters 550 // GPU driver parameters
494 std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; 551 std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
@@ -613,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
613 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); 670 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
614} 671}
615 672
616void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
617 Settings::values.audio_muted = true;
618}
619
620void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
621 Settings::values.audio_muted = false;
622}
623
624jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
625 return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
626}
627
628jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) { 673jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
629 return EmulationSession::GetInstance().IsHandheldOnly(); 674 return EmulationSession::GetInstance().IsHandheldOnly();
630} 675}
@@ -780,34 +825,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
780 Config{}; 825 Config{};
781} 826}
782 827
783jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
784 jstring j_game_id, jstring j_section,
785 jstring j_key) {
786 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
787 std::string_view section = env->GetStringUTFChars(j_section, 0);
788 std::string_view key = env->GetStringUTFChars(j_key, 0);
789
790 env->ReleaseStringUTFChars(j_game_id, game_id.data());
791 env->ReleaseStringUTFChars(j_section, section.data());
792 env->ReleaseStringUTFChars(j_key, key.data());
793
794 return env->NewStringUTF("");
795}
796
797void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
798 jstring j_game_id, jstring j_section,
799 jstring j_key, jstring j_value) {
800 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
801 std::string_view section = env->GetStringUTFChars(j_section, 0);
802 std::string_view key = env->GetStringUTFChars(j_key, 0);
803 std::string_view value = env->GetStringUTFChars(j_value, 0);
804
805 env->ReleaseStringUTFChars(j_game_id, game_id.data());
806 env->ReleaseStringUTFChars(j_section, section.data());
807 env->ReleaseStringUTFChars(j_key, key.data());
808 env->ReleaseStringUTFChars(j_value, value.data());
809}
810
811void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz, 828void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
812 jstring j_game_id) { 829 jstring j_game_id) {
813 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); 830 std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
new file mode 100644
index 000000000..8a704960c
--- /dev/null
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -0,0 +1,237 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <string>
5
6#include <jni.h>
7
8#include "common/logging/log.h"
9#include "common/settings.h"
10#include "jni/android_common/android_common.h"
11#include "jni/config.h"
12#include "uisettings.h"
13
14template <typename T>
15Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
16 auto key = GetJString(env, jkey);
17 auto basicSetting = Settings::values.linkage.by_key[key];
18 auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
19 if (basicSetting != 0) {
20 return static_cast<Settings::Setting<T>*>(basicSetting);
21 }
22 if (basicAndroidSetting != 0) {
23 return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
24 }
25 LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
26 return nullptr;
27}
28
29extern "C" {
30
31jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
32 jstring jkey, jboolean getDefault) {
33 auto setting = getSetting<bool>(env, jkey);
34 if (setting == nullptr) {
35 return false;
36 }
37 setting->SetGlobal(true);
38
39 if (static_cast<bool>(getDefault)) {
40 return setting->GetDefault();
41 }
42
43 return setting->GetValue();
44}
45
46void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey,
47 jboolean value) {
48 auto setting = getSetting<bool>(env, jkey);
49 if (setting == nullptr) {
50 return;
51 }
52 setting->SetGlobal(true);
53 setting->SetValue(static_cast<bool>(value));
54}
55
56jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey,
57 jboolean getDefault) {
58 auto setting = getSetting<u8>(env, jkey);
59 if (setting == nullptr) {
60 return -1;
61 }
62 setting->SetGlobal(true);
63
64 if (static_cast<bool>(getDefault)) {
65 return setting->GetDefault();
66 }
67
68 return setting->GetValue();
69}
70
71void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey,
72 jbyte value) {
73 auto setting = getSetting<u8>(env, jkey);
74 if (setting == nullptr) {
75 return;
76 }
77 setting->SetGlobal(true);
78 setting->SetValue(value);
79}
80
81jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey,
82 jboolean getDefault) {
83 auto setting = getSetting<u16>(env, jkey);
84 if (setting == nullptr) {
85 return -1;
86 }
87 setting->SetGlobal(true);
88
89 if (static_cast<bool>(getDefault)) {
90 return setting->GetDefault();
91 }
92
93 return setting->GetValue();
94}
95
96void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey,
97 jshort value) {
98 auto setting = getSetting<u16>(env, jkey);
99 if (setting == nullptr) {
100 return;
101 }
102 setting->SetGlobal(true);
103 setting->SetValue(value);
104}
105
106jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
107 jboolean getDefault) {
108 auto setting = getSetting<int>(env, jkey);
109 if (setting == nullptr) {
110 return -1;
111 }
112 setting->SetGlobal(true);
113
114 if (static_cast<bool>(getDefault)) {
115 return setting->GetDefault();
116 }
117
118 return setting->GetValue();
119}
120
121void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey,
122 jint value) {
123 auto setting = getSetting<int>(env, jkey);
124 if (setting == nullptr) {
125 return;
126 }
127 setting->SetGlobal(true);
128 setting->SetValue(value);
129}
130
131jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey,
132 jboolean getDefault) {
133 auto setting = getSetting<float>(env, jkey);
134 if (setting == nullptr) {
135 return -1;
136 }
137 setting->SetGlobal(true);
138
139 if (static_cast<bool>(getDefault)) {
140 return setting->GetDefault();
141 }
142
143 return setting->GetValue();
144}
145
146void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey,
147 jfloat value) {
148 auto setting = getSetting<float>(env, jkey);
149 if (setting == nullptr) {
150 return;
151 }
152 setting->SetGlobal(true);
153 setting->SetValue(value);
154}
155
156jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey,
157 jboolean getDefault) {
158 auto setting = getSetting<long>(env, jkey);
159 if (setting == nullptr) {
160 return -1;
161 }
162 setting->SetGlobal(true);
163
164 if (static_cast<bool>(getDefault)) {
165 return setting->GetDefault();
166 }
167
168 return setting->GetValue();
169}
170
171void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey,
172 jlong value) {
173 auto setting = getSetting<long>(env, jkey);
174 if (setting == nullptr) {
175 return;
176 }
177 setting->SetGlobal(true);
178 setting->SetValue(value);
179}
180
181jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey,
182 jboolean getDefault) {
183 auto setting = getSetting<std::string>(env, jkey);
184 if (setting == nullptr) {
185 return ToJString(env, "");
186 }
187 setting->SetGlobal(true);
188
189 if (static_cast<bool>(getDefault)) {
190 return ToJString(env, setting->GetDefault());
191 }
192
193 return ToJString(env, setting->GetValue());
194}
195
196void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
197 jstring value) {
198 auto setting = getSetting<std::string>(env, jkey);
199 if (setting == nullptr) {
200 return;
201 }
202
203 setting->SetGlobal(true);
204 setting->SetValue(GetJString(env, value));
205}
206
207jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
208 jstring jkey) {
209 auto key = GetJString(env, jkey);
210 auto setting = Settings::values.linkage.by_key[key];
211 if (setting != 0) {
212 return setting->RuntimeModfiable();
213 }
214 LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
215 return true;
216}
217
218jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
219 jint jcategory) {
220 auto category = static_cast<Settings::Category>(jcategory);
221 return ToJString(env, Settings::TranslateCategory(category));
222}
223
224jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj,
225 jstring jkey) {
226 auto setting = getSetting<std::string>(env, jkey);
227 if (setting == nullptr) {
228 return ToJString(env, "");
229 }
230 if (setting->PairedSetting() == nullptr) {
231 return ToJString(env, "");
232 }
233
234 return ToJString(env, setting->PairedSetting()->GetLabel());
235}
236
237} // extern "C"
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/uisettings.cpp
new file mode 100644
index 000000000..f2f0bad50
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.cpp
@@ -0,0 +1,10 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "uisettings.h"
5
6namespace AndroidSettings {
7
8Values values;
9
10} // namespace AndroidSettings
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h
new file mode 100644
index 000000000..494654af7
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.h
@@ -0,0 +1,29 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <common/settings_common.h>
7#include "common/common_types.h"
8#include "common/settings_setting.h"
9
10namespace AndroidSettings {
11
12struct Values {
13 Settings::Linkage linkage;
14
15 // Android
16 Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
17 Settings::Category::Android};
18 Settings::Setting<s32> screen_layout{linkage,
19 5,
20 "screen_layout",
21 Settings::Category::Android,
22 Settings::Specialization::Default,
23 true,
24 true};
25};
26
27extern Values values;
28
29} // namespace AndroidSettings
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
deleted file mode 100644
index 9f49c133a..000000000
--- a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
+++ /dev/null
@@ -1,16 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="125"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10 <translate
11 android:duration="125"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="0"
14 android:toXDelta="-75" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
deleted file mode 100644
index 82fd719db..000000000
--- a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
+++ /dev/null
@@ -1,16 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="0"
8 android:toAlpha="1" />
9
10 <translate
11 android:duration="@android:integer/config_shortAnimTime"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="-200"
14 android:toXDelta="0" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
deleted file mode 100644
index 5892128f1..000000000
--- a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
+++ /dev/null
@@ -1,16 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="125"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10 <translate
11 android:duration="125"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="0"
14 android:toXDelta="75" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
deleted file mode 100644
index 98e0cf8bd..000000000
--- a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
+++ /dev/null
@@ -1,16 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="0"
8 android:toAlpha="1" />
9
10 <translate
11 android:duration="@android:integer/config_shortAnimTime"
12 android:interpolator="@android:anim/decelerate_interpolator"
13 android:fromXDelta="200"
14 android:toXDelta="0" />
15
16</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
deleted file mode 100644
index 77a40a4d1..000000000
--- a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
+++ /dev/null
@@ -1,10 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <alpha
5 android:duration="@android:integer/config_shortAnimTime"
6 android:interpolator="@android:anim/decelerate_interpolator"
7 android:fromAlpha="1"
8 android:toAlpha="0" />
9
10</set>
diff --git a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
deleted file mode 100644
index 4612aee13..000000000
--- a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
+++ /dev/null
@@ -1,20 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <objectAnimator
5 android:propertyName="translationX"
6 android:valueType="floatType"
7 android:valueFrom="-1280dp"
8 android:valueTo="0"
9 android:interpolator="@android:interpolator/decelerate_quad"
10 android:duration="300"/>
11
12 <objectAnimator
13 android:propertyName="alpha"
14 android:valueType="floatType"
15 android:valueFrom="0"
16 android:valueTo="1"
17 android:interpolator="@android:interpolator/accelerate_quad"
18 android:duration="300"/>
19
20</set> \ No newline at end of file
diff --git a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
deleted file mode 100644
index c00478946..000000000
--- a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
+++ /dev/null
@@ -1,21 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<set xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <!-- This animation is used ONLY when a submenu is replaced. -->
5 <objectAnimator
6 android:propertyName="translationX"
7 android:valueType="floatType"
8 android:valueFrom="0"
9 android:valueTo="-1280dp"
10 android:interpolator="@android:interpolator/decelerate_quad"
11 android:duration="200"/>
12
13 <objectAnimator
14 android:propertyName="alpha"
15 android:valueType="floatType"
16 android:valueFrom="1"
17 android:valueTo="0"
18 android:interpolator="@android:interpolator/decelerate_quad"
19 android:duration="200"/>
20
21</set> \ No newline at end of file
diff --git a/src/android/app/src/main/res/drawable/shortcut.xml b/src/android/app/src/main/res/drawable/shortcut.xml
new file mode 100644
index 000000000..c749e5d72
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/shortcut.xml
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <item>
5 <color android:color="@android:color/white" />
6 </item>
7 <item android:id="@+id/shortcut_foreground">
8 <bitmap android:src="@drawable/default_icon" />
9 </item>
10
11</layer-list>
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
index cbe631d88..406df9eab 100644
--- a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
+++ b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
@@ -1,5 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout 2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/setup_root" 5 android:id="@+id/setup_root"
@@ -8,33 +8,39 @@
8 8
9 <androidx.viewpager2.widget.ViewPager2 9 <androidx.viewpager2.widget.ViewPager2
10 android:id="@+id/viewPager2" 10 android:id="@+id/viewPager2"
11 android:layout_width="0dp" 11 android:layout_width="match_parent"
12 android:layout_height="0dp" 12 android:layout_height="match_parent"
13 app:layout_constraintBottom_toBottomOf="parent" 13 android:layout_alignParentTop="true"
14 app:layout_constraintEnd_toEndOf="parent" 14 android:layout_alignParentBottom="true"
15 app:layout_constraintStart_toStartOf="parent" 15 android:clipToPadding="false" />
16 app:layout_constraintTop_toTopOf="parent" />
17 16
18 <com.google.android.material.button.MaterialButton 17 <androidx.constraintlayout.widget.ConstraintLayout
19 style="@style/Widget.Material3.Button.TextButton" 18 android:id="@+id/constraint_buttons"
20 android:id="@+id/button_next" 19 android:layout_width="match_parent"
21 android:layout_width="wrap_content"
22 android:layout_height="wrap_content" 20 android:layout_height="wrap_content"
23 android:layout_margin="16dp" 21 android:layout_alignParentBottom="true"
24 android:text="@string/next" 22 android:layout_margin="8dp">
25 android:visibility="invisible"
26 app:layout_constraintBottom_toBottomOf="parent"
27 app:layout_constraintEnd_toEndOf="parent" />
28 23
29 <com.google.android.material.button.MaterialButton 24 <com.google.android.material.button.MaterialButton
30 android:id="@+id/button_back" 25 android:id="@+id/button_next"
31 style="@style/Widget.Material3.Button.TextButton" 26 style="@style/Widget.Material3.Button.TextButton"
32 android:layout_width="wrap_content" 27 android:layout_width="wrap_content"
33 android:layout_height="wrap_content" 28 android:layout_height="wrap_content"
34 android:layout_margin="16dp" 29 android:text="@string/next"
35 android:text="@string/back" 30 android:visibility="invisible"
36 android:visibility="invisible" 31 app:layout_constraintBottom_toBottomOf="parent"
37 app:layout_constraintBottom_toBottomOf="parent" 32 app:layout_constraintEnd_toEndOf="parent" />
38 app:layout_constraintStart_toStartOf="parent" /> 33
34 <com.google.android.material.button.MaterialButton
35 android:id="@+id/button_back"
36 style="@style/Widget.Material3.Button.TextButton"
37 android:layout_width="wrap_content"
38 android:layout_height="wrap_content"
39 android:text="@string/back"
40 android:visibility="invisible"
41 app:layout_constraintBottom_toBottomOf="parent"
42 app:layout_constraintStart_toStartOf="parent" />
43
44 </androidx.constraintlayout.widget.ConstraintLayout>
39 45
40</androidx.constraintlayout.widget.ConstraintLayout> 46</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout-w600dp/page_setup.xml b/src/android/app/src/main/res/layout-w600dp/page_setup.xml
index e1c26b2f8..9e0ab8ecb 100644
--- a/src/android/app/src/main/res/layout-w600dp/page_setup.xml
+++ b/src/android/app/src/main/res/layout-w600dp/page_setup.xml
@@ -21,45 +21,76 @@
21 21
22 </LinearLayout> 22 </LinearLayout>
23 23
24 <LinearLayout 24 <androidx.constraintlayout.widget.ConstraintLayout
25 android:layout_width="match_parent" 25 android:layout_width="match_parent"
26 android:layout_height="match_parent" 26 android:layout_height="match_parent"
27 android:layout_weight="1" 27 android:layout_weight="1">
28 android:orientation="vertical"
29 android:gravity="center">
30 28
31 <com.google.android.material.textview.MaterialTextView 29 <com.google.android.material.textview.MaterialTextView
32 style="@style/TextAppearance.Material3.DisplaySmall"
33 android:id="@+id/text_title" 30 android:id="@+id/text_title"
34 android:layout_width="match_parent" 31 style="@style/TextAppearance.Material3.DisplaySmall"
35 android:layout_height="wrap_content" 32 android:layout_width="0dp"
36 android:textAlignment="center" 33 android:layout_height="0dp"
34 android:gravity="center"
37 android:textColor="?attr/colorOnSurface" 35 android:textColor="?attr/colorOnSurface"
38 android:textStyle="bold" 36 android:textStyle="bold"
37 app:layout_constraintBottom_toTopOf="@+id/text_description"
38 app:layout_constraintEnd_toEndOf="parent"
39 app:layout_constraintStart_toStartOf="parent"
40 app:layout_constraintTop_toTopOf="parent"
41 app:layout_constraintVertical_weight="2"
39 tools:text="@string/welcome" /> 42 tools:text="@string/welcome" />
40 43
41 <com.google.android.material.textview.MaterialTextView 44 <com.google.android.material.textview.MaterialTextView
42 style="@style/TextAppearance.Material3.TitleLarge"
43 android:id="@+id/text_description" 45 android:id="@+id/text_description"
44 android:layout_width="match_parent" 46 style="@style/TextAppearance.Material3.TitleLarge"
45 android:layout_height="wrap_content" 47 android:layout_width="0dp"
46 android:layout_marginTop="16dp" 48 android:layout_height="0dp"
47 android:paddingHorizontal="32dp" 49 android:gravity="center"
48 android:textAlignment="center" 50 android:textSize="20sp"
49 android:textSize="26sp" 51 android:paddingHorizontal="16dp"
50 app:lineHeight="40sp" 52 app:layout_constraintBottom_toTopOf="@+id/button_action"
53 app:layout_constraintEnd_toEndOf="parent"
54 app:layout_constraintStart_toStartOf="parent"
55 app:layout_constraintTop_toBottomOf="@+id/text_title"
56 app:layout_constraintVertical_weight="2"
57 app:lineHeight="30sp"
51 tools:text="@string/welcome_description" /> 58 tools:text="@string/welcome_description" />
52 59
60 <com.google.android.material.textview.MaterialTextView
61 android:id="@+id/text_confirmation"
62 style="@style/TextAppearance.Material3.TitleLarge"
63 android:layout_width="0dp"
64 android:layout_height="0dp"
65 android:paddingHorizontal="16dp"
66 android:paddingBottom="20dp"
67 android:gravity="center"
68 android:textSize="30sp"
69 android:visibility="invisible"
70 android:text="@string/step_complete"
71 android:textStyle="bold"
72 app:layout_constraintBottom_toBottomOf="parent"
73 app:layout_constraintEnd_toEndOf="parent"
74 app:layout_constraintStart_toStartOf="parent"
75 app:layout_constraintTop_toBottomOf="@+id/text_description"
76 app:layout_constraintVertical_weight="1"
77 app:lineHeight="30sp" />
78
53 <com.google.android.material.button.MaterialButton 79 <com.google.android.material.button.MaterialButton
54 android:id="@+id/button_action" 80 android:id="@+id/button_action"
55 android:layout_width="wrap_content" 81 android:layout_width="wrap_content"
56 android:layout_height="56dp" 82 android:layout_height="56dp"
57 android:layout_marginTop="32dp" 83 android:layout_marginTop="16dp"
84 android:layout_marginBottom="48dp"
58 android:textSize="20sp" 85 android:textSize="20sp"
59 app:iconSize="24sp"
60 app:iconGravity="end" 86 app:iconGravity="end"
87 app:iconSize="24sp"
88 app:layout_constraintBottom_toBottomOf="parent"
89 app:layout_constraintEnd_toEndOf="parent"
90 app:layout_constraintStart_toStartOf="parent"
91 app:layout_constraintTop_toBottomOf="@+id/text_description"
61 tools:text="Get started" /> 92 tools:text="Get started" />
62 93
63 </LinearLayout> 94 </androidx.constraintlayout.widget.ConstraintLayout>
64 95
65</LinearLayout> 96</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml
index 14ae83b04..8a026a30a 100644
--- a/src/android/app/src/main/res/layout/activity_settings.xml
+++ b/src/android/app/src/main/res/layout/activity_settings.xml
@@ -1,42 +1,24 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<androidx.coordinatorlayout.widget.CoordinatorLayout 2<androidx.constraintlayout.widget.ConstraintLayout
3 android:id="@+id/coordinator_main"
4 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:android="http://schemas.android.com/apk/res/android"
5 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:id="@+id/constraint_settings"
6 android:layout_width="match_parent" 7 android:layout_width="match_parent"
7 android:layout_height="match_parent" 8 android:layout_height="match_parent"
8 android:background="?attr/colorSurface"> 9 android:background="?attr/colorSurface">
9 10
10 <com.google.android.material.appbar.AppBarLayout 11 <androidx.fragment.app.FragmentContainerView
11 android:id="@+id/appbar_settings" 12 android:id="@+id/fragment_container"
12 android:layout_width="match_parent" 13 android:name="androidx.navigation.fragment.NavHostFragment"
13 android:layout_height="wrap_content" 14 android:layout_width="0dp"
14 android:fitsSystemWindows="true" 15 android:layout_height="0dp"
15 app:elevation="0dp"> 16 app:defaultNavHost="true"
16 17 app:layout_constraintBottom_toBottomOf="parent"
17 <com.google.android.material.appbar.CollapsingToolbarLayout 18 app:layout_constraintLeft_toLeftOf="parent"
18 style="?attr/collapsingToolbarLayoutMediumStyle" 19 app:layout_constraintRight_toRightOf="parent"
19 android:id="@+id/toolbar_settings_layout" 20 app:layout_constraintTop_toTopOf="parent"
20 android:layout_width="match_parent" 21 tools:layout="@layout/fragment_settings" />
21 android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
22 app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
23
24 <com.google.android.material.appbar.MaterialToolbar
25 android:id="@+id/toolbar_settings"
26 android:layout_width="match_parent"
27 android:layout_height="?attr/actionBarSize"
28 app:layout_collapseMode="pin" />
29
30 </com.google.android.material.appbar.CollapsingToolbarLayout>
31
32 </com.google.android.material.appbar.AppBarLayout>
33
34 <FrameLayout
35 android:id="@+id/frame_content"
36 android:layout_width="match_parent"
37 android:layout_height="match_parent"
38 android:layout_marginHorizontal="12dp"
39 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
40 22
41 <View 23 <View
42 android:id="@+id/navigation_bar_shade" 24 android:id="@+id/navigation_bar_shade"
@@ -45,6 +27,8 @@
45 android:background="@android:color/transparent" 27 android:background="@android:color/transparent"
46 android:clickable="false" 28 android:clickable="false"
47 android:focusable="false" 29 android:focusable="false"
48 android:layout_gravity="bottom|center_horizontal" /> 30 app:layout_constraintBottom_toBottomOf="parent"
31 app:layout_constraintEnd_toEndOf="parent"
32 app:layout_constraintStart_toStartOf="parent" />
49 33
50</androidx.coordinatorlayout.widget.CoordinatorLayout> 34</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml
index dc289db17..f9f1d89fb 100644
--- a/src/android/app/src/main/res/layout/card_home_option.xml
+++ b/src/android/app/src/main/res/layout/card_home_option.xml
@@ -53,6 +53,23 @@
53 android:layout_marginTop="5dp" 53 android:layout_marginTop="5dp"
54 tools:text="@string/install_prod_keys_description" /> 54 tools:text="@string/install_prod_keys_description" />
55 55
56 <com.google.android.material.textview.MaterialTextView
57 style="@style/TextAppearance.Material3.LabelMedium"
58 android:id="@+id/option_detail"
59 android:layout_width="match_parent"
60 android:layout_height="wrap_content"
61 android:textAlignment="viewStart"
62 android:textSize="14sp"
63 android:textStyle="bold"
64 android:singleLine="true"
65 android:marqueeRepeatLimit="marquee_forever"
66 android:ellipsize="none"
67 android:requiresFadingEdge="horizontal"
68 android:layout_marginTop="5dp"
69 android:visibility="gone"
70 tools:visibility="visible"
71 tools:text="/tree/primary:Games" />
72
56 </LinearLayout> 73 </LinearLayout>
57 74
58 </LinearLayout> 75 </LinearLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_slider.xml b/src/android/app/src/main/res/layout/dialog_slider.xml
index 8c84cb606..d1cb31739 100644
--- a/src/android/app/src/main/res/layout/dialog_slider.xml
+++ b/src/android/app/src/main/res/layout/dialog_slider.xml
@@ -5,23 +5,16 @@
5 android:layout_height="wrap_content" 5 android:layout_height="wrap_content"
6 android:orientation="vertical"> 6 android:orientation="vertical">
7 7
8 <TextView 8 <com.google.android.material.textview.MaterialTextView
9 android:id="@+id/text_value" 9 android:id="@+id/text_value"
10 style="@style/TextAppearance.Material3.LabelMedium"
10 android:layout_width="wrap_content" 11 android:layout_width="wrap_content"
11 android:layout_height="wrap_content" 12 android:layout_height="wrap_content"
12 android:layout_alignParentTop="true" 13 android:layout_alignParentTop="true"
13 android:layout_centerHorizontal="true" 14 android:layout_centerHorizontal="true"
14 android:layout_marginBottom="@dimen/spacing_medlarge" 15 android:layout_marginBottom="@dimen/spacing_medlarge"
15 android:layout_marginTop="@dimen/spacing_medlarge" 16 android:layout_marginTop="@dimen/spacing_medlarge"
16 tools:text="75" /> 17 tools:text="75%" />
17
18 <TextView
19 android:id="@+id/text_units"
20 android:layout_width="wrap_content"
21 android:layout_height="wrap_content"
22 android:layout_alignTop="@+id/text_value"
23 android:layout_toEndOf="@+id/text_value"
24 tools:text="%" />
25 18
26 <com.google.android.material.slider.Slider 19 <com.google.android.material.slider.Slider
27 android:id="@+id/slider" 20 android:id="@+id/slider"
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml
index e54a10e8f..da97d85c1 100644
--- a/src/android/app/src/main/res/layout/fragment_emulation.xml
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -26,6 +26,81 @@
26 android:focusable="false" 26 android:focusable="false"
27 android:focusableInTouchMode="false" /> 27 android:focusableInTouchMode="false" />
28 28
29 <com.google.android.material.card.MaterialCardView
30 android:id="@+id/loading_indicator"
31 style="?attr/materialCardViewOutlinedStyle"
32 android:layout_width="wrap_content"
33 android:layout_height="wrap_content"
34 android:layout_gravity="center"
35 android:focusable="false">
36
37 <androidx.constraintlayout.widget.ConstraintLayout
38 android:id="@+id/loading_layout"
39 android:layout_width="wrap_content"
40 android:layout_height="wrap_content"
41 android:gravity="center_horizontal">
42
43 <ImageView
44 android:id="@+id/loading_image"
45 android:layout_width="wrap_content"
46 android:layout_height="0dp"
47 android:adjustViewBounds="true"
48 app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
49 app:layout_constraintStart_toStartOf="parent"
50 app:layout_constraintTop_toTopOf="@+id/linearLayout"
51 tools:src="@drawable/default_icon" />
52
53 <LinearLayout
54 android:id="@+id/linearLayout"
55 android:layout_width="wrap_content"
56 android:layout_height="wrap_content"
57 android:orientation="vertical"
58 android:paddingHorizontal="24dp"
59 android:paddingVertical="36dp"
60 app:layout_constraintBottom_toBottomOf="parent"
61 app:layout_constraintEnd_toEndOf="parent"
62 app:layout_constraintStart_toEndOf="@id/loading_image"
63 app:layout_constraintTop_toTopOf="parent">
64
65 <com.google.android.material.textview.MaterialTextView
66 android:id="@+id/loading_title"
67 style="@style/TextAppearance.Material3.TitleMedium"
68 android:layout_width="match_parent"
69 android:layout_height="wrap_content"
70 android:ellipsize="marquee"
71 android:marqueeRepeatLimit="marquee_forever"
72 android:requiresFadingEdge="horizontal"
73 android:singleLine="true"
74 android:textAlignment="viewStart"
75 tools:text="@string/games" />
76
77 <com.google.android.material.textview.MaterialTextView
78 android:id="@+id/loading_text"
79 style="@style/TextAppearance.Material3.TitleSmall"
80 android:layout_width="match_parent"
81 android:layout_height="wrap_content"
82 android:layout_marginTop="4dp"
83 android:ellipsize="marquee"
84 android:marqueeRepeatLimit="marquee_forever"
85 android:requiresFadingEdge="horizontal"
86 android:singleLine="true"
87 android:text="@string/loading"
88 android:textAlignment="viewStart" />
89
90 <com.google.android.material.progressindicator.LinearProgressIndicator
91 android:id="@+id/loading_progress_indicator"
92 android:layout_width="192dp"
93 android:layout_height="wrap_content"
94 android:layout_marginTop="12dp"
95 android:indeterminate="true"
96 app:trackCornerRadius="8dp" />
97
98 </LinearLayout>
99
100 </androidx.constraintlayout.widget.ConstraintLayout>
101
102 </com.google.android.material.card.MaterialCardView>
103
29 </FrameLayout> 104 </FrameLayout>
30 105
31 <FrameLayout 106 <FrameLayout
@@ -41,11 +116,12 @@
41 android:layout_height="match_parent" 116 android:layout_height="match_parent"
42 android:layout_gravity="center" 117 android:layout_gravity="center"
43 android:focusable="true" 118 android:focusable="true"
44 android:focusableInTouchMode="true" /> 119 android:focusableInTouchMode="true"
120 android:visibility="invisible" />
45 121
46 <Button 122 <Button
47 style="@style/Widget.Material3.Button.ElevatedButton"
48 android:id="@+id/done_control_config" 123 android:id="@+id/done_control_config"
124 style="@style/Widget.Material3.Button.ElevatedButton"
49 android:layout_width="wrap_content" 125 android:layout_width="wrap_content"
50 android:layout_height="wrap_content" 126 android:layout_height="wrap_content"
51 android:layout_gravity="center" 127 android:layout_gravity="center"
@@ -81,6 +157,7 @@
81 android:layout_height="match_parent" 157 android:layout_height="match_parent"
82 android:layout_gravity="start|bottom" 158 android:layout_gravity="start|bottom"
83 app:headerLayout="@layout/header_in_game" 159 app:headerLayout="@layout/header_in_game"
84 app:menu="@menu/menu_in_game" /> 160 app:menu="@menu/menu_in_game"
161 tools:visibility="gone" />
85 162
86</androidx.drawerlayout.widget.DrawerLayout> 163</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_settings.xml b/src/android/app/src/main/res/layout/fragment_settings.xml
index 167720347..ebedbf1ec 100644
--- a/src/android/app/src/main/res/layout/fragment_settings.xml
+++ b/src/android/app/src/main/res/layout/fragment_settings.xml
@@ -1,14 +1,41 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<FrameLayout 2<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/coordinator_main"
4 android:layout_width="match_parent" 5 android:layout_width="match_parent"
5 android:layout_height="match_parent"> 6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface">
8
9 <com.google.android.material.appbar.AppBarLayout
10 android:id="@+id/appbar_settings"
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:fitsSystemWindows="true"
14 app:elevation="0dp">
15
16 <com.google.android.material.appbar.CollapsingToolbarLayout
17 android:id="@+id/toolbar_settings_layout"
18 style="?attr/collapsingToolbarLayoutMediumStyle"
19 android:layout_width="match_parent"
20 android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
21 app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
22
23 <com.google.android.material.appbar.MaterialToolbar
24 android:id="@+id/toolbar_settings"
25 android:layout_width="match_parent"
26 android:layout_height="?attr/actionBarSize"
27 app:layout_collapseMode="pin"
28 app:navigationIcon="@drawable/ic_back" />
29
30 </com.google.android.material.appbar.CollapsingToolbarLayout>
31
32 </com.google.android.material.appbar.AppBarLayout>
6 33
7 <androidx.recyclerview.widget.RecyclerView 34 <androidx.recyclerview.widget.RecyclerView
8 android:id="@+id/list_settings" 35 android:id="@+id/list_settings"
9 android:layout_width="match_parent" 36 android:layout_width="match_parent"
10 android:layout_height="match_parent" 37 android:layout_height="match_parent"
11 android:background="?attr/colorSurface" 38 android:clipToPadding="false"
12 android:clipToPadding="false" /> 39 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
13 40
14</FrameLayout> 41</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_settings_search.xml b/src/android/app/src/main/res/layout/fragment_settings_search.xml
new file mode 100644
index 000000000..c779ed2fc
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml
@@ -0,0 +1,120 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent">
7
8 <RelativeLayout
9 android:id="@+id/relativeLayout"
10 android:layout_width="0dp"
11 android:layout_height="0dp"
12 app:layout_constraintBottom_toBottomOf="parent"
13 app:layout_constraintEnd_toEndOf="parent"
14 app:layout_constraintStart_toStartOf="parent"
15 app:layout_constraintTop_toBottomOf="@+id/divider">
16
17 <LinearLayout
18 android:id="@+id/no_results_view"
19 android:layout_width="match_parent"
20 android:layout_height="match_parent"
21 android:gravity="center"
22 android:orientation="vertical">
23
24 <ImageView
25 android:id="@+id/icon_no_results"
26 android:layout_width="match_parent"
27 android:layout_height="80dp"
28 android:src="@drawable/ic_search" />
29
30 <com.google.android.material.textview.MaterialTextView
31 android:id="@+id/notice_text"
32 style="@style/TextAppearance.Material3.TitleLarge"
33 android:layout_width="match_parent"
34 android:layout_height="wrap_content"
35 android:gravity="center"
36 android:paddingTop="8dp"
37 android:text="@string/search_settings"
38 tools:visibility="visible" />
39
40 </LinearLayout>
41
42 <androidx.recyclerview.widget.RecyclerView
43 android:id="@+id/settings_list"
44 android:layout_width="match_parent"
45 android:layout_height="match_parent"
46 android:clipToPadding="false" />
47
48 </RelativeLayout>
49
50 <FrameLayout
51 android:id="@+id/frame_search"
52 android:layout_width="match_parent"
53 android:layout_height="wrap_content"
54 android:clipToPadding="false"
55 app:layout_constraintEnd_toEndOf="parent"
56 app:layout_constraintStart_toStartOf="parent"
57 app:layout_constraintTop_toTopOf="parent">
58
59 <com.google.android.material.card.MaterialCardView
60 android:id="@+id/search_background"
61 style="?attr/materialCardViewFilledStyle"
62 android:layout_width="match_parent"
63 android:layout_height="56dp"
64 app:cardCornerRadius="28dp">
65
66 <LinearLayout
67 android:id="@+id/search_container"
68 android:layout_width="match_parent"
69 android:layout_height="match_parent"
70 android:layout_marginEnd="56dp"
71 android:orientation="horizontal">
72
73 <Button
74 android:id="@+id/back_button"
75 style="?attr/materialIconButtonFilledTonalStyle"
76 android:layout_width="wrap_content"
77 android:layout_height="wrap_content"
78 android:layout_gravity="center_vertical"
79 android:layout_marginStart="8dp"
80 app:backgroundTint="@android:color/transparent"
81 app:icon="@drawable/ic_back" />
82
83 <EditText
84 android:id="@+id/search_text"
85 android:layout_width="match_parent"
86 android:layout_height="match_parent"
87 android:background="@android:color/transparent"
88 android:hint="@string/search_settings"
89 android:imeOptions="flagNoFullscreen"
90 android:inputType="text"
91 android:maxLines="1" />
92
93 </LinearLayout>
94
95 <Button
96 android:id="@+id/clear_button"
97 style="?attr/materialIconButtonFilledTonalStyle"
98 android:layout_width="wrap_content"
99 android:layout_height="wrap_content"
100 android:layout_gravity="center_vertical|end"
101 android:layout_marginEnd="8dp"
102 android:visibility="invisible"
103 app:backgroundTint="@android:color/transparent"
104 app:icon="@drawable/ic_clear"
105 tools:visibility="visible" />
106
107 </com.google.android.material.card.MaterialCardView>
108
109 </FrameLayout>
110
111 <com.google.android.material.divider.MaterialDivider
112 android:id="@+id/divider"
113 android:layout_width="match_parent"
114 android:layout_height="wrap_content"
115 android:layout_marginTop="20dp"
116 app:layout_constraintEnd_toEndOf="parent"
117 app:layout_constraintStart_toStartOf="parent"
118 app:layout_constraintTop_toBottomOf="@+id/frame_search" />
119
120</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_setup.xml b/src/android/app/src/main/res/layout/fragment_setup.xml
index d7bafaea2..9499f6463 100644
--- a/src/android/app/src/main/res/layout/fragment_setup.xml
+++ b/src/android/app/src/main/res/layout/fragment_setup.xml
@@ -1,5 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout 2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/setup_root" 5 android:id="@+id/setup_root"
@@ -8,35 +8,39 @@
8 8
9 <androidx.viewpager2.widget.ViewPager2 9 <androidx.viewpager2.widget.ViewPager2
10 android:id="@+id/viewPager2" 10 android:id="@+id/viewPager2"
11 android:layout_width="0dp" 11 android:layout_width="match_parent"
12 android:layout_height="0dp"
13 android:clipToPadding="false"
14 android:layout_marginBottom="16dp"
15 app:layout_constraintBottom_toTopOf="@+id/button_next"
16 app:layout_constraintEnd_toEndOf="parent"
17 app:layout_constraintStart_toStartOf="parent"
18 app:layout_constraintTop_toTopOf="parent" />
19
20 <com.google.android.material.button.MaterialButton
21 style="@style/Widget.Material3.Button.TextButton"
22 android:id="@+id/button_next"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content" 12 android:layout_height="wrap_content"
25 android:layout_margin="12dp" 13 android:layout_above="@+id/constraint_buttons"
26 android:text="@string/next" 14 android:layout_alignParentTop="true"
27 android:visibility="invisible" 15 android:clipToPadding="false" />
28 app:layout_constraintBottom_toBottomOf="parent"
29 app:layout_constraintEnd_toEndOf="parent" />
30 16
31 <com.google.android.material.button.MaterialButton 17 <androidx.constraintlayout.widget.ConstraintLayout
32 style="@style/Widget.Material3.Button.TextButton" 18 android:id="@+id/constraint_buttons"
33 android:id="@+id/button_back" 19 android:layout_width="match_parent"
34 android:layout_width="wrap_content"
35 android:layout_height="wrap_content" 20 android:layout_height="wrap_content"
36 android:layout_margin="12dp" 21 android:layout_margin="8dp"
37 android:text="@string/back" 22 android:layout_alignParentBottom="true">
38 android:visibility="invisible" 23
39 app:layout_constraintBottom_toBottomOf="parent" 24 <com.google.android.material.button.MaterialButton
40 app:layout_constraintStart_toStartOf="parent" /> 25 android:id="@+id/button_next"
26 style="@style/Widget.Material3.Button.TextButton"
27 android:layout_width="wrap_content"
28 android:layout_height="wrap_content"
29 android:text="@string/next"
30 android:visibility="invisible"
31 app:layout_constraintBottom_toBottomOf="parent"
32 app:layout_constraintEnd_toEndOf="parent" />
33
34 <com.google.android.material.button.MaterialButton
35 android:id="@+id/button_back"
36 style="@style/Widget.Material3.Button.TextButton"
37 android:layout_width="wrap_content"
38 android:layout_height="wrap_content"
39 android:text="@string/back"
40 android:visibility="invisible"
41 app:layout_constraintBottom_toBottomOf="parent"
42 app:layout_constraintStart_toStartOf="parent" />
43
44 </androidx.constraintlayout.widget.ConstraintLayout>
41 45
42</androidx.constraintlayout.widget.ConstraintLayout> 46</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml
index ec896342b..f1037a740 100644
--- a/src/android/app/src/main/res/layout/list_item_setting.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting.xml
@@ -1,9 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
3 xmlns:tools="http://schemas.android.com/tools" 5 xmlns:tools="http://schemas.android.com/tools"
4 android:layout_width="match_parent" 6 android:layout_width="match_parent"
5 android:layout_height="wrap_content" 7 android:layout_height="wrap_content"
6 xmlns:app="http://schemas.android.com/apk/res-auto"
7 android:background="?android:attr/selectableItemBackground" 8 android:background="?android:attr/selectableItemBackground"
8 android:clickable="true" 9 android:clickable="true"
9 android:focusable="true" 10 android:focusable="true"
@@ -11,31 +12,40 @@
11 android:minHeight="72dp" 12 android:minHeight="72dp"
12 android:padding="@dimen/spacing_large"> 13 android:padding="@dimen/spacing_large">
13 14
14 <com.google.android.material.textview.MaterialTextView 15 <LinearLayout
15 style="@style/TextAppearance.Material3.HeadlineMedium" 16 android:layout_width="match_parent"
16 android:id="@+id/text_setting_name"
17 android:layout_width="0dp"
18 android:layout_height="wrap_content" 17 android:layout_height="wrap_content"
19 android:layout_alignParentEnd="true" 18 android:orientation="vertical">
20 android:layout_alignParentStart="true"
21 android:layout_alignParentTop="true"
22 android:textSize="16sp"
23 android:textAlignment="viewStart"
24 app:lineHeight="28dp"
25 tools:text="Setting Name" />
26 19
27 <TextView 20 <com.google.android.material.textview.MaterialTextView
28 style="@style/TextAppearance.Material3.BodySmall" 21 android:id="@+id/text_setting_name"
29 android:id="@+id/text_setting_description" 22 style="@style/TextAppearance.Material3.HeadlineMedium"
30 android:layout_width="wrap_content" 23 android:layout_width="match_parent"
31 android:layout_height="wrap_content" 24 android:layout_height="wrap_content"
32 android:layout_alignParentEnd="true" 25 android:textAlignment="viewStart"
33 android:layout_alignParentStart="true" 26 android:textSize="16sp"
34 android:layout_alignStart="@+id/text_setting_name" 27 app:lineHeight="22dp"
35 android:layout_below="@+id/text_setting_name" 28 tools:text="Setting Name" />
36 android:layout_marginTop="@dimen/spacing_small" 29
37 android:visibility="visible" 30 <com.google.android.material.textview.MaterialTextView
38 android:textAlignment="viewStart" 31 android:id="@+id/text_setting_description"
39 tools:text="@string/app_disclaimer" /> 32 style="@style/TextAppearance.Material3.BodySmall"
33 android:layout_width="match_parent"
34 android:layout_height="wrap_content"
35 android:layout_marginTop="@dimen/spacing_small"
36 android:textAlignment="viewStart"
37 tools:text="@string/app_disclaimer" />
38
39 <com.google.android.material.textview.MaterialTextView
40 android:id="@+id/text_setting_value"
41 style="@style/TextAppearance.Material3.LabelMedium"
42 android:layout_width="match_parent"
43 android:layout_height="wrap_content"
44 android:layout_marginTop="@dimen/spacing_small"
45 android:textAlignment="viewStart"
46 android:textStyle="bold"
47 tools:text="1x" />
48
49 </LinearLayout>
40 50
41</RelativeLayout> 51</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout/page_setup.xml b/src/android/app/src/main/res/layout/page_setup.xml
index 1436ef308..535abcf02 100644
--- a/src/android/app/src/main/res/layout/page_setup.xml
+++ b/src/android/app/src/main/res/layout/page_setup.xml
@@ -21,11 +21,12 @@
21 app:layout_constraintVertical_chainStyle="spread" 21 app:layout_constraintVertical_chainStyle="spread"
22 app:layout_constraintWidth_max="220dp" 22 app:layout_constraintWidth_max="220dp"
23 app:layout_constraintWidth_min="110dp" 23 app:layout_constraintWidth_min="110dp"
24 app:layout_constraintVertical_weight="3" /> 24 app:layout_constraintVertical_weight="3"
25 tools:src="@drawable/ic_notification" />
25 26
26 <com.google.android.material.textview.MaterialTextView 27 <com.google.android.material.textview.MaterialTextView
27 android:id="@+id/text_title" 28 android:id="@+id/text_title"
28 style="@style/TextAppearance.Material3.DisplayMedium" 29 style="@style/TextAppearance.Material3.DisplaySmall"
29 android:layout_width="0dp" 30 android:layout_width="0dp"
30 android:layout_height="0dp" 31 android:layout_height="0dp"
31 android:textAlignment="center" 32 android:textAlignment="center"
@@ -44,23 +45,42 @@
44 android:layout_width="0dp" 45 android:layout_width="0dp"
45 android:layout_height="0dp" 46 android:layout_height="0dp"
46 android:textAlignment="center" 47 android:textAlignment="center"
47 android:textSize="26sp" 48 android:textSize="20sp"
48 android:paddingHorizontal="16dp" 49 android:paddingHorizontal="16dp"
49 app:layout_constraintBottom_toTopOf="@+id/button_action" 50 app:layout_constraintBottom_toTopOf="@+id/button_action"
50 app:layout_constraintEnd_toEndOf="parent" 51 app:layout_constraintEnd_toEndOf="parent"
51 app:layout_constraintStart_toStartOf="parent" 52 app:layout_constraintStart_toStartOf="parent"
52 app:layout_constraintTop_toBottomOf="@+id/text_title" 53 app:layout_constraintTop_toBottomOf="@+id/text_title"
53 app:layout_constraintVertical_weight="2" 54 app:layout_constraintVertical_weight="2"
54 app:lineHeight="40sp" 55 app:lineHeight="30sp"
55 tools:text="@string/welcome_description" /> 56 tools:text="@string/welcome_description" />
56 57
58 <com.google.android.material.textview.MaterialTextView
59 android:id="@+id/text_confirmation"
60 style="@style/TextAppearance.Material3.TitleLarge"
61 android:layout_width="wrap_content"
62 android:layout_height="0dp"
63 android:paddingHorizontal="16dp"
64 android:paddingTop="24dp"
65 android:textAlignment="center"
66 android:textSize="30sp"
67 android:visibility="invisible"
68 android:text="@string/step_complete"
69 android:textStyle="bold"
70 app:layout_constraintBottom_toBottomOf="parent"
71 app:layout_constraintEnd_toEndOf="parent"
72 app:layout_constraintStart_toStartOf="parent"
73 app:layout_constraintTop_toBottomOf="@+id/text_description"
74 app:layout_constraintVertical_weight="1"
75 app:lineHeight="30sp" />
76
57 <com.google.android.material.button.MaterialButton 77 <com.google.android.material.button.MaterialButton
58 android:id="@+id/button_action" 78 android:id="@+id/button_action"
59 android:layout_width="wrap_content" 79 android:layout_width="wrap_content"
60 android:layout_height="56dp" 80 android:layout_height="56dp"
61 android:textSize="20sp"
62 android:layout_marginTop="16dp" 81 android:layout_marginTop="16dp"
63 android:layout_marginBottom="48dp" 82 android:layout_marginBottom="48dp"
83 android:textSize="20sp"
64 app:iconGravity="end" 84 app:iconGravity="end"
65 app:iconSize="24sp" 85 app:iconSize="24sp"
66 app:layout_constraintBottom_toBottomOf="parent" 86 app:layout_constraintBottom_toBottomOf="parent"
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml
index 1fe7aa6d4..21501a471 100644
--- a/src/android/app/src/main/res/menu/menu_settings.xml
+++ b/src/android/app/src/main/res/menu/menu_settings.xml
@@ -1,2 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<menu /> \ No newline at end of file 2<menu xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto">
4
5 <item
6 android:id="@+id/action_search"
7 android:icon="@drawable/ic_search"
8 android:title="@string/home_search"
9 app:showAsAction="always" />
10
11</menu>
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml
index 8208f4c2c..cfc494b3f 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -12,7 +12,26 @@
12 tools:layout="@layout/fragment_emulation" > 12 tools:layout="@layout/fragment_emulation" >
13 <argument 13 <argument
14 android:name="game" 14 android:name="game"
15 app:argType="org.yuzu.yuzu_emu.model.Game" /> 15 app:argType="org.yuzu.yuzu_emu.model.Game"
16 app:nullable="true"
17 android:defaultValue="@null" />
16 </fragment> 18 </fragment>
17 19
20 <activity
21 android:id="@+id/settingsActivity"
22 android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
23 android:label="SettingsActivity">
24 <argument
25 android:name="game"
26 app:argType="org.yuzu.yuzu_emu.model.Game"
27 app:nullable="true" />
28 <argument
29 android:name="menuTag"
30 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
31 </activity>
32
33 <action
34 android:id="@+id/action_global_settingsActivity"
35 app:destination="@id/settingsActivity" />
36
18</navigation> 37</navigation>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index fcebba726..2e0ce7a3d 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -62,7 +62,9 @@
62 android:label="EmulationActivity"> 62 android:label="EmulationActivity">
63 <argument 63 <argument
64 android:name="game" 64 android:name="game"
65 app:argType="org.yuzu.yuzu_emu.model.Game" /> 65 app:argType="org.yuzu.yuzu_emu.model.Game"
66 app:nullable="true"
67 android:defaultValue="@null" />
66 </activity> 68 </activity>
67 69
68 <action 70 <action
@@ -70,4 +72,21 @@
70 app:destination="@id/emulationActivity" 72 app:destination="@id/emulationActivity"
71 app:launchSingleTop="true" /> 73 app:launchSingleTop="true" />
72 74
75 <activity
76 android:id="@+id/settingsActivity"
77 android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
78 android:label="SettingsActivity">
79 <argument
80 android:name="game"
81 app:argType="org.yuzu.yuzu_emu.model.Game"
82 app:nullable="true" />
83 <argument
84 android:name="menuTag"
85 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
86 </activity>
87
88 <action
89 android:id="@+id/action_global_settingsActivity"
90 app:destination="@id/settingsActivity" />
91
73</navigation> 92</navigation>
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
new file mode 100644
index 000000000..1d87d36b3
--- /dev/null
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -0,0 +1,32 @@
1<?xml version="1.0" encoding="utf-8"?>
2<navigation xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/settings_navigation"
5 app:startDestination="@id/settingsFragment">
6
7 <fragment
8 android:id="@+id/settingsFragment"
9 android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment"
10 android:label="SettingsFragment">
11 <argument
12 android:name="menuTag"
13 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
14 <argument
15 android:name="game"
16 app:argType="org.yuzu.yuzu_emu.model.Game"
17 app:nullable="true" />
18 <action
19 android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
20 app:destination="@id/settingsSearchFragment" />
21 </fragment>
22
23 <action
24 android:id="@+id/action_global_settingsFragment"
25 app:destination="@id/settingsFragment" />
26
27 <fragment
28 android:id="@+id/settingsSearchFragment"
29 android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
30 android:label="SettingsSearchFragment" />
31
32</navigation>
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml
index 0c1d91264..daaa7ffde 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -209,7 +209,6 @@
209 <string name="emulation_pause">Emulation pausieren</string> 209 <string name="emulation_pause">Emulation pausieren</string>
210 <string name="emulation_unpause">Emulation fortsetzen</string> 210 <string name="emulation_unpause">Emulation fortsetzen</string>
211 <string name="emulation_input_overlay">Overlay-Optionen</string> 211 <string name="emulation_input_overlay">Overlay-Optionen</string>
212 <string name="emulation_game_loading">Spiel lädt…</string>
213 212
214 <string name="load_settings">Lädt Einstellungen...</string> 213 <string name="load_settings">Lädt Einstellungen...</string>
215 214
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index 357f956d1..e9129cb00 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Pausar Emulación</string> 213 <string name="emulation_pause">Pausar Emulación</string>
214 <string name="emulation_unpause">Reanudar Emulación</string> 214 <string name="emulation_unpause">Reanudar Emulación</string>
215 <string name="emulation_input_overlay">Opciones de pantalla </string> 215 <string name="emulation_input_overlay">Opciones de pantalla </string>
216 <string name="emulation_game_loading">Cargando juego...</string>
217 216
218 <string name="load_settings">Cargando configuración...</string> 217 <string name="load_settings">Cargando configuración...</string>
219 218
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index dfca1c830..2d99d618e 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Mettre en pause l\'émulation</string> 213 <string name="emulation_pause">Mettre en pause l\'émulation</string>
214 <string name="emulation_unpause">Reprendre l\'émulation</string> 214 <string name="emulation_unpause">Reprendre l\'émulation</string>
215 <string name="emulation_input_overlay">Options de l\'overlay</string> 215 <string name="emulation_input_overlay">Options de l\'overlay</string>
216 <string name="emulation_game_loading">Chargement du jeu...</string>
217 216
218 <string name="load_settings">Chargement des paramètres…</string> 217 <string name="load_settings">Chargement des paramètres…</string>
219 218
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index 089d93ed6..d9c3de385 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Metti in pausa l\'emulazione</string> 213 <string name="emulation_pause">Metti in pausa l\'emulazione</string>
214 <string name="emulation_unpause">Riprendi Emulazione</string> 214 <string name="emulation_unpause">Riprendi Emulazione</string>
215 <string name="emulation_input_overlay">Impostazioni Overlay</string> 215 <string name="emulation_input_overlay">Impostazioni Overlay</string>
216 <string name="emulation_game_loading">Caricamento del gioco...</string>
217 216
218 <string name="load_settings">Caricamento delle impostazioni...</string> 217 <string name="load_settings">Caricamento delle impostazioni...</string>
219 218
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index 39b590bee..7a226cd5c 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -211,7 +211,6 @@
211 <string name="emulation_pause">エミュレーションを一時停止</string> 211 <string name="emulation_pause">エミュレーションを一時停止</string>
212 <string name="emulation_unpause">エミュレーションを再開</string> 212 <string name="emulation_unpause">エミュレーションを再開</string>
213 <string name="emulation_input_overlay">オーバーレイオプション</string> 213 <string name="emulation_input_overlay">オーバーレイオプション</string>
214 <string name="emulation_game_loading">ロード中…</string>
215 214
216 <string name="load_settings">設定をロード中…</string> 215 <string name="load_settings">設定をロード中…</string>
217 216
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index cbcb2873f..427b6e5a0 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">에뮬레이션 일시 중지</string> 213 <string name="emulation_pause">에뮬레이션 일시 중지</string>
214 <string name="emulation_unpause">에뮬레이션 일시 중지 해제</string> 214 <string name="emulation_unpause">에뮬레이션 일시 중지 해제</string>
215 <string name="emulation_input_overlay">오버레이 옵션</string> 215 <string name="emulation_input_overlay">오버레이 옵션</string>
216 <string name="emulation_game_loading">게임 불러오기 중...</string>
217 216
218 <string name="load_settings">설정 불러오기 중...</string> 217 <string name="load_settings">설정 불러오기 중...</string>
219 218
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index e48a4be38..ce8d7a9e4 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Pause Emulering</string> 213 <string name="emulation_pause">Pause Emulering</string>
214 <string name="emulation_unpause">Opphev pausing av emulering</string> 214 <string name="emulation_unpause">Opphev pausing av emulering</string>
215 <string name="emulation_input_overlay">Alternativer for overlegg</string> 215 <string name="emulation_input_overlay">Alternativer for overlegg</string>
216 <string name="emulation_game_loading">Spillet lastes inn...</string>
217 216
218 <string name="load_settings">Laster inn innstillinger...</string> 217 <string name="load_settings">Laster inn innstillinger...</string>
219 218
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index bc9c0f7f4..c2c24b48f 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Wstrzymaj emulację</string> 213 <string name="emulation_pause">Wstrzymaj emulację</string>
214 <string name="emulation_unpause">Wznów emulację</string> 214 <string name="emulation_unpause">Wznów emulację</string>
215 <string name="emulation_input_overlay">Opcje nakładki</string> 215 <string name="emulation_input_overlay">Opcje nakładki</string>
216 <string name="emulation_game_loading">Wczytywanie gry...</string>
217 216
218 <string name="load_settings">Wczytywanie ustawień...</string> 217 <string name="load_settings">Wczytywanie ustawień...</string>
219 218
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 75fe0edbf..04f276108 100644
--- a/src/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Pausa emulação</string> 213 <string name="emulation_pause">Pausa emulação</string>
214 <string name="emulation_unpause">Retomar emulação</string> 214 <string name="emulation_unpause">Retomar emulação</string>
215 <string name="emulation_input_overlay">Opções de sobreposição </string> 215 <string name="emulation_input_overlay">Opções de sobreposição </string>
216 <string name="emulation_game_loading">Jogo a carregar...</string>
217 216
218 <string name="load_settings">Configurações a carregar...</string> 217 <string name="load_settings">Configurações a carregar...</string>
219 218
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index 96b040c66..66a3a1a2e 100644
--- a/src/android/app/src/main/res/values-pt-rPT/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Pausa emulação</string> 213 <string name="emulation_pause">Pausa emulação</string>
214 <string name="emulation_unpause">Retomar emulação</string> 214 <string name="emulation_unpause">Retomar emulação</string>
215 <string name="emulation_input_overlay">Opções de sobreposição </string> 215 <string name="emulation_input_overlay">Opções de sobreposição </string>
216 <string name="emulation_game_loading">Jogo a carregar...</string>
217 216
218 <string name="load_settings">Configurações a carregar...</string> 217 <string name="load_settings">Configurações a carregar...</string>
219 218
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index 8d954f59e..f770e954f 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Пауза эмуляции</string> 213 <string name="emulation_pause">Пауза эмуляции</string>
214 <string name="emulation_unpause">Возобновление эмуляции</string> 214 <string name="emulation_unpause">Возобновление эмуляции</string>
215 <string name="emulation_input_overlay">Настройки оверлея</string> 215 <string name="emulation_input_overlay">Настройки оверлея</string>
216 <string name="emulation_game_loading">Загрузка игры...</string>
217 216
218 <string name="load_settings">Загрузка настроек...</string> 217 <string name="load_settings">Загрузка настроек...</string>
219 218
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index 6c028535b..ea3ab1b15 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">Пауза емуляції</string> 213 <string name="emulation_pause">Пауза емуляції</string>
214 <string name="emulation_unpause">Відновлення емуляції</string> 214 <string name="emulation_unpause">Відновлення емуляції</string>
215 <string name="emulation_input_overlay">Налаштування оверлея</string> 215 <string name="emulation_input_overlay">Налаштування оверлея</string>
216 <string name="emulation_game_loading">Завантаження гри...</string>
217 216
218 <string name="load_settings">Завантаження налаштувань...</string> 217 <string name="load_settings">Завантаження налаштувань...</string>
219 218
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index e4ad2ed07..b45a5a528 100644
--- a/src/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">暂停模拟</string> 213 <string name="emulation_pause">暂停模拟</string>
214 <string name="emulation_unpause">继续模拟</string> 214 <string name="emulation_unpause">继续模拟</string>
215 <string name="emulation_input_overlay">虚拟按键选项</string> 215 <string name="emulation_input_overlay">虚拟按键选项</string>
216 <string name="emulation_game_loading">载入游戏中…</string>
217 216
218 <string name="load_settings">正在载入设定…</string> 217 <string name="load_settings">正在载入设定…</string>
219 218
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index 0d32f23df..3aab889e4 100644
--- a/src/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -213,7 +213,6 @@
213 <string name="emulation_pause">暫停模擬</string> 213 <string name="emulation_pause">暫停模擬</string>
214 <string name="emulation_unpause">取消暫停模擬</string> 214 <string name="emulation_unpause">取消暫停模擬</string>
215 <string name="emulation_input_overlay">覆疊選項</string> 215 <string name="emulation_input_overlay">覆疊選項</string>
216 <string name="emulation_game_loading">遊戲正在載入…</string>
217 216
218 <string name="load_settings">正在載入設定…</string> 217 <string name="load_settings">正在載入設定…</string>
219 218
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 200b99185..dc10159c9 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -243,10 +243,10 @@
243 <item>@string/cubeb</item> 243 <item>@string/cubeb</item>
244 <item>@string/string_null</item> 244 <item>@string/string_null</item>
245 </string-array> 245 </string-array>
246 <string-array name="outputEngineValues"> 246 <integer-array name="outputEngineValues">
247 <item>auto</item> 247 <item>0</item>
248 <item>cubeb</item> 248 <item>1</item>
249 <item>null</item> 249 <item>3</item>
250 </string-array> 250 </integer-array>
251 251
252</resources> 252</resources>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 00757e5e8..7b2296d95 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -12,6 +12,7 @@
12 <dimen name="spacing_refresh_end">72dp</dimen> 12 <dimen name="spacing_refresh_end">72dp</dimen>
13 <dimen name="menu_width">256dp</dimen> 13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen> 14 <dimen name="card_width">165dp</dimen>
15 <dimen name="icon_inset">24dp</dimen>
15 16
16 <dimen name="dialog_margin">20dp</dimen> 17 <dimen name="dialog_margin">20dp</dimen>
17 <dimen name="elevated_app_bar">3dp</dimen> 18 <dimen name="elevated_app_bar">3dp</dimen>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 02e25504d..b163e6fc1 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -29,6 +29,7 @@
29 <string name="back">Back</string> 29 <string name="back">Back</string>
30 <string name="add_games">Add Games</string> 30 <string name="add_games">Add Games</string>
31 <string name="add_games_description">Select your games folder</string> 31 <string name="add_games_description">Select your games folder</string>
32 <string name="step_complete">Complete!</string>
32 33
33 <!-- Home strings --> 34 <!-- Home strings -->
34 <string name="home_games">Games</string> 35 <string name="home_games">Games</string>
@@ -42,6 +43,7 @@
42 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string> 43 <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
43 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string> 44 <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
44 <string name="home_search_games">Search games</string> 45 <string name="home_search_games">Search games</string>
46 <string name="search_settings">Search settings</string>
45 <string name="games_dir_selected">Games directory selected</string> 47 <string name="games_dir_selected">Games directory selected</string>
46 <string name="install_prod_keys">Install prod.keys</string> 48 <string name="install_prod_keys">Install prod.keys</string>
47 <string name="install_prod_keys_description">Required to decrypt retail games</string> 49 <string name="install_prod_keys_description">Required to decrypt retail games</string>
@@ -73,6 +75,7 @@
73 <string name="install_gpu_driver">Install GPU driver</string> 75 <string name="install_gpu_driver">Install GPU driver</string>
74 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> 76 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
75 <string name="advanced_settings">Advanced settings</string> 77 <string name="advanced_settings">Advanced settings</string>
78 <string name="advanced_settings_game">Advanced settings: %1$s</string>
76 <string name="settings_description">Configure emulator settings</string> 79 <string name="settings_description">Configure emulator settings</string>
77 <string name="search_recently_played">Recently played</string> 80 <string name="search_recently_played">Recently played</string>
78 <string name="search_recently_added">Recently added</string> 81 <string name="search_recently_added">Recently added</string>
@@ -149,6 +152,7 @@
149 <string name="frame_limit_slider">Limit speed percent</string> 152 <string name="frame_limit_slider">Limit speed percent</string>
150 <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string> 153 <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string>
151 <string name="cpu_accuracy">CPU accuracy</string> 154 <string name="cpu_accuracy">CPU accuracy</string>
155 <string name="value_with_units">%1$s%2$s</string>
152 156
153 <!-- System settings strings --> 157 <!-- System settings strings -->
154 <string name="use_docked_mode">Docked Mode</string> 158 <string name="use_docked_mode">Docked Mode</string>
@@ -198,7 +202,9 @@
198 <string name="ini_saved">Saved settings</string> 202 <string name="ini_saved">Saved settings</string>
199 <string name="gameid_saved">Saved settings for %1$s</string> 203 <string name="gameid_saved">Saved settings for %1$s</string>
200 <string name="error_saving">Error saving %1$s.ini: %2$s</string> 204 <string name="error_saving">Error saving %1$s.ini: %2$s</string>
205 <string name="unimplemented_menu">Unimplemented Menu</string>
201 <string name="loading">Loading…</string> 206 <string name="loading">Loading…</string>
207 <string name="shutting_down">Shutting down…</string>
202 <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string> 208 <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
203 <string name="reset_to_default">Reset to default</string> 209 <string name="reset_to_default">Reset to default</string>
204 <string name="reset_all_settings">Reset all settings?</string> 210 <string name="reset_all_settings">Reset all settings?</string>
@@ -257,7 +263,6 @@
257 <string name="emulation_pause">Pause emulation</string> 263 <string name="emulation_pause">Pause emulation</string>
258 <string name="emulation_unpause">Unpause emulation</string> 264 <string name="emulation_unpause">Unpause emulation</string>
259 <string name="emulation_input_overlay">Overlay options</string> 265 <string name="emulation_input_overlay">Overlay options</string>
260 <string name="emulation_game_loading">Game loading…</string>
261 266
262 <string name="load_settings">Loading settings…</string> 267 <string name="load_settings">Loading settings…</string>
263 268
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index e7b595459..400988c5f 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -2,6 +2,21 @@
2# SPDX-License-Identifier: GPL-2.0-or-later 2# SPDX-License-Identifier: GPL-2.0-or-later
3 3
4add_library(audio_core STATIC 4add_library(audio_core STATIC
5 adsp/adsp.cpp
6 adsp/adsp.h
7 adsp/mailbox.h
8 adsp/apps/audio_renderer/audio_renderer.cpp
9 adsp/apps/audio_renderer/audio_renderer.h
10 adsp/apps/audio_renderer/command_buffer.h
11 adsp/apps/audio_renderer/command_list_processor.cpp
12 adsp/apps/audio_renderer/command_list_processor.h
13 adsp/apps/opus/opus_decoder.cpp
14 adsp/apps/opus/opus_decoder.h
15 adsp/apps/opus/opus_decode_object.cpp
16 adsp/apps/opus/opus_decode_object.h
17 adsp/apps/opus/opus_multistream_decode_object.cpp
18 adsp/apps/opus/opus_multistream_decode_object.h
19 adsp/apps/opus/shared_memory.h
5 audio_core.cpp 20 audio_core.cpp
6 audio_core.h 21 audio_core.h
7 audio_event.h 22 audio_event.h
@@ -27,18 +42,18 @@ add_library(audio_core STATIC
27 in/audio_in.h 42 in/audio_in.h
28 in/audio_in_system.cpp 43 in/audio_in_system.cpp
29 in/audio_in_system.h 44 in/audio_in_system.h
45 opus/hardware_opus.cpp
46 opus/hardware_opus.h
47 opus/decoder_manager.cpp
48 opus/decoder_manager.h
49 opus/decoder.cpp
50 opus/decoder.h
51 opus/parameters.h
30 out/audio_out.cpp 52 out/audio_out.cpp
31 out/audio_out.h 53 out/audio_out.h
32 out/audio_out_system.cpp 54 out/audio_out_system.cpp
33 out/audio_out_system.h 55 out/audio_out_system.h
34 precompiled_headers.h 56 precompiled_headers.h
35 renderer/adsp/adsp.cpp
36 renderer/adsp/adsp.h
37 renderer/adsp/audio_renderer.cpp
38 renderer/adsp/audio_renderer.h
39 renderer/adsp/command_buffer.h
40 renderer/adsp/command_list_processor.cpp
41 renderer/adsp/command_list_processor.h
42 renderer/audio_device.cpp 57 renderer/audio_device.cpp
43 renderer/audio_device.h 58 renderer/audio_device.h
44 renderer/audio_renderer.h 59 renderer/audio_renderer.h
@@ -213,7 +228,7 @@ else()
213 ) 228 )
214endif() 229endif()
215 230
216target_link_libraries(audio_core PUBLIC common core) 231target_link_libraries(audio_core PUBLIC common core Opus::opus)
217if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) 232if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
218 target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) 233 target_link_libraries(audio_core PRIVATE dynarmic::dynarmic)
219endif() 234endif()
diff --git a/src/audio_core/adsp/adsp.cpp b/src/audio_core/adsp/adsp.cpp
new file mode 100644
index 000000000..6c53c98fd
--- /dev/null
+++ b/src/audio_core/adsp/adsp.cpp
@@ -0,0 +1,27 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/adsp/adsp.h"
5#include "core/core.h"
6
7namespace AudioCore::ADSP {
8
9ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
10 audio_renderer = std::make_unique<AudioRenderer::AudioRenderer>(system, sink);
11 opus_decoder = std::make_unique<OpusDecoder::OpusDecoder>(system);
12 opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start);
13 if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) {
14 LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize.");
15 return;
16 }
17}
18
19AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
20 return *audio_renderer.get();
21}
22
23OpusDecoder::OpusDecoder& ADSP::OpusDecoder() {
24 return *opus_decoder.get();
25}
26
27} // namespace AudioCore::ADSP
diff --git a/src/audio_core/adsp/adsp.h b/src/audio_core/adsp/adsp.h
new file mode 100644
index 000000000..a0c24a16a
--- /dev/null
+++ b/src/audio_core/adsp/adsp.h
@@ -0,0 +1,53 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
7#include "audio_core/adsp/apps/opus/opus_decoder.h"
8#include "common/common_types.h"
9
10namespace Core {
11class System;
12} // namespace Core
13
14namespace AudioCore {
15namespace Sink {
16class Sink;
17}
18
19namespace ADSP {
20
21/**
22 * Represents the ADSP embedded within the audio sysmodule.
23 * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
24 *
25 * The kernel will run the apps you write for it, Nintendo have the following:
26 *
27 * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
28 * audio samples end up, and we skip it entirely, since we have very different backends and
29 * mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
30 *
31 * AudioRenderer - Receives command lists generated by the audio render
32 * system on the host, processes them, and sends the samples to Gmix.
33 *
34 * OpusDecoder - Contains libopus, and decodes Opus audio packets into raw pcm data.
35 *
36 * Communication between the host and ADSP is done through mailboxes, and mapping of shared memory.
37 */
38class ADSP {
39public:
40 explicit ADSP(Core::System& system, Sink::Sink& sink);
41 ~ADSP() = default;
42
43 AudioRenderer::AudioRenderer& AudioRenderer();
44 OpusDecoder::OpusDecoder& OpusDecoder();
45
46private:
47 /// AudioRenderer app
48 std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{};
49 std::unique_ptr<OpusDecoder::OpusDecoder> opus_decoder{};
50};
51
52} // namespace ADSP
53} // namespace AudioCore
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
new file mode 100644
index 000000000..972d5e45b
--- /dev/null
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
@@ -0,0 +1,218 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <chrono>
6
7#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
8#include "audio_core/audio_core.h"
9#include "audio_core/common/common.h"
10#include "audio_core/sink/sink.h"
11#include "common/logging/log.h"
12#include "common/microprofile.h"
13#include "common/thread.h"
14#include "core/core.h"
15#include "core/core_timing.h"
16
17MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP_AudioRenderer", MP_RGB(60, 19, 97));
18
19namespace AudioCore::ADSP::AudioRenderer {
20
21AudioRenderer::AudioRenderer(Core::System& system_, Sink::Sink& sink_)
22 : system{system_}, sink{sink_} {}
23
24AudioRenderer::~AudioRenderer() {
25 Stop();
26}
27
28void AudioRenderer::Start() {
29 CreateSinkStreams();
30
31 mailbox.Initialize(AppMailboxId::AudioRenderer);
32
33 main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); });
34
35 mailbox.Send(Direction::DSP, Message::InitializeOK);
36 if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
37 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
38 "message response from ADSP!");
39 return;
40 }
41 running = true;
42}
43
44void AudioRenderer::Stop() {
45 if (!running) {
46 return;
47 }
48
49 mailbox.Send(Direction::DSP, Message::Shutdown);
50 if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
51 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
52 "message response from ADSP!");
53 }
54 main_thread.request_stop();
55 main_thread.join();
56
57 for (auto& stream : streams) {
58 if (stream) {
59 stream->Stop();
60 sink.CloseStream(stream);
61 stream = nullptr;
62 }
63 }
64 running = false;
65}
66
67void AudioRenderer::Signal() {
68 signalled_tick = system.CoreTiming().GetGlobalTimeNs().count();
69 Send(Direction::DSP, Message::Render);
70}
71
72void AudioRenderer::Wait() {
73 auto msg = Receive(Direction::Host);
74 if (msg != Message::RenderResponse) {
75 LOG_ERROR(Service_Audio,
76 "Did not receive the expected render response from the AudioRenderer! Expected "
77 "{}, got {}",
78 Message::RenderResponse, msg);
79 }
80}
81
82void AudioRenderer::Send(Direction dir, u32 message) {
83 mailbox.Send(dir, std::move(message));
84}
85
86u32 AudioRenderer::Receive(Direction dir) {
87 return mailbox.Receive(dir);
88}
89
90void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
91 u64 applet_resource_user_id, bool reset) noexcept {
92 command_buffers[session_id].buffer = buffer;
93 command_buffers[session_id].size = size;
94 command_buffers[session_id].time_limit = time_limit;
95 command_buffers[session_id].applet_resource_user_id = applet_resource_user_id;
96 command_buffers[session_id].reset_buffer = reset;
97}
98
99u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
100 return command_buffers[session_id].remaining_command_count;
101}
102
103void AudioRenderer::ClearRemainCommandCount(s32 session_id) noexcept {
104 command_buffers[session_id].remaining_command_count = 0;
105}
106
107u64 AudioRenderer::GetRenderingStartTick(s32 session_id) const noexcept {
108 return (1000 * command_buffers[session_id].render_time_taken_us) + signalled_tick;
109}
110
111void AudioRenderer::CreateSinkStreams() {
112 u32 channels{sink.GetDeviceChannels()};
113 for (u32 i = 0; i < MaxRendererSessions; i++) {
114 std::string name{fmt::format("ADSP_RenderStream-{}", i)};
115 streams[i] =
116 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
117 streams[i]->SetRingSize(4);
118 }
119}
120
121void AudioRenderer::Main(std::stop_token stop_token) {
122 static constexpr char name[]{"DSP_AudioRenderer_Main"};
123 MicroProfileOnThreadCreate(name);
124 Common::SetCurrentThreadName(name);
125 Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
126
127 // TODO: Create buffer map/unmap thread + mailbox
128 // TODO: Create gMix devices, initialize them here
129
130 if (mailbox.Receive(Direction::DSP) != Message::InitializeOK) {
131 LOG_ERROR(Service_Audio,
132 "ADSP Audio Renderer -- Failed to receive initialize message from host!");
133 return;
134 }
135
136 mailbox.Send(Direction::Host, Message::InitializeOK);
137
138 // 0.12 seconds (2,304,000 / 19,200,000)
139 constexpr u64 max_process_time{2'304'000ULL};
140
141 while (!stop_token.stop_requested()) {
142 auto msg{mailbox.Receive(Direction::DSP)};
143 switch (msg) {
144 case Message::Shutdown:
145 mailbox.Send(Direction::Host, Message::Shutdown);
146 return;
147
148 case Message::Render: {
149 if (system.IsShuttingDown()) [[unlikely]] {
150 std::this_thread::sleep_for(std::chrono::milliseconds(5));
151 mailbox.Send(Direction::Host, Message::RenderResponse);
152 continue;
153 }
154 std::array<bool, MaxRendererSessions> buffers_reset{};
155 std::array<u64, MaxRendererSessions> render_times_taken{};
156 const auto start_time{system.CoreTiming().GetGlobalTimeUs().count()};
157
158 for (u32 index = 0; index < MaxRendererSessions; index++) {
159 auto& command_buffer{command_buffers[index]};
160 auto& command_list_processor{command_list_processors[index]};
161
162 // Check this buffer is valid, as it may not be used.
163 if (command_buffer.buffer != 0) {
164 // If there are no remaining commands (from the previous list),
165 // this is a new command list, initialize it.
166 if (command_buffer.remaining_command_count == 0) {
167 command_list_processor.Initialize(system, command_buffer.buffer,
168 command_buffer.size, streams[index]);
169 }
170
171 if (command_buffer.reset_buffer && !buffers_reset[index]) {
172 streams[index]->ClearQueue();
173 buffers_reset[index] = true;
174 }
175
176 u64 max_time{max_process_time};
177 if (index == 1 && command_buffer.applet_resource_user_id ==
178 command_buffers[0].applet_resource_user_id) {
179 max_time = max_process_time - render_times_taken[0];
180 if (render_times_taken[0] > max_process_time) {
181 max_time = 0;
182 }
183 }
184
185 max_time = std::min(command_buffer.time_limit, max_time);
186 command_list_processor.SetProcessTimeMax(max_time);
187
188 if (index == 0) {
189 streams[index]->WaitFreeSpace(stop_token);
190 }
191
192 // Process the command list
193 {
194 MICROPROFILE_SCOPE(Audio_Renderer);
195 render_times_taken[index] =
196 command_list_processor.Process(index) - start_time;
197 }
198
199 const auto end_time{system.CoreTiming().GetGlobalTimeUs().count()};
200
201 command_buffer.remaining_command_count =
202 command_list_processor.GetRemainingCommandCount();
203 command_buffer.render_time_taken_us = end_time - start_time;
204 }
205 }
206
207 mailbox.Send(Direction::Host, Message::RenderResponse);
208 } break;
209
210 default:
211 LOG_WARNING(Service_Audio,
212 "ADSP AudioRenderer received an invalid message, msg={:02X}!", msg);
213 break;
214 }
215 }
216}
217
218} // namespace AudioCore::ADSP::AudioRenderer
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
new file mode 100644
index 000000000..85874d88a
--- /dev/null
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
@@ -0,0 +1,109 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <thread>
9
10#include "audio_core/adsp/apps/audio_renderer/command_buffer.h"
11#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
12#include "audio_core/adsp/mailbox.h"
13#include "common/common_types.h"
14#include "common/polyfill_thread.h"
15#include "common/reader_writer_queue.h"
16#include "common/thread.h"
17
18namespace Core {
19class System;
20} // namespace Core
21
22namespace AudioCore {
23namespace Sink {
24class Sink;
25}
26
27namespace ADSP::AudioRenderer {
28
29enum Message : u32 {
30 Invalid = 0,
31 MapUnmap_Map = 1,
32 MapUnmap_MapResponse = 2,
33 MapUnmap_Unmap = 3,
34 MapUnmap_UnmapResponse = 4,
35 MapUnmap_InvalidateCache = 5,
36 MapUnmap_InvalidateCacheResponse = 6,
37 MapUnmap_Shutdown = 7,
38 MapUnmap_ShutdownResponse = 8,
39 InitializeOK = 22,
40 RenderResponse = 32,
41 Render = 42,
42 Shutdown = 52,
43};
44
45/**
46 * The AudioRenderer application running on the ADSP.
47 */
48class AudioRenderer {
49public:
50 explicit AudioRenderer(Core::System& system, Sink::Sink& sink);
51 ~AudioRenderer();
52
53 /**
54 * Start the AudioRenderer.
55 *
56 * @param mailbox The mailbox to use for this session.
57 */
58 void Start();
59
60 /**
61 * Stop the AudioRenderer.
62 */
63 void Stop();
64
65 void Signal();
66 void Wait();
67
68 void Send(Direction dir, u32 message);
69 u32 Receive(Direction dir);
70
71 void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
72 u64 applet_resource_user_id, bool reset) noexcept;
73 u32 GetRemainCommandCount(s32 session_id) const noexcept;
74 void ClearRemainCommandCount(s32 session_id) noexcept;
75 u64 GetRenderingStartTick(s32 session_id) const noexcept;
76
77private:
78 /**
79 * Main AudioRenderer thread, responsible for processing the command lists.
80 */
81 void Main(std::stop_token stop_token);
82
83 /**
84 * Creates the streams which will receive the processed samples.
85 */
86 void CreateSinkStreams();
87
88 /// Core system
89 Core::System& system;
90 /// The output sink the AudioRenderer will send samples to
91 Sink::Sink& sink;
92 /// The active mailbox
93 Mailbox mailbox;
94 /// Main thread
95 std::jthread main_thread{};
96 /// The current state
97 std::atomic<bool> running{};
98 /// Shared memory of input command buffers, set by host, read by DSP
99 std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
100 /// The command lists to process
101 std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
102 /// The streams which will receive the processed samples
103 std::array<Sink::SinkStream*, MaxRendererSessions> streams{};
104 /// CPU Tick when the DSP was signalled to process, uses time rather than tick
105 u64 signalled_tick{0};
106};
107
108} // namespace ADSP::AudioRenderer
109} // namespace AudioCore
diff --git a/src/audio_core/adsp/apps/audio_renderer/command_buffer.h b/src/audio_core/adsp/apps/audio_renderer/command_buffer.h
new file mode 100644
index 000000000..3fd1b09dc
--- /dev/null
+++ b/src/audio_core/adsp/apps/audio_renderer/command_buffer.h
@@ -0,0 +1,23 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/common/common.h"
7#include "common/common_types.h"
8
9namespace AudioCore::ADSP::AudioRenderer {
10
11struct CommandBuffer {
12 // Set by the host
13 CpuAddr buffer{};
14 u64 size{};
15 u64 time_limit{};
16 u64 applet_resource_user_id{};
17 bool reset_buffer{};
18 // Set by the DSP
19 u32 remaining_command_count{};
20 u64 render_time_taken_us{};
21};
22
23} // namespace AudioCore::ADSP::AudioRenderer
diff --git a/src/audio_core/renderer/adsp/command_list_processor.cpp b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp
index 3a0f1ae38..24e4d0496 100644
--- a/src/audio_core/renderer/adsp/command_list_processor.cpp
+++ b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp
@@ -1,9 +1,9 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <string> 4#include <string>
5 5
6#include "audio_core/renderer/adsp/command_list_processor.h" 6#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
7#include "audio_core/renderer/command/command_list_header.h" 7#include "audio_core/renderer/command/command_list_header.h"
8#include "audio_core/renderer/command/commands.h" 8#include "audio_core/renderer/command/commands.h"
9#include "common/settings.h" 9#include "common/settings.h"
@@ -11,15 +11,15 @@
11#include "core/core_timing.h" 11#include "core/core_timing.h"
12#include "core/memory.h" 12#include "core/memory.h"
13 13
14namespace AudioCore::AudioRenderer::ADSP { 14namespace AudioCore::ADSP::AudioRenderer {
15 15
16void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size, 16void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
17 Sink::SinkStream* stream_) { 17 Sink::SinkStream* stream_) {
18 system = &system_; 18 system = &system_;
19 memory = &system->ApplicationMemory(); 19 memory = &system->ApplicationMemory();
20 stream = stream_; 20 stream = stream_;
21 header = reinterpret_cast<CommandListHeader*>(buffer); 21 header = reinterpret_cast<Renderer::CommandListHeader*>(buffer);
22 commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); 22 commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader));
23 commands_buffer_size = size; 23 commands_buffer_size = size;
24 command_count = header->command_count; 24 command_count = header->command_count;
25 sample_count = header->sample_count; 25 sample_count = header->sample_count;
@@ -37,17 +37,12 @@ u32 CommandListProcessor::GetRemainingCommandCount() const {
37 return command_count - processed_command_count; 37 return command_count - processed_command_count;
38} 38}
39 39
40void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
41 commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
42 commands_buffer_size = size;
43}
44
45Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { 40Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
46 return stream; 41 return stream;
47} 42}
48 43
49u64 CommandListProcessor::Process(u32 session_id) { 44u64 CommandListProcessor::Process(u32 session_id) {
50 const auto start_time_{system->CoreTiming().GetClockTicks()}; 45 const auto start_time_{system->CoreTiming().GetGlobalTimeUs().count()};
51 const auto command_base{CpuAddr(commands)}; 46 const auto command_base{CpuAddr(commands)};
52 47
53 if (processed_command_count > 0) { 48 if (processed_command_count > 0) {
@@ -60,12 +55,12 @@ u64 CommandListProcessor::Process(u32 session_id) {
60 std::string dump{fmt::format("\nSession {}\n", session_id)}; 55 std::string dump{fmt::format("\nSession {}\n", session_id)};
61 56
62 for (u32 index = 0; index < command_count; index++) { 57 for (u32 index = 0; index < command_count; index++) {
63 auto& command{*reinterpret_cast<ICommand*>(commands)}; 58 auto& command{*reinterpret_cast<Renderer::ICommand*>(commands)};
64 59
65 if (command.magic != 0xCAFEBABE) { 60 if (command.magic != 0xCAFEBABE) {
66 LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}", 61 LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
67 command.magic); 62 command.magic);
68 return system->CoreTiming().GetClockTicks() - start_time_; 63 return system->CoreTiming().GetGlobalTimeUs().count() - start_time_;
69 } 64 }
70 65
71 auto current_offset{CpuAddr(commands) - command_base}; 66 auto current_offset{CpuAddr(commands) - command_base};
@@ -74,8 +69,8 @@ u64 CommandListProcessor::Process(u32 session_id) {
74 LOG_ERROR(Service_Audio, 69 LOG_ERROR(Service_Audio,
75 "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}", 70 "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
76 commands_buffer_size, 71 commands_buffer_size,
77 CpuAddr(commands) + command.size - sizeof(CommandListHeader)); 72 CpuAddr(commands) + command.size - sizeof(Renderer::CommandListHeader));
78 return system->CoreTiming().GetClockTicks() - start_time_; 73 return system->CoreTiming().GetGlobalTimeUs().count() - start_time_;
79 } 74 }
80 75
81 if (Settings::values.dump_audio_commands) { 76 if (Settings::values.dump_audio_commands) {
@@ -101,8 +96,8 @@ u64 CommandListProcessor::Process(u32 session_id) {
101 last_dump = dump; 96 last_dump = dump;
102 } 97 }
103 98
104 end_time = system->CoreTiming().GetClockTicks(); 99 end_time = system->CoreTiming().GetGlobalTimeUs().count();
105 return end_time - start_time_; 100 return end_time - start_time_;
106} 101}
107 102
108} // namespace AudioCore::AudioRenderer::ADSP 103} // namespace AudioCore::ADSP::AudioRenderer
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h
index d78269e1d..4e5fb793e 100644
--- a/src/audio_core/renderer/adsp/command_list_processor.h
+++ b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h
@@ -1,4 +1,4 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#pragma once 4#pragma once
@@ -6,6 +6,7 @@
6#include <span> 6#include <span>
7 7
8#include "audio_core/common/common.h" 8#include "audio_core/common/common.h"
9#include "audio_core/renderer/command/command_list_header.h"
9#include "common/common_types.h" 10#include "common/common_types.h"
10 11
11namespace Core { 12namespace Core {
@@ -20,10 +21,11 @@ namespace Sink {
20class SinkStream; 21class SinkStream;
21} 22}
22 23
23namespace AudioRenderer { 24namespace Renderer {
24struct CommandListHeader; 25struct CommandListHeader;
26}
25 27
26namespace ADSP { 28namespace ADSP::AudioRenderer {
27 29
28/** 30/**
29 * A processor for command lists given to the AudioRenderer. 31 * A processor for command lists given to the AudioRenderer.
@@ -55,14 +57,6 @@ public:
55 u32 GetRemainingCommandCount() const; 57 u32 GetRemainingCommandCount() const;
56 58
57 /** 59 /**
58 * Set the command buffer.
59 *
60 * @param buffer - The buffer to use.
61 * @param size - The size of the buffer.
62 */
63 void SetBuffer(CpuAddr buffer, u64 size);
64
65 /**
66 * Get the stream for this command list. 60 * Get the stream for this command list.
67 * 61 *
68 * @return The stream associated with this command list. 62 * @return The stream associated with this command list.
@@ -85,7 +79,7 @@ public:
85 /// Stream for the processed samples 79 /// Stream for the processed samples
86 Sink::SinkStream* stream{}; 80 Sink::SinkStream* stream{};
87 /// Header info for this command list 81 /// Header info for this command list
88 CommandListHeader* header{}; 82 Renderer::CommandListHeader* header{};
89 /// The command buffer 83 /// The command buffer
90 u8* commands{}; 84 u8* commands{};
91 /// The command buffer size 85 /// The command buffer size
@@ -114,6 +108,5 @@ public:
114 std::string last_dump{}; 108 std::string last_dump{};
115}; 109};
116 110
117} // namespace ADSP 111} // namespace ADSP::AudioRenderer
118} // namespace AudioRenderer
119} // namespace AudioCore 112} // namespace AudioCore
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp
new file mode 100644
index 000000000..2c16d3769
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp
@@ -0,0 +1,107 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/adsp/apps/opus/opus_decode_object.h"
5#include "common/assert.h"
6
7namespace AudioCore::ADSP::OpusDecoder {
8namespace {
9bool IsValidChannelCount(u32 channel_count) {
10 return channel_count == 1 || channel_count == 2;
11}
12} // namespace
13
14u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
15 if (!IsValidChannelCount(channel_count)) {
16 return 0;
17 }
18 return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
19}
20
21OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
22 auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
23 auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
24
25 if (new_decoder->magic == DecodeObjectMagic) {
26 if (!new_decoder->initialized ||
27 (new_decoder->initialized && new_decoder->self == comparison)) {
28 new_decoder->state_valid = true;
29 }
30 } else {
31 new_decoder->initialized = false;
32 new_decoder->state_valid = true;
33 }
34 return *new_decoder;
35}
36
37s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
38 if (!state_valid) {
39 return OPUS_INVALID_STATE;
40 }
41
42 if (initialized) {
43 return OPUS_OK;
44 }
45
46 // Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
47 // it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
48 // provided.
49 // We could use _create and have libopus allocate it for us, but then we have to separately
50 // track which decoder is being used between this and multistream in order to call the correct
51 // destroy from the host side.
52 // This is a bit cringe, but is safe as these objects are only ever initialized inside the given
53 // workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
54 decoder = (LibOpusDecoder*)(this + 1);
55 s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
56 if (ret == OPUS_OK) {
57 magic = DecodeObjectMagic;
58 initialized = true;
59 state_valid = true;
60 self = this;
61 final_range = 0;
62 }
63 return ret;
64}
65
66s32 OpusDecodeObject::Shutdown() {
67 if (!state_valid) {
68 return OPUS_INVALID_STATE;
69 }
70
71 if (initialized) {
72 magic = 0x0;
73 initialized = false;
74 state_valid = false;
75 self = nullptr;
76 final_range = 0;
77 decoder = nullptr;
78 }
79 return OPUS_OK;
80}
81
82s32 OpusDecodeObject::ResetDecoder() {
83 return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
84}
85
86s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
87 u64 input_data, u64 input_data_size) {
88 ASSERT(initialized);
89 out_sample_count = 0;
90
91 if (!state_valid) {
92 return OPUS_INVALID_STATE;
93 }
94
95 auto ret_code_or_samples = opus_decode(
96 decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
97 reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
98
99 if (ret_code_or_samples < OPUS_OK) {
100 return ret_code_or_samples;
101 }
102
103 out_sample_count = ret_code_or_samples;
104 return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
105}
106
107} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.h b/src/audio_core/adsp/apps/opus/opus_decode_object.h
new file mode 100644
index 000000000..6425f987c
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <opus.h>
7
8#include "common/common_types.h"
9
10namespace AudioCore::ADSP::OpusDecoder {
11using LibOpusDecoder = ::OpusDecoder;
12static constexpr u32 DecodeObjectMagic = 0xDEADBEEF;
13
14class OpusDecodeObject {
15public:
16 static u32 GetWorkBufferSize(u32 channel_count);
17 static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2);
18
19 s32 InitializeDecoder(u32 sample_rate, u32 channel_count);
20 s32 Shutdown();
21 s32 ResetDecoder();
22 s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
23 u64 input_data_size);
24 u32 GetFinalRange() const noexcept {
25 return final_range;
26 }
27
28private:
29 u32 magic;
30 bool initialized;
31 bool state_valid;
32 OpusDecodeObject* self;
33 u32 final_range;
34 LibOpusDecoder* decoder;
35};
36static_assert(std::is_trivially_constructible_v<OpusDecodeObject>);
37
38} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp
new file mode 100644
index 000000000..2084de128
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp
@@ -0,0 +1,269 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <chrono>
6
7#include "audio_core/adsp/apps/opus/opus_decode_object.h"
8#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
9#include "audio_core/adsp/apps/opus/shared_memory.h"
10#include "audio_core/audio_core.h"
11#include "audio_core/common/common.h"
12#include "common/logging/log.h"
13#include "common/microprofile.h"
14#include "common/thread.h"
15#include "core/core.h"
16#include "core/core_timing.h"
17
18MICROPROFILE_DEFINE(OpusDecoder, "Audio", "DSP_OpusDecoder", MP_RGB(60, 19, 97));
19
20namespace AudioCore::ADSP::OpusDecoder {
21
22namespace {
23constexpr size_t OpusStreamCountMax = 255;
24
25bool IsValidChannelCount(u32 channel_count) {
26 return channel_count == 1 || channel_count == 2;
27}
28
29bool IsValidMultiStreamChannelCount(u32 channel_count) {
30 return channel_count <= OpusStreamCountMax;
31}
32
33bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) {
34 return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
35 sterero_stream_count > 0 && sterero_stream_count <= total_stream_count;
36}
37} // namespace
38
39OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
40 init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
41}
42
43OpusDecoder::~OpusDecoder() {
44 if (!running) {
45 init_thread.request_stop();
46 return;
47 }
48
49 // Shutdown the thread
50 Send(Direction::DSP, Message::Shutdown);
51 auto msg = Receive(Direction::Host);
52 ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
53 Message::ShutdownOK, msg);
54 main_thread.request_stop();
55 main_thread.join();
56 running = false;
57}
58
59void OpusDecoder::Send(Direction dir, u32 message) {
60 mailbox.Send(dir, std::move(message));
61}
62
63u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
64 return mailbox.Receive(dir, stop_token);
65}
66
67void OpusDecoder::Init(std::stop_token stop_token) {
68 Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
69
70 if (Receive(Direction::DSP, stop_token) != Message::Start) {
71 LOG_ERROR(Service_Audio,
72 "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
73 return;
74 }
75 main_thread = std::jthread([this](std::stop_token st) { Main(st); });
76 running = true;
77 Send(Direction::Host, Message::StartOK);
78}
79
80void OpusDecoder::Main(std::stop_token stop_token) {
81 Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
82
83 while (!stop_token.stop_requested()) {
84 auto msg = Receive(Direction::DSP, stop_token);
85 switch (msg) {
86 case Shutdown:
87 Send(Direction::Host, Message::ShutdownOK);
88 return;
89
90 case GetWorkBufferSize: {
91 auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
92
93 ASSERT(IsValidChannelCount(channel_count));
94
95 shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
96 Send(Direction::Host, Message::GetWorkBufferSizeOK);
97 } break;
98
99 case InitializeDecodeObject: {
100 auto buffer = shared_memory->host_send_data[0];
101 auto buffer_size = shared_memory->host_send_data[1];
102 auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
103 auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
104
105 ASSERT(sample_rate >= 0);
106 ASSERT(IsValidChannelCount(channel_count));
107 ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
108
109 auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
110 shared_memory->dsp_return_data[0] =
111 decoder_object.InitializeDecoder(sample_rate, channel_count);
112
113 Send(Direction::Host, Message::InitializeDecodeObjectOK);
114 } break;
115
116 case ShutdownDecodeObject: {
117 auto buffer = shared_memory->host_send_data[0];
118 [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
119
120 auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
121 shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
122
123 Send(Direction::Host, Message::ShutdownDecodeObjectOK);
124 } break;
125
126 case DecodeInterleaved: {
127 auto start_time = system.CoreTiming().GetGlobalTimeUs();
128
129 auto buffer = shared_memory->host_send_data[0];
130 auto input_data = shared_memory->host_send_data[1];
131 auto input_data_size = shared_memory->host_send_data[2];
132 auto output_data = shared_memory->host_send_data[3];
133 auto output_data_size = shared_memory->host_send_data[4];
134 auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
135 auto reset_requested = shared_memory->host_send_data[6];
136
137 u32 decoded_samples{0};
138
139 auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
140 s32 error_code{OPUS_OK};
141 if (reset_requested) {
142 error_code = decoder_object.ResetDecoder();
143 }
144
145 if (error_code == OPUS_OK) {
146 error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
147 input_data, input_data_size);
148 }
149
150 if (error_code == OPUS_OK) {
151 if (final_range && decoder_object.GetFinalRange() != final_range) {
152 error_code = OPUS_INVALID_PACKET;
153 }
154 }
155
156 auto end_time = system.CoreTiming().GetGlobalTimeUs();
157 shared_memory->dsp_return_data[0] = error_code;
158 shared_memory->dsp_return_data[1] = decoded_samples;
159 shared_memory->dsp_return_data[2] = (end_time - start_time).count();
160
161 Send(Direction::Host, Message::DecodeInterleavedOK);
162 } break;
163
164 case MapMemory: {
165 [[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
166 [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
167 Send(Direction::Host, Message::MapMemoryOK);
168 } break;
169
170 case UnmapMemory: {
171 [[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
172 [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
173 Send(Direction::Host, Message::UnmapMemoryOK);
174 } break;
175
176 case GetWorkBufferSizeForMultiStream: {
177 auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
178 auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
179
180 ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
181
182 shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
183 total_stream_count, stereo_stream_count);
184 Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
185 } break;
186
187 case InitializeMultiStreamDecodeObject: {
188 auto buffer = shared_memory->host_send_data[0];
189 auto buffer_size = shared_memory->host_send_data[1];
190 auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
191 auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
192 auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
193 auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
194 // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
195 // mappings, but [6] is never set, and there is not enough room in the argument data for
196 // more than 40 channels, when 255 are possible.
197 // It also means the mapping values are undefined, though likely always 0,
198 // and the mappings given by the game are ignored. The mappings are copied to this
199 // dedicated buffer host side, so let's do as intended.
200 auto mappings = shared_memory->channel_mapping.data();
201
202 ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
203 ASSERT(sample_rate >= 0);
204 ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
205 total_stream_count, stereo_stream_count));
206
207 auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
208 shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder(
209 sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings);
210
211 Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
212 } break;
213
214 case ShutdownMultiStreamDecodeObject: {
215 auto buffer = shared_memory->host_send_data[0];
216 [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
217
218 auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
219 shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
220
221 Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
222 } break;
223
224 case DecodeInterleavedForMultiStream: {
225 auto start_time = system.CoreTiming().GetGlobalTimeUs();
226
227 auto buffer = shared_memory->host_send_data[0];
228 auto input_data = shared_memory->host_send_data[1];
229 auto input_data_size = shared_memory->host_send_data[2];
230 auto output_data = shared_memory->host_send_data[3];
231 auto output_data_size = shared_memory->host_send_data[4];
232 auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
233 auto reset_requested = shared_memory->host_send_data[6];
234
235 u32 decoded_samples{0};
236
237 auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
238 s32 error_code{OPUS_OK};
239 if (reset_requested) {
240 error_code = decoder_object.ResetDecoder();
241 }
242
243 if (error_code == OPUS_OK) {
244 error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
245 input_data, input_data_size);
246 }
247
248 if (error_code == OPUS_OK) {
249 if (final_range && decoder_object.GetFinalRange() != final_range) {
250 error_code = OPUS_INVALID_PACKET;
251 }
252 }
253
254 auto end_time = system.CoreTiming().GetGlobalTimeUs();
255 shared_memory->dsp_return_data[0] = error_code;
256 shared_memory->dsp_return_data[1] = decoded_samples;
257 shared_memory->dsp_return_data[2] = (end_time - start_time).count();
258
259 Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
260 } break;
261
262 default:
263 LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
264 continue;
265 }
266 }
267}
268
269} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.h b/src/audio_core/adsp/apps/opus/opus_decoder.h
new file mode 100644
index 000000000..fcb89bb40
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decoder.h
@@ -0,0 +1,92 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <thread>
8
9#include "audio_core/adsp/apps/opus/shared_memory.h"
10#include "audio_core/adsp/mailbox.h"
11#include "common/common_types.h"
12
13namespace Core {
14class System;
15} // namespace Core
16
17namespace AudioCore::ADSP::OpusDecoder {
18
19enum Message : u32 {
20 Invalid = 0,
21 Start = 1,
22 Shutdown = 2,
23 StartOK = 11,
24 ShutdownOK = 12,
25 GetWorkBufferSize = 21,
26 InitializeDecodeObject = 22,
27 ShutdownDecodeObject = 23,
28 DecodeInterleaved = 24,
29 MapMemory = 25,
30 UnmapMemory = 26,
31 GetWorkBufferSizeForMultiStream = 27,
32 InitializeMultiStreamDecodeObject = 28,
33 ShutdownMultiStreamDecodeObject = 29,
34 DecodeInterleavedForMultiStream = 30,
35
36 GetWorkBufferSizeOK = 41,
37 InitializeDecodeObjectOK = 42,
38 ShutdownDecodeObjectOK = 43,
39 DecodeInterleavedOK = 44,
40 MapMemoryOK = 45,
41 UnmapMemoryOK = 46,
42 GetWorkBufferSizeForMultiStreamOK = 47,
43 InitializeMultiStreamDecodeObjectOK = 48,
44 ShutdownMultiStreamDecodeObjectOK = 49,
45 DecodeInterleavedForMultiStreamOK = 50,
46};
47
48/**
49 * The AudioRenderer application running on the ADSP.
50 */
51class OpusDecoder {
52public:
53 explicit OpusDecoder(Core::System& system);
54 ~OpusDecoder();
55
56 bool IsRunning() const noexcept {
57 return running;
58 }
59
60 void Send(Direction dir, u32 message);
61 u32 Receive(Direction dir, std::stop_token stop_token = {});
62
63 void SetSharedMemory(SharedMemory& shared_memory_) {
64 shared_memory = &shared_memory_;
65 }
66
67private:
68 /**
69 * Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
70 */
71 void Init(std::stop_token stop_token);
72 /**
73 * Main OpusDecoder thread, responsible for processing the incoming Opus packets.
74 */
75 void Main(std::stop_token stop_token);
76
77 /// Core system
78 Core::System& system;
79 /// Mailbox to communicate messages with the host, drives the main thread
80 Mailbox mailbox;
81 /// Init thread
82 std::jthread init_thread{};
83 /// Main thread
84 std::jthread main_thread{};
85 /// The current state
86 bool running{};
87 /// Structure shared with the host, input data set by the host before sending a mailbox message,
88 /// and the responses are written back by the OpusDecoder.
89 SharedMemory* shared_memory{};
90};
91
92} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
new file mode 100644
index 000000000..f6d362e68
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
@@ -0,0 +1,111 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
5#include "common/assert.h"
6
7namespace AudioCore::ADSP::OpusDecoder {
8
9namespace {
10bool IsValidChannelCount(u32 channel_count) {
11 return channel_count == 1 || channel_count == 2;
12}
13
14bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
15 return total_stream_count > 0 && stereo_stream_count > 0 &&
16 stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count);
17}
18} // namespace
19
20u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
21 u32 stereo_stream_count) {
22 if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
23 return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
24 opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
25 }
26 return 0;
27}
28
29OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
30 auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
31 auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
32
33 if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
34 if (!new_decoder->initialized ||
35 (new_decoder->initialized && new_decoder->self == comparison)) {
36 new_decoder->state_valid = true;
37 }
38 } else {
39 new_decoder->initialized = false;
40 new_decoder->state_valid = true;
41 }
42 return *new_decoder;
43}
44
45s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
46 u32 channel_count, u32 stereo_stream_count,
47 u8* mappings) {
48 if (!state_valid) {
49 return OPUS_INVALID_STATE;
50 }
51
52 if (initialized) {
53 return OPUS_OK;
54 }
55
56 // See OpusDecodeObject::InitializeDecoder for an explanation of this
57 decoder = (LibOpusMSDecoder*)(this + 1);
58 s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
59 stereo_stream_count, mappings);
60 if (ret == OPUS_OK) {
61 magic = DecodeMultiStreamObjectMagic;
62 initialized = true;
63 state_valid = true;
64 self = this;
65 final_range = 0;
66 }
67 return ret;
68}
69
70s32 OpusMultiStreamDecodeObject::Shutdown() {
71 if (!state_valid) {
72 return OPUS_INVALID_STATE;
73 }
74
75 if (initialized) {
76 magic = 0x0;
77 initialized = false;
78 state_valid = false;
79 self = nullptr;
80 final_range = 0;
81 decoder = nullptr;
82 }
83 return OPUS_OK;
84}
85
86s32 OpusMultiStreamDecodeObject::ResetDecoder() {
87 return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
88}
89
90s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
91 u64 output_data_size, u64 input_data, u64 input_data_size) {
92 ASSERT(initialized);
93 out_sample_count = 0;
94
95 if (!state_valid) {
96 return OPUS_INVALID_STATE;
97 }
98
99 auto ret_code_or_samples = opus_multistream_decode(
100 decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
101 reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
102
103 if (ret_code_or_samples < OPUS_OK) {
104 return ret_code_or_samples;
105 }
106
107 out_sample_count = ret_code_or_samples;
108 return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
109}
110
111} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h
new file mode 100644
index 000000000..93558ded5
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h
@@ -0,0 +1,39 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <opus_multistream.h>
7
8#include "common/common_types.h"
9
10namespace AudioCore::ADSP::OpusDecoder {
11using LibOpusMSDecoder = ::OpusMSDecoder;
12static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF;
13
14class OpusMultiStreamDecodeObject {
15public:
16 static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count);
17 static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2);
18
19 s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count,
20 u32 stereo_stream_count, u8* mappings);
21 s32 Shutdown();
22 s32 ResetDecoder();
23 s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
24 u64 input_data_size);
25 u32 GetFinalRange() const noexcept {
26 return final_range;
27 }
28
29private:
30 u32 magic;
31 bool initialized;
32 bool state_valid;
33 OpusMultiStreamDecodeObject* self;
34 u32 final_range;
35 LibOpusMSDecoder* decoder;
36};
37static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>);
38
39} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/shared_memory.h b/src/audio_core/adsp/apps/opus/shared_memory.h
new file mode 100644
index 000000000..c696731ed
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/shared_memory.h
@@ -0,0 +1,17 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace AudioCore::ADSP::OpusDecoder {
10
11struct SharedMemory {
12 std::array<u8, 0x100> channel_mapping{};
13 std::array<u64, 16> host_send_data{};
14 std::array<u64, 16> dsp_return_data{};
15};
16
17} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/mailbox.h b/src/audio_core/adsp/mailbox.h
new file mode 100644
index 000000000..1dd40ebfa
--- /dev/null
+++ b/src/audio_core/adsp/mailbox.h
@@ -0,0 +1,60 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "common/bounded_threadsafe_queue.h"
9#include "common/common_types.h"
10
11namespace AudioCore::ADSP {
12
13enum class AppMailboxId : u32 {
14 Invalid = 0,
15 AudioRenderer = 50,
16 AudioRendererMemoryMapUnmap = 51,
17};
18
19enum class Direction : u32 {
20 Host,
21 DSP,
22};
23
24class Mailbox {
25public:
26 void Initialize(AppMailboxId id_) {
27 Reset();
28 id = id_;
29 }
30
31 AppMailboxId Id() const noexcept {
32 return id;
33 }
34
35 void Send(Direction dir, u32 message) {
36 auto& queue = dir == Direction::Host ? host_queue : adsp_queue;
37 queue.EmplaceWait(message);
38 }
39
40 u32 Receive(Direction dir, std::stop_token stop_token = {}) {
41 auto& queue = dir == Direction::Host ? host_queue : adsp_queue;
42 return queue.PopWait(stop_token);
43 }
44
45 void Reset() {
46 id = AppMailboxId::Invalid;
47 u32 t{};
48 while (host_queue.TryPop(t)) {
49 }
50 while (adsp_queue.TryPop(t)) {
51 }
52 }
53
54private:
55 AppMailboxId id{0};
56 Common::SPSCQueue<u32> host_queue;
57 Common::SPSCQueue<u32> adsp_queue;
58};
59
60} // namespace AudioCore::ADSP
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 703ef4494..fcaab2b32 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -11,7 +11,7 @@ namespace AudioCore {
11AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} { 11AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} {
12 CreateSinks(); 12 CreateSinks();
13 // Must be created after the sinks 13 // Must be created after the sinks
14 adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink); 14 adsp = std::make_unique<ADSP::ADSP>(system, *output_sink);
15} 15}
16 16
17AudioCore ::~AudioCore() { 17AudioCore ::~AudioCore() {
@@ -43,7 +43,7 @@ Sink::Sink& AudioCore::GetInputSink() {
43 return *input_sink; 43 return *input_sink;
44} 44}
45 45
46AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() { 46ADSP::ADSP& AudioCore::ADSP() {
47 return *adsp; 47 return *adsp;
48} 48}
49 49
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index ea047773e..e4e27fc66 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -5,8 +5,8 @@
5 5
6#include <memory> 6#include <memory>
7 7
8#include "audio_core/adsp/adsp.h"
8#include "audio_core/audio_manager.h" 9#include "audio_core/audio_manager.h"
9#include "audio_core/renderer/adsp/adsp.h"
10#include "audio_core/sink/sink.h" 10#include "audio_core/sink/sink.h"
11 11
12namespace Core { 12namespace Core {
@@ -55,7 +55,7 @@ public:
55 * 55 *
56 * @return Ref to the ADSP. 56 * @return Ref to the ADSP.
57 */ 57 */
58 AudioRenderer::ADSP::ADSP& GetADSP(); 58 ADSP::ADSP& ADSP();
59 59
60private: 60private:
61 /** 61 /**
@@ -70,7 +70,7 @@ private:
70 /// Sink used for audio input 70 /// Sink used for audio input
71 std::unique_ptr<Sink::Sink> input_sink; 71 std::unique_ptr<Sink::Sink> input_sink;
72 /// The ADSP in the sysmodule 72 /// The ADSP in the sysmodule
73 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; 73 std::unique_ptr<ADSP::ADSP> adsp;
74}; 74};
75 75
76} // namespace AudioCore 76} // namespace AudioCore
diff --git a/src/audio_core/audio_event.cpp b/src/audio_core/audio_event.cpp
index d15568e1f..c23ef0990 100644
--- a/src/audio_core/audio_event.cpp
+++ b/src/audio_core/audio_event.cpp
@@ -20,7 +20,6 @@ size_t Event::GetManagerIndex(const Type type) const {
20 default: 20 default:
21 UNREACHABLE(); 21 UNREACHABLE();
22 } 22 }
23 return 3;
24} 23}
25 24
26void Event::SetAudioEvent(const Type type, const bool signalled) { 25void Event::SetAudioEvent(const Type type, const bool signalled) {
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp
index 3dfb613cb..a3667524f 100644
--- a/src/audio_core/audio_in_manager.cpp
+++ b/src/audio_core/audio_in_manager.cpp
@@ -73,7 +73,7 @@ void Manager::BufferReleaseAndRegister() {
73 } 73 }
74} 74}
75 75
76u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, 76u32 Manager::GetDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names,
77 [[maybe_unused]] const u32 max_count, 77 [[maybe_unused]] const u32 max_count,
78 [[maybe_unused]] const bool filter) { 78 [[maybe_unused]] const bool filter) {
79 std::scoped_lock l{mutex}; 79 std::scoped_lock l{mutex};
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h
index 8a519df99..5c4614cd1 100644
--- a/src/audio_core/audio_in_manager.h
+++ b/src/audio_core/audio_in_manager.h
@@ -65,8 +65,8 @@ public:
65 * 65 *
66 * @return Number of names written. 66 * @return Number of names written.
67 */ 67 */
68 u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, 68 u32 GetDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names, u32 max_count,
69 u32 max_count, bool filter); 69 bool filter);
70 70
71 /// Core system 71 /// Core system
72 Core::System& system; 72 Core::System& system;
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp
index f22821360..316ea7c81 100644
--- a/src/audio_core/audio_out_manager.cpp
+++ b/src/audio_core/audio_out_manager.cpp
@@ -73,7 +73,7 @@ void Manager::BufferReleaseAndRegister() {
73} 73}
74 74
75u32 Manager::GetAudioOutDeviceNames( 75u32 Manager::GetAudioOutDeviceNames(
76 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { 76 std::vector<Renderer::AudioDevice::AudioDeviceName>& names) const {
77 names.emplace_back("DeviceOut"); 77 names.emplace_back("DeviceOut");
78 return 1; 78 return 1;
79} 79}
diff --git a/src/audio_core/audio_out_manager.h b/src/audio_core/audio_out_manager.h
index 1e05ec5ed..c3e445d5d 100644
--- a/src/audio_core/audio_out_manager.h
+++ b/src/audio_core/audio_out_manager.h
@@ -61,8 +61,7 @@ public:
61 * @param names - Output container to write names to. 61 * @param names - Output container to write names to.
62 * @return Number of names written. 62 * @return Number of names written.
63 */ 63 */
64 u32 GetAudioOutDeviceNames( 64 u32 GetAudioOutDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names) const;
65 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const;
66 65
67 /// Core system 66 /// Core system
68 Core::System& system; 67 Core::System& system;
diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp
index 320715727..3c53e3afd 100644
--- a/src/audio_core/audio_render_manager.cpp
+++ b/src/audio_core/audio_render_manager.cpp
@@ -6,7 +6,7 @@
6#include "audio_core/common/feature_support.h" 6#include "audio_core/common/feature_support.h"
7#include "core/core.h" 7#include "core/core.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11Manager::Manager(Core::System& system_) 11Manager::Manager(Core::System& system_)
12 : system{system_}, system_manager{std::make_unique<SystemManager>(system)} { 12 : system{system_}, system_manager{std::make_unique<SystemManager>(system)} {
@@ -67,4 +67,4 @@ bool Manager::RemoveSystem(System& system_) {
67 return system_manager->Remove(system_); 67 return system_manager->Remove(system_);
68} 68}
69 69
70} // namespace AudioCore::AudioRenderer 70} // namespace AudioCore::Renderer
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h
index fffa5944d..45537b270 100644
--- a/src/audio_core/audio_render_manager.h
+++ b/src/audio_core/audio_render_manager.h
@@ -20,7 +20,7 @@ class System;
20namespace AudioCore { 20namespace AudioCore {
21struct AudioRendererParameterInternal; 21struct AudioRendererParameterInternal;
22 22
23namespace AudioRenderer { 23namespace Renderer {
24/** 24/**
25 * Wrapper for the audio system manager, handles service calls. 25 * Wrapper for the audio system manager, handles service calls.
26 */ 26 */
@@ -101,5 +101,5 @@ private:
101 std::unique_ptr<SystemManager> system_manager{}; 101 std::unique_ptr<SystemManager> system_manager{};
102}; 102};
103 103
104} // namespace AudioRenderer 104} // namespace Renderer
105} // namespace AudioCore 105} // namespace AudioCore
diff --git a/src/audio_core/common/audio_renderer_parameter.h b/src/audio_core/common/audio_renderer_parameter.h
index 8c7892bcf..6c4e9fdc6 100644
--- a/src/audio_core/common/audio_renderer_parameter.h
+++ b/src/audio_core/common/audio_renderer_parameter.h
@@ -51,10 +51,10 @@ struct AudioRendererSystemContext {
51 s32 session_id; 51 s32 session_id;
52 s8 channels; 52 s8 channels;
53 s16 mix_buffer_count; 53 s16 mix_buffer_count;
54 AudioRenderer::BehaviorInfo* behavior; 54 Renderer::BehaviorInfo* behavior;
55 std::span<s32> depop_buffer; 55 std::span<s32> depop_buffer;
56 AudioRenderer::UpsamplerManager* upsampler_manager; 56 Renderer::UpsamplerManager* upsampler_manager;
57 AudioRenderer::MemoryPoolInfo* memory_pool_info; 57 Renderer::MemoryPoolInfo* memory_pool_info;
58}; 58};
59 59
60} // namespace AudioCore 60} // namespace AudioCore
diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp
new file mode 100644
index 000000000..5b23fce14
--- /dev/null
+++ b/src/audio_core/opus/decoder.cpp
@@ -0,0 +1,179 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/opus/decoder.h"
5#include "audio_core/opus/hardware_opus.h"
6#include "audio_core/opus/parameters.h"
7#include "common/alignment.h"
8#include "common/swap.h"
9#include "core/core.h"
10
11namespace AudioCore::OpusDecoder {
12using namespace Service::Audio;
13namespace {
14OpusPacketHeader ReverseHeader(OpusPacketHeader header) {
15 OpusPacketHeader out;
16 out.size = Common::swap32(header.size);
17 out.final_range = Common::swap32(header.final_range);
18 return out;
19}
20} // namespace
21
22OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_)
23 : system{system_}, hardware_opus{hardware_opus_} {}
24
25OpusDecoder::~OpusDecoder() {
26 if (decode_object_initialized) {
27 hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size);
28 }
29}
30
31Result OpusDecoder::Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
32 u64 transfer_memory_size) {
33 auto frame_size{params.use_large_frame_size ? 5760 : 1920};
34 shared_buffer_size = transfer_memory_size;
35 shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
36 shared_memory_mapped = true;
37
38 buffer_size =
39 Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
40
41 out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
42 size_t in_data_size{0x600u};
43 in_data = {out_data.data() - in_data_size, in_data_size};
44
45 ON_RESULT_FAILURE {
46 if (shared_memory_mapped) {
47 shared_memory_mapped = false;
48 ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
49 }
50 };
51
52 R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count,
53 shared_buffer.get(), shared_buffer_size));
54
55 sample_rate = params.sample_rate;
56 channel_count = params.channel_count;
57 use_large_frame_size = params.use_large_frame_size;
58 decode_object_initialized = true;
59 R_SUCCEED();
60}
61
62Result OpusDecoder::Initialize(OpusMultiStreamParametersEx& params,
63 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
64 auto frame_size{params.use_large_frame_size ? 5760 : 1920};
65 shared_buffer_size = transfer_memory_size;
66 shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
67 shared_memory_mapped = true;
68
69 buffer_size =
70 Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
71
72 out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
73 size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)};
74 in_data = {out_data.data() - in_data_size, in_data_size};
75
76 ON_RESULT_FAILURE {
77 if (shared_memory_mapped) {
78 shared_memory_mapped = false;
79 ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
80 }
81 };
82
83 R_TRY(hardware_opus.InitializeMultiStreamDecodeObject(
84 params.sample_rate, params.channel_count, params.total_stream_count,
85 params.stereo_stream_count, params.mappings.data(), shared_buffer.get(),
86 shared_buffer_size));
87
88 sample_rate = params.sample_rate;
89 channel_count = params.channel_count;
90 total_stream_count = params.total_stream_count;
91 stereo_stream_count = params.stereo_stream_count;
92 use_large_frame_size = params.use_large_frame_size;
93 decode_object_initialized = true;
94 R_SUCCEED();
95}
96
97Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken,
98 u32* out_sample_count, std::span<const u8> input_data,
99 std::span<u8> output_data, bool reset) {
100 u32 out_samples;
101 u64 time_taken{};
102
103 R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
104
105 auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
106 OpusPacketHeader header{ReverseHeader(*header_p)};
107
108 R_UNLESS(in_data.size_bytes() >= header.size &&
109 header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
110 ResultBufferTooSmall);
111
112 if (!shared_memory_mapped) {
113 R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
114 shared_memory_mapped = true;
115 }
116
117 std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
118
119 R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(),
120 channel_count, in_data.data(), header.size,
121 shared_buffer.get(), time_taken, reset));
122
123 std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
124
125 *out_data_size = header.size + sizeof(OpusPacketHeader);
126 *out_sample_count = out_samples;
127 if (out_time_taken) {
128 *out_time_taken = time_taken / 1000;
129 }
130 R_SUCCEED();
131}
132
133Result OpusDecoder::SetContext([[maybe_unused]] std::span<const u8> context) {
134 R_SUCCEED_IF(shared_memory_mapped);
135 shared_memory_mapped = true;
136 R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
137}
138
139Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
140 u32* out_sample_count,
141 std::span<const u8> input_data,
142 std::span<u8> output_data, bool reset) {
143 u32 out_samples;
144 u64 time_taken{};
145
146 R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
147
148 auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
149 OpusPacketHeader header{ReverseHeader(*header_p)};
150
151 LOG_ERROR(Service_Audio, "header size 0x{:X} input data size 0x{:X} in_data size 0x{:X}",
152 header.size, input_data.size_bytes(), in_data.size_bytes());
153
154 R_UNLESS(in_data.size_bytes() >= header.size &&
155 header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
156 ResultBufferTooSmall);
157
158 if (!shared_memory_mapped) {
159 R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
160 shared_memory_mapped = true;
161 }
162
163 std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
164
165 R_TRY(hardware_opus.DecodeInterleavedForMultiStream(
166 out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(),
167 header.size, shared_buffer.get(), time_taken, reset));
168
169 std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
170
171 *out_data_size = header.size + sizeof(OpusPacketHeader);
172 *out_sample_count = out_samples;
173 if (out_time_taken) {
174 *out_time_taken = time_taken / 1000;
175 }
176 R_SUCCEED();
177}
178
179} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder.h b/src/audio_core/opus/decoder.h
new file mode 100644
index 000000000..d08d8a4a4
--- /dev/null
+++ b/src/audio_core/opus/decoder.h
@@ -0,0 +1,53 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/opus/parameters.h"
9#include "common/common_types.h"
10#include "core/hle/kernel/k_transfer_memory.h"
11#include "core/hle/service/audio/errors.h"
12
13namespace Core {
14class System;
15}
16
17namespace AudioCore::OpusDecoder {
18class HardwareOpus;
19
20class OpusDecoder {
21public:
22 explicit OpusDecoder(Core::System& system, HardwareOpus& hardware_opus_);
23 ~OpusDecoder();
24
25 Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
26 u64 transfer_memory_size);
27 Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
28 u64 transfer_memory_size);
29 Result DecodeInterleaved(u32* out_data_size, u64* out_time_taken, u32* out_sample_count,
30 std::span<const u8> input_data, std::span<u8> output_data, bool reset);
31 Result SetContext([[maybe_unused]] std::span<const u8> context);
32 Result DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
33 u32* out_sample_count, std::span<const u8> input_data,
34 std::span<u8> output_data, bool reset);
35
36private:
37 Core::System& system;
38 HardwareOpus& hardware_opus;
39 std::unique_ptr<u8[]> shared_buffer{};
40 u64 shared_buffer_size;
41 std::span<u8> in_data{};
42 std::span<u8> out_data{};
43 u64 buffer_size{};
44 s32 sample_rate{};
45 s32 channel_count{};
46 bool use_large_frame_size{false};
47 s32 total_stream_count{};
48 s32 stereo_stream_count{};
49 bool shared_memory_mapped{false};
50 bool decode_object_initialized{false};
51};
52
53} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder_manager.cpp b/src/audio_core/opus/decoder_manager.cpp
new file mode 100644
index 000000000..4a5382973
--- /dev/null
+++ b/src/audio_core/opus/decoder_manager.cpp
@@ -0,0 +1,102 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/adsp/apps/opus/opus_decoder.h"
5#include "audio_core/opus/decoder_manager.h"
6#include "common/alignment.h"
7#include "core/core.h"
8
9namespace AudioCore::OpusDecoder {
10using namespace Service::Audio;
11
12namespace {
13bool IsValidChannelCount(u32 channel_count) {
14 return channel_count == 1 || channel_count == 2;
15}
16
17bool IsValidMultiStreamChannelCount(u32 channel_count) {
18 return channel_count > 0 && channel_count <= OpusStreamCountMax;
19}
20
21bool IsValidSampleRate(u32 sample_rate) {
22 return sample_rate == 8'000 || sample_rate == 12'000 || sample_rate == 16'000 ||
23 sample_rate == 24'000 || sample_rate == 48'000;
24}
25
26bool IsValidStreamCount(u32 channel_count, u32 total_stream_count, u32 stereo_stream_count) {
27 return total_stream_count > 0 && stereo_stream_count > 0 &&
28 stereo_stream_count <= total_stream_count &&
29 total_stream_count + stereo_stream_count <= channel_count;
30}
31
32} // namespace
33
34OpusDecoderManager::OpusDecoderManager(Core::System& system_)
35 : system{system_}, hardware_opus{system} {
36 for (u32 i = 0; i < MaxChannels; i++) {
37 required_workbuffer_sizes[i] = hardware_opus.GetWorkBufferSize(1 + i);
38 }
39}
40
41Result OpusDecoderManager::GetWorkBufferSize(OpusParameters& params, u64& out_size) {
42 OpusParametersEx ex{
43 .sample_rate = params.sample_rate,
44 .channel_count = params.channel_count,
45 .use_large_frame_size = false,
46 };
47 R_RETURN(GetWorkBufferSizeExEx(ex, out_size));
48}
49
50Result OpusDecoderManager::GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size) {
51 R_RETURN(GetWorkBufferSizeExEx(params, out_size));
52}
53
54Result OpusDecoderManager::GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size) {
55 R_UNLESS(IsValidChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
56 R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
57
58 auto work_buffer_size{required_workbuffer_sizes[params.channel_count - 1]};
59 auto frame_size{params.use_large_frame_size ? 5760 : 1920};
60 work_buffer_size +=
61 Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
62 out_size = work_buffer_size + 0x600;
63 R_SUCCEED();
64}
65
66Result OpusDecoderManager::GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params,
67 u64& out_size) {
68 OpusMultiStreamParametersEx ex{
69 .sample_rate = params.sample_rate,
70 .channel_count = params.channel_count,
71 .total_stream_count = params.total_stream_count,
72 .stereo_stream_count = params.stereo_stream_count,
73 .use_large_frame_size = false,
74 .mappings = {},
75 };
76 R_RETURN(GetWorkBufferSizeForMultiStreamExEx(ex, out_size));
77}
78
79Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params,
80 u64& out_size) {
81 R_RETURN(GetWorkBufferSizeForMultiStreamExEx(params, out_size));
82}
83
84Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params,
85 u64& out_size) {
86 R_UNLESS(IsValidMultiStreamChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
87 R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
88 R_UNLESS(IsValidStreamCount(params.channel_count, params.total_stream_count,
89 params.stereo_stream_count),
90 ResultInvalidOpusSampleRate);
91
92 auto work_buffer_size{hardware_opus.GetWorkBufferSizeForMultiStream(
93 params.total_stream_count, params.stereo_stream_count)};
94 auto frame_size{params.use_large_frame_size ? 5760 : 1920};
95 work_buffer_size += Common::AlignUp(1500 * params.total_stream_count, 64);
96 work_buffer_size +=
97 Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
98 out_size = work_buffer_size;
99 R_SUCCEED();
100}
101
102} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder_manager.h b/src/audio_core/opus/decoder_manager.h
new file mode 100644
index 000000000..466e1967b
--- /dev/null
+++ b/src/audio_core/opus/decoder_manager.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/opus/hardware_opus.h"
7#include "audio_core/opus/parameters.h"
8#include "common/common_types.h"
9#include "core/hle/service/audio/errors.h"
10
11namespace Core {
12class System;
13}
14
15namespace AudioCore::OpusDecoder {
16
17class OpusDecoderManager {
18public:
19 OpusDecoderManager(Core::System& system);
20
21 HardwareOpus& GetHardwareOpus() {
22 return hardware_opus;
23 }
24
25 Result GetWorkBufferSize(OpusParameters& params, u64& out_size);
26 Result GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size);
27 Result GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size);
28 Result GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, u64& out_size);
29 Result GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, u64& out_size);
30 Result GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, u64& out_size);
31
32private:
33 Core::System& system;
34 HardwareOpus hardware_opus;
35 std::array<u64, MaxChannels> required_workbuffer_sizes{};
36};
37
38} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp
new file mode 100644
index 000000000..d6544dcb0
--- /dev/null
+++ b/src/audio_core/opus/hardware_opus.cpp
@@ -0,0 +1,241 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5
6#include "audio_core/audio_core.h"
7#include "audio_core/opus/hardware_opus.h"
8#include "core/core.h"
9
10namespace AudioCore::OpusDecoder {
11namespace {
12using namespace Service::Audio;
13
14static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) {
15 s32 error{static_cast<s32>(error_code)};
16 ASSERT(error <= OPUS_OK);
17 switch (error) {
18 case OPUS_ALLOC_FAIL:
19 R_THROW(ResultLibOpusAllocFail);
20 case OPUS_INVALID_STATE:
21 R_THROW(ResultLibOpusInvalidState);
22 case OPUS_UNIMPLEMENTED:
23 R_THROW(ResultLibOpusUnimplemented);
24 case OPUS_INVALID_PACKET:
25 R_THROW(ResultLibOpusInvalidPacket);
26 case OPUS_INTERNAL_ERROR:
27 R_THROW(ResultLibOpusInternalError);
28 case OPUS_BUFFER_TOO_SMALL:
29 R_THROW(ResultBufferTooSmall);
30 case OPUS_BAD_ARG:
31 R_THROW(ResultLibOpusBadArg);
32 case OPUS_OK:
33 R_RETURN(ResultSuccess);
34 }
35 UNREACHABLE();
36}
37
38} // namespace
39
40HardwareOpus::HardwareOpus(Core::System& system_)
41 : system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} {
42 opus_decoder.SetSharedMemory(shared_memory);
43}
44
45u64 HardwareOpus::GetWorkBufferSize(u32 channel) {
46 if (!opus_decoder.IsRunning()) {
47 return 0;
48 }
49 std::scoped_lock l{mutex};
50 shared_memory.host_send_data[0] = channel;
51 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::GetWorkBufferSize);
52 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
53 if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeOK) {
54 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
55 ADSP::OpusDecoder::Message::GetWorkBufferSizeOK, msg);
56 return 0;
57 }
58 return shared_memory.dsp_return_data[0];
59}
60
61u64 HardwareOpus::GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count) {
62 std::scoped_lock l{mutex};
63 shared_memory.host_send_data[0] = total_stream_count;
64 shared_memory.host_send_data[1] = stereo_stream_count;
65 opus_decoder.Send(ADSP::Direction::DSP,
66 ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStream);
67 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
68 if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK) {
69 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
70 ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK, msg);
71 return 0;
72 }
73 return shared_memory.dsp_return_data[0];
74}
75
76Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
77 u64 buffer_size) {
78 std::scoped_lock l{mutex};
79 shared_memory.host_send_data[0] = (u64)buffer;
80 shared_memory.host_send_data[1] = buffer_size;
81 shared_memory.host_send_data[2] = sample_rate;
82 shared_memory.host_send_data[3] = channel_count;
83
84 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::InitializeDecodeObject);
85 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
86 if (msg != ADSP::OpusDecoder::Message::InitializeDecodeObjectOK) {
87 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
88 ADSP::OpusDecoder::Message::InitializeDecodeObjectOK, msg);
89 R_THROW(ResultInvalidOpusDSPReturnCode);
90 }
91
92 R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
93}
94
95Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
96 u32 total_stream_count,
97 u32 stereo_stream_count, void* mappings,
98 void* buffer, u64 buffer_size) {
99 std::scoped_lock l{mutex};
100 shared_memory.host_send_data[0] = (u64)buffer;
101 shared_memory.host_send_data[1] = buffer_size;
102 shared_memory.host_send_data[2] = sample_rate;
103 shared_memory.host_send_data[3] = channel_count;
104 shared_memory.host_send_data[4] = total_stream_count;
105 shared_memory.host_send_data[5] = stereo_stream_count;
106
107 ASSERT(channel_count <= MaxChannels);
108 std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8));
109
110 opus_decoder.Send(ADSP::Direction::DSP,
111 ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObject);
112 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
113 if (msg != ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK) {
114 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
115 ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK, msg);
116 R_THROW(ResultInvalidOpusDSPReturnCode);
117 }
118
119 R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
120}
121
122Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
123 std::scoped_lock l{mutex};
124 shared_memory.host_send_data[0] = (u64)buffer;
125 shared_memory.host_send_data[1] = buffer_size;
126
127 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::ShutdownDecodeObject);
128 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
129 ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK,
130 "Expected Opus shutdown code {}, got {}",
131 ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
132
133 R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
134}
135
136Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
137 std::scoped_lock l{mutex};
138 shared_memory.host_send_data[0] = (u64)buffer;
139 shared_memory.host_send_data[1] = buffer_size;
140
141 opus_decoder.Send(ADSP::Direction::DSP,
142 ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObject);
143 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
144 ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK,
145 "Expected Opus shutdown code {}, got {}",
146 ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
147
148 R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
149}
150
151Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
152 u64 output_data_size, u32 channel_count, void* input_data,
153 u64 input_data_size, void* buffer, u64& out_time_taken,
154 bool reset) {
155 std::scoped_lock l{mutex};
156 shared_memory.host_send_data[0] = (u64)buffer;
157 shared_memory.host_send_data[1] = (u64)input_data;
158 shared_memory.host_send_data[2] = input_data_size;
159 shared_memory.host_send_data[3] = (u64)output_data;
160 shared_memory.host_send_data[4] = output_data_size;
161 shared_memory.host_send_data[5] = 0;
162 shared_memory.host_send_data[6] = reset;
163
164 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleaved);
165 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
166 if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedOK) {
167 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
168 ADSP::OpusDecoder::Message::DecodeInterleavedOK, msg);
169 R_THROW(ResultInvalidOpusDSPReturnCode);
170 }
171
172 auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
173 if (error_code == OPUS_OK) {
174 out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
175 out_time_taken = 1000 * shared_memory.dsp_return_data[2];
176 }
177 R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
178}
179
180Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
181 u64 output_data_size, u32 channel_count,
182 void* input_data, u64 input_data_size,
183 void* buffer, u64& out_time_taken,
184 bool reset) {
185 std::scoped_lock l{mutex};
186 shared_memory.host_send_data[0] = (u64)buffer;
187 shared_memory.host_send_data[1] = (u64)input_data;
188 shared_memory.host_send_data[2] = input_data_size;
189 shared_memory.host_send_data[3] = (u64)output_data;
190 shared_memory.host_send_data[4] = output_data_size;
191 shared_memory.host_send_data[5] = 0;
192 shared_memory.host_send_data[6] = reset;
193
194 opus_decoder.Send(ADSP::Direction::DSP,
195 ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
196 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
197 if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
198 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
199 ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
200 R_THROW(ResultInvalidOpusDSPReturnCode);
201 }
202
203 auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
204 if (error_code == OPUS_OK) {
205 out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
206 out_time_taken = 1000 * shared_memory.dsp_return_data[2];
207 }
208 R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
209}
210
211Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
212 std::scoped_lock l{mutex};
213 shared_memory.host_send_data[0] = (u64)buffer;
214 shared_memory.host_send_data[1] = buffer_size;
215
216 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
217 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
218 if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
219 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
220 ADSP::OpusDecoder::Message::MapMemoryOK, msg);
221 R_THROW(ResultInvalidOpusDSPReturnCode);
222 }
223 R_SUCCEED();
224}
225
226Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
227 std::scoped_lock l{mutex};
228 shared_memory.host_send_data[0] = (u64)buffer;
229 shared_memory.host_send_data[1] = buffer_size;
230
231 opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
232 auto msg = opus_decoder.Receive(ADSP::Direction::Host);
233 if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
234 LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
235 ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
236 R_THROW(ResultInvalidOpusDSPReturnCode);
237 }
238 R_SUCCEED();
239}
240
241} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h
new file mode 100644
index 000000000..7013a6b40
--- /dev/null
+++ b/src/audio_core/opus/hardware_opus.h
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7#include <opus.h>
8
9#include "audio_core/adsp/apps/opus/opus_decoder.h"
10#include "audio_core/adsp/apps/opus/shared_memory.h"
11#include "audio_core/adsp/mailbox.h"
12#include "core/hle/service/audio/errors.h"
13
14namespace AudioCore::OpusDecoder {
15class HardwareOpus {
16public:
17 HardwareOpus(Core::System& system);
18
19 u64 GetWorkBufferSize(u32 channel);
20 u64 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
21
22 Result InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
23 u64 buffer_size);
24 Result InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
25 u32 totaL_stream_count, u32 stereo_stream_count,
26 void* mappings, void* buffer, u64 buffer_size);
27 Result ShutdownDecodeObject(void* buffer, u64 buffer_size);
28 Result ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size);
29 Result DecodeInterleaved(u32& out_sample_count, void* output_data, u64 output_data_size,
30 u32 channel_count, void* input_data, u64 input_data_size, void* buffer,
31 u64& out_time_taken, bool reset);
32 Result DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
33 u64 output_data_size, u32 channel_count,
34 void* input_data, u64 input_data_size, void* buffer,
35 u64& out_time_taken, bool reset);
36 Result MapMemory(void* buffer, u64 buffer_size);
37 Result UnmapMemory(void* buffer, u64 buffer_size);
38
39private:
40 Core::System& system;
41 std::mutex mutex;
42 ADSP::OpusDecoder::OpusDecoder& opus_decoder;
43 ADSP::OpusDecoder::SharedMemory shared_memory;
44};
45} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/parameters.h b/src/audio_core/opus/parameters.h
new file mode 100644
index 000000000..4c54b2825
--- /dev/null
+++ b/src/audio_core/opus/parameters.h
@@ -0,0 +1,54 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace AudioCore::OpusDecoder {
10constexpr size_t OpusStreamCountMax = 255;
11constexpr size_t MaxChannels = 2;
12
13struct OpusParameters {
14 /* 0x00 */ u32 sample_rate;
15 /* 0x04 */ u32 channel_count;
16}; // size = 0x8
17static_assert(sizeof(OpusParameters) == 0x8, "OpusParameters has the wrong size!");
18
19struct OpusParametersEx {
20 /* 0x00 */ u32 sample_rate;
21 /* 0x04 */ u32 channel_count;
22 /* 0x08 */ bool use_large_frame_size;
23 /* 0x09 */ INSERT_PADDING_BYTES(7);
24}; // size = 0x10
25static_assert(sizeof(OpusParametersEx) == 0x10, "OpusParametersEx has the wrong size!");
26
27struct OpusMultiStreamParameters {
28 /* 0x00 */ u32 sample_rate;
29 /* 0x04 */ u32 channel_count;
30 /* 0x08 */ u32 total_stream_count;
31 /* 0x0C */ u32 stereo_stream_count;
32 /* 0x10 */ std::array<u8, OpusStreamCountMax + 1> mappings;
33}; // size = 0x110
34static_assert(sizeof(OpusMultiStreamParameters) == 0x110,
35 "OpusMultiStreamParameters has the wrong size!");
36
37struct OpusMultiStreamParametersEx {
38 /* 0x00 */ u32 sample_rate;
39 /* 0x04 */ u32 channel_count;
40 /* 0x08 */ u32 total_stream_count;
41 /* 0x0C */ u32 stereo_stream_count;
42 /* 0x10 */ bool use_large_frame_size;
43 /* 0x11 */ INSERT_PADDING_BYTES(7);
44 /* 0x18 */ std::array<u8, OpusStreamCountMax + 1> mappings;
45}; // size = 0x118
46static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118,
47 "OpusMultiStreamParametersEx has the wrong size!");
48
49struct OpusPacketHeader {
50 /* 0x00 */ u32 size;
51 /* 0x04 */ u32 final_range;
52}; // size = 0x8
53static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusPacketHeader has the wrong size!");
54} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp
deleted file mode 100644
index b1db31e93..000000000
--- a/src/audio_core/renderer/adsp/adsp.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/adsp.h"
5#include "audio_core/renderer/adsp/command_buffer.h"
6#include "audio_core/sink/sink.h"
7#include "common/logging/log.h"
8#include "core/core.h"
9#include "core/core_timing.h"
10#include "core/memory.h"
11
12namespace AudioCore::AudioRenderer::ADSP {
13
14ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
15 : system{system_}, memory{system.ApplicationMemory()}, sink{sink_} {}
16
17ADSP::~ADSP() {
18 ClearCommandBuffers();
19}
20
21State ADSP::GetState() const {
22 if (running) {
23 return State::Started;
24 }
25 return State::Stopped;
26}
27
28AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
29 return &render_mailbox;
30}
31
32void ADSP::ClearRemainCount(const u32 session_id) {
33 render_mailbox.ClearRemainCount(session_id);
34}
35
36u64 ADSP::GetSignalledTick() const {
37 return render_mailbox.GetSignalledTick();
38}
39
40u64 ADSP::GetTimeTaken() const {
41 return render_mailbox.GetRenderTimeTaken();
42}
43
44u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
45 return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
46}
47
48u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
49 return render_mailbox.GetRemainCommandCount(session_id);
50}
51
52void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
53 render_mailbox.SetCommandBuffer(session_id, command_buffer);
54}
55
56u64 ADSP::GetRenderingStartTick(const u32 session_id) {
57 return render_mailbox.GetSignalledTick() +
58 render_mailbox.GetCommandBuffer(session_id).render_time_taken;
59}
60
61bool ADSP::Start() {
62 if (running) {
63 return running;
64 }
65
66 running = true;
67 systems_active++;
68 audio_renderer = std::make_unique<AudioRenderer>(system);
69 audio_renderer->Start(&render_mailbox);
70 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
71 if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
72 LOG_ERROR(
73 Service_Audio,
74 "Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
75 }
76 return running;
77}
78
79void ADSP::Stop() {
80 systems_active--;
81 if (running && systems_active == 0) {
82 {
83 std::scoped_lock l{mailbox_lock};
84 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
85 if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
86 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
87 "message response from ADSP!");
88 }
89 }
90 audio_renderer->Stop();
91 running = false;
92 }
93}
94
95void ADSP::Signal() {
96 const auto signalled_tick{system.CoreTiming().GetClockTicks()};
97 render_mailbox.SetSignalledTick(signalled_tick);
98 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
99}
100
101void ADSP::Wait() {
102 std::scoped_lock l{mailbox_lock};
103 auto response{render_mailbox.HostWaitMessage()};
104 if (response != RenderMessage::AudioRenderer_RenderResponse) {
105 LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
106 static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
107 static_cast<u32>(response));
108 }
109
110 ClearCommandBuffers();
111}
112
113void ADSP::ClearCommandBuffers() {
114 render_mailbox.ClearCommandBuffers();
115}
116
117} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h
deleted file mode 100644
index f7a2f25e4..000000000
--- a/src/audio_core/renderer/adsp/adsp.h
+++ /dev/null
@@ -1,171 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <mutex>
8
9#include "audio_core/renderer/adsp/audio_renderer.h"
10#include "common/common_types.h"
11
12namespace Core {
13namespace Memory {
14class Memory;
15}
16class System;
17} // namespace Core
18
19namespace AudioCore {
20namespace Sink {
21class Sink;
22}
23
24namespace AudioRenderer::ADSP {
25struct CommandBuffer;
26
27enum class State {
28 Started,
29 Stopped,
30};
31
32/**
33 * Represents the ADSP embedded within the audio sysmodule.
34 * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
35 *
36 * The kernel will run apps you program for it, Nintendo have the following:
37 *
38 * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
39 * audio samples end up, and we skip it entirely, since we have very different backends and
40 * mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
41 *
42 * AudioRenderer - Receives command lists generated by the audio render
43 * system, processes them, and sends the samples to Gmix.
44 *
45 * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
46 * Not much research done here, TODO if needed.
47 *
48 * We only implement the AudioRenderer for now.
49 *
50 * Communication for the apps is done through mailboxes, and some shared memory.
51 */
52class ADSP {
53public:
54 explicit ADSP(Core::System& system, Sink::Sink& sink);
55 ~ADSP();
56
57 /**
58 * Start the ADSP.
59 *
60 * @return True if started or already running, otherwise false.
61 */
62 bool Start();
63
64 /**
65 * Stop the ADSP.
66 */
67 void Stop();
68
69 /**
70 * Get the ADSP's state.
71 *
72 * @return Started or Stopped.
73 */
74 State GetState() const;
75
76 /**
77 * Get the AudioRenderer mailbox to communicate with it.
78 *
79 * @return The AudioRenderer mailbox.
80 */
81 AudioRenderer_Mailbox* GetRenderMailbox();
82
83 /**
84 * Get the tick the ADSP was signalled.
85 *
86 * @return The tick the ADSP was signalled.
87 */
88 u64 GetSignalledTick() const;
89
90 /**
91 * Get the total time it took for the ADSP to run the last command lists (both command lists).
92 *
93 * @return The tick the ADSP was signalled.
94 */
95 u64 GetTimeTaken() const;
96
97 /**
98 * Get the last time a given command list took to run.
99 *
100 * @param session_id - The session id to check (0 or 1).
101 * @return The time it took.
102 */
103 u64 GetRenderTimeTaken(u32 session_id);
104
105 /**
106 * Clear the remaining command count for a given session.
107 *
108 * @param session_id - The session id to check (0 or 1).
109 */
110 void ClearRemainCount(u32 session_id);
111
112 /**
113 * Get the remaining number of commands left to process for a command list.
114 *
115 * @param session_id - The session id to check (0 or 1).
116 * @return The number of commands remaining.
117 */
118 u32 GetRemainCommandCount(u32 session_id) const;
119
120 /**
121 * Get the last tick a command list started processing.
122 *
123 * @param session_id - The session id to check (0 or 1).
124 * @return The last tick the given command list started.
125 */
126 u64 GetRenderingStartTick(u32 session_id);
127
128 /**
129 * Set a command buffer to be processed.
130 *
131 * @param session_id - The session id to check (0 or 1).
132 * @param command_buffer - The command buffer to process.
133 */
134 void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
135
136 /**
137 * Clear the command buffers (does not clear the time taken or the remaining command count)
138 */
139 void ClearCommandBuffers();
140
141 /**
142 * Signal the AudioRenderer to begin processing.
143 */
144 void Signal();
145
146 /**
147 * Wait for the AudioRenderer to finish processing.
148 */
149 void Wait();
150
151private:
152 /// Core system
153 Core::System& system;
154 /// Core memory
155 Core::Memory::Memory& memory;
156 /// Number of systems active, used to prevent accidental shutdowns
157 u8 systems_active{0};
158 /// ADSP running state
159 std::atomic<bool> running{false};
160 /// Output sink used by the ADSP
161 Sink::Sink& sink;
162 /// AudioRenderer app
163 std::unique_ptr<AudioRenderer> audio_renderer{};
164 /// Communication for the AudioRenderer
165 AudioRenderer_Mailbox render_mailbox{};
166 /// Mailbox lock ffor the render mailbox
167 std::mutex mailbox_lock;
168};
169
170} // namespace AudioRenderer::ADSP
171} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
deleted file mode 100644
index 9ca716b60..000000000
--- a/src/audio_core/renderer/adsp/audio_renderer.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <chrono>
6
7#include "audio_core/audio_core.h"
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/adsp/audio_renderer.h"
10#include "audio_core/sink/sink.h"
11#include "common/logging/log.h"
12#include "common/microprofile.h"
13#include "common/thread.h"
14#include "core/core.h"
15#include "core/core_timing.h"
16
17MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
18
19namespace AudioCore::AudioRenderer::ADSP {
20
21void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
22 adsp_messages.enqueue(message_);
23 adsp_event.Set();
24}
25
26RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
27 host_event.Wait();
28 RenderMessage msg{RenderMessage::Invalid};
29 if (!host_messages.try_dequeue(msg)) {
30 LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
31 }
32 return msg;
33}
34
35void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
36 host_messages.enqueue(message_);
37 host_event.Set();
38}
39
40RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
41 adsp_event.Wait();
42 RenderMessage msg{RenderMessage::Invalid};
43 if (!adsp_messages.try_dequeue(msg)) {
44 LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
45 }
46 return msg;
47}
48
49CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
50 return command_buffers[session_id];
51}
52
53void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
54 command_buffers[session_id] = buffer;
55}
56
57u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
58 return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
59}
60
61u64 AudioRenderer_Mailbox::GetSignalledTick() const {
62 return signalled_tick;
63}
64
65void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
66 signalled_tick = tick;
67}
68
69void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
70 command_buffers[session_id].remaining_command_count = 0;
71}
72
73u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
74 return command_buffers[session_id].remaining_command_count;
75}
76
77void AudioRenderer_Mailbox::ClearCommandBuffers() {
78 command_buffers[0].buffer = 0;
79 command_buffers[0].size = 0;
80 command_buffers[0].reset_buffers = false;
81 command_buffers[1].buffer = 0;
82 command_buffers[1].size = 0;
83 command_buffers[1].reset_buffers = false;
84}
85
86AudioRenderer::AudioRenderer(Core::System& system_)
87 : system{system_}, sink{system.AudioCore().GetOutputSink()} {
88 CreateSinkStreams();
89}
90
91AudioRenderer::~AudioRenderer() {
92 Stop();
93 for (auto& stream : streams) {
94 if (stream) {
95 sink.CloseStream(stream);
96 }
97 stream = nullptr;
98 }
99}
100
101void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
102 if (running) {
103 return;
104 }
105
106 mailbox = mailbox_;
107 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); });
108 running = true;
109}
110
111void AudioRenderer::Stop() {
112 if (!running) {
113 return;
114 }
115
116 for (auto& stream : streams) {
117 stream->Stop();
118 }
119 thread.join();
120 running = false;
121}
122
123void AudioRenderer::CreateSinkStreams() {
124 u32 channels{sink.GetDeviceChannels()};
125 for (u32 i = 0; i < MaxRendererSessions; i++) {
126 std::string name{fmt::format("ADSP_RenderStream-{}", i)};
127 streams[i] =
128 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
129 streams[i]->SetRingSize(4);
130 }
131}
132
133void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
134 static constexpr char name[]{"AudioRenderer"};
135 MicroProfileOnThreadCreate(name);
136 Common::SetCurrentThreadName(name);
137 Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
138 if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
139 LOG_ERROR(Service_Audio,
140 "ADSP Audio Renderer -- Failed to receive initialize message from host!");
141 return;
142 }
143
144 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
145
146 // 0.12 seconds (2304000 / 19200000)
147 constexpr u64 max_process_time{2'304'000ULL};
148
149 while (!stop_token.stop_requested()) {
150 auto message{mailbox->ADSPWaitMessage()};
151 switch (message) {
152 case RenderMessage::AudioRenderer_Shutdown:
153 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
154 return;
155
156 case RenderMessage::AudioRenderer_Render: {
157 if (system.IsShuttingDown()) [[unlikely]] {
158 std::this_thread::sleep_for(std::chrono::milliseconds(5));
159 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
160 continue;
161 }
162 std::array<bool, MaxRendererSessions> buffers_reset{};
163 std::array<u64, MaxRendererSessions> render_times_taken{};
164 const auto start_time{system.CoreTiming().GetClockTicks()};
165
166 for (u32 index = 0; index < 2; index++) {
167 auto& command_buffer{mailbox->GetCommandBuffer(index)};
168 auto& command_list_processor{command_list_processors[index]};
169
170 // Check this buffer is valid, as it may not be used.
171 if (command_buffer.buffer != 0) {
172 // If there are no remaining commands (from the previous list),
173 // this is a new command list, initialize it.
174 if (command_buffer.remaining_command_count == 0) {
175 command_list_processor.Initialize(system, command_buffer.buffer,
176 command_buffer.size, streams[index]);
177 }
178
179 if (command_buffer.reset_buffers && !buffers_reset[index]) {
180 streams[index]->ClearQueue();
181 buffers_reset[index] = true;
182 }
183
184 u64 max_time{max_process_time};
185 if (index == 1 && command_buffer.applet_resource_user_id ==
186 mailbox->GetCommandBuffer(0).applet_resource_user_id) {
187 max_time = max_process_time - render_times_taken[0];
188 if (render_times_taken[0] > max_process_time) {
189 max_time = 0;
190 }
191 }
192
193 max_time = std::min(command_buffer.time_limit, max_time);
194 command_list_processor.SetProcessTimeMax(max_time);
195
196 streams[index]->WaitFreeSpace(stop_token);
197
198 // Process the command list
199 {
200 MICROPROFILE_SCOPE(Audio_Renderer);
201 render_times_taken[index] =
202 command_list_processor.Process(index) - start_time;
203 }
204
205 const auto end_time{system.CoreTiming().GetClockTicks()};
206
207 command_buffer.remaining_command_count =
208 command_list_processor.GetRemainingCommandCount();
209 command_buffer.render_time_taken = end_time - start_time;
210 }
211 }
212
213 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
214 } break;
215
216 default:
217 LOG_WARNING(Service_Audio,
218 "ADSP AudioRenderer received an invalid message, msg={:02X}!",
219 static_cast<u32>(message));
220 break;
221 }
222 }
223}
224
225} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
deleted file mode 100644
index 88e558183..000000000
--- a/src/audio_core/renderer/adsp/audio_renderer.h
+++ /dev/null
@@ -1,204 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <thread>
9
10#include "audio_core/renderer/adsp/command_buffer.h"
11#include "audio_core/renderer/adsp/command_list_processor.h"
12#include "common/common_types.h"
13#include "common/polyfill_thread.h"
14#include "common/reader_writer_queue.h"
15#include "common/thread.h"
16
17namespace Core {
18namespace Timing {
19struct EventType;
20}
21class System;
22} // namespace Core
23
24namespace AudioCore {
25namespace Sink {
26class Sink;
27}
28
29namespace AudioRenderer::ADSP {
30
31enum class RenderMessage {
32 /* 0x00 */ Invalid,
33 /* 0x01 */ AudioRenderer_MapUnmap_Map,
34 /* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
35 /* 0x03 */ AudioRenderer_MapUnmap_Unmap,
36 /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
37 /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
38 /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
39 /* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
40 /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
41 /* 0x16 */ AudioRenderer_InitializeOK = 0x16,
42 /* 0x20 */ AudioRenderer_RenderResponse = 0x20,
43 /* 0x2A */ AudioRenderer_Render = 0x2A,
44 /* 0x34 */ AudioRenderer_Shutdown = 0x34,
45};
46
47/**
48 * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
49 * running on the ADSP.
50 */
51class AudioRenderer_Mailbox {
52public:
53 /**
54 * Send a message from the host to the AudioRenderer.
55 *
56 * @param message - The message to send to the AudioRenderer.
57 */
58 void HostSendMessage(RenderMessage message);
59
60 /**
61 * Host wait for a message from the AudioRenderer.
62 *
63 * @return The message returned from the AudioRenderer.
64 */
65 RenderMessage HostWaitMessage();
66
67 /**
68 * Send a message from the AudioRenderer to the host.
69 *
70 * @param message - The message to send to the host.
71 */
72 void ADSPSendMessage(RenderMessage message);
73
74 /**
75 * AudioRenderer wait for a message from the host.
76 *
77 * @return The message returned from the AudioRenderer.
78 */
79 RenderMessage ADSPWaitMessage();
80
81 /**
82 * Get the command buffer with the given session id (0 or 1).
83 *
84 * @param session_id - The session id to get (0 or 1).
85 * @return The command buffer.
86 */
87 CommandBuffer& GetCommandBuffer(u32 session_id);
88
89 /**
90 * Set the command buffer with the given session id (0 or 1).
91 *
92 * @param session_id - The session id to get (0 or 1).
93 * @param buffer - The command buffer to set.
94 */
95 void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
96
97 /**
98 * Get the total render time taken for the last command lists sent.
99 *
100 * @return Total render time taken for the last command lists.
101 */
102 u64 GetRenderTimeTaken() const;
103
104 /**
105 * Get the tick the AudioRenderer was signalled.
106 *
107 * @return The tick the AudioRenderer was signalled.
108 */
109 u64 GetSignalledTick() const;
110
111 /**
112 * Set the tick the AudioRenderer was signalled.
113 *
114 * @param tick - The tick the AudioRenderer was signalled.
115 */
116 void SetSignalledTick(u64 tick);
117
118 /**
119 * Clear the remaining command count.
120 *
121 * @param session_id - Index for which command list to clear (0 or 1).
122 */
123 void ClearRemainCount(u32 session_id);
124
125 /**
126 * Get the remaining command count for a given command list.
127 *
128 * @param session_id - Index for which command list to clear (0 or 1).
129 * @return The remaining command count.
130 */
131 u32 GetRemainCommandCount(u32 session_id) const;
132
133 /**
134 * Clear the command buffers (does not clear the time taken or the remaining command count).
135 */
136 void ClearCommandBuffers();
137
138private:
139 /// Host signalling event
140 Common::Event host_event{};
141 /// AudioRenderer signalling event
142 Common::Event adsp_event{};
143 /// Host message queue
144
145 Common::ReaderWriterQueue<RenderMessage> host_messages{};
146 /// AudioRenderer message queue
147
148 Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
149 /// Command buffers
150
151 std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
152 /// Tick the AudioRnederer was signalled
153 u64 signalled_tick{};
154};
155
156/**
157 * The AudioRenderer application running on the ADSP.
158 */
159class AudioRenderer {
160public:
161 explicit AudioRenderer(Core::System& system);
162 ~AudioRenderer();
163
164 /**
165 * Start the AudioRenderer.
166 *
167 * @param mailbox The mailbox to use for this session.
168 */
169 void Start(AudioRenderer_Mailbox* mailbox);
170
171 /**
172 * Stop the AudioRenderer.
173 */
174 void Stop();
175
176private:
177 /**
178 * Main AudioRenderer thread, responsible for processing the command lists.
179 */
180 void ThreadFunc(std::stop_token stop_token);
181
182 /**
183 * Creates the streams which will receive the processed samples.
184 */
185 void CreateSinkStreams();
186
187 /// Core system
188 Core::System& system;
189 /// Main thread
190 std::jthread thread{};
191 /// The current state
192 std::atomic<bool> running{};
193 /// The active mailbox
194 AudioRenderer_Mailbox* mailbox{};
195 /// The command lists to process
196 std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
197 /// The output sink the AudioRenderer will use
198 Sink::Sink& sink;
199 /// The streams which will receive the processed samples
200 std::array<Sink::SinkStream*, MaxRendererSessions> streams;
201};
202
203} // namespace AudioRenderer::ADSP
204} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/command_buffer.h b/src/audio_core/renderer/adsp/command_buffer.h
deleted file mode 100644
index 880b279d8..000000000
--- a/src/audio_core/renderer/adsp/command_buffer.h
+++ /dev/null
@@ -1,21 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/common/common.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer::ADSP {
10
11struct CommandBuffer {
12 CpuAddr buffer;
13 u64 size;
14 u64 time_limit;
15 u32 remaining_command_count;
16 bool reset_buffers;
17 u64 applet_resource_user_id;
18 u64 render_time_taken;
19};
20
21} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp
index 0d9d8f6ce..2d9bf82bb 100644
--- a/src/audio_core/renderer/audio_device.cpp
+++ b/src/audio_core/renderer/audio_device.cpp
@@ -10,7 +10,7 @@
10#include "audio_core/sink/sink.h" 10#include "audio_core/sink/sink.h"
11#include "core/core.h" 11#include "core/core.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::Renderer {
14 14
15constexpr std::array usb_device_names{ 15constexpr std::array usb_device_names{
16 AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, 16 AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
@@ -71,4 +71,4 @@ f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
71 return output_sink.GetDeviceVolume(); 71 return output_sink.GetDeviceVolume();
72} 72}
73 73
74} // namespace AudioCore::AudioRenderer 74} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h
index dd6be70ee..ca4040add 100644
--- a/src/audio_core/renderer/audio_device.h
+++ b/src/audio_core/renderer/audio_device.h
@@ -16,7 +16,7 @@ namespace Sink {
16class Sink; 16class Sink;
17} 17}
18 18
19namespace AudioRenderer { 19namespace Renderer {
20/** 20/**
21 * An interface to an output audio device available to the Switch. 21 * An interface to an output audio device available to the Switch.
22 */ 22 */
@@ -76,5 +76,5 @@ private:
76 const u32 user_revision; 76 const u32 user_revision;
77}; 77};
78 78
79} // namespace AudioRenderer 79} // namespace Renderer
80} // namespace AudioCore 80} // namespace AudioCore
diff --git a/src/audio_core/renderer/audio_renderer.cpp b/src/audio_core/renderer/audio_renderer.cpp
index a8257eb2e..09efe9be9 100644
--- a/src/audio_core/renderer/audio_renderer.cpp
+++ b/src/audio_core/renderer/audio_renderer.cpp
@@ -9,7 +9,7 @@
9#include "core/hle/kernel/k_transfer_memory.h" 9#include "core/hle/kernel/k_transfer_memory.h"
10#include "core/hle/service/audio/errors.h" 10#include "core/hle/service/audio/errors.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13 13
14Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event) 14Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
15 : core{system_}, manager{manager_}, system{system_, rendered_event} {} 15 : core{system_}, manager{manager_}, system{system_, rendered_event} {}
@@ -64,4 +64,4 @@ Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performa
64 return system.Update(input, performance, output); 64 return system.Update(input, performance, output);
65} 65}
66 66
67} // namespace AudioCore::AudioRenderer 67} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h
index 90c6f9727..24650278b 100644
--- a/src/audio_core/renderer/audio_renderer.h
+++ b/src/audio_core/renderer/audio_renderer.h
@@ -19,7 +19,7 @@ class KTransferMemory;
19namespace AudioCore { 19namespace AudioCore {
20struct AudioRendererParameterInternal; 20struct AudioRendererParameterInternal;
21 21
22namespace AudioRenderer { 22namespace Renderer {
23class Manager; 23class Manager;
24 24
25/** 25/**
@@ -31,7 +31,7 @@ public:
31 31
32 /** 32 /**
33 * Initialize the renderer. 33 * Initialize the renderer.
34 * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes 34 * Registers the system with the Renderer::Manager, allocates workbuffers and initializes
35 * everything to a default state. 35 * everything to a default state.
36 * 36 *
37 * @param params - Input parameters to initialize the system with. 37 * @param params - Input parameters to initialize the system with.
@@ -93,5 +93,5 @@ private:
93 System system; 93 System system;
94}; 94};
95 95
96} // namespace AudioRenderer 96} // namespace Renderer
97} // namespace AudioCore 97} // namespace AudioCore
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp
index 3d2a91312..058539042 100644
--- a/src/audio_core/renderer/behavior/behavior_info.cpp
+++ b/src/audio_core/renderer/behavior/behavior_info.cpp
@@ -4,7 +4,7 @@
4#include "audio_core/common/feature_support.h" 4#include "audio_core/common/feature_support.h"
5#include "audio_core/renderer/behavior/behavior_info.h" 5#include "audio_core/renderer/behavior/behavior_info.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8 8
9BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {} 9BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
10 10
@@ -190,4 +190,4 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
190 return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); 190 return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
191} 191}
192 192
193} // namespace AudioCore::AudioRenderer 193} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h
index b52340229..a4958857a 100644
--- a/src/audio_core/renderer/behavior/behavior_info.h
+++ b/src/audio_core/renderer/behavior/behavior_info.h
@@ -10,7 +10,7 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/hle/service/audio/errors.h" 11#include "core/hle/service/audio/errors.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::Renderer {
14/** 14/**
15 * Holds host and user revisions, checks whether render features can be enabled, and reports errors. 15 * Holds host and user revisions, checks whether render features can be enabled, and reports errors.
16 */ 16 */
@@ -264,7 +264,7 @@ public:
264 /** 264 /**
265 * Check if skipping voice pitch and sample rate conversion is supported. 265 * Check if skipping voice pitch and sample rate conversion is supported.
266 * This speeds up the data source commands by skipping resampling if unwanted. 266 * This speeds up the data source commands by skipping resampling if unwanted.
267 * See AudioCore::AudioRenderer::DecodeFromWaveBuffers 267 * See AudioCore::Renderer::DecodeFromWaveBuffers
268 * 268 *
269 * @return True if supported, otherwise false. 269 * @return True if supported, otherwise false.
270 */ 270 */
@@ -273,7 +273,7 @@ public:
273 /** 273 /**
274 * Check if resetting played sample count at loop points is supported. 274 * Check if resetting played sample count at loop points is supported.
275 * This resets the number of samples played in a voice state when a loop point is reached. 275 * This resets the number of samples played in a voice state when a loop point is reached.
276 * See AudioCore::AudioRenderer::DecodeFromWaveBuffers 276 * See AudioCore::Renderer::DecodeFromWaveBuffers
277 * 277 *
278 * @return True if supported, otherwise false. 278 * @return True if supported, otherwise false.
279 */ 279 */
@@ -373,4 +373,4 @@ public:
373 u32 error_count{}; 373 u32 error_count{};
374}; 374};
375 375
376} // namespace AudioCore::AudioRenderer 376} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp
index e312eb166..667711e17 100644
--- a/src/audio_core/renderer/behavior/info_updater.cpp
+++ b/src/audio_core/renderer/behavior/info_updater.cpp
@@ -15,7 +15,7 @@
15#include "audio_core/renderer/splitter/splitter_context.h" 15#include "audio_core/renderer/splitter/splitter_context.h"
16#include "audio_core/renderer/voice/voice_context.h" 16#include "audio_core/renderer/voice/voice_context.h"
17 17
18namespace AudioCore::AudioRenderer { 18namespace AudioCore::Renderer {
19 19
20InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_, 20InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_,
21 const u32 process_handle_, BehaviorInfo& behaviour_) 21 const u32 process_handle_, BehaviorInfo& behaviour_)
@@ -536,4 +536,4 @@ Result InfoUpdater::CheckConsumedSize() {
536 return ResultSuccess; 536 return ResultSuccess;
537} 537}
538 538
539} // namespace AudioCore::AudioRenderer 539} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h
index c817d8d8d..fb4b7d25a 100644
--- a/src/audio_core/renderer/behavior/info_updater.h
+++ b/src/audio_core/renderer/behavior/info_updater.h
@@ -8,7 +8,7 @@
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "core/hle/service/audio/errors.h" 9#include "core/hle/service/audio/errors.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::Renderer {
12class BehaviorInfo; 12class BehaviorInfo;
13class VoiceContext; 13class VoiceContext;
14class MixContext; 14class MixContext;
@@ -202,4 +202,4 @@ private:
202 BehaviorInfo& behaviour; 202 BehaviorInfo& behaviour;
203}; 203};
204 204
205} // namespace AudioCore::AudioRenderer 205} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp
index 0bd418306..67d43e69a 100644
--- a/src/audio_core/renderer/command/command_buffer.cpp
+++ b/src/audio_core/renderer/command/command_buffer.cpp
@@ -16,7 +16,7 @@
16#include "audio_core/renderer/voice/voice_info.h" 16#include "audio_core/renderer/voice/voice_info.h"
17#include "audio_core/renderer/voice/voice_state.h" 17#include "audio_core/renderer/voice/voice_state.h"
18 18
19namespace AudioCore::AudioRenderer { 19namespace AudioCore::Renderer {
20 20
21template <typename T, CommandId Id> 21template <typename T, CommandId Id>
22T& CommandBuffer::GenerateStart(const s32 node_id) { 22T& CommandBuffer::GenerateStart(const s32 node_id) {
@@ -713,4 +713,4 @@ void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase&
713 GenerateEnd<CompressorCommand>(cmd); 713 GenerateEnd<CompressorCommand>(cmd);
714} 714}
715 715
716} // namespace AudioCore::AudioRenderer 716} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h
index 162170846..12e8c2c81 100644
--- a/src/audio_core/renderer/command/command_buffer.h
+++ b/src/audio_core/renderer/command/command_buffer.h
@@ -10,7 +10,7 @@
10#include "audio_core/renderer/performance/performance_manager.h" 10#include "audio_core/renderer/performance/performance_manager.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::Renderer {
14struct UpsamplerInfo; 14struct UpsamplerInfo;
15struct VoiceState; 15struct VoiceState;
16class EffectInfoBase; 16class EffectInfoBase;
@@ -465,4 +465,4 @@ private:
465 void GenerateEnd(T& cmd); 465 void GenerateEnd(T& cmd);
466}; 466};
467 467
468} // namespace AudioCore::AudioRenderer 468} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp
index fba84c7bd..ccb186209 100644
--- a/src/audio_core/renderer/command/command_generator.cpp
+++ b/src/audio_core/renderer/command/command_generator.cpp
@@ -21,7 +21,7 @@
21#include "audio_core/renderer/voice/voice_context.h" 21#include "audio_core/renderer/voice/voice_context.h"
22#include "common/alignment.h" 22#include "common/alignment.h"
23 23
24namespace AudioCore::AudioRenderer { 24namespace AudioCore::Renderer {
25 25
26CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, 26CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_,
27 const CommandListHeader& command_list_header_, 27 const CommandListHeader& command_list_header_,
@@ -793,4 +793,4 @@ void CommandGenerator::GeneratePerformanceCommand(
793 command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); 793 command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
794} 794}
795 795
796} // namespace AudioCore::AudioRenderer 796} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h
index b3cd7b408..38ee2a64e 100644
--- a/src/audio_core/renderer/command/command_generator.h
+++ b/src/audio_core/renderer/command/command_generator.h
@@ -12,7 +12,7 @@
12namespace AudioCore { 12namespace AudioCore {
13struct AudioRendererSystemContext; 13struct AudioRendererSystemContext;
14 14
15namespace AudioRenderer { 15namespace Renderer {
16class CommandBuffer; 16class CommandBuffer;
17struct CommandListHeader; 17struct CommandListHeader;
18class VoiceContext; 18class VoiceContext;
@@ -345,5 +345,5 @@ private:
345 PerformanceManager* performance_manager; 345 PerformanceManager* performance_manager;
346}; 346};
347 347
348} // namespace AudioRenderer 348} // namespace Renderer
349} // namespace AudioCore 349} // namespace AudioCore
diff --git a/src/audio_core/renderer/command/command_list_header.h b/src/audio_core/renderer/command/command_list_header.h
index 988530b1f..de9ee070b 100644
--- a/src/audio_core/renderer/command/command_list_header.h
+++ b/src/audio_core/renderer/command/command_list_header.h
@@ -8,7 +8,7 @@
8#include "audio_core/common/common.h" 8#include "audio_core/common/common.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::Renderer {
12 12
13struct CommandListHeader { 13struct CommandListHeader {
14 u64 buffer_size; 14 u64 buffer_size;
@@ -19,4 +19,4 @@ struct CommandListHeader {
19 u32 sample_rate; 19 u32 sample_rate;
20}; 20};
21 21
22} // namespace AudioCore::AudioRenderer 22} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
index 3091f587a..0f7aff1b4 100644
--- a/src/audio_core/renderer/command/command_processing_time_estimator.cpp
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/command/command_processing_time_estimator.h" 4#include "audio_core/renderer/command/command_processing_time_estimator.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8u32 CommandProcessingTimeEstimatorVersion1::Estimate( 8u32 CommandProcessingTimeEstimatorVersion1::Estimate(
9 const PcmInt16DataSourceVersion1Command& command) const { 9 const PcmInt16DataSourceVersion1Command& command) const {
@@ -27,12 +27,12 @@ u32 CommandProcessingTimeEstimatorVersion1::Estimate(
27 27
28u32 CommandProcessingTimeEstimatorVersion1::Estimate( 28u32 CommandProcessingTimeEstimatorVersion1::Estimate(
29 const AdpcmDataSourceVersion1Command& command) const { 29 const AdpcmDataSourceVersion1Command& command) const {
30 return static_cast<u32>(command.pitch * 0.25f * 1.2f); 30 return static_cast<u32>(command.pitch * 0.46f * 1.2f);
31} 31}
32 32
33u32 CommandProcessingTimeEstimatorVersion1::Estimate( 33u32 CommandProcessingTimeEstimatorVersion1::Estimate(
34 const AdpcmDataSourceVersion2Command& command) const { 34 const AdpcmDataSourceVersion2Command& command) const {
35 return static_cast<u32>(command.pitch * 0.25f * 1.2f); 35 return static_cast<u32>(command.pitch * 0.46f * 1.2f);
36} 36}
37 37
38u32 CommandProcessingTimeEstimatorVersion1::Estimate( 38u32 CommandProcessingTimeEstimatorVersion1::Estimate(
@@ -3617,4 +3617,4 @@ u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& co
3617 } 3617 }
3618} 3618}
3619 3619
3620} // namespace AudioCore::AudioRenderer 3620} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.h b/src/audio_core/renderer/command/command_processing_time_estimator.h
index 452217196..1c76e4ba4 100644
--- a/src/audio_core/renderer/command/command_processing_time_estimator.h
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.h
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/command/commands.h" 6#include "audio_core/renderer/command/commands.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10/** 10/**
11 * Estimate the processing time required for all commands. 11 * Estimate the processing time required for all commands.
12 */ 12 */
@@ -251,4 +251,4 @@ private:
251 u32 buffer_count{}; 251 u32 buffer_count{};
252}; 252};
253 253
254} // namespace AudioCore::AudioRenderer 254} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp
index e66ed2990..e7f82d3b3 100644
--- a/src/audio_core/renderer/command/data_source/adpcm.cpp
+++ b/src/audio_core/renderer/command/data_source/adpcm.cpp
@@ -3,23 +3,29 @@
3 3
4#include <span> 4#include <span>
5 5
6#include "audio_core/renderer/adsp/command_list_processor.h" 6#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
7#include "audio_core/renderer/command/data_source/adpcm.h" 7#include "audio_core/renderer/command/data_source/adpcm.h"
8#include "audio_core/renderer/command/data_source/decode.h" 8#include "audio_core/renderer/command/data_source/decode.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, 12void AdpcmDataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor,
13 std::string& string) { 13 std::string& string) {
14 string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample " 14 string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
15 "rate {} target sample rate {} src quality {}\n", 15 "rate {} target sample rate {} src quality {}\n",
16 output_index, sample_rate, processor.target_sample_rate, src_quality); 16 output_index, sample_rate, processor.target_sample_rate, src_quality);
17} 17}
18 18
19void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { 19void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListProcessor& processor) {
20 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, 20 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
21 processor.sample_count)}; 21 processor.sample_count)};
22 22
23 for (auto& wave_buffer : wave_buffers) {
24 wave_buffer.loop_start_offset = wave_buffer.start_offset;
25 wave_buffer.loop_end_offset = wave_buffer.end_offset;
26 wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
27 }
28
23 DecodeFromWaveBuffersArgs args{ 29 DecodeFromWaveBuffersArgs args{
24 .sample_format{SampleFormat::Adpcm}, 30 .sample_format{SampleFormat::Adpcm},
25 .output{out_buffer}, 31 .output{out_buffer},
@@ -41,18 +47,18 @@ void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& p
41 DecodeFromWaveBuffers(*processor.memory, args); 47 DecodeFromWaveBuffers(*processor.memory, args);
42} 48}
43 49
44bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { 50bool AdpcmDataSourceVersion1Command::Verify(const AudioRenderer::CommandListProcessor& processor) {
45 return true; 51 return true;
46} 52}
47 53
48void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, 54void AdpcmDataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor,
49 std::string& string) { 55 std::string& string) {
50 string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample " 56 string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
51 "rate {} target sample rate {} src quality {}\n", 57 "rate {} target sample rate {} src quality {}\n",
52 output_index, sample_rate, processor.target_sample_rate, src_quality); 58 output_index, sample_rate, processor.target_sample_rate, src_quality);
53} 59}
54 60
55void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { 61void AdpcmDataSourceVersion2Command::Process(const AudioRenderer::CommandListProcessor& processor) {
56 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, 62 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
57 processor.sample_count)}; 63 processor.sample_count)};
58 64
@@ -77,8 +83,8 @@ void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& p
77 DecodeFromWaveBuffers(*processor.memory, args); 83 DecodeFromWaveBuffers(*processor.memory, args);
78} 84}
79 85
80bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { 86bool AdpcmDataSourceVersion2Command::Verify(const AudioRenderer::CommandListProcessor& processor) {
81 return true; 87 return true;
82} 88}
83 89
84} // namespace AudioCore::AudioRenderer 90} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/data_source/adpcm.h b/src/audio_core/renderer/command/data_source/adpcm.h
index a9cf9cee4..487846f0c 100644
--- a/src/audio_core/renderer/command/data_source/adpcm.h
+++ b/src/audio_core/renderer/command/data_source/adpcm.h
@@ -11,11 +11,12 @@
11#include "audio_core/renderer/command/icommand.h" 11#include "audio_core/renderer/command/icommand.h"
12#include "common/common_types.h" 12#include "common/common_types.h"
13 13
14namespace AudioCore::AudioRenderer { 14namespace AudioCore::ADSP::AudioRenderer {
15namespace ADSP {
16class CommandListProcessor; 15class CommandListProcessor;
17} 16}
18 17
18namespace AudioCore::Renderer {
19
19/** 20/**
20 * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers 21 * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
21 * into the output_index mix buffer. 22 * into the output_index mix buffer.
@@ -27,14 +28,14 @@ struct AdpcmDataSourceVersion1Command : ICommand {
27 * @param processor - The CommandListProcessor processing this command. 28 * @param processor - The CommandListProcessor processing this command.
28 * @param string - The string to print into. 29 * @param string - The string to print into.
29 */ 30 */
30 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 31 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
31 32
32 /** 33 /**
33 * Process this command. 34 * Process this command.
34 * 35 *
35 * @param processor - The CommandListProcessor processing this command. 36 * @param processor - The CommandListProcessor processing this command.
36 */ 37 */
37 void Process(const ADSP::CommandListProcessor& processor) override; 38 void Process(const AudioRenderer::CommandListProcessor& processor) override;
38 39
39 /** 40 /**
40 * Verify this command's data is valid. 41 * Verify this command's data is valid.
@@ -42,13 +43,13 @@ struct AdpcmDataSourceVersion1Command : ICommand {
42 * @param processor - The CommandListProcessor processing this command. 43 * @param processor - The CommandListProcessor processing this command.
43 * @return True if the command is valid, otherwise false. 44 * @return True if the command is valid, otherwise false.
44 */ 45 */
45 bool Verify(const ADSP::CommandListProcessor& processor) override; 46 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
46 47
47 /// Quality used for sample rate conversion 48 /// Quality used for sample rate conversion
48 SrcQuality src_quality; 49 SrcQuality src_quality;
49 /// Mix buffer index for decoded samples 50 /// Mix buffer index for decoded samples
50 s16 output_index; 51 s16 output_index;
51 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) 52 /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
52 u16 flags; 53 u16 flags;
53 /// Wavebuffer sample rate 54 /// Wavebuffer sample rate
54 u32 sample_rate; 55 u32 sample_rate;
@@ -75,14 +76,14 @@ struct AdpcmDataSourceVersion2Command : ICommand {
75 * @param processor - The CommandListProcessor processing this command. 76 * @param processor - The CommandListProcessor processing this command.
76 * @param string - The string to print into. 77 * @param string - The string to print into.
77 */ 78 */
78 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 79 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
79 80
80 /** 81 /**
81 * Process this command. 82 * Process this command.
82 * 83 *
83 * @param processor - The CommandListProcessor processing this command. 84 * @param processor - The CommandListProcessor processing this command.
84 */ 85 */
85 void Process(const ADSP::CommandListProcessor& processor) override; 86 void Process(const AudioRenderer::CommandListProcessor& processor) override;
86 87
87 /** 88 /**
88 * Verify this command's data is valid. 89 * Verify this command's data is valid.
@@ -90,13 +91,13 @@ struct AdpcmDataSourceVersion2Command : ICommand {
90 * @param processor - The CommandListProcessor processing this command. 91 * @param processor - The CommandListProcessor processing this command.
91 * @return True if the command is valid, otherwise false. 92 * @return True if the command is valid, otherwise false.
92 */ 93 */
93 bool Verify(const ADSP::CommandListProcessor& processor) override; 94 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
94 95
95 /// Quality used for sample rate conversion 96 /// Quality used for sample rate conversion
96 SrcQuality src_quality; 97 SrcQuality src_quality;
97 /// Mix buffer index for decoded samples 98 /// Mix buffer index for decoded samples
98 s16 output_index; 99 s16 output_index;
99 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) 100 /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
100 u16 flags; 101 u16 flags;
101 /// Wavebuffer sample rate 102 /// Wavebuffer sample rate
102 u32 sample_rate; 103 u32 sample_rate;
@@ -116,4 +117,4 @@ struct AdpcmDataSourceVersion2Command : ICommand {
116 u64 data_size; 117 u64 data_size;
117}; 118};
118 119
119} // namespace AudioCore::AudioRenderer 120} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp
index 257aa866e..911dae3c1 100644
--- a/src/audio_core/renderer/command/data_source/decode.cpp
+++ b/src/audio_core/renderer/command/data_source/decode.cpp
@@ -11,7 +11,7 @@
11#include "common/scratch_buffer.h" 11#include "common/scratch_buffer.h"
12#include "core/memory.h" 12#include "core/memory.h"
13 13
14namespace AudioCore::AudioRenderer { 14namespace AudioCore::Renderer {
15 15
16constexpr u32 TempBufferSize = 0x3F00; 16constexpr u32 TempBufferSize = 0x3F00;
17constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4}; 17constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
@@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
123 return 0; 123 return 0;
124 } 124 }
125 125
126 auto samples_to_process{ 126 auto start_pos{req.start_offset + req.offset};
127 std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; 127 auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)};
128 if (samples_to_process == 0) {
129 return 0;
130 }
128 131
129 auto samples_to_read{samples_to_process}; 132 auto samples_to_read{samples_to_process};
130 auto start_pos{req.start_offset + req.offset};
131 auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; 133 auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
132 auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + 134 auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
133 samples_remaining_in_frame}; 135 samples_remaining_in_frame};
@@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
225 * @param args - The wavebuffer data, and information for how to decode it. 227 * @param args - The wavebuffer data, and information for how to decode it.
226 */ 228 */
227void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { 229void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
230 static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
231 auto& played_samples, auto& consumed) -> void {
232 voice_state.wave_buffer_valid[index] = false;
233 voice_state.loop_count = 0;
234
235 if (wavebuffer.stream_ended) {
236 played_samples = 0;
237 }
238
239 index = (index + 1) % MaxWaveBuffers;
240 consumed++;
241 };
228 auto& voice_state{*args.voice_state}; 242 auto& voice_state{*args.voice_state};
229 auto remaining_sample_count{args.sample_count}; 243 auto remaining_sample_count{args.sample_count};
230 auto fraction{voice_state.fraction}; 244 auto fraction{voice_state.fraction};
231 245
232 const auto sample_rate_ratio{ 246 const auto sample_rate_ratio{Common::FixedPoint<49, 15>(
233 (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * 247 (f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)};
234 args.pitch};
235 const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; 248 const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
236 249
237 if (size_required < 0) { 250 if (size_required < 0) {
@@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
298 auto end_offset{wavebuffer.end_offset}; 311 auto end_offset{wavebuffer.end_offset};
299 312
300 if (wavebuffer.loop && voice_state.loop_count > 0 && 313 if (wavebuffer.loop && voice_state.loop_count > 0 &&
301 wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
302 wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { 314 wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
303 start_offset = wavebuffer.loop_start_offset; 315 start_offset = wavebuffer.loop_start_offset;
304 end_offset = wavebuffer.loop_end_offset; 316 end_offset = wavebuffer.loop_end_offset;
305 } 317 }
306 318
307 DecodeArg decode_arg{.buffer{wavebuffer.buffer}, 319 DecodeArg decode_arg{
308 .buffer_size{wavebuffer.buffer_size}, 320 .buffer{wavebuffer.buffer},
309 .start_offset{start_offset}, 321 .buffer_size{wavebuffer.buffer_size},
310 .end_offset{end_offset}, 322 .start_offset{start_offset},
311 .channel_count{args.channel_count}, 323 .end_offset{end_offset},
312 .coefficients{}, 324 .channel_count{args.channel_count},
313 .adpcm_context{nullptr}, 325 .coefficients{},
314 .target_channel{args.channel}, 326 .adpcm_context{nullptr},
315 .offset{offset}, 327 .target_channel{args.channel},
316 .samples_to_read{samples_to_read - samples_read}}; 328 .offset{offset},
329 .samples_to_read{samples_to_read - samples_read},
330 };
317 331
318 s32 samples_decoded{0}; 332 s32 samples_decoded{0};
319 333
@@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
350 temp_buffer_pos += samples_decoded; 364 temp_buffer_pos += samples_decoded;
351 offset += samples_decoded; 365 offset += samples_decoded;
352 366
353 if (samples_decoded == 0 || offset >= end_offset - start_offset) { 367 if (samples_decoded && offset < end_offset - start_offset) {
354 offset = 0; 368 continue;
355 if (!wavebuffer.loop) { 369 }
356 voice_state.wave_buffer_valid[wavebuffer_index] = false; 370
357 voice_state.loop_count = 0; 371 offset = 0;
358 372 if (wavebuffer.loop) {
359 if (wavebuffer.stream_ended) { 373 voice_state.loop_count++;
360 played_sample_count = 0; 374 if (wavebuffer.loop_count >= 0 &&
361 } 375 (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
362 376 EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
363 wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; 377 wavebuffers_consumed);
364 wavebuffers_consumed++; 378 }
365 } else { 379
366 voice_state.loop_count++; 380 if (samples_decoded == 0) {
367 if (wavebuffer.loop_count > 0 && 381 is_buffer_starved = true;
368 (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { 382 break;
369 voice_state.wave_buffer_valid[wavebuffer_index] = false; 383 }
370 voice_state.loop_count = 0; 384
371 385 if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
372 if (wavebuffer.stream_ended) { 386 played_sample_count = 0;
373 played_sample_count = 0;
374 }
375
376 wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
377 wavebuffers_consumed++;
378 }
379
380 if (samples_decoded == 0) {
381 is_buffer_starved = true;
382 break;
383 }
384
385 if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
386 played_sample_count = 0;
387 }
388 } 387 }
388 } else {
389 EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
390 wavebuffers_consumed);
389 } 391 }
390 } 392 }
391 393
@@ -423,4 +425,4 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
423 voice_state.fraction = fraction; 425 voice_state.fraction = fraction;
424} 426}
425 427
426} // namespace AudioCore::AudioRenderer 428} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/data_source/decode.h b/src/audio_core/renderer/command/data_source/decode.h
index 4d63d6fa8..5f52f32f0 100644
--- a/src/audio_core/renderer/command/data_source/decode.h
+++ b/src/audio_core/renderer/command/data_source/decode.h
@@ -15,7 +15,7 @@ namespace Core::Memory {
15class Memory; 15class Memory;
16} 16}
17 17
18namespace AudioCore::AudioRenderer { 18namespace AudioCore::Renderer {
19 19
20struct DecodeFromWaveBuffersArgs { 20struct DecodeFromWaveBuffersArgs {
21 SampleFormat sample_format; 21 SampleFormat sample_format;
@@ -56,4 +56,4 @@ struct DecodeArg {
56 */ 56 */
57void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); 57void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
58 58
59} // namespace AudioCore::AudioRenderer 59} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp
index be77fab69..d1f685656 100644
--- a/src/audio_core/renderer/command/data_source/pcm_float.cpp
+++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp
@@ -1,13 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/data_source/decode.h" 5#include "audio_core/renderer/command/data_source/decode.h"
6#include "audio_core/renderer/command/data_source/pcm_float.h" 6#include "audio_core/renderer/command/data_source/pcm_float.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, 10void PcmFloatDataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor,
11 std::string& string) { 11 std::string& string) {
12 string += 12 string +=
13 fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " 13 fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
@@ -16,10 +16,17 @@ void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& p
16 processor.target_sample_rate, src_quality); 16 processor.target_sample_rate, src_quality);
17} 17}
18 18
19void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { 19void PcmFloatDataSourceVersion1Command::Process(
20 const AudioRenderer::CommandListProcessor& processor) {
20 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, 21 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
21 processor.sample_count); 22 processor.sample_count);
22 23
24 for (auto& wave_buffer : wave_buffers) {
25 wave_buffer.loop_start_offset = wave_buffer.start_offset;
26 wave_buffer.loop_end_offset = wave_buffer.end_offset;
27 wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
28 }
29
23 DecodeFromWaveBuffersArgs args{ 30 DecodeFromWaveBuffersArgs args{
24 .sample_format{SampleFormat::PcmFloat}, 31 .sample_format{SampleFormat::PcmFloat},
25 .output{out_buffer}, 32 .output{out_buffer},
@@ -41,11 +48,12 @@ void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor
41 DecodeFromWaveBuffers(*processor.memory, args); 48 DecodeFromWaveBuffers(*processor.memory, args);
42} 49}
43 50
44bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { 51bool PcmFloatDataSourceVersion1Command::Verify(
52 const AudioRenderer::CommandListProcessor& processor) {
45 return true; 53 return true;
46} 54}
47 55
48void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, 56void PcmFloatDataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor,
49 std::string& string) { 57 std::string& string) {
50 string += 58 string +=
51 fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} " 59 fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
@@ -54,7 +62,8 @@ void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& p
54 processor.target_sample_rate, src_quality); 62 processor.target_sample_rate, src_quality);
55} 63}
56 64
57void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { 65void PcmFloatDataSourceVersion2Command::Process(
66 const AudioRenderer::CommandListProcessor& processor) {
58 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, 67 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
59 processor.sample_count); 68 processor.sample_count);
60 69
@@ -79,8 +88,9 @@ void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor
79 DecodeFromWaveBuffers(*processor.memory, args); 88 DecodeFromWaveBuffers(*processor.memory, args);
80} 89}
81 90
82bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { 91bool PcmFloatDataSourceVersion2Command::Verify(
92 const AudioRenderer::CommandListProcessor& processor) {
83 return true; 93 return true;
84} 94}
85 95
86} // namespace AudioCore::AudioRenderer 96} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.h b/src/audio_core/renderer/command/data_source/pcm_float.h
index e4af77c20..2c9d1877e 100644
--- a/src/audio_core/renderer/command/data_source/pcm_float.h
+++ b/src/audio_core/renderer/command/data_source/pcm_float.h
@@ -9,11 +9,12 @@
9#include "audio_core/renderer/command/icommand.h" 9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::ADSP::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor; 13class CommandListProcessor;
15} 14}
16 15
16namespace AudioCore::Renderer {
17
17/** 18/**
18 * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers 19 * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
19 * into the output_index mix buffer. 20 * into the output_index mix buffer.
@@ -25,14 +26,14 @@ struct PcmFloatDataSourceVersion1Command : ICommand {
25 * @param processor - The CommandListProcessor processing this command. 26 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into. 27 * @param string - The string to print into.
27 */ 28 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 29 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
29 30
30 /** 31 /**
31 * Process this command. 32 * Process this command.
32 * 33 *
33 * @param processor - The CommandListProcessor processing this command. 34 * @param processor - The CommandListProcessor processing this command.
34 */ 35 */
35 void Process(const ADSP::CommandListProcessor& processor) override; 36 void Process(const AudioRenderer::CommandListProcessor& processor) override;
36 37
37 /** 38 /**
38 * Verify this command's data is valid. 39 * Verify this command's data is valid.
@@ -40,13 +41,13 @@ struct PcmFloatDataSourceVersion1Command : ICommand {
40 * @param processor - The CommandListProcessor processing this command. 41 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false. 42 * @return True if the command is valid, otherwise false.
42 */ 43 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override; 44 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
44 45
45 /// Quality used for sample rate conversion 46 /// Quality used for sample rate conversion
46 SrcQuality src_quality; 47 SrcQuality src_quality;
47 /// Mix buffer index for decoded samples 48 /// Mix buffer index for decoded samples
48 s16 output_index; 49 s16 output_index;
49 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) 50 /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
50 u16 flags; 51 u16 flags;
51 /// Wavebuffer sample rate 52 /// Wavebuffer sample rate
52 u32 sample_rate; 53 u32 sample_rate;
@@ -73,14 +74,14 @@ struct PcmFloatDataSourceVersion2Command : ICommand {
73 * @param processor - The CommandListProcessor processing this command. 74 * @param processor - The CommandListProcessor processing this command.
74 * @param string - The string to print into. 75 * @param string - The string to print into.
75 */ 76 */
76 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 77 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
77 78
78 /** 79 /**
79 * Process this command. 80 * Process this command.
80 * 81 *
81 * @param processor - The CommandListProcessor processing this command. 82 * @param processor - The CommandListProcessor processing this command.
82 */ 83 */
83 void Process(const ADSP::CommandListProcessor& processor) override; 84 void Process(const AudioRenderer::CommandListProcessor& processor) override;
84 85
85 /** 86 /**
86 * Verify this command's data is valid. 87 * Verify this command's data is valid.
@@ -88,13 +89,13 @@ struct PcmFloatDataSourceVersion2Command : ICommand {
88 * @param processor - The CommandListProcessor processing this command. 89 * @param processor - The CommandListProcessor processing this command.
89 * @return True if the command is valid, otherwise false. 90 * @return True if the command is valid, otherwise false.
90 */ 91 */
91 bool Verify(const ADSP::CommandListProcessor& processor) override; 92 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
92 93
93 /// Quality used for sample rate conversion 94 /// Quality used for sample rate conversion
94 SrcQuality src_quality; 95 SrcQuality src_quality;
95 /// Mix buffer index for decoded samples 96 /// Mix buffer index for decoded samples
96 s16 output_index; 97 s16 output_index;
97 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) 98 /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
98 u16 flags; 99 u16 flags;
99 /// Wavebuffer sample rate 100 /// Wavebuffer sample rate
100 u32 sample_rate; 101 u32 sample_rate;
@@ -110,4 +111,4 @@ struct PcmFloatDataSourceVersion2Command : ICommand {
110 CpuAddr voice_state; 111 CpuAddr voice_state;
111}; 112};
112 113
113} // namespace AudioCore::AudioRenderer 114} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp
index 7a27463e4..c89a5aaac 100644
--- a/src/audio_core/renderer/command/data_source/pcm_int16.cpp
+++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp
@@ -3,13 +3,13 @@
3 3
4#include <span> 4#include <span>
5 5
6#include "audio_core/renderer/adsp/command_list_processor.h" 6#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
7#include "audio_core/renderer/command/data_source/decode.h" 7#include "audio_core/renderer/command/data_source/decode.h"
8#include "audio_core/renderer/command/data_source/pcm_int16.h" 8#include "audio_core/renderer/command/data_source/pcm_int16.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, 12void PcmInt16DataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor,
13 std::string& string) { 13 std::string& string) {
14 string += 14 string +=
15 fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} " 15 fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
@@ -18,10 +18,17 @@ void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& p
18 processor.target_sample_rate, src_quality); 18 processor.target_sample_rate, src_quality);
19} 19}
20 20
21void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { 21void PcmInt16DataSourceVersion1Command::Process(
22 const AudioRenderer::CommandListProcessor& processor) {
22 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, 23 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
23 processor.sample_count); 24 processor.sample_count);
24 25
26 for (auto& wave_buffer : wave_buffers) {
27 wave_buffer.loop_start_offset = wave_buffer.start_offset;
28 wave_buffer.loop_end_offset = wave_buffer.end_offset;
29 wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
30 }
31
25 DecodeFromWaveBuffersArgs args{ 32 DecodeFromWaveBuffersArgs args{
26 .sample_format{SampleFormat::PcmInt16}, 33 .sample_format{SampleFormat::PcmInt16},
27 .output{out_buffer}, 34 .output{out_buffer},
@@ -43,11 +50,12 @@ void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor
43 DecodeFromWaveBuffers(*processor.memory, args); 50 DecodeFromWaveBuffers(*processor.memory, args);
44} 51}
45 52
46bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { 53bool PcmInt16DataSourceVersion1Command::Verify(
54 const AudioRenderer::CommandListProcessor& processor) {
47 return true; 55 return true;
48} 56}
49 57
50void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, 58void PcmInt16DataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor,
51 std::string& string) { 59 std::string& string) {
52 string += 60 string +=
53 fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} " 61 fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
@@ -56,7 +64,8 @@ void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& p
56 processor.target_sample_rate, src_quality); 64 processor.target_sample_rate, src_quality);
57} 65}
58 66
59void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { 67void PcmInt16DataSourceVersion2Command::Process(
68 const AudioRenderer::CommandListProcessor& processor) {
60 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, 69 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
61 processor.sample_count); 70 processor.sample_count);
62 DecodeFromWaveBuffersArgs args{ 71 DecodeFromWaveBuffersArgs args{
@@ -80,8 +89,9 @@ void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor
80 DecodeFromWaveBuffers(*processor.memory, args); 89 DecodeFromWaveBuffers(*processor.memory, args);
81} 90}
82 91
83bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { 92bool PcmInt16DataSourceVersion2Command::Verify(
93 const AudioRenderer::CommandListProcessor& processor) {
84 return true; 94 return true;
85} 95}
86 96
87} // namespace AudioCore::AudioRenderer 97} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.h b/src/audio_core/renderer/command/data_source/pcm_int16.h
index 5de1ad60d..2c013f003 100644
--- a/src/audio_core/renderer/command/data_source/pcm_int16.h
+++ b/src/audio_core/renderer/command/data_source/pcm_int16.h
@@ -9,11 +9,12 @@
9#include "audio_core/renderer/command/icommand.h" 9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::ADSP::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor; 13class CommandListProcessor;
15} 14}
16 15
16namespace AudioCore::Renderer {
17
17/** 18/**
18 * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers 19 * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
19 * into the output_index mix buffer. 20 * into the output_index mix buffer.
@@ -25,14 +26,14 @@ struct PcmInt16DataSourceVersion1Command : ICommand {
25 * @param processor - The CommandListProcessor processing this command. 26 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into. 27 * @param string - The string to print into.
27 */ 28 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 29 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
29 30
30 /** 31 /**
31 * Process this command. 32 * Process this command.
32 * 33 *
33 * @param processor - The CommandListProcessor processing this command. 34 * @param processor - The CommandListProcessor processing this command.
34 */ 35 */
35 void Process(const ADSP::CommandListProcessor& processor) override; 36 void Process(const AudioRenderer::CommandListProcessor& processor) override;
36 37
37 /** 38 /**
38 * Verify this command's data is valid. 39 * Verify this command's data is valid.
@@ -40,13 +41,13 @@ struct PcmInt16DataSourceVersion1Command : ICommand {
40 * @param processor - The CommandListProcessor processing this command. 41 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false. 42 * @return True if the command is valid, otherwise false.
42 */ 43 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override; 44 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
44 45
45 /// Quality used for sample rate conversion 46 /// Quality used for sample rate conversion
46 SrcQuality src_quality; 47 SrcQuality src_quality;
47 /// Mix buffer index for decoded samples 48 /// Mix buffer index for decoded samples
48 s16 output_index; 49 s16 output_index;
49 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) 50 /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
50 u16 flags; 51 u16 flags;
51 /// Wavebuffer sample rate 52 /// Wavebuffer sample rate
52 u32 sample_rate; 53 u32 sample_rate;
@@ -72,26 +73,26 @@ struct PcmInt16DataSourceVersion2Command : ICommand {
72 * @param processor - The CommandListProcessor processing this command. 73 * @param processor - The CommandListProcessor processing this command.
73 * @param string - The string to print into. 74 * @param string - The string to print into.
74 */ 75 */
75 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 76 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
76 77
77 /** 78 /**
78 * Process this command. 79 * Process this command.
79 * @param processor - The CommandListProcessor processing this command. 80 * @param processor - The CommandListProcessor processing this command.
80 */ 81 */
81 void Process(const ADSP::CommandListProcessor& processor) override; 82 void Process(const AudioRenderer::CommandListProcessor& processor) override;
82 83
83 /** 84 /**
84 * Verify this command's data is valid. 85 * Verify this command's data is valid.
85 * @param processor - The CommandListProcessor processing this command. 86 * @param processor - The CommandListProcessor processing this command.
86 * @return True if the command is valid, otherwise false. 87 * @return True if the command is valid, otherwise false.
87 */ 88 */
88 bool Verify(const ADSP::CommandListProcessor& processor) override; 89 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
89 90
90 /// Quality used for sample rate conversion 91 /// Quality used for sample rate conversion
91 SrcQuality src_quality; 92 SrcQuality src_quality;
92 /// Mix buffer index for decoded samples 93 /// Mix buffer index for decoded samples
93 s16 output_index; 94 s16 output_index;
94 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) 95 /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
95 u16 flags; 96 u16 flags;
96 /// Wavebuffer sample rate 97 /// Wavebuffer sample rate
97 u32 sample_rate; 98 u32 sample_rate;
@@ -107,4 +108,4 @@ struct PcmInt16DataSourceVersion2Command : ICommand {
107 CpuAddr voice_state; 108 CpuAddr voice_state;
108}; 109};
109 110
110} // namespace AudioCore::AudioRenderer 111} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/aux_.cpp b/src/audio_core/renderer/command/effect/aux_.cpp
index a3e12b3e7..74d9c229f 100644
--- a/src/audio_core/renderer/command/effect/aux_.cpp
+++ b/src/audio_core/renderer/command/effect/aux_.cpp
@@ -1,13 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/effect/aux_.h" 5#include "audio_core/renderer/command/effect/aux_.h"
6#include "audio_core/renderer/effect/aux_.h" 6#include "audio_core/renderer/effect/aux_.h"
7#include "core/core.h" 7#include "core/core.h"
8#include "core/memory.h" 8#include "core/memory.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11/** 11/**
12 * Reset an AuxBuffer. 12 * Reset an AuxBuffer.
13 * 13 *
@@ -175,13 +175,13 @@ static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr return_info_,
175 return read_count_; 175 return read_count_;
176} 176}
177 177
178void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 178void AuxCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
179 std::string& string) { 179 std::string& string) {
180 string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, 180 string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
181 input, output); 181 input, output);
182} 182}
183 183
184void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { 184void AuxCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
185 auto input_buffer{ 185 auto input_buffer{
186 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; 186 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
187 auto output_buffer{ 187 auto output_buffer{
@@ -208,8 +208,8 @@ void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
208 } 208 }
209} 209}
210 210
211bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) { 211bool AuxCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
212 return true; 212 return true;
213} 213}
214 214
215} // namespace AudioCore::AudioRenderer 215} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/aux_.h b/src/audio_core/renderer/command/effect/aux_.h
index 825c93732..da1e55261 100644
--- a/src/audio_core/renderer/command/effect/aux_.h
+++ b/src/audio_core/renderer/command/effect/aux_.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game 18 * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
18 * memory, and reading into the output buffer from game memory. 19 * memory, and reading into the output buffer from game memory.
@@ -24,14 +25,14 @@ struct AuxCommand : ICommand {
24 * @param processor - The CommandListProcessor processing this command. 25 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into. 26 * @param string - The string to print into.
26 */ 27 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 28 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
28 29
29 /** 30 /**
30 * Process this command. 31 * Process this command.
31 * 32 *
32 * @param processor - The CommandListProcessor processing this command. 33 * @param processor - The CommandListProcessor processing this command.
33 */ 34 */
34 void Process(const ADSP::CommandListProcessor& processor) override; 35 void Process(const AudioRenderer::CommandListProcessor& processor) override;
35 36
36 /** 37 /**
37 * Verify this command's data is valid. 38 * Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct AuxCommand : ICommand {
39 * @param processor - The CommandListProcessor processing this command. 40 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false. 41 * @return True if the command is valid, otherwise false.
41 */ 42 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override; 43 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
43 44
44 /// Input mix buffer index 45 /// Input mix buffer index
45 s16 input; 46 s16 input;
@@ -63,4 +64,4 @@ struct AuxCommand : ICommand {
63 bool effect_enabled; 64 bool effect_enabled;
64}; 65};
65 66
66} // namespace AudioCore::AudioRenderer 67} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp
index dea6423dc..3392e7747 100644
--- a/src/audio_core/renderer/command/effect/biquad_filter.cpp
+++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp
@@ -1,12 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/effect/biquad_filter.h" 5#include "audio_core/renderer/command/effect/biquad_filter.h"
6#include "audio_core/renderer/voice/voice_state.h" 6#include "audio_core/renderer/voice/voice_state.h"
7#include "common/bit_cast.h" 7#include "common/bit_cast.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10/** 10/**
11 * Biquad filter float implementation. 11 * Biquad filter float implementation.
12 * 12 *
@@ -76,14 +76,14 @@ static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> inp
76 } 76 }
77} 77}
78 78
79void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 79void BiquadFilterCommand::Dump(
80 std::string& string) { 80 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
81 string += fmt::format( 81 string += fmt::format(
82 "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n", 82 "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
83 input, output, needs_init, use_float_processing); 83 input, output, needs_init, use_float_processing);
84} 84}
85 85
86void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { 86void BiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
87 auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)}; 87 auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
88 if (needs_init) { 88 if (needs_init) {
89 *state_ = {}; 89 *state_ = {};
@@ -103,8 +103,8 @@ void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
103 } 103 }
104} 104}
105 105
106bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { 106bool BiquadFilterCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
107 return true; 107 return true;
108} 108}
109 109
110} // namespace AudioCore::AudioRenderer 110} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h
index 4c9c42d29..0e903930a 100644
--- a/src/audio_core/renderer/command/effect/biquad_filter.h
+++ b/src/audio_core/renderer/command/effect/biquad_filter.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/voice/voice_state.h" 10#include "audio_core/renderer/voice/voice_state.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to 20 * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
20 * the output mix buffer. 21 * the output mix buffer.
@@ -26,14 +27,14 @@ struct BiquadFilterCommand : ICommand {
26 * @param processor - The CommandListProcessor processing this command. 27 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into. 28 * @param string - The string to print into.
28 */ 29 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 30 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
30 31
31 /** 32 /**
32 * Process this command. 33 * Process this command.
33 * 34 *
34 * @param processor - The CommandListProcessor processing this command. 35 * @param processor - The CommandListProcessor processing this command.
35 */ 36 */
36 void Process(const ADSP::CommandListProcessor& processor) override; 37 void Process(const AudioRenderer::CommandListProcessor& processor) override;
37 38
38 /** 39 /**
39 * Verify this command's data is valid. 40 * Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct BiquadFilterCommand : ICommand {
41 * @param processor - The CommandListProcessor processing this command. 42 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false. 43 * @return True if the command is valid, otherwise false.
43 */ 44 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override; 45 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
45 46
46 /// Input mix buffer index 47 /// Input mix buffer index
47 s16 input; 48 s16 input;
@@ -71,4 +72,4 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
71 std::array<s16, 3>& b, std::array<s16, 2>& a, 72 std::array<s16, 3>& b, std::array<s16, 2>& a,
72 VoiceState::BiquadFilterState& state, const u32 sample_count); 73 VoiceState::BiquadFilterState& state, const u32 sample_count);
73 74
74} // namespace AudioCore::AudioRenderer 75} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/capture.cpp b/src/audio_core/renderer/command/effect/capture.cpp
index 042fd286e..f235ce027 100644
--- a/src/audio_core/renderer/command/effect/capture.cpp
+++ b/src/audio_core/renderer/command/effect/capture.cpp
@@ -1,12 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/effect/capture.h" 5#include "audio_core/renderer/command/effect/capture.h"
6#include "audio_core/renderer/effect/aux_.h" 6#include "audio_core/renderer/effect/aux_.h"
7#include "core/memory.h" 7#include "core/memory.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10/** 10/**
11 * Reset an AuxBuffer. 11 * Reset an AuxBuffer.
12 * 12 *
@@ -118,13 +118,13 @@ static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_in
118 return write_count_; 118 return write_count_;
119} 119}
120 120
121void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 121void CaptureCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
122 std::string& string) { 122 std::string& string) {
123 string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, 123 string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
124 input, output); 124 input, output);
125} 125}
126 126
127void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { 127void CaptureCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
128 if (effect_enabled) { 128 if (effect_enabled) {
129 auto input_buffer{ 129 auto input_buffer{
130 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; 130 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
@@ -135,8 +135,8 @@ void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
135 } 135 }
136} 136}
137 137
138bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) { 138bool CaptureCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
139 return true; 139 return true;
140} 140}
141 141
142} // namespace AudioCore::AudioRenderer 142} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/capture.h b/src/audio_core/renderer/command/effect/capture.h
index 8670acb24..a0016c6f6 100644
--- a/src/audio_core/renderer/command/effect/capture.h
+++ b/src/audio_core/renderer/command/effect/capture.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory 18 * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
18 * address. 19 * address.
@@ -24,14 +25,14 @@ struct CaptureCommand : ICommand {
24 * @param processor - The CommandListProcessor processing this command. 25 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into. 26 * @param string - The string to print into.
26 */ 27 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 28 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
28 29
29 /** 30 /**
30 * Process this command. 31 * Process this command.
31 * 32 *
32 * @param processor - The CommandListProcessor processing this command. 33 * @param processor - The CommandListProcessor processing this command.
33 */ 34 */
34 void Process(const ADSP::CommandListProcessor& processor) override; 35 void Process(const AudioRenderer::CommandListProcessor& processor) override;
35 36
36 /** 37 /**
37 * Verify this command's data is valid. 38 * Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct CaptureCommand : ICommand {
39 * @param processor - The CommandListProcessor processing this command. 40 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false. 41 * @return True if the command is valid, otherwise false.
41 */ 42 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override; 43 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
43 44
44 /// Input mix buffer index 45 /// Input mix buffer index
45 s16 input; 46 s16 input;
@@ -59,4 +60,4 @@ struct CaptureCommand : ICommand {
59 bool effect_enabled; 60 bool effect_enabled;
60}; 61};
61 62
62} // namespace AudioCore::AudioRenderer 63} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp
index ee9b68d5b..7ff707f4e 100644
--- a/src/audio_core/renderer/command/effect/compressor.cpp
+++ b/src/audio_core/renderer/command/effect/compressor.cpp
@@ -5,11 +5,11 @@
5#include <span> 5#include <span>
6#include <vector> 6#include <vector>
7 7
8#include "audio_core/renderer/adsp/command_list_processor.h" 8#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
9#include "audio_core/renderer/command/effect/compressor.h" 9#include "audio_core/renderer/command/effect/compressor.h"
10#include "audio_core/renderer/effect/compressor.h" 10#include "audio_core/renderer/effect/compressor.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13 13
14static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params, 14static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
15 CompressorInfo::State& state) { 15 CompressorInfo::State& state) {
@@ -110,7 +110,7 @@ static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& param
110 } 110 }
111} 111}
112 112
113void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 113void CompressorCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
114 std::string& string) { 114 std::string& string) {
115 string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled); 115 string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
116 for (s16 i = 0; i < parameter.channel_count; i++) { 116 for (s16 i = 0; i < parameter.channel_count; i++) {
@@ -123,7 +123,7 @@ void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
123 string += "\n"; 123 string += "\n";
124} 124}
125 125
126void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { 126void CompressorCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
127 std::array<std::span<const s32>, MaxChannels> input_buffers{}; 127 std::array<std::span<const s32>, MaxChannels> input_buffers{};
128 std::array<std::span<s32>, MaxChannels> output_buffers{}; 128 std::array<std::span<s32>, MaxChannels> output_buffers{};
129 129
@@ -148,8 +148,8 @@ void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
148 processor.sample_count); 148 processor.sample_count);
149} 149}
150 150
151bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) { 151bool CompressorCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
152 return true; 152 return true;
153} 153}
154 154
155} // namespace AudioCore::AudioRenderer 155} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h
index f8e96cb43..c011aa927 100644
--- a/src/audio_core/renderer/command/effect/compressor.h
+++ b/src/audio_core/renderer/command/effect/compressor.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/effect/compressor.h" 10#include "audio_core/renderer/effect/compressor.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for limiting volume between a high and low threshold. 20 * AudioRenderer command for limiting volume between a high and low threshold.
20 * Version 1. 21 * Version 1.
@@ -26,14 +27,14 @@ struct CompressorCommand : ICommand {
26 * @param processor - The CommandListProcessor processing this command. 27 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into. 28 * @param string - The string to print into.
28 */ 29 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 30 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
30 31
31 /** 32 /**
32 * Process this command. 33 * Process this command.
33 * 34 *
34 * @param processor - The CommandListProcessor processing this command. 35 * @param processor - The CommandListProcessor processing this command.
35 */ 36 */
36 void Process(const ADSP::CommandListProcessor& processor) override; 37 void Process(const AudioRenderer::CommandListProcessor& processor) override;
37 38
38 /** 39 /**
39 * Verify this command's data is valid. 40 * Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct CompressorCommand : ICommand {
41 * @param processor - The CommandListProcessor processing this command. 42 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false. 43 * @return True if the command is valid, otherwise false.
43 */ 44 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override; 45 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
45 46
46 /// Input mix buffer offsets for each channel 47 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs; 48 std::array<s16, MaxChannels> inputs;
@@ -57,4 +58,4 @@ struct CompressorCommand : ICommand {
57 bool effect_enabled; 58 bool effect_enabled;
58}; 59};
59 60
60} // namespace AudioCore::AudioRenderer 61} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/delay.cpp b/src/audio_core/renderer/command/effect/delay.cpp
index e536cbb1e..ffb298c07 100644
--- a/src/audio_core/renderer/command/effect/delay.cpp
+++ b/src/audio_core/renderer/command/effect/delay.cpp
@@ -1,10 +1,10 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/effect/delay.h" 5#include "audio_core/renderer/command/effect/delay.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8/** 8/**
9 * Update the DelayInfo state according to the given parameters. 9 * Update the DelayInfo state according to the given parameters.
10 * 10 *
@@ -194,7 +194,7 @@ static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayIn
194 } 194 }
195} 195}
196 196
197void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 197void DelayCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
198 std::string& string) { 198 std::string& string) {
199 string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled); 199 string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
200 for (u32 i = 0; i < MaxChannels; i++) { 200 for (u32 i = 0; i < MaxChannels; i++) {
@@ -207,7 +207,7 @@ void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proce
207 string += "\n"; 207 string += "\n";
208} 208}
209 209
210void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { 210void DelayCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
211 std::array<std::span<const s32>, MaxChannels> input_buffers{}; 211 std::array<std::span<const s32>, MaxChannels> input_buffers{};
212 std::array<std::span<s32>, MaxChannels> output_buffers{}; 212 std::array<std::span<s32>, MaxChannels> output_buffers{};
213 213
@@ -231,8 +231,8 @@ void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
231 processor.sample_count); 231 processor.sample_count);
232} 232}
233 233
234bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) { 234bool DelayCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
235 return true; 235 return true;
236} 236}
237 237
238} // namespace AudioCore::AudioRenderer 238} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/delay.h b/src/audio_core/renderer/command/effect/delay.h
index b7a15ae6b..bfeac7af4 100644
--- a/src/audio_core/renderer/command/effect/delay.h
+++ b/src/audio_core/renderer/command/effect/delay.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/effect/delay.h" 10#include "audio_core/renderer/effect/delay.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters 20 * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
20 * and state, outputs receives the delayed samples. 21 * and state, outputs receives the delayed samples.
@@ -26,14 +27,14 @@ struct DelayCommand : ICommand {
26 * @param processor - The CommandListProcessor processing this command. 27 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into. 28 * @param string - The string to print into.
28 */ 29 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 30 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
30 31
31 /** 32 /**
32 * Process this command. 33 * Process this command.
33 * 34 *
34 * @param processor - The CommandListProcessor processing this command. 35 * @param processor - The CommandListProcessor processing this command.
35 */ 36 */
36 void Process(const ADSP::CommandListProcessor& processor) override; 37 void Process(const AudioRenderer::CommandListProcessor& processor) override;
37 38
38 /** 39 /**
39 * Verify this command's data is valid. 40 * Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct DelayCommand : ICommand {
41 * @param processor - The CommandListProcessor processing this command. 42 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false. 43 * @return True if the command is valid, otherwise false.
43 */ 44 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override; 45 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
45 46
46 /// Input mix buffer offsets for each channel 47 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs; 48 std::array<s16, MaxChannels> inputs;
@@ -57,4 +58,4 @@ struct DelayCommand : ICommand {
57 bool effect_enabled; 58 bool effect_enabled;
58}; 59};
59 60
60} // namespace AudioCore::AudioRenderer 61} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
index d2bfb67cc..ecfdfabc6 100644
--- a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
+++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
@@ -3,11 +3,11 @@
3 3
4#include <numbers> 4#include <numbers>
5 5
6#include "audio_core/renderer/adsp/command_list_processor.h" 6#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
7#include "audio_core/renderer/command/effect/i3dl2_reverb.h" 7#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
8#include "common/polyfill_ranges.h" 8#include "common/polyfill_ranges.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{ 12constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
13 5.0f, 13 5.0f,
@@ -394,7 +394,7 @@ static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& par
394 } 394 }
395} 395}
396 396
397void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 397void I3dl2ReverbCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
398 std::string& string) { 398 std::string& string) {
399 string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled); 399 string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
400 for (u32 i = 0; i < parameter.channel_count; i++) { 400 for (u32 i = 0; i < parameter.channel_count; i++) {
@@ -407,7 +407,7 @@ void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
407 string += "\n"; 407 string += "\n";
408} 408}
409 409
410void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { 410void I3dl2ReverbCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
411 std::array<std::span<const s32>, MaxChannels> input_buffers{}; 411 std::array<std::span<const s32>, MaxChannels> input_buffers{};
412 std::array<std::span<s32>, MaxChannels> output_buffers{}; 412 std::array<std::span<s32>, MaxChannels> output_buffers{};
413 413
@@ -431,8 +431,8 @@ void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
431 processor.sample_count); 431 processor.sample_count);
432} 432}
433 433
434bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { 434bool I3dl2ReverbCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
435 return true; 435 return true;
436} 436}
437 437
438} // namespace AudioCore::AudioRenderer 438} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.h b/src/audio_core/renderer/command/effect/i3dl2_reverb.h
index 243877056..e4c538ae8 100644
--- a/src/audio_core/renderer/command/effect/i3dl2_reverb.h
+++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/effect/i3dl2.h" 10#include "audio_core/renderer/effect/i3dl2.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to 20 * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
20 * the I3DL2 spec, outputs receives the results. 21 * the I3DL2 spec, outputs receives the results.
@@ -26,14 +27,14 @@ struct I3dl2ReverbCommand : ICommand {
26 * @param processor - The CommandListProcessor processing this command. 27 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into. 28 * @param string - The string to print into.
28 */ 29 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 30 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
30 31
31 /** 32 /**
32 * Process this command. 33 * Process this command.
33 * 34 *
34 * @param processor - The CommandListProcessor processing this command. 35 * @param processor - The CommandListProcessor processing this command.
35 */ 36 */
36 void Process(const ADSP::CommandListProcessor& processor) override; 37 void Process(const AudioRenderer::CommandListProcessor& processor) override;
37 38
38 /** 39 /**
39 * Verify this command's data is valid. 40 * Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct I3dl2ReverbCommand : ICommand {
41 * @param processor - The CommandListProcessor processing this command. 42 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false. 43 * @return True if the command is valid, otherwise false.
43 */ 44 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override; 45 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
45 46
46 /// Input mix buffer offsets for each channel 47 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs; 48 std::array<s16, MaxChannels> inputs;
@@ -57,4 +58,4 @@ struct I3dl2ReverbCommand : ICommand {
57 bool effect_enabled; 58 bool effect_enabled;
58}; 59};
59 60
60} // namespace AudioCore::AudioRenderer 61} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp
index 4161a9821..63aa06f5c 100644
--- a/src/audio_core/renderer/command/effect/light_limiter.cpp
+++ b/src/audio_core/renderer/command/effect/light_limiter.cpp
@@ -1,10 +1,10 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/effect/light_limiter.h" 5#include "audio_core/renderer/command/effect/light_limiter.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8/** 8/**
9 * Update the LightLimiterInfo state according to the given parameters. 9 * Update the LightLimiterInfo state according to the given parameters.
10 * A no-op. 10 * A no-op.
@@ -133,8 +133,8 @@ static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& p
133 } 133 }
134} 134}
135 135
136void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 136void LightLimiterVersion1Command::Dump(
137 std::string& string) { 137 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
138 string += fmt::format("LightLimiterVersion1Command\n\tinputs: "); 138 string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
139 for (u32 i = 0; i < MaxChannels; i++) { 139 for (u32 i = 0; i < MaxChannels; i++) {
140 string += fmt::format("{:02X}, ", inputs[i]); 140 string += fmt::format("{:02X}, ", inputs[i]);
@@ -146,7 +146,7 @@ void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListP
146 string += "\n"; 146 string += "\n";
147} 147}
148 148
149void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) { 149void LightLimiterVersion1Command::Process(const AudioRenderer::CommandListProcessor& processor) {
150 std::array<std::span<const s32>, MaxChannels> input_buffers{}; 150 std::array<std::span<const s32>, MaxChannels> input_buffers{};
151 std::array<std::span<s32>, MaxChannels> output_buffers{}; 151 std::array<std::span<s32>, MaxChannels> output_buffers{};
152 152
@@ -172,12 +172,12 @@ void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& proc
172 processor.sample_count, statistics); 172 processor.sample_count, statistics);
173} 173}
174 174
175bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { 175bool LightLimiterVersion1Command::Verify(const AudioRenderer::CommandListProcessor& processor) {
176 return true; 176 return true;
177} 177}
178 178
179void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 179void LightLimiterVersion2Command::Dump(
180 std::string& string) { 180 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
181 string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n"); 181 string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
182 for (u32 i = 0; i < MaxChannels; i++) { 182 for (u32 i = 0; i < MaxChannels; i++) {
183 string += fmt::format("{:02X}, ", inputs[i]); 183 string += fmt::format("{:02X}, ", inputs[i]);
@@ -189,7 +189,7 @@ void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListP
189 string += "\n"; 189 string += "\n";
190} 190}
191 191
192void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) { 192void LightLimiterVersion2Command::Process(const AudioRenderer::CommandListProcessor& processor) {
193 std::array<std::span<const s32>, MaxChannels> input_buffers{}; 193 std::array<std::span<const s32>, MaxChannels> input_buffers{};
194 std::array<std::span<s32>, MaxChannels> output_buffers{}; 194 std::array<std::span<s32>, MaxChannels> output_buffers{};
195 195
@@ -215,8 +215,8 @@ void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& proc
215 processor.sample_count, statistics); 215 processor.sample_count, statistics);
216} 216}
217 217
218bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { 218bool LightLimiterVersion2Command::Verify(const AudioRenderer::CommandListProcessor& processor) {
219 return true; 219 return true;
220} 220}
221 221
222} // namespace AudioCore::AudioRenderer 222} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/light_limiter.h b/src/audio_core/renderer/command/effect/light_limiter.h
index 5d98272c7..6e3ee1b53 100644
--- a/src/audio_core/renderer/command/effect/light_limiter.h
+++ b/src/audio_core/renderer/command/effect/light_limiter.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/effect/light_limiter.h" 10#include "audio_core/renderer/effect/light_limiter.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for limiting volume between a high and low threshold. 20 * AudioRenderer command for limiting volume between a high and low threshold.
20 * Version 1. 21 * Version 1.
@@ -26,14 +27,14 @@ struct LightLimiterVersion1Command : ICommand {
26 * @param processor - The CommandListProcessor processing this command. 27 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into. 28 * @param string - The string to print into.
28 */ 29 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 30 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
30 31
31 /** 32 /**
32 * Process this command. 33 * Process this command.
33 * 34 *
34 * @param processor - The CommandListProcessor processing this command. 35 * @param processor - The CommandListProcessor processing this command.
35 */ 36 */
36 void Process(const ADSP::CommandListProcessor& processor) override; 37 void Process(const AudioRenderer::CommandListProcessor& processor) override;
37 38
38 /** 39 /**
39 * Verify this command's data is valid. 40 * Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct LightLimiterVersion1Command : ICommand {
41 * @param processor - The CommandListProcessor processing this command. 42 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false. 43 * @return True if the command is valid, otherwise false.
43 */ 44 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override; 45 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
45 46
46 /// Input mix buffer offsets for each channel 47 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs; 48 std::array<s16, MaxChannels> inputs;
@@ -68,21 +69,21 @@ struct LightLimiterVersion2Command : ICommand {
68 * @param processor - The CommandListProcessor processing this command. 69 * @param processor - The CommandListProcessor processing this command.
69 * @param string - The string to print into. 70 * @param string - The string to print into.
70 */ 71 */
71 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 72 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
72 73
73 /** 74 /**
74 * Process this command. 75 * Process this command.
75 * 76 *
76 * @param processor - The CommandListProcessor processing this command. 77 * @param processor - The CommandListProcessor processing this command.
77 */ 78 */
78 void Process(const ADSP::CommandListProcessor& processor) override; 79 void Process(const AudioRenderer::CommandListProcessor& processor) override;
79 80
80 /** 81 /**
81 * Verify this command's data is valid. 82 * Verify this command's data is valid.
82 * 83 *
83 * @param processor - The CommandListProcessor processing this command. 84 * @param processor - The CommandListProcessor processing this command.
84 */ 85 */
85 bool Verify(const ADSP::CommandListProcessor& processor) override; 86 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
86 87
87 /// Input mix buffer offsets for each channel 88 /// Input mix buffer offsets for each channel
88 std::array<s16, MaxChannels> inputs; 89 std::array<s16, MaxChannels> inputs;
@@ -100,4 +101,4 @@ struct LightLimiterVersion2Command : ICommand {
100 bool effect_enabled; 101 bool effect_enabled;
101}; 102};
102 103
103} // namespace AudioCore::AudioRenderer 104} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
index 48a7cba8a..208bbeaf2 100644
--- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
+++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
@@ -1,20 +1,20 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/effect/biquad_filter.h" 5#include "audio_core/renderer/command/effect/biquad_filter.h"
6#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" 6#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 10void MultiTapBiquadFilterCommand::Dump(
11 std::string& string) { 11 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
12 string += fmt::format( 12 string += fmt::format(
13 "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n", 13 "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
14 input, output, needs_init[0], needs_init[1]); 14 input, output, needs_init[0], needs_init[1]);
15} 15}
16 16
17void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { 17void MultiTapBiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
18 if (filter_tap_count > MaxBiquadFilters) { 18 if (filter_tap_count > MaxBiquadFilters) {
19 LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count); 19 LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
20 filter_tap_count = MaxBiquadFilters; 20 filter_tap_count = MaxBiquadFilters;
@@ -38,8 +38,8 @@ void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& proc
38 } 38 }
39} 39}
40 40
41bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { 41bool MultiTapBiquadFilterCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
42 return true; 42 return true;
43} 43}
44 44
45} // namespace AudioCore::AudioRenderer 45} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
index 99c2c0830..50fce80b0 100644
--- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
+++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/voice/voice_info.h" 10#include "audio_core/renderer/voice/voice_info.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for applying multiple biquads at once. 20 * AudioRenderer command for applying multiple biquads at once.
20 */ 21 */
@@ -25,14 +26,14 @@ struct MultiTapBiquadFilterCommand : ICommand {
25 * @param processor - The CommandListProcessor processing this command. 26 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into. 27 * @param string - The string to print into.
27 */ 28 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 29 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
29 30
30 /** 31 /**
31 * Process this command. 32 * Process this command.
32 * 33 *
33 * @param processor - The CommandListProcessor processing this command. 34 * @param processor - The CommandListProcessor processing this command.
34 */ 35 */
35 void Process(const ADSP::CommandListProcessor& processor) override; 36 void Process(const AudioRenderer::CommandListProcessor& processor) override;
36 37
37 /** 38 /**
38 * Verify this command's data is valid. 39 * Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct MultiTapBiquadFilterCommand : ICommand {
40 * @param processor - The CommandListProcessor processing this command. 41 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false. 42 * @return True if the command is valid, otherwise false.
42 */ 43 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override; 44 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
44 45
45 /// Input mix buffer index 46 /// Input mix buffer index
46 s16 input; 47 s16 input;
@@ -56,4 +57,4 @@ struct MultiTapBiquadFilterCommand : ICommand {
56 u8 filter_tap_count; 57 u8 filter_tap_count;
57}; 58};
58 59
59} // namespace AudioCore::AudioRenderer 60} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/reverb.cpp b/src/audio_core/renderer/command/effect/reverb.cpp
index fc2f15a5e..7f152a962 100644
--- a/src/audio_core/renderer/command/effect/reverb.cpp
+++ b/src/audio_core/renderer/command/effect/reverb.cpp
@@ -4,11 +4,11 @@
4#include <numbers> 4#include <numbers>
5#include <ranges> 5#include <ranges>
6 6
7#include "audio_core/renderer/adsp/command_list_processor.h" 7#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
8#include "audio_core/renderer/command/effect/reverb.h" 8#include "audio_core/renderer/command/effect/reverb.h"
9#include "common/polyfill_ranges.h" 9#include "common/polyfill_ranges.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::Renderer {
12 12
13constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = { 13constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
14 53.9532470703125f, 14 53.9532470703125f,
@@ -396,7 +396,7 @@ static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, Rever
396 } 396 }
397} 397}
398 398
399void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 399void ReverbCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
400 std::string& string) { 400 std::string& string) {
401 string += fmt::format( 401 string += fmt::format(
402 "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled, 402 "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled,
@@ -411,7 +411,7 @@ void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc
411 string += "\n"; 411 string += "\n";
412} 412}
413 413
414void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { 414void ReverbCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
415 std::array<std::span<const s32>, MaxChannels> input_buffers{}; 415 std::array<std::span<const s32>, MaxChannels> input_buffers{};
416 std::array<std::span<s32>, MaxChannels> output_buffers{}; 416 std::array<std::span<s32>, MaxChannels> output_buffers{};
417 417
@@ -435,8 +435,8 @@ void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
435 processor.sample_count); 435 processor.sample_count);
436} 436}
437 437
438bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { 438bool ReverbCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
439 return true; 439 return true;
440} 440}
441 441
442} // namespace AudioCore::AudioRenderer 442} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/effect/reverb.h b/src/audio_core/renderer/command/effect/reverb.h
index 328756150..2056c73f2 100644
--- a/src/audio_core/renderer/command/effect/reverb.h
+++ b/src/audio_core/renderer/command/effect/reverb.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/effect/reverb.h" 10#include "audio_core/renderer/effect/reverb.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives 20 * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
20 * the results. 21 * the results.
@@ -26,14 +27,14 @@ struct ReverbCommand : ICommand {
26 * @param processor - The CommandListProcessor processing this command. 27 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into. 28 * @param string - The string to print into.
28 */ 29 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 30 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
30 31
31 /** 32 /**
32 * Process this command. 33 * Process this command.
33 * 34 *
34 * @param processor - The CommandListProcessor processing this command. 35 * @param processor - The CommandListProcessor processing this command.
35 */ 36 */
36 void Process(const ADSP::CommandListProcessor& processor) override; 37 void Process(const AudioRenderer::CommandListProcessor& processor) override;
37 38
38 /** 39 /**
39 * Verify this command's data is valid. 40 * Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct ReverbCommand : ICommand {
41 * @param processor - The CommandListProcessor processing this command. 42 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false. 43 * @return True if the command is valid, otherwise false.
43 */ 44 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override; 45 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
45 46
46 /// Input mix buffer offsets for each channel 47 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs; 48 std::array<s16, MaxChannels> inputs;
@@ -59,4 +60,4 @@ struct ReverbCommand : ICommand {
59 bool long_size_pre_delay_supported; 60 bool long_size_pre_delay_supported;
60}; 61};
61 62
62} // namespace AudioCore::AudioRenderer 63} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/icommand.h b/src/audio_core/renderer/command/icommand.h
index f2dd41254..10a78ddf2 100644
--- a/src/audio_core/renderer/command/icommand.h
+++ b/src/audio_core/renderer/command/icommand.h
@@ -3,14 +3,18 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <string>
7
6#include "audio_core/common/common.h" 8#include "audio_core/common/common.h"
7#include "common/common_types.h" 9#include "common/common_types.h"
8 10
9namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
10namespace ADSP {
11class CommandListProcessor; 12class CommandListProcessor;
12} 13}
13 14
15namespace AudioCore::Renderer {
16using namespace ::AudioCore::ADSP;
17
14enum class CommandId : u8 { 18enum class CommandId : u8 {
15 /* 0x00 */ Invalid, 19 /* 0x00 */ Invalid,
16 /* 0x01 */ DataSourcePcmInt16Version1, 20 /* 0x01 */ DataSourcePcmInt16Version1,
@@ -59,14 +63,15 @@ struct ICommand {
59 * @param processor - The CommandListProcessor processing this command. 63 * @param processor - The CommandListProcessor processing this command.
60 * @param string - The string to print into. 64 * @param string - The string to print into.
61 */ 65 */
62 virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0; 66 virtual void Dump(const AudioRenderer::CommandListProcessor& processor,
67 std::string& string) = 0;
63 68
64 /** 69 /**
65 * Process this command. 70 * Process this command.
66 * 71 *
67 * @param processor - The CommandListProcessor processing this command. 72 * @param processor - The CommandListProcessor processing this command.
68 */ 73 */
69 virtual void Process(const ADSP::CommandListProcessor& processor) = 0; 74 virtual void Process(const AudioRenderer::CommandListProcessor& processor) = 0;
70 75
71 /** 76 /**
72 * Verify this command's data is valid. 77 * Verify this command's data is valid.
@@ -74,7 +79,7 @@ struct ICommand {
74 * @param processor - The CommandListProcessor processing this command. 79 * @param processor - The CommandListProcessor processing this command.
75 * @return True if the command is valid, otherwise false. 80 * @return True if the command is valid, otherwise false.
76 */ 81 */
77 virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0; 82 virtual bool Verify(const AudioRenderer::CommandListProcessor& processor) = 0;
78 83
79 /// Command magic 0xCAFEBABE 84 /// Command magic 0xCAFEBABE
80 u32 magic{}; 85 u32 magic{};
@@ -90,4 +95,4 @@ struct ICommand {
90 u32 node_id{}; 95 u32 node_id{};
91}; 96};
92 97
93} // namespace AudioCore::AudioRenderer 98} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/clear_mix.cpp b/src/audio_core/renderer/command/mix/clear_mix.cpp
index 4f649d6a8..060d7cb28 100644
--- a/src/audio_core/renderer/command/mix/clear_mix.cpp
+++ b/src/audio_core/renderer/command/mix/clear_mix.cpp
@@ -3,22 +3,22 @@
3 3
4#include <string> 4#include <string>
5 5
6#include "audio_core/renderer/adsp/command_list_processor.h" 6#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
7#include "audio_core/renderer/command/mix/clear_mix.h" 7#include "audio_core/renderer/command/mix/clear_mix.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 11void ClearMixBufferCommand::Dump(
12 std::string& string) { 12 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
13 string += fmt::format("ClearMixBufferCommand\n"); 13 string += fmt::format("ClearMixBufferCommand\n");
14} 14}
15 15
16void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { 16void ClearMixBufferCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
17 memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes()); 17 memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
18} 18}
19 19
20bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { 20bool ClearMixBufferCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
21 return true; 21 return true;
22} 22}
23 23
24} // namespace AudioCore::AudioRenderer 24} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/clear_mix.h b/src/audio_core/renderer/command/mix/clear_mix.h
index 956ec0b65..650fa1a8a 100644
--- a/src/audio_core/renderer/command/mix/clear_mix.h
+++ b/src/audio_core/renderer/command/mix/clear_mix.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for a clearing the mix buffers. 18 * AudioRenderer command for a clearing the mix buffers.
18 * Used at the start of each command list. 19 * Used at the start of each command list.
@@ -24,14 +25,14 @@ struct ClearMixBufferCommand : ICommand {
24 * @param processor - The CommandListProcessor processing this command. 25 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into. 26 * @param string - The string to print into.
26 */ 27 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 28 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
28 29
29 /** 30 /**
30 * Process this command. 31 * Process this command.
31 * 32 *
32 * @param processor - The CommandListProcessor processing this command. 33 * @param processor - The CommandListProcessor processing this command.
33 */ 34 */
34 void Process(const ADSP::CommandListProcessor& processor) override; 35 void Process(const AudioRenderer::CommandListProcessor& processor) override;
35 36
36 /** 37 /**
37 * Verify this command's data is valid. 38 * Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct ClearMixBufferCommand : ICommand {
39 * @param processor - The CommandListProcessor processing this command. 40 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false. 41 * @return True if the command is valid, otherwise false.
41 */ 42 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override; 43 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
43}; 44};
44 45
45} // namespace AudioCore::AudioRenderer 46} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/copy_mix.cpp b/src/audio_core/renderer/command/mix/copy_mix.cpp
index 1d49f1644..5d386f95a 100644
--- a/src/audio_core/renderer/command/mix/copy_mix.cpp
+++ b/src/audio_core/renderer/command/mix/copy_mix.cpp
@@ -1,18 +1,18 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/mix/copy_mix.h" 5#include "audio_core/renderer/command/mix/copy_mix.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8 8
9void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 9void CopyMixBufferCommand::Dump(
10 std::string& string) { 10 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
11 string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index, 11 string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
12 output_index); 12 output_index);
13} 13}
14 14
15void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { 15void CopyMixBufferCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
16 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, 16 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
17 processor.sample_count)}; 17 processor.sample_count)};
18 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, 18 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
@@ -20,8 +20,8 @@ void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor)
20 std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32)); 20 std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
21} 21}
22 22
23bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { 23bool CopyMixBufferCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
24 return true; 24 return true;
25} 25}
26 26
27} // namespace AudioCore::AudioRenderer 27} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/copy_mix.h b/src/audio_core/renderer/command/mix/copy_mix.h
index a59007fb6..ae247c3f8 100644
--- a/src/audio_core/renderer/command/mix/copy_mix.h
+++ b/src/audio_core/renderer/command/mix/copy_mix.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for a copying a mix buffer from input to output. 18 * AudioRenderer command for a copying a mix buffer from input to output.
18 */ 19 */
@@ -23,14 +24,14 @@ struct CopyMixBufferCommand : ICommand {
23 * @param processor - The CommandListProcessor processing this command. 24 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into. 25 * @param string - The string to print into.
25 */ 26 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 27 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
27 28
28 /** 29 /**
29 * Process this command. 30 * Process this command.
30 * 31 *
31 * @param processor - The CommandListProcessor processing this command. 32 * @param processor - The CommandListProcessor processing this command.
32 */ 33 */
33 void Process(const ADSP::CommandListProcessor& processor) override; 34 void Process(const AudioRenderer::CommandListProcessor& processor) override;
34 35
35 /** 36 /**
36 * Verify this command's data is valid. 37 * Verify this command's data is valid.
@@ -38,7 +39,7 @@ struct CopyMixBufferCommand : ICommand {
38 * @param processor - The CommandListProcessor processing this command. 39 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false. 40 * @return True if the command is valid, otherwise false.
40 */ 41 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override; 42 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
42 43
43 /// Input mix buffer index 44 /// Input mix buffer index
44 s16 input_index; 45 s16 input_index;
@@ -46,4 +47,4 @@ struct CopyMixBufferCommand : ICommand {
46 s16 output_index; 47 s16 output_index;
47}; 48};
48 49
49} // namespace AudioCore::AudioRenderer 50} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
index c2bc10061..caedb56b7 100644
--- a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
+++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
@@ -1,11 +1,11 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
4#include "audio_core/common/common.h" 5#include "audio_core/common/common.h"
5#include "audio_core/renderer/adsp/command_list_processor.h"
6#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" 6#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9/** 9/**
10 * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time 10 * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
11 * according to decay. 11 * according to decay.
@@ -36,13 +36,13 @@ static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
36 } 36 }
37} 37}
38 38
39void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 39void DepopForMixBuffersCommand::Dump(
40 std::string& string) { 40 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
41 string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input, 41 string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
42 count, decay.to_float()); 42 count, decay.to_float());
43} 43}
44 44
45void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) { 45void DepopForMixBuffersCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
46 auto end_index{std::min(processor.buffer_count, input + count)}; 46 auto end_index{std::min(processor.buffer_count, input + count)};
47 std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index}; 47 std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
48 48
@@ -57,8 +57,8 @@ void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& proces
57 } 57 }
58} 58}
59 59
60bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) { 60bool DepopForMixBuffersCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
61 return true; 61 return true;
62} 62}
63 63
64} // namespace AudioCore::AudioRenderer 64} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
index e7268ff27..699d38988 100644
--- a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
+++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
@@ -9,11 +9,12 @@
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/fixed_point.h" 10#include "common/fixed_point.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::ADSP::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor; 13class CommandListProcessor;
15} 14}
16 15
16namespace AudioCore::Renderer {
17
17/** 18/**
18 * AudioRenderer command for depopping a mix buffer. 19 * AudioRenderer command for depopping a mix buffer.
19 * Adds a cumulation of previous samples to the current mix buffer with a decay. 20 * Adds a cumulation of previous samples to the current mix buffer with a decay.
@@ -25,14 +26,14 @@ struct DepopForMixBuffersCommand : ICommand {
25 * @param processor - The CommandListProcessor processing this command. 26 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into. 27 * @param string - The string to print into.
27 */ 28 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 29 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
29 30
30 /** 31 /**
31 * Process this command. 32 * Process this command.
32 * 33 *
33 * @param processor - The CommandListProcessor processing this command. 34 * @param processor - The CommandListProcessor processing this command.
34 */ 35 */
35 void Process(const ADSP::CommandListProcessor& processor) override; 36 void Process(const AudioRenderer::CommandListProcessor& processor) override;
36 37
37 /** 38 /**
38 * Verify this command's data is valid. 39 * Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct DepopForMixBuffersCommand : ICommand {
40 * @param processor - The CommandListProcessor processing this command. 41 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false. 42 * @return True if the command is valid, otherwise false.
42 */ 43 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override; 44 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
44 45
45 /// Starting input mix buffer index 46 /// Starting input mix buffer index
46 u32 input; 47 u32 input;
@@ -52,4 +53,4 @@ struct DepopForMixBuffersCommand : ICommand {
52 CpuAddr depop_buffer; 53 CpuAddr depop_buffer;
53}; 54};
54 55
55} // namespace AudioCore::AudioRenderer 56} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.cpp b/src/audio_core/renderer/command/mix/depop_prepare.cpp
index 69bb78ccc..2faf4681a 100644
--- a/src/audio_core/renderer/command/mix/depop_prepare.cpp
+++ b/src/audio_core/renderer/command/mix/depop_prepare.cpp
@@ -1,15 +1,15 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/mix/depop_prepare.h" 5#include "audio_core/renderer/command/mix/depop_prepare.h"
6#include "audio_core/renderer/voice/voice_state.h" 6#include "audio_core/renderer/voice/voice_state.h"
7#include "common/fixed_point.h" 7#include "common/fixed_point.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 11void DepopPrepareCommand::Dump(
12 std::string& string) { 12 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
13 string += fmt::format("DepopPrepareCommand\n\tinputs: "); 13 string += fmt::format("DepopPrepareCommand\n\tinputs: ");
14 for (u32 i = 0; i < buffer_count; i++) { 14 for (u32 i = 0; i < buffer_count; i++) {
15 string += fmt::format("{:02X}, ", inputs[i]); 15 string += fmt::format("{:02X}, ", inputs[i]);
@@ -17,7 +17,7 @@ void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor
17 string += "\n"; 17 string += "\n";
18} 18}
19 19
20void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) { 20void DepopPrepareCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
21 auto samples{reinterpret_cast<s32*>(previous_samples)}; 21 auto samples{reinterpret_cast<s32*>(previous_samples)};
22 auto buffer{reinterpret_cast<s32*>(depop_buffer)}; 22 auto buffer{reinterpret_cast<s32*>(depop_buffer)};
23 23
@@ -29,8 +29,8 @@ void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
29 } 29 }
30} 30}
31 31
32bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) { 32bool DepopPrepareCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
33 return true; 33 return true;
34} 34}
35 35
36} // namespace AudioCore::AudioRenderer 36} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h
index a5465da9a..161a94461 100644
--- a/src/audio_core/renderer/command/mix/depop_prepare.h
+++ b/src/audio_core/renderer/command/mix/depop_prepare.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for preparing depop. 18 * AudioRenderer command for preparing depop.
18 * Adds the previusly output last samples to the depop buffer. 19 * Adds the previusly output last samples to the depop buffer.
@@ -24,14 +25,14 @@ struct DepopPrepareCommand : ICommand {
24 * @param processor - The CommandListProcessor processing this command. 25 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into. 26 * @param string - The string to print into.
26 */ 27 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 28 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
28 29
29 /** 30 /**
30 * Process this command. 31 * Process this command.
31 * 32 *
32 * @param processor - The CommandListProcessor processing this command. 33 * @param processor - The CommandListProcessor processing this command.
33 */ 34 */
34 void Process(const ADSP::CommandListProcessor& processor) override; 35 void Process(const AudioRenderer::CommandListProcessor& processor) override;
35 36
36 /** 37 /**
37 * Verify this command's data is valid. 38 * Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct DepopPrepareCommand : ICommand {
39 * @param processor - The CommandListProcessor processing this command. 40 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false. 41 * @return True if the command is valid, otherwise false.
41 */ 42 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override; 43 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
43 44
44 /// Depop buffer offset for each mix buffer 45 /// Depop buffer offset for each mix buffer
45 std::array<s16, MaxMixBuffers> inputs; 46 std::array<s16, MaxMixBuffers> inputs;
@@ -51,4 +52,4 @@ struct DepopPrepareCommand : ICommand {
51 CpuAddr depop_buffer; 52 CpuAddr depop_buffer;
52}; 53};
53 54
54} // namespace AudioCore::AudioRenderer 55} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/mix.cpp b/src/audio_core/renderer/command/mix/mix.cpp
index 8ecf9b05a..8bd689b88 100644
--- a/src/audio_core/renderer/command/mix/mix.cpp
+++ b/src/audio_core/renderer/command/mix/mix.cpp
@@ -5,11 +5,11 @@
5#include <limits> 5#include <limits>
6#include <span> 6#include <span>
7 7
8#include "audio_core/renderer/adsp/command_list_processor.h" 8#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
9#include "audio_core/renderer/command/mix/mix.h" 9#include "audio_core/renderer/command/mix/mix.h"
10#include "common/fixed_point.h" 10#include "common/fixed_point.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13/** 13/**
14 * Mix input mix buffer into output mix buffer, with volume applied to the input. 14 * Mix input mix buffer into output mix buffer, with volume applied to the input.
15 * 15 *
@@ -28,7 +28,7 @@ static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f3
28 } 28 }
29} 29}
30 30
31void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 31void MixCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
32 std::string& string) { 32 std::string& string) {
33 string += fmt::format("MixCommand"); 33 string += fmt::format("MixCommand");
34 string += fmt::format("\n\tinput {:02X}", input_index); 34 string += fmt::format("\n\tinput {:02X}", input_index);
@@ -37,7 +37,7 @@ void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& process
37 string += "\n"; 37 string += "\n";
38} 38}
39 39
40void MixCommand::Process(const ADSP::CommandListProcessor& processor) { 40void MixCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
41 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, 41 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
42 processor.sample_count)}; 42 processor.sample_count)};
43 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, 43 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
@@ -63,8 +63,8 @@ void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
63 } 63 }
64} 64}
65 65
66bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) { 66bool MixCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
67 return true; 67 return true;
68} 68}
69 69
70} // namespace AudioCore::AudioRenderer 70} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/mix.h b/src/audio_core/renderer/command/mix/mix.h
index 0201cf171..64c812382 100644
--- a/src/audio_core/renderer/command/mix/mix.h
+++ b/src/audio_core/renderer/command/mix/mix.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume 18 * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
18 * applied to the input. 19 * applied to the input.
@@ -24,14 +25,14 @@ struct MixCommand : ICommand {
24 * @param processor - The CommandListProcessor processing this command. 25 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into. 26 * @param string - The string to print into.
26 */ 27 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 28 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
28 29
29 /** 30 /**
30 * Process this command. 31 * Process this command.
31 * 32 *
32 * @param processor - The CommandListProcessor processing this command. 33 * @param processor - The CommandListProcessor processing this command.
33 */ 34 */
34 void Process(const ADSP::CommandListProcessor& processor) override; 35 void Process(const AudioRenderer::CommandListProcessor& processor) override;
35 36
36 /** 37 /**
37 * Verify this command's data is valid. 38 * Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct MixCommand : ICommand {
39 * @param processor - The CommandListProcessor processing this command. 40 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false. 41 * @return True if the command is valid, otherwise false.
41 */ 42 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override; 43 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
43 44
44 /// Fixed point precision 45 /// Fixed point precision
45 u8 precision; 46 u8 precision;
@@ -51,4 +52,4 @@ struct MixCommand : ICommand {
51 f32 volume; 52 f32 volume;
52}; 53};
53 54
54} // namespace AudioCore::AudioRenderer 55} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp
index d67123cd8..2f6500da5 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp.cpp
+++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp
@@ -1,12 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/mix/mix_ramp.h" 5#include "audio_core/renderer/command/mix/mix_ramp.h"
6#include "common/fixed_point.h" 6#include "common/fixed_point.h"
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11template <size_t Q> 11template <size_t Q>
12s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, 12s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
@@ -33,7 +33,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo
33template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32); 33template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
34template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32); 34template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
35 35
36void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { 36void MixRampCommand::Dump(const AudioRenderer::CommandListProcessor& processor,
37 std::string& string) {
37 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; 38 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
38 string += fmt::format("MixRampCommand"); 39 string += fmt::format("MixRampCommand");
39 string += fmt::format("\n\tinput {:02X}", input_index); 40 string += fmt::format("\n\tinput {:02X}", input_index);
@@ -44,7 +45,7 @@ void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::stri
44 string += "\n"; 45 string += "\n";
45} 46}
46 47
47void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) { 48void MixRampCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
48 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, 49 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
49 processor.sample_count)}; 50 processor.sample_count)};
50 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, 51 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
@@ -75,8 +76,8 @@ void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
75 } 76 }
76} 77}
77 78
78bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) { 79bool MixRampCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
79 return true; 80 return true;
80} 81}
81 82
82} // namespace AudioCore::AudioRenderer 83} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h
index 52f74a273..92209b53a 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp.h
+++ b/src/audio_core/renderer/command/mix/mix_ramp.h
@@ -9,11 +9,12 @@
9#include "audio_core/renderer/command/icommand.h" 9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::ADSP::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor; 13class CommandListProcessor;
15} 14}
16 15
16namespace AudioCore::Renderer {
17
17/** 18/**
18 * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume 19 * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
19 * applied to the input, and volume ramping to smooth out the transition. 20 * applied to the input, and volume ramping to smooth out the transition.
@@ -25,14 +26,14 @@ struct MixRampCommand : ICommand {
25 * @param processor - The CommandListProcessor processing this command. 26 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into. 27 * @param string - The string to print into.
27 */ 28 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 29 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
29 30
30 /** 31 /**
31 * Process this command. 32 * Process this command.
32 * 33 *
33 * @param processor - The CommandListProcessor processing this command. 34 * @param processor - The CommandListProcessor processing this command.
34 */ 35 */
35 void Process(const ADSP::CommandListProcessor& processor) override; 36 void Process(const AudioRenderer::CommandListProcessor& processor) override;
36 37
37 /** 38 /**
38 * Verify this command's data is valid. 39 * Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct MixRampCommand : ICommand {
40 * @param processor - The CommandListProcessor processing this command. 41 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false. 42 * @return True if the command is valid, otherwise false.
42 */ 43 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override; 44 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
44 45
45 /// Fixed point precision 46 /// Fixed point precision
46 u8 precision; 47 u8 precision;
@@ -70,4 +71,4 @@ template <size_t Q>
70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_, 71s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
71 u32 sample_count); 72 u32 sample_count);
72 73
73} // namespace AudioCore::AudioRenderer 74} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
index 43dbef9fc..64138a9bf 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
@@ -1,13 +1,14 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/mix/mix_ramp.h" 5#include "audio_core/renderer/command/mix/mix_ramp.h"
6#include "audio_core/renderer/command/mix/mix_ramp_grouped.h" 6#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { 10void MixRampGroupedCommand::Dump(const AudioRenderer::CommandListProcessor& processor,
11 std::string& string) {
11 string += "MixRampGroupedCommand"; 12 string += "MixRampGroupedCommand";
12 for (u32 i = 0; i < buffer_count; i++) { 13 for (u32 i = 0; i < buffer_count; i++) {
13 string += fmt::format("\n\t{}", i); 14 string += fmt::format("\n\t{}", i);
@@ -21,7 +22,7 @@ void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, st
21 } 22 }
22} 23}
23 24
24void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) { 25void MixRampGroupedCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
25 std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers}; 26 std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
26 27
27 for (u32 i = 0; i < buffer_count; i++) { 28 for (u32 i = 0; i < buffer_count; i++) {
@@ -58,8 +59,8 @@ void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor)
58 } 59 }
59} 60}
60 61
61bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) { 62bool MixRampGroupedCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
62 return true; 63 return true;
63} 64}
64 65
65} // namespace AudioCore::AudioRenderer 66} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
index 3b0ce67ef..9621e42a3 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
@@ -9,11 +9,12 @@
9#include "audio_core/renderer/command/icommand.h" 9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::ADSP::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor; 13class CommandListProcessor;
15} 14}
16 15
16namespace AudioCore::Renderer {
17
17/** 18/**
18 * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with 19 * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
19 * a volume applied to the input, and volume ramping to smooth out the transition. 20 * a volume applied to the input, and volume ramping to smooth out the transition.
@@ -25,14 +26,14 @@ struct MixRampGroupedCommand : ICommand {
25 * @param processor - The CommandListProcessor processing this command. 26 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into. 27 * @param string - The string to print into.
27 */ 28 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 29 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
29 30
30 /** 31 /**
31 * Process this command. 32 * Process this command.
32 * 33 *
33 * @param processor - The CommandListProcessor processing this command. 34 * @param processor - The CommandListProcessor processing this command.
34 */ 35 */
35 void Process(const ADSP::CommandListProcessor& processor) override; 36 void Process(const AudioRenderer::CommandListProcessor& processor) override;
36 37
37 /** 38 /**
38 * Verify this command's data is valid. 39 * Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct MixRampGroupedCommand : ICommand {
40 * @param processor - The CommandListProcessor processing this command. 41 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false. 42 * @return True if the command is valid, otherwise false.
42 */ 43 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override; 44 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
44 45
45 /// Fixed point precision 46 /// Fixed point precision
46 u8 precision; 47 u8 precision;
@@ -58,4 +59,4 @@ struct MixRampGroupedCommand : ICommand {
58 CpuAddr previous_samples; 59 CpuAddr previous_samples;
59}; 60};
60 61
61} // namespace AudioCore::AudioRenderer 62} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/volume.cpp b/src/audio_core/renderer/command/mix/volume.cpp
index b045fb062..92baf6cc3 100644
--- a/src/audio_core/renderer/command/mix/volume.cpp
+++ b/src/audio_core/renderer/command/mix/volume.cpp
@@ -1,12 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/mix/volume.h" 5#include "audio_core/renderer/command/mix/volume.h"
6#include "common/fixed_point.h" 6#include "common/fixed_point.h"
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10/** 10/**
11 * Apply volume to the input mix buffer, saving to the output buffer. 11 * Apply volume to the input mix buffer, saving to the output buffer.
12 * 12 *
@@ -29,7 +29,7 @@ static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input,
29 } 29 }
30} 30}
31 31
32void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 32void VolumeCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
33 std::string& string) { 33 std::string& string) {
34 string += fmt::format("VolumeCommand"); 34 string += fmt::format("VolumeCommand");
35 string += fmt::format("\n\tinput {:02X}", input_index); 35 string += fmt::format("\n\tinput {:02X}", input_index);
@@ -38,7 +38,7 @@ void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc
38 string += "\n"; 38 string += "\n";
39} 39}
40 40
41void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) { 41void VolumeCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
42 // If input and output buffers are the same, and the volume is 1.0f, this won't do 42 // If input and output buffers are the same, and the volume is 1.0f, this won't do
43 // anything, so just skip. 43 // anything, so just skip.
44 if (input_index == output_index && volume == 1.0f) { 44 if (input_index == output_index && volume == 1.0f) {
@@ -65,8 +65,8 @@ void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
65 } 65 }
66} 66}
67 67
68bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) { 68bool VolumeCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
69 return true; 69 return true;
70} 70}
71 71
72} // namespace AudioCore::AudioRenderer 72} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/volume.h b/src/audio_core/renderer/command/mix/volume.h
index 6ae9fb794..fbb8156ca 100644
--- a/src/audio_core/renderer/command/mix/volume.h
+++ b/src/audio_core/renderer/command/mix/volume.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for applying volume to a mix buffer. 18 * AudioRenderer command for applying volume to a mix buffer.
18 */ 19 */
@@ -23,14 +24,14 @@ struct VolumeCommand : ICommand {
23 * @param processor - The CommandListProcessor processing this command. 24 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into. 25 * @param string - The string to print into.
25 */ 26 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 27 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
27 28
28 /** 29 /**
29 * Process this command. 30 * Process this command.
30 * 31 *
31 * @param processor - The CommandListProcessor processing this command. 32 * @param processor - The CommandListProcessor processing this command.
32 */ 33 */
33 void Process(const ADSP::CommandListProcessor& processor) override; 34 void Process(const AudioRenderer::CommandListProcessor& processor) override;
34 35
35 /** 36 /**
36 * Verify this command's data is valid. 37 * Verify this command's data is valid.
@@ -38,7 +39,7 @@ struct VolumeCommand : ICommand {
38 * @param processor - The CommandListProcessor processing this command. 39 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false. 40 * @return True if the command is valid, otherwise false.
40 */ 41 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override; 42 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
42 43
43 /// Fixed point precision 44 /// Fixed point precision
44 u8 precision; 45 u8 precision;
@@ -50,4 +51,4 @@ struct VolumeCommand : ICommand {
50 f32 volume; 51 f32 volume;
51}; 52};
52 53
53} // namespace AudioCore::AudioRenderer 54} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.cpp b/src/audio_core/renderer/command/mix/volume_ramp.cpp
index 424307148..fdc751957 100644
--- a/src/audio_core/renderer/command/mix/volume_ramp.cpp
+++ b/src/audio_core/renderer/command/mix/volume_ramp.cpp
@@ -1,11 +1,11 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/mix/volume_ramp.h" 5#include "audio_core/renderer/command/mix/volume_ramp.h"
6#include "common/fixed_point.h" 6#include "common/fixed_point.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9/** 9/**
10 * Apply volume with ramping to the input mix buffer, saving to the output buffer. 10 * Apply volume with ramping to the input mix buffer, saving to the output buffer.
11 * 11 *
@@ -38,7 +38,8 @@ static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32>
38 } 38 }
39} 39}
40 40
41void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { 41void VolumeRampCommand::Dump(const AudioRenderer::CommandListProcessor& processor,
42 std::string& string) {
42 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; 43 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
43 string += fmt::format("VolumeRampCommand"); 44 string += fmt::format("VolumeRampCommand");
44 string += fmt::format("\n\tinput {:02X}", input_index); 45 string += fmt::format("\n\tinput {:02X}", input_index);
@@ -49,7 +50,7 @@ void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::s
49 string += "\n"; 50 string += "\n";
50} 51}
51 52
52void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) { 53void VolumeRampCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
53 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, 54 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
54 processor.sample_count)}; 55 processor.sample_count)};
55 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, 56 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
@@ -77,8 +78,8 @@ void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
77 } 78 }
78} 79}
79 80
80bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) { 81bool VolumeRampCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
81 return true; 82 return true;
82} 83}
83 84
84} // namespace AudioCore::AudioRenderer 85} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.h b/src/audio_core/renderer/command/mix/volume_ramp.h
index 77b61547e..d9794fb95 100644
--- a/src/audio_core/renderer/command/mix/volume_ramp.h
+++ b/src/audio_core/renderer/command/mix/volume_ramp.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth 18 * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
18 * out the transition. 19 * out the transition.
@@ -24,14 +25,14 @@ struct VolumeRampCommand : ICommand {
24 * @param processor - The CommandListProcessor processing this command. 25 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into. 26 * @param string - The string to print into.
26 */ 27 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 28 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
28 29
29 /** 30 /**
30 * Process this command. 31 * Process this command.
31 * 32 *
32 * @param processor - The CommandListProcessor processing this command. 33 * @param processor - The CommandListProcessor processing this command.
33 */ 34 */
34 void Process(const ADSP::CommandListProcessor& processor) override; 35 void Process(const AudioRenderer::CommandListProcessor& processor) override;
35 36
36 /** 37 /**
37 * Verify this command's data is valid. 38 * Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct VolumeRampCommand : ICommand {
39 * @param processor - The CommandListProcessor processing this command. 40 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false. 41 * @return True if the command is valid, otherwise false.
41 */ 42 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override; 43 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
43 44
44 /// Fixed point precision 45 /// Fixed point precision
45 u8 precision; 46 u8 precision;
@@ -53,4 +54,4 @@ struct VolumeRampCommand : ICommand {
53 f32 volume; 54 f32 volume;
54}; 55};
55 56
56} // namespace AudioCore::AudioRenderer 57} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/performance/performance.cpp b/src/audio_core/renderer/command/performance/performance.cpp
index 4a881547f..f0cfcc9fd 100644
--- a/src/audio_core/renderer/command/performance/performance.cpp
+++ b/src/audio_core/renderer/command/performance/performance.cpp
@@ -1,25 +1,25 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/performance/performance.h" 5#include "audio_core/renderer/command/performance/performance.h"
6#include "core/core.h" 6#include "core/core.h"
7#include "core/core_timing.h" 7#include "core/core_timing.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 11void PerformanceCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
12 std::string& string) { 12 std::string& string) {
13 string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state)); 13 string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
14} 14}
15 15
16void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) { 16void PerformanceCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
17 auto base{entry_address.translated_address}; 17 auto base{entry_address.translated_address};
18 if (state == PerformanceState::Start) { 18 if (state == PerformanceState::Start) {
19 auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)}; 19 auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
20 *start_time_ptr = 20 *start_time_ptr =
21 static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time - 21 static_cast<u32>(processor.system->CoreTiming().GetGlobalTimeUs().count() -
22 processor.current_processing_time); 22 processor.start_time - processor.current_processing_time);
23 } else if (state == PerformanceState::Stop) { 23 } else if (state == PerformanceState::Stop) {
24 auto processed_time_ptr{ 24 auto processed_time_ptr{
25 reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)}; 25 reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
@@ -27,14 +27,14 @@ void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
27 reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)}; 27 reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
28 28
29 *processed_time_ptr = 29 *processed_time_ptr =
30 static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time - 30 static_cast<u32>(processor.system->CoreTiming().GetGlobalTimeUs().count() -
31 processor.current_processing_time); 31 processor.start_time - processor.current_processing_time);
32 (*entry_count_ptr)++; 32 (*entry_count_ptr)++;
33 } 33 }
34} 34}
35 35
36bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) { 36bool PerformanceCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
37 return true; 37 return true;
38} 38}
39 39
40} // namespace AudioCore::AudioRenderer 40} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/performance/performance.h b/src/audio_core/renderer/command/performance/performance.h
index 11a7d6c08..522e51e34 100644
--- a/src/audio_core/renderer/command/performance/performance.h
+++ b/src/audio_core/renderer/command/performance/performance.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/performance/performance_manager.h" 10#include "audio_core/renderer/performance/performance_manager.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule. 20 * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
20 */ 21 */
@@ -25,14 +26,14 @@ struct PerformanceCommand : ICommand {
25 * @param processor - The CommandListProcessor processing this command. 26 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into. 27 * @param string - The string to print into.
27 */ 28 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 29 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
29 30
30 /** 31 /**
31 * Process this command. 32 * Process this command.
32 * 33 *
33 * @param processor - The CommandListProcessor processing this command. 34 * @param processor - The CommandListProcessor processing this command.
34 */ 35 */
35 void Process(const ADSP::CommandListProcessor& processor) override; 36 void Process(const AudioRenderer::CommandListProcessor& processor) override;
36 37
37 /** 38 /**
38 * Verify this command's data is valid. 39 * Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct PerformanceCommand : ICommand {
40 * @param processor - The CommandListProcessor processing this command. 41 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false. 42 * @return True if the command is valid, otherwise false.
42 */ 43 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override; 44 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
44 45
45 /// State of the performance 46 /// State of the performance
46 PerformanceState state; 47 PerformanceState state;
@@ -48,4 +49,4 @@ struct PerformanceCommand : ICommand {
48 PerformanceEntryAddresses entry_address; 49 PerformanceEntryAddresses entry_address;
49}; 50};
50 51
51} // namespace AudioCore::AudioRenderer 52} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
index 1fd90308a..f9b289887 100644
--- a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
+++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
@@ -1,13 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/renderer/adsp/command_list_processor.h" 4#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
5#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" 5#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8 8
9void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 9void DownMix6chTo2chCommand::Dump(
10 std::string& string) { 10 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
11 string += fmt::format("DownMix6chTo2chCommand\n\tinputs: "); 11 string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
12 for (u32 i = 0; i < MaxChannels; i++) { 12 for (u32 i = 0; i < MaxChannels; i++) {
13 string += fmt::format("{:02X}, ", inputs[i]); 13 string += fmt::format("{:02X}, ", inputs[i]);
@@ -19,7 +19,7 @@ void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProces
19 string += "\n"; 19 string += "\n";
20} 20}
21 21
22void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) { 22void DownMix6chTo2chCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
23 auto in_front_left{ 23 auto in_front_left{
24 processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)}; 24 processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
25 auto in_front_right{ 25 auto in_front_right{
@@ -67,8 +67,8 @@ void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor
67 std::memset(out_back_right.data(), 0, out_back_right.size_bytes()); 67 std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
68} 68}
69 69
70bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) { 70bool DownMix6chTo2chCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
71 return true; 71 return true;
72} 72}
73 73
74} // namespace AudioCore::AudioRenderer 74} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
index dc133a73b..96cbc5506 100644
--- a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
+++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
@@ -9,11 +9,12 @@
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/fixed_point.h" 10#include "common/fixed_point.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::ADSP::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor; 13class CommandListProcessor;
15} 14}
16 15
16namespace AudioCore::Renderer {
17
17/** 18/**
18 * AudioRenderer command for downmixing 6 channels to 2. 19 * AudioRenderer command for downmixing 6 channels to 2.
19 * Channel layout (SMPTE): 20 * Channel layout (SMPTE):
@@ -31,14 +32,14 @@ struct DownMix6chTo2chCommand : ICommand {
31 * @param processor - The CommandListProcessor processing this command. 32 * @param processor - The CommandListProcessor processing this command.
32 * @param string - The string to print into. 33 * @param string - The string to print into.
33 */ 34 */
34 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 35 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
35 36
36 /** 37 /**
37 * Process this command. 38 * Process this command.
38 * 39 *
39 * @param processor - The CommandListProcessor processing this command. 40 * @param processor - The CommandListProcessor processing this command.
40 */ 41 */
41 void Process(const ADSP::CommandListProcessor& processor) override; 42 void Process(const AudioRenderer::CommandListProcessor& processor) override;
42 43
43 /** 44 /**
44 * Verify this command's data is valid. 45 * Verify this command's data is valid.
@@ -46,7 +47,7 @@ struct DownMix6chTo2chCommand : ICommand {
46 * @param processor - The CommandListProcessor processing this command. 47 * @param processor - The CommandListProcessor processing this command.
47 * @return True if the command is valid, otherwise false. 48 * @return True if the command is valid, otherwise false.
48 */ 49 */
49 bool Verify(const ADSP::CommandListProcessor& processor) override; 50 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
50 51
51 /// Input mix buffer offsets for each channel 52 /// Input mix buffer offsets for each channel
52 std::array<s16, MaxChannels> inputs; 53 std::array<s16, MaxChannels> inputs;
@@ -56,4 +57,4 @@ struct DownMix6chTo2chCommand : ICommand {
56 std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff; 57 std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
57}; 58};
58 59
59} // namespace AudioCore::AudioRenderer 60} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp
index 070c9d2b8..51f4ba39e 100644
--- a/src/audio_core/renderer/command/resample/resample.cpp
+++ b/src/audio_core/renderer/command/resample/resample.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/command/resample/resample.h" 4#include "audio_core/renderer/command/resample/resample.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, 8static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
9 const Common::FixedPoint<49, 15>& sample_rate_ratio, 9 const Common::FixedPoint<49, 15>& sample_rate_ratio,
@@ -880,4 +880,4 @@ void Resample(std::span<s32> output, std::span<const s16> input,
880 } 880 }
881} 881}
882 882
883} // namespace AudioCore::AudioRenderer 883} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/resample/resample.h b/src/audio_core/renderer/command/resample/resample.h
index ba9209b82..134aff0c9 100644
--- a/src/audio_core/renderer/command/resample/resample.h
+++ b/src/audio_core/renderer/command/resample/resample.h
@@ -9,7 +9,7 @@
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/fixed_point.h" 10#include "common/fixed_point.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13/** 13/**
14 * Resample an input buffer into an output buffer, according to the sample_rate_ratio. 14 * Resample an input buffer into an output buffer, according to the sample_rate_ratio.
15 * 15 *
@@ -26,4 +26,4 @@ void Resample(std::span<s32> output, std::span<const s16> input,
26 const Common::FixedPoint<49, 15>& sample_rate_ratio, 26 const Common::FixedPoint<49, 15>& sample_rate_ratio,
27 Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality); 27 Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
28 28
29} // namespace AudioCore::AudioRenderer 29} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/resample/upsample.cpp b/src/audio_core/renderer/command/resample/upsample.cpp
index 86ddee1a4..691d70390 100644
--- a/src/audio_core/renderer/command/resample/upsample.cpp
+++ b/src/audio_core/renderer/command/resample/upsample.cpp
@@ -3,11 +3,11 @@
3 3
4#include <array> 4#include <array>
5 5
6#include "audio_core/renderer/adsp/command_list_processor.h" 6#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
7#include "audio_core/renderer/command/resample/upsample.h" 7#include "audio_core/renderer/command/resample/upsample.h"
8#include "audio_core/renderer/upsampler/upsampler_info.h" 8#include "audio_core/renderer/upsampler/upsampler_info.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11/** 11/**
12 * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K. 12 * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
13 * 13 *
@@ -198,7 +198,7 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
198 } 198 }
199} 199}
200 200
201auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 201auto UpsampleCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
202 std::string& string) -> void { 202 std::string& string) -> void {
203 string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}", 203 string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
204 source_sample_count, source_sample_rate); 204 source_sample_count, source_sample_rate);
@@ -213,7 +213,7 @@ auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& pr
213 string += "\n"; 213 string += "\n";
214} 214}
215 215
216void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) { 216void UpsampleCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
217 const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)}; 217 const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
218 const auto input_count{std::min(info->input_count, buffer_count)}; 218 const auto input_count{std::min(info->input_count, buffer_count)};
219 const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count}; 219 const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
@@ -234,8 +234,8 @@ void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
234 } 234 }
235} 235}
236 236
237bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) { 237bool UpsampleCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
238 return true; 238 return true;
239} 239}
240 240
241} // namespace AudioCore::AudioRenderer 241} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/resample/upsample.h b/src/audio_core/renderer/command/resample/upsample.h
index bfc94e8af..877271ba9 100644
--- a/src/audio_core/renderer/command/resample/upsample.h
+++ b/src/audio_core/renderer/command/resample/upsample.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for upsampling a mix buffer to 48Khz. 18 * AudioRenderer command for upsampling a mix buffer to 48Khz.
18 * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz. 19 * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
@@ -24,14 +25,14 @@ struct UpsampleCommand : ICommand {
24 * @param processor - The CommandListProcessor processing this command. 25 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into. 26 * @param string - The string to print into.
26 */ 27 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 28 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
28 29
29 /** 30 /**
30 * Process this command. 31 * Process this command.
31 * 32 *
32 * @param processor - The CommandListProcessor processing this command. 33 * @param processor - The CommandListProcessor processing this command.
33 */ 34 */
34 void Process(const ADSP::CommandListProcessor& processor) override; 35 void Process(const AudioRenderer::CommandListProcessor& processor) override;
35 36
36 /** 37 /**
37 * Verify this command's data is valid. 38 * Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct UpsampleCommand : ICommand {
39 * @param processor - The CommandListProcessor processing this command. 40 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false. 41 * @return True if the command is valid, otherwise false.
41 */ 42 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override; 43 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
43 44
44 /// Pointer to the output samples buffer. 45 /// Pointer to the output samples buffer.
45 CpuAddr samples_buffer; 46 CpuAddr samples_buffer;
@@ -57,4 +58,4 @@ struct UpsampleCommand : ICommand {
57 CpuAddr upsampler_info; 58 CpuAddr upsampler_info;
58}; 59};
59 60
60} // namespace AudioCore::AudioRenderer 61} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.cpp b/src/audio_core/renderer/command/sink/circular_buffer.cpp
index e2ce59792..e056d15a6 100644
--- a/src/audio_core/renderer/command/sink/circular_buffer.cpp
+++ b/src/audio_core/renderer/command/sink/circular_buffer.cpp
@@ -3,14 +3,14 @@
3 3
4#include <vector> 4#include <vector>
5 5
6#include "audio_core/renderer/adsp/command_list_processor.h" 6#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
7#include "audio_core/renderer/command/sink/circular_buffer.h" 7#include "audio_core/renderer/command/sink/circular_buffer.h"
8#include "core/memory.h" 8#include "core/memory.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 12void CircularBufferSinkCommand::Dump(
13 std::string& string) { 13 [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
14 string += fmt::format( 14 string += fmt::format(
15 "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ", 15 "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
16 input_count, size, pos); 16 input_count, size, pos);
@@ -20,7 +20,7 @@ void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListPro
20 string += "\n"; 20 string += "\n";
21} 21}
22 22
23void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) { 23void CircularBufferSinkCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
24 constexpr s32 min{std::numeric_limits<s16>::min()}; 24 constexpr s32 min{std::numeric_limits<s16>::min()};
25 constexpr s32 max{std::numeric_limits<s16>::max()}; 25 constexpr s32 max{std::numeric_limits<s16>::max()};
26 26
@@ -41,8 +41,8 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
41 } 41 }
42} 42}
43 43
44bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { 44bool CircularBufferSinkCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
45 return true; 45 return true;
46} 46}
47 47
48} // namespace AudioCore::AudioRenderer 48} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.h b/src/audio_core/renderer/command/sink/circular_buffer.h
index e7d5be26e..a3234a406 100644
--- a/src/audio_core/renderer/command/sink/circular_buffer.h
+++ b/src/audio_core/renderer/command/sink/circular_buffer.h
@@ -8,11 +8,12 @@
8#include "audio_core/renderer/command/icommand.h" 8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::ADSP::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor; 12class CommandListProcessor;
14} 13}
15 14
15namespace AudioCore::Renderer {
16
16/** 17/**
17 * AudioRenderer command for sinking samples to a circular buffer. 18 * AudioRenderer command for sinking samples to a circular buffer.
18 */ 19 */
@@ -23,14 +24,14 @@ struct CircularBufferSinkCommand : ICommand {
23 * @param processor - The CommandListProcessor processing this command. 24 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into. 25 * @param string - The string to print into.
25 */ 26 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 27 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
27 28
28 /** 29 /**
29 * Process this command. 30 * Process this command.
30 * 31 *
31 * @param processor - The CommandListProcessor processing this command. 32 * @param processor - The CommandListProcessor processing this command.
32 */ 33 */
33 void Process(const ADSP::CommandListProcessor& processor) override; 34 void Process(const AudioRenderer::CommandListProcessor& processor) override;
34 35
35 /** 36 /**
36 * Verify this command's data is valid. 37 * Verify this command's data is valid.
@@ -38,7 +39,7 @@ struct CircularBufferSinkCommand : ICommand {
38 * @param processor - The CommandListProcessor processing this command. 39 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false. 40 * @return True if the command is valid, otherwise false.
40 */ 41 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override; 42 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
42 43
43 /// Number of input mix buffers 44 /// Number of input mix buffers
44 u32 input_count; 45 u32 input_count;
@@ -52,4 +53,4 @@ struct CircularBufferSinkCommand : ICommand {
52 u32 pos; 53 u32 pos;
53}; 54};
54 55
55} // namespace AudioCore::AudioRenderer 56} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp
index 5f74dd7ad..3480ed475 100644
--- a/src/audio_core/renderer/command/sink/device.cpp
+++ b/src/audio_core/renderer/command/sink/device.cpp
@@ -3,13 +3,13 @@
3 3
4#include <algorithm> 4#include <algorithm>
5 5
6#include "audio_core/renderer/adsp/command_list_processor.h" 6#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
7#include "audio_core/renderer/command/sink/device.h" 7#include "audio_core/renderer/command/sink/device.h"
8#include "audio_core/sink/sink.h" 8#include "audio_core/sink/sink.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, 12void DeviceSinkCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
13 std::string& string) { 13 std::string& string) {
14 string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ", 14 string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
15 std::string_view(name), session_id, input_count); 15 std::string_view(name), session_id, input_count);
@@ -19,7 +19,7 @@ void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
19 string += "\n"; 19 string += "\n";
20} 20}
21 21
22void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) { 22void DeviceSinkCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
23 constexpr s32 min = std::numeric_limits<s16>::min(); 23 constexpr s32 min = std::numeric_limits<s16>::min();
24 constexpr s32 max = std::numeric_limits<s16>::max(); 24 constexpr s32 max = std::numeric_limits<s16>::max();
25 25
@@ -51,8 +51,8 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
51 } 51 }
52} 52}
53 53
54bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { 54bool DeviceSinkCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
55 return true; 55 return true;
56} 56}
57 57
58} // namespace AudioCore::AudioRenderer 58} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/command/sink/device.h b/src/audio_core/renderer/command/sink/device.h
index 1099bcf8c..385b51ecc 100644
--- a/src/audio_core/renderer/command/sink/device.h
+++ b/src/audio_core/renderer/command/sink/device.h
@@ -10,11 +10,12 @@
10#include "audio_core/renderer/command/icommand.h" 10#include "audio_core/renderer/command/icommand.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::ADSP::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor; 14class CommandListProcessor;
16} 15}
17 16
17namespace AudioCore::Renderer {
18
18/** 19/**
19 * AudioRenderer command for sinking samples to an output device. 20 * AudioRenderer command for sinking samples to an output device.
20 */ 21 */
@@ -25,14 +26,14 @@ struct DeviceSinkCommand : ICommand {
25 * @param processor - The CommandListProcessor processing this command. 26 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into. 27 * @param string - The string to print into.
27 */ 28 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; 29 void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
29 30
30 /** 31 /**
31 * Process this command. 32 * Process this command.
32 * 33 *
33 * @param processor - The CommandListProcessor processing this command. 34 * @param processor - The CommandListProcessor processing this command.
34 */ 35 */
35 void Process(const ADSP::CommandListProcessor& processor) override; 36 void Process(const AudioRenderer::CommandListProcessor& processor) override;
36 37
37 /** 38 /**
38 * Verify this command's data is valid. 39 * Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct DeviceSinkCommand : ICommand {
40 * @param processor - The CommandListProcessor processing this command. 41 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false. 42 * @return True if the command is valid, otherwise false.
42 */ 43 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override; 44 bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
44 45
45 /// Device name 46 /// Device name
46 char name[0x100]; 47 char name[0x100];
@@ -54,4 +55,4 @@ struct DeviceSinkCommand : ICommand {
54 std::array<s16, MaxChannels> inputs; 55 std::array<s16, MaxChannels> inputs;
55}; 56};
56 57
57} // namespace AudioCore::AudioRenderer 58} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/aux_.cpp b/src/audio_core/renderer/effect/aux_.cpp
index 51e780ef1..1c1411eff 100644
--- a/src/audio_core/renderer/effect/aux_.cpp
+++ b/src/audio_core/renderer/effect/aux_.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/aux_.h" 4#include "audio_core/renderer/effect/aux_.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, 8void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) { 9 const PoolMapper& pool_mapper) {
@@ -90,4 +90,4 @@ CpuAddr AuxInfo::GetWorkbuffer(s32 index) {
90 return workbuffers[index].GetReference(true); 90 return workbuffers[index].GetReference(true);
91} 91}
92 92
93} // namespace AudioCore::AudioRenderer 93} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/aux_.h b/src/audio_core/renderer/effect/aux_.h
index 4d3d9e3d9..c5b3058da 100644
--- a/src/audio_core/renderer/effect/aux_.h
+++ b/src/audio_core/renderer/effect/aux_.h
@@ -9,7 +9,7 @@
9#include "audio_core/renderer/effect/effect_info_base.h" 9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13/** 13/**
14 * Auxiliary Buffer used for Aux commands. 14 * Auxiliary Buffer used for Aux commands.
15 * Send and return buffers are available (names from the game's perspective). 15 * Send and return buffers are available (names from the game's perspective).
@@ -120,4 +120,4 @@ public:
120 CpuAddr GetWorkbuffer(s32 index) override; 120 CpuAddr GetWorkbuffer(s32 index) override;
121}; 121};
122 122
123} // namespace AudioCore::AudioRenderer 123} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp
index a1efb3231..08161d840 100644
--- a/src/audio_core/renderer/effect/biquad_filter.cpp
+++ b/src/audio_core/renderer/effect/biquad_filter.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/biquad_filter.h" 4#include "audio_core/renderer/effect/biquad_filter.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, 8void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { 9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
@@ -49,4 +49,4 @@ void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
49void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state, 49void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state,
50 EffectResultState& dsp_state) {} 50 EffectResultState& dsp_state) {}
51 51
52} // namespace AudioCore::AudioRenderer 52} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h
index f53fd5bab..5a22899ab 100644
--- a/src/audio_core/renderer/effect/biquad_filter.h
+++ b/src/audio_core/renderer/effect/biquad_filter.h
@@ -9,7 +9,7 @@
9#include "audio_core/renderer/effect/effect_info_base.h" 9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13 13
14class BiquadFilterInfo : public EffectInfoBase { 14class BiquadFilterInfo : public EffectInfoBase {
15public: 15public:
@@ -76,4 +76,4 @@ public:
76 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; 76 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
77}; 77};
78 78
79} // namespace AudioCore::AudioRenderer 79} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/buffer_mixer.cpp b/src/audio_core/renderer/effect/buffer_mixer.cpp
index 9c8877f01..826e246ec 100644
--- a/src/audio_core/renderer/effect/buffer_mixer.cpp
+++ b/src/audio_core/renderer/effect/buffer_mixer.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/buffer_mixer.h" 4#include "audio_core/renderer/effect/buffer_mixer.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, 8void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { 9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
@@ -46,4 +46,4 @@ void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {}
46void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state, 46void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state,
47 EffectResultState& dsp_state) {} 47 EffectResultState& dsp_state) {}
48 48
49} // namespace AudioCore::AudioRenderer 49} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/buffer_mixer.h b/src/audio_core/renderer/effect/buffer_mixer.h
index 23eed4a8b..0c01ef38d 100644
--- a/src/audio_core/renderer/effect/buffer_mixer.h
+++ b/src/audio_core/renderer/effect/buffer_mixer.h
@@ -9,7 +9,7 @@
9#include "audio_core/renderer/effect/effect_info_base.h" 9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13 13
14class BufferMixerInfo : public EffectInfoBase { 14class BufferMixerInfo : public EffectInfoBase {
15public: 15public:
@@ -72,4 +72,4 @@ public:
72 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; 72 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
73}; 73};
74 74
75} // namespace AudioCore::AudioRenderer 75} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/capture.cpp b/src/audio_core/renderer/effect/capture.cpp
index 3f038efdb..dfa062a59 100644
--- a/src/audio_core/renderer/effect/capture.cpp
+++ b/src/audio_core/renderer/effect/capture.cpp
@@ -4,7 +4,7 @@
4#include "audio_core/renderer/effect/aux_.h" 4#include "audio_core/renderer/effect/aux_.h"
5#include "audio_core/renderer/effect/capture.h" 5#include "audio_core/renderer/effect/capture.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8 8
9void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, 9void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
10 const PoolMapper& pool_mapper) { 10 const PoolMapper& pool_mapper) {
@@ -79,4 +79,4 @@ CpuAddr CaptureInfo::GetWorkbuffer(s32 index) {
79 return workbuffers[index].GetReference(true); 79 return workbuffers[index].GetReference(true);
80} 80}
81 81
82} // namespace AudioCore::AudioRenderer 82} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/capture.h b/src/audio_core/renderer/effect/capture.h
index 6fbed8e6b..cbe71e22a 100644
--- a/src/audio_core/renderer/effect/capture.h
+++ b/src/audio_core/renderer/effect/capture.h
@@ -9,7 +9,7 @@
9#include "audio_core/renderer/effect/effect_info_base.h" 9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13 13
14class CaptureInfo : public EffectInfoBase { 14class CaptureInfo : public EffectInfoBase {
15public: 15public:
@@ -62,4 +62,4 @@ public:
62 CpuAddr GetWorkbuffer(s32 index) override; 62 CpuAddr GetWorkbuffer(s32 index) override;
63}; 63};
64 64
65} // namespace AudioCore::AudioRenderer 65} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp
index 220ae02f9..fea0aefcf 100644
--- a/src/audio_core/renderer/effect/compressor.cpp
+++ b/src/audio_core/renderer/effect/compressor.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/compressor.h" 4#include "audio_core/renderer/effect/compressor.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, 8void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {} 9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {}
@@ -37,4 +37,4 @@ CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
37 return GetSingleBuffer(index); 37 return GetSingleBuffer(index);
38} 38}
39 39
40} // namespace AudioCore::AudioRenderer 40} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h
index 019a5ae58..cda55c284 100644
--- a/src/audio_core/renderer/effect/compressor.h
+++ b/src/audio_core/renderer/effect/compressor.h
@@ -10,7 +10,7 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/fixed_point.h" 11#include "common/fixed_point.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::Renderer {
14 14
15class CompressorInfo : public EffectInfoBase { 15class CompressorInfo : public EffectInfoBase {
16public: 16public:
@@ -103,4 +103,4 @@ public:
103 CpuAddr GetWorkbuffer(s32 index) override; 103 CpuAddr GetWorkbuffer(s32 index) override;
104}; 104};
105 105
106} // namespace AudioCore::AudioRenderer 106} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/delay.cpp b/src/audio_core/renderer/effect/delay.cpp
index d9853efd9..e038d4498 100644
--- a/src/audio_core/renderer/effect/delay.cpp
+++ b/src/audio_core/renderer/effect/delay.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/delay.h" 4#include "audio_core/renderer/effect/delay.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, 8void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) { 9 const PoolMapper& pool_mapper) {
@@ -90,4 +90,4 @@ CpuAddr DelayInfo::GetWorkbuffer(s32 index) {
90 return GetSingleBuffer(index); 90 return GetSingleBuffer(index);
91} 91}
92 92
93} // namespace AudioCore::AudioRenderer 93} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/delay.h b/src/audio_core/renderer/effect/delay.h
index accc42a06..47417fbc6 100644
--- a/src/audio_core/renderer/effect/delay.h
+++ b/src/audio_core/renderer/effect/delay.h
@@ -11,7 +11,7 @@
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/fixed_point.h" 12#include "common/fixed_point.h"
13 13
14namespace AudioCore::AudioRenderer { 14namespace AudioCore::Renderer {
15 15
16class DelayInfo : public EffectInfoBase { 16class DelayInfo : public EffectInfoBase {
17public: 17public:
@@ -132,4 +132,4 @@ public:
132 CpuAddr GetWorkbuffer(s32 index) override; 132 CpuAddr GetWorkbuffer(s32 index) override;
133}; 133};
134 134
135} // namespace AudioCore::AudioRenderer 135} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/effect_context.cpp b/src/audio_core/renderer/effect/effect_context.cpp
index 74c7801c9..00f6d7822 100644
--- a/src/audio_core/renderer/effect/effect_context.cpp
+++ b/src/audio_core/renderer/effect/effect_context.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/effect_context.h" 4#include "audio_core/renderer/effect/effect_context.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, 8void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
9 std::span<EffectResultState> result_states_cpu_, 9 std::span<EffectResultState> result_states_cpu_,
@@ -38,4 +38,4 @@ void EffectContext::UpdateStateByDspShared() {
38 } 38 }
39} 39}
40 40
41} // namespace AudioCore::AudioRenderer 41} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h
index 8f6d6e7d8..8364c5521 100644
--- a/src/audio_core/renderer/effect/effect_context.h
+++ b/src/audio_core/renderer/effect/effect_context.h
@@ -9,7 +9,7 @@
9#include "audio_core/renderer/effect/effect_result_state.h" 9#include "audio_core/renderer/effect/effect_result_state.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13 13
14class EffectContext { 14class EffectContext {
15public: 15public:
@@ -72,4 +72,4 @@ private:
72 size_t dsp_state_count{}; 72 size_t dsp_state_count{};
73}; 73};
74 74
75} // namespace AudioCore::AudioRenderer 75} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h
index dbdccf278..b49503409 100644
--- a/src/audio_core/renderer/effect/effect_info_base.h
+++ b/src/audio_core/renderer/effect/effect_info_base.h
@@ -12,7 +12,7 @@
12#include "audio_core/renderer/memory/pool_mapper.h" 12#include "audio_core/renderer/memory/pool_mapper.h"
13#include "common/common_types.h" 13#include "common/common_types.h"
14 14
15namespace AudioCore::AudioRenderer { 15namespace AudioCore::Renderer {
16/** 16/**
17 * Base of all effects. Holds various data and functions used for all derived effects. 17 * Base of all effects. Holds various data and functions used for all derived effects.
18 * Should not be used directly. 18 * Should not be used directly.
@@ -432,4 +432,4 @@ protected:
432 std::array<u8, sizeof(State)> state{}; 432 std::array<u8, sizeof(State)> state{};
433}; 433};
434 434
435} // namespace AudioCore::AudioRenderer 435} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/effect_reset.h b/src/audio_core/renderer/effect/effect_reset.h
index 1ea67e334..c9e3b4b78 100644
--- a/src/audio_core/renderer/effect/effect_reset.h
+++ b/src/audio_core/renderer/effect/effect_reset.h
@@ -14,7 +14,7 @@
14#include "audio_core/renderer/effect/reverb.h" 14#include "audio_core/renderer/effect/reverb.h"
15#include "common/common_types.h" 15#include "common/common_types.h"
16 16
17namespace AudioCore::AudioRenderer { 17namespace AudioCore::Renderer {
18/** 18/**
19 * Reset an effect, and create a new one of the given type. 19 * Reset an effect, and create a new one of the given type.
20 * 20 *
@@ -68,4 +68,4 @@ static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type)
68 } 68 }
69} 69}
70 70
71} // namespace AudioCore::AudioRenderer 71} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/effect_result_state.h b/src/audio_core/renderer/effect/effect_result_state.h
index ae096ad69..f4d4b6086 100644
--- a/src/audio_core/renderer/effect/effect_result_state.h
+++ b/src/audio_core/renderer/effect/effect_result_state.h
@@ -7,10 +7,10 @@
7 7
8#include "common/common_types.h" 8#include "common/common_types.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12struct EffectResultState { 12struct EffectResultState {
13 std::array<u8, 0x80> state; 13 std::array<u8, 0x80> state;
14}; 14};
15 15
16} // namespace AudioCore::AudioRenderer 16} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/i3dl2.cpp b/src/audio_core/renderer/effect/i3dl2.cpp
index 960b29cfc..a3c324c1e 100644
--- a/src/audio_core/renderer/effect/i3dl2.cpp
+++ b/src/audio_core/renderer/effect/i3dl2.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/i3dl2.h" 4#include "audio_core/renderer/effect/i3dl2.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, 8void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { 9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
@@ -91,4 +91,4 @@ CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) {
91 return GetSingleBuffer(index); 91 return GetSingleBuffer(index);
92} 92}
93 93
94} // namespace AudioCore::AudioRenderer 94} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h
index 6e3ffd1d4..e0432b4ae 100644
--- a/src/audio_core/renderer/effect/i3dl2.h
+++ b/src/audio_core/renderer/effect/i3dl2.h
@@ -11,7 +11,7 @@
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/fixed_point.h" 12#include "common/fixed_point.h"
13 13
14namespace AudioCore::AudioRenderer { 14namespace AudioCore::Renderer {
15 15
16class I3dl2ReverbInfo : public EffectInfoBase { 16class I3dl2ReverbInfo : public EffectInfoBase {
17public: 17public:
@@ -198,4 +198,4 @@ public:
198 CpuAddr GetWorkbuffer(s32 index) override; 198 CpuAddr GetWorkbuffer(s32 index) override;
199}; 199};
200 200
201} // namespace AudioCore::AudioRenderer 201} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/light_limiter.cpp b/src/audio_core/renderer/effect/light_limiter.cpp
index 1635a952d..9c8ea3c49 100644
--- a/src/audio_core/renderer/effect/light_limiter.cpp
+++ b/src/audio_core/renderer/effect/light_limiter.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/light_limiter.h" 4#include "audio_core/renderer/effect/light_limiter.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, 8void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { 9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
@@ -78,4 +78,4 @@ CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) {
78 return GetSingleBuffer(index); 78 return GetSingleBuffer(index);
79} 79}
80 80
81} // namespace AudioCore::AudioRenderer 81} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/light_limiter.h b/src/audio_core/renderer/effect/light_limiter.h
index 338d67bbc..7f2ede405 100644
--- a/src/audio_core/renderer/effect/light_limiter.h
+++ b/src/audio_core/renderer/effect/light_limiter.h
@@ -11,7 +11,7 @@
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/fixed_point.h" 12#include "common/fixed_point.h"
13 13
14namespace AudioCore::AudioRenderer { 14namespace AudioCore::Renderer {
15 15
16class LightLimiterInfo : public EffectInfoBase { 16class LightLimiterInfo : public EffectInfoBase {
17public: 17public:
@@ -135,4 +135,4 @@ public:
135 CpuAddr GetWorkbuffer(s32 index) override; 135 CpuAddr GetWorkbuffer(s32 index) override;
136}; 136};
137 137
138} // namespace AudioCore::AudioRenderer 138} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/reverb.cpp b/src/audio_core/renderer/effect/reverb.cpp
index 2d32383d0..4da72469a 100644
--- a/src/audio_core/renderer/effect/reverb.cpp
+++ b/src/audio_core/renderer/effect/reverb.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/effect/reverb.h" 4#include "audio_core/renderer/effect/reverb.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, 8void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) { 9 const PoolMapper& pool_mapper) {
@@ -90,4 +90,4 @@ CpuAddr ReverbInfo::GetWorkbuffer(s32 index) {
90 return GetSingleBuffer(index); 90 return GetSingleBuffer(index);
91} 91}
92 92
93} // namespace AudioCore::AudioRenderer 93} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h
index 6cc345ef6..52a048da6 100644
--- a/src/audio_core/renderer/effect/reverb.h
+++ b/src/audio_core/renderer/effect/reverb.h
@@ -11,7 +11,7 @@
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/fixed_point.h" 12#include "common/fixed_point.h"
13 13
14namespace AudioCore::AudioRenderer { 14namespace AudioCore::Renderer {
15 15
16class ReverbInfo : public EffectInfoBase { 16class ReverbInfo : public EffectInfoBase {
17public: 17public:
@@ -187,4 +187,4 @@ public:
187 CpuAddr GetWorkbuffer(s32 index) override; 187 CpuAddr GetWorkbuffer(s32 index) override;
188}; 188};
189 189
190} // namespace AudioCore::AudioRenderer 190} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h
index bb5c930e1..c81ef1b8a 100644
--- a/src/audio_core/renderer/memory/address_info.h
+++ b/src/audio_core/renderer/memory/address_info.h
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/memory/memory_pool_info.h" 6#include "audio_core/renderer/memory/memory_pool_info.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11/** 11/**
12 * Represents a region of mapped or unmapped memory. 12 * Represents a region of mapped or unmapped memory.
@@ -121,4 +121,4 @@ private:
121 CpuAddr dsp_address; 121 CpuAddr dsp_address;
122}; 122};
123 123
124} // namespace AudioCore::AudioRenderer 124} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/memory/memory_pool_info.cpp b/src/audio_core/renderer/memory/memory_pool_info.cpp
index 9b7824af1..03b44d5f3 100644
--- a/src/audio_core/renderer/memory/memory_pool_info.cpp
+++ b/src/audio_core/renderer/memory/memory_pool_info.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/memory/memory_pool_info.h" 4#include "audio_core/renderer/memory/memory_pool_info.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8CpuAddr MemoryPoolInfo::GetCpuAddress() const { 8CpuAddr MemoryPoolInfo::GetCpuAddress() const {
9 return cpu_address; 9 return cpu_address;
@@ -58,4 +58,4 @@ bool MemoryPoolInfo::IsUsed() const {
58 return in_use; 58 return in_use;
59} 59}
60 60
61} // namespace AudioCore::AudioRenderer 61} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/memory/memory_pool_info.h b/src/audio_core/renderer/memory/memory_pool_info.h
index 80c571bc1..2f9c85184 100644
--- a/src/audio_core/renderer/memory/memory_pool_info.h
+++ b/src/audio_core/renderer/memory/memory_pool_info.h
@@ -8,7 +8,7 @@
8#include "audio_core/common/common.h" 8#include "audio_core/common/common.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::Renderer {
12/** 12/**
13 * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). 13 * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
14 */ 14 */
@@ -167,4 +167,4 @@ private:
167 bool in_use{}; 167 bool in_use{};
168}; 168};
169 169
170} // namespace AudioCore::AudioRenderer 170} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp
index 7fd2b5f47..999bb746b 100644
--- a/src/audio_core/renderer/memory/pool_mapper.cpp
+++ b/src/audio_core/renderer/memory/pool_mapper.cpp
@@ -6,7 +6,7 @@
6#include "core/hle/kernel/k_process.h" 6#include "core/hle/kernel/k_process.h"
7#include "core/hle/kernel/svc.h" 7#include "core/hle/kernel/svc.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11PoolMapper::PoolMapper(u32 process_handle_, bool force_map_) 11PoolMapper::PoolMapper(u32 process_handle_, bool force_map_)
12 : process_handle{process_handle_}, force_map{force_map_} {} 12 : process_handle{process_handle_}, force_map{force_map_} {}
@@ -240,4 +240,4 @@ bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory,
240 } 240 }
241} 241}
242 242
243} // namespace AudioCore::AudioRenderer 243} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/memory/pool_mapper.h b/src/audio_core/renderer/memory/pool_mapper.h
index 9a691da7a..95ae5d8ea 100644
--- a/src/audio_core/renderer/memory/pool_mapper.h
+++ b/src/audio_core/renderer/memory/pool_mapper.h
@@ -10,7 +10,7 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/hle/service/audio/errors.h" 11#include "core/hle/service/audio/errors.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::Renderer {
14class AddressInfo; 14class AddressInfo;
15 15
16/** 16/**
@@ -176,4 +176,4 @@ private:
176 bool force_map; 176 bool force_map;
177}; 177};
178 178
179} // namespace AudioCore::AudioRenderer 179} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp
index 3a18ae7c2..c712610bb 100644
--- a/src/audio_core/renderer/mix/mix_context.cpp
+++ b/src/audio_core/renderer/mix/mix_context.cpp
@@ -7,7 +7,7 @@
7#include "audio_core/renderer/splitter/splitter_context.h" 7#include "audio_core/renderer/splitter/splitter_context.h"
8#include "common/polyfill_ranges.h" 8#include "common/polyfill_ranges.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_, 12void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_,
13 const u32 count_, std::span<s32> effect_process_order_buffer_, 13 const u32 count_, std::span<s32> effect_process_order_buffer_,
@@ -139,4 +139,4 @@ EdgeMatrix& MixContext::GetEdgeMatrix() {
139 return edge_matrix; 139 return edge_matrix;
140} 140}
141 141
142} // namespace AudioCore::AudioRenderer 142} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h
index bcd9637da..ce19ec8d6 100644
--- a/src/audio_core/renderer/mix/mix_context.h
+++ b/src/audio_core/renderer/mix/mix_context.h
@@ -10,7 +10,7 @@
10#include "audio_core/renderer/nodes/node_states.h" 10#include "audio_core/renderer/nodes/node_states.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::Renderer {
14class SplitterContext; 14class SplitterContext;
15 15
16/* 16/*
@@ -121,4 +121,4 @@ private:
121 EdgeMatrix edge_matrix{}; 121 EdgeMatrix edge_matrix{};
122}; 122};
123 123
124} // namespace AudioCore::AudioRenderer 124} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp
index cc18e57ee..5e44bde18 100644
--- a/src/audio_core/renderer/mix/mix_info.cpp
+++ b/src/audio_core/renderer/mix/mix_info.cpp
@@ -7,7 +7,7 @@
7#include "audio_core/renderer/nodes/edge_matrix.h" 7#include "audio_core/renderer/nodes/edge_matrix.h"
8#include "audio_core/renderer/splitter/splitter_context.h" 8#include "audio_core/renderer/splitter/splitter_context.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior) 12MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior)
13 : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_}, 13 : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_},
@@ -117,4 +117,4 @@ bool MixInfo::HasAnyConnection() const {
117 return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId; 117 return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId;
118} 118}
119 119
120} // namespace AudioCore::AudioRenderer 120} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h
index b5fa4c0c7..7005daa4f 100644
--- a/src/audio_core/renderer/mix/mix_info.h
+++ b/src/audio_core/renderer/mix/mix_info.h
@@ -9,7 +9,7 @@
9#include "audio_core/common/common.h" 9#include "audio_core/common/common.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13class EdgeMatrix; 13class EdgeMatrix;
14class SplitterContext; 14class SplitterContext;
15class EffectContext; 15class EffectContext;
@@ -121,4 +121,4 @@ public:
121 const bool long_size_pre_delay_supported; 121 const bool long_size_pre_delay_supported;
122}; 122};
123 123
124} // namespace AudioCore::AudioRenderer 124} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/nodes/bit_array.h b/src/audio_core/renderer/nodes/bit_array.h
index b0d53cd51..d8a2d09d0 100644
--- a/src/audio_core/renderer/nodes/bit_array.h
+++ b/src/audio_core/renderer/nodes/bit_array.h
@@ -7,7 +7,7 @@
7 7
8#include "common/common_types.h" 8#include "common/common_types.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11/** 11/**
12 * Represents an array of bits used for nodes and edges for the mixing graph. 12 * Represents an array of bits used for nodes and edges for the mixing graph.
13 */ 13 */
@@ -22,4 +22,4 @@ struct BitArray {
22 u32 size{}; 22 u32 size{};
23}; 23};
24 24
25} // namespace AudioCore::AudioRenderer 25} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/nodes/edge_matrix.cpp b/src/audio_core/renderer/nodes/edge_matrix.cpp
index 5573f33b9..c28773b22 100644
--- a/src/audio_core/renderer/nodes/edge_matrix.cpp
+++ b/src/audio_core/renderer/nodes/edge_matrix.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/nodes/edge_matrix.h" 4#include "audio_core/renderer/nodes/edge_matrix.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer, 8void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer,
9 [[maybe_unused]] const u64 node_buffer_size, const u32 count_) { 9 [[maybe_unused]] const u64 node_buffer_size, const u32 count_) {
@@ -35,4 +35,4 @@ u32 EdgeMatrix::GetNodeCount() const {
35 return count; 35 return count;
36} 36}
37 37
38} // namespace AudioCore::AudioRenderer 38} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/nodes/edge_matrix.h b/src/audio_core/renderer/nodes/edge_matrix.h
index 27a20e43e..0271c23b1 100644
--- a/src/audio_core/renderer/nodes/edge_matrix.h
+++ b/src/audio_core/renderer/nodes/edge_matrix.h
@@ -9,7 +9,7 @@
9#include "common/alignment.h" 9#include "common/alignment.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13/** 13/**
14 * An edge matrix, holding the connections for each node to every other node in the graph. 14 * An edge matrix, holding the connections for each node to every other node in the graph.
15 */ 15 */
@@ -79,4 +79,4 @@ private:
79 u32 count; 79 u32 count;
80}; 80};
81 81
82} // namespace AudioCore::AudioRenderer 82} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/nodes/node_states.cpp b/src/audio_core/renderer/nodes/node_states.cpp
index b7a44a54c..028a58041 100644
--- a/src/audio_core/renderer/nodes/node_states.cpp
+++ b/src/audio_core/renderer/nodes/node_states.cpp
@@ -4,7 +4,7 @@
4#include "audio_core/renderer/nodes/node_states.h" 4#include "audio_core/renderer/nodes/node_states.h"
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8 8
9void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size, 9void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size,
10 const u32 count) { 10 const u32 count) {
@@ -138,4 +138,4 @@ std::pair<std::span<u32>::reverse_iterator, size_t> NodeStates::GetSortedResuls(
138 return {results.rbegin(), result_pos}; 138 return {results.rbegin(), result_pos};
139} 139}
140 140
141} // namespace AudioCore::AudioRenderer 141} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h
index e768cd4b5..991a82841 100644
--- a/src/audio_core/renderer/nodes/node_states.h
+++ b/src/audio_core/renderer/nodes/node_states.h
@@ -10,7 +10,7 @@
10#include "common/alignment.h" 10#include "common/alignment.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::Renderer {
14/** 14/**
15 * Graph utility functions for sorting and getting results from the DAG. 15 * Graph utility functions for sorting and getting results from the DAG.
16 */ 16 */
@@ -192,4 +192,4 @@ private:
192 Stack stack{}; 192 Stack stack{};
193}; 193};
194 194
195} // namespace AudioCore::AudioRenderer 195} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp
index f6405937f..ef8b47cee 100644
--- a/src/audio_core/renderer/performance/detail_aspect.cpp
+++ b/src/audio_core/renderer/performance/detail_aspect.cpp
@@ -5,7 +5,7 @@
5#include "audio_core/renderer/command/command_generator.h" 5#include "audio_core/renderer/command/command_generator.h"
6#include "audio_core/renderer/performance/detail_aspect.h" 6#include "audio_core/renderer/performance/detail_aspect.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10DetailAspect::DetailAspect(CommandGenerator& command_generator_, 10DetailAspect::DetailAspect(CommandGenerator& command_generator_,
11 const PerformanceEntryType entry_type, const s32 node_id_, 11 const PerformanceEntryType entry_type, const s32 node_id_,
@@ -22,4 +22,4 @@ DetailAspect::DetailAspect(CommandGenerator& command_generator_,
22 } 22 }
23} 23}
24 24
25} // namespace AudioCore::AudioRenderer 25} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h
index 736c331b9..0bd7f80c8 100644
--- a/src/audio_core/renderer/performance/detail_aspect.h
+++ b/src/audio_core/renderer/performance/detail_aspect.h
@@ -7,7 +7,7 @@
7#include "audio_core/renderer/performance/performance_manager.h" 7#include "audio_core/renderer/performance/performance_manager.h"
8#include "common/common_types.h" 8#include "common/common_types.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11class CommandGenerator; 11class CommandGenerator;
12 12
13/** 13/**
@@ -29,4 +29,4 @@ public:
29 s32 node_id; 29 s32 node_id;
30}; 30};
31 31
32} // namespace AudioCore::AudioRenderer 32} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp
index dd4165803..c9241a639 100644
--- a/src/audio_core/renderer/performance/entry_aspect.cpp
+++ b/src/audio_core/renderer/performance/entry_aspect.cpp
@@ -5,7 +5,7 @@
5#include "audio_core/renderer/command/command_generator.h" 5#include "audio_core/renderer/command/command_generator.h"
6#include "audio_core/renderer/performance/entry_aspect.h" 6#include "audio_core/renderer/performance/entry_aspect.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type, 10EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type,
11 const s32 node_id_) 11 const s32 node_id_)
@@ -20,4 +20,4 @@ EntryAspect::EntryAspect(CommandGenerator& command_generator_, const Performance
20 } 20 }
21} 21}
22 22
23} // namespace AudioCore::AudioRenderer 23} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h
index 14c9e3baf..f99287d68 100644
--- a/src/audio_core/renderer/performance/entry_aspect.h
+++ b/src/audio_core/renderer/performance/entry_aspect.h
@@ -7,7 +7,7 @@
7#include "audio_core/renderer/performance/performance_manager.h" 7#include "audio_core/renderer/performance/performance_manager.h"
8#include "common/common_types.h" 8#include "common/common_types.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11class CommandGenerator; 11class CommandGenerator;
12 12
13/** 13/**
@@ -28,4 +28,4 @@ public:
28 s32 node_id; 28 s32 node_id;
29}; 29};
30 30
31} // namespace AudioCore::AudioRenderer 31} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h
index f603b9026..2b0cf9422 100644
--- a/src/audio_core/renderer/performance/performance_detail.h
+++ b/src/audio_core/renderer/performance/performance_detail.h
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/performance/performance_entry.h" 6#include "audio_core/renderer/performance/performance_entry.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11enum class PerformanceDetailType : u8 { 11enum class PerformanceDetailType : u8 {
12 Invalid, 12 Invalid,
@@ -47,4 +47,4 @@ struct PerformanceDetailVersion2 {
47static_assert(sizeof(PerformanceDetailVersion2) == 0x18, 47static_assert(sizeof(PerformanceDetailVersion2) == 0x18,
48 "PerformanceDetailVersion2 has the wrong size!"); 48 "PerformanceDetailVersion2 has the wrong size!");
49 49
50} // namespace AudioCore::AudioRenderer 50} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h
index d6b1158db..dbd6053a5 100644
--- a/src/audio_core/renderer/performance/performance_entry.h
+++ b/src/audio_core/renderer/performance/performance_entry.h
@@ -5,7 +5,7 @@
5 5
6#include "common/common_types.h" 6#include "common/common_types.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10enum class PerformanceEntryType : u8 { 10enum class PerformanceEntryType : u8 {
11 Invalid, 11 Invalid,
@@ -34,4 +34,4 @@ struct PerformanceEntryVersion2 {
34static_assert(sizeof(PerformanceEntryVersion2) == 0x18, 34static_assert(sizeof(PerformanceEntryVersion2) == 0x18,
35 "PerformanceEntryVersion2 has the wrong size!"); 35 "PerformanceEntryVersion2 has the wrong size!");
36 36
37} // namespace AudioCore::AudioRenderer 37} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h
index e381d765c..51eee975f 100644
--- a/src/audio_core/renderer/performance/performance_entry_addresses.h
+++ b/src/audio_core/renderer/performance/performance_entry_addresses.h
@@ -5,7 +5,7 @@
5 5
6#include "audio_core/common/common.h" 6#include "audio_core/common/common.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10struct PerformanceEntryAddresses { 10struct PerformanceEntryAddresses {
11 CpuAddr translated_address; 11 CpuAddr translated_address;
@@ -14,4 +14,4 @@ struct PerformanceEntryAddresses {
14 CpuAddr entry_processed_time_offset; 14 CpuAddr entry_processed_time_offset;
15}; 15};
16 16
17} // namespace AudioCore::AudioRenderer 17} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h
index b1848284e..24e4989f8 100644
--- a/src/audio_core/renderer/performance/performance_frame_header.h
+++ b/src/audio_core/renderer/performance/performance_frame_header.h
@@ -5,7 +5,7 @@
5 5
6#include "common/common_types.h" 6#include "common/common_types.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10struct PerformanceFrameHeaderVersion1 { 10struct PerformanceFrameHeaderVersion1 {
11 /* 0x00 */ u32 magic; // "PERF" 11 /* 0x00 */ u32 magic; // "PERF"
@@ -33,4 +33,4 @@ struct PerformanceFrameHeaderVersion2 {
33static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30, 33static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30,
34 "PerformanceFrameHeaderVersion2 has the wrong size!"); 34 "PerformanceFrameHeaderVersion2 has the wrong size!");
35 35
36} // namespace AudioCore::AudioRenderer 36} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp
index 8aa0f5ed0..ce736db71 100644
--- a/src/audio_core/renderer/performance/performance_manager.cpp
+++ b/src/audio_core/renderer/performance/performance_manager.cpp
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/performance/performance_manager.h" 6#include "audio_core/renderer/performance/performance_manager.h"
7#include "common/common_funcs.h" 7#include "common/common_funcs.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11void PerformanceManager::CreateImpl(const size_t version) { 11void PerformanceManager::CreateImpl(const size_t version) {
12 switch (version) { 12 switch (version) {
@@ -643,4 +643,4 @@ void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeader
643 target_node_id = target_node_id_; 643 target_node_id = target_node_id_;
644} 644}
645 645
646} // namespace AudioCore::AudioRenderer 646} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h
index b65caa9b6..ffd0fa1fb 100644
--- a/src/audio_core/renderer/performance/performance_manager.h
+++ b/src/audio_core/renderer/performance/performance_manager.h
@@ -14,7 +14,7 @@
14#include "audio_core/renderer/performance/performance_frame_header.h" 14#include "audio_core/renderer/performance/performance_frame_header.h"
15#include "common/common_types.h" 15#include "common/common_types.h"
16 16
17namespace AudioCore::AudioRenderer { 17namespace AudioCore::Renderer {
18class BehaviorInfo; 18class BehaviorInfo;
19class MemoryPoolInfo; 19class MemoryPoolInfo;
20 20
@@ -272,4 +272,4 @@ private:
272 PerformanceVersion version{}; 272 PerformanceVersion version{};
273}; 273};
274 274
275} // namespace AudioCore::AudioRenderer 275} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
index d91f10402..0ede02b6b 100644
--- a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
+++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
@@ -5,7 +5,7 @@
5#include "audio_core/renderer/sink/circular_buffer_sink_info.h" 5#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
6#include "audio_core/renderer/upsampler/upsampler_manager.h" 6#include "audio_core/renderer/upsampler/upsampler_manager.h"
7 7
8namespace AudioCore::AudioRenderer { 8namespace AudioCore::Renderer {
9 9
10CircularBufferSinkInfo::CircularBufferSinkInfo() { 10CircularBufferSinkInfo::CircularBufferSinkInfo() {
11 state.fill(0); 11 state.fill(0);
@@ -73,4 +73,4 @@ void CircularBufferSinkInfo::UpdateForCommandGeneration() {
73 } 73 }
74} 74}
75 75
76} // namespace AudioCore::AudioRenderer 76} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.h b/src/audio_core/renderer/sink/circular_buffer_sink_info.h
index 3356213ea..d4e61d641 100644
--- a/src/audio_core/renderer/sink/circular_buffer_sink_info.h
+++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.h
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/sink/sink_info_base.h" 6#include "audio_core/renderer/sink/sink_info_base.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10/** 10/**
11 * Info for a circular buffer sink. 11 * Info for a circular buffer sink.
12 */ 12 */
@@ -38,4 +38,4 @@ public:
38static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase), 38static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase),
39 "CircularBufferSinkInfo is too large!"); 39 "CircularBufferSinkInfo is too large!");
40 40
41} // namespace AudioCore::AudioRenderer 41} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/sink/device_sink_info.cpp b/src/audio_core/renderer/sink/device_sink_info.cpp
index b7b3d6f1d..2de05e38e 100644
--- a/src/audio_core/renderer/sink/device_sink_info.cpp
+++ b/src/audio_core/renderer/sink/device_sink_info.cpp
@@ -4,7 +4,7 @@
4#include "audio_core/renderer/sink/device_sink_info.h" 4#include "audio_core/renderer/sink/device_sink_info.h"
5#include "audio_core/renderer/upsampler/upsampler_manager.h" 5#include "audio_core/renderer/upsampler/upsampler_manager.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8 8
9DeviceSinkInfo::DeviceSinkInfo() { 9DeviceSinkInfo::DeviceSinkInfo() {
10 state.fill(0); 10 state.fill(0);
@@ -54,4 +54,4 @@ void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_
54 54
55void DeviceSinkInfo::UpdateForCommandGeneration() {} 55void DeviceSinkInfo::UpdateForCommandGeneration() {}
56 56
57} // namespace AudioCore::AudioRenderer 57} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/sink/device_sink_info.h b/src/audio_core/renderer/sink/device_sink_info.h
index a1c441454..7974ae820 100644
--- a/src/audio_core/renderer/sink/device_sink_info.h
+++ b/src/audio_core/renderer/sink/device_sink_info.h
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/sink/sink_info_base.h" 6#include "audio_core/renderer/sink/sink_info_base.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10/** 10/**
11 * Info for a device sink. 11 * Info for a device sink.
12 */ 12 */
@@ -37,4 +37,4 @@ public:
37}; 37};
38static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!"); 38static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!");
39 39
40} // namespace AudioCore::AudioRenderer 40} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/sink/sink_context.cpp b/src/audio_core/renderer/sink/sink_context.cpp
index 634bc1cf9..a4f9cac21 100644
--- a/src/audio_core/renderer/sink/sink_context.cpp
+++ b/src/audio_core/renderer/sink/sink_context.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/sink/sink_context.h" 4#include "audio_core/renderer/sink/sink_context.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) { 8void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) {
9 sink_infos = sink_infos_; 9 sink_infos = sink_infos_;
@@ -18,4 +18,4 @@ u32 SinkContext::GetCount() const {
18 return sink_count; 18 return sink_count;
19} 19}
20 20
21} // namespace AudioCore::AudioRenderer 21} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/sink/sink_context.h b/src/audio_core/renderer/sink/sink_context.h
index 185572e29..66925b48e 100644
--- a/src/audio_core/renderer/sink/sink_context.h
+++ b/src/audio_core/renderer/sink/sink_context.h
@@ -8,7 +8,7 @@
8#include "audio_core/renderer/sink/sink_info_base.h" 8#include "audio_core/renderer/sink/sink_info_base.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::Renderer {
12/** 12/**
13 * Manages output sinks. 13 * Manages output sinks.
14 */ 14 */
@@ -44,4 +44,4 @@ private:
44 u32 sink_count{}; 44 u32 sink_count{};
45}; 45};
46 46
47} // namespace AudioCore::AudioRenderer 47} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/sink/sink_info_base.cpp b/src/audio_core/renderer/sink/sink_info_base.cpp
index 4279beaa0..8a064f15a 100644
--- a/src/audio_core/renderer/sink/sink_info_base.cpp
+++ b/src/audio_core/renderer/sink/sink_info_base.cpp
@@ -4,7 +4,7 @@
4#include "audio_core/renderer/memory/pool_mapper.h" 4#include "audio_core/renderer/memory/pool_mapper.h"
5#include "audio_core/renderer/sink/sink_info_base.h" 5#include "audio_core/renderer/sink/sink_info_base.h"
6 6
7namespace AudioCore::AudioRenderer { 7namespace AudioCore::Renderer {
8 8
9void SinkInfoBase::CleanUp() { 9void SinkInfoBase::CleanUp() {
10 type = Type::Invalid; 10 type = Type::Invalid;
@@ -48,4 +48,4 @@ u8* SinkInfoBase::GetParameter() {
48 return parameter.data(); 48 return parameter.data();
49} 49}
50 50
51} // namespace AudioCore::AudioRenderer 51} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/sink/sink_info_base.h b/src/audio_core/renderer/sink/sink_info_base.h
index a1b855f20..e10d1cb38 100644
--- a/src/audio_core/renderer/sink/sink_info_base.h
+++ b/src/audio_core/renderer/sink/sink_info_base.h
@@ -11,7 +11,7 @@
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/fixed_point.h" 12#include "common/fixed_point.h"
13 13
14namespace AudioCore::AudioRenderer { 14namespace AudioCore::Renderer {
15struct UpsamplerInfo; 15struct UpsamplerInfo;
16class PoolMapper; 16class PoolMapper;
17 17
@@ -174,4 +174,4 @@ protected:
174 parameter{}; 174 parameter{};
175}; 175};
176 176
177} // namespace AudioCore::AudioRenderer 177} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp
index 7a23ba43f..686150ea6 100644
--- a/src/audio_core/renderer/splitter/splitter_context.cpp
+++ b/src/audio_core/renderer/splitter/splitter_context.cpp
@@ -7,7 +7,7 @@
7#include "audio_core/renderer/splitter/splitter_context.h" 7#include "audio_core/renderer/splitter/splitter_context.h"
8#include "common/alignment.h" 8#include "common/alignment.h"
9 9
10namespace AudioCore::AudioRenderer { 10namespace AudioCore::Renderer {
11 11
12SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, 12SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id,
13 const s32 destination_id) { 13 const s32 destination_id) {
@@ -214,4 +214,4 @@ u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior,
214 return size; 214 return size;
215} 215}
216 216
217} // namespace AudioCore::AudioRenderer 217} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h
index 1a63db1d3..556e6dcc3 100644
--- a/src/audio_core/renderer/splitter/splitter_context.h
+++ b/src/audio_core/renderer/splitter/splitter_context.h
@@ -13,7 +13,7 @@ namespace AudioCore {
13struct AudioRendererParameterInternal; 13struct AudioRendererParameterInternal;
14class WorkbufferAllocator; 14class WorkbufferAllocator;
15 15
16namespace AudioRenderer { 16namespace Renderer {
17class BehaviorInfo; 17class BehaviorInfo;
18 18
19/** 19/**
@@ -185,5 +185,5 @@ private:
185 bool splitter_bug_fixed{}; 185 bool splitter_bug_fixed{};
186}; 186};
187 187
188} // namespace AudioRenderer 188} // namespace Renderer
189} // namespace AudioCore 189} // namespace AudioCore
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
index b27d44896..5ec37e48e 100644
--- a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
+++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/splitter/splitter_destinations_data.h" 4#include "audio_core/renderer/splitter/splitter_destinations_data.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {} 8SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {}
9 9
@@ -84,4 +84,4 @@ void SplitterDestinationData::SetNext(SplitterDestinationData* next_) {
84 next = next_; 84 next = next_;
85} 85}
86 86
87} // namespace AudioCore::AudioRenderer 87} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h
index d55ce0ad3..90edfc667 100644
--- a/src/audio_core/renderer/splitter/splitter_destinations_data.h
+++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h
@@ -9,7 +9,7 @@
9#include "audio_core/common/common.h" 9#include "audio_core/common/common.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13/** 13/**
14 * Represents a mixing node, can be connected to a previous and next destination forming a chain 14 * Represents a mixing node, can be connected to a previous and next destination forming a chain
15 * that a certain mix buffer will pass through to output. 15 * that a certain mix buffer will pass through to output.
@@ -132,4 +132,4 @@ private:
132 bool need_update{}; 132 bool need_update{};
133}; 133};
134 134
135} // namespace AudioCore::AudioRenderer 135} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp
index 1aee6720b..beb5b7f19 100644
--- a/src/audio_core/renderer/splitter/splitter_info.cpp
+++ b/src/audio_core/renderer/splitter/splitter_info.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/splitter/splitter_info.h" 4#include "audio_core/renderer/splitter/splitter_info.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {} 8SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {}
9 9
@@ -76,4 +76,4 @@ void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) {
76 destinations = destinations_; 76 destinations = destinations_;
77} 77}
78 78
79} // namespace AudioCore::AudioRenderer 79} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h
index b0ad01fe0..c1e4c2df1 100644
--- a/src/audio_core/renderer/splitter/splitter_info.h
+++ b/src/audio_core/renderer/splitter/splitter_info.h
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/splitter/splitter_destinations_data.h" 6#include "audio_core/renderer/splitter/splitter_destinations_data.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10/** 10/**
11 * Represents a splitter, wraps multiple output destinations to split an input mix into. 11 * Represents a splitter, wraps multiple output destinations to split an input mix into.
12 */ 12 */
@@ -104,4 +104,4 @@ private:
104 u32 channel_count{}; 104 u32 channel_count{};
105}; 105};
106 106
107} // namespace AudioCore::AudioRenderer 107} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp
index a23627472..31f92087c 100644
--- a/src/audio_core/renderer/system.cpp
+++ b/src/audio_core/renderer/system.cpp
@@ -4,12 +4,13 @@
4#include <chrono> 4#include <chrono>
5#include <span> 5#include <span>
6 6
7#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
8#include "audio_core/adsp/apps/audio_renderer/command_buffer.h"
7#include "audio_core/audio_core.h" 9#include "audio_core/audio_core.h"
8#include "audio_core/common/audio_renderer_parameter.h" 10#include "audio_core/common/audio_renderer_parameter.h"
9#include "audio_core/common/common.h" 11#include "audio_core/common/common.h"
10#include "audio_core/common/feature_support.h" 12#include "audio_core/common/feature_support.h"
11#include "audio_core/common/workbuffer_allocator.h" 13#include "audio_core/common/workbuffer_allocator.h"
12#include "audio_core/renderer/adsp/adsp.h"
13#include "audio_core/renderer/behavior/info_updater.h" 14#include "audio_core/renderer/behavior/info_updater.h"
14#include "audio_core/renderer/command/command_buffer.h" 15#include "audio_core/renderer/command/command_buffer.h"
15#include "audio_core/renderer/command/command_generator.h" 16#include "audio_core/renderer/command/command_generator.h"
@@ -34,7 +35,7 @@
34#include "core/hle/kernel/k_transfer_memory.h" 35#include "core/hle/kernel/k_transfer_memory.h"
35#include "core/memory.h" 36#include "core/memory.h"
36 37
37namespace AudioCore::AudioRenderer { 38namespace AudioCore::Renderer {
38 39
39u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) { 40u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
40 BehaviorInfo behavior; 41 BehaviorInfo behavior;
@@ -95,7 +96,8 @@ u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
95} 96}
96 97
97System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) 98System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
98 : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {} 99 : core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()},
100 adsp_rendered_event{adsp_rendered_event_} {}
99 101
100Result System::Initialize(const AudioRendererParameterInternal& params, 102Result System::Initialize(const AudioRendererParameterInternal& params,
101 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, 103 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
@@ -443,7 +445,7 @@ void System::Stop() {
443Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) { 445Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) {
444 std::scoped_lock l{lock}; 446 std::scoped_lock l{lock};
445 447
446 const auto start_time{core.CoreTiming().GetClockTicks()}; 448 const auto start_time{core.CoreTiming().GetGlobalTimeNs().count()};
447 std::memset(output.data(), 0, output.size()); 449 std::memset(output.data(), 0, output.size());
448 450
449 InfoUpdater info_updater(input, output, process_handle, behavior); 451 InfoUpdater info_updater(input, output, process_handle, behavior);
@@ -535,7 +537,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
535 adsp_rendered_event->Clear(); 537 adsp_rendered_event->Clear();
536 num_times_updated++; 538 num_times_updated++;
537 539
538 const auto end_time{core.CoreTiming().GetClockTicks()}; 540 const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
539 ticks_spent_updating += end_time - start_time; 541 ticks_spent_updating += end_time - start_time;
540 542
541 return ResultSuccess; 543 return ResultSuccess;
@@ -583,7 +585,7 @@ void System::SendCommandToDsp() {
583 if (initialized) { 585 if (initialized) {
584 if (active) { 586 if (active) {
585 terminate_event.Reset(); 587 terminate_event.Reset();
586 const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)}; 588 const auto remaining_command_count{audio_renderer.GetRemainCommandCount(session_id)};
587 u64 command_size{0}; 589 u64 command_size{0};
588 590
589 if (remaining_command_count) { 591 if (remaining_command_count) {
@@ -607,26 +609,18 @@ void System::SendCommandToDsp() {
607 time_limit_percent = 70.0f; 609 time_limit_percent = 70.0f;
608 } 610 }
609 611
610 ADSP::CommandBuffer command_buffer{ 612 auto time_limit{
611 .buffer{translated_addr}, 613 static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
612 .size{command_size}, 614 (static_cast<f32>(render_time_limit_percent) / 100.0f))};
613 .time_limit{ 615 audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
614 static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * 616 applet_resource_user_id, reset_command_buffers);
615 (static_cast<f32>(render_time_limit_percent) / 100.0f))},
616 .remaining_command_count{remaining_command_count},
617 .reset_buffers{reset_command_buffers},
618 .applet_resource_user_id{applet_resource_user_id},
619 .render_time_taken{adsp.GetRenderTimeTaken(session_id)},
620 };
621
622 adsp.SendCommandBuffer(session_id, command_buffer);
623 reset_command_buffers = false; 617 reset_command_buffers = false;
624 command_buffer_size = command_size; 618 command_buffer_size = command_size;
625 if (remaining_command_count == 0) { 619 if (remaining_command_count == 0) {
626 adsp_rendered_event->Signal(); 620 adsp_rendered_event->Signal();
627 } 621 }
628 } else { 622 } else {
629 adsp.ClearRemainCount(session_id); 623 audio_renderer.ClearRemainCommandCount(session_id);
630 terminate_event.Set(); 624 terminate_event.Set();
631 } 625 }
632 } 626 }
@@ -635,7 +629,7 @@ void System::SendCommandToDsp() {
635u64 System::GenerateCommand(std::span<u8> in_command_buffer, 629u64 System::GenerateCommand(std::span<u8> in_command_buffer,
636 [[maybe_unused]] u64 command_buffer_size_) { 630 [[maybe_unused]] u64 command_buffer_size_) {
637 PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count); 631 PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count);
638 const auto start_time{core.CoreTiming().GetClockTicks()}; 632 const auto start_time{core.CoreTiming().GetGlobalTimeNs().count()};
639 633
640 auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())}; 634 auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())};
641 635
@@ -690,11 +684,11 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
690 sink_context, splitter_context, perf_manager}; 684 sink_context, splitter_context, perf_manager};
691 685
692 voice_context.SortInfo(); 686 voice_context.SortInfo();
687 command_generator.GenerateVoiceCommands();
693 688
694 const auto start_estimated_time{drop_voice_param * 689 const auto start_estimated_time{drop_voice_param *
695 static_cast<f32>(command_buffer.estimated_process_time)}; 690 static_cast<f32>(command_buffer.estimated_process_time)};
696 691
697 command_generator.GenerateVoiceCommands();
698 command_generator.GenerateSubMixCommands(); 692 command_generator.GenerateSubMixCommands();
699 command_generator.GenerateFinalMixCommands(); 693 command_generator.GenerateFinalMixCommands();
700 command_generator.GenerateSinkCommands(); 694 command_generator.GenerateSinkCommands();
@@ -714,11 +708,13 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
714 708
715 const auto end_estimated_time{drop_voice_param * 709 const auto end_estimated_time{drop_voice_param *
716 static_cast<f32>(command_buffer.estimated_process_time)}; 710 static_cast<f32>(command_buffer.estimated_process_time)};
711
712 const auto dsp_time_limit{((time_limit_percent / 100.0f) * 2'880'000.0f) *
713 (static_cast<f32>(render_time_limit_percent) / 100.0f)};
714
717 const auto estimated_time{start_estimated_time - end_estimated_time}; 715 const auto estimated_time{start_estimated_time - end_estimated_time};
718 716
719 const auto time_limit{static_cast<u32>( 717 const auto time_limit{static_cast<u32>(std::max(dsp_time_limit + estimated_time, 0.0f))};
720 estimated_time + (((time_limit_percent / 100.0f) * 2'880'000.0) *
721 (static_cast<f32>(render_time_limit_percent) / 100.0f)))};
722 num_voices_dropped = 718 num_voices_dropped =
723 DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit); 719 DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit);
724 } 720 }
@@ -732,10 +728,10 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
732 effect_context.UpdateStateByDspShared(); 728 effect_context.UpdateStateByDspShared();
733 } 729 }
734 730
735 const auto end_time{core.CoreTiming().GetClockTicks()}; 731 const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
736 total_ticks_elapsed += end_time - start_time; 732 total_ticks_elapsed += end_time - start_time;
737 num_command_lists_generated++; 733 num_command_lists_generated++;
738 render_start_tick = adsp.GetRenderingStartTick(session_id); 734 render_start_tick = audio_renderer.GetRenderingStartTick(session_id);
739 frames_elapsed++; 735 frames_elapsed++;
740 736
741 return command_buffer.size; 737 return command_buffer.size;
@@ -778,7 +774,7 @@ u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time
778 while (i < command_buffer.count) { 774 while (i < command_buffer.count) {
779 const auto node_id{cmd->node_id}; 775 const auto node_id{cmd->node_id};
780 const auto node_id_type{cmd->node_id >> 28}; 776 const auto node_id_type{cmd->node_id >> 28};
781 const auto node_id_base{cmd->node_id & 0xFFF}; 777 const auto node_id_base{(cmd->node_id >> 16) & 0xFFF};
782 778
783 // If the new estimated process time falls below the limit, we're done dropping. 779 // If the new estimated process time falls below the limit, we're done dropping.
784 if (estimated_process_time <= time_limit) { 780 if (estimated_process_time <= time_limit) {
@@ -819,4 +815,4 @@ u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time
819 return voices_dropped; 815 return voices_dropped;
820} 816}
821 817
822} // namespace AudioCore::AudioRenderer 818} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h
index e328783b6..8a8341710 100644
--- a/src/audio_core/renderer/system.h
+++ b/src/audio_core/renderer/system.h
@@ -34,12 +34,16 @@ class KTransferMemory;
34 34
35namespace AudioCore { 35namespace AudioCore {
36struct AudioRendererParameterInternal; 36struct AudioRendererParameterInternal;
37
38namespace AudioRenderer {
39class CommandBuffer;
40namespace ADSP { 37namespace ADSP {
41class ADSP; 38class ADSP;
39namespace AudioRenderer {
40class AudioRenderer;
42} 41}
42} // namespace ADSP
43
44namespace Renderer {
45using namespace ::AudioCore::ADSP;
46class CommandBuffer;
43 47
44/** 48/**
45 * Audio Renderer System, the main worker for audio rendering. 49 * Audio Renderer System, the main worker for audio rendering.
@@ -213,8 +217,8 @@ public:
213private: 217private:
214 /// Core system 218 /// Core system
215 Core::System& core; 219 Core::System& core;
216 /// Reference to the ADSP for communication 220 /// Reference to the ADSP's AudioRenderer for communication
217 ADSP::ADSP& adsp; 221 ::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
218 /// Is this system initialized? 222 /// Is this system initialized?
219 bool initialized{}; 223 bool initialized{};
220 /// Is this system currently active? 224 /// Is this system currently active?
@@ -319,5 +323,5 @@ private:
319 f32 drop_voice_param{1.0f}; 323 f32 drop_voice_param{1.0f};
320}; 324};
321 325
322} // namespace AudioRenderer 326} // namespace Renderer
323} // namespace AudioCore 327} // namespace AudioCore
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp
index 300ecdbf1..a0b8ef29e 100644
--- a/src/audio_core/renderer/system_manager.cpp
+++ b/src/audio_core/renderer/system_manager.cpp
@@ -3,8 +3,8 @@
3 3
4#include <chrono> 4#include <chrono>
5 5
6#include "audio_core/adsp/adsp.h"
6#include "audio_core/audio_core.h" 7#include "audio_core/audio_core.h"
7#include "audio_core/renderer/adsp/adsp.h"
8#include "audio_core/renderer/system_manager.h" 8#include "audio_core/renderer/system_manager.h"
9#include "common/microprofile.h" 9#include "common/microprofile.h"
10#include "common/thread.h" 10#include "common/thread.h"
@@ -14,24 +14,21 @@
14MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", 14MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
15 MP_RGB(60, 19, 97)); 15 MP_RGB(60, 19, 97));
16 16
17namespace AudioCore::AudioRenderer { 17namespace AudioCore::Renderer {
18 18
19SystemManager::SystemManager(Core::System& core_) 19SystemManager::SystemManager(Core::System& core_)
20 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()} {} 20 : core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()} {}
21 21
22SystemManager::~SystemManager() { 22SystemManager::~SystemManager() {
23 Stop(); 23 Stop();
24} 24}
25 25
26bool SystemManager::InitializeUnsafe() { 26void SystemManager::InitializeUnsafe() {
27 if (!active) { 27 if (!active) {
28 if (adsp.Start()) { 28 active = true;
29 active = true; 29 audio_renderer.Start();
30 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); }); 30 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); });
31 }
32 } 31 }
33
34 return adsp.GetState() == ADSP::State::Started;
35} 32}
36 33
37void SystemManager::Stop() { 34void SystemManager::Stop() {
@@ -41,7 +38,7 @@ void SystemManager::Stop() {
41 active = false; 38 active = false;
42 thread.request_stop(); 39 thread.request_stop();
43 thread.join(); 40 thread.join();
44 adsp.Stop(); 41 audio_renderer.Stop();
45} 42}
46 43
47bool SystemManager::Add(System& system_) { 44bool SystemManager::Add(System& system_) {
@@ -55,10 +52,7 @@ bool SystemManager::Add(System& system_) {
55 { 52 {
56 std::scoped_lock l{mutex1}; 53 std::scoped_lock l{mutex1};
57 if (systems.empty()) { 54 if (systems.empty()) {
58 if (!InitializeUnsafe()) { 55 InitializeUnsafe();
59 LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager");
60 return false;
61 }
62 } 56 }
63 } 57 }
64 58
@@ -100,9 +94,9 @@ void SystemManager::ThreadFunc(std::stop_token stop_token) {
100 } 94 }
101 } 95 }
102 96
103 adsp.Signal(); 97 audio_renderer.Signal();
104 adsp.Wait(); 98 audio_renderer.Wait();
105 } 99 }
106} 100}
107 101
108} // namespace AudioCore::AudioRenderer 102} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h
index 9681fd121..62e8e5f15 100644
--- a/src/audio_core/renderer/system_manager.h
+++ b/src/audio_core/renderer/system_manager.h
@@ -18,11 +18,14 @@ struct EventType;
18class System; 18class System;
19} // namespace Core 19} // namespace Core
20 20
21namespace AudioCore::AudioRenderer { 21namespace AudioCore::ADSP {
22namespace ADSP {
23class ADSP; 22class ADSP;
24class AudioRenderer_Mailbox; 23namespace AudioRenderer {
25} // namespace ADSP 24class AudioRenderer;
25} // namespace AudioRenderer
26} // namespace AudioCore::ADSP
27
28namespace AudioCore::Renderer {
26 29
27/** 30/**
28 * Manages all audio renderers, responsible for triggering command list generation and signalling 31 * Manages all audio renderers, responsible for triggering command list generation and signalling
@@ -38,7 +41,7 @@ public:
38 * 41 *
39 * @return True if successfully initialized, otherwise false. 42 * @return True if successfully initialized, otherwise false.
40 */ 43 */
41 bool InitializeUnsafe(); 44 void InitializeUnsafe();
42 45
43 /** 46 /**
44 * Stop the system manager. 47 * Stop the system manager.
@@ -80,10 +83,8 @@ private:
80 std::mutex mutex2{}; 83 std::mutex mutex2{};
81 /// Is the system manager thread active? 84 /// Is the system manager thread active?
82 std::atomic<bool> active{}; 85 std::atomic<bool> active{};
83 /// Reference to the ADSP for communication 86 /// Reference to the ADSP's AudioRenderer for communication
84 ADSP::ADSP& adsp; 87 ::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
85 /// AudioRenderer mailbox for communication
86 ADSP::AudioRenderer_Mailbox* mailbox{};
87}; 88};
88 89
89} // namespace AudioCore::AudioRenderer 90} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h
index a43c15af3..85c87f137 100644
--- a/src/audio_core/renderer/upsampler/upsampler_info.h
+++ b/src/audio_core/renderer/upsampler/upsampler_info.h
@@ -9,7 +9,7 @@
9#include "audio_core/renderer/upsampler/upsampler_state.h" 9#include "audio_core/renderer/upsampler/upsampler_state.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13class UpsamplerManager; 13class UpsamplerManager;
14 14
15/** 15/**
@@ -32,4 +32,4 @@ struct UpsamplerInfo {
32 std::array<s16, MaxChannels> inputs{}; 32 std::array<s16, MaxChannels> inputs{};
33}; 33};
34 34
35} // namespace AudioCore::AudioRenderer 35} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.cpp b/src/audio_core/renderer/upsampler/upsampler_manager.cpp
index 4c76a5066..ef740f6c9 100644
--- a/src/audio_core/renderer/upsampler/upsampler_manager.cpp
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.cpp
@@ -3,7 +3,7 @@
3 3
4#include "audio_core/renderer/upsampler/upsampler_manager.h" 4#include "audio_core/renderer/upsampler/upsampler_manager.h"
5 5
6namespace AudioCore::AudioRenderer { 6namespace AudioCore::Renderer {
7 7
8UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_, 8UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_,
9 std::span<s32> workbuffer_) 9 std::span<s32> workbuffer_)
@@ -41,4 +41,4 @@ void UpsamplerManager::Free(UpsamplerInfo* info) {
41 info->enabled = false; 41 info->enabled = false;
42} 42}
43 43
44} // namespace AudioCore::AudioRenderer 44} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h
index 83c697c0c..263e5718b 100644
--- a/src/audio_core/renderer/upsampler/upsampler_manager.h
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.h
@@ -9,7 +9,7 @@
9#include "audio_core/renderer/upsampler/upsampler_info.h" 9#include "audio_core/renderer/upsampler/upsampler_info.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13/** 13/**
14 * Manages and has utility functions for upsampler infos. 14 * Manages and has utility functions for upsampler infos.
15 */ 15 */
@@ -42,4 +42,4 @@ private:
42 std::mutex lock{}; 42 std::mutex lock{};
43}; 43};
44 44
45} // namespace AudioCore::AudioRenderer 45} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_state.h b/src/audio_core/renderer/upsampler/upsampler_state.h
index 28cebe200..dc7b31d42 100644
--- a/src/audio_core/renderer/upsampler/upsampler_state.h
+++ b/src/audio_core/renderer/upsampler/upsampler_state.h
@@ -8,7 +8,7 @@
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "common/fixed_point.h" 9#include "common/fixed_point.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::Renderer {
12/** 12/**
13 * Upsampling state used by the AudioRenderer across calls. 13 * Upsampling state used by the AudioRenderer across calls.
14 */ 14 */
@@ -37,4 +37,4 @@ struct UpsamplerState {
37 u8 sample_index; 37 u8 sample_index;
38}; 38};
39 39
40} // namespace AudioCore::AudioRenderer 40} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h
index 26ab4ccce..4f19c2fcc 100644
--- a/src/audio_core/renderer/voice/voice_channel_resource.h
+++ b/src/audio_core/renderer/voice/voice_channel_resource.h
@@ -8,7 +8,7 @@
8#include "audio_core/common/common.h" 8#include "audio_core/common/common.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10 10
11namespace AudioCore::AudioRenderer { 11namespace AudioCore::Renderer {
12/** 12/**
13 * Represents one channel for mixing a voice. 13 * Represents one channel for mixing a voice.
14 */ 14 */
@@ -35,4 +35,4 @@ public:
35 bool in_use{}; 35 bool in_use{};
36}; 36};
37 37
38} // namespace AudioCore::AudioRenderer 38} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp
index 16a3e839d..c3644e38b 100644
--- a/src/audio_core/renderer/voice/voice_context.cpp
+++ b/src/audio_core/renderer/voice/voice_context.cpp
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/voice/voice_context.h" 6#include "audio_core/renderer/voice/voice_context.h"
7#include "common/polyfill_ranges.h" 7#include "common/polyfill_ranges.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11VoiceState& VoiceContext::GetDspSharedState(const u32 index) { 11VoiceState& VoiceContext::GetDspSharedState(const u32 index) {
12 if (index >= dsp_states.size()) { 12 if (index >= dsp_states.size()) {
@@ -84,4 +84,4 @@ void VoiceContext::UpdateStateByDspShared() {
84 std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState)); 84 std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState));
85} 85}
86 86
87} // namespace AudioCore::AudioRenderer 87} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h
index 43b677154..138ab2773 100644
--- a/src/audio_core/renderer/voice/voice_context.h
+++ b/src/audio_core/renderer/voice/voice_context.h
@@ -10,7 +10,7 @@
10#include "audio_core/renderer/voice/voice_state.h" 10#include "audio_core/renderer/voice/voice_state.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace AudioCore::AudioRenderer { 13namespace AudioCore::Renderer {
14/** 14/**
15 * Contains all voices, with utility functions for managing them. 15 * Contains all voices, with utility functions for managing them.
16 */ 16 */
@@ -123,4 +123,4 @@ private:
123 u32 active_count{}; 123 u32 active_count{};
124}; 124};
125 125
126} // namespace AudioCore::AudioRenderer 126} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp
index c0bfb23fc..6239cfab7 100644
--- a/src/audio_core/renderer/voice/voice_info.cpp
+++ b/src/audio_core/renderer/voice/voice_info.cpp
@@ -6,7 +6,7 @@
6#include "audio_core/renderer/voice/voice_info.h" 6#include "audio_core/renderer/voice/voice_info.h"
7#include "audio_core/renderer/voice/voice_state.h" 7#include "audio_core/renderer/voice/voice_state.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::Renderer {
10 10
11VoiceInfo::VoiceInfo() { 11VoiceInfo::VoiceInfo() {
12 Initialize(); 12 Initialize();
@@ -405,4 +405,4 @@ void VoiceInfo::ResetResources(VoiceContext& voice_context) const {
405 } 405 }
406} 406}
407 407
408} // namespace AudioCore::AudioRenderer 408} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h
index 3c5d3e04f..14a687dcb 100644
--- a/src/audio_core/renderer/voice/voice_info.h
+++ b/src/audio_core/renderer/voice/voice_info.h
@@ -12,7 +12,7 @@
12#include "audio_core/renderer/memory/address_info.h" 12#include "audio_core/renderer/memory/address_info.h"
13#include "common/common_types.h" 13#include "common/common_types.h"
14 14
15namespace AudioCore::AudioRenderer { 15namespace AudioCore::Renderer {
16class PoolMapper; 16class PoolMapper;
17class VoiceContext; 17class VoiceContext;
18struct VoiceState; 18struct VoiceState;
@@ -377,4 +377,4 @@ public:
377 u8 flush_buffer_count{}; 377 u8 flush_buffer_count{};
378}; 378};
379 379
380} // namespace AudioCore::AudioRenderer 380} // namespace AudioCore::Renderer
diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h
index ce947233f..c7aee167b 100644
--- a/src/audio_core/renderer/voice/voice_state.h
+++ b/src/audio_core/renderer/voice/voice_state.h
@@ -9,7 +9,7 @@
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/fixed_point.h" 10#include "common/fixed_point.h"
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::Renderer {
13/** 13/**
14 * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer, 14 * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer,
15 * host-side is updated on the next iteration. 15 * host-side is updated on the next iteration.
@@ -67,4 +67,4 @@ struct VoiceState {
67 s32 loop_count; 67 s32 loop_count;
68}; 68};
69 69
70} // namespace AudioCore::AudioRenderer 70} // namespace AudioCore::Renderer
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index 9a0801888..bbb598bc5 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -8,6 +8,7 @@
8#include "audio_core/sink/cubeb_sink.h" 8#include "audio_core/sink/cubeb_sink.h"
9#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/scope_exit.h"
11#include "core/core.h" 12#include "core/core.h"
12 13
13#ifdef _WIN32 14#ifdef _WIN32
@@ -332,25 +333,38 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) {
332 return device_list; 333 return device_list;
333} 334}
334 335
335u32 GetCubebLatency() { 336namespace {
336 cubeb* ctx; 337static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) {
338 return TargetSampleCount;
339}
340static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {}
341} // namespace
342
343bool IsCubebSuitable() {
344#if !defined(HAVE_CUBEB)
345 return false;
346#else
347 cubeb* ctx{nullptr};
337 348
338#ifdef _WIN32 349#ifdef _WIN32
339 auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); 350 auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
340#endif 351#endif
341 352
353 // Init cubeb
342 if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) { 354 if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
343 LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); 355 LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable.");
344 // Return a large latency so we choose SDL instead. 356 return false;
345 return 10000u;
346 } 357 }
347 358
359 SCOPE_EXIT({ cubeb_destroy(ctx); });
360
348#ifdef _WIN32 361#ifdef _WIN32
349 if (SUCCEEDED(com_init_result)) { 362 if (SUCCEEDED(com_init_result)) {
350 CoUninitialize(); 363 CoUninitialize();
351 } 364 }
352#endif 365#endif
353 366
367 // Get min latency
354 cubeb_stream_params params{}; 368 cubeb_stream_params params{};
355 params.rate = TargetSampleRate; 369 params.rate = TargetSampleRate;
356 params.channels = 2; 370 params.channels = 2;
@@ -361,12 +375,27 @@ u32 GetCubebLatency() {
361 u32 latency{0}; 375 u32 latency{0};
362 const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency); 376 const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency);
363 if (latency_error != CUBEB_OK) { 377 if (latency_error != CUBEB_OK) {
364 LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); 378 LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable.");
365 latency = TargetSampleCount * 2; 379 return false;
366 } 380 }
367 latency = std::max(latency, TargetSampleCount * 2); 381 latency = std::max(latency, TargetSampleCount * 2);
368 cubeb_destroy(ctx); 382
369 return latency; 383 // Test opening a device with standard parameters
384 cubeb_devid output_device{0};
385 cubeb_devid input_device{0};
386 std::string name{"Yuzu test"};
387 cubeb_stream* stream{nullptr};
388
389 if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, &params,
390 latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) {
391 LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable.");
392 return false;
393 }
394
395 cubeb_stream_stop(stream);
396 cubeb_stream_destroy(stream);
397 return true;
398#endif
370} 399}
371 400
372} // namespace AudioCore::Sink 401} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h
index 3302cb98d..f49a6fdaa 100644
--- a/src/audio_core/sink/cubeb_sink.h
+++ b/src/audio_core/sink/cubeb_sink.h
@@ -97,10 +97,11 @@ private:
97std::vector<std::string> ListCubebSinkDevices(bool capture); 97std::vector<std::string> ListCubebSinkDevices(bool capture);
98 98
99/** 99/**
100 * Get the reported latency for this sink. 100 * Check if this backend is suitable for use.
101 * Checks if enabled, its latency, whether it opens successfully, etc.
101 * 102 *
102 * @return Minimum latency for this sink. 103 * @return True is this backend is suitable, false otherwise.
103 */ 104 */
104u32 GetCubebLatency(); 105bool IsCubebSuitable();
105 106
106} // namespace AudioCore::Sink 107} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index c1529d1f9..7b89151de 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -9,6 +9,7 @@
9#include "audio_core/sink/sdl2_sink.h" 9#include "audio_core/sink/sdl2_sink.h"
10#include "audio_core/sink/sink_stream.h" 10#include "audio_core/sink/sink_stream.h"
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/scope_exit.h"
12#include "core/core.h" 13#include "core/core.h"
13 14
14namespace AudioCore::Sink { 15namespace AudioCore::Sink {
@@ -84,6 +85,7 @@ public:
84 } 85 }
85 86
86 Stop(); 87 Stop();
88 SDL_ClearQueuedAudio(device);
87 SDL_CloseAudioDevice(device); 89 SDL_CloseAudioDevice(device);
88 } 90 }
89 91
@@ -227,8 +229,42 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) {
227 return device_list; 229 return device_list;
228} 230}
229 231
230u32 GetSDLLatency() { 232bool IsSDLSuitable() {
231 return TargetSampleCount * 2; 233#if !defined(HAVE_SDL2)
234 return false;
235#else
236 // Check SDL can init
237 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
238 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
239 LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
240 SDL_GetError());
241 return false;
242 }
243 }
244
245 // We can set any latency frequency we want with SDL, so no need to check that.
246
247 // Check we can open a device with standard parameters
248 SDL_AudioSpec spec;
249 spec.freq = TargetSampleRate;
250 spec.channels = 2u;
251 spec.format = AUDIO_S16SYS;
252 spec.samples = TargetSampleCount * 2;
253 spec.callback = nullptr;
254 spec.userdata = nullptr;
255
256 SDL_AudioSpec obtained;
257 auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false);
258
259 if (device == 0) {
260 LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}",
261 SDL_GetError());
262 return false;
263 }
264
265 SDL_CloseAudioDevice(device);
266 return true;
267#endif
232} 268}
233 269
234} // namespace AudioCore::Sink 270} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h
index 27ed1ab94..9211d2e97 100644
--- a/src/audio_core/sink/sdl2_sink.h
+++ b/src/audio_core/sink/sdl2_sink.h
@@ -88,10 +88,11 @@ private:
88std::vector<std::string> ListSDLSinkDevices(bool capture); 88std::vector<std::string> ListSDLSinkDevices(bool capture);
89 89
90/** 90/**
91 * Get the reported latency for this sink. 91 * Check if this backend is suitable for use.
92 * Checks if enabled, its latency, whether it opens successfully, etc.
92 * 93 *
93 * @return Minimum latency for this sink. 94 * @return True is this backend is suitable, false otherwise.
94 */ 95 */
95u32 GetSDLLatency(); 96bool IsSDLSuitable();
96 97
97} // namespace AudioCore::Sink 98} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 027bfa517..7c9a4e3ac 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -22,7 +22,7 @@ namespace {
22struct SinkDetails { 22struct SinkDetails {
23 using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); 23 using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
24 using ListDevicesFn = std::vector<std::string> (*)(bool); 24 using ListDevicesFn = std::vector<std::string> (*)(bool);
25 using LatencyFn = u32 (*)(); 25 using SuitableFn = bool (*)();
26 26
27 /// Name for this sink. 27 /// Name for this sink.
28 Settings::AudioEngine id; 28 Settings::AudioEngine id;
@@ -30,8 +30,8 @@ struct SinkDetails {
30 FactoryFn factory; 30 FactoryFn factory;
31 /// A method to call to list available devices. 31 /// A method to call to list available devices.
32 ListDevicesFn list_devices; 32 ListDevicesFn list_devices;
33 /// Method to get the latency of this backend. 33 /// Check whether this backend is suitable to be used.
34 LatencyFn latency; 34 SuitableFn is_suitable;
35}; 35};
36 36
37// sink_details is ordered in terms of desirability, with the best choice at the top. 37// sink_details is ordered in terms of desirability, with the best choice at the top.
@@ -43,7 +43,7 @@ constexpr SinkDetails sink_details[] = {
43 return std::make_unique<CubebSink>(device_id); 43 return std::make_unique<CubebSink>(device_id);
44 }, 44 },
45 &ListCubebSinkDevices, 45 &ListCubebSinkDevices,
46 &GetCubebLatency, 46 &IsCubebSuitable,
47 }, 47 },
48#endif 48#endif
49#ifdef HAVE_SDL2 49#ifdef HAVE_SDL2
@@ -53,14 +53,17 @@ constexpr SinkDetails sink_details[] = {
53 return std::make_unique<SDLSink>(device_id); 53 return std::make_unique<SDLSink>(device_id);
54 }, 54 },
55 &ListSDLSinkDevices, 55 &ListSDLSinkDevices,
56 &GetSDLLatency, 56 &IsSDLSuitable,
57 }, 57 },
58#endif 58#endif
59 SinkDetails{Settings::AudioEngine::Null, 59 SinkDetails{
60 [](std::string_view device_id) -> std::unique_ptr<Sink> { 60 Settings::AudioEngine::Null,
61 return std::make_unique<NullSink>(device_id); 61 [](std::string_view device_id) -> std::unique_ptr<Sink> {
62 }, 62 return std::make_unique<NullSink>(device_id);
63 [](bool capture) { return std::vector<std::string>{"null"}; }, []() { return 0u; }}, 63 },
64 [](bool capture) { return std::vector<std::string>{"null"}; },
65 []() { return true; },
66 },
64}; 67};
65 68
66const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { 69const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
@@ -72,18 +75,22 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
72 auto iter = find_backend(sink_id); 75 auto iter = find_backend(sink_id);
73 76
74 if (sink_id == Settings::AudioEngine::Auto) { 77 if (sink_id == Settings::AudioEngine::Auto) {
75 // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which 78 // Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking
76 // causes audio issues, in that case go with SDL. 79 // that the backend is available and suitable to use.
77#if defined(HAVE_CUBEB) && defined(HAVE_SDL2) 80 for (auto& details : sink_details) {
78 iter = find_backend(Settings::AudioEngine::Cubeb); 81 if (details.is_suitable()) {
79 if (iter->latency() > TargetSampleCount * 3) { 82 iter = &details;
80 iter = find_backend(Settings::AudioEngine::Sdl2); 83 break;
84 }
81 } 85 }
82#else
83 iter = std::begin(sink_details);
84#endif
85 LOG_INFO(Service_Audio, "Auto-selecting the {} backend", 86 LOG_INFO(Service_Audio, "Auto-selecting the {} backend",
86 Settings::CanonicalizeEnum(iter->id)); 87 Settings::CanonicalizeEnum(iter->id));
88 } else {
89 if (iter != std::end(sink_details) && !iter->is_suitable()) {
90 LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null",
91 Settings::CanonicalizeEnum(iter->id));
92 iter = find_backend(Settings::AudioEngine::Null);
93 }
87 } 94 }
88 95
89 if (iter == std::end(sink_details)) { 96 if (iter == std::end(sink_details)) {
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index bf97d9ba2..416203c59 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -26,12 +26,11 @@ add_library(common STATIC
26 assert.h 26 assert.h
27 atomic_helpers.h 27 atomic_helpers.h
28 atomic_ops.h 28 atomic_ops.h
29 detached_tasks.cpp
30 detached_tasks.h
31 bit_cast.h 29 bit_cast.h
32 bit_field.h 30 bit_field.h
33 bit_set.h 31 bit_set.h
34 bit_util.h 32 bit_util.h
33 bounded_threadsafe_queue.h
35 cityhash.cpp 34 cityhash.cpp
36 cityhash.h 35 cityhash.h
37 common_funcs.h 36 common_funcs.h
@@ -41,6 +40,8 @@ add_library(common STATIC
41 container_hash.h 40 container_hash.h
42 demangle.cpp 41 demangle.cpp
43 demangle.h 42 demangle.h
43 detached_tasks.cpp
44 detached_tasks.h
44 div_ceil.h 45 div_ceil.h
45 dynamic_library.cpp 46 dynamic_library.cpp
46 dynamic_library.h 47 dynamic_library.h
@@ -151,6 +152,10 @@ add_library(common STATIC
151 zstd_compression.h 152 zstd_compression.h
152) 153)
153 154
155if (YUZU_ENABLE_PORTABLE)
156 add_compile_definitions(YUZU_ENABLE_PORTABLE)
157endif()
158
154if (WIN32) 159if (WIN32)
155 target_sources(common PRIVATE 160 target_sources(common PRIVATE
156 windows/timer_resolution.cpp 161 windows/timer_resolution.cpp
@@ -191,8 +196,6 @@ if (MSVC)
191 _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 196 _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
192 ) 197 )
193 target_compile_options(common PRIVATE 198 target_compile_options(common PRIVATE
194 /W4
195
196 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data 199 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
197 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 200 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
198 /we4800 # Implicit conversion from 'type' to bool. Possible information loss 201 /we4800 # Implicit conversion from 'type' to bool. Possible information loss
diff --git a/src/common/alignment.h b/src/common/alignment.h
index fa715d497..fc5c26898 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <bit>
6#include <cstddef> 7#include <cstddef>
7#include <new> 8#include <new>
8#include <type_traits> 9#include <type_traits>
@@ -10,8 +11,10 @@
10namespace Common { 11namespace Common {
11 12
12template <typename T> 13template <typename T>
13 requires std::is_unsigned_v<T> 14 requires std::is_integral_v<T>
14[[nodiscard]] constexpr T AlignUp(T value, size_t size) { 15[[nodiscard]] constexpr T AlignUp(T value_, size_t size) {
16 using U = typename std::make_unsigned_t<T>;
17 auto value{static_cast<U>(value_)};
15 auto mod{static_cast<T>(value % size)}; 18 auto mod{static_cast<T>(value % size)};
16 value -= mod; 19 value -= mod;
17 return static_cast<T>(mod == T{0} ? value : value + size); 20 return static_cast<T>(mod == T{0} ? value : value + size);
@@ -24,8 +27,10 @@ template <typename T>
24} 27}
25 28
26template <typename T> 29template <typename T>
27 requires std::is_unsigned_v<T> 30 requires std::is_integral_v<T>
28[[nodiscard]] constexpr T AlignDown(T value, size_t size) { 31[[nodiscard]] constexpr T AlignDown(T value_, size_t size) {
32 using U = typename std::make_unsigned_t<T>;
33 const auto value{static_cast<U>(value_)};
29 return static_cast<T>(value - value % size); 34 return static_cast<T>(value - value % size);
30} 35}
31 36
@@ -55,6 +60,30 @@ template <typename T, typename U>
55 return (x + (y - 1)) / y; 60 return (x + (y - 1)) / y;
56} 61}
57 62
63template <typename T>
64 requires std::is_integral_v<T>
65[[nodiscard]] constexpr T LeastSignificantOneBit(T x) {
66 return x & ~(x - 1);
67}
68
69template <typename T>
70 requires std::is_integral_v<T>
71[[nodiscard]] constexpr T ResetLeastSignificantOneBit(T x) {
72 return x & (x - 1);
73}
74
75template <typename T>
76 requires std::is_integral_v<T>
77[[nodiscard]] constexpr bool IsPowerOfTwo(T x) {
78 return x > 0 && ResetLeastSignificantOneBit(x) == 0;
79}
80
81template <typename T>
82 requires std::is_integral_v<T>
83[[nodiscard]] constexpr T FloorPowerOfTwo(T x) {
84 return T{1} << (sizeof(T) * 8 - std::countl_zero(x) - 1);
85}
86
58template <typename T, size_t Align = 16> 87template <typename T, size_t Align = 16>
59class AlignmentAllocator { 88class AlignmentAllocator {
60public: 89public:
diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h
index bd87aa09b..b36fc1de9 100644
--- a/src/common/bounded_threadsafe_queue.h
+++ b/src/common/bounded_threadsafe_queue.h
@@ -45,13 +45,13 @@ public:
45 } 45 }
46 46
47 T PopWait() { 47 T PopWait() {
48 T t; 48 T t{};
49 Pop<PopMode::Wait>(t); 49 Pop<PopMode::Wait>(t);
50 return t; 50 return t;
51 } 51 }
52 52
53 T PopWait(std::stop_token stop_token) { 53 T PopWait(std::stop_token stop_token) {
54 T t; 54 T t{};
55 Pop<PopMode::WaitWithStopToken>(t, stop_token); 55 Pop<PopMode::WaitWithStopToken>(t, stop_token);
56 return t; 56 return t;
57 } 57 }
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index 36e67c145..174aed49b 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -528,38 +528,41 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
528// Generic Filesystem Operations 528// Generic Filesystem Operations
529 529
530bool Exists(const fs::path& path) { 530bool Exists(const fs::path& path) {
531 std::error_code ec;
531#ifdef ANDROID 532#ifdef ANDROID
532 if (Android::IsContentUri(path)) { 533 if (Android::IsContentUri(path)) {
533 return Android::Exists(path); 534 return Android::Exists(path);
534 } else { 535 } else {
535 return fs::exists(path); 536 return fs::exists(path, ec);
536 } 537 }
537#else 538#else
538 return fs::exists(path); 539 return fs::exists(path, ec);
539#endif 540#endif
540} 541}
541 542
542bool IsFile(const fs::path& path) { 543bool IsFile(const fs::path& path) {
544 std::error_code ec;
543#ifdef ANDROID 545#ifdef ANDROID
544 if (Android::IsContentUri(path)) { 546 if (Android::IsContentUri(path)) {
545 return !Android::IsDirectory(path); 547 return !Android::IsDirectory(path);
546 } else { 548 } else {
547 return fs::is_regular_file(path); 549 return fs::is_regular_file(path, ec);
548 } 550 }
549#else 551#else
550 return fs::is_regular_file(path); 552 return fs::is_regular_file(path, ec);
551#endif 553#endif
552} 554}
553 555
554bool IsDir(const fs::path& path) { 556bool IsDir(const fs::path& path) {
557 std::error_code ec;
555#ifdef ANDROID 558#ifdef ANDROID
556 if (Android::IsContentUri(path)) { 559 if (Android::IsContentUri(path)) {
557 return Android::IsDirectory(path); 560 return Android::IsDirectory(path);
558 } else { 561 } else {
559 return fs::is_directory(path); 562 return fs::is_directory(path, ec);
560 } 563 }
561#else 564#else
562 return fs::is_directory(path); 565 return fs::is_directory(path, ec);
563#endif 566#endif
564} 567}
565 568
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index d71cfacc6..dce219fcf 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -88,8 +88,9 @@ public:
88 fs::path yuzu_path_config; 88 fs::path yuzu_path_config;
89 89
90#ifdef _WIN32 90#ifdef _WIN32
91#ifdef YUZU_ENABLE_PORTABLE
91 yuzu_path = GetExeDirectory() / PORTABLE_DIR; 92 yuzu_path = GetExeDirectory() / PORTABLE_DIR;
92 93#endif
93 if (!IsDir(yuzu_path)) { 94 if (!IsDir(yuzu_path)) {
94 yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; 95 yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
95 } 96 }
@@ -101,8 +102,9 @@ public:
101 yuzu_path_cache = yuzu_path / CACHE_DIR; 102 yuzu_path_cache = yuzu_path / CACHE_DIR;
102 yuzu_path_config = yuzu_path / CONFIG_DIR; 103 yuzu_path_config = yuzu_path / CONFIG_DIR;
103#else 104#else
105#ifdef YUZU_ENABLE_PORTABLE
104 yuzu_path = GetCurrentDir() / PORTABLE_DIR; 106 yuzu_path = GetCurrentDir() / PORTABLE_DIR;
105 107#endif
106 if (Exists(yuzu_path) && IsDir(yuzu_path)) { 108 if (Exists(yuzu_path) && IsDir(yuzu_path)) {
107 yuzu_path_cache = yuzu_path / CACHE_DIR; 109 yuzu_path_cache = yuzu_path / CACHE_DIR;
108 yuzu_path_config = yuzu_path / CONFIG_DIR; 110 yuzu_path_config = yuzu_path / CONFIG_DIR;
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index c95909561..4e3a614a4 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
112 SUB(Service, NCM) \ 112 SUB(Service, NCM) \
113 SUB(Service, NFC) \ 113 SUB(Service, NFC) \
114 SUB(Service, NFP) \ 114 SUB(Service, NFP) \
115 SUB(Service, NGCT) \ 115 SUB(Service, NGC) \
116 SUB(Service, NIFM) \ 116 SUB(Service, NIFM) \
117 SUB(Service, NIM) \ 117 SUB(Service, NIM) \
118 SUB(Service, NOTIF) \ 118 SUB(Service, NOTIF) \
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
index 8356e3183..08af50ee0 100644
--- a/src/common/logging/types.h
+++ b/src/common/logging/types.h
@@ -80,7 +80,7 @@ enum class Class : u8 {
80 Service_NCM, ///< The NCM service 80 Service_NCM, ///< The NCM service
81 Service_NFC, ///< The NFC (Near-field communication) service 81 Service_NFC, ///< The NFC (Near-field communication) service
82 Service_NFP, ///< The NFP service 82 Service_NFP, ///< The NFP service
83 Service_NGCT, ///< The NGCT (No Good Content for Terra) service 83 Service_NGC, ///< The NGC (No Good Content) service
84 Service_NIFM, ///< The NIFM (Network interface) service 84 Service_NIFM, ///< The NIFM (Network interface) service
85 Service_NIM, ///< The NIM service 85 Service_NIM, ///< The NIM service
86 Service_NOTIF, ///< The NOTIF (Notification) service 86 Service_NOTIF, ///< The NOTIF (Notification) service
diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp
index ffb32fecf..d85ab1742 100644
--- a/src/common/lz4_compression.cpp
+++ b/src/common/lz4_compression.cpp
@@ -71,4 +71,10 @@ std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t un
71 return uncompressed; 71 return uncompressed;
72} 72}
73 73
74int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size) {
75 // This is just a thin wrapper around LZ4.
76 return LZ4_decompress_safe(reinterpret_cast<const char*>(src), reinterpret_cast<char*>(dst),
77 static_cast<int>(src_size), static_cast<int>(dst_size));
78}
79
74} // namespace Common::Compression 80} // namespace Common::Compression
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h
index 7fd53a960..3ae17c2bb 100644
--- a/src/common/lz4_compression.h
+++ b/src/common/lz4_compression.h
@@ -56,4 +56,6 @@ namespace Common::Compression {
56[[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, 56[[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed,
57 std::size_t uncompressed_size); 57 std::size_t uncompressed_size);
58 58
59[[nodiscard]] int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size);
60
59} // namespace Common::Compression 61} // namespace Common::Compression
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h
index b5ef055db..41cbb9ed5 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -19,8 +19,8 @@
19namespace Common { 19namespace Common {
20 20
21template <typename Condvar, typename Lock, typename Pred> 21template <typename Condvar, typename Lock, typename Pred>
22void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { 22void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
23 cv.wait(lock, token, std::move(pred)); 23 cv.wait(lk, token, std::move(pred));
24} 24}
25 25
26template <typename Rep, typename Period> 26template <typename Rep, typename Period>
@@ -332,13 +332,17 @@ private:
332namespace Common { 332namespace Common {
333 333
334template <typename Condvar, typename Lock, typename Pred> 334template <typename Condvar, typename Lock, typename Pred>
335void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { 335void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
336 if (token.stop_requested()) { 336 if (token.stop_requested()) {
337 return; 337 return;
338 } 338 }
339 339
340 std::stop_callback callback(token, [&] { cv.notify_all(); }); 340 std::stop_callback callback(token, [&] {
341 cv.wait(lock, [&] { return pred() || token.stop_requested(); }); 341 { std::scoped_lock lk2{*lk.mutex()}; }
342 cv.notify_all();
343 });
344
345 cv.wait(lk, [&] { return pred() || token.stop_requested(); });
342} 346}
343 347
344template <typename Rep, typename Period> 348template <typename Rep, typename Period>
@@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
353 357
354 std::stop_callback cb(token, [&] { 358 std::stop_callback cb(token, [&] {
355 // Wake up the waiting thread. 359 // Wake up the waiting thread.
356 std::unique_lock lk{m}; 360 {
357 stop_requested = true; 361 std::scoped_lock lk{m};
362 stop_requested = true;
363 }
358 cv.notify_one(); 364 cv.notify_one();
359 }); 365 });
360 366
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 15fd2e222..4ecaf550b 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -2,6 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <version> 4#include <version>
5#include "common/settings_enums.h"
5#if __cpp_lib_chrono >= 201907L 6#if __cpp_lib_chrono >= 201907L
6#include <chrono> 7#include <chrono>
7#include <exception> 8#include <exception>
@@ -145,6 +146,10 @@ bool IsFastmemEnabled() {
145 return true; 146 return true;
146} 147}
147 148
149bool IsDockedMode() {
150 return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
151}
152
148float Volume() { 153float Volume() {
149 if (values.audio_muted) { 154 if (values.audio_muted) {
150 return 0.0f; 155 return 0.0f;
@@ -154,6 +159,8 @@ float Volume() {
154 159
155const char* TranslateCategory(Category category) { 160const char* TranslateCategory(Category category) {
156 switch (category) { 161 switch (category) {
162 case Category::Android:
163 return "Android";
157 case Category::Audio: 164 case Category::Audio:
158 return "Audio"; 165 return "Audio";
159 case Category::Core: 166 case Category::Core:
@@ -207,9 +214,7 @@ const char* TranslateCategory(Category category) {
207 return "Miscellaneous"; 214 return "Miscellaneous";
208} 215}
209 216
210void UpdateRescalingInfo() { 217void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info) {
211 const auto setup = values.resolution_setup.GetValue();
212 auto& info = values.resolution_info;
213 info.downscale = false; 218 info.downscale = false;
214 switch (setup) { 219 switch (setup) {
215 case ResolutionSetup::Res1_2X: 220 case ResolutionSetup::Res1_2X:
@@ -269,6 +274,12 @@ void UpdateRescalingInfo() {
269 info.active = info.up_scale != 1 || info.down_shift != 0; 274 info.active = info.up_scale != 1 || info.down_shift != 0;
270} 275}
271 276
277void UpdateRescalingInfo() {
278 const auto setup = values.resolution_setup.GetValue();
279 auto& info = values.resolution_info;
280 TranslateResolutionInfo(setup, info);
281}
282
272void RestoreGlobalState(bool is_powered_on) { 283void RestoreGlobalState(bool is_powered_on) {
273 // If a game is running, DO NOT restore the global settings state 284 // If a game is running, DO NOT restore the global settings state
274 if (is_powered_on) { 285 if (is_powered_on) {
diff --git a/src/common/settings.h b/src/common/settings.h
index b0bc6519a..82ec9077e 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -348,6 +348,8 @@ struct Values {
348 Category::RendererDebug}; 348 Category::RendererDebug};
349 Setting<bool> disable_shader_loop_safety_checks{ 349 Setting<bool> disable_shader_loop_safety_checks{
350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; 350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
351 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
352 Category::RendererDebug};
351 353
352 // System 354 // System
353 SwitchableSetting<Language, true> language_index{linkage, 355 SwitchableSetting<Language, true> language_index{linkage,
@@ -379,7 +381,13 @@ struct Values {
379 381
380 Setting<s32> current_user{linkage, 0, "current_user", Category::System}; 382 Setting<s32> current_user{linkage, 0, "current_user", Category::System};
381 383
382 SwitchableSetting<bool> use_docked_mode{linkage, true, "use_docked_mode", Category::System}; 384 SwitchableSetting<ConsoleMode> use_docked_mode{linkage,
385 ConsoleMode::Docked,
386 "use_docked_mode",
387 Category::System,
388 Specialization::Radio,
389 true,
390 true};
383 391
384 // Controls 392 // Controls
385 InputSetting<std::array<PlayerInput, 10>> players; 393 InputSetting<std::array<PlayerInput, 10>> players;
@@ -519,12 +527,15 @@ bool IsGPULevelHigh();
519 527
520bool IsFastmemEnabled(); 528bool IsFastmemEnabled();
521 529
530bool IsDockedMode();
531
522float Volume(); 532float Volume();
523 533
524std::string GetTimeZoneString(TimeZone time_zone); 534std::string GetTimeZoneString(TimeZone time_zone);
525 535
526void LogSettings(); 536void LogSettings();
527 537
538void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info);
528void UpdateRescalingInfo(); 539void UpdateRescalingInfo();
529 540
530// Restore the global state of all applicable settings in the Values struct 541// Restore the global state of all applicable settings in the Values struct
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp
index dedf5ef90..5960b78aa 100644
--- a/src/common/settings_common.cpp
+++ b/src/common/settings_common.cpp
@@ -1,7 +1,9 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <functional>
4#include <string> 5#include <string>
6#include <vector>
5#include "common/settings_common.h" 7#include "common/settings_common.h"
6 8
7namespace Settings { 9namespace Settings {
@@ -12,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
12 : label{name}, category{category_}, id{linkage.count}, save{save_}, 14 : label{name}, category{category_}, id{linkage.count}, save{save_},
13 runtime_modifiable{runtime_modifiable_}, specialization{specialization_}, 15 runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
14 other_setting{other_setting_} { 16 other_setting{other_setting_} {
17 linkage.by_key.insert({name, this});
15 linkage.by_category[category].push_back(this); 18 linkage.by_category[category].push_back(this);
16 linkage.count++; 19 linkage.count++;
17} 20}
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 2efb329b0..1800ab10d 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -12,6 +12,7 @@
12namespace Settings { 12namespace Settings {
13 13
14enum class Category : u32 { 14enum class Category : u32 {
15 Android,
15 Audio, 16 Audio,
16 Core, 17 Core,
17 Cpu, 18 Cpu,
@@ -56,6 +57,7 @@ enum Specialization : u8 {
56 Scalar = 5, // Values are continuous 57 Scalar = 5, // Values are continuous
57 Countable = 6, // Can be stepped through 58 Countable = 6, // Can be stepped through
58 Paired = 7, // Another setting is associated with this setting 59 Paired = 7, // Another setting is associated with this setting
60 Radio = 8, // Setting should be presented in a radio group
59 61
60 Percentage = (1 << SpecializationAttributeOffset), // Should be represented as a percentage 62 Percentage = (1 << SpecializationAttributeOffset), // Should be represented as a percentage
61}; 63};
@@ -67,6 +69,7 @@ public:
67 explicit Linkage(u32 initial_count = 0); 69 explicit Linkage(u32 initial_count = 0);
68 ~Linkage(); 70 ~Linkage();
69 std::map<Category, std::vector<BasicSetting*>> by_category{}; 71 std::map<Category, std::vector<BasicSetting*>> by_category{};
72 std::map<std::string, Settings::BasicSetting*> by_key{};
70 std::vector<std::function<void()>> restore_functions{}; 73 std::vector<std::function<void()>> restore_functions{};
71 u32 count; 74 u32 count;
72}; 75};
@@ -222,6 +225,16 @@ public:
222 */ 225 */
223 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; 226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
224 227
228 /**
229 * @returns True if the underlying type is a floating point storage
230 */
231 [[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0;
232
233 /**
234 * @returns True if the underlying type is an integer storage
235 */
236 [[nodiscard]] virtual constexpr bool IsIntegral() const = 0;
237
225 /* 238 /*
226 * Switchable settings 239 * Switchable settings
227 */ 240 */
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index a1a29ebf6..815cafe15 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -12,8 +12,8 @@ namespace Settings {
12 12
13template <typename T> 13template <typename T>
14struct EnumMetadata { 14struct EnumMetadata {
15 static constexpr std::vector<std::pair<std::string, T>> Canonicalizations(); 15 static std::vector<std::pair<std::string, T>> Canonicalizations();
16 static constexpr u32 Index(); 16 static u32 Index();
17}; 17};
18 18
19#define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__)) 19#define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__))
@@ -66,11 +66,11 @@ struct EnumMetadata {
66#define ENUM(NAME, ...) \ 66#define ENUM(NAME, ...) \
67 enum class NAME : u32 { __VA_ARGS__ }; \ 67 enum class NAME : u32 { __VA_ARGS__ }; \
68 template <> \ 68 template <> \
69 constexpr std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \ 69 inline std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \
70 return {PAIR(NAME, __VA_ARGS__)}; \ 70 return {PAIR(NAME, __VA_ARGS__)}; \
71 } \ 71 } \
72 template <> \ 72 template <> \
73 constexpr u32 EnumMetadata<NAME>::Index() { \ 73 inline u32 EnumMetadata<NAME>::Index() { \
74 return __COUNTER__; \ 74 return __COUNTER__; \
75 } 75 }
76 76
@@ -85,7 +85,7 @@ enum class AudioEngine : u32 {
85}; 85};
86 86
87template <> 87template <>
88constexpr std::vector<std::pair<std::string, AudioEngine>> 88inline std::vector<std::pair<std::string, AudioEngine>>
89EnumMetadata<AudioEngine>::Canonicalizations() { 89EnumMetadata<AudioEngine>::Canonicalizations() {
90 return { 90 return {
91 {"auto", AudioEngine::Auto}, 91 {"auto", AudioEngine::Auto},
@@ -96,7 +96,7 @@ EnumMetadata<AudioEngine>::Canonicalizations() {
96} 96}
97 97
98template <> 98template <>
99constexpr u32 EnumMetadata<AudioEngine>::Index() { 99inline u32 EnumMetadata<AudioEngine>::Index() {
100 // This is just a sufficiently large number that is more than the number of other enums declared 100 // This is just a sufficiently large number that is more than the number of other enums declared
101 // here 101 // here
102 return 100; 102 return 100;
@@ -146,8 +146,10 @@ ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);
146 146
147ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); 147ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
148 148
149ENUM(ConsoleMode, Handheld, Docked);
150
149template <typename Type> 151template <typename Type>
150constexpr std::string CanonicalizeEnum(Type id) { 152inline std::string CanonicalizeEnum(Type id) {
151 const auto group = EnumMetadata<Type>::Canonicalizations(); 153 const auto group = EnumMetadata<Type>::Canonicalizations();
152 for (auto& [name, value] : group) { 154 for (auto& [name, value] : group) {
153 if (value == id) { 155 if (value == id) {
@@ -158,7 +160,7 @@ constexpr std::string CanonicalizeEnum(Type id) {
158} 160}
159 161
160template <typename Type> 162template <typename Type>
161constexpr Type ToEnum(const std::string& canonicalization) { 163inline Type ToEnum(const std::string& canonicalization) {
162 const auto group = EnumMetadata<Type>::Canonicalizations(); 164 const auto group = EnumMetadata<Type>::Canonicalizations();
163 for (auto& [name, value] : group) { 165 for (auto& [name, value] : group) {
164 if (name == canonicalization) { 166 if (name == canonicalization) {
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index a8beb06e9..7be6f26f7 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -10,6 +10,7 @@
10#include <string> 10#include <string>
11#include <typeindex> 11#include <typeindex>
12#include <typeinfo> 12#include <typeinfo>
13#include <fmt/core.h>
13#include "common/common_types.h" 14#include "common/common_types.h"
14#include "common/settings_common.h" 15#include "common/settings_common.h"
15#include "common/settings_enums.h" 16#include "common/settings_enums.h"
@@ -115,8 +116,12 @@ protected:
115 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 116 } else if constexpr (std::is_same_v<Type, AudioEngine>) {
116 // Compatibility with old AudioEngine setting being a string 117 // Compatibility with old AudioEngine setting being a string
117 return CanonicalizeEnum(value_); 118 return CanonicalizeEnum(value_);
119 } else if constexpr (std::is_floating_point_v<Type>) {
120 return fmt::format("{:f}", value_);
121 } else if constexpr (std::is_enum_v<Type>) {
122 return std::to_string(static_cast<u32>(value_));
118 } else { 123 } else {
119 return std::to_string(static_cast<u64>(value_)); 124 return std::to_string(value_);
120 } 125 }
121 } 126 }
122 127
@@ -180,17 +185,19 @@ public:
180 this->SetValue(static_cast<u32>(std::stoul(input))); 185 this->SetValue(static_cast<u32>(std::stoul(input)));
181 } else if constexpr (std::is_same_v<Type, bool>) { 186 } else if constexpr (std::is_same_v<Type, bool>) {
182 this->SetValue(input == "true"); 187 this->SetValue(input == "true");
183 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 188 } else if constexpr (std::is_same_v<Type, float>) {
184 this->SetValue(ToEnum<Type>(input)); 189 this->SetValue(std::stof(input));
185 } else { 190 } else {
186 this->SetValue(static_cast<Type>(std::stoll(input))); 191 this->SetValue(static_cast<Type>(std::stoll(input)));
187 } 192 }
188 } catch (std::invalid_argument&) { 193 } catch (std::invalid_argument&) {
189 this->SetValue(this->GetDefault()); 194 this->SetValue(this->GetDefault());
195 } catch (std::out_of_range&) {
196 this->SetValue(this->GetDefault());
190 } 197 }
191 } 198 }
192 199
193 [[nodiscard]] std::string constexpr Canonicalize() const override final { 200 [[nodiscard]] std::string Canonicalize() const override final {
194 if constexpr (std::is_enum_v<Type>) { 201 if constexpr (std::is_enum_v<Type>) {
195 return CanonicalizeEnum(this->GetValue()); 202 return CanonicalizeEnum(this->GetValue());
196 } else { 203 } else {
@@ -215,11 +222,27 @@ public:
215 } 222 }
216 } 223 }
217 224
225 [[nodiscard]] constexpr bool IsFloatingPoint() const final {
226 return std::is_floating_point_v<Type>;
227 }
228
229 [[nodiscard]] constexpr bool IsIntegral() const final {
230 return std::is_integral_v<Type>;
231 }
232
218 [[nodiscard]] std::string MinVal() const override final { 233 [[nodiscard]] std::string MinVal() const override final {
219 return this->ToString(minimum); 234 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
235 return this->ToString(std::numeric_limits<Type>::min());
236 } else {
237 return this->ToString(minimum);
238 }
220 } 239 }
221 [[nodiscard]] std::string MaxVal() const override final { 240 [[nodiscard]] std::string MaxVal() const override final {
222 return this->ToString(maximum); 241 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
242 return this->ToString(std::numeric_limits<Type>::max());
243 } else {
244 return this->ToString(maximum);
245 }
223 } 246 }
224 247
225 [[nodiscard]] constexpr bool Ranged() const override { 248 [[nodiscard]] constexpr bool Ranged() const override {
@@ -256,11 +279,11 @@ public:
256 * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded 279 * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
257 * @param other_setting_ A second Setting to associate to this one in metadata 280 * @param other_setting_ A second Setting to associate to this one in metadata
258 */ 281 */
282 template <typename T = BasicSetting>
259 explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name, 283 explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name,
260 Category category_, u32 specialization_ = Specialization::Default, 284 Category category_, u32 specialization_ = Specialization::Default,
261 bool save_ = true, bool runtime_modifiable_ = false, 285 bool save_ = true, bool runtime_modifiable_ = false,
262 BasicSetting* other_setting_ = nullptr) 286 typename std::enable_if<!ranged, T*>::type other_setting_ = nullptr)
263 requires(!ranged)
264 : Setting<Type, false>{ 287 : Setting<Type, false>{
265 linkage, default_val, name, category_, specialization_, 288 linkage, default_val, name, category_, specialization_,
266 save_, runtime_modifiable_, other_setting_} { 289 save_, runtime_modifiable_, other_setting_} {
@@ -282,12 +305,12 @@ public:
282 * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded 305 * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
283 * @param other_setting_ A second Setting to associate to this one in metadata 306 * @param other_setting_ A second Setting to associate to this one in metadata
284 */ 307 */
308 template <typename T = BasicSetting>
285 explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val, 309 explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val,
286 const Type& max_val, const std::string& name, Category category_, 310 const Type& max_val, const std::string& name, Category category_,
287 u32 specialization_ = Specialization::Default, bool save_ = true, 311 u32 specialization_ = Specialization::Default, bool save_ = true,
288 bool runtime_modifiable_ = false, 312 bool runtime_modifiable_ = false,
289 BasicSetting* other_setting_ = nullptr) 313 typename std::enable_if<ranged, T*>::type other_setting_ = nullptr)
290 requires(ranged)
291 : Setting<Type, true>{linkage, default_val, min_val, 314 : Setting<Type, true>{linkage, default_val, min_val,
292 max_val, name, category_, 315 max_val, name, category_,
293 specialization_, save_, runtime_modifiable_, 316 specialization_, save_, runtime_modifiable_,
diff --git a/src/common/swap.h b/src/common/swap.h
index 085baaf9a..fde343e45 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -460,11 +460,6 @@ S operator&(const S& i, const swap_struct_t<T, F> v) {
460 return i & v.swap(); 460 return i & v.swap();
461} 461}
462 462
463template <typename S, typename T, typename F>
464S operator&(const swap_struct_t<T, F> v, const S& i) {
465 return static_cast<S>(v.swap() & i);
466}
467
468// Comparison 463// Comparison
469template <typename S, typename T, typename F> 464template <typename S, typename T, typename F>
470bool operator<(const S& p, const swap_struct_t<T, F> v) { 465bool operator<(const S& p, const swap_struct_t<T, F> v) {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4b7395be8..b2dc71d4c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -37,6 +37,49 @@ add_library(core STATIC
37 debugger/gdbstub.h 37 debugger/gdbstub.h
38 device_memory.cpp 38 device_memory.cpp
39 device_memory.h 39 device_memory.h
40 file_sys/fssystem/fs_i_storage.h
41 file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
42 file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
43 file_sys/fssystem/fssystem_aes_ctr_storage.cpp
44 file_sys/fssystem/fssystem_aes_ctr_storage.h
45 file_sys/fssystem/fssystem_aes_xts_storage.cpp
46 file_sys/fssystem/fssystem_aes_xts_storage.h
47 file_sys/fssystem/fssystem_alignment_matching_storage.h
48 file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp
49 file_sys/fssystem/fssystem_alignment_matching_storage_impl.h
50 file_sys/fssystem/fssystem_bucket_tree.cpp
51 file_sys/fssystem/fssystem_bucket_tree.h
52 file_sys/fssystem/fssystem_bucket_tree_utils.h
53 file_sys/fssystem/fssystem_compressed_storage.h
54 file_sys/fssystem/fssystem_compression_common.h
55 file_sys/fssystem/fssystem_compression_configuration.cpp
56 file_sys/fssystem/fssystem_compression_configuration.h
57 file_sys/fssystem/fssystem_crypto_configuration.cpp
58 file_sys/fssystem/fssystem_crypto_configuration.h
59 file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
60 file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
61 file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
62 file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
63 file_sys/fssystem/fssystem_indirect_storage.cpp
64 file_sys/fssystem/fssystem_indirect_storage.h
65 file_sys/fssystem/fssystem_integrity_romfs_storage.cpp
66 file_sys/fssystem/fssystem_integrity_romfs_storage.h
67 file_sys/fssystem/fssystem_integrity_verification_storage.cpp
68 file_sys/fssystem/fssystem_integrity_verification_storage.h
69 file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h
70 file_sys/fssystem/fssystem_nca_file_system_driver.cpp
71 file_sys/fssystem/fssystem_nca_file_system_driver.h
72 file_sys/fssystem/fssystem_nca_header.cpp
73 file_sys/fssystem/fssystem_nca_header.h
74 file_sys/fssystem/fssystem_nca_reader.cpp
75 file_sys/fssystem/fssystem_pooled_buffer.cpp
76 file_sys/fssystem/fssystem_pooled_buffer.h
77 file_sys/fssystem/fssystem_sparse_storage.cpp
78 file_sys/fssystem/fssystem_sparse_storage.h
79 file_sys/fssystem/fssystem_switch_storage.h
80 file_sys/fssystem/fssystem_utility.cpp
81 file_sys/fssystem/fssystem_utility.h
82 file_sys/fssystem/fs_types.h
40 file_sys/bis_factory.cpp 83 file_sys/bis_factory.cpp
41 file_sys/bis_factory.h 84 file_sys/bis_factory.h
42 file_sys/card_image.cpp 85 file_sys/card_image.cpp
@@ -57,8 +100,6 @@ add_library(core STATIC
57 file_sys/mode.h 100 file_sys/mode.h
58 file_sys/nca_metadata.cpp 101 file_sys/nca_metadata.cpp
59 file_sys/nca_metadata.h 102 file_sys/nca_metadata.h
60 file_sys/nca_patch.cpp
61 file_sys/nca_patch.h
62 file_sys/partition_filesystem.cpp 103 file_sys/partition_filesystem.cpp
63 file_sys/partition_filesystem.h 104 file_sys/partition_filesystem.h
64 file_sys/patch_manager.cpp 105 file_sys/patch_manager.cpp
@@ -543,13 +584,23 @@ add_library(core STATIC
543 hle/service/lm/lm.h 584 hle/service/lm/lm.h
544 hle/service/mig/mig.cpp 585 hle/service/mig/mig.cpp
545 hle/service/mig/mig.h 586 hle/service/mig/mig.h
587 hle/service/mii/types/char_info.cpp
588 hle/service/mii/types/char_info.h
589 hle/service/mii/types/core_data.cpp
590 hle/service/mii/types/core_data.h
591 hle/service/mii/types/raw_data.cpp
592 hle/service/mii/types/raw_data.h
593 hle/service/mii/types/store_data.cpp
594 hle/service/mii/types/store_data.h
595 hle/service/mii/types/ver3_store_data.cpp
596 hle/service/mii/types/ver3_store_data.h
546 hle/service/mii/mii.cpp 597 hle/service/mii/mii.cpp
547 hle/service/mii/mii.h 598 hle/service/mii/mii.h
548 hle/service/mii/mii_manager.cpp 599 hle/service/mii/mii_manager.cpp
549 hle/service/mii/mii_manager.h 600 hle/service/mii/mii_manager.h
550 hle/service/mii/raw_data.cpp 601 hle/service/mii/mii_result.h
551 hle/service/mii/raw_data.h 602 hle/service/mii/mii_types.h
552 hle/service/mii/types.h 603 hle/service/mii/mii_util.h
553 hle/service/mm/mm_u.cpp 604 hle/service/mm/mm_u.cpp
554 hle/service/mm/mm_u.h 605 hle/service/mm/mm_u.h
555 hle/service/mnpp/mnpp_app.cpp 606 hle/service/mnpp/mnpp_app.cpp
@@ -576,8 +627,8 @@ add_library(core STATIC
576 hle/service/nfp/nfp_interface.h 627 hle/service/nfp/nfp_interface.h
577 hle/service/nfp/nfp_result.h 628 hle/service/nfp/nfp_result.h
578 hle/service/nfp/nfp_types.h 629 hle/service/nfp/nfp_types.h
579 hle/service/ngct/ngct.cpp 630 hle/service/ngc/ngc.cpp
580 hle/service/ngct/ngct.h 631 hle/service/ngc/ngc.h
581 hle/service/nifm/nifm.cpp 632 hle/service/nifm/nifm.cpp
582 hle/service/nifm/nifm.h 633 hle/service/nifm/nifm.h
583 hle/service/nim/nim.cpp 634 hle/service/nim/nim.cpp
@@ -813,6 +864,8 @@ add_library(core STATIC
813 telemetry_session.h 864 telemetry_session.h
814 tools/freezer.cpp 865 tools/freezer.cpp
815 tools/freezer.h 866 tools/freezer.h
867 tools/renderdoc.cpp
868 tools/renderdoc.h
816) 869)
817 870
818if (MSVC) 871if (MSVC)
@@ -828,6 +881,7 @@ else()
828 -Werror=conversion 881 -Werror=conversion
829 882
830 -Wno-sign-conversion 883 -Wno-sign-conversion
884 -Wno-cast-function-type
831 885
832 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> 886 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
833 ) 887 )
@@ -836,7 +890,7 @@ endif()
836create_target_directory_groups(core) 890create_target_directory_groups(core)
837 891
838target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) 892target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
839target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) 893target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc)
840if (MINGW) 894if (MINGW)
841 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 895 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
842endif() 896endif()
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 2f67e60a9..e8300cd05 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -51,6 +51,7 @@
51#include "core/reporter.h" 51#include "core/reporter.h"
52#include "core/telemetry_session.h" 52#include "core/telemetry_session.h"
53#include "core/tools/freezer.h" 53#include "core/tools/freezer.h"
54#include "core/tools/renderdoc.h"
54#include "network/network.h" 55#include "network/network.h"
55#include "video_core/host1x/host1x.h" 56#include "video_core/host1x/host1x.h"
56#include "video_core/renderer_base.h" 57#include "video_core/renderer_base.h"
@@ -273,13 +274,18 @@ struct System::Impl {
273 time_manager.Initialize(); 274 time_manager.Initialize();
274 275
275 is_powered_on = true; 276 is_powered_on = true;
276 exit_lock = false; 277 exit_locked = false;
278 exit_requested = false;
277 279
278 microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); 280 microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0);
279 microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); 281 microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1);
280 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); 282 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
281 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); 283 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
282 284
285 if (Settings::values.enable_renderdoc_hotkey) {
286 renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
287 }
288
283 LOG_DEBUG(Core, "Initialized OK"); 289 LOG_DEBUG(Core, "Initialized OK");
284 290
285 return SystemResultStatus::Success; 291 return SystemResultStatus::Success;
@@ -398,12 +404,14 @@ struct System::Impl {
398 } 404 }
399 405
400 is_powered_on = false; 406 is_powered_on = false;
401 exit_lock = false; 407 exit_locked = false;
408 exit_requested = false;
402 409
403 if (gpu_core != nullptr) { 410 if (gpu_core != nullptr) {
404 gpu_core->NotifyShutdown(); 411 gpu_core->NotifyShutdown();
405 } 412 }
406 413
414 Network::CancelPendingSocketOperations();
407 kernel.SuspendApplication(true); 415 kernel.SuspendApplication(true);
408 if (services) { 416 if (services) {
409 services->KillNVNFlinger(); 417 services->KillNVNFlinger();
@@ -425,6 +433,7 @@ struct System::Impl {
425 debugger.reset(); 433 debugger.reset();
426 kernel.Shutdown(); 434 kernel.Shutdown();
427 memory.Reset(); 435 memory.Reset();
436 Network::RestartSocketOperations();
428 437
429 if (auto room_member = room_network.GetRoomMember().lock()) { 438 if (auto room_member = room_network.GetRoomMember().lock()) {
430 Network::GameInfo game_info{}; 439 Network::GameInfo game_info{};
@@ -507,7 +516,8 @@ struct System::Impl {
507 516
508 CpuManager cpu_manager; 517 CpuManager cpu_manager;
509 std::atomic_bool is_powered_on{}; 518 std::atomic_bool is_powered_on{};
510 bool exit_lock = false; 519 bool exit_locked = false;
520 bool exit_requested = false;
511 521
512 bool nvdec_active{}; 522 bool nvdec_active{};
513 523
@@ -516,6 +526,8 @@ struct System::Impl {
516 std::unique_ptr<Tools::Freezer> memory_freezer; 526 std::unique_ptr<Tools::Freezer> memory_freezer;
517 std::array<u8, 0x20> build_id{}; 527 std::array<u8, 0x20> build_id{};
518 528
529 std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
530
519 /// Frontend applets 531 /// Frontend applets
520 Service::AM::Applets::AppletManager applet_manager; 532 Service::AM::Applets::AppletManager applet_manager;
521 533
@@ -559,6 +571,8 @@ struct System::Impl {
559 571
560 std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> 572 std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
561 gpu_dirty_memory_write_manager{}; 573 gpu_dirty_memory_write_manager{};
574
575 std::deque<std::vector<u8>> user_channel;
562}; 576};
563 577
564System::System() : impl{std::make_unique<Impl>(*this)} {} 578System::System() : impl{std::make_unique<Impl>(*this)} {}
@@ -943,12 +957,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const {
943 return impl->time_manager; 957 return impl->time_manager;
944} 958}
945 959
946void System::SetExitLock(bool locked) { 960void System::SetExitLocked(bool locked) {
947 impl->exit_lock = locked; 961 impl->exit_locked = locked;
962}
963
964bool System::GetExitLocked() const {
965 return impl->exit_locked;
948} 966}
949 967
950bool System::GetExitLock() const { 968void System::SetExitRequested(bool requested) {
951 return impl->exit_lock; 969 impl->exit_requested = requested;
970}
971
972bool System::GetExitRequested() const {
973 return impl->exit_requested;
952} 974}
953 975
954void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { 976void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) {
@@ -1009,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
1009 return impl->room_network; 1031 return impl->room_network;
1010} 1032}
1011 1033
1034Tools::RenderdocAPI& System::GetRenderdocAPI() {
1035 return *impl->renderdoc_api;
1036}
1037
1012void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { 1038void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
1013 return impl->kernel.RunServer(std::move(server_manager)); 1039 return impl->kernel.RunServer(std::move(server_manager));
1014} 1040}
@@ -1025,6 +1051,10 @@ void System::ExecuteProgram(std::size_t program_index) {
1025 } 1051 }
1026} 1052}
1027 1053
1054std::deque<std::vector<u8>>& System::GetUserChannel() {
1055 return impl->user_channel;
1056}
1057
1028void System::RegisterExitCallback(ExitCallback&& callback) { 1058void System::RegisterExitCallback(ExitCallback&& callback) {
1029 impl->exit_callback = std::move(callback); 1059 impl->exit_callback = std::move(callback);
1030} 1060}
diff --git a/src/core/core.h b/src/core/core.h
index c70ea1965..df20f26f3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -4,6 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include <cstddef> 6#include <cstddef>
7#include <deque>
7#include <functional> 8#include <functional>
8#include <memory> 9#include <memory>
9#include <mutex> 10#include <mutex>
@@ -101,6 +102,10 @@ namespace Network {
101class RoomNetwork; 102class RoomNetwork;
102} 103}
103 104
105namespace Tools {
106class RenderdocAPI;
107}
108
104namespace Core { 109namespace Core {
105 110
106class ARM_Interface; 111class ARM_Interface;
@@ -412,8 +417,13 @@ public:
412 /// Gets an immutable reference to the Room Network. 417 /// Gets an immutable reference to the Room Network.
413 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; 418 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
414 419
415 void SetExitLock(bool locked); 420 [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
416 [[nodiscard]] bool GetExitLock() const; 421
422 void SetExitLocked(bool locked);
423 bool GetExitLocked() const;
424
425 void SetExitRequested(bool requested);
426 bool GetExitRequested() const;
417 427
418 void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); 428 void SetApplicationProcessBuildID(const CurrentBuildProcessID& id);
419 [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; 429 [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const;
@@ -456,6 +466,12 @@ public:
456 */ 466 */
457 void ExecuteProgram(std::size_t program_index); 467 void ExecuteProgram(std::size_t program_index);
458 468
469 /**
470 * Gets a reference to the user channel stack.
471 * It is used to transfer data between programs.
472 */
473 [[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel();
474
459 /// Type used for the frontend to designate a callback for System to exit the application. 475 /// Type used for the frontend to designate a callback for System to exit the application.
460 using ExitCallback = std::function<void()>; 476 using ExitCallback = std::function<void()>;
461 477
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 4ff2c50e5..43a3c5ffd 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -35,7 +35,6 @@ namespace Core::Crypto {
35namespace { 35namespace {
36 36
37constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; 37constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
38constexpr u64 FULL_TICKET_SIZE = 0x400;
39 38
40using Common::AsArray; 39using Common::AsArray;
41 40
@@ -156,6 +155,10 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
156 UNREACHABLE(); 155 UNREACHABLE();
157} 156}
158 157
158bool Ticket::IsValid() const {
159 return !std::holds_alternative<std::monostate>(data);
160}
161
159SignatureType Ticket::GetSignatureType() const { 162SignatureType Ticket::GetSignatureType() const {
160 if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { 163 if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
161 return ticket->sig_type; 164 return ticket->sig_type;
@@ -210,6 +213,54 @@ Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& righ
210 return Ticket{out}; 213 return Ticket{out};
211} 214}
212 215
216Ticket Ticket::Read(const FileSys::VirtualFile& file) {
217 // Attempt to read up to the largest ticket size, and make sure we read at least a signature
218 // type.
219 std::array<u8, sizeof(RSA4096Ticket)> raw_data{};
220 auto read_size = file->Read(raw_data.data(), raw_data.size(), 0);
221 if (read_size < sizeof(SignatureType)) {
222 LOG_WARNING(Crypto, "Attempted to read ticket file with invalid size {}.", read_size);
223 return Ticket{std::monostate()};
224 }
225 return Read(std::span{raw_data});
226}
227
228Ticket Ticket::Read(std::span<const u8> raw_data) {
229 // Some tools read only 0x180 bytes of ticket data instead of 0x2C0, so
230 // just make sure we have at least the bare minimum of data to work with.
231 SignatureType sig_type;
232 if (raw_data.size() < sizeof(SignatureType)) {
233 LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.",
234 raw_data.size());
235 return Ticket{std::monostate()};
236 }
237 std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type));
238
239 switch (sig_type) {
240 case SignatureType::RSA_4096_SHA1:
241 case SignatureType::RSA_4096_SHA256: {
242 RSA4096Ticket ticket{};
243 std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
244 return Ticket{ticket};
245 }
246 case SignatureType::RSA_2048_SHA1:
247 case SignatureType::RSA_2048_SHA256: {
248 RSA2048Ticket ticket{};
249 std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
250 return Ticket{ticket};
251 }
252 case SignatureType::ECDSA_SHA1:
253 case SignatureType::ECDSA_SHA256: {
254 ECDSATicket ticket{};
255 std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
256 return Ticket{ticket};
257 }
258 default:
259 LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid type {}.", sig_type);
260 return Ticket{std::monostate()};
261 }
262}
263
213Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { 264Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
214 Key128 out{}; 265 Key128 out{};
215 266
@@ -290,9 +341,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
290 } 341 }
291} 342}
292 343
293RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { 344void KeyManager::DeriveETicketRSAKey() {
294 if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { 345 if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) {
295 return {}; 346 return;
296 } 347 }
297 348
298 const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); 349 const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
@@ -304,12 +355,12 @@ RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
304 rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, 355 rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
305 extended_dec.data(), Op::Decrypt); 356 extended_dec.data(), Op::Decrypt);
306 357
307 RSAKeyPair<2048> rsa_key{}; 358 std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(),
308 std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); 359 eticket_rsa_keypair.decryption_key.size());
309 std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); 360 std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100,
310 std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); 361 eticket_rsa_keypair.modulus.size());
311 362 std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200,
312 return rsa_key; 363 eticket_rsa_keypair.exponent.size());
313} 364}
314 365
315Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { 366Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
@@ -447,10 +498,12 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
447 for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { 498 for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
448 if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && 499 if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
449 buffer[offset + 3] == 0x0) { 500 buffer[offset + 3] == 0x0) {
450 out.emplace_back(); 501 // NOTE: Assumes ticket blob will only contain RSA-2048 tickets.
451 auto& next = out.back(); 502 auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)});
452 std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); 503 offset += sizeof(RSA2048Ticket);
453 offset += FULL_TICKET_SIZE; 504 if (ticket.IsValid()) {
505 out.push_back(ticket);
506 }
454 } 507 }
455 } 508 }
456 509
@@ -503,25 +556,36 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
503 return offset; 556 return offset;
504} 557}
505 558
506std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, 559std::optional<Key128> KeyManager::ParseTicketTitleKey(const Ticket& ticket) {
507 const RSAKeyPair<2048>& key) { 560 if (!ticket.IsValid()) {
561 LOG_WARNING(Crypto, "Attempted to parse title key of invalid ticket.");
562 return std::nullopt;
563 }
564
565 if (ticket.GetData().rights_id == Key128{}) {
566 LOG_WARNING(Crypto, "Attempted to parse title key of ticket with no rights ID.");
567 return std::nullopt;
568 }
569
508 const auto issuer = ticket.GetData().issuer; 570 const auto issuer = ticket.GetData().issuer;
509 if (IsAllZeroArray(issuer)) { 571 if (IsAllZeroArray(issuer)) {
572 LOG_WARNING(Crypto, "Attempted to parse title key of ticket with invalid issuer.");
510 return std::nullopt; 573 return std::nullopt;
511 } 574 }
575
512 if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { 576 if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
513 LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); 577 LOG_WARNING(Crypto, "Parsing ticket with non-standard certificate authority.");
514 } 578 }
515 579
516 Key128 rights_id = ticket.GetData().rights_id; 580 if (ticket.GetData().type == TitleKeyType::Common) {
517 581 return ticket.GetData().title_key_common;
518 if (rights_id == Key128{}) {
519 return std::nullopt;
520 } 582 }
521 583
522 if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), 584 if (eticket_rsa_keypair == RSAKeyPair<2048>{}) {
523 ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { 585 LOG_WARNING(
524 return std::make_pair(rights_id, ticket.GetData().title_key_common); 586 Crypto,
587 "Skipping personalized ticket title key parsing due to missing ETicket RSA key-pair.");
588 return std::nullopt;
525 } 589 }
526 590
527 mbedtls_mpi D; // RSA Private Exponent 591 mbedtls_mpi D; // RSA Private Exponent
@@ -534,9 +598,12 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
534 mbedtls_mpi_init(&S); 598 mbedtls_mpi_init(&S);
535 mbedtls_mpi_init(&M); 599 mbedtls_mpi_init(&M);
536 600
537 mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); 601 const auto& title_key_block = ticket.GetData().title_key_block;
538 mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); 602 mbedtls_mpi_read_binary(&D, eticket_rsa_keypair.decryption_key.data(),
539 mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); 603 eticket_rsa_keypair.decryption_key.size());
604 mbedtls_mpi_read_binary(&N, eticket_rsa_keypair.modulus.data(),
605 eticket_rsa_keypair.modulus.size());
606 mbedtls_mpi_read_binary(&S, title_key_block.data(), title_key_block.size());
540 607
541 mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); 608 mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
542 609
@@ -564,8 +631,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
564 631
565 Key128 key_temp{}; 632 Key128 key_temp{};
566 std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); 633 std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
567 634 return key_temp;
568 return std::make_pair(rights_id, key_temp);
569} 635}
570 636
571KeyManager::KeyManager() { 637KeyManager::KeyManager() {
@@ -658,17 +724,25 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
658 continue; 724 continue;
659 } 725 }
660 726
661 const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); 727 const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
662 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); 728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
663 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { 729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
664 if (!ValidCryptoRevisionString(out[0], 18, 2)) { 730 if (!ValidCryptoRevisionString(out[0], 18, 2)) {
665 continue; 731 continue;
666 } 732 }
667 733
668 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); 734 const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
669 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); 735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
670 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { 736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
671 eticket_extended_kek = Common::HexStringToArray<576>(out[1]); 737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
738 } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) {
739 const auto key_data = Common::HexStringToArray<528>(out[1]);
740 std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(),
741 eticket_rsa_keypair.decryption_key.size());
742 std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100,
743 eticket_rsa_keypair.modulus.size());
744 std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200,
745 eticket_rsa_keypair.exponent.size());
672 } else { 746 } else {
673 for (const auto& kv : KEYS_VARIABLE_LENGTH) { 747 for (const auto& kv : KEYS_VARIABLE_LENGTH) {
674 if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { 748 if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) {
@@ -676,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
676 } 750 }
677 if (out[0].compare(0, kv.second.size(), kv.second) == 0) { 751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
678 const auto index = 752 const auto index =
679 std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); 753 std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
680 const auto sub = kv.first.second; 754 const auto sub = kv.first.second;
681 if (sub == 0) { 755 if (sub == 0) {
682 s128_keys[{kv.first.first, index, 0}] = 756 s128_keys[{kv.first.first, index, 0}] =
@@ -696,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
696 const auto& match = kak_names[j]; 770 const auto& match = kak_names[j];
697 if (out[0].compare(0, std::strlen(match), match) == 0) { 771 if (out[0].compare(0, std::strlen(match), match) == 0) {
698 const auto index = 772 const auto index =
699 std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); 773 std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
700 s128_keys[{S128KeyType::KeyArea, index, j}] = 774 s128_keys[{S128KeyType::KeyArea, index, j}] =
701 Common::HexStringToArray<16>(out[1]); 775 Common::HexStringToArray<16>(out[1]);
702 } 776 }
@@ -1110,56 +1184,38 @@ void KeyManager::DeriveETicket(PartitionDataManager& data,
1110 1184
1111 eticket_extended_kek = data.GetETicketExtendedKek(); 1185 eticket_extended_kek = data.GetETicketExtendedKek();
1112 WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); 1186 WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
1187 DeriveETicketRSAKey();
1113 PopulateTickets(); 1188 PopulateTickets();
1114} 1189}
1115 1190
1116void KeyManager::PopulateTickets() { 1191void KeyManager::PopulateTickets() {
1117 const auto rsa_key = GetETicketRSAKey(); 1192 if (ticket_databases_loaded) {
1118
1119 if (rsa_key == RSAKeyPair<2048>{}) {
1120 return; 1193 return;
1121 } 1194 }
1195 ticket_databases_loaded = true;
1122 1196
1123 if (!common_tickets.empty() && !personal_tickets.empty()) { 1197 std::vector<Ticket> tickets;
1124 return;
1125 }
1126 1198
1127 const auto system_save_e1_path = 1199 const auto system_save_e1_path =
1128 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; 1200 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1";
1129 1201 if (Common::FS::Exists(system_save_e1_path)) {
1130 const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, 1202 const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read,
1131 Common::FS::FileType::BinaryFile}; 1203 Common::FS::FileType::BinaryFile};
1204 const auto blob1 = GetTicketblob(save_e1);
1205 tickets.insert(tickets.end(), blob1.begin(), blob1.end());
1206 }
1132 1207
1133 const auto system_save_e2_path = 1208 const auto system_save_e2_path =
1134 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; 1209 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2";
1210 if (Common::FS::Exists(system_save_e2_path)) {
1211 const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read,
1212 Common::FS::FileType::BinaryFile};
1213 const auto blob2 = GetTicketblob(save_e2);
1214 tickets.insert(tickets.end(), blob2.begin(), blob2.end());
1215 }
1135 1216
1136 const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, 1217 for (const auto& ticket : tickets) {
1137 Common::FS::FileType::BinaryFile}; 1218 AddTicket(ticket);
1138
1139 const auto blob2 = GetTicketblob(save_e2);
1140 auto res = GetTicketblob(save_e1);
1141
1142 const auto idx = res.size();
1143 res.insert(res.end(), blob2.begin(), blob2.end());
1144
1145 for (std::size_t i = 0; i < res.size(); ++i) {
1146 const auto common = i < idx;
1147 const auto pair = ParseTicket(res[i], rsa_key);
1148 if (!pair) {
1149 continue;
1150 }
1151
1152 const auto& [rid, key] = *pair;
1153 u128 rights_id;
1154 std::memcpy(rights_id.data(), rid.data(), rid.size());
1155
1156 if (common) {
1157 common_tickets[rights_id] = res[i];
1158 } else {
1159 personal_tickets[rights_id] = res[i];
1160 }
1161
1162 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
1163 } 1219 }
1164} 1220}
1165 1221
@@ -1291,41 +1347,33 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
1291 return personal_tickets; 1347 return personal_tickets;
1292} 1348}
1293 1349
1294bool KeyManager::AddTicketCommon(Ticket raw) { 1350bool KeyManager::AddTicket(const Ticket& ticket) {
1295 const auto rsa_key = GetETicketRSAKey(); 1351 if (!ticket.IsValid()) {
1296 if (rsa_key == RSAKeyPair<2048>{}) { 1352 LOG_WARNING(Crypto, "Attempted to add invalid ticket.");
1297 return false;
1298 }
1299
1300 const auto pair = ParseTicket(raw, rsa_key);
1301 if (!pair) {
1302 return false; 1353 return false;
1303 } 1354 }
1304 1355
1305 const auto& [rid, key] = *pair; 1356 const auto& rid = ticket.GetData().rights_id;
1306 u128 rights_id; 1357 u128 rights_id;
1307 std::memcpy(rights_id.data(), rid.data(), rid.size()); 1358 std::memcpy(rights_id.data(), rid.data(), rid.size());
1308 common_tickets[rights_id] = raw; 1359 if (ticket.GetData().type == Core::Crypto::TitleKeyType::Common) {
1309 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); 1360 common_tickets[rights_id] = ticket;
1310 return true; 1361 } else {
1311} 1362 personal_tickets[rights_id] = ticket;
1363 }
1312 1364
1313bool KeyManager::AddTicketPersonalized(Ticket raw) { 1365 if (HasKey(S128KeyType::Titlekey, rights_id[1], rights_id[0])) {
1314 const auto rsa_key = GetETicketRSAKey(); 1366 LOG_DEBUG(Crypto,
1315 if (rsa_key == RSAKeyPair<2048>{}) { 1367 "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.",
1316 return false; 1368 rights_id[1], rights_id[0]);
1369 return true;
1317 } 1370 }
1318 1371
1319 const auto pair = ParseTicket(raw, rsa_key); 1372 const auto key = ParseTicketTitleKey(ticket);
1320 if (!pair) { 1373 if (!key) {
1321 return false; 1374 return false;
1322 } 1375 }
1323 1376 SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]);
1324 const auto& [rid, key] = *pair;
1325 u128 rights_id;
1326 std::memcpy(rights_id.data(), rid.data(), rid.size());
1327 common_tickets[rights_id] = raw;
1328 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
1329 return true; 1377 return true;
1330} 1378}
1331} // namespace Core::Crypto 1379} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 8c864503b..2250eccec 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -7,6 +7,7 @@
7#include <filesystem> 7#include <filesystem>
8#include <map> 8#include <map>
9#include <optional> 9#include <optional>
10#include <span>
10#include <string> 11#include <string>
11 12
12#include <variant> 13#include <variant>
@@ -29,8 +30,6 @@ enum class ResultStatus : u16;
29 30
30namespace Core::Crypto { 31namespace Core::Crypto {
31 32
32constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
33
34using Key128 = std::array<u8, 0x10>; 33using Key128 = std::array<u8, 0x10>;
35using Key256 = std::array<u8, 0x20>; 34using Key256 = std::array<u8, 0x20>;
36using SHA256Hash = std::array<u8, 0x20>; 35using SHA256Hash = std::array<u8, 0x20>;
@@ -82,6 +81,7 @@ struct RSA4096Ticket {
82 INSERT_PADDING_BYTES(0x3C); 81 INSERT_PADDING_BYTES(0x3C);
83 TicketData data; 82 TicketData data;
84}; 83};
84static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size.");
85 85
86struct RSA2048Ticket { 86struct RSA2048Ticket {
87 SignatureType sig_type; 87 SignatureType sig_type;
@@ -89,6 +89,7 @@ struct RSA2048Ticket {
89 INSERT_PADDING_BYTES(0x3C); 89 INSERT_PADDING_BYTES(0x3C);
90 TicketData data; 90 TicketData data;
91}; 91};
92static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size.");
92 93
93struct ECDSATicket { 94struct ECDSATicket {
94 SignatureType sig_type; 95 SignatureType sig_type;
@@ -96,16 +97,41 @@ struct ECDSATicket {
96 INSERT_PADDING_BYTES(0x40); 97 INSERT_PADDING_BYTES(0x40);
97 TicketData data; 98 TicketData data;
98}; 99};
100static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size.");
99 101
100struct Ticket { 102struct Ticket {
101 std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; 103 std::variant<std::monostate, RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
102 104
103 SignatureType GetSignatureType() const; 105 [[nodiscard]] bool IsValid() const;
104 TicketData& GetData(); 106 [[nodiscard]] SignatureType GetSignatureType() const;
105 const TicketData& GetData() const; 107 [[nodiscard]] TicketData& GetData();
106 u64 GetSize() const; 108 [[nodiscard]] const TicketData& GetData() const;
107 109 [[nodiscard]] u64 GetSize() const;
110
111 /**
112 * Synthesizes a common ticket given a title key and rights ID.
113 *
114 * @param title_key Title key to store in the ticket.
115 * @param rights_id Rights ID the ticket is for.
116 * @return The synthesized common ticket.
117 */
108 static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); 118 static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
119
120 /**
121 * Reads a ticket from a file.
122 *
123 * @param file File to read the ticket from.
124 * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false.
125 */
126 static Ticket Read(const FileSys::VirtualFile& file);
127
128 /**
129 * Reads a ticket from a memory buffer.
130 *
131 * @param raw_data Buffer to read the ticket from.
132 * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false.
133 */
134 static Ticket Read(std::span<const u8> raw_data);
109}; 135};
110 136
111static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); 137static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
@@ -264,8 +290,7 @@ public:
264 const std::map<u128, Ticket>& GetCommonTickets() const; 290 const std::map<u128, Ticket>& GetCommonTickets() const;
265 const std::map<u128, Ticket>& GetPersonalizedTickets() const; 291 const std::map<u128, Ticket>& GetPersonalizedTickets() const;
266 292
267 bool AddTicketCommon(Ticket raw); 293 bool AddTicket(const Ticket& ticket);
268 bool AddTicketPersonalized(Ticket raw);
269 294
270 void ReloadKeys(); 295 void ReloadKeys();
271 bool AreKeysLoaded() const; 296 bool AreKeysLoaded() const;
@@ -279,10 +304,12 @@ private:
279 // Map from rights ID to ticket 304 // Map from rights ID to ticket
280 std::map<u128, Ticket> common_tickets; 305 std::map<u128, Ticket> common_tickets;
281 std::map<u128, Ticket> personal_tickets; 306 std::map<u128, Ticket> personal_tickets;
307 bool ticket_databases_loaded = false;
282 308
283 std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; 309 std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
284 std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; 310 std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
285 std::array<u8, 576> eticket_extended_kek{}; 311 std::array<u8, 576> eticket_extended_kek{};
312 RSAKeyPair<2048> eticket_rsa_keypair{};
286 313
287 bool dev_mode; 314 bool dev_mode;
288 void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); 315 void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys);
@@ -293,10 +320,13 @@ private:
293 320
294 void DeriveGeneralPurposeKeys(std::size_t crypto_revision); 321 void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
295 322
296 RSAKeyPair<2048> GetETicketRSAKey() const; 323 void DeriveETicketRSAKey();
297 324
298 void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); 325 void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
299 void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); 326 void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
327
328 /// Parses the title key section of a ticket.
329 std::optional<Key128> ParseTicketTitleKey(const Ticket& ticket);
300}; 330};
301 331
302Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); 332Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
@@ -311,9 +341,4 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
311 341
312std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); 342std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save);
313 343
314// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
315// (offset 0x140-0x144 is zero)
316std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
317 const RSAKeyPair<2048>& eticket_extended_key);
318
319} // namespace Core::Crypto 344} // namespace Core::Crypto
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 0f839d5b4..e55831f27 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -263,6 +263,23 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
263 263
264 std::vector<u8> mem(size); 264 std::vector<u8> mem(size);
265 if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { 265 if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) {
266 // Restore any bytes belonging to replaced instructions.
267 auto it = replaced_instructions.lower_bound(addr);
268 for (; it != replaced_instructions.end() && it->first < addr + size; it++) {
269 // Get the bytes of the instruction we previously replaced.
270 const u32 original_bytes = it->second;
271
272 // Calculate where to start writing to the output buffer.
273 const size_t output_offset = it->first - addr;
274
275 // Calculate how many bytes to write.
276 // The loop condition ensures output_offset < size.
277 const size_t n = std::min<size_t>(size - output_offset, sizeof(u32));
278
279 // Write the bytes to the output buffer.
280 std::memcpy(mem.data() + output_offset, &original_bytes, n);
281 }
282
266 SendReply(Common::HexToString(mem)); 283 SendReply(Common::HexToString(mem));
267 } else { 284 } else {
268 SendReply(GDB_STUB_REPLY_ERR); 285 SendReply(GDB_STUB_REPLY_ERR);
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 5d02865f4..8b9a4fc5a 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -31,13 +31,9 @@ XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index)
31 : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, 31 : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
32 partitions(partition_names.size()), 32 partitions(partition_names.size()),
33 partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { 33 partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
34 if (file->ReadObject(&header) != sizeof(GamecardHeader)) { 34 const auto header_status = TryReadHeader();
35 status = Loader::ResultStatus::ErrorBadXCIHeader; 35 if (header_status != Loader::ResultStatus::Success) {
36 return; 36 status = header_status;
37 }
38
39 if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
40 status = Loader::ResultStatus::ErrorBadXCIHeader;
41 return; 37 return;
42 } 38 }
43 39
@@ -183,9 +179,9 @@ u32 XCI::GetSystemUpdateVersion() {
183 } 179 }
184 180
185 for (const auto& update_file : update->GetFiles()) { 181 for (const auto& update_file : update->GetFiles()) {
186 NCA nca{update_file, nullptr, 0}; 182 NCA nca{update_file};
187 183
188 if (nca.GetStatus() != Loader::ResultStatus::Success) { 184 if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) {
189 continue; 185 continue;
190 } 186 }
191 187
@@ -296,7 +292,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
296 continue; 292 continue;
297 } 293 }
298 294
299 auto nca = std::make_shared<NCA>(partition_file, nullptr, 0); 295 auto nca = std::make_shared<NCA>(partition_file);
300 if (nca->IsUpdate()) { 296 if (nca->IsUpdate()) {
301 continue; 297 continue;
302 } 298 }
@@ -316,6 +312,44 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
316 return Loader::ResultStatus::Success; 312 return Loader::ResultStatus::Success;
317} 313}
318 314
315Loader::ResultStatus XCI::TryReadHeader() {
316 constexpr size_t CardInitialDataRegionSize = 0x1000;
317
318 // Define the function we'll use to determine if we read a valid header.
319 const auto ReadCardHeader = [&]() {
320 // Ensure we can read the entire header. If we can't, we can't read the card image.
321 if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
322 return Loader::ResultStatus::ErrorBadXCIHeader;
323 }
324
325 // Ensure the header magic matches. If it doesn't, this isn't a card image header.
326 if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
327 return Loader::ResultStatus::ErrorBadXCIHeader;
328 }
329
330 // We read a card image header.
331 return Loader::ResultStatus::Success;
332 };
333
334 // Try to read the header directly.
335 if (ReadCardHeader() == Loader::ResultStatus::Success) {
336 return Loader::ResultStatus::Success;
337 }
338
339 // Get the size of the file.
340 const size_t card_image_size = file->GetSize();
341
342 // If we are large enough to have a key area, offset past the key area and retry.
343 if (card_image_size >= CardInitialDataRegionSize) {
344 file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize,
345 CardInitialDataRegionSize);
346 return ReadCardHeader();
347 }
348
349 // We had no header and aren't large enough to have a key area, so this can't be parsed.
350 return Loader::ResultStatus::ErrorBadXCIHeader;
351}
352
319u8 XCI::GetFormatVersion() { 353u8 XCI::GetFormatVersion() {
320 return GetLogoPartition() == nullptr ? 0x1 : 0x2; 354 return GetLogoPartition() == nullptr ? 0x1 : 0x2;
321} 355}
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 1283f8216..9886123e7 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -128,6 +128,7 @@ public:
128 128
129private: 129private:
130 Loader::ResultStatus AddNCAFromPartition(XCIPartition part); 130 Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
131 Loader::ResultStatus TryReadHeader();
131 132
132 VirtualFile file; 133 VirtualFile file;
133 GamecardHeader header{}; 134 GamecardHeader header{};
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 06efab46d..7d2f0abb8 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -12,545 +12,118 @@
12#include "core/crypto/ctr_encryption_layer.h" 12#include "core/crypto/ctr_encryption_layer.h"
13#include "core/crypto/key_manager.h" 13#include "core/crypto/key_manager.h"
14#include "core/file_sys/content_archive.h" 14#include "core/file_sys/content_archive.h"
15#include "core/file_sys/nca_patch.h"
16#include "core/file_sys/partition_filesystem.h" 15#include "core/file_sys/partition_filesystem.h"
17#include "core/file_sys/vfs_offset.h" 16#include "core/file_sys/vfs_offset.h"
18#include "core/loader/loader.h" 17#include "core/loader/loader.h"
19 18
19#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
20#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
21#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
22
20namespace FileSys { 23namespace FileSys {
21 24
22// Media offsets in headers are stored divided by 512. Mult. by this to get real offset. 25static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
23constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; 26 return std::max<u8>(key_generation, 1) - 1;
24
25constexpr u64 SECTION_HEADER_SIZE = 0x200;
26constexpr u64 SECTION_HEADER_OFFSET = 0x400;
27
28constexpr u32 IVFC_MAX_LEVEL = 6;
29
30enum class NCASectionFilesystemType : u8 {
31 PFS0 = 0x2,
32 ROMFS = 0x3,
33};
34
35struct IVFCLevel {
36 u64_le offset;
37 u64_le size;
38 u32_le block_size;
39 u32_le reserved;
40};
41static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
42
43struct IVFCHeader {
44 u32_le magic;
45 u32_le magic_number;
46 INSERT_PADDING_BYTES_NOINIT(8);
47 std::array<IVFCLevel, 6> levels;
48 INSERT_PADDING_BYTES_NOINIT(64);
49};
50static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
51
52struct NCASectionHeaderBlock {
53 INSERT_PADDING_BYTES_NOINIT(3);
54 NCASectionFilesystemType filesystem_type;
55 NCASectionCryptoType crypto_type;
56 INSERT_PADDING_BYTES_NOINIT(3);
57};
58static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
59
60struct NCABucketInfo {
61 u64 table_offset;
62 u64 table_size;
63 std::array<u8, 0x10> table_header;
64};
65static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size.");
66
67struct NCASparseInfo {
68 NCABucketInfo bucket;
69 u64 physical_offset;
70 u16 generation;
71 INSERT_PADDING_BYTES_NOINIT(0x6);
72};
73static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size.");
74
75struct NCACompressionInfo {
76 NCABucketInfo bucket;
77 INSERT_PADDING_BYTES_NOINIT(0x8);
78};
79static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size.");
80
81struct NCASectionRaw {
82 NCASectionHeaderBlock header;
83 std::array<u8, 0x138> block_data;
84 std::array<u8, 0x8> section_ctr;
85 NCASparseInfo sparse_info;
86 NCACompressionInfo compression_info;
87 INSERT_PADDING_BYTES_NOINIT(0x60);
88};
89static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
90
91struct PFS0Superblock {
92 NCASectionHeaderBlock header_block;
93 std::array<u8, 0x20> hash;
94 u32_le size;
95 INSERT_PADDING_BYTES_NOINIT(4);
96 u64_le hash_table_offset;
97 u64_le hash_table_size;
98 u64_le pfs0_header_offset;
99 u64_le pfs0_size;
100 INSERT_PADDING_BYTES_NOINIT(0x1B0);
101};
102static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
103
104struct RomFSSuperblock {
105 NCASectionHeaderBlock header_block;
106 IVFCHeader ivfc;
107 INSERT_PADDING_BYTES_NOINIT(0x118);
108};
109static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
110
111struct BKTRHeader {
112 u64_le offset;
113 u64_le size;
114 u32_le magic;
115 INSERT_PADDING_BYTES_NOINIT(0x4);
116 u32_le number_entries;
117 INSERT_PADDING_BYTES_NOINIT(0x4);
118};
119static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
120
121struct BKTRSuperblock {
122 NCASectionHeaderBlock header_block;
123 IVFCHeader ivfc;
124 INSERT_PADDING_BYTES_NOINIT(0x18);
125 BKTRHeader relocation;
126 BKTRHeader subsection;
127 INSERT_PADDING_BYTES_NOINIT(0xC0);
128};
129static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
130
131union NCASectionHeader {
132 NCASectionRaw raw{};
133 PFS0Superblock pfs0;
134 RomFSSuperblock romfs;
135 BKTRSuperblock bktr;
136};
137static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
138
139static bool IsValidNCA(const NCAHeader& header) {
140 // TODO(DarkLordZach): Add NCA2/NCA0 support.
141 return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
142} 27}
143 28
144NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) 29NCA::NCA(VirtualFile file_, const NCA* base_nca)
145 : file(std::move(file_)), 30 : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
146 bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} {
147 if (file == nullptr) { 31 if (file == nullptr) {
148 status = Loader::ResultStatus::ErrorNullFile; 32 status = Loader::ResultStatus::ErrorNullFile;
149 return; 33 return;
150 } 34 }
151 35
152 if (sizeof(NCAHeader) != file->ReadObject(&header)) { 36 reader = std::make_shared<NcaReader>();
153 LOG_ERROR(Loader, "File reader errored out during header read."); 37 if (Result rc =
38 reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration());
39 R_FAILED(rc)) {
40 if (rc != ResultInvalidNcaSignature) {
41 LOG_ERROR(Loader, "File reader errored out during header read: {:#x}",
42 rc.GetInnerValue());
43 }
154 status = Loader::ResultStatus::ErrorBadNCAHeader; 44 status = Loader::ResultStatus::ErrorBadNCAHeader;
155 return; 45 return;
156 } 46 }
157 47
158 if (!HandlePotentialHeaderDecryption()) { 48 // Ensure we have the proper key area keys to continue.
159 return; 49 const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration());
160 } 50 if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) {
161 51 status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
162 has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; });
163
164 const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
165 is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) {
166 return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
167 });
168
169 if (!ReadSections(sections, bktr_base_ivfc_offset)) {
170 return; 52 return;
171 } 53 }
172 54
173 status = Loader::ResultStatus::Success; 55 RightsId rights_id{};
174} 56 reader->GetRightsId(rights_id.data(), rights_id.size());
175 57 if (rights_id != RightsId{}) {
176NCA::~NCA() = default; 58 // External decryption key required; provide it here.
177 59 u128 rights_id_u128;
178bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { 60 std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id));
179 if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
180 status = Loader::ResultStatus::ErrorNCA2;
181 return false;
182 }
183 61
184 if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { 62 auto titlekey =
185 status = Loader::ResultStatus::ErrorNCA0; 63 keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]);
186 return false; 64 if (titlekey == Core::Crypto::Key128{}) {
187 } 65 status = Loader::ResultStatus::ErrorMissingTitlekey;
188 66 return;
189 return true;
190}
191
192bool NCA::HandlePotentialHeaderDecryption() {
193 if (IsValidNCA(header)) {
194 return true;
195 }
196
197 if (!CheckSupportedNCA(header)) {
198 return false;
199 }
200
201 NCAHeader dec_header{};
202 Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
203 keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
204 cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
205 Core::Crypto::Op::Decrypt);
206 if (IsValidNCA(dec_header)) {
207 header = dec_header;
208 encrypted = true;
209 } else {
210 if (!CheckSupportedNCA(dec_header)) {
211 return false;
212 } 67 }
213 68
214 if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { 69 if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
215 status = Loader::ResultStatus::ErrorIncorrectHeaderKey; 70 status = Loader::ResultStatus::ErrorMissingTitlekek;
216 } else { 71 return;
217 status = Loader::ResultStatus::ErrorMissingHeaderKey;
218 } 72 }
219 return false;
220 }
221 73
222 return true; 74 auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id);
223} 75 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB);
76 cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(),
77 Core::Crypto::Op::Decrypt);
224 78
225std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const { 79 reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size());
226 const std::ptrdiff_t number_sections =
227 std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) {
228 return entry.media_offset > 0;
229 });
230
231 std::vector<NCASectionHeader> sections(number_sections);
232 const auto length_sections = SECTION_HEADER_SIZE * number_sections;
233
234 if (encrypted) {
235 auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
236 Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
237 keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
238 cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
239 Core::Crypto::Op::Decrypt);
240 } else {
241 file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
242 }
243
244 return sections;
245}
246
247bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
248 for (std::size_t i = 0; i < sections.size(); ++i) {
249 const auto& section = sections[i];
250
251 if (section.raw.sparse_info.bucket.table_offset != 0 &&
252 section.raw.sparse_info.bucket.table_size != 0) {
253 LOG_ERROR(Loader, "Sparse NCAs are not supported.");
254 status = Loader::ResultStatus::ErrorSparseNCA;
255 return false;
256 }
257
258 if (section.raw.compression_info.bucket.table_offset != 0 &&
259 section.raw.compression_info.bucket.table_size != 0) {
260 LOG_ERROR(Loader, "Compressed NCAs are not supported.");
261 status = Loader::ResultStatus::ErrorCompressedNCA;
262 return false;
263 }
264
265 if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
266 if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
267 return false;
268 }
269 } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
270 if (!ReadPFS0Section(section, header.section_tables[i])) {
271 return false;
272 }
273 }
274 }
275
276 return true;
277}
278
279bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
280 u64 bktr_base_ivfc_offset) {
281 const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
282 ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
283 const std::size_t romfs_offset = base_offset + ivfc_offset;
284 const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
285 auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
286 auto dec = Decrypt(section, raw, romfs_offset);
287
288 if (dec == nullptr) {
289 if (status != Loader::ResultStatus::Success)
290 return false;
291 if (has_rights_id)
292 status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
293 else
294 status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
295 return false;
296 } 80 }
297 81
298 if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { 82 const s32 fs_count = reader->GetFsCount();
299 if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || 83 NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader);
300 section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { 84 std::vector<VirtualFile> filesystems(fs_count);
301 status = Loader::ResultStatus::ErrorBadBKTRHeader; 85 for (s32 i = 0; i < fs_count; i++) {
302 return false; 86 NcaFsHeaderReader header_reader;
303 } 87 const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i);
304 88 if (R_FAILED(rc)) {
305 if (section.bktr.relocation.offset + section.bktr.relocation.size != 89 LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i,
306 section.bktr.subsection.offset) { 90 rc.GetInnerValue());
307 status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; 91 status = Loader::ResultStatus::ErrorBadNCAHeader;
308 return false; 92 return;
309 }
310
311 const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
312 if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
313 status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
314 return false;
315 }
316
317 const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
318 RelocationBlock relocation_block{};
319 if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
320 sizeof(RelocationBlock)) {
321 status = Loader::ResultStatus::ErrorBadRelocationBlock;
322 return false;
323 }
324 SubsectionBlock subsection_block{};
325 if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
326 sizeof(RelocationBlock)) {
327 status = Loader::ResultStatus::ErrorBadSubsectionBlock;
328 return false;
329 }
330
331 std::vector<RelocationBucketRaw> relocation_buckets_raw(
332 (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
333 if (dec->ReadBytes(relocation_buckets_raw.data(),
334 section.bktr.relocation.size - sizeof(RelocationBlock),
335 section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
336 section.bktr.relocation.size - sizeof(RelocationBlock)) {
337 status = Loader::ResultStatus::ErrorBadRelocationBuckets;
338 return false;
339 } 93 }
340 94
341 std::vector<SubsectionBucketRaw> subsection_buckets_raw( 95 if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) {
342 (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw)); 96 files.push_back(filesystems[i]);
343 if (dec->ReadBytes(subsection_buckets_raw.data(), 97 romfs = files.back();
344 section.bktr.subsection.size - sizeof(SubsectionBlock),
345 section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
346 section.bktr.subsection.size - sizeof(SubsectionBlock)) {
347 status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
348 return false;
349 } 98 }
350 99
351 std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); 100 if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) {
352 std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(), 101 auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]);
353 &ConvertRelocationBucketRaw); 102 if (npfs->GetStatus() == Loader::ResultStatus::Success) {
354 std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size()); 103 dirs.push_back(npfs);
355 std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(), 104 if (IsDirectoryExeFS(npfs)) {
356 &ConvertSubsectionBucketRaw); 105 exefs = dirs.back();
357 106 } else if (IsDirectoryLogoPartition(npfs)) {
358 u32 ctr_low; 107 logo = dirs.back();
359 std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); 108 } else {
360 subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); 109 continue;
361 subsection_buckets.back().entries.push_back({size, {0}, 0});
362
363 std::optional<Core::Crypto::Key128> key;
364 if (encrypted) {
365 if (has_rights_id) {
366 status = Loader::ResultStatus::Success;
367 key = GetTitlekey();
368 if (!key) {
369 status = Loader::ResultStatus::ErrorMissingTitlekey;
370 return false;
371 }
372 } else {
373 key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
374 if (!key) {
375 status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
376 return false;
377 } 110 }
378 } 111 }
379 } 112 }
380 113
381 if (bktr_base_romfs == nullptr) { 114 if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) {
382 status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; 115 is_update = true;
383 return false;
384 } 116 }
385
386 auto bktr = std::make_shared<BKTR>(
387 bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
388 relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
389 encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
390 section.raw.section_ctr);
391
392 // BKTR applies to entire IVFC, so make an offset version to level 6
393 files.push_back(std::make_shared<OffsetVfsFile>(
394 bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
395 } else {
396 files.push_back(std::move(dec));
397 } 117 }
398 118
399 romfs = files.back(); 119 if (is_update && base_nca == nullptr) {
400 return true; 120 status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
401}
402
403bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
404 const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
405 section.pfs0.pfs0_header_offset;
406 const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
407
408 auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
409 if (dec != nullptr) {
410 auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
411
412 if (npfs->GetStatus() == Loader::ResultStatus::Success) {
413 dirs.push_back(std::move(npfs));
414 if (IsDirectoryExeFS(dirs.back()))
415 exefs = dirs.back();
416 else if (IsDirectoryLogoPartition(dirs.back()))
417 logo = dirs.back();
418 } else {
419 if (has_rights_id)
420 status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
421 else
422 status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
423 return false;
424 }
425 } else { 121 } else {
426 if (status != Loader::ResultStatus::Success) 122 status = Loader::ResultStatus::Success;
427 return false;
428 if (has_rights_id)
429 status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
430 else
431 status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
432 return false;
433 } 123 }
434
435 return true;
436}
437
438u8 NCA::GetCryptoRevision() const {
439 u8 master_key_id = header.crypto_type;
440 if (header.crypto_type_2 > master_key_id)
441 master_key_id = header.crypto_type_2;
442 if (master_key_id > 0)
443 --master_key_id;
444 return master_key_id;
445}
446
447std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
448 const auto master_key_id = GetCryptoRevision();
449
450 if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) {
451 return std::nullopt;
452 }
453
454 std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
455 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
456 keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
457 Core::Crypto::Mode::ECB);
458 cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
459
460 Core::Crypto::Key128 out{};
461 if (type == NCASectionCryptoType::XTS) {
462 std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
463 } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) {
464 std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
465 } else {
466 LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
467 type);
468 }
469
470 u128 out_128{};
471 std::memcpy(out_128.data(), out.data(), sizeof(u128));
472 LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
473 master_key_id, header.key_index, out_128[1], out_128[0]);
474
475 return out;
476} 124}
477 125
478std::optional<Core::Crypto::Key128> NCA::GetTitlekey() { 126NCA::~NCA() = default;
479 const auto master_key_id = GetCryptoRevision();
480
481 u128 rights_id{};
482 memcpy(rights_id.data(), header.rights_id.data(), 16);
483 if (rights_id == u128{}) {
484 status = Loader::ResultStatus::ErrorInvalidRightsID;
485 return std::nullopt;
486 }
487
488 auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
489 if (titlekey == Core::Crypto::Key128{}) {
490 status = Loader::ResultStatus::ErrorMissingTitlekey;
491 return std::nullopt;
492 }
493
494 if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
495 status = Loader::ResultStatus::ErrorMissingTitlekek;
496 return std::nullopt;
497 }
498
499 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
500 keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB);
501 cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt);
502
503 return titlekey;
504}
505
506VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
507 if (!encrypted)
508 return in;
509
510 switch (s_header.raw.header.crypto_type) {
511 case NCASectionCryptoType::NONE:
512 LOG_TRACE(Crypto, "called with mode=NONE");
513 return in;
514 case NCASectionCryptoType::CTR:
515 // During normal BKTR decryption, this entire function is skipped. This is for the metadata,
516 // which uses the same CTR as usual.
517 case NCASectionCryptoType::BKTR:
518 LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
519 {
520 std::optional<Core::Crypto::Key128> key;
521 if (has_rights_id) {
522 status = Loader::ResultStatus::Success;
523 key = GetTitlekey();
524 if (!key) {
525 if (status == Loader::ResultStatus::Success)
526 status = Loader::ResultStatus::ErrorMissingTitlekey;
527 return nullptr;
528 }
529 } else {
530 key = GetKeyAreaKey(NCASectionCryptoType::CTR);
531 if (!key) {
532 status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
533 return nullptr;
534 }
535 }
536
537 auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
538 starting_offset);
539 Core::Crypto::CTREncryptionLayer::IVData iv{};
540 for (std::size_t i = 0; i < 8; ++i) {
541 iv[i] = s_header.raw.section_ctr[8 - i - 1];
542 }
543 out->SetIV(iv);
544 return std::static_pointer_cast<VfsFile>(out);
545 }
546 case NCASectionCryptoType::XTS:
547 // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
548 default:
549 LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
550 s_header.raw.header.crypto_type);
551 return nullptr;
552 }
553}
554 127
555Loader::ResultStatus NCA::GetStatus() const { 128Loader::ResultStatus NCA::GetStatus() const {
556 return status; 129 return status;
@@ -579,21 +152,24 @@ VirtualDir NCA::GetParentDirectory() const {
579} 152}
580 153
581NCAContentType NCA::GetType() const { 154NCAContentType NCA::GetType() const {
582 return header.content_type; 155 return static_cast<NCAContentType>(reader->GetContentType());
583} 156}
584 157
585u64 NCA::GetTitleId() const { 158u64 NCA::GetTitleId() const {
586 if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) 159 if (is_update) {
587 return header.title_id | 0x800; 160 return reader->GetProgramId() | 0x800;
588 return header.title_id; 161 }
162 return reader->GetProgramId();
589} 163}
590 164
591std::array<u8, 16> NCA::GetRightsId() const { 165RightsId NCA::GetRightsId() const {
592 return header.rights_id; 166 RightsId result;
167 reader->GetRightsId(result.data(), result.size());
168 return result;
593} 169}
594 170
595u32 NCA::GetSDKVersion() const { 171u32 NCA::GetSDKVersion() const {
596 return header.sdk_version; 172 return reader->GetSdkAddonVersion();
597} 173}
598 174
599bool NCA::IsUpdate() const { 175bool NCA::IsUpdate() const {
@@ -612,10 +188,6 @@ VirtualFile NCA::GetBaseFile() const {
612 return file; 188 return file;
613} 189}
614 190
615u64 NCA::GetBaseIVFCOffset() const {
616 return ivfc_offset;
617}
618
619VirtualDir NCA::GetLogoPartition() const { 191VirtualDir NCA::GetLogoPartition() const {
620 return logo; 192 return logo;
621} 193}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 20f524f80..af521d453 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -21,7 +21,7 @@ enum class ResultStatus : u16;
21 21
22namespace FileSys { 22namespace FileSys {
23 23
24union NCASectionHeader; 24class NcaReader;
25 25
26/// Describes the type of content within an NCA archive. 26/// Describes the type of content within an NCA archive.
27enum class NCAContentType : u8 { 27enum class NCAContentType : u8 {
@@ -45,41 +45,7 @@ enum class NCAContentType : u8 {
45 PublicData = 5, 45 PublicData = 5,
46}; 46};
47 47
48enum class NCASectionCryptoType : u8 { 48using RightsId = std::array<u8, 0x10>;
49 NONE = 1,
50 XTS = 2,
51 CTR = 3,
52 BKTR = 4,
53};
54
55struct NCASectionTableEntry {
56 u32_le media_offset;
57 u32_le media_end_offset;
58 INSERT_PADDING_BYTES(0x8);
59};
60static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
61
62struct NCAHeader {
63 std::array<u8, 0x100> rsa_signature_1;
64 std::array<u8, 0x100> rsa_signature_2;
65 u32_le magic;
66 u8 is_system;
67 NCAContentType content_type;
68 u8 crypto_type;
69 u8 key_index;
70 u64_le size;
71 u64_le title_id;
72 INSERT_PADDING_BYTES(0x4);
73 u32_le sdk_version;
74 u8 crypto_type_2;
75 INSERT_PADDING_BYTES(15);
76 std::array<u8, 0x10> rights_id;
77 std::array<NCASectionTableEntry, 0x4> section_tables;
78 std::array<std::array<u8, 0x20>, 0x4> hash_tables;
79 std::array<u8, 0x40> key_area;
80 INSERT_PADDING_BYTES(0xC0);
81};
82static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
83 49
84inline bool IsDirectoryExeFS(const VirtualDir& pfs) { 50inline bool IsDirectoryExeFS(const VirtualDir& pfs) {
85 // According to switchbrew, an exefs must only contain these two files: 51 // According to switchbrew, an exefs must only contain these two files:
@@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
97// After construction, use GetStatus to determine if the file is valid and ready to be used. 63// After construction, use GetStatus to determine if the file is valid and ready to be used.
98class NCA : public ReadOnlyVfsDirectory { 64class NCA : public ReadOnlyVfsDirectory {
99public: 65public:
100 explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, 66 explicit NCA(VirtualFile file, const NCA* base_nca = nullptr);
101 u64 bktr_base_ivfc_offset = 0);
102 ~NCA() override; 67 ~NCA() override;
103 68
104 Loader::ResultStatus GetStatus() const; 69 Loader::ResultStatus GetStatus() const;
@@ -110,7 +75,7 @@ public:
110 75
111 NCAContentType GetType() const; 76 NCAContentType GetType() const;
112 u64 GetTitleId() const; 77 u64 GetTitleId() const;
113 std::array<u8, 0x10> GetRightsId() const; 78 RightsId GetRightsId() const;
114 u32 GetSDKVersion() const; 79 u32 GetSDKVersion() const;
115 bool IsUpdate() const; 80 bool IsUpdate() const;
116 81
@@ -119,26 +84,9 @@ public:
119 84
120 VirtualFile GetBaseFile() const; 85 VirtualFile GetBaseFile() const;
121 86
122 // Returns the base ivfc offset used in BKTR patching.
123 u64 GetBaseIVFCOffset() const;
124
125 VirtualDir GetLogoPartition() const; 87 VirtualDir GetLogoPartition() const;
126 88
127private: 89private:
128 bool CheckSupportedNCA(const NCAHeader& header);
129 bool HandlePotentialHeaderDecryption();
130
131 std::vector<NCASectionHeader> ReadSectionHeaders() const;
132 bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
133 bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
134 u64 bktr_base_ivfc_offset);
135 bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
136
137 u8 GetCryptoRevision() const;
138 std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
139 std::optional<Core::Crypto::Key128> GetTitlekey();
140 VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
141
142 std::vector<VirtualDir> dirs; 90 std::vector<VirtualDir> dirs;
143 std::vector<VirtualFile> files; 91 std::vector<VirtualFile> files;
144 92
@@ -146,11 +94,6 @@ private:
146 VirtualDir exefs = nullptr; 94 VirtualDir exefs = nullptr;
147 VirtualDir logo = nullptr; 95 VirtualDir logo = nullptr;
148 VirtualFile file; 96 VirtualFile file;
149 VirtualFile bktr_base_romfs;
150 u64 ivfc_offset = 0;
151
152 NCAHeader header{};
153 bool has_rights_id{};
154 97
155 Loader::ResultStatus status{}; 98 Loader::ResultStatus status{};
156 99
@@ -158,6 +101,7 @@ private:
158 bool is_update = false; 101 bool is_update = false;
159 102
160 Core::Crypto::KeyManager& keys; 103 Core::Crypto::KeyManager& keys;
104 std::shared_ptr<NcaReader> reader;
161}; 105};
162 106
163} // namespace FileSys 107} // namespace FileSys
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index 7cee0c7df..2f5045a67 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -17,4 +17,74 @@ constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
17constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; 17constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
18constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; 18constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
19 19
20constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50};
21constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001};
22constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002};
23constexpr Result ResultOutOfRange{ErrorModule::FS, 3005};
24constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294};
25constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341};
26constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363};
27constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399};
28constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412};
29constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422};
30constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423};
31constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012};
32constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021};
33constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022};
34constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023};
35constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024};
36constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032};
37constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033};
38constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034};
39constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035};
40constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036};
41constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037};
42constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038};
43constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039};
44constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084};
45constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085};
46constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086};
47constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087};
48constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088};
49constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089};
50constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090};
51constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091};
52constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091};
53constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509};
54constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510};
55constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511};
56constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517};
57constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520};
58constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521};
59constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522};
60constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523};
61constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524};
62constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525};
63constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526};
64constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528};
65constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529};
66constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530};
67constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532};
68constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533};
69constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534};
70constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535};
71constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541};
72constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542};
73constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543};
74constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547};
75constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548};
76constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549};
77constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
78constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
79constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
80constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
81constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
82constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
83constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
84constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
85constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
86constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
87constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
88constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705};
89
20} // namespace FileSys 90} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h
new file mode 100644
index 000000000..416dd57b8
--- /dev/null
+++ b/src/core/file_sys/fssystem/fs_i_storage.h
@@ -0,0 +1,58 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/overflow.h"
7#include "core/file_sys/errors.h"
8#include "core/file_sys/vfs.h"
9
10namespace FileSys {
11
12class IStorage : public VfsFile {
13public:
14 virtual std::string GetName() const override {
15 return {};
16 }
17
18 virtual VirtualDir GetContainingDirectory() const override {
19 return {};
20 }
21
22 virtual bool IsWritable() const override {
23 return true;
24 }
25
26 virtual bool IsReadable() const override {
27 return true;
28 }
29
30 virtual bool Resize(size_t size) override {
31 return false;
32 }
33
34 virtual bool Rename(std::string_view name) override {
35 return false;
36 }
37
38 static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) {
39 R_UNLESS(offset >= 0, ResultInvalidOffset);
40 R_UNLESS(size >= 0, ResultInvalidSize);
41 R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange);
42 R_UNLESS(offset + size <= total_size, ResultOutOfRange);
43 R_SUCCEED();
44 }
45};
46
47class IReadOnlyStorage : public IStorage {
48public:
49 virtual bool IsWritable() const override {
50 return false;
51 }
52
53 virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
54 return 0;
55 }
56};
57
58} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fs_types.h b/src/core/file_sys/fssystem/fs_types.h
new file mode 100644
index 000000000..43aeaf447
--- /dev/null
+++ b/src/core/file_sys/fssystem/fs_types.h
@@ -0,0 +1,46 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7
8namespace FileSys {
9
10struct Int64 {
11 u32 low;
12 u32 high;
13
14 constexpr void Set(s64 v) {
15 this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0);
16 this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32);
17 }
18
19 constexpr s64 Get() const {
20 return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low));
21 }
22
23 constexpr Int64& operator=(s64 v) {
24 this->Set(v);
25 return *this;
26 }
27
28 constexpr operator s64() const {
29 return this->Get();
30 }
31};
32
33struct HashSalt {
34 static constexpr size_t Size = 32;
35
36 std::array<u8, Size> value;
37};
38static_assert(std::is_trivial_v<HashSalt>);
39static_assert(sizeof(HashSalt) == HashSalt::Size);
40
41constexpr inline size_t IntegrityMinLayerCount = 2;
42constexpr inline size_t IntegrityMaxLayerCount = 7;
43constexpr inline size_t IntegrityLayerCountSave = 5;
44constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4;
45
46} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
new file mode 100644
index 000000000..f25c95472
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
@@ -0,0 +1,251 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
5#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
6#include "core/file_sys/fssystem/fssystem_nca_header.h"
7#include "core/file_sys/vfs_offset.h"
8
9namespace FileSys {
10
11namespace {
12
13class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
14public:
15 virtual void Decrypt(
16 u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
17 const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final;
18};
19
20} // namespace
21
22Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) {
23 std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>();
24 R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA);
25 *out = std::move(decryptor);
26 R_SUCCEED();
27}
28
29Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
30 VirtualFile data_storage,
31 VirtualFile table_storage) {
32 // Read and verify the bucket tree header.
33 BucketTree::Header header;
34 table_storage->ReadObject(std::addressof(header), 0);
35 R_TRY(header.Verify());
36
37 // Determine extents.
38 const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
39 const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
40 const auto node_storage_offset = QueryHeaderStorageSize();
41 const auto entry_storage_offset = node_storage_offset + node_storage_size;
42
43 // Create a software decryptor.
44 std::unique_ptr<IDecryptor> sw_decryptor;
45 R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
46
47 // Initialize.
48 R_RETURN(this->Initialize(
49 key, key_size, secure_value, 0, data_storage,
50 std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
51 std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
52 header.entry_count, std::move(sw_decryptor)));
53}
54
55Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
56 s64 counter_offset, VirtualFile data_storage,
57 VirtualFile node_storage, VirtualFile entry_storage,
58 s32 entry_count,
59 std::unique_ptr<IDecryptor>&& decryptor) {
60 // Validate preconditions.
61 ASSERT(key != nullptr);
62 ASSERT(key_size == KeySize);
63 ASSERT(counter_offset >= 0);
64 ASSERT(decryptor != nullptr);
65
66 // Initialize the bucket tree table.
67 if (entry_count > 0) {
68 R_TRY(
69 m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
70 } else {
71 m_table.Initialize(NodeSize, 0);
72 }
73
74 // Set members.
75 m_data_storage = data_storage;
76 std::memcpy(m_key.data(), key, key_size);
77 m_secure_value = secure_value;
78 m_counter_offset = counter_offset;
79 m_decryptor = std::move(decryptor);
80
81 R_SUCCEED();
82}
83
84void AesCtrCounterExtendedStorage::Finalize() {
85 if (this->IsInitialized()) {
86 m_table.Finalize();
87 m_data_storage = VirtualFile();
88 }
89}
90
91Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count,
92 s32 entry_count, s64 offset, s64 size) {
93 // Validate pre-conditions.
94 ASSERT(offset >= 0);
95 ASSERT(size >= 0);
96 ASSERT(this->IsInitialized());
97
98 // Clear the out count.
99 R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
100 *out_entry_count = 0;
101
102 // Succeed if there's no range.
103 R_SUCCEED_IF(size == 0);
104
105 // If we have an output array, we need it to be non-null.
106 R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
107
108 // Check that our range is valid.
109 BucketTree::Offsets table_offsets;
110 R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
111
112 R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
113
114 // Find the offset in our tree.
115 BucketTree::Visitor visitor;
116 R_TRY(m_table.Find(std::addressof(visitor), offset));
117 {
118 const auto entry_offset = visitor.Get<Entry>()->GetOffset();
119 R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
120 ResultInvalidAesCtrCounterExtendedEntryOffset);
121 }
122
123 // Prepare to loop over entries.
124 const auto end_offset = offset + static_cast<s64>(size);
125 s32 count = 0;
126
127 auto cur_entry = *visitor.Get<Entry>();
128 while (cur_entry.GetOffset() < end_offset) {
129 // Try to write the entry to the out list.
130 if (entry_count != 0) {
131 if (count >= entry_count) {
132 break;
133 }
134 std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
135 }
136
137 count++;
138
139 // Advance.
140 if (visitor.CanMoveNext()) {
141 R_TRY(visitor.MoveNext());
142 cur_entry = *visitor.Get<Entry>();
143 } else {
144 break;
145 }
146 }
147
148 // Write the output count.
149 *out_entry_count = count;
150 R_SUCCEED();
151}
152
153size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const {
154 // Validate preconditions.
155 ASSERT(this->IsInitialized());
156
157 // Allow zero size.
158 if (size == 0) {
159 return size;
160 }
161
162 // Validate arguments.
163 ASSERT(buffer != nullptr);
164 ASSERT(Common::IsAligned(offset, BlockSize));
165 ASSERT(Common::IsAligned(size, BlockSize));
166
167 BucketTree::Offsets table_offsets;
168 ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets))));
169
170 ASSERT(table_offsets.IsInclude(offset, size));
171
172 // Read the data.
173 m_data_storage->Read(buffer, size, offset);
174
175 // Find the offset in our tree.
176 BucketTree::Visitor visitor;
177 ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset)));
178 {
179 const auto entry_offset = visitor.Get<Entry>()->GetOffset();
180 ASSERT(Common::IsAligned(entry_offset, BlockSize));
181 ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset));
182 }
183
184 // Prepare to read in chunks.
185 u8* cur_data = static_cast<u8*>(buffer);
186 auto cur_offset = offset;
187 const auto end_offset = offset + static_cast<s64>(size);
188
189 while (cur_offset < end_offset) {
190 // Get the current entry.
191 const auto cur_entry = *visitor.Get<Entry>();
192
193 // Get and validate the entry's offset.
194 const auto cur_entry_offset = cur_entry.GetOffset();
195 ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset);
196
197 // Get and validate the next entry offset.
198 s64 next_entry_offset;
199 if (visitor.CanMoveNext()) {
200 ASSERT(R_SUCCEEDED(visitor.MoveNext()));
201 next_entry_offset = visitor.Get<Entry>()->GetOffset();
202 ASSERT(table_offsets.IsInclude(next_entry_offset));
203 } else {
204 next_entry_offset = table_offsets.end_offset;
205 }
206 ASSERT(Common::IsAligned(next_entry_offset, BlockSize));
207 ASSERT(cur_offset < static_cast<size_t>(next_entry_offset));
208
209 // Get the offset of the entry in the data we read.
210 const auto data_offset = cur_offset - cur_entry_offset;
211 const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset;
212 ASSERT(data_size > 0);
213
214 // Determine how much is left.
215 const auto remaining_size = end_offset - cur_offset;
216 const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size));
217 ASSERT(cur_size <= size);
218
219 // If necessary, perform decryption.
220 if (cur_entry.encryption_value == Entry::Encryption::Encrypted) {
221 // Make the CTR for the data we're decrypting.
222 const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset;
223 NcaAesCtrUpperIv upper_iv = {
224 .part = {.generation = static_cast<u32>(cur_entry.generation),
225 .secure_value = m_secure_value}};
226
227 std::array<u8, IvSize> iv;
228 AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset);
229
230 // Decrypt.
231 m_decryptor->Decrypt(cur_data, cur_size, m_key, iv);
232 }
233
234 // Advance.
235 cur_data += cur_size;
236 cur_offset += cur_size;
237 }
238
239 return size;
240}
241
242void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size,
243 const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
244 const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) {
245 Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher(
246 key, Core::Crypto::Mode::CTR);
247 cipher.SetIV(iv);
248 cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt);
249}
250
251} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
new file mode 100644
index 000000000..d0e9ceed0
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
@@ -0,0 +1,114 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <optional>
7
8#include "common/literals.h"
9#include "core/file_sys/fssystem/fs_i_storage.h"
10#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
11
12namespace FileSys {
13
14using namespace Common::Literals;
15
16class AesCtrCounterExtendedStorage : public IReadOnlyStorage {
17 YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage);
18 YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage);
19
20public:
21 static constexpr size_t BlockSize = 0x10;
22 static constexpr size_t KeySize = 0x10;
23 static constexpr size_t IvSize = 0x10;
24 static constexpr size_t NodeSize = 16_KiB;
25
26 class IDecryptor {
27 public:
28 virtual ~IDecryptor() {}
29 virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key,
30 const std::array<u8, IvSize>& iv) = 0;
31 };
32
33 struct Entry {
34 enum class Encryption : u8 {
35 Encrypted = 0,
36 NotEncrypted = 1,
37 };
38
39 std::array<u8, sizeof(s64)> offset;
40 Encryption encryption_value;
41 std::array<u8, 3> reserved;
42 s32 generation;
43
44 void SetOffset(s64 value) {
45 std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64));
46 }
47
48 s64 GetOffset() const {
49 s64 value;
50 std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64));
51 return value;
52 }
53 };
54 static_assert(sizeof(Entry) == 0x10);
55 static_assert(alignof(Entry) == 4);
56 static_assert(std::is_trivial_v<Entry>);
57
58public:
59 static constexpr s64 QueryHeaderStorageSize() {
60 return BucketTree::QueryHeaderStorageSize();
61 }
62
63 static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
64 return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
65 }
66
67 static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
68 return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
69 }
70
71 static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out);
72
73public:
74 AesCtrCounterExtendedStorage()
75 : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {}
76 virtual ~AesCtrCounterExtendedStorage() {
77 this->Finalize();
78 }
79
80 Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset,
81 VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
82 s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor);
83 void Finalize();
84
85 bool IsInitialized() const {
86 return m_table.IsInitialized();
87 }
88
89 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
90
91 virtual size_t GetSize() const override {
92 BucketTree::Offsets offsets;
93 ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets))));
94
95 return offsets.end_offset;
96 }
97
98 Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
99 s64 size);
100
101private:
102 Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage,
103 VirtualFile table_storage);
104
105private:
106 mutable BucketTree m_table;
107 VirtualFile m_data_storage;
108 std::array<u8, KeySize> m_key;
109 u32 m_secure_value;
110 s64 m_counter_offset;
111 std::unique_ptr<IDecryptor> m_decryptor;
112};
113
114} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp
new file mode 100644
index 000000000..b65aca18d
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp
@@ -0,0 +1,129 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "common/swap.h"
6#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
7#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
8#include "core/file_sys/fssystem/fssystem_utility.h"
9
10namespace FileSys {
11
12void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) {
13 ASSERT(dst != nullptr);
14 ASSERT(dst_size == IvSize);
15 ASSERT(offset >= 0);
16
17 const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
18
19 *reinterpret_cast<u64_be*>(out_addr + 0) = upper;
20 *reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize);
21}
22
23AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
24 size_t iv_size)
25 : m_base_storage(std::move(base)) {
26 ASSERT(m_base_storage != nullptr);
27 ASSERT(key != nullptr);
28 ASSERT(iv != nullptr);
29 ASSERT(key_size == KeySize);
30 ASSERT(iv_size == IvSize);
31
32 std::memcpy(m_key.data(), key, KeySize);
33 std::memcpy(m_iv.data(), iv, IvSize);
34
35 m_cipher.emplace(m_key, Core::Crypto::Mode::CTR);
36}
37
38size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const {
39 // Allow zero-size reads.
40 if (size == 0) {
41 return size;
42 }
43
44 // Ensure buffer is valid.
45 ASSERT(buffer != nullptr);
46
47 // We can only read at block aligned offsets.
48 ASSERT(Common::IsAligned(offset, BlockSize));
49 ASSERT(Common::IsAligned(size, BlockSize));
50
51 // Read the data.
52 m_base_storage->Read(buffer, size, offset);
53
54 // Setup the counter.
55 std::array<u8, IvSize> ctr;
56 std::memcpy(ctr.data(), m_iv.data(), IvSize);
57 AddCounter(ctr.data(), IvSize, offset / BlockSize);
58
59 // Decrypt.
60 m_cipher->SetIV(ctr);
61 m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt);
62
63 return size;
64}
65
66size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) {
67 // Allow zero-size writes.
68 if (size == 0) {
69 return size;
70 }
71
72 // Ensure buffer is valid.
73 ASSERT(buffer != nullptr);
74
75 // We can only write at block aligned offsets.
76 ASSERT(Common::IsAligned(offset, BlockSize));
77 ASSERT(Common::IsAligned(size, BlockSize));
78
79 // Get a pooled buffer.
80 PooledBuffer pooled_buffer;
81 const bool use_work_buffer = true;
82 if (use_work_buffer) {
83 pooled_buffer.Allocate(size, BlockSize);
84 }
85
86 // Setup the counter.
87 std::array<u8, IvSize> ctr;
88 std::memcpy(ctr.data(), m_iv.data(), IvSize);
89 AddCounter(ctr.data(), IvSize, offset / BlockSize);
90
91 // Loop until all data is written.
92 size_t remaining = size;
93 s64 cur_offset = 0;
94 while (remaining > 0) {
95 // Determine data we're writing and where.
96 const size_t write_size =
97 use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
98
99 void* write_buf;
100 if (use_work_buffer) {
101 write_buf = pooled_buffer.GetBuffer();
102 } else {
103 write_buf = const_cast<u8*>(buffer);
104 }
105
106 // Encrypt the data.
107 m_cipher->SetIV(ctr);
108 m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf),
109 Core::Crypto::Op::Encrypt);
110
111 // Write the encrypted data.
112 m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset);
113
114 // Advance.
115 cur_offset += write_size;
116 remaining -= write_size;
117 if (remaining > 0) {
118 AddCounter(ctr.data(), IvSize, write_size / BlockSize);
119 }
120 }
121
122 return size;
123}
124
125size_t AesCtrStorage::GetSize() const {
126 return m_base_storage->GetSize();
127}
128
129} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h
new file mode 100644
index 000000000..339e49697
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h
@@ -0,0 +1,43 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <optional>
7
8#include "core/crypto/aes_util.h"
9#include "core/crypto/key_manager.h"
10#include "core/file_sys/errors.h"
11#include "core/file_sys/fssystem/fs_i_storage.h"
12#include "core/file_sys/vfs.h"
13
14namespace FileSys {
15
16class AesCtrStorage : public IStorage {
17 YUZU_NON_COPYABLE(AesCtrStorage);
18 YUZU_NON_MOVEABLE(AesCtrStorage);
19
20public:
21 static constexpr size_t BlockSize = 0x10;
22 static constexpr size_t KeySize = 0x10;
23 static constexpr size_t IvSize = 0x10;
24
25public:
26 static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset);
27
28public:
29 AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
30 size_t iv_size);
31
32 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
33 virtual size_t Write(const u8* buffer, size_t size, size_t offset) override;
34 virtual size_t GetSize() const override;
35
36private:
37 VirtualFile m_base_storage;
38 std::array<u8, KeySize> m_key;
39 std::array<u8, IvSize> m_iv;
40 mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher;
41};
42
43} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp
new file mode 100644
index 000000000..022424229
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp
@@ -0,0 +1,112 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "common/swap.h"
6#include "core/file_sys/errors.h"
7#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
8#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
9#include "core/file_sys/fssystem/fssystem_utility.h"
10
11namespace FileSys {
12
13void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) {
14 ASSERT(dst != nullptr);
15 ASSERT(dst_size == IvSize);
16 ASSERT(offset >= 0);
17
18 const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
19
20 *reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size;
21}
22
23AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
24 const void* iv, size_t iv_size, size_t block_size)
25 : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() {
26 ASSERT(m_base_storage != nullptr);
27 ASSERT(key1 != nullptr);
28 ASSERT(key2 != nullptr);
29 ASSERT(iv != nullptr);
30 ASSERT(key_size == KeySize);
31 ASSERT(iv_size == IvSize);
32 ASSERT(Common::IsAligned(m_block_size, AesBlockSize));
33
34 std::memcpy(m_key.data() + 0, key1, KeySize);
35 std::memcpy(m_key.data() + 0x10, key2, KeySize);
36 std::memcpy(m_iv.data(), iv, IvSize);
37
38 m_cipher.emplace(m_key, Core::Crypto::Mode::XTS);
39}
40
41size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const {
42 // Allow zero-size reads.
43 if (size == 0) {
44 return size;
45 }
46
47 // Ensure buffer is valid.
48 ASSERT(buffer != nullptr);
49
50 // We can only read at block aligned offsets.
51 ASSERT(Common::IsAligned(offset, AesBlockSize));
52 ASSERT(Common::IsAligned(size, AesBlockSize));
53
54 // Read the data.
55 m_base_storage->Read(buffer, size, offset);
56
57 // Setup the counter.
58 std::array<u8, IvSize> ctr;
59 std::memcpy(ctr.data(), m_iv.data(), IvSize);
60 AddCounter(ctr.data(), IvSize, offset / m_block_size);
61
62 // Handle any unaligned data before the start.
63 size_t processed_size = 0;
64 if ((offset % m_block_size) != 0) {
65 // Determine the size of the pre-data read.
66 const size_t skip_size =
67 static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size));
68 const size_t data_size = std::min(size, m_block_size - skip_size);
69
70 // Decrypt into a pooled buffer.
71 {
72 PooledBuffer tmp_buf(m_block_size, m_block_size);
73 ASSERT(tmp_buf.GetSize() >= m_block_size);
74
75 std::memset(tmp_buf.GetBuffer(), 0, skip_size);
76 std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
77
78 m_cipher->SetIV(ctr);
79 m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(),
80 Core::Crypto::Op::Decrypt);
81
82 std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size);
83 }
84
85 AddCounter(ctr.data(), IvSize, 1);
86 processed_size += data_size;
87 ASSERT(processed_size == std::min(size, m_block_size - skip_size));
88 }
89
90 // Decrypt aligned chunks.
91 char* cur = reinterpret_cast<char*>(buffer) + processed_size;
92 size_t remaining = size - processed_size;
93 while (remaining > 0) {
94 const size_t cur_size = std::min(m_block_size, remaining);
95
96 m_cipher->SetIV(ctr);
97 m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt);
98
99 remaining -= cur_size;
100 cur += cur_size;
101
102 AddCounter(ctr.data(), IvSize, 1);
103 }
104
105 return size;
106}
107
108size_t AesXtsStorage::GetSize() const {
109 return m_base_storage->GetSize();
110}
111
112} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h
new file mode 100644
index 000000000..f342efb57
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h
@@ -0,0 +1,42 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <optional>
7
8#include "core/crypto/aes_util.h"
9#include "core/crypto/key_manager.h"
10#include "core/file_sys/fssystem/fs_i_storage.h"
11
12namespace FileSys {
13
14class AesXtsStorage : public IReadOnlyStorage {
15 YUZU_NON_COPYABLE(AesXtsStorage);
16 YUZU_NON_MOVEABLE(AesXtsStorage);
17
18public:
19 static constexpr size_t AesBlockSize = 0x10;
20 static constexpr size_t KeySize = 0x20;
21 static constexpr size_t IvSize = 0x10;
22
23public:
24 static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size);
25
26public:
27 AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
28 const void* iv, size_t iv_size, size_t block_size);
29
30 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
31 virtual size_t GetSize() const override;
32
33private:
34 VirtualFile m_base_storage;
35 std::array<u8, KeySize> m_key;
36 std::array<u8, IvSize> m_iv;
37 const size_t m_block_size;
38 std::mutex m_mutex;
39 mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher;
40};
41
42} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h
new file mode 100644
index 000000000..f96691d03
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h
@@ -0,0 +1,146 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/alignment.h"
7#include "core/file_sys/errors.h"
8#include "core/file_sys/fssystem/fs_i_storage.h"
9#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
10#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
11
12namespace FileSys {
13
14template <size_t DataAlign_, size_t BufferAlign_>
15class AlignmentMatchingStorage : public IStorage {
16 YUZU_NON_COPYABLE(AlignmentMatchingStorage);
17 YUZU_NON_MOVEABLE(AlignmentMatchingStorage);
18
19public:
20 static constexpr size_t DataAlign = DataAlign_;
21 static constexpr size_t BufferAlign = BufferAlign_;
22
23 static constexpr size_t DataAlignMax = 0x200;
24 static_assert(DataAlign <= DataAlignMax);
25 static_assert(Common::IsPowerOfTwo(DataAlign));
26 static_assert(Common::IsPowerOfTwo(BufferAlign));
27
28private:
29 VirtualFile m_base_storage;
30 s64 m_base_storage_size;
31
32public:
33 explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {}
34
35 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
36 // Allocate a work buffer on stack.
37 alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
38
39 // Succeed if zero size.
40 if (size == 0) {
41 return size;
42 }
43
44 // Validate arguments.
45 ASSERT(buffer != nullptr);
46
47 s64 bs_size = this->GetSize();
48 ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
49
50 return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(),
51 DataAlign, BufferAlign, offset, buffer, size);
52 }
53
54 virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
55 // Allocate a work buffer on stack.
56 alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
57
58 // Succeed if zero size.
59 if (size == 0) {
60 return size;
61 }
62
63 // Validate arguments.
64 ASSERT(buffer != nullptr);
65
66 s64 bs_size = this->GetSize();
67 ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
68
69 return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(),
70 DataAlign, BufferAlign, offset, buffer, size);
71 }
72
73 virtual size_t GetSize() const override {
74 return m_base_storage->GetSize();
75 }
76};
77
78template <size_t BufferAlign_>
79class AlignmentMatchingStoragePooledBuffer : public IStorage {
80 YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer);
81 YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer);
82
83public:
84 static constexpr size_t BufferAlign = BufferAlign_;
85
86 static_assert(Common::IsPowerOfTwo(BufferAlign));
87
88private:
89 VirtualFile m_base_storage;
90 s64 m_base_storage_size;
91 size_t m_data_align;
92
93public:
94 explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da)
95 : m_base_storage(std::move(bs)), m_data_align(da) {
96 ASSERT(Common::IsPowerOfTwo(da));
97 }
98
99 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
100 // Succeed if zero size.
101 if (size == 0) {
102 return size;
103 }
104
105 // Validate arguments.
106 ASSERT(buffer != nullptr);
107
108 s64 bs_size = this->GetSize();
109 ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
110
111 // Allocate a pooled buffer.
112 PooledBuffer pooled_buffer;
113 pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
114
115 return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(),
116 pooled_buffer.GetSize(), m_data_align,
117 BufferAlign, offset, buffer, size);
118 }
119
120 virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
121 // Succeed if zero size.
122 if (size == 0) {
123 return size;
124 }
125
126 // Validate arguments.
127 ASSERT(buffer != nullptr);
128
129 s64 bs_size = this->GetSize();
130 ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
131
132 // Allocate a pooled buffer.
133 PooledBuffer pooled_buffer;
134 pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
135
136 return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(),
137 pooled_buffer.GetSize(), m_data_align,
138 BufferAlign, offset, buffer, size);
139 }
140
141 virtual size_t GetSize() const override {
142 return m_base_storage->GetSize();
143 }
144};
145
146} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp
new file mode 100644
index 000000000..641c888ae
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp
@@ -0,0 +1,204 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
6
7namespace FileSys {
8
9namespace {
10
11template <typename T>
12constexpr size_t GetRoundDownDifference(T x, size_t align) {
13 return static_cast<size_t>(x - Common::AlignDown(x, align));
14}
15
16template <typename T>
17constexpr size_t GetRoundUpDifference(T x, size_t align) {
18 return static_cast<size_t>(Common::AlignUp(x, align) - x);
19}
20
21template <typename T>
22size_t GetRoundUpDifference(T* x, size_t align) {
23 return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align);
24}
25
26} // namespace
27
28size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf,
29 size_t work_buf_size, size_t data_alignment,
30 size_t buffer_alignment, s64 offset, u8* buffer,
31 size_t size) {
32 // Check preconditions.
33 ASSERT(work_buf_size >= data_alignment);
34
35 // Succeed if zero size.
36 if (size == 0) {
37 return size;
38 }
39
40 // Validate arguments.
41 ASSERT(buffer != nullptr);
42
43 // Determine extents.
44 u8* aligned_core_buffer;
45 s64 core_offset;
46 size_t core_size;
47 size_t buffer_gap;
48 size_t offset_gap;
49 s64 covered_offset;
50
51 const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
52 if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
53 buffer_alignment)) {
54 aligned_core_buffer = buffer + offset_round_up_difference;
55
56 core_offset = Common::AlignUp(offset, data_alignment);
57 core_size = (size < offset_round_up_difference)
58 ? 0
59 : Common::AlignDown(size - offset_round_up_difference, data_alignment);
60 buffer_gap = 0;
61 offset_gap = 0;
62
63 covered_offset = core_size > 0 ? core_offset : offset;
64 } else {
65 const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment);
66
67 aligned_core_buffer = buffer + buffer_round_up_difference;
68
69 core_offset = Common::AlignDown(offset, data_alignment);
70 core_size = (size < buffer_round_up_difference)
71 ? 0
72 : Common::AlignDown(size - buffer_round_up_difference, data_alignment);
73 buffer_gap = buffer_round_up_difference;
74 offset_gap = GetRoundDownDifference(offset, data_alignment);
75
76 covered_offset = offset;
77 }
78
79 // Read the core portion.
80 if (core_size > 0) {
81 base_storage->Read(aligned_core_buffer, core_size, core_offset);
82
83 if (offset_gap != 0 || buffer_gap != 0) {
84 std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap,
85 core_size - offset_gap);
86 core_size -= offset_gap;
87 }
88 }
89
90 // Handle the head portion.
91 if (offset < covered_offset) {
92 const s64 head_offset = Common::AlignDown(offset, data_alignment);
93 const size_t head_size = static_cast<size_t>(covered_offset - offset);
94
95 ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size);
96
97 base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
98 std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size);
99 }
100
101 // Handle the tail portion.
102 s64 tail_offset = covered_offset + core_size;
103 size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
104 while (remaining_tail_size > 0) {
105 const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
106 const auto cur_size =
107 std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
108 remaining_tail_size);
109 base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
110
111 ASSERT((tail_offset - offset) + cur_size <= size);
112 ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment);
113 std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset),
114 work_buf + (tail_offset - aligned_tail_offset), cur_size);
115
116 remaining_tail_size -= cur_size;
117 tail_offset += cur_size;
118 }
119
120 return size;
121}
122
123size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf,
124 size_t work_buf_size, size_t data_alignment,
125 size_t buffer_alignment, s64 offset, const u8* buffer,
126 size_t size) {
127 // Check preconditions.
128 ASSERT(work_buf_size >= data_alignment);
129
130 // Succeed if zero size.
131 if (size == 0) {
132 return size;
133 }
134
135 // Validate arguments.
136 ASSERT(buffer != nullptr);
137
138 // Determine extents.
139 const u8* aligned_core_buffer;
140 s64 core_offset;
141 size_t core_size;
142 s64 covered_offset;
143
144 const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
145 if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
146 buffer_alignment)) {
147 aligned_core_buffer = buffer + offset_round_up_difference;
148
149 core_offset = Common::AlignUp(offset, data_alignment);
150 core_size = (size < offset_round_up_difference)
151 ? 0
152 : Common::AlignDown(size - offset_round_up_difference, data_alignment);
153
154 covered_offset = core_size > 0 ? core_offset : offset;
155 } else {
156 aligned_core_buffer = nullptr;
157
158 core_offset = Common::AlignDown(offset, data_alignment);
159 core_size = 0;
160
161 covered_offset = offset;
162 }
163
164 // Write the core portion.
165 if (core_size > 0) {
166 base_storage->Write(aligned_core_buffer, core_size, core_offset);
167 }
168
169 // Handle the head portion.
170 if (offset < covered_offset) {
171 const s64 head_offset = Common::AlignDown(offset, data_alignment);
172 const size_t head_size = static_cast<size_t>(covered_offset - offset);
173
174 ASSERT((offset - head_offset) + head_size <= data_alignment);
175
176 base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
177 std::memcpy(work_buf + (offset - head_offset), buffer, head_size);
178 base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
179 }
180
181 // Handle the tail portion.
182 s64 tail_offset = covered_offset + core_size;
183 size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
184 while (remaining_tail_size > 0) {
185 ASSERT(static_cast<size_t>(tail_offset - offset) < size);
186
187 const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
188 const auto cur_size =
189 std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
190 remaining_tail_size);
191
192 base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
193 std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment),
194 buffer + (tail_offset - offset), cur_size);
195 base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
196
197 remaining_tail_size -= cur_size;
198 tail_offset += cur_size;
199 }
200
201 return size;
202}
203
204} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h
new file mode 100644
index 000000000..4a05b0e88
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h
@@ -0,0 +1,21 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/errors.h"
7#include "core/file_sys/fssystem/fs_i_storage.h"
8
9namespace FileSys {
10
11class AlignmentMatchingStorageImpl {
12public:
13 static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
14 size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer,
15 size_t size);
16 static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
17 size_t data_alignment, size_t buffer_alignment, s64 offset,
18 const u8* buffer, size_t size);
19};
20
21} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
new file mode 100644
index 000000000..af8541009
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
@@ -0,0 +1,598 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/errors.h"
5#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
6#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
7#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
8
9namespace FileSys {
10
11namespace {
12
13using Node = impl::BucketTreeNode<const s64*>;
14static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader));
15static_assert(std::is_trivial_v<Node>);
16
17constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader);
18
19class StorageNode {
20private:
21 class Offset {
22 public:
23 using difference_type = s64;
24
25 private:
26 s64 m_offset;
27 s32 m_stride;
28
29 public:
30 constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {}
31
32 constexpr Offset& operator++() {
33 m_offset += m_stride;
34 return *this;
35 }
36 constexpr Offset operator++(int) {
37 Offset ret(*this);
38 m_offset += m_stride;
39 return ret;
40 }
41
42 constexpr Offset& operator--() {
43 m_offset -= m_stride;
44 return *this;
45 }
46 constexpr Offset operator--(int) {
47 Offset ret(*this);
48 m_offset -= m_stride;
49 return ret;
50 }
51
52 constexpr difference_type operator-(const Offset& rhs) const {
53 return (m_offset - rhs.m_offset) / m_stride;
54 }
55
56 constexpr Offset operator+(difference_type ofs) const {
57 return Offset(m_offset + ofs * m_stride, m_stride);
58 }
59 constexpr Offset operator-(difference_type ofs) const {
60 return Offset(m_offset - ofs * m_stride, m_stride);
61 }
62
63 constexpr Offset& operator+=(difference_type ofs) {
64 m_offset += ofs * m_stride;
65 return *this;
66 }
67 constexpr Offset& operator-=(difference_type ofs) {
68 m_offset -= ofs * m_stride;
69 return *this;
70 }
71
72 constexpr bool operator==(const Offset& rhs) const {
73 return m_offset == rhs.m_offset;
74 }
75 constexpr bool operator!=(const Offset& rhs) const {
76 return m_offset != rhs.m_offset;
77 }
78
79 constexpr s64 Get() const {
80 return m_offset;
81 }
82 };
83
84private:
85 const Offset m_start;
86 const s32 m_count;
87 s32 m_index;
88
89public:
90 StorageNode(size_t size, s32 count)
91 : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {}
92 StorageNode(s64 ofs, size_t size, s32 count)
93 : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {}
94
95 s32 GetIndex() const {
96 return m_index;
97 }
98
99 void Find(const char* buffer, s64 virtual_address) {
100 s32 end = m_count;
101 auto pos = m_start;
102
103 while (end > 0) {
104 auto half = end / 2;
105 auto mid = pos + half;
106
107 s64 offset = 0;
108 std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64));
109
110 if (offset <= virtual_address) {
111 pos = mid + 1;
112 end -= half + 1;
113 } else {
114 end = half;
115 }
116 }
117
118 m_index = static_cast<s32>(pos - m_start) - 1;
119 }
120
121 Result Find(VirtualFile storage, s64 virtual_address) {
122 s32 end = m_count;
123 auto pos = m_start;
124
125 while (end > 0) {
126 auto half = end / 2;
127 auto mid = pos + half;
128
129 s64 offset = 0;
130 storage->ReadObject(std::addressof(offset), mid.Get());
131
132 if (offset <= virtual_address) {
133 pos = mid + 1;
134 end -= half + 1;
135 } else {
136 end = half;
137 }
138 }
139
140 m_index = static_cast<s32>(pos - m_start) - 1;
141 R_SUCCEED();
142 }
143};
144
145} // namespace
146
147void BucketTree::Header::Format(s32 entry_count_) {
148 ASSERT(entry_count_ >= 0);
149
150 this->magic = Magic;
151 this->version = Version;
152 this->entry_count = entry_count_;
153 this->reserved = 0;
154}
155
156Result BucketTree::Header::Verify() const {
157 R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature);
158 R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount);
159 R_UNLESS(this->version <= Version, ResultUnsupportedVersion);
160 R_SUCCEED();
161}
162
163Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const {
164 R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex);
165 R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize);
166
167 const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size;
168 R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count,
169 ResultInvalidBucketTreeNodeEntryCount);
170 R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset);
171
172 R_SUCCEED();
173}
174
175Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
176 size_t entry_size, s32 entry_count) {
177 // Validate preconditions.
178 ASSERT(entry_size >= sizeof(s64));
179 ASSERT(node_size >= entry_size + sizeof(NodeHeader));
180 ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
181 ASSERT(Common::IsPowerOfTwo(node_size));
182 ASSERT(!this->IsInitialized());
183
184 // Ensure valid entry count.
185 R_UNLESS(entry_count > 0, ResultInvalidArgument);
186
187 // Allocate node.
188 R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed);
189 ON_RESULT_FAILURE {
190 m_node_l1.Free(node_size);
191 };
192
193 // Read node.
194 node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size);
195
196 // Verify node.
197 R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64)));
198
199 // Validate offsets.
200 const auto offset_count = GetOffsetCount(node_size);
201 const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
202 const auto* const node = m_node_l1.Get<Node>();
203
204 s64 start_offset;
205 if (offset_count < entry_set_count && node->GetCount() < offset_count) {
206 start_offset = *node->GetEnd();
207 } else {
208 start_offset = *node->GetBegin();
209 }
210 const auto end_offset = node->GetEndOffset();
211
212 R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
213 ResultInvalidBucketTreeEntryOffset);
214 R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
215
216 // Set member variables.
217 m_node_storage = node_storage;
218 m_entry_storage = entry_storage;
219 m_node_size = node_size;
220 m_entry_size = entry_size;
221 m_entry_count = entry_count;
222 m_offset_count = offset_count;
223 m_entry_set_count = entry_set_count;
224
225 m_offset_cache.offsets.start_offset = start_offset;
226 m_offset_cache.offsets.end_offset = end_offset;
227 m_offset_cache.is_initialized = true;
228
229 // We succeeded.
230 R_SUCCEED();
231}
232
233void BucketTree::Initialize(size_t node_size, s64 end_offset) {
234 ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
235 ASSERT(Common::IsPowerOfTwo(node_size));
236 ASSERT(end_offset > 0);
237 ASSERT(!this->IsInitialized());
238
239 m_node_size = node_size;
240
241 m_offset_cache.offsets.start_offset = 0;
242 m_offset_cache.offsets.end_offset = end_offset;
243 m_offset_cache.is_initialized = true;
244}
245
246void BucketTree::Finalize() {
247 if (this->IsInitialized()) {
248 m_node_storage = VirtualFile();
249 m_entry_storage = VirtualFile();
250 m_node_l1.Free(m_node_size);
251 m_node_size = 0;
252 m_entry_size = 0;
253 m_entry_count = 0;
254 m_offset_count = 0;
255 m_entry_set_count = 0;
256
257 m_offset_cache.offsets.start_offset = 0;
258 m_offset_cache.offsets.end_offset = 0;
259 m_offset_cache.is_initialized = false;
260 }
261}
262
263Result BucketTree::Find(Visitor* visitor, s64 virtual_address) {
264 ASSERT(visitor != nullptr);
265 ASSERT(this->IsInitialized());
266
267 R_UNLESS(virtual_address >= 0, ResultInvalidOffset);
268 R_UNLESS(!this->IsEmpty(), ResultOutOfRange);
269
270 BucketTree::Offsets offsets;
271 R_TRY(this->GetOffsets(std::addressof(offsets)));
272
273 R_TRY(visitor->Initialize(this, offsets));
274
275 R_RETURN(visitor->Find(virtual_address));
276}
277
278Result BucketTree::InvalidateCache() {
279 // Reset our offsets.
280 m_offset_cache.is_initialized = false;
281
282 R_SUCCEED();
283}
284
285Result BucketTree::EnsureOffsetCache() {
286 // If we already have an offset cache, we're good.
287 R_SUCCEED_IF(m_offset_cache.is_initialized);
288
289 // Acquire exclusive right to edit the offset cache.
290 std::scoped_lock lk(m_offset_cache.mutex);
291
292 // Check again, to be sure.
293 R_SUCCEED_IF(m_offset_cache.is_initialized);
294
295 // Read/verify L1.
296 m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size);
297 R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64)));
298
299 // Get the node.
300 auto* const node = m_node_l1.Get<Node>();
301
302 s64 start_offset;
303 if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) {
304 start_offset = *node->GetEnd();
305 } else {
306 start_offset = *node->GetBegin();
307 }
308 const auto end_offset = node->GetEndOffset();
309
310 R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
311 ResultInvalidBucketTreeEntryOffset);
312 R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
313
314 m_offset_cache.offsets.start_offset = start_offset;
315 m_offset_cache.offsets.end_offset = end_offset;
316 m_offset_cache.is_initialized = true;
317
318 R_SUCCEED();
319}
320
321Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) {
322 ASSERT(tree != nullptr);
323 ASSERT(m_tree == nullptr || m_tree == tree);
324
325 if (m_entry == nullptr) {
326 m_entry = ::operator new(tree->m_entry_size);
327 R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed);
328
329 m_tree = tree;
330 m_offsets = offsets;
331 }
332
333 R_SUCCEED();
334}
335
336Result BucketTree::Visitor::MoveNext() {
337 R_UNLESS(this->IsValid(), ResultOutOfRange);
338
339 // Invalidate our index, and read the header for the next index.
340 auto entry_index = m_entry_index + 1;
341 if (entry_index == m_entry_set.info.count) {
342 const auto entry_set_index = m_entry_set.info.index + 1;
343 R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange);
344
345 m_entry_index = -1;
346
347 const auto end = m_entry_set.info.end;
348
349 const auto entry_set_size = m_tree->m_node_size;
350 const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
351
352 m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
353 R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
354
355 R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end,
356 ResultInvalidBucketTreeEntrySetOffset);
357
358 entry_index = 0;
359 } else {
360 m_entry_index = -1;
361 }
362
363 // Read the new entry.
364 const auto entry_size = m_tree->m_entry_size;
365 const auto entry_offset = impl::GetBucketTreeEntryOffset(
366 m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
367 m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
368
369 // Note that we changed index.
370 m_entry_index = entry_index;
371 R_SUCCEED();
372}
373
374Result BucketTree::Visitor::MovePrevious() {
375 R_UNLESS(this->IsValid(), ResultOutOfRange);
376
377 // Invalidate our index, and read the header for the previous index.
378 auto entry_index = m_entry_index;
379 if (entry_index == 0) {
380 R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange);
381
382 m_entry_index = -1;
383
384 const auto start = m_entry_set.info.start;
385
386 const auto entry_set_size = m_tree->m_node_size;
387 const auto entry_set_index = m_entry_set.info.index - 1;
388 const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
389
390 m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
391 R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
392
393 R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end,
394 ResultInvalidBucketTreeEntrySetOffset);
395
396 entry_index = m_entry_set.info.count;
397 } else {
398 m_entry_index = -1;
399 }
400
401 --entry_index;
402
403 // Read the new entry.
404 const auto entry_size = m_tree->m_entry_size;
405 const auto entry_offset = impl::GetBucketTreeEntryOffset(
406 m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
407 m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
408
409 // Note that we changed index.
410 m_entry_index = entry_index;
411 R_SUCCEED();
412}
413
414Result BucketTree::Visitor::Find(s64 virtual_address) {
415 ASSERT(m_tree != nullptr);
416
417 // Get the node.
418 const auto* const node = m_tree->m_node_l1.Get<Node>();
419 R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange);
420
421 // Get the entry set index.
422 s32 entry_set_index = -1;
423 if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) {
424 const auto start = node->GetEnd();
425 const auto end = node->GetBegin() + m_tree->m_offset_count;
426
427 auto pos = std::upper_bound(start, end, virtual_address);
428 R_UNLESS(start < pos, ResultOutOfRange);
429 --pos;
430
431 entry_set_index = static_cast<s32>(pos - start);
432 } else {
433 const auto start = node->GetBegin();
434 const auto end = node->GetEnd();
435
436 auto pos = std::upper_bound(start, end, virtual_address);
437 R_UNLESS(start < pos, ResultOutOfRange);
438 --pos;
439
440 if (m_tree->IsExistL2()) {
441 const auto node_index = static_cast<s32>(pos - start);
442 R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count,
443 ResultInvalidBucketTreeNodeOffset);
444
445 R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index));
446 } else {
447 entry_set_index = static_cast<s32>(pos - start);
448 }
449 }
450
451 // Validate the entry set index.
452 R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count,
453 ResultInvalidBucketTreeNodeOffset);
454
455 // Find the entry.
456 R_TRY(this->FindEntry(virtual_address, entry_set_index));
457
458 // Set count.
459 m_entry_set_count = m_tree->m_entry_set_count;
460 R_SUCCEED();
461}
462
463Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) {
464 const auto node_size = m_tree->m_node_size;
465
466 PooledBuffer pool(node_size, 1);
467 if (node_size <= pool.GetSize()) {
468 R_RETURN(
469 this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer()));
470 } else {
471 pool.Deallocate();
472 R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index));
473 }
474}
475
476Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address,
477 s32 node_index, char* buffer) {
478 // Calculate node extents.
479 const auto node_size = m_tree->m_node_size;
480 const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
481 VirtualFile storage = m_tree->m_node_storage;
482
483 // Read the node.
484 storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset);
485
486 // Validate the header.
487 NodeHeader header;
488 std::memcpy(std::addressof(header), buffer, NodeHeaderSize);
489 R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
490
491 // Create the node, and find.
492 StorageNode node(sizeof(s64), header.count);
493 node.Find(buffer, virtual_address);
494 R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset);
495
496 // Return the index.
497 *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
498 R_SUCCEED();
499}
500
501Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address,
502 s32 node_index) {
503 // Calculate node extents.
504 const auto node_size = m_tree->m_node_size;
505 const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
506 VirtualFile storage = m_tree->m_node_storage;
507
508 // Read and validate the header.
509 NodeHeader header;
510 storage->ReadObject(std::addressof(header), node_offset);
511 R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
512
513 // Create the node, and find.
514 StorageNode node(node_offset, sizeof(s64), header.count);
515 R_TRY(node.Find(storage, virtual_address));
516 R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
517
518 // Return the index.
519 *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
520 R_SUCCEED();
521}
522
523Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) {
524 const auto entry_set_size = m_tree->m_node_size;
525
526 PooledBuffer pool(entry_set_size, 1);
527 if (entry_set_size <= pool.GetSize()) {
528 R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer()));
529 } else {
530 pool.Deallocate();
531 R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index));
532 }
533}
534
535Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index,
536 char* buffer) {
537 // Calculate entry set extents.
538 const auto entry_size = m_tree->m_entry_size;
539 const auto entry_set_size = m_tree->m_node_size;
540 const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
541 VirtualFile storage = m_tree->m_entry_storage;
542
543 // Read the entry set.
544 storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset);
545
546 // Validate the entry_set.
547 EntrySetHeader entry_set;
548 std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader));
549 R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
550
551 // Create the node, and find.
552 StorageNode node(entry_size, entry_set.info.count);
553 node.Find(buffer, virtual_address);
554 R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
555
556 // Copy the data into entry.
557 const auto entry_index = node.GetIndex();
558 const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index);
559 std::memcpy(m_entry, buffer + entry_offset, entry_size);
560
561 // Set our entry set/index.
562 m_entry_set = entry_set;
563 m_entry_index = entry_index;
564
565 R_SUCCEED();
566}
567
568Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) {
569 // Calculate entry set extents.
570 const auto entry_size = m_tree->m_entry_size;
571 const auto entry_set_size = m_tree->m_node_size;
572 const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
573 VirtualFile storage = m_tree->m_entry_storage;
574
575 // Read and validate the entry_set.
576 EntrySetHeader entry_set;
577 storage->ReadObject(std::addressof(entry_set), entry_set_offset);
578 R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
579
580 // Create the node, and find.
581 StorageNode node(entry_set_offset, entry_size, entry_set.info.count);
582 R_TRY(node.Find(storage, virtual_address));
583 R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
584
585 // Copy the data into entry.
586 const auto entry_index = node.GetIndex();
587 const auto entry_offset =
588 impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index);
589 storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
590
591 // Set our entry set/index.
592 m_entry_set = entry_set;
593 m_entry_index = entry_index;
594
595 R_SUCCEED();
596}
597
598} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h
new file mode 100644
index 000000000..46850cd48
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h
@@ -0,0 +1,416 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7
8#include "common/alignment.h"
9#include "common/common_funcs.h"
10#include "common/common_types.h"
11#include "common/literals.h"
12
13#include "core/file_sys/vfs.h"
14#include "core/hle/result.h"
15
16namespace FileSys {
17
18using namespace Common::Literals;
19
20class BucketTree {
21 YUZU_NON_COPYABLE(BucketTree);
22 YUZU_NON_MOVEABLE(BucketTree);
23
24public:
25 static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R');
26 static constexpr u32 Version = 1;
27
28 static constexpr size_t NodeSizeMin = 1_KiB;
29 static constexpr size_t NodeSizeMax = 512_KiB;
30
31public:
32 class Visitor;
33
34 struct Header {
35 u32 magic;
36 u32 version;
37 s32 entry_count;
38 s32 reserved;
39
40 void Format(s32 entry_count);
41 Result Verify() const;
42 };
43 static_assert(std::is_trivial_v<Header>);
44 static_assert(sizeof(Header) == 0x10);
45
46 struct NodeHeader {
47 s32 index;
48 s32 count;
49 s64 offset;
50
51 Result Verify(s32 node_index, size_t node_size, size_t entry_size) const;
52 };
53 static_assert(std::is_trivial_v<NodeHeader>);
54 static_assert(sizeof(NodeHeader) == 0x10);
55
56 struct Offsets {
57 s64 start_offset;
58 s64 end_offset;
59
60 constexpr bool IsInclude(s64 offset) const {
61 return this->start_offset <= offset && offset < this->end_offset;
62 }
63
64 constexpr bool IsInclude(s64 offset, s64 size) const {
65 return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset);
66 }
67 };
68 static_assert(std::is_trivial_v<Offsets>);
69 static_assert(sizeof(Offsets) == 0x10);
70
71 struct OffsetCache {
72 Offsets offsets;
73 std::mutex mutex;
74 bool is_initialized;
75
76 OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {}
77 };
78
79 class ContinuousReadingInfo {
80 public:
81 constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {}
82
83 constexpr void Reset() {
84 m_read_size = 0;
85 m_skip_count = 0;
86 m_done = false;
87 }
88
89 constexpr void SetSkipCount(s32 count) {
90 ASSERT(count >= 0);
91 m_skip_count = count;
92 }
93 constexpr s32 GetSkipCount() const {
94 return m_skip_count;
95 }
96 constexpr bool CheckNeedScan() {
97 return (--m_skip_count) <= 0;
98 }
99
100 constexpr void Done() {
101 m_read_size = 0;
102 m_done = true;
103 }
104 constexpr bool IsDone() const {
105 return m_done;
106 }
107
108 constexpr void SetReadSize(size_t size) {
109 m_read_size = size;
110 }
111 constexpr size_t GetReadSize() const {
112 return m_read_size;
113 }
114 constexpr bool CanDo() const {
115 return m_read_size > 0;
116 }
117
118 private:
119 size_t m_read_size;
120 s32 m_skip_count;
121 bool m_done;
122 };
123
124private:
125 class NodeBuffer {
126 YUZU_NON_COPYABLE(NodeBuffer);
127
128 public:
129 NodeBuffer() : m_header() {}
130
131 ~NodeBuffer() {
132 ASSERT(m_header == nullptr);
133 }
134
135 NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) {
136 rhs.m_header = nullptr;
137 }
138
139 NodeBuffer& operator=(NodeBuffer&& rhs) {
140 if (this != std::addressof(rhs)) {
141 ASSERT(m_header == nullptr);
142
143 m_header = rhs.m_header;
144
145 rhs.m_header = nullptr;
146 }
147 return *this;
148 }
149
150 bool Allocate(size_t node_size) {
151 ASSERT(m_header == nullptr);
152
153 m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)});
154
155 // ASSERT(Common::IsAligned(m_header, sizeof(s64)));
156
157 return m_header != nullptr;
158 }
159
160 void Free(size_t node_size) {
161 if (m_header) {
162 ::operator delete(m_header, std::align_val_t{sizeof(s64)});
163 m_header = nullptr;
164 }
165 }
166
167 void FillZero(size_t node_size) const {
168 if (m_header) {
169 std::memset(m_header, 0, node_size);
170 }
171 }
172
173 NodeHeader* Get() const {
174 return reinterpret_cast<NodeHeader*>(m_header);
175 }
176
177 NodeHeader* operator->() const {
178 return this->Get();
179 }
180
181 template <typename T>
182 T* Get() const {
183 static_assert(std::is_trivial_v<T>);
184 static_assert(sizeof(T) == sizeof(NodeHeader));
185 return reinterpret_cast<T*>(m_header);
186 }
187
188 private:
189 void* m_header;
190 };
191
192private:
193 static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) {
194 return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size);
195 }
196
197 static constexpr s32 GetOffsetCount(size_t node_size) {
198 return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64));
199 }
200
201 static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) {
202 const s32 entry_count_per_node = GetEntryCount(node_size, entry_size);
203 return Common::DivideUp(entry_count, entry_count_per_node);
204 }
205
206 static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) {
207 const s32 offset_count_per_node = GetOffsetCount(node_size);
208 const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
209
210 if (entry_set_count <= offset_count_per_node) {
211 return 0;
212 }
213
214 const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node);
215 ASSERT(node_l2_count <= offset_count_per_node);
216
217 return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)),
218 offset_count_per_node);
219 }
220
221public:
222 BucketTree()
223 : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(),
224 m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {}
225 ~BucketTree() {
226 this->Finalize();
227 }
228
229 Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
230 size_t entry_size, s32 entry_count);
231 void Initialize(size_t node_size, s64 end_offset);
232 void Finalize();
233
234 bool IsInitialized() const {
235 return m_node_size > 0;
236 }
237 bool IsEmpty() const {
238 return m_entry_size == 0;
239 }
240
241 Result Find(Visitor* visitor, s64 virtual_address);
242 Result InvalidateCache();
243
244 s32 GetEntryCount() const {
245 return m_entry_count;
246 }
247
248 Result GetOffsets(Offsets* out) {
249 // Ensure we have an offset cache.
250 R_TRY(this->EnsureOffsetCache());
251
252 // Set the output.
253 *out = m_offset_cache.offsets;
254 R_SUCCEED();
255 }
256
257public:
258 static constexpr s64 QueryHeaderStorageSize() {
259 return sizeof(Header);
260 }
261
262 static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size,
263 s32 entry_count) {
264 ASSERT(entry_size >= sizeof(s64));
265 ASSERT(node_size >= entry_size + sizeof(NodeHeader));
266 ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
267 ASSERT(Common::IsPowerOfTwo(node_size));
268 ASSERT(entry_count >= 0);
269
270 if (entry_count <= 0) {
271 return 0;
272 }
273 return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) *
274 static_cast<s64>(node_size);
275 }
276
277 static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size,
278 s32 entry_count) {
279 ASSERT(entry_size >= sizeof(s64));
280 ASSERT(node_size >= entry_size + sizeof(NodeHeader));
281 ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
282 ASSERT(Common::IsPowerOfTwo(node_size));
283 ASSERT(entry_count >= 0);
284
285 if (entry_count <= 0) {
286 return 0;
287 }
288 return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size);
289 }
290
291private:
292 template <typename EntryType>
293 struct ContinuousReadingParam {
294 s64 offset;
295 size_t size;
296 NodeHeader entry_set;
297 s32 entry_index;
298 Offsets offsets;
299 EntryType entry;
300 };
301
302private:
303 template <typename EntryType>
304 Result ScanContinuousReading(ContinuousReadingInfo* out_info,
305 const ContinuousReadingParam<EntryType>& param) const;
306
307 bool IsExistL2() const {
308 return m_offset_count < m_entry_set_count;
309 }
310 bool IsExistOffsetL2OnL1() const {
311 return this->IsExistL2() && m_node_l1->count < m_offset_count;
312 }
313
314 s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const {
315 return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index;
316 }
317
318 Result EnsureOffsetCache();
319
320private:
321 mutable VirtualFile m_node_storage;
322 mutable VirtualFile m_entry_storage;
323 NodeBuffer m_node_l1;
324 size_t m_node_size;
325 size_t m_entry_size;
326 s32 m_entry_count;
327 s32 m_offset_count;
328 s32 m_entry_set_count;
329 OffsetCache m_offset_cache;
330};
331
332class BucketTree::Visitor {
333 YUZU_NON_COPYABLE(Visitor);
334 YUZU_NON_MOVEABLE(Visitor);
335
336public:
337 constexpr Visitor()
338 : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {}
339 ~Visitor() {
340 if (m_entry != nullptr) {
341 ::operator delete(m_entry, m_tree->m_entry_size);
342 m_tree = nullptr;
343 m_entry = nullptr;
344 }
345 }
346
347 bool IsValid() const {
348 return m_entry_index >= 0;
349 }
350 bool CanMoveNext() const {
351 return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count ||
352 m_entry_set.info.index + 1 < m_entry_set_count);
353 }
354 bool CanMovePrevious() const {
355 return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0);
356 }
357
358 Result MoveNext();
359 Result MovePrevious();
360
361 template <typename EntryType>
362 Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const;
363
364 const void* Get() const {
365 ASSERT(this->IsValid());
366 return m_entry;
367 }
368
369 template <typename T>
370 const T* Get() const {
371 ASSERT(this->IsValid());
372 return reinterpret_cast<const T*>(m_entry);
373 }
374
375 const BucketTree* GetTree() const {
376 return m_tree;
377 }
378
379private:
380 Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets);
381
382 Result Find(s64 virtual_address);
383
384 Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index);
385 Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index,
386 char* buffer);
387 Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index);
388
389 Result FindEntry(s64 virtual_address, s32 entry_set_index);
390 Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer);
391 Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index);
392
393private:
394 friend class BucketTree;
395
396 union EntrySetHeader {
397 NodeHeader header;
398 struct Info {
399 s32 index;
400 s32 count;
401 s64 end;
402 s64 start;
403 } info;
404 static_assert(std::is_trivial_v<Info>);
405 };
406 static_assert(std::is_trivial_v<EntrySetHeader>);
407
408 const BucketTree* m_tree;
409 BucketTree::Offsets m_offsets;
410 void* m_entry;
411 s32 m_entry_index;
412 s32 m_entry_set_count;
413 EntrySetHeader m_entry_set;
414};
415
416} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h
new file mode 100644
index 000000000..030b2916b
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h
@@ -0,0 +1,170 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/errors.h"
7#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
8#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
9#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
10
11namespace FileSys {
12
13template <typename EntryType>
14Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info,
15 const ContinuousReadingParam<EntryType>& param) const {
16 static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>);
17
18 // Validate our preconditions.
19 ASSERT(this->IsInitialized());
20 ASSERT(out_info != nullptr);
21 ASSERT(m_entry_size == sizeof(EntryType));
22
23 // Reset the output.
24 out_info->Reset();
25
26 // If there's nothing to read, we're done.
27 R_SUCCEED_IF(param.size == 0);
28
29 // If we're reading a fragment, we're done.
30 R_SUCCEED_IF(param.entry.IsFragment());
31
32 // Validate the first entry.
33 auto entry = param.entry;
34 auto cur_offset = param.offset;
35 R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange);
36
37 // Create a pooled buffer for our scan.
38 PooledBuffer pool(m_node_size, 1);
39 char* buffer = nullptr;
40
41 s64 entry_storage_size = m_entry_storage->GetSize();
42
43 // Read the node.
44 if (m_node_size <= pool.GetSize()) {
45 buffer = pool.GetBuffer();
46 const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size);
47 R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size),
48 ResultInvalidBucketTreeNodeEntryCount);
49
50 m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs);
51 }
52
53 // Calculate extents.
54 const auto end_offset = cur_offset + static_cast<s64>(param.size);
55 s64 phys_offset = entry.GetPhysicalOffset();
56
57 // Start merge tracking.
58 s64 merge_size = 0;
59 s64 readable_size = 0;
60 bool merged = false;
61
62 // Iterate.
63 auto entry_index = param.entry_index;
64 for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) {
65 // If we're past the end, we're done.
66 if (end_offset <= cur_offset) {
67 break;
68 }
69
70 // Validate the entry offset.
71 const auto entry_offset = entry.GetVirtualOffset();
72 R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
73
74 // Get the next entry.
75 EntryType next_entry = {};
76 s64 next_entry_offset;
77
78 if (entry_index + 1 < entry_count) {
79 if (buffer != nullptr) {
80 const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1);
81 std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size);
82 } else {
83 const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size,
84 m_entry_size, entry_index + 1);
85 m_entry_storage->ReadObject(std::addressof(next_entry), ofs);
86 }
87
88 next_entry_offset = next_entry.GetVirtualOffset();
89 R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
90 } else {
91 next_entry_offset = param.entry_set.offset;
92 }
93
94 // Validate the next entry offset.
95 R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
96
97 // Determine the much data there is.
98 const auto data_size = next_entry_offset - cur_offset;
99 ASSERT(data_size > 0);
100
101 // Determine how much data we should read.
102 const auto remaining_size = end_offset - cur_offset;
103 const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size));
104 ASSERT(read_size <= param.size);
105
106 // Update our merge tracking.
107 if (entry.IsFragment()) {
108 // If we can't merge, stop looping.
109 if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) {
110 break;
111 }
112
113 // Otherwise, add the current size to the merge size.
114 merge_size += read_size;
115 } else {
116 // If we can't merge, stop looping.
117 if (phys_offset != entry.GetPhysicalOffset()) {
118 break;
119 }
120
121 // Add the size to the readable amount.
122 readable_size += merge_size + read_size;
123 ASSERT(readable_size <= static_cast<s64>(param.size));
124
125 // Update whether we've merged.
126 merged |= merge_size > 0;
127 merge_size = 0;
128 }
129
130 // Advance.
131 cur_offset += read_size;
132 ASSERT(cur_offset <= end_offset);
133
134 phys_offset += next_entry_offset - entry_offset;
135 entry = next_entry;
136 }
137
138 // If we merged, set our readable size.
139 if (merged) {
140 out_info->SetReadSize(static_cast<size_t>(readable_size));
141 }
142 out_info->SetSkipCount(entry_index - param.entry_index);
143
144 R_SUCCEED();
145}
146
147template <typename EntryType>
148Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset,
149 size_t size) const {
150 static_assert(std::is_trivial_v<EntryType>);
151 ASSERT(this->IsValid());
152
153 // Create our parameters.
154 ContinuousReadingParam<EntryType> param = {
155 .offset = offset,
156 .size = size,
157 .entry_set = m_entry_set.header,
158 .entry_index = m_entry_index,
159 .offsets{},
160 .entry{},
161 };
162 std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets),
163 sizeof(BucketTree::Offsets));
164 std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType));
165
166 // Scan.
167 R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param));
168}
169
170} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h
new file mode 100644
index 000000000..5503613fc
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h
@@ -0,0 +1,110 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
7
8namespace FileSys::impl {
9
10class SafeValue {
11public:
12 static s64 GetInt64(const void* ptr) {
13 s64 value;
14 std::memcpy(std::addressof(value), ptr, sizeof(s64));
15 return value;
16 }
17
18 static s64 GetInt64(const s64* ptr) {
19 return GetInt64(static_cast<const void*>(ptr));
20 }
21
22 static s64 GetInt64(const s64& v) {
23 return GetInt64(std::addressof(v));
24 }
25
26 static void SetInt64(void* dst, const void* src) {
27 std::memcpy(dst, src, sizeof(s64));
28 }
29
30 static void SetInt64(void* dst, const s64* src) {
31 return SetInt64(dst, static_cast<const void*>(src));
32 }
33
34 static void SetInt64(void* dst, const s64& v) {
35 return SetInt64(dst, std::addressof(v));
36 }
37};
38
39template <typename IteratorType>
40struct BucketTreeNode {
41 using Header = BucketTree::NodeHeader;
42
43 Header header;
44
45 s32 GetCount() const {
46 return this->header.count;
47 }
48
49 void* GetArray() {
50 return std::addressof(this->header) + 1;
51 }
52 template <typename T>
53 T* GetArray() {
54 return reinterpret_cast<T*>(this->GetArray());
55 }
56 const void* GetArray() const {
57 return std::addressof(this->header) + 1;
58 }
59 template <typename T>
60 const T* GetArray() const {
61 return reinterpret_cast<const T*>(this->GetArray());
62 }
63
64 s64 GetBeginOffset() const {
65 return *this->GetArray<s64>();
66 }
67 s64 GetEndOffset() const {
68 return this->header.offset;
69 }
70
71 IteratorType GetBegin() {
72 return IteratorType(this->GetArray<s64>());
73 }
74 IteratorType GetEnd() {
75 return IteratorType(this->GetArray<s64>()) + this->header.count;
76 }
77 IteratorType GetBegin() const {
78 return IteratorType(this->GetArray<s64>());
79 }
80 IteratorType GetEnd() const {
81 return IteratorType(this->GetArray<s64>()) + this->header.count;
82 }
83
84 IteratorType GetBegin(size_t entry_size) {
85 return IteratorType(this->GetArray(), entry_size);
86 }
87 IteratorType GetEnd(size_t entry_size) {
88 return IteratorType(this->GetArray(), entry_size) + this->header.count;
89 }
90 IteratorType GetBegin(size_t entry_size) const {
91 return IteratorType(this->GetArray(), entry_size);
92 }
93 IteratorType GetEnd(size_t entry_size) const {
94 return IteratorType(this->GetArray(), entry_size) + this->header.count;
95 }
96};
97
98constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size,
99 s32 entry_index) {
100 return entry_set_offset + sizeof(BucketTree::NodeHeader) +
101 entry_index * static_cast<s64>(entry_size);
102}
103
104constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size,
105 size_t entry_size, s32 entry_index) {
106 return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size,
107 entry_index);
108}
109
110} // namespace FileSys::impl
diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h
new file mode 100644
index 000000000..33d93938e
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h
@@ -0,0 +1,963 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/literals.h"
7
8#include "core/file_sys/errors.h"
9#include "core/file_sys/fssystem/fs_i_storage.h"
10#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
11#include "core/file_sys/fssystem/fssystem_compression_common.h"
12#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
13#include "core/file_sys/vfs.h"
14
15namespace FileSys {
16
17using namespace Common::Literals;
18
19class CompressedStorage : public IReadOnlyStorage {
20 YUZU_NON_COPYABLE(CompressedStorage);
21 YUZU_NON_MOVEABLE(CompressedStorage);
22
23public:
24 static constexpr size_t NodeSize = 16_KiB;
25
26 struct Entry {
27 s64 virt_offset;
28 s64 phys_offset;
29 CompressionType compression_type;
30 s32 phys_size;
31
32 s64 GetPhysicalSize() const {
33 return this->phys_size;
34 }
35 };
36 static_assert(std::is_trivial_v<Entry>);
37 static_assert(sizeof(Entry) == 0x18);
38
39public:
40 static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
41 return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
42 }
43
44 static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
45 return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
46 }
47
48private:
49 class CompressedStorageCore {
50 YUZU_NON_COPYABLE(CompressedStorageCore);
51 YUZU_NON_MOVEABLE(CompressedStorageCore);
52
53 public:
54 CompressedStorageCore() : m_table(), m_data_storage() {}
55
56 ~CompressedStorageCore() {
57 this->Finalize();
58 }
59
60 public:
61 Result Initialize(VirtualFile data_storage, VirtualFile node_storage,
62 VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max,
63 size_t continuous_reading_size_max,
64 GetDecompressorFunction get_decompressor) {
65 // Check pre-conditions.
66 ASSERT(0 < block_size_max);
67 ASSERT(block_size_max <= continuous_reading_size_max);
68 ASSERT(get_decompressor != nullptr);
69
70 // Initialize our entry table.
71 R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry),
72 bktr_entry_count));
73
74 // Set our other fields.
75 m_block_size_max = block_size_max;
76 m_continuous_reading_size_max = continuous_reading_size_max;
77 m_data_storage = data_storage;
78 m_get_decompressor_function = get_decompressor;
79
80 R_SUCCEED();
81 }
82
83 void Finalize() {
84 if (this->IsInitialized()) {
85 m_table.Finalize();
86 m_data_storage = VirtualFile();
87 }
88 }
89
90 VirtualFile GetDataStorage() {
91 return m_data_storage;
92 }
93
94 Result GetDataStorageSize(s64* out) {
95 // Check pre-conditions.
96 ASSERT(out != nullptr);
97
98 // Get size.
99 *out = m_data_storage->GetSize();
100
101 R_SUCCEED();
102 }
103
104 BucketTree& GetEntryTable() {
105 return m_table;
106 }
107
108 Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count,
109 s64 offset, s64 size) {
110 // Check pre-conditions.
111 ASSERT(offset >= 0);
112 ASSERT(size >= 0);
113 ASSERT(this->IsInitialized());
114
115 // Check that we can output the count.
116 R_UNLESS(out_read_count != nullptr, ResultNullptrArgument);
117
118 // Check that we have anything to read at all.
119 R_SUCCEED_IF(size == 0);
120
121 // Check that either we have a buffer, or this is to determine how many we need.
122 if (max_entry_count != 0) {
123 R_UNLESS(out_entries != nullptr, ResultNullptrArgument);
124 }
125
126 // Get the table offsets.
127 BucketTree::Offsets table_offsets;
128 R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
129
130 // Validate arguments.
131 R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
132
133 // Find the offset in our tree.
134 BucketTree::Visitor visitor;
135 R_TRY(m_table.Find(std::addressof(visitor), offset));
136 {
137 const auto entry_offset = visitor.Get<Entry>()->virt_offset;
138 R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
139 ResultUnexpectedInCompressedStorageA);
140 }
141
142 // Get the entries.
143 const auto end_offset = offset + size;
144 s32 read_count = 0;
145 while (visitor.Get<Entry>()->virt_offset < end_offset) {
146 // If we should be setting the output, do so.
147 if (max_entry_count != 0) {
148 // Ensure we only read as many entries as we can.
149 if (read_count >= max_entry_count) {
150 break;
151 }
152
153 // Set the current output entry.
154 out_entries[read_count] = *visitor.Get<Entry>();
155 }
156
157 // Increase the read count.
158 ++read_count;
159
160 // If we're at the end, we're done.
161 if (!visitor.CanMoveNext()) {
162 break;
163 }
164
165 // Move to the next entry.
166 R_TRY(visitor.MoveNext());
167 }
168
169 // Set the output read count.
170 *out_read_count = read_count;
171 R_SUCCEED();
172 }
173
174 Result GetSize(s64* out) {
175 // Check pre-conditions.
176 ASSERT(out != nullptr);
177
178 // Get our table offsets.
179 BucketTree::Offsets offsets;
180 R_TRY(m_table.GetOffsets(std::addressof(offsets)));
181
182 // Set the output.
183 *out = offsets.end_offset;
184 R_SUCCEED();
185 }
186
187 Result OperatePerEntry(s64 offset, s64 size, auto f) {
188 // Check pre-conditions.
189 ASSERT(offset >= 0);
190 ASSERT(size >= 0);
191 ASSERT(this->IsInitialized());
192
193 // Succeed if there's nothing to operate on.
194 R_SUCCEED_IF(size == 0);
195
196 // Get the table offsets.
197 BucketTree::Offsets table_offsets;
198 R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
199
200 // Validate arguments.
201 R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
202
203 // Find the offset in our tree.
204 BucketTree::Visitor visitor;
205 R_TRY(m_table.Find(std::addressof(visitor), offset));
206 {
207 const auto entry_offset = visitor.Get<Entry>()->virt_offset;
208 R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
209 ResultUnexpectedInCompressedStorageA);
210 }
211
212 // Prepare to operate in chunks.
213 auto cur_offset = offset;
214 const auto end_offset = offset + static_cast<s64>(size);
215
216 while (cur_offset < end_offset) {
217 // Get the current entry.
218 const auto cur_entry = *visitor.Get<Entry>();
219
220 // Get and validate the entry's offset.
221 const auto cur_entry_offset = cur_entry.virt_offset;
222 R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA);
223
224 // Get and validate the next entry offset.
225 s64 next_entry_offset;
226 if (visitor.CanMoveNext()) {
227 R_TRY(visitor.MoveNext());
228 next_entry_offset = visitor.Get<Entry>()->virt_offset;
229 R_UNLESS(table_offsets.IsInclude(next_entry_offset),
230 ResultUnexpectedInCompressedStorageA);
231 } else {
232 next_entry_offset = table_offsets.end_offset;
233 }
234 R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA);
235
236 // Get the offset of the entry in the data we read.
237 const auto data_offset = cur_offset - cur_entry_offset;
238 const auto data_size = (next_entry_offset - cur_entry_offset);
239 ASSERT(data_size > 0);
240
241 // Determine how much is left.
242 const auto remaining_size = end_offset - cur_offset;
243 const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
244 ASSERT(cur_size <= size);
245
246 // Get the data storage size.
247 s64 storage_size = m_data_storage->GetSize();
248
249 // Check that our read remains naively physically in bounds.
250 R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size,
251 ResultUnexpectedInCompressedStorageC);
252
253 // If we have any compression, verify that we remain physically in bounds.
254 if (cur_entry.compression_type != CompressionType::None) {
255 R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size,
256 ResultUnexpectedInCompressedStorageC);
257 }
258
259 // Check that block alignment requirements are met.
260 if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) {
261 R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment),
262 ResultUnexpectedInCompressedStorageA);
263 }
264
265 // Invoke the operator.
266 bool is_continuous = true;
267 R_TRY(
268 f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size));
269
270 // If not continuous, we're done.
271 if (!is_continuous) {
272 break;
273 }
274
275 // Advance.
276 cur_offset += cur_size;
277 }
278
279 R_SUCCEED();
280 }
281
282 public:
283 using ReadImplFunction = std::function<Result(void*, size_t)>;
284 using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>;
285
286 public:
287 Result Read(s64 offset, s64 size, const ReadFunction& read_func) {
288 // Check pre-conditions.
289 ASSERT(offset >= 0);
290 ASSERT(this->IsInitialized());
291
292 // Succeed immediately, if we have nothing to read.
293 R_SUCCEED_IF(size == 0);
294
295 // Declare read lambda.
296 constexpr int EntriesCountMax = 0x80;
297 struct Entries {
298 CompressionType compression_type;
299 u32 gap_from_prev;
300 u32 physical_size;
301 u32 virtual_size;
302 };
303 std::array<Entries, EntriesCountMax> entries;
304 s32 entry_count = 0;
305 Entry prev_entry = {
306 .virt_offset = -1,
307 .phys_offset{},
308 .compression_type{},
309 .phys_size{},
310 };
311 bool will_allocate_pooled_buffer = false;
312 s64 required_access_physical_offset = 0;
313 s64 required_access_physical_size = 0;
314
315 auto PerformRequiredRead = [&]() -> Result {
316 // If there are no entries, we have nothing to do.
317 R_SUCCEED_IF(entry_count == 0);
318
319 // Get the remaining size in a convenient form.
320 const size_t total_required_size =
321 static_cast<size_t>(required_access_physical_size);
322
323 // Perform the read based on whether we need to allocate a buffer.
324 if (will_allocate_pooled_buffer) {
325 // Allocate a pooled buffer.
326 PooledBuffer pooled_buffer;
327 if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) {
328 pooled_buffer.Allocate(total_required_size, m_block_size_max);
329 } else {
330 pooled_buffer.AllocateParticularlyLarge(
331 std::min<size_t>(
332 total_required_size,
333 PooledBuffer::GetAllocatableParticularlyLargeSizeMax()),
334 m_block_size_max);
335 }
336
337 // Read each of the entries.
338 for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) {
339 // Determine the current read size.
340 bool will_use_pooled_buffer = false;
341 const size_t cur_read_size = [&]() -> size_t {
342 if (const size_t target_entry_size =
343 static_cast<size_t>(entries[entry_idx].physical_size) +
344 static_cast<size_t>(entries[entry_idx].gap_from_prev);
345 target_entry_size <= pooled_buffer.GetSize()) {
346 // We'll be using the pooled buffer.
347 will_use_pooled_buffer = true;
348
349 // Determine how much we can read.
350 const size_t max_size = std::min<size_t>(
351 required_access_physical_size, pooled_buffer.GetSize());
352
353 size_t read_size = 0;
354 for (auto n = entry_idx; n < entry_count; ++n) {
355 const size_t cur_entry_size =
356 static_cast<size_t>(entries[n].physical_size) +
357 static_cast<size_t>(entries[n].gap_from_prev);
358 if (read_size + cur_entry_size > max_size) {
359 break;
360 }
361
362 read_size += cur_entry_size;
363 }
364
365 return read_size;
366 } else {
367 // If we don't fit, we must be uncompressed.
368 ASSERT(entries[entry_idx].compression_type ==
369 CompressionType::None);
370
371 // We can perform the whole of an uncompressed read directly.
372 return entries[entry_idx].virtual_size;
373 }
374 }();
375
376 // Perform the read based on whether or not we'll use the pooled buffer.
377 if (will_use_pooled_buffer) {
378 // Read the compressed data into the pooled buffer.
379 auto* const buffer = pooled_buffer.GetBuffer();
380 m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size,
381 required_access_physical_offset);
382
383 // Decompress the data.
384 size_t buffer_offset;
385 for (buffer_offset = 0;
386 entry_idx < entry_count &&
387 ((static_cast<size_t>(entries[entry_idx].physical_size) +
388 static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 ||
389 buffer_offset < cur_read_size);
390 buffer_offset += entries[entry_idx++].physical_size) {
391 // Advance by the relevant gap.
392 buffer_offset += entries[entry_idx].gap_from_prev;
393
394 const auto compression_type = entries[entry_idx].compression_type;
395 switch (compression_type) {
396 case CompressionType::None: {
397 // Check that we can remain within bounds.
398 ASSERT(buffer_offset + entries[entry_idx].virtual_size <=
399 cur_read_size);
400
401 // Perform no decompression.
402 R_TRY(read_func(
403 entries[entry_idx].virtual_size,
404 [&](void* dst, size_t dst_size) -> Result {
405 // Check that the size is valid.
406 ASSERT(dst_size == entries[entry_idx].virtual_size);
407
408 // We have no compression, so just copy the data
409 // out.
410 std::memcpy(dst, buffer + buffer_offset,
411 entries[entry_idx].virtual_size);
412 R_SUCCEED();
413 }));
414
415 break;
416 }
417 case CompressionType::Zeros: {
418 // Check that we can remain within bounds.
419 ASSERT(buffer_offset <= cur_read_size);
420
421 // Zero the memory.
422 R_TRY(read_func(
423 entries[entry_idx].virtual_size,
424 [&](void* dst, size_t dst_size) -> Result {
425 // Check that the size is valid.
426 ASSERT(dst_size == entries[entry_idx].virtual_size);
427
428 // The data is zeroes, so zero the buffer.
429 std::memset(dst, 0, entries[entry_idx].virtual_size);
430 R_SUCCEED();
431 }));
432
433 break;
434 }
435 default: {
436 // Check that we can remain within bounds.
437 ASSERT(buffer_offset + entries[entry_idx].physical_size <=
438 cur_read_size);
439
440 // Get the decompressor.
441 const auto decompressor =
442 this->GetDecompressor(compression_type);
443 R_UNLESS(decompressor != nullptr,
444 ResultUnexpectedInCompressedStorageB);
445
446 // Decompress the data.
447 R_TRY(read_func(entries[entry_idx].virtual_size,
448 [&](void* dst, size_t dst_size) -> Result {
449 // Check that the size is valid.
450 ASSERT(dst_size ==
451 entries[entry_idx].virtual_size);
452
453 // Perform the decompression.
454 R_RETURN(decompressor(
455 dst, entries[entry_idx].virtual_size,
456 buffer + buffer_offset,
457 entries[entry_idx].physical_size));
458 }));
459
460 break;
461 }
462 }
463 }
464
465 // Check that we processed the correct amount of data.
466 ASSERT(buffer_offset == cur_read_size);
467 } else {
468 // Account for the gap from the previous entry.
469 required_access_physical_offset += entries[entry_idx].gap_from_prev;
470 required_access_physical_size -= entries[entry_idx].gap_from_prev;
471
472 // We don't need the buffer (as the data is uncompressed), so just
473 // execute the read.
474 R_TRY(
475 read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result {
476 // Check that the size is valid.
477 ASSERT(dst_size == cur_read_size);
478
479 // Perform the read.
480 m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size,
481 required_access_physical_offset);
482
483 R_SUCCEED();
484 }));
485 }
486
487 // Advance on.
488 required_access_physical_offset += cur_read_size;
489 required_access_physical_size -= cur_read_size;
490 }
491
492 // Verify that we have nothing remaining to read.
493 ASSERT(required_access_physical_size == 0);
494
495 R_SUCCEED();
496 } else {
497 // We don't need a buffer, so just execute the read.
498 R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result {
499 // Check that the size is valid.
500 ASSERT(dst_size == total_required_size);
501
502 // Perform the read.
503 m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size,
504 required_access_physical_offset);
505
506 R_SUCCEED();
507 }));
508 }
509
510 R_SUCCEED();
511 };
512
513 R_TRY(this->OperatePerEntry(
514 offset, size,
515 [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
516 s64 data_offset, s64 read_size) -> Result {
517 // Determine the physical extents.
518 s64 physical_offset, physical_size;
519 if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) {
520 physical_offset = entry.phys_offset + data_offset;
521 physical_size = read_size;
522 } else {
523 physical_offset = entry.phys_offset;
524 physical_size = entry.GetPhysicalSize();
525 }
526
527 // If we have a pending data storage operation, perform it if we have to.
528 const s64 required_access_physical_end =
529 required_access_physical_offset + required_access_physical_size;
530 if (required_access_physical_size > 0) {
531 const bool required_by_gap =
532 !(required_access_physical_end <= physical_offset &&
533 physical_offset <= Common::AlignUp(required_access_physical_end,
534 CompressionBlockAlignment));
535 const bool required_by_continuous_size =
536 ((physical_size + physical_offset) - required_access_physical_end) +
537 required_access_physical_size >
538 static_cast<s64>(m_continuous_reading_size_max);
539 const bool required_by_entry_count = entry_count == EntriesCountMax;
540 if (required_by_gap || required_by_continuous_size ||
541 required_by_entry_count) {
542 // Check that our planned access is sane.
543 ASSERT(!will_allocate_pooled_buffer ||
544 required_access_physical_size <=
545 static_cast<s64>(m_continuous_reading_size_max));
546
547 // Perform the required read.
548 const Result rc = PerformRequiredRead();
549 if (R_FAILED(rc)) {
550 R_THROW(rc);
551 }
552
553 // Reset our requirements.
554 prev_entry.virt_offset = -1;
555 required_access_physical_size = 0;
556 entry_count = 0;
557 will_allocate_pooled_buffer = false;
558 }
559 }
560
561 // Sanity check that we're within bounds on entries.
562 ASSERT(entry_count < EntriesCountMax);
563
564 // Determine if a buffer allocation is needed.
565 if (entry.compression_type != CompressionType::None ||
566 (prev_entry.virt_offset >= 0 &&
567 entry.virt_offset - prev_entry.virt_offset !=
568 entry.phys_offset - prev_entry.phys_offset)) {
569 will_allocate_pooled_buffer = true;
570 }
571
572 // If we need to access the data storage, update our required access parameters.
573 if (CompressionTypeUtility::IsDataStorageAccessRequired(
574 entry.compression_type)) {
575 // If the data is compressed, ensure the access is sane.
576 if (entry.compression_type != CompressionType::None) {
577 R_UNLESS(data_offset == 0, ResultInvalidOffset);
578 R_UNLESS(virtual_data_size == read_size, ResultInvalidSize);
579 R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max),
580 ResultUnexpectedInCompressedStorageD);
581 }
582
583 // Update the required access parameters.
584 s64 gap_from_prev;
585 if (required_access_physical_size > 0) {
586 gap_from_prev = physical_offset - required_access_physical_end;
587 } else {
588 gap_from_prev = 0;
589 required_access_physical_offset = physical_offset;
590 }
591 required_access_physical_size += physical_size + gap_from_prev;
592
593 // Create an entry to access the data storage.
594 entries[entry_count++] = {
595 .compression_type = entry.compression_type,
596 .gap_from_prev = static_cast<u32>(gap_from_prev),
597 .physical_size = static_cast<u32>(physical_size),
598 .virtual_size = static_cast<u32>(read_size),
599 };
600 } else {
601 // Verify that we're allowed to be operating on the non-data-storage-access
602 // type.
603 R_UNLESS(entry.compression_type == CompressionType::Zeros,
604 ResultUnexpectedInCompressedStorageB);
605
606 // If we have entries, create a fake entry for the zero region.
607 if (entry_count != 0) {
608 // We need to have a physical size.
609 R_UNLESS(entry.GetPhysicalSize() != 0,
610 ResultUnexpectedInCompressedStorageD);
611
612 // Create a fake entry.
613 entries[entry_count++] = {
614 .compression_type = CompressionType::Zeros,
615 .gap_from_prev = 0,
616 .physical_size = 0,
617 .virtual_size = static_cast<u32>(read_size),
618 };
619 } else {
620 // We have no entries, so we can just perform the read.
621 const Result rc =
622 read_func(static_cast<size_t>(read_size),
623 [&](void* dst, size_t dst_size) -> Result {
624 // Check the space we should zero is correct.
625 ASSERT(dst_size == static_cast<size_t>(read_size));
626
627 // Zero the memory.
628 std::memset(dst, 0, read_size);
629 R_SUCCEED();
630 });
631 if (R_FAILED(rc)) {
632 R_THROW(rc);
633 }
634 }
635 }
636
637 // Set the previous entry.
638 prev_entry = entry;
639
640 // We're continuous.
641 *out_continuous = true;
642 R_SUCCEED();
643 }));
644
645 // If we still have a pending access, perform it.
646 if (required_access_physical_size != 0) {
647 R_TRY(PerformRequiredRead());
648 }
649
650 R_SUCCEED();
651 }
652
653 private:
654 DecompressorFunction GetDecompressor(CompressionType type) const {
655 // Check that we can get a decompressor for the type.
656 if (CompressionTypeUtility::IsUnknownType(type)) {
657 return nullptr;
658 }
659
660 // Get the decompressor.
661 return m_get_decompressor_function(type);
662 }
663
664 bool IsInitialized() const {
665 return m_table.IsInitialized();
666 }
667
668 private:
669 size_t m_block_size_max;
670 size_t m_continuous_reading_size_max;
671 BucketTree m_table;
672 VirtualFile m_data_storage;
673 GetDecompressorFunction m_get_decompressor_function;
674 };
675
676 class CacheManager {
677 YUZU_NON_COPYABLE(CacheManager);
678 YUZU_NON_MOVEABLE(CacheManager);
679
680 private:
681 struct AccessRange {
682 s64 virtual_offset;
683 s64 virtual_size;
684 u32 physical_size;
685 bool is_block_alignment_required;
686
687 s64 GetEndVirtualOffset() const {
688 return this->virtual_offset + this->virtual_size;
689 }
690 };
691 static_assert(std::is_trivial_v<AccessRange>);
692
693 public:
694 CacheManager() = default;
695
696 public:
697 Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1,
698 size_t max_cache_entries) {
699 // Set our fields.
700 m_storage_size = storage_size;
701
702 R_SUCCEED();
703 }
704
705 Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) {
706 // If we have nothing to read, succeed.
707 R_SUCCEED_IF(size == 0);
708
709 // Check that we have a buffer to read into.
710 R_UNLESS(buffer != nullptr, ResultNullptrArgument);
711
712 // Check that the read is in bounds.
713 R_UNLESS(offset <= m_storage_size, ResultInvalidOffset);
714
715 // Determine how much we can read.
716 const size_t read_size = std::min<size_t>(size, m_storage_size - offset);
717
718 // Create head/tail ranges.
719 AccessRange head_range = {};
720 AccessRange tail_range = {};
721 bool is_tail_set = false;
722
723 // Operate to determine the head range.
724 R_TRY(core.OperatePerEntry(
725 offset, 1,
726 [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
727 s64 data_offset, s64 data_read_size) -> Result {
728 // Set the head range.
729 head_range = {
730 .virtual_offset = entry.virt_offset,
731 .virtual_size = virtual_data_size,
732 .physical_size = static_cast<u32>(entry.phys_size),
733 .is_block_alignment_required =
734 CompressionTypeUtility::IsBlockAlignmentRequired(
735 entry.compression_type),
736 };
737
738 // If required, set the tail range.
739 if (static_cast<s64>(offset + read_size) <=
740 entry.virt_offset + virtual_data_size) {
741 tail_range = {
742 .virtual_offset = entry.virt_offset,
743 .virtual_size = virtual_data_size,
744 .physical_size = static_cast<u32>(entry.phys_size),
745 .is_block_alignment_required =
746 CompressionTypeUtility::IsBlockAlignmentRequired(
747 entry.compression_type),
748 };
749 is_tail_set = true;
750 }
751
752 // We only want to determine the head range, so we're not continuous.
753 *out_continuous = false;
754 R_SUCCEED();
755 }));
756
757 // If necessary, determine the tail range.
758 if (!is_tail_set) {
759 R_TRY(core.OperatePerEntry(
760 offset + read_size - 1, 1,
761 [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
762 s64 data_offset, s64 data_read_size) -> Result {
763 // Set the tail range.
764 tail_range = {
765 .virtual_offset = entry.virt_offset,
766 .virtual_size = virtual_data_size,
767 .physical_size = static_cast<u32>(entry.phys_size),
768 .is_block_alignment_required =
769 CompressionTypeUtility::IsBlockAlignmentRequired(
770 entry.compression_type),
771 };
772
773 // We only want to determine the tail range, so we're not continuous.
774 *out_continuous = false;
775 R_SUCCEED();
776 }));
777 }
778
779 // Begin performing the accesses.
780 s64 cur_offset = offset;
781 size_t cur_size = read_size;
782 char* cur_dst = static_cast<char*>(buffer);
783
784 // Determine our alignment.
785 const bool head_unaligned = head_range.is_block_alignment_required &&
786 (cur_offset != head_range.virtual_offset ||
787 static_cast<s64>(cur_size) < head_range.virtual_size);
788 const bool tail_unaligned = [&]() -> bool {
789 if (tail_range.is_block_alignment_required) {
790 if (static_cast<s64>(cur_size + cur_offset) ==
791 tail_range.GetEndVirtualOffset()) {
792 return false;
793 } else if (!head_unaligned) {
794 return true;
795 } else {
796 return head_range.GetEndVirtualOffset() <
797 static_cast<s64>(cur_size + cur_offset);
798 }
799 } else {
800 return false;
801 }
802 }();
803
804 // Determine start/end offsets.
805 const s64 start_offset =
806 head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset;
807 const s64 end_offset = tail_range.is_block_alignment_required
808 ? tail_range.GetEndVirtualOffset()
809 : cur_offset + cur_size;
810
811 // Perform the read.
812 bool is_burst_reading = false;
813 R_TRY(core.Read(
814 start_offset, end_offset - start_offset,
815 [&](size_t size_buffer_required,
816 const CompressedStorageCore::ReadImplFunction& read_impl) -> Result {
817 // Determine whether we're burst reading.
818 const AccessRange* unaligned_range = nullptr;
819 if (!is_burst_reading) {
820 // Check whether we're using head, tail, or none as unaligned.
821 if (head_unaligned && head_range.virtual_offset <= cur_offset &&
822 cur_offset < head_range.GetEndVirtualOffset()) {
823 unaligned_range = std::addressof(head_range);
824 } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset &&
825 cur_offset < tail_range.GetEndVirtualOffset()) {
826 unaligned_range = std::addressof(tail_range);
827 } else {
828 is_burst_reading = true;
829 }
830 }
831 ASSERT((is_burst_reading ^ (unaligned_range != nullptr)));
832
833 // Perform reading by burst, or not.
834 if (is_burst_reading) {
835 // Check that the access is valid for burst reading.
836 ASSERT(size_buffer_required <= cur_size);
837
838 // Perform the read.
839 Result rc = read_impl(cur_dst, size_buffer_required);
840 if (R_FAILED(rc)) {
841 R_THROW(rc);
842 }
843
844 // Advance.
845 cur_dst += size_buffer_required;
846 cur_offset += size_buffer_required;
847 cur_size -= size_buffer_required;
848
849 // Determine whether we're going to continue burst reading.
850 const s64 offset_aligned =
851 tail_unaligned ? tail_range.virtual_offset : end_offset;
852 ASSERT(cur_offset <= offset_aligned);
853
854 if (offset_aligned <= cur_offset) {
855 is_burst_reading = false;
856 }
857 } else {
858 // We're not burst reading, so we have some unaligned range.
859 ASSERT(unaligned_range != nullptr);
860
861 // Check that the size is correct.
862 ASSERT(size_buffer_required ==
863 static_cast<size_t>(unaligned_range->virtual_size));
864
865 // Get a pooled buffer for our read.
866 PooledBuffer pooled_buffer;
867 pooled_buffer.Allocate(size_buffer_required, size_buffer_required);
868
869 // Perform read.
870 Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required);
871 if (R_FAILED(rc)) {
872 R_THROW(rc);
873 }
874
875 // Copy the data we read to the destination.
876 const size_t skip_size = cur_offset - unaligned_range->virtual_offset;
877 const size_t copy_size = std::min<size_t>(
878 cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset);
879
880 std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size);
881
882 // Advance.
883 cur_dst += copy_size;
884 cur_offset += copy_size;
885 cur_size -= copy_size;
886 }
887
888 R_SUCCEED();
889 }));
890
891 R_SUCCEED();
892 }
893
894 private:
895 s64 m_storage_size = 0;
896 };
897
898public:
899 CompressedStorage() = default;
900 virtual ~CompressedStorage() {
901 this->Finalize();
902 }
903
904 Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
905 s32 bktr_entry_count, size_t block_size_max,
906 size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor,
907 size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) {
908 // Initialize our core.
909 R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count,
910 block_size_max, continuous_reading_size_max, get_decompressor));
911
912 // Get our core size.
913 s64 core_size = 0;
914 R_TRY(m_core.GetSize(std::addressof(core_size)));
915
916 // Initialize our cache manager.
917 R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries));
918
919 R_SUCCEED();
920 }
921
922 void Finalize() {
923 m_core.Finalize();
924 }
925
926 VirtualFile GetDataStorage() {
927 return m_core.GetDataStorage();
928 }
929
930 Result GetDataStorageSize(s64* out) {
931 R_RETURN(m_core.GetDataStorageSize(out));
932 }
933
934 Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset,
935 s64 size) {
936 R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size));
937 }
938
939 BucketTree& GetEntryTable() {
940 return m_core.GetEntryTable();
941 }
942
943public:
944 virtual size_t GetSize() const override {
945 s64 ret{};
946 m_core.GetSize(&ret);
947 return ret;
948 }
949
950 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
951 if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) {
952 return size;
953 } else {
954 return 0;
955 }
956 }
957
958private:
959 mutable CompressedStorageCore m_core;
960 mutable CacheManager m_cache_manager;
961};
962
963} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_compression_common.h b/src/core/file_sys/fssystem/fssystem_compression_common.h
new file mode 100644
index 000000000..266e0a7e5
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_compression_common.h
@@ -0,0 +1,43 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7
8namespace FileSys {
9
10enum class CompressionType : u8 {
11 None = 0,
12 Zeros = 1,
13 Two = 2,
14 Lz4 = 3,
15 Unknown = 4,
16};
17
18using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t);
19using GetDecompressorFunction = DecompressorFunction (*)(CompressionType);
20
21constexpr s64 CompressionBlockAlignment = 0x10;
22
23namespace CompressionTypeUtility {
24
25constexpr bool IsBlockAlignmentRequired(CompressionType type) {
26 return type != CompressionType::None && type != CompressionType::Zeros;
27}
28
29constexpr bool IsDataStorageAccessRequired(CompressionType type) {
30 return type != CompressionType::Zeros;
31}
32
33constexpr bool IsRandomAccessible(CompressionType type) {
34 return type == CompressionType::None;
35}
36
37constexpr bool IsUnknownType(CompressionType type) {
38 return type >= CompressionType::Unknown;
39}
40
41} // namespace CompressionTypeUtility
42
43} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp
new file mode 100644
index 000000000..ef552cefe
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/lz4_compression.h"
5#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
6
7namespace FileSys {
8
9namespace {
10
11Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) {
12 auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size);
13 R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC);
14 R_SUCCEED();
15}
16
17constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) {
18 switch (type) {
19 case CompressionType::Lz4:
20 return DecompressLz4;
21 default:
22 return nullptr;
23 }
24}
25
26} // namespace
27
28const NcaCompressionConfiguration& GetNcaCompressionConfiguration() {
29 static const NcaCompressionConfiguration configuration = {
30 .get_decompressor = GetNcaDecompressorFunction,
31 };
32
33 return configuration;
34}
35
36} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.h b/src/core/file_sys/fssystem/fssystem_compression_configuration.h
new file mode 100644
index 000000000..ec9b48e9a
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.h
@@ -0,0 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
7
8namespace FileSys {
9
10const NcaCompressionConfiguration& GetNcaCompressionConfiguration();
11
12}
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp
new file mode 100644
index 000000000..a4f0cde28
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp
@@ -0,0 +1,65 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/crypto/aes_util.h"
5#include "core/crypto/key_manager.h"
6#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
7
8namespace FileSys {
9
10namespace {
11
12void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size,
13 s32 key_type) {
14 if (key_type == static_cast<s32>(KeyType::ZeroKey)) {
15 std::memset(dst_key, 0, dst_key_size);
16 return;
17 }
18
19 if (key_type == static_cast<s32>(KeyType::InvalidKey) ||
20 key_type < static_cast<s32>(KeyType::ZeroKey) ||
21 key_type >= static_cast<s32>(KeyType::NcaExternalKey)) {
22 std::memset(dst_key, 0xFF, dst_key_size);
23 return;
24 }
25
26 const auto& instance = Core::Crypto::KeyManager::Instance();
27
28 if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) ||
29 key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) {
30 const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type;
31 const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header);
32 std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2));
33 return;
34 }
35
36 const s32 key_generation =
37 std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1;
38 const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount;
39
40 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
41 instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index),
42 Core::Crypto::Mode::ECB);
43 cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size,
44 reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt);
45}
46
47} // namespace
48
49const NcaCryptoConfiguration& GetCryptoConfiguration() {
50 static const NcaCryptoConfiguration configuration = {
51 .header_1_sign_key_moduli{},
52 .header_1_sign_key_public_exponent{},
53 .key_area_encryption_key_source{},
54 .header_encryption_key_source{},
55 .header_encrypted_encryption_keys{},
56 .generate_key = GenerateKey,
57 .verify_sign1{},
58 .is_plaintext_header_available{},
59 .is_available_sw_key{},
60 };
61
62 return configuration;
63}
64
65} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.h b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h
new file mode 100644
index 000000000..7fd9c5a8d
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h
@@ -0,0 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
7
8namespace FileSys {
9
10const NcaCryptoConfiguration& GetCryptoConfiguration();
11
12}
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
new file mode 100644
index 000000000..4a75b5308
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
@@ -0,0 +1,127 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
5#include "core/file_sys/vfs_offset.h"
6
7namespace FileSys {
8
9HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage()
10 : m_data_size(-1) {
11 for (size_t i = 0; i < MaxLayers - 1; i++) {
12 m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>();
13 }
14}
15
16Result HierarchicalIntegrityVerificationStorage::Initialize(
17 const HierarchicalIntegrityVerificationInformation& info,
18 HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries,
19 s8 buffer_level) {
20 // Validate preconditions.
21 ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount);
22
23 // Set member variables.
24 m_max_layers = info.max_layers;
25
26 // Initialize the top level verification storage.
27 m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage],
28 storage[HierarchicalStorageInformation::Layer1Storage],
29 static_cast<s64>(1) << info.info[0].block_order, HashSize,
30 false);
31
32 // Ensure we don't leak state if further initialization goes wrong.
33 ON_RESULT_FAILURE {
34 m_verify_storages[0]->Finalize();
35 m_data_size = -1;
36 };
37
38 // Initialize the top level buffer storage.
39 m_buffer_storages[0] = m_verify_storages[0];
40 R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared);
41
42 // Prepare to initialize the level storages.
43 s32 level = 0;
44
45 // Ensure we don't leak state if further initialization goes wrong.
46 ON_RESULT_FAILURE_2 {
47 m_verify_storages[level + 1]->Finalize();
48 for (; level > 0; --level) {
49 m_buffer_storages[level].reset();
50 m_verify_storages[level]->Finalize();
51 }
52 };
53
54 // Initialize the level storages.
55 for (; level < m_max_layers - 3; ++level) {
56 // Initialize the verification storage.
57 auto buffer_storage =
58 std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
59 m_verify_storages[level + 1]->Initialize(
60 std::move(buffer_storage), storage[level + 2],
61 static_cast<s64>(1) << info.info[level + 1].block_order,
62 static_cast<s64>(1) << info.info[level].block_order, false);
63
64 // Initialize the buffer storage.
65 m_buffer_storages[level + 1] = m_verify_storages[level + 1];
66 R_UNLESS(m_buffer_storages[level + 1] != nullptr,
67 ResultAllocationMemoryFailedAllocateShared);
68 }
69
70 // Initialize the final level storage.
71 {
72 // Initialize the verification storage.
73 auto buffer_storage =
74 std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
75 m_verify_storages[level + 1]->Initialize(
76 std::move(buffer_storage), storage[level + 2],
77 static_cast<s64>(1) << info.info[level + 1].block_order,
78 static_cast<s64>(1) << info.info[level].block_order, true);
79
80 // Initialize the buffer storage.
81 m_buffer_storages[level + 1] = m_verify_storages[level + 1];
82 R_UNLESS(m_buffer_storages[level + 1] != nullptr,
83 ResultAllocationMemoryFailedAllocateShared);
84 }
85
86 // Set the data size.
87 m_data_size = info.info[level + 1].size;
88
89 // We succeeded.
90 R_SUCCEED();
91}
92
93void HierarchicalIntegrityVerificationStorage::Finalize() {
94 if (m_data_size >= 0) {
95 m_data_size = 0;
96
97 for (s32 level = m_max_layers - 2; level >= 0; --level) {
98 m_buffer_storages[level].reset();
99 m_verify_storages[level]->Finalize();
100 }
101
102 m_data_size = -1;
103 }
104}
105
106size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size,
107 size_t offset) const {
108 // Validate preconditions.
109 ASSERT(m_data_size >= 0);
110
111 // Succeed if zero-size.
112 if (size == 0) {
113 return size;
114 }
115
116 // Validate arguments.
117 ASSERT(buffer != nullptr);
118
119 // Read the data.
120 return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset);
121}
122
123size_t HierarchicalIntegrityVerificationStorage::GetSize() const {
124 return m_data_size;
125}
126
127} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
new file mode 100644
index 000000000..5cf697efe
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
@@ -0,0 +1,164 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/alignment.h"
7#include "core/file_sys/fssystem/fs_i_storage.h"
8#include "core/file_sys/fssystem/fs_types.h"
9#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
10#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
11#include "core/file_sys/vfs_offset.h"
12
13namespace FileSys {
14
15struct HierarchicalIntegrityVerificationLevelInformation {
16 Int64 offset;
17 Int64 size;
18 s32 block_order;
19 std::array<u8, 4> reserved;
20};
21static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>);
22static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18);
23static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4);
24
25struct HierarchicalIntegrityVerificationInformation {
26 u32 max_layers;
27 std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info;
28 HashSalt seed;
29
30 s64 GetLayeredHashSize() const {
31 return this->info[this->max_layers - 2].offset;
32 }
33
34 s64 GetDataOffset() const {
35 return this->info[this->max_layers - 2].offset;
36 }
37
38 s64 GetDataSize() const {
39 return this->info[this->max_layers - 2].size;
40 }
41};
42static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>);
43
44struct HierarchicalIntegrityVerificationMetaInformation {
45 u32 magic;
46 u32 version;
47 u32 master_hash_size;
48 HierarchicalIntegrityVerificationInformation level_hash_info;
49};
50static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>);
51
52struct HierarchicalIntegrityVerificationSizeSet {
53 s64 control_size;
54 s64 master_hash_size;
55 std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes;
56};
57static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>);
58
59class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage {
60 YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage);
61 YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage);
62
63public:
64 using GenerateRandomFunction = void (*)(void* dst, size_t size);
65
66 class HierarchicalStorageInformation {
67 public:
68 enum {
69 MasterStorage = 0,
70 Layer1Storage = 1,
71 Layer2Storage = 2,
72 Layer3Storage = 3,
73 Layer4Storage = 4,
74 Layer5Storage = 5,
75 DataStorage = 6,
76 };
77
78 private:
79 std::array<VirtualFile, DataStorage + 1> m_storages;
80
81 public:
82 void SetMasterHashStorage(VirtualFile s) {
83 m_storages[MasterStorage] = s;
84 }
85 void SetLayer1HashStorage(VirtualFile s) {
86 m_storages[Layer1Storage] = s;
87 }
88 void SetLayer2HashStorage(VirtualFile s) {
89 m_storages[Layer2Storage] = s;
90 }
91 void SetLayer3HashStorage(VirtualFile s) {
92 m_storages[Layer3Storage] = s;
93 }
94 void SetLayer4HashStorage(VirtualFile s) {
95 m_storages[Layer4Storage] = s;
96 }
97 void SetLayer5HashStorage(VirtualFile s) {
98 m_storages[Layer5Storage] = s;
99 }
100 void SetDataStorage(VirtualFile s) {
101 m_storages[DataStorage] = s;
102 }
103
104 VirtualFile& operator[](s32 index) {
105 ASSERT(MasterStorage <= index && index <= DataStorage);
106 return m_storages[index];
107 }
108 };
109
110public:
111 HierarchicalIntegrityVerificationStorage();
112 virtual ~HierarchicalIntegrityVerificationStorage() override {
113 this->Finalize();
114 }
115
116 Result Initialize(const HierarchicalIntegrityVerificationInformation& info,
117 HierarchicalStorageInformation storage, int max_data_cache_entries,
118 int max_hash_cache_entries, s8 buffer_level);
119 void Finalize();
120
121 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
122 virtual size_t GetSize() const override;
123
124 bool IsInitialized() const {
125 return m_data_size >= 0;
126 }
127
128 s64 GetL1HashVerificationBlockSize() const {
129 return m_verify_storages[m_max_layers - 2]->GetBlockSize();
130 }
131
132 VirtualFile GetL1HashStorage() {
133 return std::make_shared<OffsetVfsFile>(
134 m_buffer_storages[m_max_layers - 3],
135 Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0);
136 }
137
138public:
139 static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) {
140 return static_cast<s8>(16 + max_layers - 2);
141 }
142
143protected:
144 static constexpr s64 HashSize = 256 / 8;
145 static constexpr size_t MaxLayers = IntegrityMaxLayerCount;
146
147private:
148 static GenerateRandomFunction s_generate_random;
149
150 static void SetGenerateRandomFunction(GenerateRandomFunction func) {
151 s_generate_random = func;
152 }
153
154private:
155 friend struct HierarchicalIntegrityVerificationMetaInformation;
156
157private:
158 std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages;
159 std::array<VirtualFile, MaxLayers - 1> m_buffer_storages;
160 s64 m_data_size;
161 s32 m_max_layers;
162};
163
164} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
new file mode 100644
index 000000000..caea0b8f8
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
@@ -0,0 +1,80 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "common/scope_exit.h"
6#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h"
7
8namespace FileSys {
9
10namespace {
11
12s32 Log2(s32 value) {
13 ASSERT(value > 0);
14 ASSERT(Common::IsPowerOfTwo(value));
15
16 s32 log = 0;
17 while ((value >>= 1) > 0) {
18 ++log;
19 }
20 return log;
21}
22
23} // namespace
24
25Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count,
26 size_t htbs, void* hash_buf, size_t hash_buf_size) {
27 // Validate preconditions.
28 ASSERT(layer_count == LayerCount);
29 ASSERT(Common::IsPowerOfTwo(htbs));
30 ASSERT(hash_buf != nullptr);
31
32 // Set size tracking members.
33 m_hash_target_block_size = static_cast<s32>(htbs);
34 m_log_size_ratio = Log2(m_hash_target_block_size / HashSize);
35
36 // Get the base storage size.
37 m_base_storage_size = base_storages[2]->GetSize();
38 {
39 auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; });
40 R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize)
41 << m_log_size_ratio << m_log_size_ratio,
42 ResultHierarchicalSha256BaseStorageTooLarge);
43 size_guard.Cancel();
44 }
45
46 // Set hash buffer tracking members.
47 m_base_storage = base_storages[2];
48 m_hash_buffer = static_cast<char*>(hash_buf);
49 m_hash_buffer_size = hash_buf_size;
50
51 // Read the master hash.
52 std::array<u8, HashSize> master_hash{};
53 base_storages[0]->ReadObject(std::addressof(master_hash));
54
55 // Read and validate the data being hashed.
56 s64 hash_storage_size = base_storages[1]->GetSize();
57 ASSERT(Common::IsAligned(hash_storage_size, HashSize));
58 ASSERT(hash_storage_size <= m_hash_target_block_size);
59 ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size));
60
61 base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer),
62 static_cast<size_t>(hash_storage_size), 0);
63
64 R_SUCCEED();
65}
66
67size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const {
68 // Succeed if zero-size.
69 if (size == 0) {
70 return size;
71 }
72
73 // Validate that we have a buffer to read into.
74 ASSERT(buffer != nullptr);
75
76 // Read the data.
77 return m_base_storage->Read(buffer, size, offset);
78}
79
80} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
new file mode 100644
index 000000000..18df400af
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
@@ -0,0 +1,44 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7
8#include "core/file_sys/errors.h"
9#include "core/file_sys/fssystem/fs_i_storage.h"
10#include "core/file_sys/vfs.h"
11
12namespace FileSys {
13
14class HierarchicalSha256Storage : public IReadOnlyStorage {
15 YUZU_NON_COPYABLE(HierarchicalSha256Storage);
16 YUZU_NON_MOVEABLE(HierarchicalSha256Storage);
17
18public:
19 static constexpr s32 LayerCount = 3;
20 static constexpr size_t HashSize = 256 / 8;
21
22public:
23 HierarchicalSha256Storage() : m_mutex() {}
24
25 Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf,
26 size_t hash_buf_size);
27
28 virtual size_t GetSize() const override {
29 return m_base_storage->GetSize();
30 }
31
32 virtual size_t Read(u8* buffer, size_t length, size_t offset) const override;
33
34private:
35 VirtualFile m_base_storage;
36 s64 m_base_storage_size;
37 char* m_hash_buffer;
38 size_t m_hash_buffer_size;
39 s32 m_hash_target_block_size;
40 s32 m_log_size_ratio;
41 std::mutex m_mutex;
42};
43
44} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
new file mode 100644
index 000000000..7544e70b2
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
@@ -0,0 +1,119 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/errors.h"
5#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
6
7namespace FileSys {
8
9Result IndirectStorage::Initialize(VirtualFile table_storage) {
10 // Read and verify the bucket tree header.
11 BucketTree::Header header;
12 table_storage->ReadObject(std::addressof(header));
13 R_TRY(header.Verify());
14
15 // Determine extents.
16 const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
17 const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
18 const auto node_storage_offset = QueryHeaderStorageSize();
19 const auto entry_storage_offset = node_storage_offset + node_storage_size;
20
21 // Initialize.
22 R_RETURN(this->Initialize(
23 std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
24 std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
25 header.entry_count));
26}
27
28void IndirectStorage::Finalize() {
29 if (this->IsInitialized()) {
30 m_table.Finalize();
31 for (auto i = 0; i < StorageCount; i++) {
32 m_data_storage[i] = VirtualFile();
33 }
34 }
35}
36
37Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count,
38 s64 offset, s64 size) {
39 // Validate pre-conditions.
40 ASSERT(offset >= 0);
41 ASSERT(size >= 0);
42 ASSERT(this->IsInitialized());
43
44 // Clear the out count.
45 R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
46 *out_entry_count = 0;
47
48 // Succeed if there's no range.
49 R_SUCCEED_IF(size == 0);
50
51 // If we have an output array, we need it to be non-null.
52 R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
53
54 // Check that our range is valid.
55 BucketTree::Offsets table_offsets;
56 R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
57
58 R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
59
60 // Find the offset in our tree.
61 BucketTree::Visitor visitor;
62 R_TRY(m_table.Find(std::addressof(visitor), offset));
63 {
64 const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
65 R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
66 ResultInvalidIndirectEntryOffset);
67 }
68
69 // Prepare to loop over entries.
70 const auto end_offset = offset + static_cast<s64>(size);
71 s32 count = 0;
72
73 auto cur_entry = *visitor.Get<Entry>();
74 while (cur_entry.GetVirtualOffset() < end_offset) {
75 // Try to write the entry to the out list.
76 if (entry_count != 0) {
77 if (count >= entry_count) {
78 break;
79 }
80 std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
81 }
82
83 count++;
84
85 // Advance.
86 if (visitor.CanMoveNext()) {
87 R_TRY(visitor.MoveNext());
88 cur_entry = *visitor.Get<Entry>();
89 } else {
90 break;
91 }
92 }
93
94 // Write the output count.
95 *out_entry_count = count;
96 R_SUCCEED();
97}
98
99size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
100 // Validate pre-conditions.
101 ASSERT(this->IsInitialized());
102 ASSERT(buffer != nullptr);
103
104 // Succeed if there's nothing to read.
105 if (size == 0) {
106 return 0;
107 }
108
109 const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>(
110 offset, size,
111 [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
112 storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
113 static_cast<size_t>(cur_size), data_offset);
114 R_SUCCEED();
115 });
116
117 return size;
118}
119} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
new file mode 100644
index 000000000..7854335bf
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
@@ -0,0 +1,294 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/errors.h"
7#include "core/file_sys/fssystem/fs_i_storage.h"
8#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
9#include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h"
10#include "core/file_sys/vfs.h"
11#include "core/file_sys/vfs_offset.h"
12
13namespace FileSys {
14
15class IndirectStorage : public IReadOnlyStorage {
16 YUZU_NON_COPYABLE(IndirectStorage);
17 YUZU_NON_MOVEABLE(IndirectStorage);
18
19public:
20 static constexpr s32 StorageCount = 2;
21 static constexpr size_t NodeSize = 16_KiB;
22
23 struct Entry {
24 std::array<u8, sizeof(s64)> virt_offset;
25 std::array<u8, sizeof(s64)> phys_offset;
26 s32 storage_index;
27
28 void SetVirtualOffset(const s64& ofs) {
29 std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64));
30 }
31
32 s64 GetVirtualOffset() const {
33 s64 offset;
34 std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64));
35 return offset;
36 }
37
38 void SetPhysicalOffset(const s64& ofs) {
39 std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64));
40 }
41
42 s64 GetPhysicalOffset() const {
43 s64 offset;
44 std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64));
45 return offset;
46 }
47 };
48 static_assert(std::is_trivial_v<Entry>);
49 static_assert(sizeof(Entry) == 0x14);
50
51 struct EntryData {
52 s64 virt_offset;
53 s64 phys_offset;
54 s32 storage_index;
55
56 void Set(const Entry& entry) {
57 this->virt_offset = entry.GetVirtualOffset();
58 this->phys_offset = entry.GetPhysicalOffset();
59 this->storage_index = entry.storage_index;
60 }
61 };
62 static_assert(std::is_trivial_v<EntryData>);
63
64public:
65 IndirectStorage() : m_table(), m_data_storage() {}
66 virtual ~IndirectStorage() {
67 this->Finalize();
68 }
69
70 Result Initialize(VirtualFile table_storage);
71 void Finalize();
72
73 bool IsInitialized() const {
74 return m_table.IsInitialized();
75 }
76
77 Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) {
78 R_RETURN(
79 m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
80 }
81
82 void SetStorage(s32 idx, VirtualFile storage) {
83 ASSERT(0 <= idx && idx < StorageCount);
84 m_data_storage[idx] = storage;
85 }
86
87 template <typename T>
88 void SetStorage(s32 idx, T storage, s64 offset, s64 size) {
89 ASSERT(0 <= idx && idx < StorageCount);
90 m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset);
91 }
92
93 Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
94 s64 size);
95
96 virtual size_t GetSize() const override {
97 BucketTree::Offsets offsets{};
98 m_table.GetOffsets(std::addressof(offsets));
99
100 return offsets.end_offset;
101 }
102
103 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
104
105public:
106 static constexpr s64 QueryHeaderStorageSize() {
107 return BucketTree::QueryHeaderStorageSize();
108 }
109
110 static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
111 return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
112 }
113
114 static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
115 return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
116 }
117
118protected:
119 BucketTree& GetEntryTable() {
120 return m_table;
121 }
122
123 VirtualFile& GetDataStorage(s32 index) {
124 ASSERT(0 <= index && index < StorageCount);
125 return m_data_storage[index];
126 }
127
128 template <bool ContinuousCheck, bool RangeCheck, typename F>
129 Result OperatePerEntry(s64 offset, s64 size, F func);
130
131private:
132 struct ContinuousReadingEntry {
133 static constexpr size_t FragmentSizeMax = 4_KiB;
134
135 IndirectStorage::Entry entry;
136
137 s64 GetVirtualOffset() const {
138 return this->entry.GetVirtualOffset();
139 }
140
141 s64 GetPhysicalOffset() const {
142 return this->entry.GetPhysicalOffset();
143 }
144
145 bool IsFragment() const {
146 return this->entry.storage_index != 0;
147 }
148 };
149 static_assert(std::is_trivial_v<ContinuousReadingEntry>);
150
151private:
152 mutable BucketTree m_table;
153 std::array<VirtualFile, StorageCount> m_data_storage;
154};
155
156template <bool ContinuousCheck, bool RangeCheck, typename F>
157Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
158 // Validate preconditions.
159 ASSERT(offset >= 0);
160 ASSERT(size >= 0);
161 ASSERT(this->IsInitialized());
162
163 // Succeed if there's nothing to operate on.
164 R_SUCCEED_IF(size == 0);
165
166 // Get the table offsets.
167 BucketTree::Offsets table_offsets;
168 R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
169
170 // Validate arguments.
171 R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
172
173 // Find the offset in our tree.
174 BucketTree::Visitor visitor;
175 R_TRY(m_table.Find(std::addressof(visitor), offset));
176 {
177 const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
178 R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
179 ResultInvalidIndirectEntryOffset);
180 }
181
182 // Prepare to operate in chunks.
183 auto cur_offset = offset;
184 const auto end_offset = offset + static_cast<s64>(size);
185 BucketTree::ContinuousReadingInfo cr_info;
186
187 while (cur_offset < end_offset) {
188 // Get the current entry.
189 const auto cur_entry = *visitor.Get<Entry>();
190
191 // Get and validate the entry's offset.
192 const auto cur_entry_offset = cur_entry.GetVirtualOffset();
193 R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
194
195 // Validate the storage index.
196 R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount,
197 ResultInvalidIndirectEntryStorageIndex);
198
199 // If we need to check the continuous info, do so.
200 if constexpr (ContinuousCheck) {
201 // Scan, if we need to.
202 if (cr_info.CheckNeedScan()) {
203 R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>(
204 std::addressof(cr_info), cur_offset,
205 static_cast<size_t>(end_offset - cur_offset)));
206 }
207
208 // Process a base storage entry.
209 if (cr_info.CanDo()) {
210 // Ensure that we can process.
211 R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex);
212
213 // Ensure that we remain within range.
214 const auto data_offset = cur_offset - cur_entry_offset;
215 const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
216 const auto cur_size = static_cast<s64>(cr_info.GetReadSize());
217
218 // If we should, verify the range.
219 if constexpr (RangeCheck) {
220 // Get the current data storage's size.
221 s64 cur_data_storage_size = m_data_storage[0]->GetSize();
222
223 R_UNLESS(0 <= cur_entry_phys_offset &&
224 cur_entry_phys_offset <= cur_data_storage_size,
225 ResultInvalidIndirectEntryOffset);
226 R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <=
227 cur_data_storage_size,
228 ResultInvalidIndirectStorageSize);
229 }
230
231 // Operate.
232 R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset,
233 cur_size));
234
235 // Mark as done.
236 cr_info.Done();
237 }
238 }
239
240 // Get and validate the next entry offset.
241 s64 next_entry_offset;
242 if (visitor.CanMoveNext()) {
243 R_TRY(visitor.MoveNext());
244 next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
245 R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
246 } else {
247 next_entry_offset = table_offsets.end_offset;
248 }
249 R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
250
251 // Get the offset of the entry in the data we read.
252 const auto data_offset = cur_offset - cur_entry_offset;
253 const auto data_size = (next_entry_offset - cur_entry_offset);
254 ASSERT(data_size > 0);
255
256 // Determine how much is left.
257 const auto remaining_size = end_offset - cur_offset;
258 const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
259 ASSERT(cur_size <= size);
260
261 // Operate, if we need to.
262 bool needs_operate;
263 if constexpr (!ContinuousCheck) {
264 needs_operate = true;
265 } else {
266 needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0;
267 }
268
269 if (needs_operate) {
270 const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
271
272 if constexpr (RangeCheck) {
273 // Get the current data storage's size.
274 s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
275
276 // Ensure that we remain within range.
277 R_UNLESS(0 <= cur_entry_phys_offset &&
278 cur_entry_phys_offset <= cur_data_storage_size,
279 ResultIndirectStorageCorrupted);
280 R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size,
281 ResultIndirectStorageCorrupted);
282 }
283
284 R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset,
285 cur_offset, cur_size));
286 }
287
288 cur_offset += cur_size;
289 }
290
291 R_SUCCEED();
292}
293
294} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp
new file mode 100644
index 000000000..2c3da230c
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp
@@ -0,0 +1,30 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h"
5
6namespace FileSys {
7
8Result IntegrityRomFsStorage::Initialize(
9 HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
10 HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
11 int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) {
12 // Set master hash.
13 m_master_hash = master_hash;
14 m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value);
15 R_UNLESS(m_master_hash_storage != nullptr,
16 ResultAllocationMemoryFailedInIntegrityRomFsStorageA);
17
18 // Set the master hash storage.
19 storage_info[0] = m_master_hash_storage;
20
21 // Initialize our integrity storage.
22 R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries,
23 max_hash_cache_entries, buffer_level));
24}
25
26void IntegrityRomFsStorage::Finalize() {
27 m_integrity_storage.Finalize();
28}
29
30} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h
new file mode 100644
index 000000000..5f8512b2a
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h
@@ -0,0 +1,42 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
7#include "core/file_sys/fssystem/fssystem_nca_header.h"
8#include "core/file_sys/vfs_vector.h"
9
10namespace FileSys {
11
12constexpr inline size_t IntegrityLayerCountRomFs = 7;
13constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB;
14
15class IntegrityRomFsStorage : public IReadOnlyStorage {
16public:
17 IntegrityRomFsStorage() {}
18 virtual ~IntegrityRomFsStorage() override {
19 this->Finalize();
20 }
21
22 Result Initialize(
23 HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
24 HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
25 int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
26 void Finalize();
27
28 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
29 return m_integrity_storage.Read(buffer, size, offset);
30 }
31
32 virtual size_t GetSize() const override {
33 return m_integrity_storage.GetSize();
34 }
35
36private:
37 HierarchicalIntegrityVerificationStorage m_integrity_storage;
38 Hash m_master_hash;
39 std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage;
40};
41
42} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp
new file mode 100644
index 000000000..2f73abf86
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp
@@ -0,0 +1,91 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
6
7namespace FileSys {
8
9constexpr inline u32 ILog2(u32 val) {
10 ASSERT(val > 0);
11 return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val));
12}
13
14void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
15 s64 upper_layer_verif_block_size, bool is_real_data) {
16 // Validate preconditions.
17 ASSERT(verif_block_size >= HashSize);
18
19 // Set storages.
20 m_hash_storage = hs;
21 m_data_storage = ds;
22
23 // Set verification block sizes.
24 m_verification_block_size = verif_block_size;
25 m_verification_block_order = ILog2(static_cast<u32>(verif_block_size));
26 ASSERT(m_verification_block_size == 1ll << m_verification_block_order);
27
28 // Set upper layer block sizes.
29 upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize);
30 m_upper_layer_verification_block_size = upper_layer_verif_block_size;
31 m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size));
32 ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order);
33
34 // Validate sizes.
35 {
36 s64 hash_size = m_hash_storage->GetSize();
37 s64 data_size = m_data_storage->GetSize();
38 ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size);
39 }
40
41 // Set data.
42 m_is_real_data = is_real_data;
43}
44
45void IntegrityVerificationStorage::Finalize() {
46 m_hash_storage = VirtualFile();
47 m_data_storage = VirtualFile();
48}
49
50size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const {
51 // Succeed if zero size.
52 if (size == 0) {
53 return size;
54 }
55
56 // Validate arguments.
57 ASSERT(buffer != nullptr);
58
59 // Validate the offset.
60 s64 data_size = m_data_storage->GetSize();
61 ASSERT(offset <= static_cast<size_t>(data_size));
62
63 // Validate the access range.
64 ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(
65 offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size)))));
66
67 // Determine the read extents.
68 size_t read_size = size;
69 if (static_cast<s64>(offset + read_size) > data_size) {
70 // Determine the padding sizes.
71 s64 padding_offset = data_size - offset;
72 size_t padding_size = static_cast<size_t>(
73 m_verification_block_size - (padding_offset & (m_verification_block_size - 1)));
74 ASSERT(static_cast<s64>(padding_size) < m_verification_block_size);
75
76 // Clear the padding.
77 std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size);
78
79 // Set the new in-bounds size.
80 read_size = static_cast<size_t>(data_size - offset);
81 }
82
83 // Perform the read.
84 return m_data_storage->Read(buffer, read_size, offset);
85}
86
87size_t IntegrityVerificationStorage::GetSize() const {
88 return m_data_storage->GetSize();
89}
90
91} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h
new file mode 100644
index 000000000..09f76799d
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h
@@ -0,0 +1,65 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <optional>
7
8#include "core/file_sys/fssystem/fs_i_storage.h"
9#include "core/file_sys/fssystem/fs_types.h"
10
11namespace FileSys {
12
13class IntegrityVerificationStorage : public IReadOnlyStorage {
14 YUZU_NON_COPYABLE(IntegrityVerificationStorage);
15 YUZU_NON_MOVEABLE(IntegrityVerificationStorage);
16
17public:
18 static constexpr s64 HashSize = 256 / 8;
19
20 struct BlockHash {
21 std::array<u8, HashSize> hash;
22 };
23 static_assert(std::is_trivial_v<BlockHash>);
24
25public:
26 IntegrityVerificationStorage()
27 : m_verification_block_size(0), m_verification_block_order(0),
28 m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {}
29 virtual ~IntegrityVerificationStorage() override {
30 this->Finalize();
31 }
32
33 void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
34 s64 upper_layer_verif_block_size, bool is_real_data);
35 void Finalize();
36
37 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
38 virtual size_t GetSize() const override;
39
40 s64 GetBlockSize() const {
41 return m_verification_block_size;
42 }
43
44private:
45 static void SetValidationBit(BlockHash* hash) {
46 ASSERT(hash != nullptr);
47 hash->hash[HashSize - 1] |= 0x80;
48 }
49
50 static bool IsValidationBit(const BlockHash* hash) {
51 ASSERT(hash != nullptr);
52 return (hash->hash[HashSize - 1] & 0x80) != 0;
53 }
54
55private:
56 VirtualFile m_hash_storage;
57 VirtualFile m_data_storage;
58 s64 m_verification_block_size;
59 s64 m_verification_block_order;
60 s64 m_upper_layer_verification_block_size;
61 s64 m_upper_layer_verification_block_order;
62 bool m_is_real_data;
63};
64
65} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h
new file mode 100644
index 000000000..c07a127fb
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/fssystem/fs_i_storage.h"
7
8namespace FileSys {
9
10class MemoryResourceBufferHoldStorage : public IStorage {
11 YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage);
12 YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage);
13
14public:
15 MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size)
16 : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)),
17 m_buffer_size(buffer_size) {}
18
19 virtual ~MemoryResourceBufferHoldStorage() {
20 // If we have a buffer, deallocate it.
21 if (m_buffer != nullptr) {
22 ::operator delete(m_buffer);
23 }
24 }
25
26 bool IsValid() const {
27 return m_buffer != nullptr;
28 }
29 void* GetBuffer() const {
30 return m_buffer;
31 }
32
33public:
34 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
35 // Check pre-conditions.
36 ASSERT(m_storage != nullptr);
37
38 return m_storage->Read(buffer, size, offset);
39 }
40
41 virtual size_t GetSize() const override {
42 // Check pre-conditions.
43 ASSERT(m_storage != nullptr);
44
45 return m_storage->GetSize();
46 }
47
48 virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
49 // Check pre-conditions.
50 ASSERT(m_storage != nullptr);
51
52 return m_storage->Write(buffer, size, offset);
53 }
54
55private:
56 VirtualFile m_storage;
57 void* m_buffer;
58 size_t m_buffer_size;
59};
60
61} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
new file mode 100644
index 000000000..0f5432203
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
@@ -0,0 +1,1351 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
5#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
6#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
7#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
8#include "core/file_sys/fssystem/fssystem_compressed_storage.h"
9#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
10#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h"
11#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
12#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h"
13#include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h"
14#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
15#include "core/file_sys/fssystem/fssystem_sparse_storage.h"
16#include "core/file_sys/fssystem/fssystem_switch_storage.h"
17#include "core/file_sys/vfs_offset.h"
18#include "core/file_sys/vfs_vector.h"
19
20namespace FileSys {
21
22namespace {
23
24constexpr inline s32 IntegrityDataCacheCount = 24;
25constexpr inline s32 IntegrityHashCacheCount = 8;
26
27constexpr inline s32 IntegrityDataCacheCountForMeta = 16;
28constexpr inline s32 IntegrityHashCacheCountForMeta = 2;
29
30class SharedNcaBodyStorage : public IReadOnlyStorage {
31 YUZU_NON_COPYABLE(SharedNcaBodyStorage);
32 YUZU_NON_MOVEABLE(SharedNcaBodyStorage);
33
34private:
35 VirtualFile m_storage;
36 std::shared_ptr<NcaReader> m_nca_reader;
37
38public:
39 SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r)
40 : m_storage(std::move(s)), m_nca_reader(std::move(r)) {}
41
42 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
43 // Validate pre-conditions.
44 ASSERT(m_storage != nullptr);
45
46 // Read from the base storage.
47 return m_storage->Read(buffer, size, offset);
48 }
49
50 virtual size_t GetSize() const override {
51 // Validate pre-conditions.
52 ASSERT(m_storage != nullptr);
53
54 return m_storage->GetSize();
55 }
56};
57
58inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) {
59 return static_cast<s64>(reader.GetFsOffset(fs_index));
60}
61
62inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) {
63 return static_cast<s64>(reader.GetFsEndOffset(fs_index));
64}
65
66using Sha256DataRegion = NcaFsHeader::Region;
67using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo;
68using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation;
69
70} // namespace
71
72Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out,
73 NcaFsHeaderReader* out_header_reader,
74 s32 fs_index, StorageContext* ctx) {
75 // Open storage.
76 R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx));
77}
78
79Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader,
80 s32 fs_index, StorageContext* ctx) {
81 // Validate preconditions.
82 ASSERT(out != nullptr);
83 ASSERT(out_header_reader != nullptr);
84 ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax);
85
86 // Validate the fs index.
87 R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound);
88
89 // Initialize our header reader for the fs index.
90 R_TRY(out_header_reader->Initialize(*m_reader, fs_index));
91
92 // Declare the storage we're opening.
93 VirtualFile storage;
94
95 // Process sparse layer.
96 s64 fs_data_offset = 0;
97 if (out_header_reader->ExistsSparseLayer()) {
98 // Get the sparse info.
99 const auto& sparse_info = out_header_reader->GetSparseInfo();
100
101 // Create based on whether we have a meta hash layer.
102 if (out_header_reader->ExistsSparseMetaHashLayer()) {
103 // Create the sparse storage with verification.
104 R_TRY(this->CreateSparseStorageWithVerification(
105 std::addressof(storage), std::addressof(fs_data_offset),
106 ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr,
107 ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
108 ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index,
109 out_header_reader->GetAesCtrUpperIv(), sparse_info,
110 out_header_reader->GetSparseMetaDataHashDataInfo(),
111 out_header_reader->GetSparseMetaHashType()));
112 } else {
113 // Create the sparse storage.
114 R_TRY(this->CreateSparseStorage(
115 std::addressof(storage), std::addressof(fs_data_offset),
116 ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr,
117 ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
118 fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info));
119 }
120 } else {
121 // Get the data offsets.
122 fs_data_offset = GetFsOffset(*m_reader, fs_index);
123 const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index);
124
125 // Validate that we're within range.
126 const auto data_size = fs_end_offset - fs_data_offset;
127 R_UNLESS(data_size > 0, ResultInvalidNcaHeader);
128
129 // Create the body substorage.
130 R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size));
131
132 // Potentially save the body substorage to our context.
133 if (ctx != nullptr) {
134 ctx->body_substorage = storage;
135 }
136 }
137
138 // Process patch layer.
139 const auto& patch_info = out_header_reader->GetPatchInfo();
140 VirtualFile patch_meta_aes_ctr_ex_meta_storage;
141 VirtualFile patch_meta_indirect_meta_storage;
142 if (out_header_reader->ExistsPatchMetaHashLayer()) {
143 // Check the meta hash type.
144 R_UNLESS(out_header_reader->GetPatchMetaHashType() ==
145 NcaFsHeader::MetaDataHashType::HierarchicalIntegrity,
146 ResultRomNcaInvalidPatchMetaDataHashType);
147
148 // Create the patch meta storage.
149 R_TRY(this->CreatePatchMetaStorage(
150 std::addressof(patch_meta_aes_ctr_ex_meta_storage),
151 std::addressof(patch_meta_indirect_meta_storage),
152 ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage,
153 fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info,
154 out_header_reader->GetPatchMetaDataHashDataInfo()));
155 }
156
157 if (patch_info.HasAesCtrExTable()) {
158 // Check the encryption type.
159 ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None ||
160 out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx ||
161 out_header_reader->GetEncryptionType() ==
162 NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
163
164 // Create the ex meta storage.
165 VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage;
166 if (aes_ctr_ex_storage_meta_storage == nullptr) {
167 // If we don't have a meta storage, we must not have a patch meta hash layer.
168 ASSERT(!out_header_reader->ExistsPatchMetaHashLayer());
169
170 R_TRY(this->CreateAesCtrExStorageMetaStorage(
171 std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset,
172 out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(),
173 patch_info));
174 }
175
176 // Create the ex storage.
177 VirtualFile aes_ctr_ex_storage;
178 R_TRY(this->CreateAesCtrExStorage(
179 std::addressof(aes_ctr_ex_storage),
180 ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage),
181 aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
182 patch_info));
183
184 // Set the base storage as the ex storage.
185 storage = std::move(aes_ctr_ex_storage);
186
187 // Potentially save storages to our context.
188 if (ctx != nullptr) {
189 ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage;
190 ctx->aes_ctr_ex_storage_data_storage = storage;
191 ctx->fs_data_storage = storage;
192 }
193 } else {
194 // Create the appropriate storage for the encryption type.
195 switch (out_header_reader->GetEncryptionType()) {
196 case NcaFsHeader::EncryptionType::None:
197 // If there's no encryption, use the base storage we made previously.
198 break;
199 case NcaFsHeader::EncryptionType::AesXts:
200 R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
201 fs_data_offset));
202 break;
203 case NcaFsHeader::EncryptionType::AesCtr:
204 R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
205 fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
206 AlignmentStorageRequirement::None));
207 break;
208 case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: {
209 // Create the aes ctr storage.
210 VirtualFile aes_ctr_storage;
211 R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage,
212 fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
213 AlignmentStorageRequirement::None));
214
215 // Create region switch storage.
216 R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
217 std::move(storage), std::move(aes_ctr_storage)));
218 } break;
219 default:
220 R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
221 }
222
223 // Potentially save storages to our context.
224 if (ctx != nullptr) {
225 ctx->fs_data_storage = storage;
226 }
227 }
228
229 // Process indirect layer.
230 if (patch_info.HasIndirectTable()) {
231 // Create the indirect meta storage.
232 VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage;
233 if (indirect_storage_meta_storage == nullptr) {
234 // If we don't have a meta storage, we must not have a patch meta hash layer.
235 ASSERT(!out_header_reader->ExistsPatchMetaHashLayer());
236
237 R_TRY(this->CreateIndirectStorageMetaStorage(
238 std::addressof(indirect_storage_meta_storage), storage, patch_info));
239 }
240
241 // Potentially save the indirect meta storage to our context.
242 if (ctx != nullptr) {
243 ctx->indirect_storage_meta_storage = indirect_storage_meta_storage;
244 }
245
246 // Get the original indirectable storage.
247 VirtualFile original_indirectable_storage;
248 if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) {
249 // Create a driver for the original.
250 NcaFileSystemDriver original_driver(m_original_reader);
251
252 // Create a header reader for the original.
253 NcaFsHeaderReader original_header_reader;
254 R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index));
255
256 // Open original indirectable storage.
257 R_TRY(original_driver.OpenIndirectableStorageAsOriginal(
258 std::addressof(original_indirectable_storage),
259 std::addressof(original_header_reader), ctx));
260 } else if (ctx != nullptr && ctx->external_original_storage != nullptr) {
261 // Use the external original storage.
262 original_indirectable_storage = ctx->external_original_storage;
263 } else {
264 // Allocate a dummy memory storage as original storage.
265 original_indirectable_storage = std::make_shared<VectorVfsFile>();
266 R_UNLESS(original_indirectable_storage != nullptr,
267 ResultAllocationMemoryFailedAllocateShared);
268 }
269
270 // Create the indirect storage.
271 VirtualFile indirect_storage;
272 R_TRY(this->CreateIndirectStorage(
273 std::addressof(indirect_storage),
274 ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage),
275 std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage),
276 patch_info));
277
278 // Set storage as the indirect storage.
279 storage = std::move(indirect_storage);
280 }
281
282 // Check if we're sparse or requested to skip the integrity layer.
283 if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) {
284 *out = std::move(storage);
285 R_SUCCEED();
286 }
287
288 // Create the non-raw storage.
289 R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx));
290}
291
292Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out,
293 const NcaFsHeaderReader* header_reader,
294 VirtualFile raw_storage,
295 StorageContext* ctx) {
296 // Initialize storage as raw storage.
297 VirtualFile storage = std::move(raw_storage);
298
299 // Process hash/integrity layer.
300 switch (header_reader->GetHashType()) {
301 case NcaFsHeader::HashType::HierarchicalSha256Hash:
302 R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage),
303 header_reader->GetHashData().hierarchical_sha256_data));
304 break;
305 case NcaFsHeader::HashType::HierarchicalIntegrityHash:
306 R_TRY(this->CreateIntegrityVerificationStorage(
307 std::addressof(storage), std::move(storage),
308 header_reader->GetHashData().integrity_meta_info));
309 break;
310 default:
311 R_THROW(ResultInvalidNcaFsHeaderHashType);
312 }
313
314 // Process compression layer.
315 if (header_reader->ExistsCompressionLayer()) {
316 R_TRY(this->CreateCompressedStorage(
317 std::addressof(storage),
318 ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr,
319 ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr,
320 std::move(storage), header_reader->GetCompressionInfo()));
321 }
322
323 // Set output storage.
324 *out = std::move(storage);
325 R_SUCCEED();
326}
327
328Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(
329 VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) {
330 // Get the fs index.
331 const auto fs_index = header_reader->GetFsIndex();
332
333 // Declare the storage we're opening.
334 VirtualFile storage;
335
336 // Process sparse layer.
337 s64 fs_data_offset = 0;
338 if (header_reader->ExistsSparseLayer()) {
339 // Get the sparse info.
340 const auto& sparse_info = header_reader->GetSparseInfo();
341
342 // Create based on whether we have a meta hash layer.
343 if (header_reader->ExistsSparseMetaHashLayer()) {
344 // Create the sparse storage with verification.
345 R_TRY(this->CreateSparseStorageWithVerification(
346 std::addressof(storage), std::addressof(fs_data_offset),
347 ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr,
348 ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
349 ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index,
350 header_reader->GetAesCtrUpperIv(), sparse_info,
351 header_reader->GetSparseMetaDataHashDataInfo(),
352 header_reader->GetSparseMetaHashType()));
353 } else {
354 // Create the sparse storage.
355 R_TRY(this->CreateSparseStorage(
356 std::addressof(storage), std::addressof(fs_data_offset),
357 ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr,
358 ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
359 fs_index, header_reader->GetAesCtrUpperIv(), sparse_info));
360 }
361 } else {
362 // Get the data offsets.
363 fs_data_offset = GetFsOffset(*m_reader, fs_index);
364 const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index);
365
366 // Validate that we're within range.
367 const auto data_size = fs_end_offset - fs_data_offset;
368 R_UNLESS(data_size > 0, ResultInvalidNcaHeader);
369
370 // Create the body substorage.
371 R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size));
372 }
373
374 // Create the appropriate storage for the encryption type.
375 switch (header_reader->GetEncryptionType()) {
376 case NcaFsHeader::EncryptionType::None:
377 // If there's no encryption, use the base storage we made previously.
378 break;
379 case NcaFsHeader::EncryptionType::AesXts:
380 R_TRY(
381 this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset));
382 break;
383 case NcaFsHeader::EncryptionType::AesCtr:
384 R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset,
385 header_reader->GetAesCtrUpperIv(),
386 AlignmentStorageRequirement::CacheBlockSize));
387 break;
388 default:
389 R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
390 }
391
392 // Set output storage.
393 *out = std::move(storage);
394 R_SUCCEED();
395}
396
397Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) {
398 // Create the body storage.
399 auto body_storage =
400 std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader);
401 R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
402
403 // Get the body storage size.
404 s64 body_size = body_storage->GetSize();
405
406 // Check that we're within range.
407 R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB);
408
409 // Create substorage.
410 auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset);
411 R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared);
412
413 // Set the output storage.
414 *out = std::move(body_substorage);
415 R_SUCCEED();
416}
417
418Result NcaFileSystemDriver::CreateAesCtrStorage(
419 VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv,
420 AlignmentStorageRequirement alignment_storage_requirement) {
421 // Check pre-conditions.
422 ASSERT(out != nullptr);
423 ASSERT(base_storage != nullptr);
424
425 // Create the iv.
426 std::array<u8, AesCtrStorage::IvSize> iv{};
427 AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset);
428
429 // Create the ctr storage.
430 VirtualFile aes_ctr_storage;
431 if (m_reader->HasExternalDecryptionKey()) {
432 aes_ctr_storage = std::make_shared<AesCtrStorage>(
433 std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize,
434 iv.data(), AesCtrStorage::IvSize);
435 R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
436 } else {
437 // Create software decryption storage.
438 auto sw_storage = std::make_shared<AesCtrStorage>(
439 base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr),
440 AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize);
441 R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
442
443 aes_ctr_storage = std::move(sw_storage);
444 }
445
446 // Create alignment matching storage.
447 auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>(
448 std::move(aes_ctr_storage));
449 R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
450
451 // Set the out storage.
452 *out = std::move(aligned_storage);
453 R_SUCCEED();
454}
455
456Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage,
457 s64 offset) {
458 // Check pre-conditions.
459 ASSERT(out != nullptr);
460 ASSERT(base_storage != nullptr);
461
462 // Create the iv.
463 std::array<u8, AesXtsStorage::IvSize> iv{};
464 AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize);
465
466 // Make the aes xts storage.
467 const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1);
468 const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2);
469 auto xts_storage =
470 std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize,
471 iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize);
472 R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
473
474 // Create alignment matching storage.
475 auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>(
476 std::move(xts_storage));
477 R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
478
479 // Set the out storage.
480 *out = std::move(xts_storage);
481 R_SUCCEED();
482}
483
484Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out,
485 VirtualFile base_storage, s64 offset,
486 const NcaAesCtrUpperIv& upper_iv,
487 const NcaSparseInfo& sparse_info) {
488 // Validate preconditions.
489 ASSERT(out != nullptr);
490 ASSERT(base_storage != nullptr);
491
492 // Get the base storage size.
493 s64 base_size = base_storage->GetSize();
494
495 // Get the meta extents.
496 const auto meta_offset = sparse_info.bucket.offset;
497 const auto meta_size = sparse_info.bucket.size;
498 R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB);
499
500 // Create the encrypted storage.
501 auto enc_storage =
502 std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset);
503 R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
504
505 // Create the decrypted storage.
506 VirtualFile decrypted_storage;
507 R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
508 offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv),
509 AlignmentStorageRequirement::None));
510
511 // Create buffered storage.
512 std::vector<u8> meta_data(meta_size);
513 decrypted_storage->Read(meta_data.data(), meta_size, 0);
514
515 auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
516 R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
517
518 // Set the output.
519 *out = std::move(buffered_storage);
520 R_SUCCEED();
521}
522
523Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out,
524 VirtualFile base_storage, s64 base_size,
525 VirtualFile meta_storage,
526 const NcaSparseInfo& sparse_info,
527 bool external_info) {
528 // Validate preconditions.
529 ASSERT(out != nullptr);
530 ASSERT(base_storage != nullptr);
531 ASSERT(meta_storage != nullptr);
532
533 // Read and verify the bucket tree header.
534 BucketTree::Header header;
535 std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
536 R_TRY(header.Verify());
537
538 // Determine storage extents.
539 const auto node_offset = 0;
540 const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count);
541 const auto entry_offset = node_offset + node_size;
542 const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count);
543
544 // Create the sparse storage.
545 auto sparse_storage = std::make_shared<SparseStorage>();
546 R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
547
548 // Sanity check that we can be doing this.
549 ASSERT(header.entry_count != 0);
550
551 // Initialize the sparse storage.
552 R_TRY(sparse_storage->Initialize(
553 std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset),
554 std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset),
555 header.entry_count));
556
557 // If not external, set the data storage.
558 if (!external_info) {
559 sparse_storage->SetDataStorage(
560 std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0));
561 }
562
563 // Set the output.
564 *out = std::move(sparse_storage);
565 R_SUCCEED();
566}
567
568Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset,
569 std::shared_ptr<SparseStorage>* out_sparse_storage,
570 VirtualFile* out_meta_storage, s32 index,
571 const NcaAesCtrUpperIv& upper_iv,
572 const NcaSparseInfo& sparse_info) {
573 // Validate preconditions.
574 ASSERT(out != nullptr);
575 ASSERT(out_fs_data_offset != nullptr);
576
577 // Check the sparse info generation.
578 R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader);
579
580 // Read and verify the bucket tree header.
581 BucketTree::Header header;
582 std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
583 R_TRY(header.Verify());
584
585 // Determine the storage extents.
586 const auto fs_offset = GetFsOffset(*m_reader, index);
587 const auto fs_end_offset = GetFsEndOffset(*m_reader, index);
588 const auto fs_size = fs_end_offset - fs_offset;
589
590 // Create the sparse storage.
591 std::shared_ptr<SparseStorage> sparse_storage;
592 if (header.entry_count != 0) {
593 // Create the body substorage.
594 VirtualFile body_substorage;
595 R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage),
596 sparse_info.physical_offset,
597 sparse_info.GetPhysicalSize()));
598
599 // Create the meta storage.
600 VirtualFile meta_storage;
601 R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage,
602 sparse_info.physical_offset, upper_iv,
603 sparse_info));
604
605 // Potentially set the output meta storage.
606 if (out_meta_storage != nullptr) {
607 *out_meta_storage = meta_storage;
608 }
609
610 // Create the sparse storage.
611 R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage,
612 sparse_info.GetPhysicalSize(), std::move(meta_storage),
613 sparse_info, false));
614 } else {
615 // If there are no entries, there's nothing to actually do.
616 sparse_storage = std::make_shared<SparseStorage>();
617 R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
618
619 sparse_storage->Initialize(fs_size);
620 }
621
622 // Potentially set the output sparse storage.
623 if (out_sparse_storage != nullptr) {
624 *out_sparse_storage = sparse_storage;
625 }
626
627 // Set the output fs data offset.
628 *out_fs_data_offset = fs_offset;
629
630 // Set the output storage.
631 *out = std::move(sparse_storage);
632 R_SUCCEED();
633}
634
635Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification(
636 VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
637 const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
638 const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
639 // Validate preconditions.
640 ASSERT(out != nullptr);
641 ASSERT(base_storage != nullptr);
642
643 // Get the base storage size.
644 s64 base_size = base_storage->GetSize();
645
646 // Get the meta extents.
647 const auto meta_offset = sparse_info.bucket.offset;
648 const auto meta_size = sparse_info.bucket.size;
649 R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB);
650
651 // Get the meta data hash data extents.
652 const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset;
653 const s64 meta_data_hash_data_size =
654 Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize);
655 R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size,
656 ResultNcaBaseStorageOutOfRangeB);
657
658 // Check that the meta is before the hash data.
659 R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset,
660 ResultRomNcaInvalidSparseMetaDataHashDataOffset);
661
662 // Check that offsets are appropriately aligned.
663 R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize),
664 ResultRomNcaInvalidSparseMetaDataHashDataOffset);
665 R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize),
666 ResultInvalidNcaFsHeader);
667
668 // Create the meta storage.
669 auto enc_storage = std::make_shared<OffsetVfsFile>(
670 std::move(base_storage),
671 meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset);
672 R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
673
674 // Create the decrypted storage.
675 VirtualFile decrypted_storage;
676 R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
677 offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv),
678 AlignmentStorageRequirement::None));
679
680 // Create the verification storage.
681 VirtualFile integrity_storage;
682 Result rc = this->CreateIntegrityVerificationStorageForMeta(
683 std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage),
684 meta_offset, meta_data_hash_data_info);
685 if (rc == ResultInvalidNcaMetaDataHashDataSize) {
686 R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize);
687 }
688 if (rc == ResultInvalidNcaMetaDataHashDataHash) {
689 R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash);
690 }
691 R_TRY(rc);
692
693 // Create the meta storage.
694 auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0);
695 R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
696
697 // Set the output.
698 *out = std::move(meta_storage);
699 R_SUCCEED();
700}
701
702Result NcaFileSystemDriver::CreateSparseStorageWithVerification(
703 VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage,
704 VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index,
705 const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
706 const NcaMetaDataHashDataInfo& meta_data_hash_data_info,
707 NcaFsHeader::MetaDataHashType meta_data_hash_type) {
708 // Validate preconditions.
709 ASSERT(out != nullptr);
710 ASSERT(out_fs_data_offset != nullptr);
711
712 // Check the sparse info generation.
713 R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader);
714
715 // Read and verify the bucket tree header.
716 BucketTree::Header header;
717 std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
718 R_TRY(header.Verify());
719
720 // Determine the storage extents.
721 const auto fs_offset = GetFsOffset(*m_reader, index);
722 const auto fs_end_offset = GetFsEndOffset(*m_reader, index);
723 const auto fs_size = fs_end_offset - fs_offset;
724
725 // Create the sparse storage.
726 std::shared_ptr<SparseStorage> sparse_storage;
727 if (header.entry_count != 0) {
728 // Create the body substorage.
729 VirtualFile body_substorage;
730 R_TRY(this->CreateBodySubStorage(
731 std::addressof(body_substorage), sparse_info.physical_offset,
732 Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) +
733 static_cast<s64>(meta_data_hash_data_info.size),
734 NcaHeader::CtrBlockSize)));
735
736 // Check the meta data hash type.
737 R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity,
738 ResultRomNcaInvalidSparseMetaDataHashType);
739
740 // Create the meta storage.
741 VirtualFile meta_storage;
742 R_TRY(this->CreateSparseStorageMetaStorageWithVerification(
743 std::addressof(meta_storage), out_layer_info_storage, body_substorage,
744 sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info));
745
746 // Potentially set the output meta storage.
747 if (out_meta_storage != nullptr) {
748 *out_meta_storage = meta_storage;
749 }
750
751 // Create the sparse storage.
752 R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage,
753 sparse_info.GetPhysicalSize(), std::move(meta_storage),
754 sparse_info, false));
755 } else {
756 // If there are no entries, there's nothing to actually do.
757 sparse_storage = std::make_shared<SparseStorage>();
758 R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
759
760 sparse_storage->Initialize(fs_size);
761 }
762
763 // Potentially set the output sparse storage.
764 if (out_sparse_storage != nullptr) {
765 *out_sparse_storage = sparse_storage;
766 }
767
768 // Set the output fs data offset.
769 *out_fs_data_offset = fs_offset;
770
771 // Set the output storage.
772 *out = std::move(sparse_storage);
773 R_SUCCEED();
774}
775
776Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(
777 VirtualFile* out, VirtualFile base_storage, s64 offset,
778 NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv,
779 const NcaPatchInfo& patch_info) {
780 // Validate preconditions.
781 ASSERT(out != nullptr);
782 ASSERT(base_storage != nullptr);
783 ASSERT(encryption_type == NcaFsHeader::EncryptionType::None ||
784 encryption_type == NcaFsHeader::EncryptionType::AesCtrEx ||
785 encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
786 ASSERT(patch_info.HasAesCtrExTable());
787
788 // Validate patch info extents.
789 R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize);
790 R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize);
791 R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset,
792 ResultInvalidNcaPatchInfoAesCtrExOffset);
793
794 // Get the base storage size.
795 s64 base_size = base_storage->GetSize();
796
797 // Get and validate the meta extents.
798 const s64 meta_offset = patch_info.aes_ctr_ex_offset;
799 const s64 meta_size =
800 Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize);
801 R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB);
802
803 // Create the encrypted storage.
804 auto enc_storage =
805 std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset);
806 R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
807
808 // Create the decrypted storage.
809 VirtualFile decrypted_storage;
810 if (encryption_type != NcaFsHeader::EncryptionType::None) {
811 R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
812 offset + meta_offset, upper_iv,
813 AlignmentStorageRequirement::None));
814 } else {
815 // If encryption type is none, don't do any decryption.
816 decrypted_storage = std::move(enc_storage);
817 }
818
819 // Create meta storage.
820 auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0);
821 R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
822
823 // Create buffered storage.
824 std::vector<u8> meta_data(meta_size);
825 meta_storage->Read(meta_data.data(), meta_size, 0);
826
827 auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
828 R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
829
830 // Set the output.
831 *out = std::move(buffered_storage);
832 R_SUCCEED();
833}
834
835Result NcaFileSystemDriver::CreateAesCtrExStorage(
836 VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext,
837 VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset,
838 const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) {
839 // Validate pre-conditions.
840 ASSERT(out != nullptr);
841 ASSERT(base_storage != nullptr);
842 ASSERT(meta_storage != nullptr);
843 ASSERT(patch_info.HasAesCtrExTable());
844
845 // Read the bucket tree header.
846 BucketTree::Header header;
847 std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header));
848 R_TRY(header.Verify());
849
850 // Determine the bucket extents.
851 const auto entry_count = header.entry_count;
852 const s64 data_offset = 0;
853 const s64 data_size = patch_info.aes_ctr_ex_offset;
854 const s64 node_offset = 0;
855 const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count);
856 const s64 entry_offset = node_offset + node_size;
857 const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count);
858
859 // Create bucket storages.
860 auto data_storage =
861 std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset);
862 auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset);
863 auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset);
864
865 // Get the secure value.
866 const auto secure_value = upper_iv.part.secure_value;
867
868 // Create the aes ctr ex storage.
869 VirtualFile aes_ctr_ex_storage;
870 if (m_reader->HasExternalDecryptionKey()) {
871 // Create the decryptor.
872 std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor;
873 R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor)));
874
875 // Create the aes ctr ex storage.
876 auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>();
877 R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
878
879 // Initialize the aes ctr ex storage.
880 R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize,
881 secure_value, counter_offset, data_storage, node_storage,
882 entry_storage, entry_count, std::move(decryptor)));
883
884 // Potentially set the output implementation storage.
885 if (out_ext != nullptr) {
886 *out_ext = impl_storage;
887 }
888
889 // Set the implementation storage.
890 aes_ctr_ex_storage = std::move(impl_storage);
891 } else {
892 // Create the software decryptor.
893 std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor;
894 R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
895
896 // Make the software storage.
897 auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>();
898 R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
899
900 // Initialize the software storage.
901 R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr),
902 AesCtrStorage::KeySize, secure_value, counter_offset,
903 data_storage, node_storage, entry_storage, entry_count,
904 std::move(sw_decryptor)));
905
906 // Potentially set the output implementation storage.
907 if (out_ext != nullptr) {
908 *out_ext = sw_storage;
909 }
910
911 // Set the implementation storage.
912 aes_ctr_ex_storage = std::move(sw_storage);
913 }
914
915 // Create an alignment-matching storage.
916 using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>;
917 auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage));
918 R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
919
920 // Set the output.
921 *out = std::move(aligned_storage);
922 R_SUCCEED();
923}
924
925Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out,
926 VirtualFile base_storage,
927 const NcaPatchInfo& patch_info) {
928 // Validate preconditions.
929 ASSERT(out != nullptr);
930 ASSERT(base_storage != nullptr);
931 ASSERT(patch_info.HasIndirectTable());
932
933 // Get the base storage size.
934 s64 base_size = base_storage->GetSize();
935
936 // Check that we're within range.
937 R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size,
938 ResultNcaBaseStorageOutOfRangeE);
939
940 // Create the meta storage.
941 auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size,
942 patch_info.indirect_offset);
943 R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
944
945 // Create buffered storage.
946 std::vector<u8> meta_data(patch_info.indirect_size);
947 meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0);
948
949 auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
950 R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
951
952 // Set the output.
953 *out = std::move(buffered_storage);
954 R_SUCCEED();
955}
956
957Result NcaFileSystemDriver::CreateIndirectStorage(
958 VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage,
959 VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) {
960 // Validate preconditions.
961 ASSERT(out != nullptr);
962 ASSERT(base_storage != nullptr);
963 ASSERT(meta_storage != nullptr);
964 ASSERT(patch_info.HasIndirectTable());
965
966 // Read the bucket tree header.
967 BucketTree::Header header;
968 std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header));
969 R_TRY(header.Verify());
970
971 // Determine the storage sizes.
972 const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count);
973 const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count);
974 R_UNLESS(node_size + entry_size <= patch_info.indirect_size,
975 ResultInvalidNcaIndirectStorageOutOfRange);
976
977 // Get the indirect data size.
978 const s64 indirect_data_size = patch_info.indirect_offset;
979 ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize));
980
981 // Create the indirect data storage.
982 auto indirect_data_storage =
983 std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0);
984 R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
985
986 // Create the indirect storage.
987 auto indirect_storage = std::make_shared<IndirectStorage>();
988 R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
989
990 // Initialize the indirect storage.
991 R_TRY(indirect_storage->Initialize(
992 std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0),
993 std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count));
994
995 // Get the original data size.
996 s64 original_data_size = original_data_storage->GetSize();
997
998 // Set the indirect storages.
999 indirect_storage->SetStorage(
1000 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0));
1001 indirect_storage->SetStorage(
1002 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0));
1003
1004 // If necessary, set the output indirect storage.
1005 if (out_ind != nullptr) {
1006 *out_ind = indirect_storage;
1007 }
1008
1009 // Set the output.
1010 *out = std::move(indirect_storage);
1011 R_SUCCEED();
1012}
1013
1014Result NcaFileSystemDriver::CreatePatchMetaStorage(
1015 VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta,
1016 VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
1017 const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info,
1018 const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
1019 // Validate preconditions.
1020 ASSERT(out_aes_ctr_ex_meta != nullptr);
1021 ASSERT(out_indirect_meta != nullptr);
1022 ASSERT(base_storage != nullptr);
1023 ASSERT(patch_info.HasAesCtrExTable());
1024 ASSERT(patch_info.HasIndirectTable());
1025 ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize));
1026
1027 // Validate patch info extents.
1028 R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize);
1029 R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize);
1030 R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset,
1031 ResultInvalidNcaPatchInfoAesCtrExOffset);
1032 R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <=
1033 meta_data_hash_data_info.offset,
1034 ResultRomNcaInvalidPatchMetaDataHashDataOffset);
1035
1036 // Get the base storage size.
1037 s64 base_size = base_storage->GetSize();
1038
1039 // Check that extents remain within range.
1040 R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size,
1041 ResultNcaBaseStorageOutOfRangeE);
1042 R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size,
1043 ResultNcaBaseStorageOutOfRangeB);
1044
1045 // Check that metadata hash data extents remain within range.
1046 const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset;
1047 const s64 meta_data_hash_data_size =
1048 Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize);
1049 R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size,
1050 ResultNcaBaseStorageOutOfRangeB);
1051
1052 // Create the encrypted storage.
1053 auto enc_storage = std::make_shared<OffsetVfsFile>(
1054 std::move(base_storage),
1055 meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset,
1056 patch_info.indirect_offset);
1057 R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1058
1059 // Create the decrypted storage.
1060 VirtualFile decrypted_storage;
1061 R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
1062 offset + patch_info.indirect_offset, upper_iv,
1063 AlignmentStorageRequirement::None));
1064
1065 // Create the verification storage.
1066 VirtualFile integrity_storage;
1067 Result rc = this->CreateIntegrityVerificationStorageForMeta(
1068 std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage),
1069 patch_info.indirect_offset, meta_data_hash_data_info);
1070 if (rc == ResultInvalidNcaMetaDataHashDataSize) {
1071 R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize);
1072 }
1073 if (rc == ResultInvalidNcaMetaDataHashDataHash) {
1074 R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash);
1075 }
1076 R_TRY(rc);
1077
1078 // Create the indirect meta storage.
1079 auto indirect_meta_storage =
1080 std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size,
1081 patch_info.indirect_offset - patch_info.indirect_offset);
1082 R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1083
1084 // Create the aes ctr ex meta storage.
1085 auto aes_ctr_ex_meta_storage =
1086 std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size,
1087 patch_info.aes_ctr_ex_offset - patch_info.indirect_offset);
1088 R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1089
1090 // Set the output.
1091 *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage);
1092 *out_indirect_meta = std::move(indirect_meta_storage);
1093 R_SUCCEED();
1094}
1095
1096Result NcaFileSystemDriver::CreateSha256Storage(
1097 VirtualFile* out, VirtualFile base_storage,
1098 const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) {
1099 // Validate preconditions.
1100 ASSERT(out != nullptr);
1101 ASSERT(base_storage != nullptr);
1102
1103 // Define storage types.
1104 using VerificationStorage = HierarchicalSha256Storage;
1105
1106 // Validate the hash data.
1107 R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size),
1108 ResultInvalidHierarchicalSha256BlockSize);
1109 R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1,
1110 ResultInvalidHierarchicalSha256LayerCount);
1111
1112 // Get the regions.
1113 const auto& hash_region = hash_data.hash_layer_region[0];
1114 const auto& data_region = hash_data.hash_layer_region[1];
1115
1116 // Determine buffer sizes.
1117 constexpr s32 CacheBlockCount = 2;
1118 const auto hash_buffer_size = static_cast<size_t>(hash_region.size);
1119 const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size;
1120 const auto total_buffer_size = hash_buffer_size + cache_buffer_size;
1121
1122 // Make a buffer holder storage.
1123 auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>(
1124 std::move(base_storage), total_buffer_size);
1125 R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1126 R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI);
1127
1128 // Get storage size.
1129 s64 base_size = buffer_hold_storage->GetSize();
1130
1131 // Check that we're within range.
1132 R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC);
1133 R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC);
1134
1135 // Create the master hash storage.
1136 auto master_hash_storage =
1137 std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value);
1138
1139 // Make the verification storage.
1140 auto verification_storage = std::make_shared<VerificationStorage>();
1141 R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1142
1143 // Make layer storages.
1144 std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{
1145 std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0),
1146 std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset),
1147 std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset),
1148 };
1149
1150 // Initialize the verification storage.
1151 R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount,
1152 hash_data.hash_block_size,
1153 buffer_hold_storage->GetBuffer(), hash_buffer_size));
1154
1155 // Set the output.
1156 *out = std::move(verification_storage);
1157 R_SUCCEED();
1158}
1159
1160Result NcaFileSystemDriver::CreateIntegrityVerificationStorage(
1161 VirtualFile* out, VirtualFile base_storage,
1162 const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) {
1163 R_RETURN(this->CreateIntegrityVerificationStorageImpl(
1164 out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount,
1165 HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel(
1166 meta_info.level_hash_info.max_layers)));
1167}
1168
1169Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta(
1170 VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
1171 const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
1172 // Validate preconditions.
1173 ASSERT(out != nullptr);
1174
1175 // Check the meta data hash data size.
1176 R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData),
1177 ResultInvalidNcaMetaDataHashDataSize);
1178
1179 // Read the meta data hash data.
1180 NcaMetaDataHashData meta_data_hash_data;
1181 base_storage->ReadObject(std::addressof(meta_data_hash_data),
1182 meta_data_hash_data_info.offset - offset);
1183
1184 // Set the out layer info storage, if necessary.
1185 if (out_layer_info_storage != nullptr) {
1186 auto layer_info_storage = std::make_shared<OffsetVfsFile>(
1187 base_storage,
1188 meta_data_hash_data_info.offset + meta_data_hash_data_info.size -
1189 meta_data_hash_data.layer_info_offset,
1190 meta_data_hash_data.layer_info_offset - offset);
1191 R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1192
1193 *out_layer_info_storage = std::move(layer_info_storage);
1194 }
1195
1196 // Create the meta storage.
1197 auto meta_storage = std::make_shared<OffsetVfsFile>(
1198 std::move(base_storage), meta_data_hash_data_info.offset - offset, 0);
1199 R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1200
1201 // Create the integrity verification storage.
1202 R_RETURN(this->CreateIntegrityVerificationStorageImpl(
1203 out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info,
1204 meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta,
1205 IntegrityHashCacheCountForMeta, 0));
1206}
1207
1208Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl(
1209 VirtualFile* out, VirtualFile base_storage,
1210 const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset,
1211 int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) {
1212 // Validate preconditions.
1213 ASSERT(out != nullptr);
1214 ASSERT(base_storage != nullptr);
1215 ASSERT(layer_info_offset >= 0);
1216
1217 // Define storage types.
1218 using VerificationStorage = HierarchicalIntegrityVerificationStorage;
1219 using StorageInfo = VerificationStorage::HierarchicalStorageInformation;
1220
1221 // Validate the meta info.
1222 HierarchicalIntegrityVerificationInformation level_hash_info;
1223 std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info),
1224 sizeof(level_hash_info));
1225
1226 R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers,
1227 ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
1228 R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount,
1229 ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
1230
1231 // Get the base storage size.
1232 s64 base_storage_size = base_storage->GetSize();
1233
1234 // Create storage info.
1235 StorageInfo storage_info;
1236 for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) {
1237 const auto& layer_info = level_hash_info.info[i];
1238 R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size,
1239 ResultNcaBaseStorageOutOfRangeD);
1240
1241 storage_info[i + 1] = std::make_shared<OffsetVfsFile>(
1242 base_storage, layer_info.size, layer_info_offset + layer_info.offset);
1243 }
1244
1245 // Set the last layer info.
1246 const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2];
1247 const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get();
1248 R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size,
1249 ResultNcaBaseStorageOutOfRangeD);
1250 if (layer_info_offset > 0) {
1251 R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset,
1252 ResultRomNcaInvalidIntegrityLayerInfoOffset);
1253 }
1254 storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>(
1255 std::move(base_storage), layer_info.size, last_layer_info_offset));
1256
1257 // Make the integrity romfs storage.
1258 auto integrity_storage = std::make_shared<IntegrityRomFsStorage>();
1259 R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1260
1261 // Initialize the integrity storage.
1262 R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info,
1263 max_data_cache_entries, max_hash_cache_entries,
1264 buffer_level));
1265
1266 // Set the output.
1267 *out = std::move(integrity_storage);
1268 R_SUCCEED();
1269}
1270
1271Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out,
1272 const NcaFsHeaderReader* header_reader,
1273 VirtualFile inside_storage,
1274 VirtualFile outside_storage) {
1275 // Check pre-conditions.
1276 ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash);
1277
1278 // Create the region.
1279 RegionSwitchStorage::Region region = {};
1280 R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size)));
1281
1282 // Create the region switch storage.
1283 auto region_switch_storage = std::make_shared<RegionSwitchStorage>(
1284 std::move(inside_storage), std::move(outside_storage), region);
1285 R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1286
1287 // Set the output.
1288 *out = std::move(region_switch_storage);
1289 R_SUCCEED();
1290}
1291
1292Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out,
1293 std::shared_ptr<CompressedStorage>* out_cmp,
1294 VirtualFile* out_meta, VirtualFile base_storage,
1295 const NcaCompressionInfo& compression_info) {
1296 R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage),
1297 compression_info, m_reader->GetDecompressor()));
1298}
1299
1300Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out,
1301 std::shared_ptr<CompressedStorage>* out_cmp,
1302 VirtualFile* out_meta, VirtualFile base_storage,
1303 const NcaCompressionInfo& compression_info,
1304 GetDecompressorFunction get_decompressor) {
1305 // Check pre-conditions.
1306 ASSERT(out != nullptr);
1307 ASSERT(base_storage != nullptr);
1308 ASSERT(get_decompressor != nullptr);
1309
1310 // Read and verify the bucket tree header.
1311 BucketTree::Header header;
1312 std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header));
1313 R_TRY(header.Verify());
1314
1315 // Determine the storage extents.
1316 const auto table_offset = compression_info.bucket.offset;
1317 const auto table_size = compression_info.bucket.size;
1318 const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count);
1319 const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count);
1320 R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize);
1321
1322 // If we should, set the output meta storage.
1323 if (out_meta != nullptr) {
1324 auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset);
1325 R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1326
1327 *out_meta = std::move(meta_storage);
1328 }
1329
1330 // Allocate the compressed storage.
1331 auto compressed_storage = std::make_shared<CompressedStorage>();
1332 R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
1333
1334 // Initialize the compressed storage.
1335 R_TRY(compressed_storage->Initialize(
1336 std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0),
1337 std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset),
1338 std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size),
1339 header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32));
1340
1341 // Potentially set the output compressed storage.
1342 if (out_cmp) {
1343 *out_cmp = compressed_storage;
1344 }
1345
1346 // Set the output.
1347 *out = std::move(compressed_storage);
1348 R_SUCCEED();
1349}
1350
1351} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h
new file mode 100644
index 000000000..5771a21fc
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h
@@ -0,0 +1,364 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/fssystem/fssystem_compression_common.h"
7#include "core/file_sys/fssystem/fssystem_nca_header.h"
8#include "core/file_sys/vfs.h"
9
10namespace FileSys {
11
12class CompressedStorage;
13class AesCtrCounterExtendedStorage;
14class IndirectStorage;
15class SparseStorage;
16
17struct NcaCryptoConfiguration;
18
19using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key,
20 size_t src_key_size, s32 key_type);
21using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data,
22 size_t data_size, u8 generation);
23
24struct NcaCryptoConfiguration {
25 static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8;
26 static constexpr size_t Rsa2048KeyPublicExponentSize = 3;
27 static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize;
28
29 static constexpr size_t Aes128KeySize = 128 / 8;
30
31 static constexpr size_t Header1SignatureKeyGenerationMax = 1;
32
33 static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3;
34 static constexpr s32 HeaderEncryptionKeyCount = 2;
35
36 static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF;
37
38 static constexpr size_t KeyGenerationMax = 32;
39
40 std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli;
41 std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent;
42 std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount>
43 key_area_encryption_key_source;
44 std::array<u8, Aes128KeySize> header_encryption_key_source;
45 std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount>
46 header_encrypted_encryption_keys;
47 KeyGenerationFunction generate_key;
48 VerifySign1Function verify_sign1;
49 bool is_plaintext_header_available;
50 bool is_available_sw_key;
51};
52static_assert(std::is_trivial_v<NcaCryptoConfiguration>);
53
54struct NcaCompressionConfiguration {
55 GetDecompressorFunction get_decompressor;
56};
57static_assert(std::is_trivial_v<NcaCompressionConfiguration>);
58
59constexpr inline s32 KeyAreaEncryptionKeyCount =
60 NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount *
61 NcaCryptoConfiguration::KeyGenerationMax;
62
63enum class KeyType : s32 {
64 ZeroKey = -2,
65 InvalidKey = -1,
66 NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0,
67 NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1,
68 NcaExternalKey = KeyAreaEncryptionKeyCount + 2,
69 SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3,
70 SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4,
71 SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5,
72};
73
74constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) {
75 return key_type < 0;
76}
77
78constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) {
79 if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) {
80 return static_cast<s32>(KeyType::ZeroKey);
81 }
82
83 if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) {
84 return static_cast<s32>(KeyType::InvalidKey);
85 }
86
87 return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index;
88}
89
90class NcaReader {
91 YUZU_NON_COPYABLE(NcaReader);
92 YUZU_NON_MOVEABLE(NcaReader);
93
94public:
95 NcaReader();
96 ~NcaReader();
97
98 Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg,
99 const NcaCompressionConfiguration& compression_cfg);
100
101 VirtualFile GetSharedBodyStorage();
102 u32 GetMagic() const;
103 NcaHeader::DistributionType GetDistributionType() const;
104 NcaHeader::ContentType GetContentType() const;
105 u8 GetHeaderSign1KeyGeneration() const;
106 u8 GetKeyGeneration() const;
107 u8 GetKeyIndex() const;
108 u64 GetContentSize() const;
109 u64 GetProgramId() const;
110 u32 GetContentIndex() const;
111 u32 GetSdkAddonVersion() const;
112 void GetRightsId(u8* dst, size_t dst_size) const;
113 bool HasFsInfo(s32 index) const;
114 s32 GetFsCount() const;
115 const Hash& GetFsHeaderHash(s32 index) const;
116 void GetFsHeaderHash(Hash* dst, s32 index) const;
117 void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const;
118 u64 GetFsOffset(s32 index) const;
119 u64 GetFsEndOffset(s32 index) const;
120 u64 GetFsSize(s32 index) const;
121 void GetEncryptedKey(void* dst, size_t size) const;
122 const void* GetDecryptionKey(s32 index) const;
123 bool HasValidInternalKey() const;
124 bool HasInternalDecryptionKeyForAesHw() const;
125 bool IsSoftwareAesPrioritized() const;
126 void PrioritizeSoftwareAes();
127 bool IsAvailableSwKey() const;
128 bool HasExternalDecryptionKey() const;
129 const void* GetExternalDecryptionKey() const;
130 void SetExternalDecryptionKey(const void* src, size_t size);
131 void GetRawData(void* dst, size_t dst_size) const;
132 NcaHeader::EncryptionType GetEncryptionType() const;
133 Result ReadHeader(NcaFsHeader* dst, s32 index) const;
134
135 GetDecompressorFunction GetDecompressor() const;
136
137 bool GetHeaderSign1Valid() const;
138
139 void GetHeaderSign2(void* dst, size_t size) const;
140
141private:
142 NcaHeader m_header;
143 std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>,
144 NcaHeader::DecryptionKey_Count>
145 m_decryption_keys;
146 VirtualFile m_body_storage;
147 VirtualFile m_header_storage;
148 std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key;
149 bool m_is_software_aes_prioritized;
150 bool m_is_available_sw_key;
151 NcaHeader::EncryptionType m_header_encryption_type;
152 bool m_is_header_sign1_signature_valid;
153 GetDecompressorFunction m_get_decompressor;
154};
155
156class NcaFsHeaderReader {
157 YUZU_NON_COPYABLE(NcaFsHeaderReader);
158 YUZU_NON_MOVEABLE(NcaFsHeaderReader);
159
160public:
161 NcaFsHeaderReader() : m_fs_index(-1) {
162 std::memset(std::addressof(m_data), 0, sizeof(m_data));
163 }
164
165 Result Initialize(const NcaReader& reader, s32 index);
166 bool IsInitialized() const {
167 return m_fs_index >= 0;
168 }
169
170 void GetRawData(void* dst, size_t dst_size) const;
171
172 NcaFsHeader::HashData& GetHashData();
173 const NcaFsHeader::HashData& GetHashData() const;
174 u16 GetVersion() const;
175 s32 GetFsIndex() const;
176 NcaFsHeader::FsType GetFsType() const;
177 NcaFsHeader::HashType GetHashType() const;
178 NcaFsHeader::EncryptionType GetEncryptionType() const;
179 NcaPatchInfo& GetPatchInfo();
180 const NcaPatchInfo& GetPatchInfo() const;
181 const NcaAesCtrUpperIv GetAesCtrUpperIv() const;
182
183 bool IsSkipLayerHashEncryption() const;
184 Result GetHashTargetOffset(s64* out) const;
185
186 bool ExistsSparseLayer() const;
187 NcaSparseInfo& GetSparseInfo();
188 const NcaSparseInfo& GetSparseInfo() const;
189
190 bool ExistsCompressionLayer() const;
191 NcaCompressionInfo& GetCompressionInfo();
192 const NcaCompressionInfo& GetCompressionInfo() const;
193
194 bool ExistsPatchMetaHashLayer() const;
195 NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo();
196 const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const;
197 NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const;
198
199 bool ExistsSparseMetaHashLayer() const;
200 NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo();
201 const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const;
202 NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const;
203
204private:
205 NcaFsHeader m_data;
206 s32 m_fs_index;
207};
208
209class NcaFileSystemDriver {
210 YUZU_NON_COPYABLE(NcaFileSystemDriver);
211 YUZU_NON_MOVEABLE(NcaFileSystemDriver);
212
213public:
214 struct StorageContext {
215 bool open_raw_storage;
216 VirtualFile body_substorage;
217 std::shared_ptr<SparseStorage> current_sparse_storage;
218 VirtualFile sparse_storage_meta_storage;
219 std::shared_ptr<SparseStorage> original_sparse_storage;
220 void* external_current_sparse_storage;
221 void* external_original_sparse_storage;
222 VirtualFile aes_ctr_ex_storage_meta_storage;
223 VirtualFile aes_ctr_ex_storage_data_storage;
224 std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage;
225 VirtualFile indirect_storage_meta_storage;
226 std::shared_ptr<IndirectStorage> indirect_storage;
227 VirtualFile fs_data_storage;
228 VirtualFile compressed_storage_meta_storage;
229 std::shared_ptr<CompressedStorage> compressed_storage;
230
231 VirtualFile patch_layer_info_storage;
232 VirtualFile sparse_layer_info_storage;
233
234 VirtualFile external_original_storage;
235 };
236
237private:
238 enum class AlignmentStorageRequirement {
239 CacheBlockSize = 0,
240 None = 1,
241 };
242
243public:
244 static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader,
245 s32 fs_index);
246
247public:
248 NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) {
249 ASSERT(m_reader != nullptr);
250 }
251
252 NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader,
253 std::shared_ptr<NcaReader> reader)
254 : m_original_reader(original_reader), m_reader(reader) {
255 ASSERT(m_reader != nullptr);
256 }
257
258 Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader,
259 s32 fs_index, StorageContext* ctx);
260
261 Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) {
262 // Create a storage context.
263 StorageContext ctx{};
264
265 // Open the storage.
266 R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx)));
267 }
268
269public:
270 Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
271 VirtualFile raw_storage, StorageContext* ctx);
272
273private:
274 Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index,
275 StorageContext* ctx);
276
277 Result OpenIndirectableStorageAsOriginal(VirtualFile* out,
278 const NcaFsHeaderReader* header_reader,
279 StorageContext* ctx);
280
281 Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size);
282
283 Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
284 const NcaAesCtrUpperIv& upper_iv,
285 AlignmentStorageRequirement alignment_storage_requirement);
286 Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset);
287
288 Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
289 const NcaAesCtrUpperIv& upper_iv,
290 const NcaSparseInfo& sparse_info);
291 Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage,
292 s64 base_size, VirtualFile meta_storage,
293 const NcaSparseInfo& sparse_info, bool external_info);
294 Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset,
295 std::shared_ptr<SparseStorage>* out_sparse_storage,
296 VirtualFile* out_meta_storage, s32 index,
297 const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info);
298
299 Result CreateSparseStorageMetaStorageWithVerification(
300 VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset,
301 const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
302 const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
303 Result CreateSparseStorageWithVerification(
304 VirtualFile* out, s64* out_fs_data_offset,
305 std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage,
306 VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv,
307 const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info,
308 NcaFsHeader::MetaDataHashType meta_data_hash_type);
309
310 Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
311 NcaFsHeader::EncryptionType encryption_type,
312 const NcaAesCtrUpperIv& upper_iv,
313 const NcaPatchInfo& patch_info);
314 Result CreateAesCtrExStorage(VirtualFile* out,
315 std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext,
316 VirtualFile base_storage, VirtualFile meta_storage,
317 s64 counter_offset, const NcaAesCtrUpperIv& upper_iv,
318 const NcaPatchInfo& patch_info);
319
320 Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage,
321 const NcaPatchInfo& patch_info);
322 Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind,
323 VirtualFile base_storage, VirtualFile original_data_storage,
324 VirtualFile meta_storage, const NcaPatchInfo& patch_info);
325
326 Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta,
327 VirtualFile* out_verification, VirtualFile base_storage,
328 s64 offset, const NcaAesCtrUpperIv& upper_iv,
329 const NcaPatchInfo& patch_info,
330 const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
331
332 Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage,
333 const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data);
334
335 Result CreateIntegrityVerificationStorage(
336 VirtualFile* out, VirtualFile base_storage,
337 const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info);
338 Result CreateIntegrityVerificationStorageForMeta(
339 VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset,
340 const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
341 Result CreateIntegrityVerificationStorageImpl(
342 VirtualFile* out, VirtualFile base_storage,
343 const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset,
344 int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
345
346 Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
347 VirtualFile inside_storage, VirtualFile outside_storage);
348
349 Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp,
350 VirtualFile* out_meta, VirtualFile base_storage,
351 const NcaCompressionInfo& compression_info);
352
353public:
354 Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp,
355 VirtualFile* out_meta, VirtualFile base_storage,
356 const NcaCompressionInfo& compression_info,
357 GetDecompressorFunction get_decompressor);
358
359private:
360 std::shared_ptr<NcaReader> m_original_reader;
361 std::shared_ptr<NcaReader> m_reader;
362};
363
364} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp
new file mode 100644
index 000000000..bf5742d39
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp
@@ -0,0 +1,20 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/fssystem/fssystem_nca_header.h"
5
6namespace FileSys {
7
8u8 NcaHeader::GetProperKeyGeneration() const {
9 return std::max(this->key_generation, this->key_generation_2);
10}
11
12bool NcaPatchInfo::HasIndirectTable() const {
13 return this->indirect_size != 0;
14}
15
16bool NcaPatchInfo::HasAesCtrExTable() const {
17 return this->aes_ctr_ex_size != 0;
18}
19
20} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h
new file mode 100644
index 000000000..a02c5d881
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_header.h
@@ -0,0 +1,338 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8#include "common/literals.h"
9
10#include "core/file_sys/errors.h"
11#include "core/file_sys/fssystem/fs_types.h"
12
13namespace FileSys {
14
15using namespace Common::Literals;
16
17struct Hash {
18 static constexpr std::size_t Size = 256 / 8;
19 std::array<u8, Size> value;
20};
21static_assert(sizeof(Hash) == Hash::Size);
22static_assert(std::is_trivial_v<Hash>);
23
24using NcaDigest = Hash;
25
26struct NcaHeader {
27 enum class ContentType : u8 {
28 Program = 0,
29 Meta = 1,
30 Control = 2,
31 Manual = 3,
32 Data = 4,
33 PublicData = 5,
34
35 Start = Program,
36 End = PublicData,
37 };
38
39 enum class DistributionType : u8 {
40 Download = 0,
41 GameCard = 1,
42
43 Start = Download,
44 End = GameCard,
45 };
46
47 enum class EncryptionType : u8 {
48 Auto = 0,
49 None = 1,
50 };
51
52 enum DecryptionKey {
53 DecryptionKey_AesXts = 0,
54 DecryptionKey_AesXts1 = DecryptionKey_AesXts,
55 DecryptionKey_AesXts2 = 1,
56 DecryptionKey_AesCtr = 2,
57 DecryptionKey_AesCtrEx = 3,
58 DecryptionKey_AesCtrHw = 4,
59 DecryptionKey_Count,
60 };
61
62 struct FsInfo {
63 u32 start_sector;
64 u32 end_sector;
65 u32 hash_sectors;
66 u32 reserved;
67 };
68 static_assert(sizeof(FsInfo) == 0x10);
69 static_assert(std::is_trivial_v<FsInfo>);
70
71 static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0');
72 static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1');
73 static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2');
74 static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3');
75
76 static constexpr u32 Magic = Magic3;
77
78 static constexpr std::size_t Size = 1_KiB;
79 static constexpr s32 FsCountMax = 4;
80 static constexpr std::size_t HeaderSignCount = 2;
81 static constexpr std::size_t HeaderSignSize = 0x100;
82 static constexpr std::size_t EncryptedKeyAreaSize = 0x100;
83 static constexpr std::size_t SectorSize = 0x200;
84 static constexpr std::size_t SectorShift = 9;
85 static constexpr std::size_t RightsIdSize = 0x10;
86 static constexpr std::size_t XtsBlockSize = 0x200;
87 static constexpr std::size_t CtrBlockSize = 0x10;
88
89 static_assert(SectorSize == (1 << SectorShift));
90
91 // Data members.
92 std::array<u8, HeaderSignSize> header_sign_1;
93 std::array<u8, HeaderSignSize> header_sign_2;
94 u32 magic;
95 DistributionType distribution_type;
96 ContentType content_type;
97 u8 key_generation;
98 u8 key_index;
99 u64 content_size;
100 u64 program_id;
101 u32 content_index;
102 u32 sdk_addon_version;
103 u8 key_generation_2;
104 u8 header1_signature_key_generation;
105 std::array<u8, 2> reserved_222;
106 std::array<u32, 3> reserved_224;
107 std::array<u8, RightsIdSize> rights_id;
108 std::array<FsInfo, FsCountMax> fs_info;
109 std::array<Hash, FsCountMax> fs_header_hash;
110 std::array<u8, EncryptedKeyAreaSize> encrypted_key_area;
111
112 static constexpr u64 SectorToByte(u32 sector) {
113 return static_cast<u64>(sector) << SectorShift;
114 }
115
116 static constexpr u32 ByteToSector(u64 byte) {
117 return static_cast<u32>(byte >> SectorShift);
118 }
119
120 u8 GetProperKeyGeneration() const;
121};
122static_assert(sizeof(NcaHeader) == NcaHeader::Size);
123static_assert(std::is_trivial_v<NcaHeader>);
124
125struct NcaBucketInfo {
126 static constexpr size_t HeaderSize = 0x10;
127 Int64 offset;
128 Int64 size;
129 std::array<u8, HeaderSize> header;
130};
131static_assert(std::is_trivial_v<NcaBucketInfo>);
132
133struct NcaPatchInfo {
134 static constexpr size_t Size = 0x40;
135 static constexpr size_t Offset = 0x100;
136
137 Int64 indirect_offset;
138 Int64 indirect_size;
139 std::array<u8, NcaBucketInfo::HeaderSize> indirect_header;
140 Int64 aes_ctr_ex_offset;
141 Int64 aes_ctr_ex_size;
142 std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header;
143
144 bool HasIndirectTable() const;
145 bool HasAesCtrExTable() const;
146};
147static_assert(std::is_trivial_v<NcaPatchInfo>);
148
149union NcaAesCtrUpperIv {
150 u64 value;
151 struct {
152 u32 generation;
153 u32 secure_value;
154 } part;
155};
156static_assert(std::is_trivial_v<NcaAesCtrUpperIv>);
157
158struct NcaSparseInfo {
159 NcaBucketInfo bucket;
160 Int64 physical_offset;
161 u16 generation;
162 std::array<u8, 6> reserved;
163
164 s64 GetPhysicalSize() const {
165 return this->bucket.offset + this->bucket.size;
166 }
167
168 u32 GetGeneration() const {
169 return static_cast<u32>(this->generation) << 16;
170 }
171
172 const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const {
173 NcaAesCtrUpperIv sparse_upper_iv = upper_iv;
174 sparse_upper_iv.part.generation = this->GetGeneration();
175 return sparse_upper_iv;
176 }
177};
178static_assert(std::is_trivial_v<NcaSparseInfo>);
179
180struct NcaCompressionInfo {
181 NcaBucketInfo bucket;
182 std::array<u8, 8> resreved;
183};
184static_assert(std::is_trivial_v<NcaCompressionInfo>);
185
186struct NcaMetaDataHashDataInfo {
187 Int64 offset;
188 Int64 size;
189 Hash hash;
190};
191static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>);
192
193struct NcaFsHeader {
194 static constexpr size_t Size = 0x200;
195 static constexpr size_t HashDataOffset = 0x8;
196
197 struct Region {
198 Int64 offset;
199 Int64 size;
200 };
201 static_assert(std::is_trivial_v<Region>);
202
203 enum class FsType : u8 {
204 RomFs = 0,
205 PartitionFs = 1,
206 };
207
208 enum class EncryptionType : u8 {
209 Auto = 0,
210 None = 1,
211 AesXts = 2,
212 AesCtr = 3,
213 AesCtrEx = 4,
214 AesCtrSkipLayerHash = 5,
215 AesCtrExSkipLayerHash = 6,
216 };
217
218 enum class HashType : u8 {
219 Auto = 0,
220 None = 1,
221 HierarchicalSha256Hash = 2,
222 HierarchicalIntegrityHash = 3,
223 AutoSha3 = 4,
224 HierarchicalSha3256Hash = 5,
225 HierarchicalIntegritySha3Hash = 6,
226 };
227
228 enum class MetaDataHashType : u8 {
229 None = 0,
230 HierarchicalIntegrity = 1,
231 };
232
233 union HashData {
234 struct HierarchicalSha256Data {
235 static constexpr size_t HashLayerCountMax = 5;
236 static const size_t MasterHashOffset;
237
238 Hash fs_data_master_hash;
239 s32 hash_block_size;
240 s32 hash_layer_count;
241 std::array<Region, HashLayerCountMax> hash_layer_region;
242 } hierarchical_sha256_data;
243 static_assert(std::is_trivial_v<HierarchicalSha256Data>);
244
245 struct IntegrityMetaInfo {
246 static const size_t MasterHashOffset;
247
248 u32 magic;
249 u32 version;
250 u32 master_hash_size;
251
252 struct LevelHashInfo {
253 u32 max_layers;
254
255 struct HierarchicalIntegrityVerificationLevelInformation {
256 static constexpr size_t IntegrityMaxLayerCount = 7;
257 Int64 offset;
258 Int64 size;
259 s32 block_order;
260 std::array<u8, 4> reserved;
261 };
262 std::array<
263 HierarchicalIntegrityVerificationLevelInformation,
264 HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1>
265 info;
266
267 struct SignatureSalt {
268 static constexpr size_t Size = 0x20;
269 std::array<u8, Size> value;
270 };
271 SignatureSalt seed;
272 } level_hash_info;
273
274 Hash master_hash;
275 } integrity_meta_info;
276 static_assert(std::is_trivial_v<IntegrityMetaInfo>);
277
278 std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding;
279 };
280
281 u16 version;
282 FsType fs_type;
283 HashType hash_type;
284 EncryptionType encryption_type;
285 MetaDataHashType meta_data_hash_type;
286 std::array<u8, 2> reserved;
287 HashData hash_data;
288 NcaPatchInfo patch_info;
289 NcaAesCtrUpperIv aes_ctr_upper_iv;
290 NcaSparseInfo sparse_info;
291 NcaCompressionInfo compression_info;
292 NcaMetaDataHashDataInfo meta_data_hash_data_info;
293 std::array<u8, 0x30> pad;
294
295 bool IsSkipLayerHashEncryption() const {
296 return this->encryption_type == EncryptionType::AesCtrSkipLayerHash ||
297 this->encryption_type == EncryptionType::AesCtrExSkipLayerHash;
298 }
299
300 Result GetHashTargetOffset(s64* out) const {
301 switch (this->hash_type) {
302 case HashType::HierarchicalIntegrityHash:
303 case HashType::HierarchicalIntegritySha3Hash:
304 *out = this->hash_data.integrity_meta_info.level_hash_info
305 .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2]
306 .offset;
307 R_SUCCEED();
308 case HashType::HierarchicalSha256Hash:
309 case HashType::HierarchicalSha3256Hash:
310 *out =
311 this->hash_data.hierarchical_sha256_data
312 .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count -
313 1]
314 .offset;
315 R_SUCCEED();
316 default:
317 R_THROW(ResultInvalidNcaFsHeader);
318 }
319 }
320};
321static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size);
322static_assert(std::is_trivial_v<NcaFsHeader>);
323static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset);
324
325inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset =
326 offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash);
327inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset =
328 offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash);
329
330struct NcaMetaDataHashData {
331 s64 layer_info_offset;
332 NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info;
333};
334static_assert(sizeof(NcaMetaDataHashData) ==
335 sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64));
336static_assert(std::is_trivial_v<NcaMetaDataHashData>);
337
338} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp
new file mode 100644
index 000000000..a3714ab37
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp
@@ -0,0 +1,531 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
5#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
6#include "core/file_sys/vfs_offset.h"
7
8namespace FileSys {
9
10namespace {
11
12constexpr inline u32 SdkAddonVersionMin = 0x000B0000;
13constexpr inline size_t Aes128KeySize = 0x10;
14constexpr const std::array<u8, Aes128KeySize> ZeroKey{};
15
16constexpr Result CheckNcaMagic(u32 magic) {
17 // Verify the magic is not a deprecated one.
18 R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion);
19 R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion);
20 R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion);
21
22 // Verify the magic is the current one.
23 R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature);
24
25 R_SUCCEED();
26}
27
28} // namespace
29
30NcaReader::NcaReader()
31 : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false),
32 m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto),
33 m_get_decompressor() {
34 std::memset(std::addressof(m_header), 0, sizeof(m_header));
35 std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys));
36 std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key));
37}
38
39NcaReader::~NcaReader() {}
40
41Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg,
42 const NcaCompressionConfiguration& compression_cfg) {
43 // Validate preconditions.
44 ASSERT(base_storage != nullptr);
45 ASSERT(m_body_storage == nullptr);
46
47 // Create the work header storage storage.
48 VirtualFile work_header_storage;
49
50 // We need to be able to generate keys.
51 R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument);
52
53 // Generate keys for header.
54 using AesXtsStorageForNcaHeader = AesXtsStorage;
55
56 constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount>
57 HeaderKeyTypeValues = {
58 static_cast<s32>(KeyType::NcaHeaderKey1),
59 static_cast<s32>(KeyType::NcaHeaderKey2),
60 };
61
62 std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>,
63 NcaCryptoConfiguration::HeaderEncryptionKeyCount>
64 header_decryption_keys;
65 for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) {
66 crypto_cfg.generate_key(header_decryption_keys[i].data(),
67 AesXtsStorageForNcaHeader::KeySize,
68 crypto_cfg.header_encrypted_encryption_keys[i].data(),
69 AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]);
70 }
71
72 // Create the header storage.
73 std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {};
74 work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>(
75 base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(),
76 AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize,
77 NcaHeader::XtsBlockSize);
78
79 // Check that we successfully created the storage.
80 R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA);
81
82 // Read the header.
83 work_header_storage->ReadObject(std::addressof(m_header), 0);
84
85 // Validate the magic.
86 if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) {
87 // Try to use a plaintext header.
88 base_storage->ReadObject(std::addressof(m_header), 0);
89 R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result);
90
91 // Configure to use the plaintext header.
92 auto base_storage_size = base_storage->GetSize();
93 work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0);
94 R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA);
95
96 // Set encryption type as plaintext.
97 m_header_encryption_type = NcaHeader::EncryptionType::None;
98 }
99
100 // Verify the header sign1.
101 if (crypto_cfg.verify_sign1 != nullptr) {
102 const u8* sig = m_header.header_sign_1.data();
103 const size_t sig_size = NcaHeader::HeaderSignSize;
104 const u8* msg =
105 static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic)));
106 const size_t msg_size =
107 NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount;
108
109 m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1(
110 sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation);
111
112 if (!m_is_header_sign1_signature_valid) {
113 LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1");
114 }
115 }
116
117 // Validate the sdk version.
118 R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion);
119
120 // Validate the key index.
121 R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount ||
122 m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey,
123 ResultInvalidNcaKeyIndex);
124
125 // Check if we have a rights id.
126 constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{};
127 if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) {
128 // If we don't, then we don't have an external key, so we need to generate decryption keys.
129 crypto_cfg.generate_key(
130 m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize,
131 m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize,
132 Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
133 crypto_cfg.generate_key(
134 m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize,
135 m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize,
136 Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
137 crypto_cfg.generate_key(
138 m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize,
139 m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize,
140 Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
141 crypto_cfg.generate_key(
142 m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize,
143 m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize,
144 Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
145
146 // Copy the hardware speed emulation key.
147 std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(),
148 m_header.encrypted_key_area.data() +
149 NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize,
150 Aes128KeySize);
151 }
152
153 // Clear the external decryption key.
154 std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size());
155
156 // Set software key availability.
157 m_is_available_sw_key = crypto_cfg.is_available_sw_key;
158
159 // Set our decompressor function getter.
160 m_get_decompressor = compression_cfg.get_decompressor;
161
162 // Set our storages.
163 m_header_storage = std::move(work_header_storage);
164 m_body_storage = std::move(base_storage);
165
166 R_SUCCEED();
167}
168
169VirtualFile NcaReader::GetSharedBodyStorage() {
170 ASSERT(m_body_storage != nullptr);
171 return m_body_storage;
172}
173
174u32 NcaReader::GetMagic() const {
175 ASSERT(m_body_storage != nullptr);
176 return m_header.magic;
177}
178
179NcaHeader::DistributionType NcaReader::GetDistributionType() const {
180 ASSERT(m_body_storage != nullptr);
181 return m_header.distribution_type;
182}
183
184NcaHeader::ContentType NcaReader::GetContentType() const {
185 ASSERT(m_body_storage != nullptr);
186 return m_header.content_type;
187}
188
189u8 NcaReader::GetHeaderSign1KeyGeneration() const {
190 ASSERT(m_body_storage != nullptr);
191 return m_header.header1_signature_key_generation;
192}
193
194u8 NcaReader::GetKeyGeneration() const {
195 ASSERT(m_body_storage != nullptr);
196 return m_header.GetProperKeyGeneration();
197}
198
199u8 NcaReader::GetKeyIndex() const {
200 ASSERT(m_body_storage != nullptr);
201 return m_header.key_index;
202}
203
204u64 NcaReader::GetContentSize() const {
205 ASSERT(m_body_storage != nullptr);
206 return m_header.content_size;
207}
208
209u64 NcaReader::GetProgramId() const {
210 ASSERT(m_body_storage != nullptr);
211 return m_header.program_id;
212}
213
214u32 NcaReader::GetContentIndex() const {
215 ASSERT(m_body_storage != nullptr);
216 return m_header.content_index;
217}
218
219u32 NcaReader::GetSdkAddonVersion() const {
220 ASSERT(m_body_storage != nullptr);
221 return m_header.sdk_addon_version;
222}
223
224void NcaReader::GetRightsId(u8* dst, size_t dst_size) const {
225 ASSERT(dst != nullptr);
226 ASSERT(dst_size >= NcaHeader::RightsIdSize);
227
228 std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize);
229}
230
231bool NcaReader::HasFsInfo(s32 index) const {
232 ASSERT(0 <= index && index < NcaHeader::FsCountMax);
233 return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0;
234}
235
236s32 NcaReader::GetFsCount() const {
237 ASSERT(m_body_storage != nullptr);
238 for (s32 i = 0; i < NcaHeader::FsCountMax; i++) {
239 if (!this->HasFsInfo(i)) {
240 return i;
241 }
242 }
243 return NcaHeader::FsCountMax;
244}
245
246const Hash& NcaReader::GetFsHeaderHash(s32 index) const {
247 ASSERT(m_body_storage != nullptr);
248 ASSERT(0 <= index && index < NcaHeader::FsCountMax);
249 return m_header.fs_header_hash[index];
250}
251
252void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const {
253 ASSERT(m_body_storage != nullptr);
254 ASSERT(0 <= index && index < NcaHeader::FsCountMax);
255 ASSERT(dst != nullptr);
256 std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst));
257}
258
259void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const {
260 ASSERT(m_body_storage != nullptr);
261 ASSERT(0 <= index && index < NcaHeader::FsCountMax);
262 ASSERT(dst != nullptr);
263 std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst));
264}
265
266u64 NcaReader::GetFsOffset(s32 index) const {
267 ASSERT(m_body_storage != nullptr);
268 ASSERT(0 <= index && index < NcaHeader::FsCountMax);
269 return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector);
270}
271
272u64 NcaReader::GetFsEndOffset(s32 index) const {
273 ASSERT(m_body_storage != nullptr);
274 ASSERT(0 <= index && index < NcaHeader::FsCountMax);
275 return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector);
276}
277
278u64 NcaReader::GetFsSize(s32 index) const {
279 ASSERT(m_body_storage != nullptr);
280 ASSERT(0 <= index && index < NcaHeader::FsCountMax);
281 return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector -
282 m_header.fs_info[index].start_sector);
283}
284
285void NcaReader::GetEncryptedKey(void* dst, size_t size) const {
286 ASSERT(m_body_storage != nullptr);
287 ASSERT(dst != nullptr);
288 ASSERT(size >= NcaHeader::EncryptedKeyAreaSize);
289
290 std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize);
291}
292
293const void* NcaReader::GetDecryptionKey(s32 index) const {
294 ASSERT(m_body_storage != nullptr);
295 ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count);
296 return m_decryption_keys[index].data();
297}
298
299bool NcaReader::HasValidInternalKey() const {
300 for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) {
301 if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize,
302 Aes128KeySize) != 0) {
303 return true;
304 }
305 }
306 return false;
307}
308
309bool NcaReader::HasInternalDecryptionKeyForAesHw() const {
310 return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw),
311 Aes128KeySize) != 0;
312}
313
314bool NcaReader::IsSoftwareAesPrioritized() const {
315 return m_is_software_aes_prioritized;
316}
317
318void NcaReader::PrioritizeSoftwareAes() {
319 m_is_software_aes_prioritized = true;
320}
321
322bool NcaReader::IsAvailableSwKey() const {
323 return m_is_available_sw_key;
324}
325
326bool NcaReader::HasExternalDecryptionKey() const {
327 return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0;
328}
329
330const void* NcaReader::GetExternalDecryptionKey() const {
331 return m_external_decryption_key.data();
332}
333
334void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) {
335 ASSERT(src != nullptr);
336 ASSERT(size == sizeof(m_external_decryption_key));
337
338 std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key));
339}
340
341void NcaReader::GetRawData(void* dst, size_t dst_size) const {
342 ASSERT(m_body_storage != nullptr);
343 ASSERT(dst != nullptr);
344 ASSERT(dst_size >= sizeof(NcaHeader));
345
346 std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader));
347}
348
349GetDecompressorFunction NcaReader::GetDecompressor() const {
350 ASSERT(m_get_decompressor != nullptr);
351 return m_get_decompressor;
352}
353
354NcaHeader::EncryptionType NcaReader::GetEncryptionType() const {
355 return m_header_encryption_type;
356}
357
358Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const {
359 ASSERT(dst != nullptr);
360 ASSERT(0 <= index && index < NcaHeader::FsCountMax);
361
362 const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index;
363 m_header_storage->ReadObject(dst, offset);
364
365 R_SUCCEED();
366}
367
368bool NcaReader::GetHeaderSign1Valid() const {
369 return m_is_header_sign1_signature_valid;
370}
371
372void NcaReader::GetHeaderSign2(void* dst, size_t size) const {
373 ASSERT(dst != nullptr);
374 ASSERT(size == NcaHeader::HeaderSignSize);
375
376 std::memcpy(dst, m_header.header_sign_2.data(), size);
377}
378
379Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) {
380 // Reset ourselves to uninitialized.
381 m_fs_index = -1;
382
383 // Read the header.
384 R_TRY(reader.ReadHeader(std::addressof(m_data), index));
385
386 // Set our index.
387 m_fs_index = index;
388 R_SUCCEED();
389}
390
391void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const {
392 ASSERT(this->IsInitialized());
393 ASSERT(dst != nullptr);
394 ASSERT(dst_size >= sizeof(NcaFsHeader));
395
396 std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader));
397}
398
399NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() {
400 ASSERT(this->IsInitialized());
401 return m_data.hash_data;
402}
403
404const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const {
405 ASSERT(this->IsInitialized());
406 return m_data.hash_data;
407}
408
409u16 NcaFsHeaderReader::GetVersion() const {
410 ASSERT(this->IsInitialized());
411 return m_data.version;
412}
413
414s32 NcaFsHeaderReader::GetFsIndex() const {
415 ASSERT(this->IsInitialized());
416 return m_fs_index;
417}
418
419NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const {
420 ASSERT(this->IsInitialized());
421 return m_data.fs_type;
422}
423
424NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const {
425 ASSERT(this->IsInitialized());
426 return m_data.hash_type;
427}
428
429NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const {
430 ASSERT(this->IsInitialized());
431 return m_data.encryption_type;
432}
433
434NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() {
435 ASSERT(this->IsInitialized());
436 return m_data.patch_info;
437}
438
439const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const {
440 ASSERT(this->IsInitialized());
441 return m_data.patch_info;
442}
443
444const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const {
445 ASSERT(this->IsInitialized());
446 return m_data.aes_ctr_upper_iv;
447}
448
449bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const {
450 ASSERT(this->IsInitialized());
451 return m_data.IsSkipLayerHashEncryption();
452}
453
454Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const {
455 ASSERT(out != nullptr);
456 ASSERT(this->IsInitialized());
457
458 R_RETURN(m_data.GetHashTargetOffset(out));
459}
460
461bool NcaFsHeaderReader::ExistsSparseLayer() const {
462 ASSERT(this->IsInitialized());
463 return m_data.sparse_info.generation != 0;
464}
465
466NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() {
467 ASSERT(this->IsInitialized());
468 return m_data.sparse_info;
469}
470
471const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const {
472 ASSERT(this->IsInitialized());
473 return m_data.sparse_info;
474}
475
476bool NcaFsHeaderReader::ExistsCompressionLayer() const {
477 ASSERT(this->IsInitialized());
478 return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0;
479}
480
481NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() {
482 ASSERT(this->IsInitialized());
483 return m_data.compression_info;
484}
485
486const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const {
487 ASSERT(this->IsInitialized());
488 return m_data.compression_info;
489}
490
491bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const {
492 ASSERT(this->IsInitialized());
493 return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable();
494}
495
496NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() {
497 ASSERT(this->IsInitialized());
498 return m_data.meta_data_hash_data_info;
499}
500
501const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const {
502 ASSERT(this->IsInitialized());
503 return m_data.meta_data_hash_data_info;
504}
505
506NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const {
507 ASSERT(this->IsInitialized());
508 return m_data.meta_data_hash_type;
509}
510
511bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const {
512 ASSERT(this->IsInitialized());
513 return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer();
514}
515
516NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() {
517 ASSERT(this->IsInitialized());
518 return m_data.meta_data_hash_data_info;
519}
520
521const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const {
522 ASSERT(this->IsInitialized());
523 return m_data.meta_data_hash_data_info;
524}
525
526NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const {
527 ASSERT(this->IsInitialized());
528 return m_data.meta_data_hash_type;
529}
530
531} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp
new file mode 100644
index 000000000..bbfaab255
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
6
7namespace FileSys {
8
9namespace {
10
11constexpr size_t HeapBlockSize = BufferPoolAlignment;
12static_assert(HeapBlockSize == 4_KiB);
13
14// A heap block is 4KiB. An order is a power of two.
15// This gives blocks of the order 32KiB, 512KiB, 4MiB.
16constexpr s32 HeapOrderMax = 7;
17constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3;
18
19constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax);
20constexpr size_t HeapAllocatableSizeMaxForLarge =
21 HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge);
22
23} // namespace
24
25size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) {
26 return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax;
27}
28
29void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) {
30 // Ensure preconditions.
31 ASSERT(m_buffer == nullptr);
32
33 // Check that we can allocate this size.
34 ASSERT(required_size <= GetAllocatableSizeMaxCore(large));
35
36 const size_t target_size =
37 std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large));
38
39 // Dummy implementation for allocate.
40 if (target_size > 0) {
41 m_buffer =
42 reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize}));
43 m_size = target_size;
44
45 // Ensure postconditions.
46 ASSERT(m_buffer != nullptr);
47 }
48}
49
50void PooledBuffer::Shrink(size_t ideal_size) {
51 ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true));
52
53 // Shrinking to zero means that we have no buffer.
54 if (ideal_size == 0) {
55 ::operator delete(m_buffer, std::align_val_t{HeapBlockSize});
56 m_buffer = nullptr;
57 m_size = ideal_size;
58 }
59}
60
61} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.h b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h
new file mode 100644
index 000000000..9a6adbcb5
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h
@@ -0,0 +1,95 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8#include "common/literals.h"
9#include "core/hle/result.h"
10
11namespace FileSys {
12
13using namespace Common::Literals;
14
15constexpr inline size_t BufferPoolAlignment = 4_KiB;
16constexpr inline size_t BufferPoolWorkSize = 320;
17
18class PooledBuffer {
19 YUZU_NON_COPYABLE(PooledBuffer);
20
21public:
22 // Constructor/Destructor.
23 constexpr PooledBuffer() : m_buffer(), m_size() {}
24
25 PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() {
26 this->Allocate(ideal_size, required_size);
27 }
28
29 ~PooledBuffer() {
30 this->Deallocate();
31 }
32
33 // Move and assignment.
34 explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) {
35 rhs.m_buffer = nullptr;
36 rhs.m_size = 0;
37 }
38
39 PooledBuffer& operator=(PooledBuffer&& rhs) {
40 PooledBuffer(std::move(rhs)).Swap(*this);
41 return *this;
42 }
43
44 // Allocation API.
45 void Allocate(size_t ideal_size, size_t required_size) {
46 return this->AllocateCore(ideal_size, required_size, false);
47 }
48
49 void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) {
50 return this->AllocateCore(ideal_size, required_size, true);
51 }
52
53 void Shrink(size_t ideal_size);
54
55 void Deallocate() {
56 // Shrink the buffer to empty.
57 this->Shrink(0);
58 ASSERT(m_buffer == nullptr);
59 }
60
61 char* GetBuffer() const {
62 ASSERT(m_buffer != nullptr);
63 return m_buffer;
64 }
65
66 size_t GetSize() const {
67 ASSERT(m_buffer != nullptr);
68 return m_size;
69 }
70
71public:
72 static size_t GetAllocatableSizeMax() {
73 return GetAllocatableSizeMaxCore(false);
74 }
75 static size_t GetAllocatableParticularlyLargeSizeMax() {
76 return GetAllocatableSizeMaxCore(true);
77 }
78
79private:
80 static size_t GetAllocatableSizeMaxCore(bool large);
81
82private:
83 void Swap(PooledBuffer& rhs) {
84 std::swap(m_buffer, rhs.m_buffer);
85 std::swap(m_size, rhs.m_size);
86 }
87
88 void AllocateCore(size_t ideal_size, size_t required_size, bool large);
89
90private:
91 char* m_buffer;
92 size_t m_size;
93};
94
95} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp
new file mode 100644
index 000000000..8574a11dd
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp
@@ -0,0 +1,39 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/fssystem/fssystem_sparse_storage.h"
5
6namespace FileSys {
7
8size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const {
9 // Validate preconditions.
10 ASSERT(this->IsInitialized());
11 ASSERT(buffer != nullptr);
12
13 // Allow zero size.
14 if (size == 0) {
15 return size;
16 }
17
18 SparseStorage* self = const_cast<SparseStorage*>(this);
19
20 if (self->GetEntryTable().IsEmpty()) {
21 BucketTree::Offsets table_offsets;
22 ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets))));
23 ASSERT(table_offsets.IsInclude(offset, size));
24
25 std::memset(buffer, 0, size);
26 } else {
27 self->OperatePerEntry<false, true>(
28 offset, size,
29 [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
30 storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
31 static_cast<size_t>(cur_size), data_offset);
32 R_SUCCEED();
33 });
34 }
35
36 return size;
37}
38
39} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.h b/src/core/file_sys/fssystem/fssystem_sparse_storage.h
new file mode 100644
index 000000000..6c196ec61
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.h
@@ -0,0 +1,72 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
7
8namespace FileSys {
9
10class SparseStorage : public IndirectStorage {
11 YUZU_NON_COPYABLE(SparseStorage);
12 YUZU_NON_MOVEABLE(SparseStorage);
13
14private:
15 class ZeroStorage : public IReadOnlyStorage {
16 public:
17 ZeroStorage() {}
18 virtual ~ZeroStorage() {}
19
20 virtual size_t GetSize() const override {
21 return std::numeric_limits<size_t>::max();
22 }
23
24 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
25 ASSERT(buffer != nullptr || size == 0);
26
27 if (size > 0) {
28 std::memset(buffer, 0, size);
29 }
30
31 return size;
32 }
33 };
34
35public:
36 SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {}
37 virtual ~SparseStorage() {}
38
39 using IndirectStorage::Initialize;
40
41 void Initialize(s64 end_offset) {
42 this->GetEntryTable().Initialize(NodeSize, end_offset);
43 this->SetZeroStorage();
44 }
45
46 void SetDataStorage(VirtualFile storage) {
47 ASSERT(this->IsInitialized());
48
49 this->SetStorage(0, storage);
50 this->SetZeroStorage();
51 }
52
53 template <typename T>
54 void SetDataStorage(T storage, s64 offset, s64 size) {
55 ASSERT(this->IsInitialized());
56
57 this->SetStorage(0, storage, offset, size);
58 this->SetZeroStorage();
59 }
60
61 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
62
63private:
64 void SetZeroStorage() {
65 return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max());
66 }
67
68private:
69 VirtualFile m_zero_storage;
70};
71
72} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_switch_storage.h b/src/core/file_sys/fssystem/fssystem_switch_storage.h
new file mode 100644
index 000000000..2b43927cb
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_switch_storage.h
@@ -0,0 +1,80 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/file_sys/fssystem/fs_i_storage.h"
7
8namespace FileSys {
9
10class RegionSwitchStorage : public IReadOnlyStorage {
11 YUZU_NON_COPYABLE(RegionSwitchStorage);
12 YUZU_NON_MOVEABLE(RegionSwitchStorage);
13
14public:
15 struct Region {
16 s64 offset;
17 s64 size;
18 };
19
20public:
21 RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r)
22 : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)),
23 m_region(r) {}
24
25 virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
26 // Process until we're done.
27 size_t processed = 0;
28 while (processed < size) {
29 // Process on the appropriate storage.
30 s64 cur_size = 0;
31 if (this->CheckRegions(std::addressof(cur_size), offset + processed,
32 size - processed)) {
33 m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed);
34 } else {
35 m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed);
36 }
37
38 // Advance.
39 processed += cur_size;
40 }
41
42 return size;
43 }
44
45 virtual size_t GetSize() const override {
46 return m_inside_region_storage->GetSize();
47 }
48
49private:
50 bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const {
51 // Check if our region contains the access.
52 if (m_region.offset <= offset) {
53 if (offset < m_region.offset + m_region.size) {
54 if (m_region.offset + m_region.size <= offset + size) {
55 *out_current_size = m_region.offset + m_region.size - offset;
56 } else {
57 *out_current_size = size;
58 }
59 return true;
60 } else {
61 *out_current_size = size;
62 return false;
63 }
64 } else {
65 if (m_region.offset <= offset + size) {
66 *out_current_size = m_region.offset - offset;
67 } else {
68 *out_current_size = size;
69 }
70 return false;
71 }
72 }
73
74private:
75 VirtualFile m_inside_region_storage;
76 VirtualFile m_outside_region_storage;
77 Region m_region;
78};
79
80} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_utility.cpp b/src/core/file_sys/fssystem/fssystem_utility.cpp
new file mode 100644
index 000000000..ceabb8ff1
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_utility.cpp
@@ -0,0 +1,27 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/fssystem/fssystem_utility.h"
5
6namespace FileSys {
7
8void AddCounter(void* counter_, size_t counter_size, u64 value) {
9 u8* counter = static_cast<u8*>(counter_);
10 u64 remaining = value;
11 u8 carry = 0;
12
13 for (size_t i = 0; i < counter_size; i++) {
14 auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry;
15 carry = static_cast<u8>(sum >> (sizeof(u8) * 8));
16 auto sum8 = static_cast<u8>(sum & 0xFF);
17
18 counter[counter_size - 1 - i] = sum8;
19
20 remaining >>= (sizeof(u8) * 8);
21 if (carry == 0 && remaining == 0) {
22 break;
23 }
24 }
25}
26
27} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_utility.h b/src/core/file_sys/fssystem/fssystem_utility.h
new file mode 100644
index 000000000..284b8b811
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_utility.h
@@ -0,0 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7
8namespace FileSys {
9
10void AddCounter(void* counter, size_t counter_size, u64 value);
11
12}
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index efdf18cee..7be1322cc 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) {
165void IPSwitchCompiler::ParseFlag(const std::string& line) { 165void IPSwitchCompiler::ParseFlag(const std::string& line) {
166 if (StartsWith(line, "@flag offset_shift ")) { 166 if (StartsWith(line, "@flag offset_shift ")) {
167 // Offset Shift Flag 167 // Offset Shift Flag
168 offset_shift = std::stoll(line.substr(19), nullptr, 0); 168 offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
169 } else if (StartsWith(line, "@little-endian")) { 169 } else if (StartsWith(line, "@little-endian")) {
170 // Set values to read as little endian 170 // Set values to read as little endian
171 is_little_endian = true; 171 is_little_endian = true;
@@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val 263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val
264 if (patch_line.length() < 11) 264 if (patch_line.length() < 11)
265 break; 265 break;
266 auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); 266 auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
267 offset += static_cast<unsigned long>(offset_shift); 267 offset += static_cast<unsigned long>(offset_shift);
268 268
269 std::vector<u8> replace; 269 std::vector<u8> replace;
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index 52c78020c..f4a774675 100644
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_,
45 45
46CNMT::~CNMT() = default; 46CNMT::~CNMT() = default;
47 47
48const CNMTHeader& CNMT::GetHeader() const {
49 return header;
50}
51
48u64 CNMT::GetTitleID() const { 52u64 CNMT::GetTitleID() const {
49 return header.title_id; 53 return header.title_id;
50} 54}
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index c59ece010..68e463b5f 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -89,6 +89,7 @@ public:
89 std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); 89 std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_);
90 ~CNMT(); 90 ~CNMT();
91 91
92 const CNMTHeader& GetHeader() const;
92 u64 GetTitleID() const; 93 u64 GetTitleID() const;
93 u32 GetTitleVersion() const; 94 u32 GetTitleVersion() const;
94 TitleType GetType() const; 95 TitleType GetType() const;
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
deleted file mode 100644
index 2735d053b..000000000
--- a/src/core/file_sys/nca_patch.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <array>
6#include <cstddef>
7#include <cstring>
8
9#include "common/assert.h"
10#include "core/crypto/aes_util.h"
11#include "core/file_sys/nca_patch.h"
12
13namespace FileSys {
14namespace {
15template <bool Subsection, typename BlockType, typename BucketType>
16std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block,
17 const BucketType& buckets) {
18 if constexpr (Subsection) {
19 const auto& last_bucket = buckets[block.number_buckets - 1];
20 if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) {
21 return {block.number_buckets - 1, last_bucket.number_entries};
22 }
23 } else {
24 ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
25 }
26
27 std::size_t bucket_id = std::count_if(
28 block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
29 [&offset](u64 base_offset) { return base_offset <= offset; });
30
31 const auto& bucket = buckets[bucket_id];
32
33 if (bucket.number_entries == 1) {
34 return {bucket_id, 0};
35 }
36
37 std::size_t low = 0;
38 std::size_t mid = 0;
39 std::size_t high = bucket.number_entries - 1;
40 while (low <= high) {
41 mid = (low + high) / 2;
42 if (bucket.entries[mid].address_patch > offset) {
43 high = mid - 1;
44 } else {
45 if (mid == bucket.number_entries - 1 ||
46 bucket.entries[mid + 1].address_patch > offset) {
47 return {bucket_id, mid};
48 }
49
50 low = mid + 1;
51 }
52 }
53 ASSERT_MSG(false, "Offset could not be found in BKTR block.");
54 return {0, 0};
55}
56} // Anonymous namespace
57
58BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
59 std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
60 std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
61 Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
62 std::array<u8, 8> section_ctr_)
63 : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
64 subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
65 base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
66 encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
67 section_ctr(section_ctr_) {
68 for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) {
69 relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0});
70 }
71
72 for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) {
73 subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch,
74 {0},
75 subsection_buckets[i + 1].entries[0].ctr});
76 }
77
78 relocation_buckets.back().entries.push_back({relocation.size, 0, 0});
79}
80
81BKTR::~BKTR() = default;
82
83std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
84 // Read out of bounds.
85 if (offset >= relocation.size) {
86 return 0;
87 }
88
89 const auto relocation_entry = GetRelocationEntry(offset);
90 const auto section_offset =
91 offset - relocation_entry.address_patch + relocation_entry.address_source;
92 const auto bktr_read = relocation_entry.from_patch;
93
94 const auto next_relocation = GetNextRelocationEntry(offset);
95
96 if (offset + length > next_relocation.address_patch) {
97 const u64 partition = next_relocation.address_patch - offset;
98 return Read(data, partition, offset) +
99 Read(data + partition, length - partition, offset + partition);
100 }
101
102 if (!bktr_read) {
103 ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative.");
104 return base_romfs->Read(data, length, section_offset - ivfc_offset);
105 }
106
107 if (!encrypted) {
108 return bktr_romfs->Read(data, length, section_offset);
109 }
110
111 const auto subsection_entry = GetSubsectionEntry(section_offset);
112 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
113
114 // Calculate AES IV
115 std::array<u8, 16> iv{};
116 auto subsection_ctr = subsection_entry.ctr;
117 auto offset_iv = section_offset + base_offset;
118 for (std::size_t i = 0; i < section_ctr.size(); ++i) {
119 iv[i] = section_ctr[0x8 - i - 1];
120 }
121 offset_iv >>= 4;
122 for (std::size_t i = 0; i < sizeof(u64); ++i) {
123 iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF);
124 offset_iv >>= 8;
125 }
126 for (std::size_t i = 0; i < sizeof(u32); ++i) {
127 iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF);
128 subsection_ctr >>= 8;
129 }
130 cipher.SetIV(iv);
131
132 const auto next_subsection = GetNextSubsectionEntry(section_offset);
133
134 if (section_offset + length > next_subsection.address_patch) {
135 const u64 partition = next_subsection.address_patch - section_offset;
136 return Read(data, partition, offset) +
137 Read(data + partition, length - partition, offset + partition);
138 }
139
140 const auto block_offset = section_offset & 0xF;
141 if (block_offset != 0) {
142 auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF);
143 cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt);
144 if (length + block_offset < 0x10) {
145 std::memcpy(data, block.data() + block_offset, std::min(length, block.size()));
146 return std::min(length, block.size());
147 }
148
149 const auto read = 0x10 - block_offset;
150 std::memcpy(data, block.data() + block_offset, read);
151 return read + Read(data + read, length - read, offset + read);
152 }
153
154 const auto raw_read = bktr_romfs->Read(data, length, section_offset);
155 cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt);
156 return raw_read;
157}
158
159RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
160 const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
161 return relocation_buckets[res.first].entries[res.second];
162}
163
164RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const {
165 const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
166 const auto bucket = relocation_buckets[res.first];
167 if (res.second + 1 < bucket.entries.size())
168 return bucket.entries[res.second + 1];
169 return relocation_buckets[res.first + 1].entries[0];
170}
171
172SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const {
173 const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
174 return subsection_buckets[res.first].entries[res.second];
175}
176
177SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const {
178 const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
179 const auto bucket = subsection_buckets[res.first];
180 if (res.second + 1 < bucket.entries.size())
181 return bucket.entries[res.second + 1];
182 return subsection_buckets[res.first + 1].entries[0];
183}
184
185std::string BKTR::GetName() const {
186 return base_romfs->GetName();
187}
188
189std::size_t BKTR::GetSize() const {
190 return relocation.size;
191}
192
193bool BKTR::Resize(std::size_t new_size) {
194 return false;
195}
196
197VirtualDir BKTR::GetContainingDirectory() const {
198 return base_romfs->GetContainingDirectory();
199}
200
201bool BKTR::IsWritable() const {
202 return false;
203}
204
205bool BKTR::IsReadable() const {
206 return true;
207}
208
209std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) {
210 return 0;
211}
212
213bool BKTR::Rename(std::string_view name) {
214 return base_romfs->Rename(name);
215}
216
217} // namespace FileSys
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h
deleted file mode 100644
index 595e3ef09..000000000
--- a/src/core/file_sys/nca_patch.h
+++ /dev/null
@@ -1,145 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <vector>
9
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/swap.h"
13#include "core/crypto/key_manager.h"
14
15namespace FileSys {
16
17#pragma pack(push, 1)
18struct RelocationEntry {
19 u64_le address_patch;
20 u64_le address_source;
21 u32 from_patch;
22};
23#pragma pack(pop)
24static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
25
26struct RelocationBucketRaw {
27 INSERT_PADDING_BYTES(4);
28 u32_le number_entries;
29 u64_le end_offset;
30 std::array<RelocationEntry, 0x332> relocation_entries;
31 INSERT_PADDING_BYTES(8);
32};
33static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
34
35// Vector version of RelocationBucketRaw
36struct RelocationBucket {
37 u32 number_entries;
38 u64 end_offset;
39 std::vector<RelocationEntry> entries;
40};
41
42struct RelocationBlock {
43 INSERT_PADDING_BYTES(4);
44 u32_le number_buckets;
45 u64_le size;
46 std::array<u64, 0x7FE> base_offsets;
47};
48static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
49
50struct SubsectionEntry {
51 u64_le address_patch;
52 INSERT_PADDING_BYTES(0x4);
53 u32_le ctr;
54};
55static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
56
57struct SubsectionBucketRaw {
58 INSERT_PADDING_BYTES(4);
59 u32_le number_entries;
60 u64_le end_offset;
61 std::array<SubsectionEntry, 0x3FF> subsection_entries;
62};
63static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
64
65// Vector version of SubsectionBucketRaw
66struct SubsectionBucket {
67 u32 number_entries;
68 u64 end_offset;
69 std::vector<SubsectionEntry> entries;
70};
71
72struct SubsectionBlock {
73 INSERT_PADDING_BYTES(4);
74 u32_le number_buckets;
75 u64_le size;
76 std::array<u64, 0x7FE> base_offsets;
77};
78static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
79
80inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) {
81 return {raw.number_entries,
82 raw.end_offset,
83 {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}};
84}
85
86inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) {
87 return {raw.number_entries,
88 raw.end_offset,
89 {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}};
90}
91
92class BKTR : public VfsFile {
93public:
94 BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation,
95 std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection,
96 std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted,
97 Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr);
98 ~BKTR() override;
99
100 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
101
102 std::string GetName() const override;
103
104 std::size_t GetSize() const override;
105
106 bool Resize(std::size_t new_size) override;
107
108 VirtualDir GetContainingDirectory() const override;
109
110 bool IsWritable() const override;
111
112 bool IsReadable() const override;
113
114 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
115
116 bool Rename(std::string_view name) override;
117
118private:
119 RelocationEntry GetRelocationEntry(u64 offset) const;
120 RelocationEntry GetNextRelocationEntry(u64 offset) const;
121
122 SubsectionEntry GetSubsectionEntry(u64 offset) const;
123 SubsectionEntry GetNextSubsectionEntry(u64 offset) const;
124
125 RelocationBlock relocation;
126 std::vector<RelocationBucket> relocation_buckets;
127 SubsectionBlock subsection;
128 std::vector<SubsectionBucket> subsection_buckets;
129
130 // Should be the raw base romfs, decrypted.
131 VirtualFile base_romfs;
132 // Should be the raw BKTR romfs, (located at media_offset with size media_size).
133 VirtualFile bktr_romfs;
134
135 bool encrypted;
136 Core::Crypto::Key128 key;
137
138 // Base offset into NCA, used for IV calculation.
139 u64 base_offset;
140 // Distance between IVFC start and RomFS start, used for base reads
141 u64 ivfc_offset;
142 std::array<u8, 8> section_ctr;
143};
144
145} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 2ba1b34a4..8e475f25a 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -141,8 +141,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
141 const auto update_tid = GetUpdateTitleID(title_id); 141 const auto update_tid = GetUpdateTitleID(title_id);
142 const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); 142 const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
143 143
144 if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && 144 if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
145 update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
146 LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", 145 LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
147 FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); 146 FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
148 exefs = update->GetExeFS(); 147 exefs = update->GetExeFS();
@@ -295,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
295 return out; 294 return out;
296} 295}
297 296
298bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { 297bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
299 const auto build_id_raw = Common::HexToString(build_id_); 298 const auto build_id_raw = Common::HexToString(build_id_);
300 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); 299 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
301 300
302 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); 301 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
303 302
304 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); 303 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
305 if (load_dir == nullptr) { 304 if (load_dir == nullptr) {
@@ -353,16 +352,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
353 const Service::FileSystem::FileSystemController& fs_controller) { 352 const Service::FileSystem::FileSystemController& fs_controller) {
354 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); 353 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
355 const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); 354 const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
356 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || 355 if ((type != ContentRecordType::Program && type != ContentRecordType::Data &&
356 type != ContentRecordType::HtmlDocument) ||
357 (load_dir == nullptr && sdmc_load_dir == nullptr)) { 357 (load_dir == nullptr && sdmc_load_dir == nullptr)) {
358 return; 358 return;
359 } 359 }
360 360
361 auto extracted = ExtractRomFS(romfs);
362 if (extracted == nullptr) {
363 return;
364 }
365
366 const auto& disabled = Settings::values.disabled_addons[title_id]; 361 const auto& disabled = Settings::values.disabled_addons[title_id];
367 std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); 362 std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
368 if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { 363 if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
@@ -387,6 +382,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
387 auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); 382 auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext");
388 if (ext_dir != nullptr) 383 if (ext_dir != nullptr)
389 layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); 384 layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir));
385
386 if (type == ContentRecordType::HtmlDocument) {
387 auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html");
388 if (manual_dir != nullptr)
389 layers.push_back(std::make_shared<CachedVfsDirectory>(manual_dir));
390 }
390 } 391 }
391 392
392 // When there are no layers to apply, return early as there is no need to rebuild the RomFS 393 // When there are no layers to apply, return early as there is no need to rebuild the RomFS
@@ -394,6 +395,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
394 return; 395 return;
395 } 396 }
396 397
398 auto extracted = ExtractRomFS(romfs);
399 if (extracted == nullptr) {
400 return;
401 }
402
397 layers.push_back(std::move(extracted)); 403 layers.push_back(std::move(extracted));
398 404
399 auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); 405 auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
@@ -412,39 +418,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
412 romfs = std::move(packed); 418 romfs = std::move(packed);
413} 419}
414 420
415VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, 421VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs,
416 VirtualFile update_raw, bool apply_layeredfs) const { 422 ContentRecordType type, VirtualFile packed_update_raw,
423 bool apply_layeredfs) const {
417 const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", 424 const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
418 title_id, static_cast<u8>(type)); 425 title_id, static_cast<u8>(type));
419
420 if (type == ContentRecordType::Program || type == ContentRecordType::Data) { 426 if (type == ContentRecordType::Program || type == ContentRecordType::Data) {
421 LOG_INFO(Loader, "{}", log_string); 427 LOG_INFO(Loader, "{}", log_string);
422 } else { 428 } else {
423 LOG_DEBUG(Loader, "{}", log_string); 429 LOG_DEBUG(Loader, "{}", log_string);
424 } 430 }
425 431
426 if (romfs == nullptr) { 432 if (base_romfs == nullptr) {
427 return romfs; 433 return base_romfs;
428 } 434 }
429 435
436 auto romfs = base_romfs;
437
430 // Game Updates 438 // Game Updates
431 const auto update_tid = GetUpdateTitleID(title_id); 439 const auto update_tid = GetUpdateTitleID(title_id);
432 const auto update = content_provider.GetEntryRaw(update_tid, type); 440 const auto update_raw = content_provider.GetEntryRaw(update_tid, type);
433 441
434 const auto& disabled = Settings::values.disabled_addons[title_id]; 442 const auto& disabled = Settings::values.disabled_addons[title_id];
435 const auto update_disabled = 443 const auto update_disabled =
436 std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); 444 std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
437 445
438 if (!update_disabled && update != nullptr) { 446 if (!update_disabled && update_raw != nullptr && base_nca != nullptr) {
439 const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); 447 const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
440 if (new_nca->GetStatus() == Loader::ResultStatus::Success && 448 if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
441 new_nca->GetRomFS() != nullptr) { 449 new_nca->GetRomFS() != nullptr) {
442 LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", 450 LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
443 FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); 451 FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
444 romfs = new_nca->GetRomFS(); 452 romfs = new_nca->GetRomFS();
453 const auto version =
454 FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0));
445 } 455 }
446 } else if (!update_disabled && update_raw != nullptr) { 456 } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
447 const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); 457 const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca);
448 if (new_nca->GetStatus() == Loader::ResultStatus::Success && 458 if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
449 new_nca->GetRomFS() != nullptr) { 459 new_nca->GetRomFS() != nullptr) {
450 LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); 460 LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
@@ -608,7 +618,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
608 return {}; 618 return {};
609 } 619 }
610 620
611 const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); 621 const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control);
612 if (romfs == nullptr) { 622 if (romfs == nullptr) {
613 return {}; 623 return {};
614 } 624 }
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 69d15e2f8..03e9c7301 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -52,7 +52,7 @@ public:
52 52
53 // Checks to see if PatchNSO() will have any effect given the NSO's build ID. 53 // Checks to see if PatchNSO() will have any effect given the NSO's build ID.
54 // Used to prevent expensive copies in NSO loader. 54 // Used to prevent expensive copies in NSO loader.
55 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; 55 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
56 56
57 // Creates a CheatList object with all 57 // Creates a CheatList object with all
58 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( 58 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
@@ -61,9 +61,9 @@ public:
61 // Currently tracked RomFS patches: 61 // Currently tracked RomFS patches:
62 // - Game Updates 62 // - Game Updates
63 // - LayeredFS 63 // - LayeredFS
64 [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, 64 [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs,
65 ContentRecordType type = ContentRecordType::Program, 65 ContentRecordType type = ContentRecordType::Program,
66 VirtualFile update_raw = nullptr, 66 VirtualFile packed_update_raw = nullptr,
67 bool apply_layeredfs = true) const; 67 bool apply_layeredfs = true) const;
68 68
69 // Returns a vector of pairs between patch names and patch versions. 69 // Returns a vector of pairs between patch names and patch versions.
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index a6960170c..04da93d5c 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -9,6 +9,7 @@
9#include "common/fs/path_util.h" 9#include "common/fs/path_util.h"
10#include "common/hex_util.h" 10#include "common/hex_util.h"
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/scope_exit.h"
12#include "core/crypto/key_manager.h" 13#include "core/crypto/key_manager.h"
13#include "core/file_sys/card_image.h" 14#include "core/file_sys/card_image.h"
14#include "core/file_sys/common_funcs.h" 15#include "core/file_sys/common_funcs.h"
@@ -416,9 +417,9 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
416 417
417 if (file == nullptr) 418 if (file == nullptr)
418 continue; 419 continue;
419 const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0); 420 const auto nca = std::make_shared<NCA>(parser(file, id));
420 if (nca->GetStatus() != Loader::ResultStatus::Success || 421 if (nca->GetStatus() != Loader::ResultStatus::Success ||
421 nca->GetType() != NCAContentType::Meta) { 422 nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) {
422 continue; 423 continue;
423 } 424 }
424 425
@@ -500,7 +501,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
500 const auto raw = GetEntryRaw(title_id, type); 501 const auto raw = GetEntryRaw(title_id, type);
501 if (raw == nullptr) 502 if (raw == nullptr)
502 return nullptr; 503 return nullptr;
503 return std::make_unique<NCA>(raw, nullptr, 0); 504 return std::make_unique<NCA>(raw);
504} 505}
505 506
506template <typename T> 507template <typename T>
@@ -606,9 +607,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
606 const auto result = RemoveExistingEntry(title_id); 607 const auto result = RemoveExistingEntry(title_id);
607 608
608 // Install Metadata File 609 // Install Metadata File
609 const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data); 610 const auto meta_result = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data);
610 if (res != InstallResult::Success) { 611 if (meta_result != InstallResult::Success) {
611 return res; 612 return meta_result;
612 } 613 }
613 614
614 // Install all the other NCAs 615 // Install all the other NCAs
@@ -621,9 +622,19 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
621 if (nca == nullptr) { 622 if (nca == nullptr) {
622 return InstallResult::ErrorCopyFailed; 623 return InstallResult::ErrorCopyFailed;
623 } 624 }
624 const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); 625 if (nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
625 if (res2 != InstallResult::Success) { 626 nca->GetTitleId() != title_id) {
626 return res2; 627 // Create fake cnmt for patch to multiprogram application
628 const auto sub_nca_result =
629 InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy);
630 if (sub_nca_result != InstallResult::Success) {
631 return sub_nca_result;
632 }
633 continue;
634 }
635 const auto nca_result = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id);
636 if (nca_result != InstallResult::Success) {
637 return nca_result;
627 } 638 }
628 } 639 }
629 640
@@ -662,7 +673,34 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
662 return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); 673 return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
663} 674}
664 675
676InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header,
677 const ContentRecord& base_record,
678 bool overwrite_if_exists, const VfsCopyFunction& copy) {
679 const CNMTHeader header{
680 .title_id = nca.GetTitleId(),
681 .title_version = base_header.title_version,
682 .type = base_header.type,
683 .reserved = {},
684 .table_offset = 0x10,
685 .number_content_entries = 1,
686 .number_meta_entries = 0,
687 .attributes = 0,
688 .reserved2 = {},
689 .is_committed = 0,
690 .required_download_system_version = 0,
691 .reserved3 = {},
692 };
693 const OptionalHeader opt_header{0, 0};
694 const CNMT new_cnmt(header, opt_header, {base_record}, {});
695 if (!RawInstallYuzuMeta(new_cnmt)) {
696 return InstallResult::ErrorMetaFailed;
697 }
698 return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id);
699}
700
665bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { 701bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
702 bool removed_data = false;
703
666 const auto delete_nca = [this](const NcaID& id) { 704 const auto delete_nca = [this](const NcaID& id) {
667 const auto path = GetRelativePathFromNcaID(id, false, true, false); 705 const auto path = GetRelativePathFromNcaID(id, false, true, false);
668 706
@@ -706,11 +744,20 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
706 const auto deleted_html = delete_nca(html_id); 744 const auto deleted_html = delete_nca(html_id);
707 const auto deleted_legal = delete_nca(legal_id); 745 const auto deleted_legal = delete_nca(legal_id);
708 746
709 return deleted_meta && (deleted_meta || deleted_program || deleted_data || 747 removed_data |= (deleted_meta || deleted_program || deleted_data || deleted_control ||
710 deleted_control || deleted_html || deleted_legal); 748 deleted_html || deleted_legal);
711 } 749 }
712 750
713 return false; 751 // If patch entries for any program exist in yuzu meta, remove them
752 for (u8 i = 0; i < 0x10; i++) {
753 const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta");
754 const auto filename = GetCNMTName(TitleType::Update, title_id + i);
755 if (meta_dir->GetFile(filename)) {
756 removed_data |= meta_dir->DeleteFile(filename);
757 }
758 }
759
760 return removed_data;
714} 761}
715 762
716InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, 763InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
@@ -964,7 +1011,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord
964 const auto res = GetEntryRaw(title_id, type); 1011 const auto res = GetEntryRaw(title_id, type);
965 if (res == nullptr) 1012 if (res == nullptr)
966 return nullptr; 1013 return nullptr;
967 return std::make_unique<NCA>(res, nullptr, 0); 1014 return std::make_unique<NCA>(res);
968} 1015}
969 1016
970std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( 1017std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index bd7f53eaf..64815a845 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -24,6 +24,7 @@ enum class NCAContentType : u8;
24enum class TitleType : u8; 24enum class TitleType : u8;
25 25
26struct ContentRecord; 26struct ContentRecord;
27struct CNMTHeader;
27struct MetaRecord; 28struct MetaRecord;
28class RegisteredCache; 29class RegisteredCache;
29 30
@@ -169,6 +170,10 @@ public:
169 InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, 170 InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
170 const VfsCopyFunction& copy = &VfsRawCopy); 171 const VfsCopyFunction& copy = &VfsRawCopy);
171 172
173 InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header,
174 const ContentRecord& base_record, bool overwrite_if_exists = false,
175 const VfsCopyFunction& copy = &VfsRawCopy);
176
172 // Removes an existing entry based on title id 177 // Removes an existing entry based on title id
173 bool RemoveExistingEntry(u64 title_id) const; 178 bool RemoveExistingEntry(u64 title_id) const;
174 179
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index aa4726cfa..1bc07dae5 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -26,13 +26,12 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi
26 } 26 }
27 27
28 updatable = app_loader.IsRomFSUpdatable(); 28 updatable = app_loader.IsRomFSUpdatable();
29 ivfc_offset = app_loader.ReadRomFSIVFCOffset();
30} 29}
31 30
32RomFSFactory::~RomFSFactory() = default; 31RomFSFactory::~RomFSFactory() = default;
33 32
34void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { 33void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) {
35 update_raw = std::move(update_raw_file); 34 packed_update_raw = std::move(update_raw_file);
36} 35}
37 36
38VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { 37VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
@@ -40,9 +39,11 @@ VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const
40 return file; 39 return file;
41 } 40 }
42 41
42 const auto type = ContentRecordType::Program;
43 const auto nca = content_provider.GetEntry(current_process_title_id, type);
43 const PatchManager patch_manager{current_process_title_id, filesystem_controller, 44 const PatchManager patch_manager{current_process_title_id, filesystem_controller,
44 content_provider}; 45 content_provider};
45 return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw); 46 return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw);
46} 47}
47 48
48VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { 49VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
@@ -54,7 +55,7 @@ VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type)
54 55
55 const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; 56 const PatchManager patch_manager{title_id, filesystem_controller, content_provider};
56 57
57 return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type); 58 return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type);
58} 59}
59 60
60VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, 61VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 7ec40d19d..e4809bc94 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -40,21 +40,22 @@ public:
40 Service::FileSystem::FileSystemController& controller); 40 Service::FileSystem::FileSystemController& controller);
41 ~RomFSFactory(); 41 ~RomFSFactory();
42 42
43 void SetPackedUpdate(VirtualFile update_raw_file); 43 void SetPackedUpdate(VirtualFile packed_update_raw);
44 [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; 44 [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const;
45 [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; 45 [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const;
46 [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, 46 [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
47 ContentRecordType type) const; 47 ContentRecordType type) const;
48 [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; 48 [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const;
49
50private:
51 [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, 49 [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage,
52 ContentRecordType type) const; 50 ContentRecordType type) const;
53 51
52private:
54 VirtualFile file; 53 VirtualFile file;
55 VirtualFile update_raw; 54 VirtualFile packed_update_raw;
55
56 VirtualFile base;
57
56 bool updatable; 58 bool updatable;
57 u64 ivfc_offset;
58 59
59 ContentProvider& content_provider; 60 ContentProvider& content_provider;
60 Service::FileSystem::FileSystemController& filesystem_controller; 61 Service::FileSystem::FileSystemController& filesystem_controller;
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index c90e6e372..68e8ec22f 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -164,24 +164,6 @@ VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType titl
164 return nullptr; 164 return nullptr;
165} 165}
166 166
167std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const {
168 if (extracted)
169 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
170 std::vector<Core::Crypto::Key128> out;
171 for (const auto& ticket_file : ticket_files) {
172 if (ticket_file == nullptr ||
173 ticket_file->GetSize() <
174 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
175 continue;
176 }
177
178 out.emplace_back();
179 ticket_file->Read(out.back().data(), out.back().size(),
180 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
181 }
182 return out;
183}
184
185std::vector<VirtualFile> NSP::GetFiles() const { 167std::vector<VirtualFile> NSP::GetFiles() const {
186 return pfs->GetFiles(); 168 return pfs->GetFiles();
187} 169}
@@ -208,22 +190,11 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) {
208 continue; 190 continue;
209 } 191 }
210 192
211 if (ticket_file->GetSize() < 193 auto ticket = Core::Crypto::Ticket::Read(ticket_file);
212 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { 194 if (!keys.AddTicket(ticket)) {
195 LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName());
213 continue; 196 continue;
214 } 197 }
215
216 Core::Crypto::Key128 key{};
217 ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
218
219 // We get the name without the extension in order to create the rights ID.
220 std::string name_only(ticket_file->GetName());
221 name_only.erase(name_only.size() - 4);
222
223 const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
224 u128 rights_id;
225 std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
226 keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
227 } 198 }
228} 199}
229 200
@@ -249,7 +220,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
249 } 220 }
250 221
251 const auto nca = std::make_shared<NCA>(outer_file); 222 const auto nca = std::make_shared<NCA>(outer_file);
252 if (nca->GetStatus() != Loader::ResultStatus::Success) { 223 if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) {
253 program_status[nca->GetTitleId()] = nca->GetStatus(); 224 program_status[nca->GetTitleId()] = nca->GetStatus();
254 continue; 225 continue;
255 } 226 }
@@ -280,7 +251,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
280 continue; 251 continue;
281 } 252 }
282 253
283 auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); 254 auto next_nca = std::make_shared<NCA>(std::move(next_file));
284 255
285 if (next_nca->GetType() == NCAContentType::Program) { 256 if (next_nca->GetType() == NCAContentType::Program) {
286 program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); 257 program_status[next_nca->GetTitleId()] = next_nca->GetStatus();
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index 27f97c725..915bffca9 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -53,7 +53,6 @@ public:
53 TitleType title_type = TitleType::Application) const; 53 TitleType title_type = TitleType::Application) const;
54 VirtualFile GetNCAFile(u64 title_id, ContentRecordType type, 54 VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
55 TitleType title_type = TitleType::Application) const; 55 TitleType title_type = TitleType::Application) const;
56 std::vector<Core::Crypto::Key128> GetTitlekey() const;
57 56
58 std::vector<VirtualFile> GetFiles() const override; 57 std::vector<VirtualFile> GetFiles() const override;
59 58
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
index 3300d4f79..27755cb58 100644
--- a/src/core/frontend/applets/controller.cpp
+++ b/src/core/frontend/applets/controller.cpp
@@ -3,6 +3,8 @@
3 3
4#include "common/assert.h" 4#include "common/assert.h"
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "common/settings.h"
7#include "common/settings_enums.h"
6#include "core/frontend/applets/controller.h" 8#include "core/frontend/applets/controller.h"
7#include "core/hid/emulated_controller.h" 9#include "core/hid/emulated_controller.h"
8#include "core/hid/hid_core.h" 10#include "core/hid/hid_core.h"
@@ -62,7 +64,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac
62 controller->Connect(true); 64 controller->Connect(true);
63 } 65 }
64 } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && 66 } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
65 !Settings::values.use_docked_mode.GetValue()) { 67 !Settings::IsDockedMode()) {
66 // We should *never* reach here under any normal circumstances. 68 // We should *never* reach here under any normal circumstances.
67 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); 69 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
68 controller->Connect(true); 70 controller->Connect(true);
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index b4081fc39..2590b20da 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -5,6 +5,7 @@
5 5
6#include "common/assert.h" 6#include "common/assert.h"
7#include "common/settings.h" 7#include "common/settings.h"
8#include "common/settings_enums.h"
8#include "core/frontend/framebuffer_layout.h" 9#include "core/frontend/framebuffer_layout.h"
9 10
10namespace Layout { 11namespace Layout {
@@ -49,7 +50,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
49} 50}
50 51
51FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) { 52FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) {
52 const bool is_docked = Settings::values.use_docked_mode.GetValue(); 53 const bool is_docked = Settings::IsDockedMode();
53 const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width; 54 const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width;
54 const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height; 55 const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height;
55 56
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp
index 7d6373414..cf53c04d9 100644
--- a/src/core/hid/hid_core.cpp
+++ b/src/core/hid/hid_core.cpp
@@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
154 return NpadIdType::Player1; 154 return NpadIdType::Player1;
155} 155}
156 156
157void HIDCore::SetLastActiveController(NpadIdType npad_id) {
158 last_active_controller = npad_id;
159}
160
161NpadIdType HIDCore::GetLastActiveController() const {
162 return last_active_controller;
163}
164
157void HIDCore::EnableAllControllerConfiguration() { 165void HIDCore::EnableAllControllerConfiguration() {
158 player_1->EnableConfiguration(); 166 player_1->EnableConfiguration();
159 player_2->EnableConfiguration(); 167 player_2->EnableConfiguration();
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h
index 5fe36551e..80abab18b 100644
--- a/src/core/hid/hid_core.h
+++ b/src/core/hid/hid_core.h
@@ -48,6 +48,12 @@ public:
48 /// Returns the first disconnected npad id 48 /// Returns the first disconnected npad id
49 NpadIdType GetFirstDisconnectedNpadId() const; 49 NpadIdType GetFirstDisconnectedNpadId() const;
50 50
51 /// Sets the npad id of the last active controller
52 void SetLastActiveController(NpadIdType npad_id);
53
54 /// Returns the npad id of the last controller that pushed a button
55 NpadIdType GetLastActiveController() const;
56
51 /// Sets all emulated controllers into configuring mode. 57 /// Sets all emulated controllers into configuring mode.
52 void EnableAllControllerConfiguration(); 58 void EnableAllControllerConfiguration();
53 59
@@ -77,6 +83,7 @@ private:
77 std::unique_ptr<EmulatedConsole> console; 83 std::unique_ptr<EmulatedConsole> console;
78 std::unique_ptr<EmulatedDevices> devices; 84 std::unique_ptr<EmulatedDevices> devices;
79 NpadStyleTag supported_style_tag{NpadStyleSet::All}; 85 NpadStyleTag supported_style_tag{NpadStyleSet::All};
86 NpadIdType last_active_controller{NpadIdType::Handheld};
80}; 87};
81 88
82} // namespace Core::HID 89} // namespace Core::HID
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index 6b35f448c..00beb40dd 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -289,6 +289,19 @@ enum class GyroscopeZeroDriftMode : u32 {
289 Tight = 2, 289 Tight = 2,
290}; 290};
291 291
292// This is nn::settings::system::TouchScreenMode
293enum class TouchScreenMode : u32 {
294 Stylus = 0,
295 Standard = 1,
296};
297
298// This is nn::hid::TouchScreenModeForNx
299enum class TouchScreenModeForNx : u8 {
300 UseSystemSetting,
301 Finger,
302 Heat2,
303};
304
292// This is nn::hid::NpadStyleTag 305// This is nn::hid::NpadStyleTag
293struct NpadStyleTag { 306struct NpadStyleTag {
294 union { 307 union {
@@ -334,6 +347,14 @@ struct TouchState {
334}; 347};
335static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); 348static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
336 349
350// This is nn::hid::TouchScreenConfigurationForNx
351struct TouchScreenConfigurationForNx {
352 TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
353 INSERT_PADDING_BYTES(0xF);
354};
355static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10,
356 "TouchScreenConfigurationForNx is an invalid size");
357
337struct NpadColor { 358struct NpadColor {
338 u8 r{}; 359 u8 r{};
339 u8 g{}; 360 u8 g{};
@@ -662,6 +683,11 @@ struct MouseState {
662}; 683};
663static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); 684static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
664 685
686struct UniquePadId {
687 u64 id;
688};
689static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size");
690
665/// Converts a NpadIdType to an array index. 691/// Converts a NpadIdType to an array index.
666constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { 692constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
667 switch (npad_id_type) { 693 switch (npad_id_type) {
diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp
index 90e4e8fb0..e7da7a21d 100644
--- a/src/core/hle/kernel/k_capabilities.cpp
+++ b/src/core/hle/kernel/k_capabilities.cpp
@@ -156,7 +156,6 @@ Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) {
156 const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize; 156 const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize;
157 const size_t num_pages = 1; 157 const size_t num_pages = 1;
158 const size_t size = num_pages * PageSize; 158 const size_t size = num_pages * PageSize;
159 R_UNLESS(num_pages != 0, ResultInvalidSize);
160 R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); 159 R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress);
161 R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); 160 R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress);
162 161
diff --git a/src/core/hle/kernel/k_hardware_timer.h b/src/core/hle/kernel/k_hardware_timer.h
index 00bef6ea1..27f43cd19 100644
--- a/src/core/hle/kernel/k_hardware_timer.h
+++ b/src/core/hle/kernel/k_hardware_timer.h
@@ -19,13 +19,7 @@ public:
19 void Initialize(); 19 void Initialize();
20 void Finalize(); 20 void Finalize();
21 21
22 s64 GetCount() const { 22 s64 GetTick() const;
23 return GetTick();
24 }
25
26 void RegisterTask(KTimerTask* task, s64 time_from_now) {
27 this->RegisterAbsoluteTask(task, GetTick() + time_from_now);
28 }
29 23
30 void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { 24 void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) {
31 KScopedDisableDispatch dd{m_kernel}; 25 KScopedDisableDispatch dd{m_kernel};
@@ -42,7 +36,6 @@ private:
42 void EnableInterrupt(s64 wakeup_time); 36 void EnableInterrupt(s64 wakeup_time);
43 void DisableInterrupt(); 37 void DisableInterrupt();
44 bool GetInterruptEnabled(); 38 bool GetInterruptEnabled();
45 s64 GetTick() const;
46 void DoTask(); 39 void DoTask();
47 40
48private: 41private:
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index e573e2a57..4a099286b 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -38,7 +38,7 @@ namespace {
38 */ 38 */
39void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, 39void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority,
40 KProcessAddress stack_top) { 40 KProcessAddress stack_top) {
41 const KProcessAddress entry_point = owner_process.GetPageTable().GetCodeRegionStart(); 41 const KProcessAddress entry_point = owner_process.GetEntryPoint();
42 ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1)); 42 ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1));
43 43
44 KThread* thread = KThread::Create(system.Kernel()); 44 KThread* thread = KThread::Create(system.Kernel());
@@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
96 process->m_is_suspended = false; 96 process->m_is_suspended = false;
97 process->m_schedule_count = 0; 97 process->m_schedule_count = 0;
98 process->m_is_handle_table_initialized = false; 98 process->m_is_handle_table_initialized = false;
99 process->m_is_hbl = false;
99 100
100 // Open a reference to the resource limit. 101 // Open a reference to the resource limit.
101 process->m_resource_limit->Open(); 102 process->m_resource_limit->Open();
@@ -351,12 +352,29 @@ Result KProcess::SetActivity(ProcessActivity activity) {
351 R_SUCCEED(); 352 R_SUCCEED();
352} 353}
353 354
354Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { 355Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
356 bool is_hbl) {
355 m_program_id = metadata.GetTitleID(); 357 m_program_id = metadata.GetTitleID();
356 m_ideal_core = metadata.GetMainThreadCore(); 358 m_ideal_core = metadata.GetMainThreadCore();
357 m_is_64bit_process = metadata.Is64BitProgram(); 359 m_is_64bit_process = metadata.Is64BitProgram();
358 m_system_resource_size = metadata.GetSystemResourceSize(); 360 m_system_resource_size = metadata.GetSystemResourceSize();
359 m_image_size = code_size; 361 m_image_size = code_size;
362 m_is_hbl = is_hbl;
363
364 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
365 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
366 // However, some (buggy) programs/libraries like skyline incorrectly depend on the
367 // existence of ASLR pages before the entry point, so we will adjust the load address
368 // to point to about 2GiB into the ASLR region.
369 m_code_address = 0x8000'0000;
370 } else {
371 // All other processes can be mapped at the beginning of the code region.
372 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) {
373 m_code_address = 0x800'0000;
374 } else {
375 m_code_address = 0x20'0000;
376 }
377 }
360 378
361 KScopedResourceReservation memory_reservation( 379 KScopedResourceReservation memory_reservation(
362 m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size); 380 m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size);
@@ -368,15 +386,15 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
368 // Initialize process address space 386 // Initialize process address space
369 if (const Result result{m_page_table.InitializeForProcess( 387 if (const Result result{m_page_table.InitializeForProcess(
370 metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application, 388 metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application,
371 0x8000000, code_size, std::addressof(m_kernel.GetAppSystemResource()), m_resource_limit, 389 this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()),
372 m_kernel.System().ApplicationMemory())}; 390 m_resource_limit, m_kernel.System().ApplicationMemory())};
373 result.IsError()) { 391 result.IsError()) {
374 R_RETURN(result); 392 R_RETURN(result);
375 } 393 }
376 394
377 // Map process code region 395 // Map process code region
378 if (const Result result{m_page_table.MapProcessCode(m_page_table.GetCodeRegionStart(), 396 if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize,
379 code_size / PageSize, KMemoryState::Code, 397 KMemoryState::Code,
380 KMemoryPermission::None)}; 398 KMemoryPermission::None)};
381 result.IsError()) { 399 result.IsError()) {
382 R_RETURN(result); 400 R_RETURN(result);
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index c9b37e138..146e07a57 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -177,6 +177,10 @@ public:
177 return m_program_id; 177 return m_program_id;
178 } 178 }
179 179
180 KProcessAddress GetEntryPoint() const {
181 return m_code_address;
182 }
183
180 /// Gets the resource limit descriptor for this process 184 /// Gets the resource limit descriptor for this process
181 KResourceLimit* GetResourceLimit() const; 185 KResourceLimit* GetResourceLimit() const;
182 186
@@ -334,7 +338,8 @@ public:
334 * @returns ResultSuccess if all relevant metadata was able to be 338 * @returns ResultSuccess if all relevant metadata was able to be
335 * loaded and parsed. Otherwise, an error code is returned. 339 * loaded and parsed. Otherwise, an error code is returned.
336 */ 340 */
337 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); 341 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
342 bool is_hbl);
338 343
339 /** 344 /**
340 * Starts the main application thread for this process. 345 * Starts the main application thread for this process.
@@ -364,6 +369,10 @@ public:
364 return GetProcessId(); 369 return GetProcessId();
365 } 370 }
366 371
372 bool IsHbl() const {
373 return m_is_hbl;
374 }
375
367 bool IsSignaled() const override; 376 bool IsSignaled() const override;
368 377
369 void DoWorkerTaskImpl(); 378 void DoWorkerTaskImpl();
@@ -485,6 +494,9 @@ private:
485 /// Address indicating the location of the process' dedicated TLS region. 494 /// Address indicating the location of the process' dedicated TLS region.
486 KProcessAddress m_plr_address = 0; 495 KProcessAddress m_plr_address = 0;
487 496
497 /// Address indicating the location of the process's entry point.
498 KProcessAddress m_code_address = 0;
499
488 /// Random values for svcGetInfo RandomEntropy 500 /// Random values for svcGetInfo RandomEntropy
489 std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{}; 501 std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{};
490 502
@@ -518,6 +530,7 @@ private:
518 bool m_is_immortal{}; 530 bool m_is_immortal{};
519 bool m_is_handle_table_initialized{}; 531 bool m_is_handle_table_initialized{};
520 bool m_is_initialized{}; 532 bool m_is_initialized{};
533 bool m_is_hbl{};
521 534
522 std::atomic<u16> m_num_running_threads{}; 535 std::atomic<u16> m_num_running_threads{};
523 536
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp
index fcee26a29..d8a63aaf8 100644
--- a/src/core/hle/kernel/k_resource_limit.cpp
+++ b/src/core/hle/kernel/k_resource_limit.cpp
@@ -5,6 +5,7 @@
5#include "common/overflow.h" 5#include "common/overflow.h"
6#include "core/core.h" 6#include "core/core.h"
7#include "core/core_timing.h" 7#include "core/core_timing.h"
8#include "core/hle/kernel/k_hardware_timer.h"
8#include "core/hle/kernel/k_resource_limit.h" 9#include "core/hle/kernel/k_resource_limit.h"
9#include "core/hle/kernel/svc_results.h" 10#include "core/hle/kernel/svc_results.h"
10 11
@@ -15,9 +16,7 @@ KResourceLimit::KResourceLimit(KernelCore& kernel)
15 : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} 16 : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {}
16KResourceLimit::~KResourceLimit() = default; 17KResourceLimit::~KResourceLimit() = default;
17 18
18void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) { 19void KResourceLimit::Initialize() {}
19 m_core_timing = core_timing;
20}
21 20
22void KResourceLimit::Finalize() {} 21void KResourceLimit::Finalize() {}
23 22
@@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
86} 85}
87 86
88bool KResourceLimit::Reserve(LimitableResource which, s64 value) { 87bool KResourceLimit::Reserve(LimitableResource which, s64 value) {
89 return Reserve(which, value, m_core_timing->GetGlobalTimeNs().count() + DefaultTimeout); 88 return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout);
90} 89}
91 90
92bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { 91bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) {
@@ -117,7 +116,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) {
117 } 116 }
118 117
119 if (m_current_hints[index] + value <= m_limit_values[index] && 118 if (m_current_hints[index] + value <= m_limit_values[index] &&
120 (timeout < 0 || m_core_timing->GetGlobalTimeNs().count() < timeout)) { 119 (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) {
121 m_waiter_count++; 120 m_waiter_count++;
122 m_cond_var.Wait(std::addressof(m_lock), timeout, false); 121 m_cond_var.Wait(std::addressof(m_lock), timeout, false);
123 m_waiter_count--; 122 m_waiter_count--;
@@ -154,7 +153,7 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) {
154 153
155KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { 154KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) {
156 auto* resource_limit = KResourceLimit::Create(system.Kernel()); 155 auto* resource_limit = KResourceLimit::Create(system.Kernel());
157 resource_limit->Initialize(std::addressof(system.CoreTiming())); 156 resource_limit->Initialize();
158 157
159 // Initialize default resource limit values. 158 // Initialize default resource limit values.
160 // TODO(bunnei): These values are the system defaults, the limits for service processes are 159 // TODO(bunnei): These values are the system defaults, the limits for service processes are
diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h
index 15e69af56..b733ec8f8 100644
--- a/src/core/hle/kernel/k_resource_limit.h
+++ b/src/core/hle/kernel/k_resource_limit.h
@@ -31,7 +31,7 @@ public:
31 explicit KResourceLimit(KernelCore& kernel); 31 explicit KResourceLimit(KernelCore& kernel);
32 ~KResourceLimit() override; 32 ~KResourceLimit() override;
33 33
34 void Initialize(const Core::Timing::CoreTiming* core_timing); 34 void Initialize();
35 void Finalize() override; 35 void Finalize() override;
36 36
37 s64 GetLimitValue(LimitableResource which) const; 37 s64 GetLimitValue(LimitableResource which) const;
@@ -57,7 +57,6 @@ private:
57 mutable KLightLock m_lock; 57 mutable KLightLock m_lock;
58 s32 m_waiter_count{}; 58 s32 m_waiter_count{};
59 KLightConditionVariable m_cond_var; 59 KLightConditionVariable m_cond_var;
60 const Core::Timing::CoreTiming* m_core_timing{};
61}; 60};
62 61
63KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); 62KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size);
diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
index c485022f5..b62415da7 100644
--- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
+++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
@@ -28,7 +28,7 @@ public:
28 ~KScopedSchedulerLockAndSleep() { 28 ~KScopedSchedulerLockAndSleep() {
29 // Register the sleep. 29 // Register the sleep.
30 if (m_timeout_tick > 0) { 30 if (m_timeout_tick > 0) {
31 m_timer->RegisterTask(m_thread, m_timeout_tick); 31 m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick);
32 } 32 }
33 33
34 // Unlock the scheduler. 34 // Unlock the scheduler.
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index ebe7582c6..a1134b7e2 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -231,7 +231,7 @@ struct KernelCore::Impl {
231 void InitializeSystemResourceLimit(KernelCore& kernel, 231 void InitializeSystemResourceLimit(KernelCore& kernel,
232 const Core::Timing::CoreTiming& core_timing) { 232 const Core::Timing::CoreTiming& core_timing) {
233 system_resource_limit = KResourceLimit::Create(system.Kernel()); 233 system_resource_limit = KResourceLimit::Create(system.Kernel());
234 system_resource_limit->Initialize(&core_timing); 234 system_resource_limit->Initialize();
235 KResourceLimit::Register(kernel, system_resource_limit); 235 KResourceLimit::Register(kernel, system_resource_limit);
236 236
237 const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; 237 const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()};
diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp
index 04cc5ea64..90ee43521 100644
--- a/src/core/hle/kernel/svc/svc_address_arbiter.cpp
+++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp
@@ -2,6 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/core.h" 4#include "core/core.h"
5#include "core/hle/kernel/k_hardware_timer.h"
5#include "core/hle/kernel/k_memory_layout.h" 6#include "core/hle/kernel/k_memory_layout.h"
6#include "core/hle/kernel/k_process.h" 7#include "core/hle/kernel/k_process.h"
7#include "core/hle/kernel/kernel.h" 8#include "core/hle/kernel/kernel.h"
@@ -52,7 +53,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ
52 if (timeout_ns > 0) { 53 if (timeout_ns > 0) {
53 const s64 offset_tick(timeout_ns); 54 const s64 offset_tick(timeout_ns);
54 if (offset_tick > 0) { 55 if (offset_tick > 0) {
55 timeout = offset_tick + 2; 56 timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2;
56 if (timeout <= 0) { 57 if (timeout <= 0) {
57 timeout = std::numeric_limits<s64>::max(); 58 timeout = std::numeric_limits<s64>::max();
58 } 59 }
diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp
index ca120d67e..bb678e6c5 100644
--- a/src/core/hle/kernel/svc/svc_condition_variable.cpp
+++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp
@@ -2,6 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/core.h" 4#include "core/core.h"
5#include "core/hle/kernel/k_hardware_timer.h"
5#include "core/hle/kernel/k_memory_layout.h" 6#include "core/hle/kernel/k_memory_layout.h"
6#include "core/hle/kernel/k_process.h" 7#include "core/hle/kernel/k_process.h"
7#include "core/hle/kernel/kernel.h" 8#include "core/hle/kernel/kernel.h"
@@ -25,7 +26,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u
25 if (timeout_ns > 0) { 26 if (timeout_ns > 0) {
26 const s64 offset_tick(timeout_ns); 27 const s64 offset_tick(timeout_ns);
27 if (offset_tick > 0) { 28 if (offset_tick > 0) {
28 timeout = offset_tick + 2; 29 timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2;
29 if (timeout <= 0) { 30 if (timeout <= 0) {
30 timeout = std::numeric_limits<s64>::max(); 31 timeout = std::numeric_limits<s64>::max();
31 } 32 }
diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp
index 4c14ce668..00b65429b 100644
--- a/src/core/hle/kernel/svc/svc_debug_string.cpp
+++ b/src/core/hle/kernel/svc/svc_debug_string.cpp
@@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) {
14 14
15 std::string str(len, '\0'); 15 std::string str(len, '\0');
16 GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); 16 GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size());
17 LOG_DEBUG(Debug_Emulated, "{}", str); 17 LOG_INFO(Debug_Emulated, "{}", str);
18 18
19 R_SUCCEED(); 19 R_SUCCEED();
20} 20}
diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp
index 580cf2f75..c581c086b 100644
--- a/src/core/hle/kernel/svc/svc_exception.cpp
+++ b/src/core/hle/kernel/svc/svc_exception.cpp
@@ -3,6 +3,7 @@
3 3
4#include "core/core.h" 4#include "core/core.h"
5#include "core/debugger/debugger.h" 5#include "core/debugger/debugger.h"
6#include "core/hle/kernel/k_process.h"
6#include "core/hle/kernel/k_thread.h" 7#include "core/hle/kernel/k_thread.h"
7#include "core/hle/kernel/svc.h" 8#include "core/hle/kernel/svc.h"
8#include "core/hle/kernel/svc_types.h" 9#include "core/hle/kernel/svc_types.h"
@@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
107 system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); 108 system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
108 } 109 }
109 110
110 if (system.DebuggerEnabled()) { 111 const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl();
112 const bool should_break = is_hbl || !notification_only;
113
114 if (system.DebuggerEnabled() && should_break) {
111 auto* thread = system.Kernel().GetCurrentEmuThread(); 115 auto* thread = system.Kernel().GetCurrentEmuThread();
112 system.GetDebugger().NotifyThreadStopped(thread); 116 system.GetDebugger().NotifyThreadStopped(thread);
113 thread->RequestSuspend(Kernel::SuspendType::Debug); 117 thread->RequestSuspend(Kernel::SuspendType::Debug);
diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp
index 373ae7c8d..6b5e1cb8d 100644
--- a/src/core/hle/kernel/svc/svc_ipc.cpp
+++ b/src/core/hle/kernel/svc/svc_ipc.cpp
@@ -5,6 +5,7 @@
5#include "common/scratch_buffer.h" 5#include "common/scratch_buffer.h"
6#include "core/core.h" 6#include "core/core.h"
7#include "core/hle/kernel/k_client_session.h" 7#include "core/hle/kernel/k_client_session.h"
8#include "core/hle/kernel/k_hardware_timer.h"
8#include "core/hle/kernel/k_process.h" 9#include "core/hle/kernel/k_process.h"
9#include "core/hle/kernel/k_server_session.h" 10#include "core/hle/kernel/k_server_session.h"
10#include "core/hle/kernel/svc.h" 11#include "core/hle/kernel/svc.h"
@@ -82,12 +83,29 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad
82 R_TRY(session->SendReply()); 83 R_TRY(session->SendReply());
83 } 84 }
84 85
86 // Convert the timeout from nanoseconds to ticks.
87 // NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
88 s64 timeout;
89 if (timeout_ns > 0) {
90 const s64 offset_tick(timeout_ns);
91 if (offset_tick > 0) {
92 timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2;
93 if (timeout <= 0) {
94 timeout = std::numeric_limits<s64>::max();
95 }
96 } else {
97 timeout = std::numeric_limits<s64>::max();
98 }
99 } else {
100 timeout = timeout_ns;
101 }
102
85 // Wait for a message. 103 // Wait for a message.
86 while (true) { 104 while (true) {
87 // Wait for an object. 105 // Wait for an object.
88 s32 index; 106 s32 index;
89 Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), 107 Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(),
90 num_handles, timeout_ns); 108 num_handles, timeout);
91 if (result == ResultTimedOut) { 109 if (result == ResultTimedOut) {
92 R_RETURN(result); 110 R_RETURN(result);
93 } 111 }
diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp
index 732bc017e..c8e820b6a 100644
--- a/src/core/hle/kernel/svc/svc_resource_limit.cpp
+++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp
@@ -21,7 +21,7 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) {
21 SCOPE_EXIT({ resource_limit->Close(); }); 21 SCOPE_EXIT({ resource_limit->Close(); });
22 22
23 // Initialize the resource limit. 23 // Initialize the resource limit.
24 resource_limit->Initialize(std::addressof(system.CoreTiming())); 24 resource_limit->Initialize();
25 25
26 // Register the limit. 26 // Register the limit.
27 KResourceLimit::Register(kernel, resource_limit); 27 KResourceLimit::Register(kernel, resource_limit);
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp
index 366e8ed4a..8ebc1bd1c 100644
--- a/src/core/hle/kernel/svc/svc_synchronization.cpp
+++ b/src/core/hle/kernel/svc/svc_synchronization.cpp
@@ -4,6 +4,7 @@
4#include "common/scope_exit.h" 4#include "common/scope_exit.h"
5#include "common/scratch_buffer.h" 5#include "common/scratch_buffer.h"
6#include "core/core.h" 6#include "core/core.h"
7#include "core/hle/kernel/k_hardware_timer.h"
7#include "core/hle/kernel/k_process.h" 8#include "core/hle/kernel/k_process.h"
8#include "core/hle/kernel/k_readable_event.h" 9#include "core/hle/kernel/k_readable_event.h"
9#include "core/hle/kernel/svc.h" 10#include "core/hle/kernel/svc.h"
@@ -83,9 +84,20 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha
83 } 84 }
84 }); 85 });
85 86
87 // Convert the timeout from nanoseconds to ticks.
88 s64 timeout;
89 if (timeout_ns > 0) {
90 u64 ticks = kernel.HardwareTimer().GetTick();
91 ticks += timeout_ns;
92 ticks += 2;
93
94 timeout = ticks;
95 } else {
96 timeout = timeout_ns;
97 }
98
86 // Wait on the objects. 99 // Wait on the objects.
87 Result res = 100 Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout);
88 KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns);
89 101
90 R_SUCCEED_IF(res == ResultSessionClosed); 102 R_SUCCEED_IF(res == ResultSessionClosed);
91 R_RETURN(res); 103 R_RETURN(res);
diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp
index 92bcea72b..933b82e30 100644
--- a/src/core/hle/kernel/svc/svc_thread.cpp
+++ b/src/core/hle/kernel/svc/svc_thread.cpp
@@ -4,6 +4,7 @@
4#include "common/scope_exit.h" 4#include "common/scope_exit.h"
5#include "core/core.h" 5#include "core/core.h"
6#include "core/core_timing.h" 6#include "core/core_timing.h"
7#include "core/hle/kernel/k_hardware_timer.h"
7#include "core/hle/kernel/k_process.h" 8#include "core/hle/kernel/k_process.h"
8#include "core/hle/kernel/k_scoped_resource_reservation.h" 9#include "core/hle/kernel/k_scoped_resource_reservation.h"
9#include "core/hle/kernel/k_thread.h" 10#include "core/hle/kernel/k_thread.h"
@@ -42,9 +43,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u
42 R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); 43 R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority);
43 44
44 // Reserve a new thread from the process resource limit (waiting up to 100ms). 45 // Reserve a new thread from the process resource limit (waiting up to 100ms).
45 KScopedResourceReservation thread_reservation( 46 KScopedResourceReservation thread_reservation(std::addressof(process),
46 std::addressof(process), LimitableResource::ThreadCountMax, 1, 47 LimitableResource::ThreadCountMax, 1,
47 system.CoreTiming().GetGlobalTimeNs().count() + 100000000); 48 kernel.HardwareTimer().GetTick() + 100000000);
48 R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); 49 R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached);
49 50
50 // Create the thread. 51 // Create the thread.
@@ -102,20 +103,31 @@ void ExitThread(Core::System& system) {
102} 103}
103 104
104/// Sleep the current thread 105/// Sleep the current thread
105void SleepThread(Core::System& system, s64 nanoseconds) { 106void SleepThread(Core::System& system, s64 ns) {
106 auto& kernel = system.Kernel(); 107 auto& kernel = system.Kernel();
107 const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); 108 const auto yield_type = static_cast<Svc::YieldType>(ns);
108 109
109 LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); 110 LOG_TRACE(Kernel_SVC, "called nanoseconds={}", ns);
110 111
111 // When the input tick is positive, sleep. 112 // When the input tick is positive, sleep.
112 if (nanoseconds > 0) { 113 if (ns > 0) {
113 // Convert the timeout from nanoseconds to ticks. 114 // Convert the timeout from nanoseconds to ticks.
114 // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... 115 // NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
116 s64 timeout;
117
118 const s64 offset_tick(ns);
119 if (offset_tick > 0) {
120 timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2;
121 if (timeout <= 0) {
122 timeout = std::numeric_limits<s64>::max();
123 }
124 } else {
125 timeout = std::numeric_limits<s64>::max();
126 }
115 127
116 // Sleep. 128 // Sleep.
117 // NOTE: Nintendo does not check the result of this sleep. 129 // NOTE: Nintendo does not check the result of this sleep.
118 static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); 130 static_cast<void>(GetCurrentThread(kernel).Sleep(timeout));
119 } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { 131 } else if (yield_type == Svc::YieldType::WithoutCoreMigration) {
120 KScheduler::YieldWithoutCoreMigration(kernel); 132 KScheduler::YieldWithoutCoreMigration(kernel);
121 } else if (yield_type == Svc::YieldType::WithCoreMigration) { 133 } else if (yield_type == Svc::YieldType::WithCoreMigration) {
@@ -124,7 +136,6 @@ void SleepThread(Core::System& system, s64 nanoseconds) {
124 KScheduler::YieldToAnyThread(kernel); 136 KScheduler::YieldToAnyThread(kernel);
125 } else { 137 } else {
126 // Nintendo does nothing at all if an otherwise invalid value is passed. 138 // Nintendo does nothing at all if an otherwise invalid value is passed.
127 ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds);
128 } 139 }
129} 140}
130 141
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 92a1439eb..dd0b27f47 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -62,7 +62,7 @@ enum class ErrorModule : u32 {
62 XCD = 108, 62 XCD = 108,
63 TMP451 = 109, 63 TMP451 = 109,
64 NIFM = 110, 64 NIFM = 110,
65 Hwopus = 111, 65 HwOpus = 111,
66 LSM6DS3 = 112, 66 LSM6DS3 = 112,
67 Bluetooth = 113, 67 Bluetooth = 113,
68 VI = 114, 68 VI = 114,
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 8d057b3a8..8ffdd19e7 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -6,6 +6,7 @@
6#include <cinttypes> 6#include <cinttypes>
7#include <cstring> 7#include <cstring>
8#include "common/settings.h" 8#include "common/settings.h"
9#include "common/settings_enums.h"
9#include "core/core.h" 10#include "core/core.h"
10#include "core/file_sys/control_metadata.h" 11#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/patch_manager.h" 12#include "core/file_sys/patch_manager.h"
@@ -45,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3};
45constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; 46constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
46 47
47enum class LaunchParameterKind : u32 { 48enum class LaunchParameterKind : u32 {
48 ApplicationSpecific = 1, 49 UserChannel = 1,
49 AccountPreselectedUser = 2, 50 AccountPreselectedUser = 2,
50}; 51};
51 52
@@ -340,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) {
340void ISelfController::LockExit(HLERequestContext& ctx) { 341void ISelfController::LockExit(HLERequestContext& ctx) {
341 LOG_DEBUG(Service_AM, "called"); 342 LOG_DEBUG(Service_AM, "called");
342 343
343 system.SetExitLock(true); 344 system.SetExitLocked(true);
344 345
345 IPC::ResponseBuilder rb{ctx, 2}; 346 IPC::ResponseBuilder rb{ctx, 2};
346 rb.Push(ResultSuccess); 347 rb.Push(ResultSuccess);
@@ -349,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) {
349void ISelfController::UnlockExit(HLERequestContext& ctx) { 350void ISelfController::UnlockExit(HLERequestContext& ctx) {
350 LOG_DEBUG(Service_AM, "called"); 351 LOG_DEBUG(Service_AM, "called");
351 352
352 system.SetExitLock(false); 353 system.SetExitLocked(false);
353 354
354 IPC::ResponseBuilder rb{ctx, 2}; 355 IPC::ResponseBuilder rb{ctx, 2};
355 rb.Push(ResultSuccess); 356 rb.Push(ResultSuccess);
357
358 if (system.GetExitRequested()) {
359 system.Exit();
360 }
356} 361}
357 362
358void ISelfController::EnterFatalSection(HLERequestContext& ctx) { 363void ISelfController::EnterFatalSection(HLERequestContext& ctx) {
@@ -833,7 +838,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(HLERequestContext& ctx) {
833 IPC::ResponseBuilder rb{ctx, 4}; 838 IPC::ResponseBuilder rb{ctx, 4};
834 rb.Push(ResultSuccess); 839 rb.Push(ResultSuccess);
835 840
836 if (Settings::values.use_docked_mode.GetValue()) { 841 if (Settings::IsDockedMode()) {
837 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); 842 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth));
838 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); 843 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight));
839 } else { 844 } else {
@@ -921,7 +926,7 @@ void IStorage::Open(HLERequestContext& ctx) {
921} 926}
922 927
923void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) { 928void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) {
924 const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()}; 929 const bool use_docked_mode{Settings::IsDockedMode()};
925 LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); 930 LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
926 931
927 IPC::ResponseBuilder rb{ctx, 3}; 932 IPC::ResponseBuilder rb{ctx, 3};
@@ -1381,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1381 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, 1386 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
1382 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, 1387 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
1383 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, 1388 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
1384 {28, nullptr, "GetSaveDataSizeMax"}, 1389 {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
1385 {29, nullptr, "GetCacheStorageMax"}, 1390 {29, nullptr, "GetCacheStorageMax"},
1386 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, 1391 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
1387 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, 1392 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
@@ -1513,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
1513 IPC::RequestParser rp{ctx}; 1518 IPC::RequestParser rp{ctx};
1514 const auto kind = rp.PopEnum<LaunchParameterKind>(); 1519 const auto kind = rp.PopEnum<LaunchParameterKind>();
1515 1520
1516 LOG_DEBUG(Service_AM, "called, kind={:08X}", kind); 1521 LOG_INFO(Service_AM, "called, kind={:08X}", kind);
1517 1522
1518 if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { 1523 if (kind == LaunchParameterKind::UserChannel) {
1519 const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) { 1524 auto channel = system.GetUserChannel();
1520 return system.GetFileSystemController().GetBCATDirectory(tid); 1525 if (channel.empty()) {
1521 }); 1526 LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
1522 const auto build_id_full = system.GetApplicationProcessBuildID(); 1527 IPC::ResponseBuilder rb{ctx, 2};
1523 u64 build_id{}; 1528 rb.Push(AM::ResultNoDataInChannel);
1524 std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
1525
1526 auto data =
1527 backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id});
1528 if (data.has_value()) {
1529 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1530 rb.Push(ResultSuccess);
1531 rb.PushIpcInterface<IStorage>(system, std::move(*data));
1532 launch_popped_application_specific = true;
1533 return; 1529 return;
1534 } 1530 }
1531
1532 auto data = channel.back();
1533 channel.pop_back();
1534
1535 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1536 rb.Push(ResultSuccess);
1537 rb.PushIpcInterface<IStorage>(system, std::move(data));
1535 } else if (kind == LaunchParameterKind::AccountPreselectedUser && 1538 } else if (kind == LaunchParameterKind::AccountPreselectedUser &&
1536 !launch_popped_account_preselect) { 1539 !launch_popped_account_preselect) {
1540 // TODO: Verify this is hw-accurate
1537 LaunchParameterAccountPreselectedUser params{}; 1541 LaunchParameterAccountPreselectedUser params{};
1538 1542
1539 params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; 1543 params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
@@ -1545,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
1545 params.current_user = *uuid; 1549 params.current_user = *uuid;
1546 1550
1547 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 1551 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1548
1549 rb.Push(ResultSuccess); 1552 rb.Push(ResultSuccess);
1550 1553
1551 std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); 1554 std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
@@ -1553,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
1553 1556
1554 rb.PushIpcInterface<IStorage>(system, std::move(buffer)); 1557 rb.PushIpcInterface<IStorage>(system, std::move(buffer));
1555 launch_popped_account_preselect = true; 1558 launch_popped_account_preselect = true;
1556 return; 1559 } else {
1560 LOG_ERROR(Service_AM, "Unknown launch parameter kind.");
1561 IPC::ResponseBuilder rb{ctx, 2};
1562 rb.Push(AM::ResultNoDataInChannel);
1557 } 1563 }
1558
1559 LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
1560 IPC::ResponseBuilder rb{ctx, 2};
1561 rb.Push(AM::ResultNoDataInChannel);
1562} 1564}
1563 1565
1564void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { 1566void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) {
@@ -1819,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
1819 rb.PushRaw(resp); 1821 rb.PushRaw(resp);
1820} 1822}
1821 1823
1824void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
1825 LOG_WARNING(Service_AM, "(STUBBED) called");
1826
1827 constexpr u64 size_max_normal = 0xFFFFFFF;
1828 constexpr u64 size_max_journal = 0xFFFFFFF;
1829
1830 IPC::ResponseBuilder rb{ctx, 6};
1831 rb.Push(ResultSuccess);
1832 rb.Push(size_max_normal);
1833 rb.Push(size_max_journal);
1834}
1835
1822void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { 1836void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
1823 LOG_WARNING(Service_AM, "(STUBBED) called"); 1837 LOG_WARNING(Service_AM, "(STUBBED) called");
1824 1838
@@ -1850,14 +1864,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) {
1850} 1864}
1851 1865
1852void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { 1866void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) {
1853 LOG_WARNING(Service_AM, "(STUBBED) called"); 1867 LOG_DEBUG(Service_AM, "called");
1868
1869 system.GetUserChannel().clear();
1854 1870
1855 IPC::ResponseBuilder rb{ctx, 2}; 1871 IPC::ResponseBuilder rb{ctx, 2};
1856 rb.Push(ResultSuccess); 1872 rb.Push(ResultSuccess);
1857} 1873}
1858 1874
1859void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { 1875void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) {
1860 LOG_WARNING(Service_AM, "(STUBBED) called"); 1876 LOG_DEBUG(Service_AM, "called");
1877
1878 IPC::RequestParser rp{ctx};
1879 const auto storage = rp.PopIpcInterface<IStorage>().lock();
1880 if (storage) {
1881 system.GetUserChannel().push_back(storage->GetData());
1882 }
1861 1883
1862 IPC::ResponseBuilder rb{ctx, 2}; 1884 IPC::ResponseBuilder rb{ctx, 2};
1863 rb.Push(ResultSuccess); 1885 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index d68998f04..f86841c60 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -316,6 +316,7 @@ private:
316 void ExtendSaveData(HLERequestContext& ctx); 316 void ExtendSaveData(HLERequestContext& ctx);
317 void GetSaveDataSize(HLERequestContext& ctx); 317 void GetSaveDataSize(HLERequestContext& ctx);
318 void CreateCacheStorage(HLERequestContext& ctx); 318 void CreateCacheStorage(HLERequestContext& ctx);
319 void GetSaveDataSizeMax(HLERequestContext& ctx);
319 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 320 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
320 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 321 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
321 void BeginBlockingHomeButton(HLERequestContext& ctx); 322 void BeginBlockingHomeButton(HLERequestContext& ctx);
@@ -339,7 +340,6 @@ private:
339 340
340 KernelHelpers::ServiceContext service_context; 341 KernelHelpers::ServiceContext service_context;
341 342
342 bool launch_popped_application_specific = false;
343 bool launch_popped_account_preselect = false; 343 bool launch_popped_account_preselect = false;
344 s32 previous_program_index{-1}; 344 s32 previous_program_index{-1};
345 Kernel::KEvent* gpu_error_detected_event; 345 Kernel::KEvent* gpu_error_detected_event;
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp
index d1f652c09..350a90818 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.cpp
+++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp
@@ -85,15 +85,18 @@ void MiiEdit::Execute() {
85 break; 85 break;
86 case MiiEditAppletMode::CreateMii: 86 case MiiEditAppletMode::CreateMii:
87 case MiiEditAppletMode::EditMii: { 87 case MiiEditAppletMode::EditMii: {
88 Service::Mii::MiiManager mii_manager; 88 Mii::CharInfo char_info{};
89 Mii::StoreData store_data{};
90 store_data.BuildBase(Mii::Gender::Male);
91 char_info.SetFromStoreData(store_data);
89 92
90 const MiiEditCharInfo char_info{ 93 const MiiEditCharInfo edit_char_info{
91 .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii 94 .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
92 ? applet_input_v4.char_info.mii_info 95 ? applet_input_v4.char_info.mii_info
93 : mii_manager.BuildDefault(0)}, 96 : char_info},
94 }; 97 };
95 98
96 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info); 99 MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
97 break; 100 break;
98 } 101 }
99 default: 102 default:
diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h
index 4705d019f..f3d764073 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit_types.h
+++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h
@@ -7,7 +7,8 @@
7 7
8#include "common/common_funcs.h" 8#include "common/common_funcs.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "core/hle/service/mii/types.h" 10#include "common/uuid.h"
11#include "core/hle/service/mii/types/char_info.h"
11 12
12namespace Service::AM::Applets { 13namespace Service::AM::Applets {
13 14
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp
index 2accf7898..1c9a1dc29 100644
--- a/src/core/hle/service/am/applets/applet_web_browser.cpp
+++ b/src/core/hle/service/am/applets/applet_web_browser.cpp
@@ -139,7 +139,7 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
139 const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), 139 const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
140 system.GetContentProvider()}; 140 system.GetContentProvider()};
141 141
142 return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); 142 return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type);
143 } 143 }
144} 144}
145 145
diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp
index 227fdd0cf..4f1aa5cc2 100644
--- a/src/core/hle/service/apm/apm_controller.cpp
+++ b/src/core/hle/service/apm/apm_controller.cpp
@@ -7,6 +7,7 @@
7 7
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "common/settings.h" 9#include "common/settings.h"
10#include "common/settings_enums.h"
10#include "core/core_timing.h" 11#include "core/core_timing.h"
11#include "core/hle/service/apm/apm_controller.h" 12#include "core/hle/service/apm/apm_controller.h"
12 13
@@ -67,8 +68,7 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
67} 68}
68 69
69PerformanceMode Controller::GetCurrentPerformanceMode() const { 70PerformanceMode Controller::GetCurrentPerformanceMode() const {
70 return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost 71 return Settings::IsDockedMode() ? PerformanceMode::Boost : PerformanceMode::Normal;
71 : PerformanceMode::Normal;
72} 72}
73 73
74PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { 74PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 526a39130..56fee4591 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -220,7 +220,7 @@ AudInU::AudInU(Core::System& system_)
220AudInU::~AudInU() = default; 220AudInU::~AudInU() = default;
221 221
222void AudInU::ListAudioIns(HLERequestContext& ctx) { 222void AudInU::ListAudioIns(HLERequestContext& ctx) {
223 using namespace AudioCore::AudioRenderer; 223 using namespace AudioCore::Renderer;
224 224
225 LOG_DEBUG(Service_Audio, "called"); 225 LOG_DEBUG(Service_Audio, "called");
226 226
@@ -240,7 +240,7 @@ void AudInU::ListAudioIns(HLERequestContext& ctx) {
240} 240}
241 241
242void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) { 242void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) {
243 using namespace AudioCore::AudioRenderer; 243 using namespace AudioCore::Renderer;
244 244
245 LOG_DEBUG(Service_Audio, "called"); 245 LOG_DEBUG(Service_Audio, "called");
246 246
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 23f84a29f..ca683d72c 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -228,7 +228,7 @@ AudOutU::AudOutU(Core::System& system_)
228AudOutU::~AudOutU() = default; 228AudOutU::~AudOutU() = default;
229 229
230void AudOutU::ListAudioOuts(HLERequestContext& ctx) { 230void AudOutU::ListAudioOuts(HLERequestContext& ctx) {
231 using namespace AudioCore::AudioRenderer; 231 using namespace AudioCore::Renderer;
232 232
233 std::scoped_lock l{impl->mutex}; 233 std::scoped_lock l{impl->mutex};
234 234
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index b723b65c8..2f09cade5 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -26,7 +26,7 @@
26#include "core/hle/service/ipc_helpers.h" 26#include "core/hle/service/ipc_helpers.h"
27#include "core/memory.h" 27#include "core/memory.h"
28 28
29using namespace AudioCore::AudioRenderer; 29using namespace AudioCore::Renderer;
30 30
31namespace Service::Audio { 31namespace Service::Audio {
32 32
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index d8e9c8719..3d7993a16 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -28,7 +28,7 @@ private:
28 void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx); 28 void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx);
29 29
30 KernelHelpers::ServiceContext service_context; 30 KernelHelpers::ServiceContext service_context;
31 std::unique_ptr<AudioCore::AudioRenderer::Manager> impl; 31 std::unique_ptr<AudioCore::Renderer::Manager> impl;
32 u32 num_audio_devices{0}; 32 u32 num_audio_devices{0};
33}; 33};
34 34
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h
index 3d3d3d97a..c41345f7e 100644
--- a/src/core/hle/service/audio/errors.h
+++ b/src/core/hle/service/audio/errors.h
@@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513};
20constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; 20constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536};
21constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; 21constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537};
22 22
23constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7};
24constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8};
25constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6};
26constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5};
27constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17};
28constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4};
29constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
30constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
31constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
32constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
33constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};
34
23} // namespace Service::Audio 35} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index fa77007f3..6a7bf9416 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -1,371 +1,506 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <chrono>
5#include <cstring>
6#include <memory> 4#include <memory>
7#include <vector> 5#include <vector>
8 6
9#include <opus.h> 7#include "audio_core/opus/decoder.h"
10#include <opus_multistream.h> 8#include "audio_core/opus/parameters.h"
11
12#include "common/assert.h" 9#include "common/assert.h"
13#include "common/logging/log.h" 10#include "common/logging/log.h"
14#include "common/scratch_buffer.h" 11#include "common/scratch_buffer.h"
12#include "core/core.h"
15#include "core/hle/service/audio/hwopus.h" 13#include "core/hle/service/audio/hwopus.h"
16#include "core/hle/service/ipc_helpers.h" 14#include "core/hle/service/ipc_helpers.h"
17 15
18namespace Service::Audio { 16namespace Service::Audio {
19namespace { 17using namespace AudioCore::OpusDecoder;
20struct OpusDeleter { 18
21 void operator()(OpusMSDecoder* ptr) const { 19class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> {
22 opus_multistream_decoder_destroy(ptr); 20public:
21 explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus)
22 : ServiceFramework{system_, "IHardwareOpusDecoder"},
23 impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} {
24 // clang-format off
25 static const FunctionInfo functions[] = {
26 {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"},
27 {1, &IHardwareOpusDecoder::SetContext, "SetContext"},
28 {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"},
29 {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"},
30 {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
31 {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"},
32 {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"},
33 {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
34 {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"},
35 {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"},
36 };
37 // clang-format on
38
39 RegisterHandlers(functions);
23 } 40 }
24};
25 41
26using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; 42 Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
43 u64 transfer_memory_size) {
44 return impl->Initialize(params, transfer_memory, transfer_memory_size);
45 }
27 46
28struct OpusPacketHeader { 47 Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
29 // Packet size in bytes. 48 u64 transfer_memory_size) {
30 u32_be size; 49 return impl->Initialize(params, transfer_memory, transfer_memory_size);
31 // Indicates the final range of the codec's entropy coder. 50 }
32 u32_be final_range;
33};
34static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
35 51
36class OpusDecoderState { 52private:
37public: 53 void DecodeInterleavedOld(HLERequestContext& ctx) {
38 /// Describes extra behavior that may be asked of the decoding context. 54 IPC::RequestParser rp{ctx};
39 enum class ExtraBehavior {
40 /// No extra behavior.
41 None,
42 55
43 /// Resets the decoder context back to a freshly initialized state. 56 auto input_data{ctx.ReadBuffer(0)};
44 ResetContext, 57 output_data.resize_destructive(ctx.GetWriteBufferSize());
45 };
46 58
47 enum class PerfTime { 59 u32 size{};
48 Disabled, 60 u32 sample_count{};
49 Enabled, 61 auto result =
50 }; 62 impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false);
63
64 LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
65
66 ctx.WriteBuffer(output_data);
51 67
52 explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) 68 IPC::ResponseBuilder rb{ctx, 4};
53 : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} 69 rb.Push(result);
54 70 rb.Push(size);
55 // Decodes interleaved Opus packets. Optionally allows reporting time taken to 71 rb.Push(sample_count);
56 // perform the decoding, as well as any relevant extra behavior.
57 void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time,
58 ExtraBehavior extra_behavior) {
59 if (perf_time == PerfTime::Disabled) {
60 DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
61 } else {
62 u64 performance = 0;
63 DecodeInterleavedHelper(ctx, &performance, extra_behavior);
64 }
65 } 72 }
66 73
67private: 74 void SetContext(HLERequestContext& ctx) {
68 void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, 75 IPC::RequestParser rp{ctx};
69 ExtraBehavior extra_behavior) { 76
70 u32 consumed = 0; 77 LOG_DEBUG(Service_Audio, "called");
71 u32 sample_count = 0; 78
72 samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>()); 79 auto input_data{ctx.ReadBuffer(0)};
73 80 auto result = impl->SetContext(input_data);
74 if (extra_behavior == ExtraBehavior::ResetContext) { 81
75 ResetDecoderContext(); 82 IPC::ResponseBuilder rb{ctx, 2};
76 } 83 rb.Push(result);
77
78 if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
79 LOG_ERROR(Audio, "Failed to decode opus data");
80 IPC::ResponseBuilder rb{ctx, 2};
81 // TODO(ogniK): Use correct error code
82 rb.Push(ResultUnknown);
83 return;
84 }
85
86 const u32 param_size = performance != nullptr ? 6 : 4;
87 IPC::ResponseBuilder rb{ctx, param_size};
88 rb.Push(ResultSuccess);
89 rb.Push<u32>(consumed);
90 rb.Push<u32>(sample_count);
91 if (performance) {
92 rb.Push<u64>(*performance);
93 }
94 ctx.WriteBuffer(samples);
95 } 84 }
96 85
97 bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, 86 void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) {
98 std::span<opus_int16> output, u64* out_performance_time) const { 87 IPC::RequestParser rp{ctx};
99 const auto start_time = std::chrono::steady_clock::now(); 88
100 const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); 89 auto input_data{ctx.ReadBuffer(0)};
101 if (sizeof(OpusPacketHeader) > input.size()) { 90 output_data.resize_destructive(ctx.GetWriteBufferSize());
102 LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", 91
103 sizeof(OpusPacketHeader), input.size()); 92 u32 size{};
104 return false; 93 u32 sample_count{};
105 } 94 auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count,
106 95 input_data, output_data, false);
107 OpusPacketHeader hdr{}; 96
108 std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); 97 LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
109 if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { 98
110 LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", 99 ctx.WriteBuffer(output_data);
111 sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); 100
112 return false; 101 IPC::ResponseBuilder rb{ctx, 4};
113 } 102 rb.Push(result);
114 103 rb.Push(size);
115 const auto frame = input.data() + sizeof(OpusPacketHeader); 104 rb.Push(sample_count);
116 const auto decoded_sample_count = opus_packet_get_nb_samples(
117 frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
118 static_cast<opus_int32>(sample_rate));
119 if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
120 LOG_ERROR(
121 Audio,
122 "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}",
123 decoded_sample_count * channel_count * sizeof(u16), raw_output_sz);
124 return false;
125 }
126
127 const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
128 const auto out_sample_count =
129 opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
130 if (out_sample_count < 0) {
131 LOG_ERROR(Audio,
132 "Incorrect sample count received from opus_decode, "
133 "output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
134 out_sample_count, frame_size, static_cast<u32>(hdr.size));
135 return false;
136 }
137
138 const auto end_time = std::chrono::steady_clock::now() - start_time;
139 sample_count = out_sample_count;
140 consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
141 if (out_performance_time != nullptr) {
142 *out_performance_time =
143 std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
144 }
145
146 return true;
147 } 105 }
148 106
149 void ResetDecoderContext() { 107 void SetContextForMultiStream(HLERequestContext& ctx) {
150 ASSERT(decoder != nullptr); 108 IPC::RequestParser rp{ctx};
109
110 LOG_DEBUG(Service_Audio, "called");
111
112 auto input_data{ctx.ReadBuffer(0)};
113 auto result = impl->SetContext(input_data);
151 114
152 opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); 115 IPC::ResponseBuilder rb{ctx, 2};
116 rb.Push(result);
153 } 117 }
154 118
155 OpusDecoderPtr decoder; 119 void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) {
156 u32 sample_rate; 120 IPC::RequestParser rp{ctx};
157 u32 channel_count;
158 Common::ScratchBuffer<opus_int16> samples;
159};
160 121
161class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { 122 auto input_data{ctx.ReadBuffer(0)};
162public: 123 output_data.resize_destructive(ctx.GetWriteBufferSize());
163 explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_)
164 : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{
165 std::move(decoder_state_)} {
166 // clang-format off
167 static const FunctionInfo functions[] = {
168 {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
169 {1, nullptr, "SetContext"},
170 {2, nullptr, "DecodeInterleavedForMultiStreamOld"},
171 {3, nullptr, "SetContextForMultiStream"},
172 {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
173 {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
174 {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
175 {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
176 {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
177 {9, nullptr, "DecodeInterleavedForMultiStream"},
178 };
179 // clang-format on
180 124
181 RegisterHandlers(functions); 125 u32 size{};
126 u32 sample_count{};
127 u64 time_taken{};
128 auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
129 output_data, false);
130
131 LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
132 sample_count, time_taken);
133
134 ctx.WriteBuffer(output_data);
135
136 IPC::ResponseBuilder rb{ctx, 6};
137 rb.Push(result);
138 rb.Push(size);
139 rb.Push(sample_count);
140 rb.Push(time_taken);
182 } 141 }
183 142
184private: 143 void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) {
185 void DecodeInterleavedOld(HLERequestContext& ctx) { 144 IPC::RequestParser rp{ctx};
186 LOG_DEBUG(Audio, "called"); 145
146 auto input_data{ctx.ReadBuffer(0)};
147 output_data.resize_destructive(ctx.GetWriteBufferSize());
148
149 u32 size{};
150 u32 sample_count{};
151 u64 time_taken{};
152 auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
153 input_data, output_data, false);
187 154
188 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, 155 LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
189 OpusDecoderState::ExtraBehavior::None); 156 sample_count, time_taken);
157
158 ctx.WriteBuffer(output_data);
159
160 IPC::ResponseBuilder rb{ctx, 6};
161 rb.Push(result);
162 rb.Push(size);
163 rb.Push(sample_count);
164 rb.Push(time_taken);
190 } 165 }
191 166
192 void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { 167 void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) {
193 LOG_DEBUG(Audio, "called"); 168 IPC::RequestParser rp{ctx};
169
170 auto reset{rp.Pop<bool>()};
171
172 auto input_data{ctx.ReadBuffer(0)};
173 output_data.resize_destructive(ctx.GetWriteBufferSize());
174
175 u32 size{};
176 u32 sample_count{};
177 u64 time_taken{};
178 auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
179 output_data, reset);
180
181 LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
182 reset, size, sample_count, time_taken);
183
184 ctx.WriteBuffer(output_data);
185
186 IPC::ResponseBuilder rb{ctx, 6};
187 rb.Push(result);
188 rb.Push(size);
189 rb.Push(sample_count);
190 rb.Push(time_taken);
191 }
192
193 void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) {
194 IPC::RequestParser rp{ctx};
195
196 auto reset{rp.Pop<bool>()};
197
198 auto input_data{ctx.ReadBuffer(0)};
199 output_data.resize_destructive(ctx.GetWriteBufferSize());
200
201 u32 size{};
202 u32 sample_count{};
203 u64 time_taken{};
204 auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
205 input_data, output_data, reset);
206
207 LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
208 reset, size, sample_count, time_taken);
194 209
195 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, 210 ctx.WriteBuffer(output_data);
196 OpusDecoderState::ExtraBehavior::None); 211
212 IPC::ResponseBuilder rb{ctx, 6};
213 rb.Push(result);
214 rb.Push(size);
215 rb.Push(sample_count);
216 rb.Push(time_taken);
197 } 217 }
198 218
199 void DecodeInterleaved(HLERequestContext& ctx) { 219 void DecodeInterleaved(HLERequestContext& ctx) {
200 LOG_DEBUG(Audio, "called"); 220 IPC::RequestParser rp{ctx};
221
222 auto reset{rp.Pop<bool>()};
223
224 auto input_data{ctx.ReadBuffer(0)};
225 output_data.resize_destructive(ctx.GetWriteBufferSize());
226
227 u32 size{};
228 u32 sample_count{};
229 u64 time_taken{};
230 auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
231 output_data, reset);
232
233 LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
234 reset, size, sample_count, time_taken);
235
236 ctx.WriteBuffer(output_data);
237
238 IPC::ResponseBuilder rb{ctx, 6};
239 rb.Push(result);
240 rb.Push(size);
241 rb.Push(sample_count);
242 rb.Push(time_taken);
243 }
201 244
245 void DecodeInterleavedForMultiStream(HLERequestContext& ctx) {
202 IPC::RequestParser rp{ctx}; 246 IPC::RequestParser rp{ctx};
203 const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
204 : OpusDecoderState::ExtraBehavior::None;
205 247
206 decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); 248 auto reset{rp.Pop<bool>()};
249
250 auto input_data{ctx.ReadBuffer(0)};
251 output_data.resize_destructive(ctx.GetWriteBufferSize());
252
253 u32 size{};
254 u32 sample_count{};
255 u64 time_taken{};
256 auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
257 input_data, output_data, reset);
258
259 LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
260 reset, size, sample_count, time_taken);
261
262 ctx.WriteBuffer(output_data);
263
264 IPC::ResponseBuilder rb{ctx, 6};
265 rb.Push(result);
266 rb.Push(size);
267 rb.Push(sample_count);
268 rb.Push(time_taken);
207 } 269 }
208 270
209 OpusDecoderState decoder_state; 271 std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl;
272 Common::ScratchBuffer<u8> output_data;
210}; 273};
211 274
212std::size_t WorkerBufferSize(u32 channel_count) { 275void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) {
213 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); 276 IPC::RequestParser rp{ctx};
214 constexpr int num_streams = 1;
215 const int num_stereo_streams = channel_count == 2 ? 1 : 0;
216 return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
217}
218 277
219// Creates the mapping table that maps the input channels to the particular 278 auto params = rp.PopRaw<OpusParameters>();
220// output channels. In the stereo case, we map the left and right input channels 279 auto transfer_memory_size{rp.Pop<u32>()};
221// to the left and right output channels respectively. 280 auto transfer_memory_handle{ctx.GetCopyHandle(0)};
222// 281 auto transfer_memory{
223// However, in the monophonic case, we only map the one available channel 282 system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
224// to the sole output channel. We specify 255 for the would-be right channel 283 transfer_memory_handle)};
225// as this is a special value defined by Opus to indicate to the decoder to
226// ignore that channel.
227std::array<u8, 2> CreateMappingTable(u32 channel_count) {
228 if (channel_count == 2) {
229 return {{0, 1}};
230 }
231 284
232 return {{0, 255}}; 285 LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
286 params.sample_rate, params.channel_count, transfer_memory_size);
287
288 auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
289
290 OpusParametersEx ex{
291 .sample_rate = params.sample_rate,
292 .channel_count = params.channel_count,
293 .use_large_frame_size = false,
294 };
295 auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
296
297 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
298 rb.Push(result);
299 rb.PushIpcInterface(decoder);
233} 300}
234} // Anonymous namespace
235 301
236void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { 302void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) {
237 IPC::RequestParser rp{ctx}; 303 IPC::RequestParser rp{ctx};
238 const auto sample_rate = rp.Pop<u32>(); 304 auto params = rp.PopRaw<OpusParameters>();
239 const auto channel_count = rp.Pop<u32>();
240 305
241 LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); 306 u64 size{};
307 auto result = impl.GetWorkBufferSize(params, size);
242 308
243 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || 309 LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}",
244 sample_rate == 12000 || sample_rate == 8000, 310 params.sample_rate, params.channel_count, size);
245 "Invalid sample rate"); 311
246 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); 312 IPC::ResponseBuilder rb{ctx, 4};
313 rb.Push(result);
314 rb.Push(size);
315}
316
317void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) {
318 IPC::RequestParser rp{ctx};
247 319
248 const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); 320 auto input{ctx.ReadBuffer()};
249 LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); 321 OpusMultiStreamParameters params;
322 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParameters));
323
324 auto transfer_memory_size{rp.Pop<u32>()};
325 auto transfer_memory_handle{ctx.GetCopyHandle(0)};
326 auto transfer_memory{
327 system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
328 transfer_memory_handle)};
329
330 LOG_DEBUG(Service_Audio,
331 "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
332 "transfer_memory_size 0x{:X}",
333 params.sample_rate, params.channel_count, params.total_stream_count,
334 params.stereo_stream_count, transfer_memory_size);
335
336 auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
337
338 OpusMultiStreamParametersEx ex{
339 .sample_rate = params.sample_rate,
340 .channel_count = params.channel_count,
341 .total_stream_count = params.total_stream_count,
342 .stereo_stream_count = params.stereo_stream_count,
343 .use_large_frame_size = false,
344 .mappings{},
345 };
346 std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings));
347 auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
250 348
251 IPC::ResponseBuilder rb{ctx, 3}; 349 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
252 rb.Push(ResultSuccess); 350 rb.Push(result);
253 rb.Push<u32>(worker_buffer_sz); 351 rb.PushIpcInterface(decoder);
254} 352}
255 353
256void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { 354void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) {
257 GetWorkBufferSize(ctx); 355 IPC::RequestParser rp{ctx};
356
357 auto input{ctx.ReadBuffer()};
358 OpusMultiStreamParameters params;
359 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParameters));
360
361 u64 size{};
362 auto result = impl.GetWorkBufferSizeForMultiStream(params, size);
363
364 LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
365
366 IPC::ResponseBuilder rb{ctx, 4};
367 rb.Push(result);
368 rb.Push(size);
258} 369}
259 370
260void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { 371void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
261 OpusMultiStreamParametersEx param; 372 IPC::RequestParser rp{ctx};
262 std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
263 373
264 const auto sample_rate = param.sample_rate; 374 auto params = rp.PopRaw<OpusParametersEx>();
265 const auto channel_count = param.channel_count; 375 auto transfer_memory_size{rp.Pop<u32>()};
266 const auto number_streams = param.number_streams; 376 auto transfer_memory_handle{ctx.GetCopyHandle(0)};
267 const auto number_stereo_streams = param.number_stereo_streams; 377 auto transfer_memory{
378 system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
379 transfer_memory_handle)};
268 380
269 LOG_DEBUG( 381 LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
270 Audio, 382 params.sample_rate, params.channel_count, transfer_memory_size);
271 "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
272 sample_rate, channel_count, number_streams, number_stereo_streams);
273 383
274 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || 384 auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
275 sample_rate == 12000 || sample_rate == 8000,
276 "Invalid sample rate");
277 385
278 const u32 worker_buffer_sz = 386 auto result =
279 static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); 387 decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
280 388
281 IPC::ResponseBuilder rb{ctx, 3}; 389 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
282 rb.Push(ResultSuccess); 390 rb.Push(result);
283 rb.Push<u32>(worker_buffer_sz); 391 rb.PushIpcInterface(decoder);
284} 392}
285 393
286void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { 394void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
287 IPC::RequestParser rp{ctx}; 395 IPC::RequestParser rp{ctx};
288 const auto sample_rate = rp.Pop<u32>(); 396 auto params = rp.PopRaw<OpusParametersEx>();
289 const auto channel_count = rp.Pop<u32>();
290 const auto buffer_sz = rp.Pop<u32>();
291
292 LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
293 channel_count, buffer_sz);
294
295 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
296 sample_rate == 12000 || sample_rate == 8000,
297 "Invalid sample rate");
298 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
299
300 const std::size_t worker_sz = WorkerBufferSize(channel_count);
301 ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
302
303 const int num_stereo_streams = channel_count == 2 ? 1 : 0;
304 const auto mapping_table = CreateMappingTable(channel_count);
305
306 int error = 0;
307 OpusDecoderPtr decoder{
308 opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
309 num_stereo_streams, mapping_table.data(), &error)};
310 if (error != OPUS_OK || decoder == nullptr) {
311 LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
312 IPC::ResponseBuilder rb{ctx, 2};
313 // TODO(ogniK): Use correct error code
314 rb.Push(ResultUnknown);
315 return;
316 }
317 397
318 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 398 u64 size{};
319 rb.Push(ResultSuccess); 399 auto result = impl.GetWorkBufferSizeEx(params, size);
320 rb.PushIpcInterface<IHardwareOpusDecoderManager>( 400
321 system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); 401 LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
402
403 IPC::ResponseBuilder rb{ctx, 4};
404 rb.Push(result);
405 rb.Push(size);
322} 406}
323 407
324void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { 408void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) {
325 IPC::RequestParser rp{ctx}; 409 IPC::RequestParser rp{ctx};
326 const auto sample_rate = rp.Pop<u32>();
327 const auto channel_count = rp.Pop<u32>();
328 410
329 LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); 411 auto input{ctx.ReadBuffer()};
412 OpusMultiStreamParametersEx params;
413 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
330 414
331 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || 415 auto transfer_memory_size{rp.Pop<u32>()};
332 sample_rate == 12000 || sample_rate == 8000, 416 auto transfer_memory_handle{ctx.GetCopyHandle(0)};
333 "Invalid sample rate"); 417 auto transfer_memory{
334 ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); 418 system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
419 transfer_memory_handle)};
335 420
336 const int num_stereo_streams = channel_count == 2 ? 1 : 0; 421 LOG_DEBUG(Service_Audio,
337 const auto mapping_table = CreateMappingTable(channel_count); 422 "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
423 "use_large_frame_size {}"
424 "transfer_memory_size 0x{:X}",
425 params.sample_rate, params.channel_count, params.total_stream_count,
426 params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size);
338 427
339 int error = 0; 428 auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
340 OpusDecoderPtr decoder{ 429
341 opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, 430 auto result =
342 num_stereo_streams, mapping_table.data(), &error)}; 431 decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
343 if (error != OPUS_OK || decoder == nullptr) {
344 LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
345 IPC::ResponseBuilder rb{ctx, 2};
346 // TODO(ogniK): Use correct error code
347 rb.Push(ResultUnknown);
348 return;
349 }
350 432
351 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 433 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
352 rb.Push(ResultSuccess); 434 rb.Push(result);
353 rb.PushIpcInterface<IHardwareOpusDecoderManager>( 435 rb.PushIpcInterface(decoder);
354 system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); 436}
437
438void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
439 IPC::RequestParser rp{ctx};
440
441 auto input{ctx.ReadBuffer()};
442 OpusMultiStreamParametersEx params;
443 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
444
445 u64 size{};
446 auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size);
447
448 LOG_DEBUG(Service_Audio,
449 "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
450 "use_large_frame_size {} -- returned size 0x{:X}",
451 params.sample_rate, params.channel_count, params.total_stream_count,
452 params.stereo_stream_count, params.use_large_frame_size, size);
453
454 IPC::ResponseBuilder rb{ctx, 4};
455 rb.Push(result);
456 rb.Push(size);
457}
458
459void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) {
460 IPC::RequestParser rp{ctx};
461 auto params = rp.PopRaw<OpusParametersEx>();
462
463 u64 size{};
464 auto result = impl.GetWorkBufferSizeExEx(params, size);
465
466 LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
467
468 IPC::ResponseBuilder rb{ctx, 4};
469 rb.Push(result);
470 rb.Push(size);
471}
472
473void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) {
474 IPC::RequestParser rp{ctx};
475
476 auto input{ctx.ReadBuffer()};
477 OpusMultiStreamParametersEx params;
478 std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
479
480 u64 size{};
481 auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size);
482
483 LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
484
485 IPC::ResponseBuilder rb{ctx, 4};
486 rb.Push(result);
487 rb.Push(size);
355} 488}
356 489
357HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { 490HwOpus::HwOpus(Core::System& system_)
491 : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} {
358 static const FunctionInfo functions[] = { 492 static const FunctionInfo functions[] = {
359 {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, 493 {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"},
360 {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, 494 {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
361 {2, nullptr, "OpenOpusDecoderForMultiStream"}, 495 {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"},
362 {3, nullptr, "GetWorkBufferSizeForMultiStream"}, 496 {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"},
363 {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, 497 {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
364 {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, 498 {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
365 {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"}, 499 {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx,
500 "OpenHardwareOpusDecoderForMultiStreamEx"},
366 {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, 501 {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
367 {8, nullptr, "GetWorkBufferSizeExEx"}, 502 {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"},
368 {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, 503 {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"},
369 }; 504 };
370 RegisterHandlers(functions); 505 RegisterHandlers(functions);
371} 506}
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h
index ece65c02c..d3960065e 100644
--- a/src/core/hle/service/audio/hwopus.h
+++ b/src/core/hle/service/audio/hwopus.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "audio_core/opus/decoder_manager.h"
6#include "core/hle/service/service.h" 7#include "core/hle/service/service.h"
7 8
8namespace Core { 9namespace Core {
@@ -11,16 +12,6 @@ class System;
11 12
12namespace Service::Audio { 13namespace Service::Audio {
13 14
14struct OpusMultiStreamParametersEx {
15 u32 sample_rate;
16 u32 channel_count;
17 u32 number_streams;
18 u32 number_stereo_streams;
19 u32 use_large_frame_size;
20 u32 padding;
21 std::array<u32, 64> channel_mappings;
22};
23
24class HwOpus final : public ServiceFramework<HwOpus> { 15class HwOpus final : public ServiceFramework<HwOpus> {
25public: 16public:
26 explicit HwOpus(Core::System& system_); 17 explicit HwOpus(Core::System& system_);
@@ -28,10 +19,18 @@ public:
28 19
29private: 20private:
30 void OpenHardwareOpusDecoder(HLERequestContext& ctx); 21 void OpenHardwareOpusDecoder(HLERequestContext& ctx);
31 void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
32 void GetWorkBufferSize(HLERequestContext& ctx); 22 void GetWorkBufferSize(HLERequestContext& ctx);
23 void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx);
24 void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx);
25 void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
33 void GetWorkBufferSizeEx(HLERequestContext& ctx); 26 void GetWorkBufferSizeEx(HLERequestContext& ctx);
27 void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx);
34 void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); 28 void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx);
29 void GetWorkBufferSizeExEx(HLERequestContext& ctx);
30 void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx);
31
32 Core::System& system;
33 AudioCore::OpusDecoder::OpusDecoderManager impl;
35}; 34};
36 35
37} // namespace Service::Audio 36} // namespace Service::Audio
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index 446f46b3c..9eaae4c4b 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -122,20 +122,18 @@ private:
122 } 122 }
123 123
124 void ImportTicket(HLERequestContext& ctx) { 124 void ImportTicket(HLERequestContext& ctx) {
125 const auto ticket = ctx.ReadBuffer(); 125 const auto raw_ticket = ctx.ReadBuffer();
126 [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); 126 [[maybe_unused]] const auto cert = ctx.ReadBuffer(1);
127 127
128 if (ticket.size() < sizeof(Core::Crypto::Ticket)) { 128 if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) {
129 LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); 129 LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
130 IPC::ResponseBuilder rb{ctx, 2}; 130 IPC::ResponseBuilder rb{ctx, 2};
131 rb.Push(ERROR_INVALID_ARGUMENT); 131 rb.Push(ERROR_INVALID_ARGUMENT);
132 return; 132 return;
133 } 133 }
134 134
135 Core::Crypto::Ticket raw{}; 135 Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket);
136 std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); 136 if (!keys.AddTicket(ticket)) {
137
138 if (!keys.AddTicketPersonalized(raw)) {
139 LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); 137 LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
140 IPC::ResponseBuilder rb{ctx, 2}; 138 IPC::ResponseBuilder rb{ctx, 2};
141 rb.Push(ERROR_INVALID_ARGUMENT); 139 rb.Push(ERROR_INVALID_ARGUMENT);
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index ac465d5a9..508db7360 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -4,6 +4,7 @@
4#include <utility> 4#include <utility>
5 5
6#include "common/assert.h" 6#include "common/assert.h"
7#include "common/fs/fs.h"
7#include "common/fs/path_util.h" 8#include "common/fs/path_util.h"
8#include "common/settings.h" 9#include "common/settings.h"
9#include "core/core.h" 10#include "core/core.h"
@@ -154,10 +155,18 @@ Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
154 std::string src_path(Common::FS::SanitizePath(src_path_)); 155 std::string src_path(Common::FS::SanitizePath(src_path_));
155 std::string dest_path(Common::FS::SanitizePath(dest_path_)); 156 std::string dest_path(Common::FS::SanitizePath(dest_path_));
156 auto src = backing->GetFileRelative(src_path); 157 auto src = backing->GetFileRelative(src_path);
158 auto dst = backing->GetFileRelative(dest_path);
157 if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { 159 if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
158 // Use more-optimized vfs implementation rename. 160 // Use more-optimized vfs implementation rename.
159 if (src == nullptr) 161 if (src == nullptr) {
160 return FileSys::ERROR_PATH_NOT_FOUND; 162 return FileSys::ERROR_PATH_NOT_FOUND;
163 }
164
165 if (dst && Common::FS::Exists(dst->GetFullPath())) {
166 LOG_ERROR(Service_FS, "File at new_path={} already exists", dst->GetFullPath());
167 return FileSys::ERROR_PATH_ALREADY_EXISTS;
168 }
169
161 if (!src->Rename(Common::FS::GetFilename(dest_path))) { 170 if (!src->Rename(Common::FS::GetFilename(dest_path))) {
162 // TODO(DarkLordZach): Find a better error code for this 171 // TODO(DarkLordZach): Find a better error code for this
163 return ResultUnknown; 172 return ResultUnknown;
@@ -373,6 +382,11 @@ FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::Stor
373 return romfs_factory->Open(title_id, storage_id, type); 382 return romfs_factory->Open(title_id, storage_id, type);
374} 383}
375 384
385std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca(
386 u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
387 return romfs_factory->GetEntry(title_id, storage_id, type);
388}
389
376Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, 390Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data,
377 FileSys::SaveDataSpaceId space, 391 FileSys::SaveDataSpaceId space,
378 const FileSys::SaveDataAttribute& save_struct) const { 392 const FileSys::SaveDataAttribute& save_struct) const {
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index fd991f976..e7e7c4c28 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -15,6 +15,7 @@ class System;
15 15
16namespace FileSys { 16namespace FileSys {
17class BISFactory; 17class BISFactory;
18class NCA;
18class RegisteredCache; 19class RegisteredCache;
19class RegisteredCacheUnion; 20class RegisteredCacheUnion;
20class PlaceholderCache; 21class PlaceholderCache;
@@ -70,6 +71,8 @@ public:
70 FileSys::ContentRecordType type) const; 71 FileSys::ContentRecordType type) const;
71 FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, 72 FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
72 FileSys::ContentRecordType type) const; 73 FileSys::ContentRecordType type) const;
74 std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id,
75 FileSys::ContentRecordType type) const;
73 76
74 Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, 77 Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
75 const FileSys::SaveDataAttribute& save_struct) const; 78 const FileSys::SaveDataAttribute& save_struct) const;
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 423a814cb..6e4d26b1e 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -1029,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
1029 1029
1030 const FileSys::PatchManager pm{title_id, fsc, content_provider}; 1030 const FileSys::PatchManager pm{title_id, fsc, content_provider};
1031 1031
1032 auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data);
1032 auto storage = std::make_shared<IStorage>( 1033 auto storage = std::make_shared<IStorage>(
1033 system, pm.PatchRomFS(std::move(data), 0, FileSys::ContentRecordType::Data)); 1034 system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data));
1034 1035
1035 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 1036 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1036 rb.Push(ResultSuccess); 1037 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index 03432f7cb..63eecd42b 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -331,7 +331,7 @@ Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties()
331 }; 331 };
332 332
333 // Hack: There is no touch in docked but games still allow it 333 // Hack: There is no touch in docked but games still allow it
334 if (Settings::values.use_docked_mode.GetValue()) { 334 if (Settings::IsDockedMode()) {
335 gesture.points[id] = { 335 gesture.points[id] = {
336 .x = static_cast<s32>(active_x * Layout::ScreenDocked::Width), 336 .x = static_cast<s32>(active_x * Layout::ScreenDocked::Width),
337 .y = static_cast<s32>(active_y * Layout::ScreenDocked::Height), 337 .y = static_cast<s32>(active_y * Layout::ScreenDocked::Height),
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 28818c813..146bb486d 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
193 shared_memory->system_properties.use_minus.Assign(1); 193 shared_memory->system_properties.use_minus.Assign(1);
194 shared_memory->system_properties.is_charging_joy_dual.Assign( 194 shared_memory->system_properties.is_charging_joy_dual.Assign(
195 battery_level.dual.is_charging); 195 battery_level.dual.is_charging);
196 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; 196 shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController;
197 shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); 197 shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
198 break; 198 break;
199 case Core::HID::NpadStyleIndex::Handheld: 199 case Core::HID::NpadStyleIndex::Handheld:
@@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
216 shared_memory->system_properties.is_charging_joy_right.Assign( 216 shared_memory->system_properties.is_charging_joy_right.Assign(
217 battery_level.right.is_charging); 217 battery_level.right.is_charging);
218 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; 218 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
219 shared_memory->applet_nfc_xcd.applet_footer.type = 219 shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
220 AppletFooterUiType::HandheldJoyConLeftJoyConRight;
221 shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); 220 shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
222 break; 221 break;
223 case Core::HID::NpadStyleIndex::JoyconDual: 222 case Core::HID::NpadStyleIndex::JoyconDual:
@@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
247 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; 246 shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
248 247
249 if (controller.is_dual_left_connected && controller.is_dual_right_connected) { 248 if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
250 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; 249 shared_memory->applet_footer_type = AppletFooterUiType::JoyDual;
251 shared_memory->fullkey_color.fullkey = body_colors.left; 250 shared_memory->fullkey_color.fullkey = body_colors.left;
252 shared_memory->battery_level_dual = battery_level.left.battery_level; 251 shared_memory->battery_level_dual = battery_level.left.battery_level;
253 shared_memory->system_properties.is_charging_joy_dual.Assign( 252 shared_memory->system_properties.is_charging_joy_dual.Assign(
254 battery_level.left.is_charging); 253 battery_level.left.is_charging);
255 } else if (controller.is_dual_left_connected) { 254 } else if (controller.is_dual_left_connected) {
256 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; 255 shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly;
257 shared_memory->fullkey_color.fullkey = body_colors.left; 256 shared_memory->fullkey_color.fullkey = body_colors.left;
258 shared_memory->battery_level_dual = battery_level.left.battery_level; 257 shared_memory->battery_level_dual = battery_level.left.battery_level;
259 shared_memory->system_properties.is_charging_joy_dual.Assign( 258 shared_memory->system_properties.is_charging_joy_dual.Assign(
260 battery_level.left.is_charging); 259 battery_level.left.is_charging);
261 } else { 260 } else {
262 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; 261 shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly;
263 shared_memory->fullkey_color.fullkey = body_colors.right; 262 shared_memory->fullkey_color.fullkey = body_colors.right;
264 shared_memory->battery_level_dual = battery_level.right.battery_level; 263 shared_memory->battery_level_dual = battery_level.right.battery_level;
265 shared_memory->system_properties.is_charging_joy_dual.Assign( 264 shared_memory->system_properties.is_charging_joy_dual.Assign(
@@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
278 shared_memory->system_properties.use_minus.Assign(1); 277 shared_memory->system_properties.use_minus.Assign(1);
279 shared_memory->system_properties.is_charging_joy_left.Assign( 278 shared_memory->system_properties.is_charging_joy_left.Assign(
280 battery_level.left.is_charging); 279 battery_level.left.is_charging);
281 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; 280 shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal;
282 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); 281 shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
283 break; 282 break;
284 case Core::HID::NpadStyleIndex::JoyconRight: 283 case Core::HID::NpadStyleIndex::JoyconRight:
@@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
293 shared_memory->system_properties.use_plus.Assign(1); 292 shared_memory->system_properties.use_plus.Assign(1);
294 shared_memory->system_properties.is_charging_joy_right.Assign( 293 shared_memory->system_properties.is_charging_joy_right.Assign(
295 battery_level.right.is_charging); 294 battery_level.right.is_charging);
296 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; 295 shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal;
297 shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); 296 shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
298 break; 297 break;
299 case Core::HID::NpadStyleIndex::GameCube: 298 case Core::HID::NpadStyleIndex::GameCube:
@@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
314 case Core::HID::NpadStyleIndex::SNES: 313 case Core::HID::NpadStyleIndex::SNES:
315 shared_memory->style_tag.lucia.Assign(1); 314 shared_memory->style_tag.lucia.Assign(1);
316 shared_memory->device_type.fullkey.Assign(1); 315 shared_memory->device_type.fullkey.Assign(1);
317 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia; 316 shared_memory->applet_footer_type = AppletFooterUiType::Lucia;
318 break; 317 break;
319 case Core::HID::NpadStyleIndex::N64: 318 case Core::HID::NpadStyleIndex::N64:
320 shared_memory->style_tag.lagoon.Assign(1); 319 shared_memory->style_tag.lagoon.Assign(1);
321 shared_memory->device_type.fullkey.Assign(1); 320 shared_memory->device_type.fullkey.Assign(1);
322 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon; 321 shared_memory->applet_footer_type = AppletFooterUiType::Lagon;
323 break; 322 break;
324 case Core::HID::NpadStyleIndex::SegaGenesis: 323 case Core::HID::NpadStyleIndex::SegaGenesis:
325 shared_memory->style_tag.lager.Assign(1); 324 shared_memory->style_tag.lager.Assign(1);
@@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
419 std::scoped_lock lock{mutex}; 418 std::scoped_lock lock{mutex};
420 auto& controller = GetControllerFromNpadIdType(npad_id); 419 auto& controller = GetControllerFromNpadIdType(npad_id);
421 const auto controller_type = controller.device->GetNpadStyleIndex(); 420 const auto controller_type = controller.device->GetNpadStyleIndex();
421
422 if (!controller.device->IsConnected() && controller.is_connected) {
423 DisconnectNpad(npad_id);
424 return;
425 }
422 if (!controller.device->IsConnected()) { 426 if (!controller.device->IsConnected()) {
423 return; 427 return;
424 } 428 }
429 if (controller.device->IsConnected() && !controller.is_connected) {
430 InitNewlyAddedController(npad_id);
431 }
425 432
426 // This function is unique to yuzu for the turbo buttons and motion to work properly 433 // This function is unique to yuzu for the turbo buttons and motion to work properly
427 controller.device->StatusUpdate(); 434 controller.device->StatusUpdate();
@@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
468 pad_entry.npad_buttons.l.Assign(button_state.zl); 475 pad_entry.npad_buttons.l.Assign(button_state.zl);
469 pad_entry.npad_buttons.r.Assign(button_state.zr); 476 pad_entry.npad_buttons.r.Assign(button_state.zr);
470 } 477 }
478
479 if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) {
480 hid_core.SetLastActiveController(npad_id);
481 }
471} 482}
472 483
473void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { 484void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
@@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
736 747
737 // Once SetSupportedStyleSet is called controllers are fully initialized 748 // Once SetSupportedStyleSet is called controllers are fully initialized
738 is_controller_initialized = true; 749 is_controller_initialized = true;
739
740 // Connect all active controllers
741 for (auto& controller : controller_data) {
742 const auto& device = controller.device;
743 if (device->IsConnected()) {
744 AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
745 }
746 }
747} 750}
748 751
749Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { 752Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
@@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
1116 .left = {}, 1119 .left = {},
1117 .right = {}, 1120 .right = {},
1118 }; 1121 };
1119 shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None; 1122 shared_memory->applet_footer_type = AppletFooterUiType::None;
1120 1123
1121 controller.is_dual_left_connected = true; 1124 controller.is_dual_left_connected = true;
1122 controller.is_dual_right_connected = true; 1125 controller.is_dual_right_connected = true;
@@ -1508,6 +1511,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() {
1508 return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); 1511 return static_cast<Core::HID::NpadButton>(press_state.exchange(0));
1509} 1512}
1510 1513
1514void Controller_NPad::ApplyNpadSystemCommonPolicy() {
1515 Core::HID::NpadStyleTag styletag{};
1516 styletag.fullkey.Assign(1);
1517 styletag.handheld.Assign(1);
1518 styletag.joycon_dual.Assign(1);
1519 styletag.system_ext.Assign(1);
1520 styletag.system.Assign(1);
1521 SetSupportedStyleSet(styletag);
1522
1523 SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual);
1524
1525 supported_npad_id_types.clear();
1526 supported_npad_id_types.resize(10);
1527 supported_npad_id_types[0] = Core::HID::NpadIdType::Player1;
1528 supported_npad_id_types[1] = Core::HID::NpadIdType::Player2;
1529 supported_npad_id_types[2] = Core::HID::NpadIdType::Player3;
1530 supported_npad_id_types[3] = Core::HID::NpadIdType::Player4;
1531 supported_npad_id_types[4] = Core::HID::NpadIdType::Player5;
1532 supported_npad_id_types[5] = Core::HID::NpadIdType::Player6;
1533 supported_npad_id_types[6] = Core::HID::NpadIdType::Player7;
1534 supported_npad_id_types[7] = Core::HID::NpadIdType::Player8;
1535 supported_npad_id_types[8] = Core::HID::NpadIdType::Other;
1536 supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
1537}
1538
1511bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { 1539bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const {
1512 if (controller == Core::HID::NpadStyleIndex::Handheld) { 1540 if (controller == Core::HID::NpadStyleIndex::Handheld) {
1513 const bool support_handheld = 1541 const bool support_handheld =
@@ -1518,7 +1546,7 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller
1518 return false; 1546 return false;
1519 } 1547 }
1520 // Handheld shouldn't be supported in docked mode 1548 // Handheld shouldn't be supported in docked mode
1521 if (Settings::values.use_docked_mode.GetValue()) { 1549 if (Settings::IsDockedMode()) {
1522 return false; 1550 return false;
1523 } 1551 }
1524 1552
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 776411261..949e58a4c 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -190,6 +190,8 @@ public:
190 // Specifically for cheat engine and other features. 190 // Specifically for cheat engine and other features.
191 Core::HID::NpadButton GetAndResetPressState(); 191 Core::HID::NpadButton GetAndResetPressState();
192 192
193 void ApplyNpadSystemCommonPolicy();
194
193 static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); 195 static bool IsNpadIdValid(Core::HID::NpadIdType npad_id);
194 static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); 196 static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
195 static Result VerifyValidSixAxisSensorHandle( 197 static Result VerifyValidSixAxisSensorHandle(
@@ -360,7 +362,7 @@ private:
360 enum class AppletFooterUiType : u8 { 362 enum class AppletFooterUiType : u8 {
361 None = 0, 363 None = 0,
362 HandheldNone = 1, 364 HandheldNone = 1,
363 HandheldJoyConLeftOnly = 1, 365 HandheldJoyConLeftOnly = 2,
364 HandheldJoyConRightOnly = 3, 366 HandheldJoyConRightOnly = 3,
365 HandheldJoyConLeftJoyConRight = 4, 367 HandheldJoyConLeftJoyConRight = 4,
366 JoyDual = 5, 368 JoyDual = 5,
@@ -382,13 +384,6 @@ private:
382 Lagon = 21, 384 Lagon = 21,
383 }; 385 };
384 386
385 struct AppletFooterUi {
386 AppletFooterUiAttributes attributes{};
387 AppletFooterUiType type{AppletFooterUiType::None};
388 INSERT_PADDING_BYTES(0x5B); // Reserved
389 };
390 static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
391
392 // This is nn::hid::NpadLarkType 387 // This is nn::hid::NpadLarkType
393 enum class NpadLarkType : u32 { 388 enum class NpadLarkType : u32 {
394 Invalid, 389 Invalid,
@@ -419,13 +414,6 @@ private:
419 U, 414 U,
420 }; 415 };
421 416
422 struct AppletNfcXcd {
423 union {
424 AppletFooterUi applet_footer{};
425 Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo;
426 };
427 };
428
429 // This is nn::hid::detail::NpadInternalState 417 // This is nn::hid::detail::NpadInternalState
430 struct NpadInternalState { 418 struct NpadInternalState {
431 Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; 419 Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None};
@@ -452,7 +440,9 @@ private:
452 Core::HID::NpadBatteryLevel battery_level_dual{}; 440 Core::HID::NpadBatteryLevel battery_level_dual{};
453 Core::HID::NpadBatteryLevel battery_level_left{}; 441 Core::HID::NpadBatteryLevel battery_level_left{};
454 Core::HID::NpadBatteryLevel battery_level_right{}; 442 Core::HID::NpadBatteryLevel battery_level_right{};
455 AppletNfcXcd applet_nfc_xcd{}; 443 AppletFooterUiAttributes applet_footer_attributes{};
444 AppletFooterUiType applet_footer_type{AppletFooterUiType::None};
445 INSERT_PADDING_BYTES(0x5B); // Reserved
456 INSERT_PADDING_BYTES(0x20); // Unknown 446 INSERT_PADDING_BYTES(0x20); // Unknown
457 Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; 447 Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{};
458 NpadLarkType lark_type_l_and_main{}; 448 NpadLarkType lark_type_l_and_main{};
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index e57a3a80e..dd00921fd 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -16,22 +16,6 @@ class EmulatedConsole;
16namespace Service::HID { 16namespace Service::HID {
17class Controller_Touchscreen final : public ControllerBase { 17class Controller_Touchscreen final : public ControllerBase {
18public: 18public:
19 // This is nn::hid::TouchScreenModeForNx
20 enum class TouchScreenModeForNx : u8 {
21 UseSystemSetting,
22 Finger,
23 Heat2,
24 };
25
26 // This is nn::hid::TouchScreenConfigurationForNx
27 struct TouchScreenConfigurationForNx {
28 TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
29 INSERT_PADDING_BYTES_NOINIT(0x7);
30 INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved
31 };
32 static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17,
33 "TouchScreenConfigurationForNx is an invalid size");
34
35 explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); 19 explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
36 ~Controller_Touchscreen() override; 20 ~Controller_Touchscreen() override;
37 21
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 2bf1d8a27..4d70006c1 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
231 return applet_resource; 231 return applet_resource;
232} 232}
233 233
234Hid::Hid(Core::System& system_) 234Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
235 : ServiceFramework{system_, "hid"}, service_context{system_, service_name} { 235 : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{
236 system_,
237 service_name} {
236 // clang-format off 238 // clang-format off
237 static const FunctionInfo functions[] = { 239 static const FunctionInfo functions[] = {
238 {0, &Hid::CreateAppletResource, "CreateAppletResource"}, 240 {0, &Hid::CreateAppletResource, "CreateAppletResource"},
@@ -2368,7 +2370,7 @@ void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) {
2368 2370
2369void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { 2371void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) {
2370 IPC::RequestParser rp{ctx}; 2372 IPC::RequestParser rp{ctx};
2371 const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()}; 2373 const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()};
2372 const auto applet_resource_user_id{rp.Pop<u64>()}; 2374 const auto applet_resource_user_id{rp.Pop<u64>()};
2373 2375
2374 LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", 2376 LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}",
@@ -2543,7 +2545,9 @@ public:
2543 2545
2544class HidSys final : public ServiceFramework<HidSys> { 2546class HidSys final : public ServiceFramework<HidSys> {
2545public: 2547public:
2546 explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} { 2548 explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
2549 : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"},
2550 applet_resource{applet_resource_} {
2547 // clang-format off 2551 // clang-format off
2548 static const FunctionInfo functions[] = { 2552 static const FunctionInfo functions[] = {
2549 {31, nullptr, "SendKeyboardLockKeyEvent"}, 2553 {31, nullptr, "SendKeyboardLockKeyEvent"},
@@ -2568,7 +2572,7 @@ public:
2568 {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, 2572 {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"},
2569 {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, 2573 {304, nullptr, "EnableAssigningSingleOnSlSrPress"},
2570 {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, 2574 {305, nullptr, "DisableAssigningSingleOnSlSrPress"},
2571 {306, nullptr, "GetLastActiveNpad"}, 2575 {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"},
2572 {307, nullptr, "GetNpadSystemExtStyle"}, 2576 {307, nullptr, "GetNpadSystemExtStyle"},
2573 {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, 2577 {308, nullptr, "ApplyNpadSystemCommonPolicyFull"},
2574 {309, nullptr, "GetNpadFullKeyGripColor"}, 2578 {309, nullptr, "GetNpadFullKeyGripColor"},
@@ -2624,7 +2628,7 @@ public:
2624 {700, nullptr, "ActivateUniquePad"}, 2628 {700, nullptr, "ActivateUniquePad"},
2625 {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, 2629 {702, nullptr, "AcquireUniquePadConnectionEventHandle"},
2626 {703, nullptr, "GetUniquePadIds"}, 2630 {703, nullptr, "GetUniquePadIds"},
2627 {751, nullptr, "AcquireJoyDetachOnBluetoothOffEventHandle"}, 2631 {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"},
2628 {800, nullptr, "ListSixAxisSensorHandles"}, 2632 {800, nullptr, "ListSixAxisSensorHandles"},
2629 {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, 2633 {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"},
2630 {802, nullptr, "ResetSixAxisSensorCalibrationValues"}, 2634 {802, nullptr, "ResetSixAxisSensorCalibrationValues"},
@@ -2650,7 +2654,7 @@ public:
2650 {830, nullptr, "SetNotificationLedPattern"}, 2654 {830, nullptr, "SetNotificationLedPattern"},
2651 {831, nullptr, "SetNotificationLedPatternWithTimeout"}, 2655 {831, nullptr, "SetNotificationLedPatternWithTimeout"},
2652 {832, nullptr, "PrepareHidsForNotificationWake"}, 2656 {832, nullptr, "PrepareHidsForNotificationWake"},
2653 {850, nullptr, "IsUsbFullKeyControllerEnabled"}, 2657 {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
2654 {851, nullptr, "EnableUsbFullKeyController"}, 2658 {851, nullptr, "EnableUsbFullKeyController"},
2655 {852, nullptr, "IsUsbConnected"}, 2659 {852, nullptr, "IsUsbConnected"},
2656 {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, 2660 {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"},
@@ -2682,7 +2686,7 @@ public:
2682 {1150, nullptr, "SetTouchScreenMagnification"}, 2686 {1150, nullptr, "SetTouchScreenMagnification"},
2683 {1151, nullptr, "GetTouchScreenFirmwareVersion"}, 2687 {1151, nullptr, "GetTouchScreenFirmwareVersion"},
2684 {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, 2688 {1152, nullptr, "SetTouchScreenDefaultConfiguration"},
2685 {1153, nullptr, "GetTouchScreenDefaultConfiguration"}, 2689 {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"},
2686 {1154, nullptr, "IsFirmwareAvailableForNotification"}, 2690 {1154, nullptr, "IsFirmwareAvailableForNotification"},
2687 {1155, nullptr, "SetForceHandheldStyleVibration"}, 2691 {1155, nullptr, "SetForceHandheldStyleVibration"},
2688 {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, 2692 {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"},
@@ -2749,37 +2753,102 @@ public:
2749 // clang-format on 2753 // clang-format on
2750 2754
2751 RegisterHandlers(functions); 2755 RegisterHandlers(functions);
2756
2757 joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent");
2752 } 2758 }
2753 2759
2754private: 2760private:
2755 void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { 2761 void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
2756 // We already do this for homebrew so we can just stub it out
2757 LOG_WARNING(Service_HID, "called"); 2762 LOG_WARNING(Service_HID, "called");
2758 2763
2764 GetAppletResource()
2765 ->GetController<Controller_NPad>(HidController::NPad)
2766 .ApplyNpadSystemCommonPolicy();
2767
2759 IPC::ResponseBuilder rb{ctx, 2}; 2768 IPC::ResponseBuilder rb{ctx, 2};
2760 rb.Push(ResultSuccess); 2769 rb.Push(ResultSuccess);
2761 } 2770 }
2762 2771
2772 void GetLastActiveNpad(HLERequestContext& ctx) {
2773 LOG_DEBUG(Service_HID, "(STUBBED) called");
2774
2775 IPC::ResponseBuilder rb{ctx, 3};
2776 rb.Push(ResultSuccess);
2777 rb.PushEnum(system.HIDCore().GetLastActiveController());
2778 }
2779
2763 void GetUniquePadsFromNpad(HLERequestContext& ctx) { 2780 void GetUniquePadsFromNpad(HLERequestContext& ctx) {
2764 IPC::RequestParser rp{ctx}; 2781 IPC::RequestParser rp{ctx};
2765 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; 2782 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
2766 2783
2767 const s64 total_entries = 0;
2768 LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); 2784 LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type);
2769 2785
2786 const std::vector<Core::HID::UniquePadId> unique_pads{};
2787
2788 ctx.WriteBuffer(unique_pads);
2789
2790 IPC::ResponseBuilder rb{ctx, 3};
2791 rb.Push(ResultSuccess);
2792 rb.Push(static_cast<u32>(unique_pads.size()));
2793 }
2794
2795 void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) {
2796 LOG_INFO(Service_AM, "called");
2797
2798 IPC::ResponseBuilder rb{ctx, 2, 1};
2799 rb.Push(ResultSuccess);
2800 rb.PushCopyObjects(joy_detach_event->GetReadableEvent());
2801 }
2802
2803 void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
2804 const bool is_enabled = false;
2805
2806 LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled);
2807
2770 IPC::ResponseBuilder rb{ctx, 3}; 2808 IPC::ResponseBuilder rb{ctx, 3};
2771 rb.Push(ResultSuccess); 2809 rb.Push(ResultSuccess);
2772 rb.Push(total_entries); 2810 rb.Push(is_enabled);
2773 } 2811 }
2812
2813 void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) {
2814 LOG_WARNING(Service_HID, "(STUBBED) called");
2815
2816 Core::HID::TouchScreenConfigurationForNx touchscreen_config{
2817 .mode = Core::HID::TouchScreenModeForNx::Finger,
2818 };
2819
2820 if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 &&
2821 touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) {
2822 touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting;
2823 }
2824
2825 IPC::ResponseBuilder rb{ctx, 6};
2826 rb.Push(ResultSuccess);
2827 rb.PushRaw(touchscreen_config);
2828 }
2829
2830 std::shared_ptr<IAppletResource> GetAppletResource() {
2831 if (applet_resource == nullptr) {
2832 applet_resource = std::make_shared<IAppletResource>(system, service_context);
2833 }
2834
2835 return applet_resource;
2836 }
2837
2838 Kernel::KEvent* joy_detach_event;
2839 KernelHelpers::ServiceContext service_context;
2840 std::shared_ptr<IAppletResource> applet_resource;
2774}; 2841};
2775 2842
2776void LoopProcess(Core::System& system) { 2843void LoopProcess(Core::System& system) {
2777 auto server_manager = std::make_unique<ServerManager>(system); 2844 auto server_manager = std::make_unique<ServerManager>(system);
2845 std::shared_ptr<IAppletResource> applet_resource;
2778 2846
2779 server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system)); 2847 server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource));
2780 server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); 2848 server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system));
2781 server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); 2849 server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system));
2782 server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system)); 2850 server_manager->RegisterNamedService("hid:sys",
2851 std::make_shared<HidSys>(system, applet_resource));
2783 2852
2784 server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); 2853 server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system));
2785 server_manager->RegisterNamedService("irs:sys", 2854 server_manager->RegisterNamedService("irs:sys",
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index f247b83c2..0ca43de93 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -95,7 +95,7 @@ private:
95 95
96class Hid final : public ServiceFramework<Hid> { 96class Hid final : public ServiceFramework<Hid> {
97public: 97public:
98 explicit Hid(Core::System& system_); 98 explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_);
99 ~Hid() override; 99 ~Hid() override;
100 100
101 std::shared_ptr<IAppletResource> GetAppletResource(); 101 std::shared_ptr<IAppletResource> GetAppletResource();
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index 65c11a2f3..3b83c5ed7 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -7,17 +7,16 @@
7#include "core/hle/service/ipc_helpers.h" 7#include "core/hle/service/ipc_helpers.h"
8#include "core/hle/service/mii/mii.h" 8#include "core/hle/service/mii/mii.h"
9#include "core/hle/service/mii/mii_manager.h" 9#include "core/hle/service/mii/mii_manager.h"
10#include "core/hle/service/mii/mii_result.h"
10#include "core/hle/service/server_manager.h" 11#include "core/hle/service/server_manager.h"
11#include "core/hle/service/service.h" 12#include "core/hle/service/service.h"
12 13
13namespace Service::Mii { 14namespace Service::Mii {
14 15
15constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
16
17class IDatabaseService final : public ServiceFramework<IDatabaseService> { 16class IDatabaseService final : public ServiceFramework<IDatabaseService> {
18public: 17public:
19 explicit IDatabaseService(Core::System& system_) 18 explicit IDatabaseService(Core::System& system_, bool is_system_)
20 : ServiceFramework{system_, "IDatabaseService"} { 19 : ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} {
21 // clang-format off 20 // clang-format off
22 static const FunctionInfo functions[] = { 21 static const FunctionInfo functions[] = {
23 {0, &IDatabaseService::IsUpdated, "IsUpdated"}, 22 {0, &IDatabaseService::IsUpdated, "IsUpdated"},
@@ -54,34 +53,27 @@ public:
54 } 53 }
55 54
56private: 55private:
57 template <typename T>
58 std::vector<u8> SerializeArray(const std::vector<T>& values) {
59 std::vector<u8> out(values.size() * sizeof(T));
60 std::size_t offset{};
61 for (const auto& value : values) {
62 std::memcpy(out.data() + offset, &value, sizeof(T));
63 offset += sizeof(T);
64 }
65 return out;
66 }
67
68 void IsUpdated(HLERequestContext& ctx) { 56 void IsUpdated(HLERequestContext& ctx) {
69 IPC::RequestParser rp{ctx}; 57 IPC::RequestParser rp{ctx};
70 const auto source_flag{rp.PopRaw<SourceFlag>()}; 58 const auto source_flag{rp.PopRaw<SourceFlag>()};
71 59
72 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 60 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
73 61
62 const bool is_updated = manager.IsUpdated(metadata, source_flag);
63
74 IPC::ResponseBuilder rb{ctx, 3}; 64 IPC::ResponseBuilder rb{ctx, 3};
75 rb.Push(ResultSuccess); 65 rb.Push(ResultSuccess);
76 rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter)); 66 rb.Push<u8>(is_updated);
77 } 67 }
78 68
79 void IsFullDatabase(HLERequestContext& ctx) { 69 void IsFullDatabase(HLERequestContext& ctx) {
80 LOG_DEBUG(Service_Mii, "called"); 70 LOG_DEBUG(Service_Mii, "called");
81 71
72 const bool is_full_database = manager.IsFullDatabase();
73
82 IPC::ResponseBuilder rb{ctx, 3}; 74 IPC::ResponseBuilder rb{ctx, 3};
83 rb.Push(ResultSuccess); 75 rb.Push(ResultSuccess);
84 rb.Push(manager.IsFullDatabase()); 76 rb.Push<u8>(is_full_database);
85 } 77 }
86 78
87 void GetCount(HLERequestContext& ctx) { 79 void GetCount(HLERequestContext& ctx) {
@@ -90,57 +82,63 @@ private:
90 82
91 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 83 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
92 84
85 const u32 mii_count = manager.GetCount(metadata, source_flag);
86
93 IPC::ResponseBuilder rb{ctx, 3}; 87 IPC::ResponseBuilder rb{ctx, 3};
94 rb.Push(ResultSuccess); 88 rb.Push(ResultSuccess);
95 rb.Push<u32>(manager.GetCount(source_flag)); 89 rb.Push(mii_count);
96 } 90 }
97 91
98 void Get(HLERequestContext& ctx) { 92 void Get(HLERequestContext& ctx) {
99 IPC::RequestParser rp{ctx}; 93 IPC::RequestParser rp{ctx};
100 const auto source_flag{rp.PopRaw<SourceFlag>()}; 94 const auto source_flag{rp.PopRaw<SourceFlag>()};
95 const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
101 96
102 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 97 LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
98
99 u32 mii_count{};
100 std::vector<CharInfoElement> char_info_elements(output_size);
101 Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag);
103 102
104 const auto default_miis{manager.GetDefault(source_flag)}; 103 if (mii_count != 0) {
105 if (default_miis.size() > 0) { 104 ctx.WriteBuffer(char_info_elements);
106 ctx.WriteBuffer(SerializeArray(default_miis));
107 } 105 }
108 106
109 IPC::ResponseBuilder rb{ctx, 3}; 107 IPC::ResponseBuilder rb{ctx, 3};
110 rb.Push(ResultSuccess); 108 rb.Push(result);
111 rb.Push<u32>(static_cast<u32>(default_miis.size())); 109 rb.Push(mii_count);
112 } 110 }
113 111
114 void Get1(HLERequestContext& ctx) { 112 void Get1(HLERequestContext& ctx) {
115 IPC::RequestParser rp{ctx}; 113 IPC::RequestParser rp{ctx};
116 const auto source_flag{rp.PopRaw<SourceFlag>()}; 114 const auto source_flag{rp.PopRaw<SourceFlag>()};
115 const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
117 116
118 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 117 LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
119 118
120 const auto default_miis{manager.GetDefault(source_flag)}; 119 u32 mii_count{};
120 std::vector<CharInfo> char_info(output_size);
121 Result result = manager.Get(metadata, char_info, mii_count, source_flag);
121 122
122 std::vector<CharInfo> values; 123 if (mii_count != 0) {
123 for (const auto& element : default_miis) { 124 ctx.WriteBuffer(char_info);
124 values.emplace_back(element.info);
125 } 125 }
126 126
127 ctx.WriteBuffer(SerializeArray(values));
128
129 IPC::ResponseBuilder rb{ctx, 3}; 127 IPC::ResponseBuilder rb{ctx, 3};
130 rb.Push(ResultSuccess); 128 rb.Push(result);
131 rb.Push<u32>(static_cast<u32>(default_miis.size())); 129 rb.Push(mii_count);
132 } 130 }
133 131
134 void UpdateLatest(HLERequestContext& ctx) { 132 void UpdateLatest(HLERequestContext& ctx) {
135 IPC::RequestParser rp{ctx}; 133 IPC::RequestParser rp{ctx};
136 const auto info{rp.PopRaw<CharInfo>()}; 134 const auto char_info{rp.PopRaw<CharInfo>()};
137 const auto source_flag{rp.PopRaw<SourceFlag>()}; 135 const auto source_flag{rp.PopRaw<SourceFlag>()};
138 136
139 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 137 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
140 138
141 CharInfo new_char_info{}; 139 CharInfo new_char_info{};
142 const auto result{manager.UpdateLatest(&new_char_info, info, source_flag)}; 140 const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag);
143 if (result != ResultSuccess) { 141 if (result.IsFailure()) {
144 IPC::ResponseBuilder rb{ctx, 2}; 142 IPC::ResponseBuilder rb{ctx, 2};
145 rb.Push(result); 143 rb.Push(result);
146 return; 144 return;
@@ -153,7 +151,6 @@ private:
153 151
154 void BuildRandom(HLERequestContext& ctx) { 152 void BuildRandom(HLERequestContext& ctx) {
155 IPC::RequestParser rp{ctx}; 153 IPC::RequestParser rp{ctx};
156
157 const auto age{rp.PopRaw<Age>()}; 154 const auto age{rp.PopRaw<Age>()};
158 const auto gender{rp.PopRaw<Gender>()}; 155 const auto gender{rp.PopRaw<Gender>()};
159 const auto race{rp.PopRaw<Race>()}; 156 const auto race{rp.PopRaw<Race>()};
@@ -162,47 +159,48 @@ private:
162 159
163 if (age > Age::All) { 160 if (age > Age::All) {
164 IPC::ResponseBuilder rb{ctx, 2}; 161 IPC::ResponseBuilder rb{ctx, 2};
165 rb.Push(ERROR_INVALID_ARGUMENT); 162 rb.Push(ResultInvalidArgument);
166 LOG_ERROR(Service_Mii, "invalid age={}", age);
167 return; 163 return;
168 } 164 }
169 165
170 if (gender > Gender::All) { 166 if (gender > Gender::All) {
171 IPC::ResponseBuilder rb{ctx, 2}; 167 IPC::ResponseBuilder rb{ctx, 2};
172 rb.Push(ERROR_INVALID_ARGUMENT); 168 rb.Push(ResultInvalidArgument);
173 LOG_ERROR(Service_Mii, "invalid gender={}", gender);
174 return; 169 return;
175 } 170 }
176 171
177 if (race > Race::All) { 172 if (race > Race::All) {
178 IPC::ResponseBuilder rb{ctx, 2}; 173 IPC::ResponseBuilder rb{ctx, 2};
179 rb.Push(ERROR_INVALID_ARGUMENT); 174 rb.Push(ResultInvalidArgument);
180 LOG_ERROR(Service_Mii, "invalid race={}", race);
181 return; 175 return;
182 } 176 }
183 177
178 CharInfo char_info{};
179 manager.BuildRandom(char_info, age, gender, race);
180
184 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 181 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
185 rb.Push(ResultSuccess); 182 rb.Push(ResultSuccess);
186 rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race)); 183 rb.PushRaw<CharInfo>(char_info);
187 } 184 }
188 185
189 void BuildDefault(HLERequestContext& ctx) { 186 void BuildDefault(HLERequestContext& ctx) {
190 IPC::RequestParser rp{ctx}; 187 IPC::RequestParser rp{ctx};
191 const auto index{rp.Pop<u32>()}; 188 const auto index{rp.Pop<u32>()};
192 189
193 LOG_DEBUG(Service_Mii, "called with index={}", index); 190 LOG_INFO(Service_Mii, "called with index={}", index);
194 191
195 if (index > 5) { 192 if (index > 5) {
196 LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
197 index);
198 IPC::ResponseBuilder rb{ctx, 2}; 193 IPC::ResponseBuilder rb{ctx, 2};
199 rb.Push(ERROR_INVALID_ARGUMENT); 194 rb.Push(ResultInvalidArgument);
200 return; 195 return;
201 } 196 }
202 197
198 CharInfo char_info{};
199 manager.BuildDefault(char_info, index);
200
203 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 201 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
204 rb.Push(ResultSuccess); 202 rb.Push(ResultSuccess);
205 rb.PushRaw<CharInfo>(manager.BuildDefault(index)); 203 rb.PushRaw<CharInfo>(char_info);
206 } 204 }
207 205
208 void GetIndex(HLERequestContext& ctx) { 206 void GetIndex(HLERequestContext& ctx) {
@@ -211,19 +209,21 @@ private:
211 209
212 LOG_DEBUG(Service_Mii, "called"); 210 LOG_DEBUG(Service_Mii, "called");
213 211
214 u32 index{}; 212 s32 index{};
213 const auto result = manager.GetIndex(metadata, info, index);
214
215 IPC::ResponseBuilder rb{ctx, 3}; 215 IPC::ResponseBuilder rb{ctx, 3};
216 rb.Push(manager.GetIndex(info, index)); 216 rb.Push(result);
217 rb.Push(index); 217 rb.Push(index);
218 } 218 }
219 219
220 void SetInterfaceVersion(HLERequestContext& ctx) { 220 void SetInterfaceVersion(HLERequestContext& ctx) {
221 IPC::RequestParser rp{ctx}; 221 IPC::RequestParser rp{ctx};
222 current_interface_version = rp.PopRaw<u32>(); 222 const auto interface_version{rp.PopRaw<u32>()};
223 223
224 LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version); 224 LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
225 225
226 UNIMPLEMENTED_IF(current_interface_version != 1); 226 manager.SetInterfaceVersion(metadata, interface_version);
227 227
228 IPC::ResponseBuilder rb{ctx, 2}; 228 IPC::ResponseBuilder rb{ctx, 2};
229 rb.Push(ResultSuccess); 229 rb.Push(ResultSuccess);
@@ -231,30 +231,27 @@ private:
231 231
232 void Convert(HLERequestContext& ctx) { 232 void Convert(HLERequestContext& ctx) {
233 IPC::RequestParser rp{ctx}; 233 IPC::RequestParser rp{ctx};
234
235 const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; 234 const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
236 235
237 LOG_INFO(Service_Mii, "called"); 236 LOG_INFO(Service_Mii, "called");
238 237
238 CharInfo char_info{};
239 manager.ConvertV3ToCharInfo(char_info, mii_v3);
240
239 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; 241 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
240 rb.Push(ResultSuccess); 242 rb.Push(ResultSuccess);
241 rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3)); 243 rb.PushRaw<CharInfo>(char_info);
242 }
243
244 constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
245 return current_interface_version >= interface_version;
246 } 244 }
247 245
248 MiiManager manager; 246 MiiManager manager{};
249 247 DatabaseSessionMetadata metadata{};
250 u32 current_interface_version{}; 248 bool is_system{};
251 u64 current_update_counter{};
252}; 249};
253 250
254class MiiDBModule final : public ServiceFramework<MiiDBModule> { 251class MiiDBModule final : public ServiceFramework<MiiDBModule> {
255public: 252public:
256 explicit MiiDBModule(Core::System& system_, const char* name_) 253 explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_)
257 : ServiceFramework{system_, name_} { 254 : ServiceFramework{system_, name_}, is_system{is_system_} {
258 // clang-format off 255 // clang-format off
259 static const FunctionInfo functions[] = { 256 static const FunctionInfo functions[] = {
260 {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, 257 {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
@@ -268,10 +265,12 @@ private:
268 void GetDatabaseService(HLERequestContext& ctx) { 265 void GetDatabaseService(HLERequestContext& ctx) {
269 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 266 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
270 rb.Push(ResultSuccess); 267 rb.Push(ResultSuccess);
271 rb.PushIpcInterface<IDatabaseService>(system); 268 rb.PushIpcInterface<IDatabaseService>(system, is_system);
272 269
273 LOG_DEBUG(Service_Mii, "called"); 270 LOG_DEBUG(Service_Mii, "called");
274 } 271 }
272
273 bool is_system{};
275}; 274};
276 275
277class MiiImg final : public ServiceFramework<MiiImg> { 276class MiiImg final : public ServiceFramework<MiiImg> {
@@ -303,8 +302,10 @@ public:
303void LoopProcess(Core::System& system) { 302void LoopProcess(Core::System& system) {
304 auto server_manager = std::make_unique<ServerManager>(system); 303 auto server_manager = std::make_unique<ServerManager>(system);
305 304
306 server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e")); 305 server_manager->RegisterNamedService("mii:e",
307 server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u")); 306 std::make_shared<MiiDBModule>(system, "mii:e", true));
307 server_manager->RegisterNamedService("mii:u",
308 std::make_shared<MiiDBModule>(system, "mii:u", false));
308 server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); 309 server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
309 ServerManager::RunServer(std::move(server_manager)); 310 ServerManager::RunServer(std::move(server_manager));
310} 311}
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 46125d473..292d63777 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -10,386 +10,24 @@
10 10
11#include "core/hle/service/acc/profile_manager.h" 11#include "core/hle/service/acc/profile_manager.h"
12#include "core/hle/service/mii/mii_manager.h" 12#include "core/hle/service/mii/mii_manager.h"
13#include "core/hle/service/mii/raw_data.h" 13#include "core/hle/service/mii/mii_result.h"
14#include "core/hle/service/mii/mii_util.h"
15#include "core/hle/service/mii/types/core_data.h"
16#include "core/hle/service/mii/types/raw_data.h"
14 17
15namespace Service::Mii { 18namespace Service::Mii {
16
17namespace {
18
19constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
20
21constexpr std::size_t BaseMiiCount{2};
22constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; 19constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
23 20
24constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; 21MiiManager::MiiManager() {}
25constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7};
26constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13};
27constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23};
28constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0};
29constexpr std::array<u8, 62> EyeRotateLookup{
30 {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
31 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
32 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
33 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}};
34constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07,
35 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06,
36 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}};
37
38template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
39std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
40 std::array<T, DestArraySize> out{};
41 std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
42 return out;
43}
44
45CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
46 MiiStoreBitFields bf;
47 std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
48
49 return {
50 .uuid = data.data.uuid,
51 .name = ResizeArray<char16_t, 10, 11>(data.data.name),
52 .font_region = static_cast<u8>(bf.font_region.Value()),
53 .favorite_color = static_cast<u8>(bf.favorite_color.Value()),
54 .gender = static_cast<u8>(bf.gender.Value()),
55 .height = static_cast<u8>(bf.height.Value()),
56 .build = static_cast<u8>(bf.build.Value()),
57 .type = static_cast<u8>(bf.type.Value()),
58 .region_move = static_cast<u8>(bf.region_move.Value()),
59 .faceline_type = static_cast<u8>(bf.faceline_type.Value()),
60 .faceline_color = static_cast<u8>(bf.faceline_color.Value()),
61 .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()),
62 .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()),
63 .hair_type = static_cast<u8>(bf.hair_type.Value()),
64 .hair_color = static_cast<u8>(bf.hair_color.Value()),
65 .hair_flip = static_cast<u8>(bf.hair_flip.Value()),
66 .eye_type = static_cast<u8>(bf.eye_type.Value()),
67 .eye_color = static_cast<u8>(bf.eye_color.Value()),
68 .eye_scale = static_cast<u8>(bf.eye_scale.Value()),
69 .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()),
70 .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()),
71 .eye_x = static_cast<u8>(bf.eye_x.Value()),
72 .eye_y = static_cast<u8>(bf.eye_y.Value()),
73 .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()),
74 .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()),
75 .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()),
76 .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()),
77 .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()),
78 .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()),
79 .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3),
80 .nose_type = static_cast<u8>(bf.nose_type.Value()),
81 .nose_scale = static_cast<u8>(bf.nose_scale.Value()),
82 .nose_y = static_cast<u8>(bf.nose_y.Value()),
83 .mouth_type = static_cast<u8>(bf.mouth_type.Value()),
84 .mouth_color = static_cast<u8>(bf.mouth_color.Value()),
85 .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()),
86 .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()),
87 .mouth_y = static_cast<u8>(bf.mouth_y.Value()),
88 .beard_color = static_cast<u8>(bf.beard_color.Value()),
89 .beard_type = static_cast<u8>(bf.beard_type.Value()),
90 .mustache_type = static_cast<u8>(bf.mustache_type.Value()),
91 .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()),
92 .mustache_y = static_cast<u8>(bf.mustache_y.Value()),
93 .glasses_type = static_cast<u8>(bf.glasses_type.Value()),
94 .glasses_color = static_cast<u8>(bf.glasses_color.Value()),
95 .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()),
96 .glasses_y = static_cast<u8>(bf.glasses_y.Value()),
97 .mole_type = static_cast<u8>(bf.mole_type.Value()),
98 .mole_scale = static_cast<u8>(bf.mole_scale.Value()),
99 .mole_x = static_cast<u8>(bf.mole_x.Value()),
100 .mole_y = static_cast<u8>(bf.mole_y.Value()),
101 .padding = 0,
102 };
103}
104
105u16 GenerateCrc16(const void* data, std::size_t size) {
106 s32 crc{};
107 for (std::size_t i = 0; i < size; i++) {
108 crc ^= static_cast<const u8*>(data)[i] << 8;
109 for (std::size_t j = 0; j < 8; j++) {
110 crc <<= 1;
111 if ((crc & 0x10000) != 0) {
112 crc = (crc ^ 0x1021) & 0xFFFF;
113 }
114 }
115 }
116 return Common::swap16(static_cast<u16>(crc));
117}
118
119template <typename T>
120T GetRandomValue(T min, T max) {
121 std::random_device device;
122 std::mt19937 gen(device());
123 std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max));
124 return static_cast<T>(distribution(gen));
125}
126
127template <typename T>
128T GetRandomValue(T max) {
129 return GetRandomValue<T>({}, max);
130}
131
132MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) {
133 MiiStoreBitFields bf{};
134
135 if (gender == Gender::All) {
136 gender = GetRandomValue<Gender>(Gender::Maximum);
137 }
138
139 bf.gender.Assign(gender);
140 bf.favorite_color.Assign(GetRandomValue<u8>(11));
141 bf.region_move.Assign(0);
142 bf.font_region.Assign(FontRegion::Standard);
143 bf.type.Assign(0);
144 bf.height.Assign(64);
145 bf.build.Assign(64);
146
147 if (age == Age::All) {
148 const auto temp{GetRandomValue<int>(10)};
149 if (temp >= 8) {
150 age = Age::Old;
151 } else if (temp >= 4) {
152 age = Age::Normal;
153 } else {
154 age = Age::Young;
155 }
156 }
157
158 if (race == Race::All) {
159 const auto temp{GetRandomValue<int>(10)};
160 if (temp >= 8) {
161 race = Race::Black;
162 } else if (temp >= 4) {
163 race = Race::White;
164 } else {
165 race = Race::Asian;
166 }
167 }
168
169 u32 axis_y{};
170 if (gender == Gender::Female && age == Age::Young) {
171 axis_y = GetRandomValue<u32>(3);
172 }
173
174 const std::size_t index{3 * static_cast<std::size_t>(age) +
175 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
176
177 const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)};
178 const auto faceline_color_info{RawData::RandomMiiFacelineColor.at(
179 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
180 const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
181 const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
182 const auto hair_type_info{RawData::RandomMiiHairType.at(index)};
183 const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
184 static_cast<std::size_t>(age))};
185 const auto eye_type_info{RawData::RandomMiiEyeType.at(index)};
186 const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
187 const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
188 const auto nose_type_info{RawData::RandomMiiNoseType.at(index)};
189 const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)};
190 const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
191
192 bf.faceline_type.Assign(
193 faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
194 bf.faceline_color.Assign(
195 faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
196 bf.faceline_wrinkle.Assign(
197 faceline_wrinkle_info
198 .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
199 bf.faceline_makeup.Assign(
200 faceline_makeup_info
201 .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
202
203 bf.hair_type.Assign(
204 hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]);
205 bf.hair_color.Assign(
206 HairColorLookup[hair_color_info
207 .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]);
208 bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum));
209
210 bf.eye_type.Assign(
211 eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]);
212
213 const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
214 const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
215 const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
216 const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]};
217
218 bf.eye_color.Assign(
219 EyeColorLookup[eye_color_info
220 .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]);
221 bf.eye_scale.Assign(4);
222 bf.eye_aspect.Assign(3);
223 bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate);
224 bf.eye_x.Assign(2);
225 bf.eye_y.Assign(axis_y + 12);
226
227 bf.eyebrow_type.Assign(
228 eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
229
230 const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
231 const auto eyebrow_y{race == Race::Asian ? 9 : 10};
232 const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6};
233 const auto eyebrow_rotate{
234 32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]};
235
236 bf.eyebrow_color.Assign(bf.hair_color);
237 bf.eyebrow_scale.Assign(4);
238 bf.eyebrow_aspect.Assign(3);
239 bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate);
240 bf.eyebrow_x.Assign(2);
241 bf.eyebrow_y.Assign(axis_y + eyebrow_y);
242
243 const auto nose_scale{gender == Gender::Female ? 3 : 4};
244
245 bf.nose_type.Assign(
246 nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]);
247 bf.nose_scale.Assign(nose_scale);
248 bf.nose_y.Assign(axis_y + 9);
249
250 const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0};
251
252 bf.mouth_type.Assign(
253 mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
254 bf.mouth_color.Assign(MouthColorLookup[mouth_color]);
255 bf.mouth_scale.Assign(4);
256 bf.mouth_aspect.Assign(3);
257 bf.mouth_y.Assign(axis_y + 13);
258
259 bf.beard_color.Assign(bf.hair_color);
260 bf.mustache_scale.Assign(4);
261
262 if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) {
263 const auto mustache_and_beard_flag{
264 GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)};
265
266 auto beard_type{BeardType::None};
267 auto mustache_type{MustacheType::None};
268
269 if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
270 BeardAndMustacheFlag::Beard) {
271 beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5);
272 }
273
274 if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
275 BeardAndMustacheFlag::Mustache) {
276 mustache_type =
277 GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5);
278 }
279
280 bf.mustache_type.Assign(mustache_type);
281 bf.beard_type.Assign(beard_type);
282 bf.mustache_y.Assign(10);
283 } else {
284 bf.mustache_type.Assign(MustacheType::None);
285 bf.beard_type.Assign(BeardType::None);
286 bf.mustache_y.Assign(axis_y + 10);
287 }
288
289 const auto glasses_type_start{GetRandomValue<std::size_t>(100)};
290 u8 glasses_type{};
291 while (glasses_type_start < glasses_type_info.values[glasses_type]) {
292 if (++glasses_type >= glasses_type_info.values_count) {
293 ASSERT(false);
294 break;
295 }
296 }
297
298 bf.glasses_type.Assign(glasses_type);
299 bf.glasses_color.Assign(GlassesColorLookup[0]);
300 bf.glasses_scale.Assign(4);
301 bf.glasses_y.Assign(axis_y + 10);
302
303 bf.mole_type.Assign(0);
304 bf.mole_scale.Assign(4);
305 bf.mole_x.Assign(2);
306 bf.mole_y.Assign(20);
307
308 return {DefaultMiiName, bf, user_id};
309}
310
311MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) {
312 MiiStoreBitFields bf{};
313
314 bf.font_region.Assign(info.font_region);
315 bf.favorite_color.Assign(info.favorite_color);
316 bf.gender.Assign(info.gender);
317 bf.height.Assign(info.height);
318 bf.build.Assign(info.weight);
319 bf.type.Assign(info.type);
320 bf.region_move.Assign(info.region);
321 bf.faceline_type.Assign(info.face_type);
322 bf.faceline_color.Assign(info.face_color);
323 bf.faceline_wrinkle.Assign(info.face_wrinkle);
324 bf.faceline_makeup.Assign(info.face_makeup);
325 bf.hair_type.Assign(info.hair_type);
326 bf.hair_color.Assign(HairColorLookup[info.hair_color]);
327 bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip));
328 bf.eye_type.Assign(info.eye_type);
329 bf.eye_color.Assign(EyeColorLookup[info.eye_color]);
330 bf.eye_scale.Assign(info.eye_scale);
331 bf.eye_aspect.Assign(info.eye_aspect);
332 bf.eye_rotate.Assign(info.eye_rotate);
333 bf.eye_x.Assign(info.eye_x);
334 bf.eye_y.Assign(info.eye_y);
335 bf.eyebrow_type.Assign(info.eyebrow_type);
336 bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]);
337 bf.eyebrow_scale.Assign(info.eyebrow_scale);
338 bf.eyebrow_aspect.Assign(info.eyebrow_aspect);
339 bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
340 bf.eyebrow_x.Assign(info.eyebrow_x);
341 bf.eyebrow_y.Assign(info.eyebrow_y - 3);
342 bf.nose_type.Assign(info.nose_type);
343 bf.nose_scale.Assign(info.nose_scale);
344 bf.nose_y.Assign(info.nose_y);
345 bf.mouth_type.Assign(info.mouth_type);
346 bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]);
347 bf.mouth_scale.Assign(info.mouth_scale);
348 bf.mouth_aspect.Assign(info.mouth_aspect);
349 bf.mouth_y.Assign(info.mouth_y);
350 bf.beard_color.Assign(HairColorLookup[info.beard_color]);
351 bf.beard_type.Assign(static_cast<BeardType>(info.beard_type));
352 bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type));
353 bf.mustache_scale.Assign(info.mustache_scale);
354 bf.mustache_y.Assign(info.mustache_y);
355 bf.glasses_type.Assign(info.glasses_type);
356 bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]);
357 bf.glasses_scale.Assign(info.glasses_scale);
358 bf.glasses_y.Assign(info.glasses_y);
359 bf.mole_type.Assign(info.mole_type);
360 bf.mole_scale.Assign(info.mole_scale);
361 bf.mole_x.Assign(info.mole_x);
362 bf.mole_y.Assign(info.mole_y);
363
364 return {DefaultMiiName, bf, user_id};
365}
366
367} // namespace
368
369MiiStoreData::MiiStoreData() = default;
370
371MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields,
372 const Common::UUID& user_id) {
373 data.name = name;
374 data.uuid = Common::UUID::MakeRandomRFC4122V4();
375
376 std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields));
377 data_crc = GenerateCrc16(data.data.data(), sizeof(data));
378 device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID));
379}
380 22
381MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} 23bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
382
383bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) {
384 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 24 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
385 return false; 25 return false;
386 } 26 }
387 27
388 const bool result{current_update_counter != update_counter}; 28 const auto metadata_update_counter = metadata.update_counter;
389 29 metadata.update_counter = update_counter;
390 current_update_counter = update_counter; 30 return metadata_update_counter != update_counter;
391
392 return result;
393} 31}
394 32
395bool MiiManager::IsFullDatabase() const { 33bool MiiManager::IsFullDatabase() const {
@@ -397,301 +35,138 @@ bool MiiManager::IsFullDatabase() const {
397 return false; 35 return false;
398} 36}
399 37
400u32 MiiManager::GetCount(SourceFlag source_flag) const { 38u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
401 std::size_t count{}; 39 u32 mii_count{};
40 if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
41 mii_count += DefaultMiiCount;
42 }
402 if ((source_flag & SourceFlag::Database) != SourceFlag::None) { 43 if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
403 // TODO(bunnei): We don't implement the Mii database, but when we do, update this 44 // TODO(bunnei): We don't implement the Mii database, but when we do, update this
404 count += 0;
405 } 45 }
406 if ((source_flag & SourceFlag::Default) != SourceFlag::None) { 46 return mii_count;
407 count += (DefaultMiiCount - BaseMiiCount);
408 }
409 return static_cast<u32>(count);
410} 47}
411 48
412Result MiiManager::UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag) { 49Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
50 const CharInfo& char_info, SourceFlag source_flag) {
413 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 51 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
414 return ERROR_CANNOT_FIND_ENTRY; 52 return ResultNotFound;
415 } 53 }
416 54
417 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry 55 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
418 return ERROR_CANNOT_FIND_ENTRY; 56 return ResultNotFound;
419} 57}
420 58
421CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { 59void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
422 return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); 60 StoreData store_data{};
61 store_data.BuildDefault(index);
62 out_char_info.SetFromStoreData(store_data);
423} 63}
424 64
425CharInfo MiiManager::BuildDefault(std::size_t index) { 65void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const {
426 return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); 66 StoreData store_data{};
67 store_data.BuildBase(gender);
68 out_char_info.SetFromStoreData(store_data);
427} 69}
428 70
429CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const { 71void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const {
430 Service::Mii::MiiManager manager; 72 StoreData store_data{};
431 auto mii = manager.BuildDefault(0); 73 store_data.BuildRandom(age, gender, race);
432 74 out_char_info.SetFromStoreData(store_data);
433 if (!ValidateV3Info(mii_v3)) {
434 return mii;
435 }
436
437 // TODO: We are ignoring a bunch of data from the mii_v3
438
439 mii.gender = static_cast<u8>(mii_v3.mii_information.gender);
440 mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color);
441 mii.height = mii_v3.height;
442 mii.build = mii_v3.build;
443
444 // Copy name until string terminator
445 mii.name = {};
446 for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
447 mii.name[index] = mii_v3.mii_name[index];
448 if (mii.name[index] == 0) {
449 break;
450 }
451 }
452
453 mii.font_region = mii_v3.region_information.character_set;
454
455 mii.faceline_type = mii_v3.appearance_bits1.face_shape;
456 mii.faceline_color = mii_v3.appearance_bits1.skin_color;
457 mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
458 mii.faceline_make = mii_v3.appearance_bits2.makeup;
459
460 mii.hair_type = mii_v3.hair_style;
461 mii.hair_color = mii_v3.appearance_bits3.hair_color;
462 mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
463
464 mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
465 mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
466 mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
467 mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
468 mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
469 mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
470 mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
471
472 mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
473 mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
474 mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
475 mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
476 mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
477 mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
478 mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
479
480 mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
481 mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
482 mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
483
484 mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
485 mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
486 mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
487 mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
488 mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
489
490 mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
491 mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
492 mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
493
494 mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
495 mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
496
497 mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
498 mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
499 mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
500 mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
501
502 mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
503 mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
504 mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
505 mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
506
507 // TODO: Validate mii data
508
509 return mii;
510} 75}
511 76
512Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const { 77void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
513 Service::Mii::MiiManager manager; 78 StoreData store_data{};
514 Ver3StoreData mii_v3{}; 79 mii_v3.BuildToStoreData(store_data);
515 80 out_char_info.SetFromStoreData(store_data);
516 // TODO: We are ignoring a bunch of data from the mii_v3 81}
517
518 mii_v3.version = 1;
519 mii_v3.mii_information.gender.Assign(mii.gender);
520 mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
521 mii_v3.height = mii.height;
522 mii_v3.build = mii.build;
523 82
524 // Copy name until string terminator 83Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
525 mii_v3.mii_name = {}; 84 std::span<CharInfoElement> out_elements, u32& out_count,
526 for (std::size_t index = 0; index < mii.name.size() - 1; index++) { 85 SourceFlag source_flag) {
527 mii_v3.mii_name[index] = mii.name[index]; 86 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
528 if (mii_v3.mii_name[index] == 0) { 87 return BuildDefault(out_elements, out_count, source_flag);
529 break;
530 }
531 } 88 }
532 89
533 mii_v3.region_information.character_set.Assign(mii.font_region); 90 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
534 91
535 mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type); 92 // Include default Mii at the end of the list
536 mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle); 93 return BuildDefault(out_elements, out_count, source_flag);
537 mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make); 94}
538 95
539 mii_v3.hair_style = mii.hair_type; 96Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
540 mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip); 97 u32& out_count, SourceFlag source_flag) {
98 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
99 return BuildDefault(out_char_info, out_count, source_flag);
100 }
541 101
542 mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type); 102 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
543 mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
544 mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
545 mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
546 mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
547 mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
548 103
549 mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type); 104 // Include default Mii at the end of the list
550 mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale); 105 return BuildDefault(out_char_info, out_count, source_flag);
551 mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect); 106}
552 mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
553 mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
554 mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
555 107
556 mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type); 108Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
557 mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale); 109 SourceFlag source_flag) {
558 mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y); 110 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
111 return ResultSuccess;
112 }
559 113
560 mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type); 114 StoreData store_data{};
561 mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
562 mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
563 mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
564 115
565 mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type); 116 for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
566 mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale); 117 if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
567 mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y); 118 return ResultInvalidArgumentSize;
119 }
568 120
569 mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type); 121 store_data.BuildDefault(static_cast<u32>(index));
570 122
571 mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale); 123 out_elements[out_count].source = Source::Default;
572 mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y); 124 out_elements[out_count].char_info.SetFromStoreData(store_data);
125 out_count++;
126 }
573 127
574 mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type); 128 return ResultSuccess;
575 mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale); 129}
576 mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
577 mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
578 130
579 // These types are converted to V3 from a table 131Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
580 mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]); 132 SourceFlag source_flag) {
581 mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]); 133 if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
582 mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]); 134 return ResultSuccess;
583 mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]); 135 }
584 mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]);
585 mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]);
586 mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]);
587 mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]);
588 136
589 mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16)); 137 StoreData store_data{};
590 138
591 // TODO: Validate mii_v3 data 139 for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
140 if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
141 return ResultInvalidArgumentSize;
142 }
592 143
593 return mii_v3; 144 store_data.BuildDefault(static_cast<u32>(index));
594}
595 145
596NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const { 146 out_char_info[out_count].SetFromStoreData(store_data);
597 return { 147 out_count++;
598 .faceline_color = static_cast<u8>(mii.faceline_color & 0xf), 148 }
599 .hair_color = static_cast<u8>(mii.hair_color & 0x7f),
600 .eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
601 .eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
602 .mouth_color = static_cast<u8>(mii.mouth_color & 0x7f),
603 .beard_color = static_cast<u8>(mii.beard_color & 0x7f),
604 .glass_color = static_cast<u8>(mii.glasses_color & 0x7f),
605 .glass_type = static_cast<u8>(mii.glasses_type & 0x1f),
606 };
607}
608 149
609bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const { 150 return ResultSuccess;
610 bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
611
612 is_valid = is_valid && (mii_v3.mii_name[0] != 0);
613
614 is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
615 is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
616 is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
617 is_valid = is_valid && (mii_v3.height < 128);
618 is_valid = is_valid && (mii_v3.build < 128);
619
620 is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
621 is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
622 is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
623 is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
624
625 is_valid = is_valid && (mii_v3.hair_style < 132);
626 is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
627
628 is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
629 is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
630 is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
631 is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
632 is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
633 is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
634 is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
635
636 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
637 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
638 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
639 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
640 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
641 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
642 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
643
644 is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
645 is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
646 is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
647
648 is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
649 is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
650 is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
651 is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
652 is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
653
654 is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
655 is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
656 is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
657
658 is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
659 is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
660
661 is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
662 is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
663 is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
664 is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
665
666 is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
667 is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
668 is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
669 is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
670
671 return is_valid;
672} 151}
673 152
674std::vector<MiiInfoElement> MiiManager::GetDefault(SourceFlag source_flag) { 153Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
675 std::vector<MiiInfoElement> result; 154 s32& out_index) {
676 155
677 if ((source_flag & SourceFlag::Default) == SourceFlag::None) { 156 if (char_info.Verify() != ValidationResult::NoErrors) {
678 return result; 157 return ResultInvalidCharInfo;
679 } 158 }
680 159
681 for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) {
682 result.emplace_back(BuildDefault(index), Source::Default);
683 }
684
685 return result;
686}
687
688Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) {
689 constexpr u32 INVALID_INDEX{0xFFFFFFFF}; 160 constexpr u32 INVALID_INDEX{0xFFFFFFFF};
690 161
691 index = INVALID_INDEX; 162 out_index = INVALID_INDEX;
692 163
693 // TODO(bunnei): We don't implement the Mii database, so we can't have an index 164 // TODO(bunnei): We don't implement the Mii database, so we can't have an index
694 return ERROR_CANNOT_FIND_ENTRY; 165 return ResultNotFound;
166}
167
168void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) {
169 metadata.interface_version = version;
695} 170}
696 171
697} // namespace Service::Mii 172} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index 45c2be3c8..a2e7a6d73 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -6,7 +6,10 @@
6#include <vector> 6#include <vector>
7 7
8#include "core/hle/result.h" 8#include "core/hle/result.h"
9#include "core/hle/service/mii/types.h" 9#include "core/hle/service/mii/mii_types.h"
10#include "core/hle/service/mii/types/char_info.h"
11#include "core/hle/service/mii/types/store_data.h"
12#include "core/hle/service/mii/types/ver3_store_data.h"
10 13
11namespace Service::Mii { 14namespace Service::Mii {
12 15
@@ -16,25 +19,30 @@ class MiiManager {
16public: 19public:
17 MiiManager(); 20 MiiManager();
18 21
19 bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); 22 bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
20 bool IsFullDatabase() const;
21 u32 GetCount(SourceFlag source_flag) const;
22 Result UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag);
23 CharInfo BuildRandom(Age age, Gender gender, Race race);
24 CharInfo BuildDefault(std::size_t index);
25 CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
26 bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
27 std::vector<MiiInfoElement> GetDefault(SourceFlag source_flag);
28 Result GetIndex(const CharInfo& info, u32& index);
29
30 // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData
31 Ver3StoreData BuildFromStoreData(const CharInfo& mii) const;
32 23
33 // This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData 24 bool IsFullDatabase() const;
34 NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const; 25 u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
26 Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
27 const CharInfo& char_info, SourceFlag source_flag);
28 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
29 u32& out_count, SourceFlag source_flag);
30 Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
31 u32& out_count, SourceFlag source_flag);
32 void BuildDefault(CharInfo& out_char_info, u32 index) const;
33 void BuildBase(CharInfo& out_char_info, Gender gender) const;
34 void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
35 void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
36 std::vector<CharInfoElement> GetDefault(SourceFlag source_flag);
37 Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
38 s32& out_index);
39 void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version);
35 40
36private: 41private:
37 const Common::UUID user_id{}; 42 Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
43 SourceFlag source_flag);
44 Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag);
45
38 u64 update_counter{}; 46 u64 update_counter{};
39}; 47};
40 48
diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h
new file mode 100644
index 000000000..021cb76da
--- /dev/null
+++ b/src/core/hle/service/mii/mii_result.h
@@ -0,0 +1,20 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7
8namespace Service::Mii {
9
10constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1};
11constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2};
12constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
13constexpr Result ResultNotFound{ErrorModule::Mii, 4};
14constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
15constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
16constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
17constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
18constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
19
20}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h
new file mode 100644
index 000000000..611ff4f81
--- /dev/null
+++ b/src/core/hle/service/mii/mii_types.h
@@ -0,0 +1,691 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <type_traits>
8
9#include "common/bit_field.h"
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/uuid.h"
13
14namespace Service::Mii {
15
16constexpr u8 MaxHeight = 127;
17constexpr u8 MaxBuild = 127;
18constexpr u8 MaxType = 1;
19constexpr u8 MaxRegionMove = 3;
20constexpr u8 MaxEyeScale = 7;
21constexpr u8 MaxEyeAspect = 6;
22constexpr u8 MaxEyeRotate = 7;
23constexpr u8 MaxEyeX = 12;
24constexpr u8 MaxEyeY = 18;
25constexpr u8 MaxEyebrowScale = 8;
26constexpr u8 MaxEyebrowAspect = 6;
27constexpr u8 MaxEyebrowRotate = 11;
28constexpr u8 MaxEyebrowX = 12;
29constexpr u8 MaxEyebrowY = 18;
30constexpr u8 MaxNoseScale = 8;
31constexpr u8 MaxNoseY = 18;
32constexpr u8 MaxMouthScale = 8;
33constexpr u8 MaxMoutAspect = 6;
34constexpr u8 MaxMouthY = 18;
35constexpr u8 MaxMustacheScale = 8;
36constexpr u8 MasMustacheY = 16;
37constexpr u8 MaxGlassScale = 7;
38constexpr u8 MaxGlassY = 20;
39constexpr u8 MaxMoleScale = 8;
40constexpr u8 MaxMoleX = 16;
41constexpr u8 MaxMoleY = 30;
42constexpr u8 MaxVer3CommonColor = 7;
43constexpr u8 MaxVer3GlassType = 8;
44
45enum class Age : u8 {
46 Young,
47 Normal,
48 Old,
49 All, // Default
50
51 Max = All,
52};
53
54enum class Gender : u8 {
55 Male,
56 Female,
57 All, // Default
58
59 Max = Female,
60};
61
62enum class Race : u8 {
63 Black,
64 White,
65 Asian,
66 All, // Default
67
68 Max = All,
69};
70
71enum class HairType : u8 {
72 NormalLong, // Default
73 NormalShort,
74 NormalMedium,
75 NormalExtraLong,
76 NormalLongBottom,
77 NormalTwoPeaks,
78 PartingLong,
79 FrontLock,
80 PartingShort,
81 PartingExtraLongCurved,
82 PartingExtraLong,
83 PartingMiddleLong,
84 PartingSquared,
85 PartingLongBottom,
86 PeaksTop,
87 PeaksSquared,
88 PartingPeaks,
89 PeaksLongBottom,
90 Peaks,
91 PeaksRounded,
92 PeaksSide,
93 PeaksMedium,
94 PeaksLong,
95 PeaksRoundedLong,
96 PartingFrontPeaks,
97 PartingLongFront,
98 PartingLongRounded,
99 PartingFrontPeaksLong,
100 PartingExtraLongRounded,
101 LongRounded,
102 NormalUnknown1,
103 NormalUnknown2,
104 NormalUnknown3,
105 NormalUnknown4,
106 NormalUnknown5,
107 NormalUnknown6,
108 DreadLocks,
109 PlatedMats,
110 Caps,
111 Afro,
112 PlatedMatsLong,
113 Beanie,
114 Short,
115 ShortTopLongSide,
116 ShortUnknown1,
117 ShortUnknown2,
118 MilitaryParting,
119 Military,
120 ShortUnknown3,
121 ShortUnknown4,
122 ShortUnknown5,
123 ShortUnknown6,
124 NoneTop,
125 None,
126 LongUnknown1,
127 LongUnknown2,
128 LongUnknown3,
129 LongUnknown4,
130 LongUnknown5,
131 LongUnknown6,
132 LongUnknown7,
133 LongUnknown8,
134 LongUnknown9,
135 LongUnknown10,
136 LongUnknown11,
137 LongUnknown12,
138 LongUnknown13,
139 LongUnknown14,
140 LongUnknown15,
141 LongUnknown16,
142 LongUnknown17,
143 LongUnknown18,
144 LongUnknown19,
145 LongUnknown20,
146 LongUnknown21,
147 LongUnknown22,
148 LongUnknown23,
149 LongUnknown24,
150 LongUnknown25,
151 LongUnknown26,
152 LongUnknown27,
153 LongUnknown28,
154 LongUnknown29,
155 LongUnknown30,
156 LongUnknown31,
157 LongUnknown32,
158 LongUnknown33,
159 LongUnknown34,
160 LongUnknown35,
161 LongUnknown36,
162 LongUnknown37,
163 LongUnknown38,
164 LongUnknown39,
165 LongUnknown40,
166 LongUnknown41,
167 LongUnknown42,
168 LongUnknown43,
169 LongUnknown44,
170 LongUnknown45,
171 LongUnknown46,
172 LongUnknown47,
173 LongUnknown48,
174 LongUnknown49,
175 LongUnknown50,
176 LongUnknown51,
177 LongUnknown52,
178 LongUnknown53,
179 LongUnknown54,
180 LongUnknown55,
181 LongUnknown56,
182 LongUnknown57,
183 LongUnknown58,
184 LongUnknown59,
185 LongUnknown60,
186 LongUnknown61,
187 LongUnknown62,
188 LongUnknown63,
189 LongUnknown64,
190 LongUnknown65,
191 LongUnknown66,
192 TwoMediumFrontStrandsOneLongBackPonyTail,
193 TwoFrontStrandsLongBackPonyTail,
194 PartingFrontTwoLongBackPonyTails,
195 TwoFrontStrandsOneLongBackPonyTail,
196 LongBackPonyTail,
197 LongFrontTwoLongBackPonyTails,
198 StrandsTwoShortSidedPonyTails,
199 TwoMediumSidedPonyTails,
200 ShortFrontTwoBackPonyTails,
201 TwoShortSidedPonyTails,
202 TwoLongSidedPonyTails,
203 LongFrontTwoBackPonyTails,
204
205 Max = LongFrontTwoBackPonyTails,
206};
207
208enum class MoleType : u8 {
209 None, // Default
210 OneDot,
211
212 Max = OneDot,
213};
214
215enum class HairFlip : u8 {
216 Left, // Default
217 Right,
218
219 Max = Right,
220};
221
222enum class CommonColor : u8 {
223 // For simplicity common colors aren't listed
224 Max = 99,
225 Count = 100,
226};
227
228enum class FavoriteColor : u8 {
229 Red, // Default
230 Orange,
231 Yellow,
232 LimeGreen,
233 Green,
234 Blue,
235 LightBlue,
236 Pink,
237 Purple,
238 Brown,
239 White,
240 Black,
241
242 Max = Black,
243};
244
245enum class EyeType : u8 {
246 Normal, // Default
247 NormalLash,
248 WhiteLash,
249 WhiteNoBottom,
250 OvalAngledWhite,
251 AngryWhite,
252 DotLashType1,
253 Line,
254 DotLine,
255 OvalWhite,
256 RoundedWhite,
257 NormalShadow,
258 CircleWhite,
259 Circle,
260 CircleWhiteStroke,
261 NormalOvalNoBottom,
262 NormalOvalLarge,
263 NormalRoundedNoBottom,
264 SmallLash,
265 Small,
266 TwoSmall,
267 NormalLongLash,
268 WhiteTwoLashes,
269 WhiteThreeLashes,
270 DotAngry,
271 DotAngled,
272 Oval,
273 SmallWhite,
274 WhiteAngledNoBottom,
275 WhiteAngledNoLeft,
276 SmallWhiteTwoLashes,
277 LeafWhiteLash,
278 WhiteLargeNoBottom,
279 Dot,
280 DotLashType2,
281 DotThreeLashes,
282 WhiteOvalTop,
283 WhiteOvalBottom,
284 WhiteOvalBottomFlat,
285 WhiteOvalTwoLashes,
286 WhiteOvalThreeLashes,
287 WhiteOvalNoBottomTwoLashes,
288 DotWhite,
289 WhiteOvalTopFlat,
290 WhiteThinLeaf,
291 StarThreeLashes,
292 LineTwoLashes,
293 CrowsFeet,
294 WhiteNoBottomFlat,
295 WhiteNoBottomRounded,
296 WhiteSmallBottomLine,
297 WhiteNoBottomLash,
298 WhiteNoPartialBottomLash,
299 WhiteOvalBottomLine,
300 WhiteNoBottomLashTopLine,
301 WhiteNoPartialBottomTwoLashes,
302 NormalTopLine,
303 WhiteOvalLash,
304 RoundTired,
305 WhiteLarge,
306
307 Max = WhiteLarge,
308};
309
310enum class MouthType : u8 {
311 Neutral, // Default
312 NeutralLips,
313 Smile,
314 SmileStroke,
315 SmileTeeth,
316 LipsSmall,
317 LipsLarge,
318 Wave,
319 WaveAngrySmall,
320 NeutralStrokeLarge,
321 TeethSurprised,
322 LipsExtraLarge,
323 LipsUp,
324 NeutralDown,
325 Surprised,
326 TeethMiddle,
327 NeutralStroke,
328 LipsExtraSmall,
329 Malicious,
330 LipsDual,
331 NeutralComma,
332 NeutralUp,
333 TeethLarge,
334 WaveAngry,
335 LipsSexy,
336 SmileInverted,
337 LipsSexyOutline,
338 SmileRounded,
339 LipsTeeth,
340 NeutralOpen,
341 TeethRounded,
342 WaveAngrySmallInverted,
343 NeutralCommaInverted,
344 TeethFull,
345 SmileDownLine,
346 Kiss,
347
348 Max = Kiss,
349};
350
351enum class FontRegion : u8 {
352 Standard, // Default
353 China,
354 Korea,
355 Taiwan,
356
357 Max = Taiwan,
358};
359
360enum class FacelineType : u8 {
361 Sharp, // Default
362 Rounded,
363 SharpRounded,
364 SharpRoundedSmall,
365 Large,
366 LargeRounded,
367 SharpSmall,
368 Flat,
369 Bump,
370 Angular,
371 FlatRounded,
372 AngularSmall,
373
374 Max = AngularSmall,
375};
376
377enum class FacelineColor : u8 {
378 Beige, // Default
379 WarmBeige,
380 Natural,
381 Honey,
382 Chestnut,
383 Porcelain,
384 Ivory,
385 WarmIvory,
386 Almond,
387 Espresso,
388
389 Max = Espresso,
390 Count = Max + 1,
391};
392
393enum class FacelineWrinkle : u8 {
394 None, // Default
395 TearTroughs,
396 FacialPain,
397 Cheeks,
398 Folds,
399 UnderTheEyes,
400 SplitChin,
401 Chin,
402 BrowDroop,
403 MouthFrown,
404 CrowsFeet,
405 FoldsCrowsFrown,
406
407 Max = FoldsCrowsFrown,
408};
409
410enum class FacelineMake : u8 {
411 None, // Default
412 CheekPorcelain,
413 CheekNatural,
414 EyeShadowBlue,
415 CheekBlushPorcelain,
416 CheekBlushNatural,
417 CheekPorcelainEyeShadowBlue,
418 CheekPorcelainEyeShadowNatural,
419 CheekBlushPorcelainEyeShadowEspresso,
420 Freckles,
421 LionsManeBeard,
422 StubbleBeard,
423
424 Max = StubbleBeard,
425};
426
427enum class EyebrowType : u8 {
428 FlatAngledLarge, // Default
429 LowArchRoundedThin,
430 SoftAngledLarge,
431 MediumArchRoundedThin,
432 RoundedMedium,
433 LowArchMedium,
434 RoundedThin,
435 UpThin,
436 MediumArchRoundedMedium,
437 RoundedLarge,
438 UpLarge,
439 FlatAngledLargeInverted,
440 MediumArchFlat,
441 AngledThin,
442 HorizontalLarge,
443 HighArchFlat,
444 Flat,
445 MediumArchLarge,
446 LowArchThin,
447 RoundedThinInverted,
448 HighArchLarge,
449 Hairy,
450 Dotted,
451 None,
452
453 Max = None,
454};
455
456enum class NoseType : u8 {
457 Normal, // Default
458 Rounded,
459 Dot,
460 Arrow,
461 Roman,
462 Triangle,
463 Button,
464 RoundedInverted,
465 Potato,
466 Grecian,
467 Snub,
468 Aquiline,
469 ArrowLeft,
470 RoundedLarge,
471 Hooked,
472 Fat,
473 Droopy,
474 ArrowLarge,
475
476 Max = ArrowLarge,
477};
478
479enum class BeardType : u8 {
480 None,
481 Goatee,
482 GoateeLong,
483 LionsManeLong,
484 LionsMane,
485 Full,
486
487 Min = Goatee,
488 Max = Full,
489};
490
491enum class MustacheType : u8 {
492 None,
493 Walrus,
494 Pencil,
495 Horseshoe,
496 Normal,
497 Toothbrush,
498
499 Min = Walrus,
500 Max = Toothbrush,
501};
502
503enum class GlassType : u8 {
504 None,
505 Oval,
506 Wayfarer,
507 Rectangle,
508 TopRimless,
509 Rounded,
510 Oversized,
511 CatEye,
512 Square,
513 BottomRimless,
514 SemiOpaqueRounded,
515 SemiOpaqueCatEye,
516 SemiOpaqueOval,
517 SemiOpaqueRectangle,
518 SemiOpaqueAviator,
519 OpaqueRounded,
520 OpaqueCatEye,
521 OpaqueOval,
522 OpaqueRectangle,
523 OpaqueAviator,
524
525 Max = OpaqueAviator,
526 Count = Max + 1,
527};
528
529enum class BeardAndMustacheFlag : u32 {
530 Beard = 1,
531 Mustache,
532 All = Beard | Mustache,
533};
534DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
535
536enum class Source : u32 {
537 Database = 0,
538 Default = 1,
539 Account = 2,
540 Friend = 3,
541};
542
543enum class SourceFlag : u32 {
544 None = 0,
545 Database = 1 << 0,
546 Default = 1 << 1,
547};
548DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
549
550enum class ValidationResult : u32 {
551 NoErrors = 0x0,
552 InvalidBeardColor = 0x1,
553 InvalidBeardType = 0x2,
554 InvalidBuild = 0x3,
555 InvalidEyeAspect = 0x4,
556 InvalidEyeColor = 0x5,
557 InvalidEyeRotate = 0x6,
558 InvalidEyeScale = 0x7,
559 InvalidEyeType = 0x8,
560 InvalidEyeX = 0x9,
561 InvalidEyeY = 0xa,
562 InvalidEyebrowAspect = 0xb,
563 InvalidEyebrowColor = 0xc,
564 InvalidEyebrowRotate = 0xd,
565 InvalidEyebrowScale = 0xe,
566 InvalidEyebrowType = 0xf,
567 InvalidEyebrowX = 0x10,
568 InvalidEyebrowY = 0x11,
569 InvalidFacelineColor = 0x12,
570 InvalidFacelineMake = 0x13,
571 InvalidFacelineWrinkle = 0x14,
572 InvalidFacelineType = 0x15,
573 InvalidColor = 0x16,
574 InvalidFont = 0x17,
575 InvalidGender = 0x18,
576 InvalidGlassColor = 0x19,
577 InvalidGlassScale = 0x1a,
578 InvalidGlassType = 0x1b,
579 InvalidGlassY = 0x1c,
580 InvalidHairColor = 0x1d,
581 InvalidHairFlip = 0x1e,
582 InvalidHairType = 0x1f,
583 InvalidHeight = 0x20,
584 InvalidMoleScale = 0x21,
585 InvalidMoleType = 0x22,
586 InvalidMoleX = 0x23,
587 InvalidMoleY = 0x24,
588 InvalidMouthAspect = 0x25,
589 InvalidMouthColor = 0x26,
590 InvalidMouthScale = 0x27,
591 InvalidMouthType = 0x28,
592 InvalidMouthY = 0x29,
593 InvalidMustacheScale = 0x2a,
594 InvalidMustacheType = 0x2b,
595 InvalidMustacheY = 0x2c,
596 InvalidNoseScale = 0x2e,
597 InvalidNoseType = 0x2f,
598 InvalidNoseY = 0x30,
599 InvalidRegionMove = 0x31,
600 InvalidCreateId = 0x32,
601 InvalidName = 0x33,
602 InvalidType = 0x35,
603};
604
605struct Nickname {
606 static constexpr std::size_t MaxNameSize = 10;
607 std::array<char16_t, MaxNameSize> data;
608
609 // Checks for null or dirty strings
610 bool IsValid() const {
611 if (data[0] == 0) {
612 return false;
613 }
614
615 std::size_t index = 1;
616 while (data[index] != 0) {
617 index++;
618 }
619 while (index < MaxNameSize && data[index] == 0) {
620 index++;
621 }
622 return index == MaxNameSize;
623 }
624};
625static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size");
626
627struct DefaultMii {
628 u32 face_type{};
629 u32 face_color{};
630 u32 face_wrinkle{};
631 u32 face_makeup{};
632 u32 hair_type{};
633 u32 hair_color{};
634 u32 hair_flip{};
635 u32 eye_type{};
636 u32 eye_color{};
637 u32 eye_scale{};
638 u32 eye_aspect{};
639 u32 eye_rotate{};
640 u32 eye_x{};
641 u32 eye_y{};
642 u32 eyebrow_type{};
643 u32 eyebrow_color{};
644 u32 eyebrow_scale{};
645 u32 eyebrow_aspect{};
646 u32 eyebrow_rotate{};
647 u32 eyebrow_x{};
648 u32 eyebrow_y{};
649 u32 nose_type{};
650 u32 nose_scale{};
651 u32 nose_y{};
652 u32 mouth_type{};
653 u32 mouth_color{};
654 u32 mouth_scale{};
655 u32 mouth_aspect{};
656 u32 mouth_y{};
657 u32 mustache_type{};
658 u32 beard_type{};
659 u32 beard_color{};
660 u32 mustache_scale{};
661 u32 mustache_y{};
662 u32 glasses_type{};
663 u32 glasses_color{};
664 u32 glasses_scale{};
665 u32 glasses_y{};
666 u32 mole_type{};
667 u32 mole_scale{};
668 u32 mole_x{};
669 u32 mole_y{};
670 u32 height{};
671 u32 weight{};
672 u32 gender{};
673 u32 favorite_color{};
674 u32 region_move{};
675 u32 font_region{};
676 u32 type{};
677 Nickname nickname;
678};
679static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size.");
680
681struct DatabaseSessionMetadata {
682 u32 interface_version;
683 u32 magic;
684 u64 update_counter;
685
686 bool IsInterfaceVersionSupported(u32 version) const {
687 return version <= interface_version;
688 }
689};
690
691} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h
new file mode 100644
index 000000000..ddb544c23
--- /dev/null
+++ b/src/core/hle/service/mii/mii_util.h
@@ -0,0 +1,59 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <random>
7#include <span>
8
9#include "common/common_types.h"
10#include "common/swap.h"
11#include "common/uuid.h"
12#include "core/hle/service/mii/mii_types.h"
13
14namespace Service::Mii {
15class MiiUtil {
16public:
17 static u16 CalculateCrc16(const void* data, std::size_t size) {
18 s32 crc{};
19 for (std::size_t i = 0; i < size; i++) {
20 crc ^= static_cast<const u8*>(data)[i] << 8;
21 for (std::size_t j = 0; j < 8; j++) {
22 crc <<= 1;
23 if ((crc & 0x10000) != 0) {
24 crc = (crc ^ 0x1021) & 0xFFFF;
25 }
26 }
27 }
28 return Common::swap16(static_cast<u16>(crc));
29 }
30
31 static Common::UUID MakeCreateId() {
32 return Common::UUID::MakeRandomRFC4122V4();
33 }
34
35 static Common::UUID GetDeviceId() {
36 // This should be nn::settings::detail::GetMiiAuthorId()
37 return Common::UUID::MakeDefault();
38 }
39
40 template <typename T>
41 static T GetRandomValue(T min, T max) {
42 std::random_device device;
43 std::mt19937 gen(device());
44 std::uniform_int_distribution<u64> distribution(static_cast<u64>(min),
45 static_cast<u64>(max));
46 return static_cast<T>(distribution(gen));
47 }
48
49 template <typename T>
50 static T GetRandomValue(T max) {
51 return GetRandomValue<T>({}, max);
52 }
53
54 static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) {
55 // TODO: This function needs to check against the font tables
56 return true;
57 }
58};
59} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h
deleted file mode 100644
index c2bec68d4..000000000
--- a/src/core/hle/service/mii/raw_data.h
+++ /dev/null
@@ -1,26 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "core/hle/service/mii/types.h"
9
10namespace Service::Mii::RawData {
11
12extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii;
13extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline;
14extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor;
15extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle;
16extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup;
17extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType;
18extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor;
19extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType;
20extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor;
21extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType;
22extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType;
23extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType;
24extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType;
25
26} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h
deleted file mode 100644
index c48d08d79..000000000
--- a/src/core/hle/service/mii/types.h
+++ /dev/null
@@ -1,553 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <type_traits>
8
9#include "common/bit_field.h"
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/uuid.h"
13
14namespace Service::Mii {
15
16enum class Age : u32 {
17 Young,
18 Normal,
19 Old,
20 All,
21};
22
23enum class BeardType : u32 {
24 None,
25 Beard1,
26 Beard2,
27 Beard3,
28 Beard4,
29 Beard5,
30};
31
32enum class BeardAndMustacheFlag : u32 {
33 Beard = 1,
34 Mustache,
35 All = Beard | Mustache,
36};
37DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
38
39enum class FontRegion : u32 {
40 Standard,
41 China,
42 Korea,
43 Taiwan,
44};
45
46enum class Gender : u32 {
47 Male,
48 Female,
49 All,
50 Maximum = Female,
51};
52
53enum class HairFlip : u32 {
54 Left,
55 Right,
56 Maximum = Right,
57};
58
59enum class MustacheType : u32 {
60 None,
61 Mustache1,
62 Mustache2,
63 Mustache3,
64 Mustache4,
65 Mustache5,
66};
67
68enum class Race : u32 {
69 Black,
70 White,
71 Asian,
72 All,
73};
74
75enum class Source : u32 {
76 Database = 0,
77 Default = 1,
78 Account = 2,
79 Friend = 3,
80};
81
82enum class SourceFlag : u32 {
83 None = 0,
84 Database = 1 << 0,
85 Default = 1 << 1,
86};
87DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
88
89// nn::mii::CharInfo
90struct CharInfo {
91 Common::UUID uuid;
92 std::array<char16_t, 11> name;
93 u8 font_region;
94 u8 favorite_color;
95 u8 gender;
96 u8 height;
97 u8 build;
98 u8 type;
99 u8 region_move;
100 u8 faceline_type;
101 u8 faceline_color;
102 u8 faceline_wrinkle;
103 u8 faceline_make;
104 u8 hair_type;
105 u8 hair_color;
106 u8 hair_flip;
107 u8 eye_type;
108 u8 eye_color;
109 u8 eye_scale;
110 u8 eye_aspect;
111 u8 eye_rotate;
112 u8 eye_x;
113 u8 eye_y;
114 u8 eyebrow_type;
115 u8 eyebrow_color;
116 u8 eyebrow_scale;
117 u8 eyebrow_aspect;
118 u8 eyebrow_rotate;
119 u8 eyebrow_x;
120 u8 eyebrow_y;
121 u8 nose_type;
122 u8 nose_scale;
123 u8 nose_y;
124 u8 mouth_type;
125 u8 mouth_color;
126 u8 mouth_scale;
127 u8 mouth_aspect;
128 u8 mouth_y;
129 u8 beard_color;
130 u8 beard_type;
131 u8 mustache_type;
132 u8 mustache_scale;
133 u8 mustache_y;
134 u8 glasses_type;
135 u8 glasses_color;
136 u8 glasses_scale;
137 u8 glasses_y;
138 u8 mole_type;
139 u8 mole_scale;
140 u8 mole_x;
141 u8 mole_y;
142 u8 padding;
143};
144static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
145static_assert(std::has_unique_object_representations_v<CharInfo>,
146 "All bits of CharInfo must contribute to its value.");
147
148#pragma pack(push, 4)
149
150struct MiiInfoElement {
151 MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
152
153 CharInfo info{};
154 Source source{};
155};
156static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
157
158struct MiiStoreBitFields {
159 union {
160 u32 word_0{};
161
162 BitField<0, 8, u32> hair_type;
163 BitField<8, 7, u32> height;
164 BitField<15, 1, u32> mole_type;
165 BitField<16, 7, u32> build;
166 BitField<23, 1, HairFlip> hair_flip;
167 BitField<24, 7, u32> hair_color;
168 BitField<31, 1, u32> type;
169 };
170
171 union {
172 u32 word_1{};
173
174 BitField<0, 7, u32> eye_color;
175 BitField<7, 1, Gender> gender;
176 BitField<8, 7, u32> eyebrow_color;
177 BitField<16, 7, u32> mouth_color;
178 BitField<24, 7, u32> beard_color;
179 };
180
181 union {
182 u32 word_2{};
183
184 BitField<0, 7, u32> glasses_color;
185 BitField<8, 6, u32> eye_type;
186 BitField<14, 2, u32> region_move;
187 BitField<16, 6, u32> mouth_type;
188 BitField<22, 2, FontRegion> font_region;
189 BitField<24, 5, u32> eye_y;
190 BitField<29, 3, u32> glasses_scale;
191 };
192
193 union {
194 u32 word_3{};
195
196 BitField<0, 5, u32> eyebrow_type;
197 BitField<5, 3, MustacheType> mustache_type;
198 BitField<8, 5, u32> nose_type;
199 BitField<13, 3, BeardType> beard_type;
200 BitField<16, 5, u32> nose_y;
201 BitField<21, 3, u32> mouth_aspect;
202 BitField<24, 5, u32> mouth_y;
203 BitField<29, 3, u32> eyebrow_aspect;
204 };
205
206 union {
207 u32 word_4{};
208
209 BitField<0, 5, u32> mustache_y;
210 BitField<5, 3, u32> eye_rotate;
211 BitField<8, 5, u32> glasses_y;
212 BitField<13, 3, u32> eye_aspect;
213 BitField<16, 5, u32> mole_x;
214 BitField<21, 3, u32> eye_scale;
215 BitField<24, 5, u32> mole_y;
216 };
217
218 union {
219 u32 word_5{};
220
221 BitField<0, 5, u32> glasses_type;
222 BitField<8, 4, u32> favorite_color;
223 BitField<12, 4, u32> faceline_type;
224 BitField<16, 4, u32> faceline_color;
225 BitField<20, 4, u32> faceline_wrinkle;
226 BitField<24, 4, u32> faceline_makeup;
227 BitField<28, 4, u32> eye_x;
228 };
229
230 union {
231 u32 word_6{};
232
233 BitField<0, 4, u32> eyebrow_scale;
234 BitField<4, 4, u32> eyebrow_rotate;
235 BitField<8, 4, u32> eyebrow_x;
236 BitField<12, 4, u32> eyebrow_y;
237 BitField<16, 4, u32> nose_scale;
238 BitField<20, 4, u32> mouth_scale;
239 BitField<24, 4, u32> mustache_scale;
240 BitField<28, 4, u32> mole_scale;
241 };
242};
243static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
244static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
245 "MiiStoreBitFields is not trivially copyable.");
246
247// This is nn::mii::Ver3StoreData
248// Based on citra HLE::Applets::MiiData and PretendoNetwork.
249// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
250// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
251struct Ver3StoreData {
252 u8 version;
253 union {
254 u8 raw;
255
256 BitField<0, 1, u8> allow_copying;
257 BitField<1, 1, u8> profanity_flag;
258 BitField<2, 2, u8> region_lock;
259 BitField<4, 2, u8> character_set;
260 } region_information;
261 u16_be mii_id;
262 u64_be system_id;
263 u32_be specialness_and_creation_date;
264 std::array<u8, 0x6> creator_mac;
265 u16_be padding;
266 union {
267 u16 raw;
268
269 BitField<0, 1, u16> gender;
270 BitField<1, 4, u16> birth_month;
271 BitField<5, 5, u16> birth_day;
272 BitField<10, 4, u16> favorite_color;
273 BitField<14, 1, u16> favorite;
274 } mii_information;
275 std::array<char16_t, 0xA> mii_name;
276 u8 height;
277 u8 build;
278 union {
279 u8 raw;
280
281 BitField<0, 1, u8> disable_sharing;
282 BitField<1, 4, u8> face_shape;
283 BitField<5, 3, u8> skin_color;
284 } appearance_bits1;
285 union {
286 u8 raw;
287
288 BitField<0, 4, u8> wrinkles;
289 BitField<4, 4, u8> makeup;
290 } appearance_bits2;
291 u8 hair_style;
292 union {
293 u8 raw;
294
295 BitField<0, 3, u8> hair_color;
296 BitField<3, 1, u8> flip_hair;
297 } appearance_bits3;
298 union {
299 u32 raw;
300
301 BitField<0, 6, u32> eye_type;
302 BitField<6, 3, u32> eye_color;
303 BitField<9, 4, u32> eye_scale;
304 BitField<13, 3, u32> eye_vertical_stretch;
305 BitField<16, 5, u32> eye_rotation;
306 BitField<21, 4, u32> eye_spacing;
307 BitField<25, 5, u32> eye_y_position;
308 } appearance_bits4;
309 union {
310 u32 raw;
311
312 BitField<0, 5, u32> eyebrow_style;
313 BitField<5, 3, u32> eyebrow_color;
314 BitField<8, 4, u32> eyebrow_scale;
315 BitField<12, 3, u32> eyebrow_yscale;
316 BitField<16, 4, u32> eyebrow_rotation;
317 BitField<21, 4, u32> eyebrow_spacing;
318 BitField<25, 5, u32> eyebrow_y_position;
319 } appearance_bits5;
320 union {
321 u16 raw;
322
323 BitField<0, 5, u16> nose_type;
324 BitField<5, 4, u16> nose_scale;
325 BitField<9, 5, u16> nose_y_position;
326 } appearance_bits6;
327 union {
328 u16 raw;
329
330 BitField<0, 6, u16> mouth_type;
331 BitField<6, 3, u16> mouth_color;
332 BitField<9, 4, u16> mouth_scale;
333 BitField<13, 3, u16> mouth_horizontal_stretch;
334 } appearance_bits7;
335 union {
336 u8 raw;
337
338 BitField<0, 5, u8> mouth_y_position;
339 BitField<5, 3, u8> mustache_type;
340 } appearance_bits8;
341 u8 allow_copying;
342 union {
343 u16 raw;
344
345 BitField<0, 3, u16> bear_type;
346 BitField<3, 3, u16> facial_hair_color;
347 BitField<6, 4, u16> mustache_scale;
348 BitField<10, 5, u16> mustache_y_position;
349 } appearance_bits9;
350 union {
351 u16 raw;
352
353 BitField<0, 4, u16> glasses_type;
354 BitField<4, 3, u16> glasses_color;
355 BitField<7, 4, u16> glasses_scale;
356 BitField<11, 5, u16> glasses_y_position;
357 } appearance_bits10;
358 union {
359 u16 raw;
360
361 BitField<0, 1, u16> mole_enabled;
362 BitField<1, 4, u16> mole_scale;
363 BitField<5, 5, u16> mole_x_position;
364 BitField<10, 5, u16> mole_y_position;
365 } appearance_bits11;
366
367 std::array<u16_le, 0xA> author_name;
368 INSERT_PADDING_BYTES(0x2);
369 u16_be crc;
370};
371static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
372
373struct NfpStoreDataExtension {
374 u8 faceline_color;
375 u8 hair_color;
376 u8 eye_color;
377 u8 eyebrow_color;
378 u8 mouth_color;
379 u8 beard_color;
380 u8 glass_color;
381 u8 glass_type;
382};
383static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
384
385constexpr std::array<u8, 0x10> Ver3FacelineColorTable{
386 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
387};
388
389constexpr std::array<u8, 100> Ver3HairColorTable{
390 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0,
391 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
392 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4,
393 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5,
394 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7,
395 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4,
396};
397
398constexpr std::array<u8, 100> Ver3EyeColorTable{
399 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4,
400 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
401 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4,
402 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
403 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2,
404 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
405};
406
407constexpr std::array<u8, 100> Ver3MouthlineColorTable{
408 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
409 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
410 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
411 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
412 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3,
413 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3,
414};
415
416constexpr std::array<u8, 100> Ver3GlassColorTable{
417 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3,
418 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
419 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
420 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5,
421 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4,
422 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
423};
424
425constexpr std::array<u8, 20> Ver3GlassTypeTable{
426 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1,
427 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7,
428};
429
430struct MiiStoreData {
431 using Name = std::array<char16_t, 10>;
432
433 MiiStoreData();
434 MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields,
435 const Common::UUID& user_id);
436
437 // This corresponds to the above structure MiiStoreBitFields. I did it like this because the
438 // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
439 // not suitable for our uses.
440 struct {
441 std::array<u8, 0x1C> data{};
442 static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
443
444 Name name{};
445 Common::UUID uuid{};
446 } data;
447
448 u16 data_crc{};
449 u16 device_crc{};
450};
451static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
452
453struct MiiStoreDataElement {
454 MiiStoreData data{};
455 Source source{};
456};
457static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
458
459struct MiiDatabase {
460 u32 magic{}; // 'NFDB'
461 std::array<MiiStoreData, 0x64> miis{};
462 INSERT_PADDING_BYTES(1);
463 u8 count{};
464 u16 crc{};
465};
466static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
467
468struct RandomMiiValues {
469 std::array<u8, 0xbc> values{};
470};
471static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
472
473struct RandomMiiData4 {
474 Gender gender{};
475 Age age{};
476 Race race{};
477 u32 values_count{};
478 std::array<u32, 47> values{};
479};
480static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
481
482struct RandomMiiData3 {
483 u32 arg_1;
484 u32 arg_2;
485 u32 values_count;
486 std::array<u32, 47> values{};
487};
488static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
489
490struct RandomMiiData2 {
491 u32 arg_1;
492 u32 values_count;
493 std::array<u32, 47> values{};
494};
495static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
496
497struct DefaultMii {
498 u32 face_type{};
499 u32 face_color{};
500 u32 face_wrinkle{};
501 u32 face_makeup{};
502 u32 hair_type{};
503 u32 hair_color{};
504 u32 hair_flip{};
505 u32 eye_type{};
506 u32 eye_color{};
507 u32 eye_scale{};
508 u32 eye_aspect{};
509 u32 eye_rotate{};
510 u32 eye_x{};
511 u32 eye_y{};
512 u32 eyebrow_type{};
513 u32 eyebrow_color{};
514 u32 eyebrow_scale{};
515 u32 eyebrow_aspect{};
516 u32 eyebrow_rotate{};
517 u32 eyebrow_x{};
518 u32 eyebrow_y{};
519 u32 nose_type{};
520 u32 nose_scale{};
521 u32 nose_y{};
522 u32 mouth_type{};
523 u32 mouth_color{};
524 u32 mouth_scale{};
525 u32 mouth_aspect{};
526 u32 mouth_y{};
527 u32 mustache_type{};
528 u32 beard_type{};
529 u32 beard_color{};
530 u32 mustache_scale{};
531 u32 mustache_y{};
532 u32 glasses_type{};
533 u32 glasses_color{};
534 u32 glasses_scale{};
535 u32 glasses_y{};
536 u32 mole_type{};
537 u32 mole_scale{};
538 u32 mole_x{};
539 u32 mole_y{};
540 u32 height{};
541 u32 weight{};
542 Gender gender{};
543 u32 favorite_color{};
544 u32 region{};
545 FontRegion font_region{};
546 u32 type{};
547 INSERT_PADDING_WORDS(5);
548};
549static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size.");
550
551#pragma pack(pop)
552
553} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp
new file mode 100644
index 000000000..bb948c628
--- /dev/null
+++ b/src/core/hle/service/mii/types/char_info.cpp
@@ -0,0 +1,482 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/mii/types/char_info.h"
5#include "core/hle/service/mii/types/store_data.h"
6
7namespace Service::Mii {
8
9void CharInfo::SetFromStoreData(const StoreData& store_data) {
10 name = store_data.GetNickname();
11 null_terminator = '\0';
12 create_id = store_data.GetCreateId();
13 font_region = store_data.GetFontRegion();
14 favorite_color = store_data.GetFavoriteColor();
15 gender = store_data.GetGender();
16 height = store_data.GetHeight();
17 build = store_data.GetBuild();
18 type = store_data.GetType();
19 region_move = store_data.GetRegionMove();
20 faceline_type = store_data.GetFacelineType();
21 faceline_color = store_data.GetFacelineColor();
22 faceline_wrinkle = store_data.GetFacelineWrinkle();
23 faceline_make = store_data.GetFacelineMake();
24 hair_type = store_data.GetHairType();
25 hair_color = store_data.GetHairColor();
26 hair_flip = store_data.GetHairFlip();
27 eye_type = store_data.GetEyeType();
28 eye_color = store_data.GetEyeColor();
29 eye_scale = store_data.GetEyeScale();
30 eye_aspect = store_data.GetEyeAspect();
31 eye_rotate = store_data.GetEyeRotate();
32 eye_x = store_data.GetEyeX();
33 eye_y = store_data.GetEyeY();
34 eyebrow_type = store_data.GetEyebrowType();
35 eyebrow_color = store_data.GetEyebrowColor();
36 eyebrow_scale = store_data.GetEyebrowScale();
37 eyebrow_aspect = store_data.GetEyebrowAspect();
38 eyebrow_rotate = store_data.GetEyebrowRotate();
39 eyebrow_x = store_data.GetEyebrowX();
40 eyebrow_y = store_data.GetEyebrowY();
41 nose_type = store_data.GetNoseType();
42 nose_scale = store_data.GetNoseScale();
43 nose_y = store_data.GetNoseY();
44 mouth_type = store_data.GetMouthType();
45 mouth_color = store_data.GetMouthColor();
46 mouth_scale = store_data.GetMouthScale();
47 mouth_aspect = store_data.GetMouthAspect();
48 mouth_y = store_data.GetMouthY();
49 beard_color = store_data.GetBeardColor();
50 beard_type = store_data.GetBeardType();
51 mustache_type = store_data.GetMustacheType();
52 mustache_scale = store_data.GetMustacheScale();
53 mustache_y = store_data.GetMustacheY();
54 glass_type = store_data.GetGlassType();
55 glass_color = store_data.GetGlassColor();
56 glass_scale = store_data.GetGlassScale();
57 glass_y = store_data.GetGlassY();
58 mole_type = store_data.GetMoleType();
59 mole_scale = store_data.GetMoleScale();
60 mole_x = store_data.GetMoleX();
61 mole_y = store_data.GetMoleY();
62 padding = '\0';
63}
64
65ValidationResult CharInfo::Verify() const {
66 if (!create_id.IsValid()) {
67 return ValidationResult::InvalidCreateId;
68 }
69 if (!name.IsValid()) {
70 return ValidationResult::InvalidName;
71 }
72 if (font_region > FontRegion::Max) {
73 return ValidationResult::InvalidFont;
74 }
75 if (favorite_color > FavoriteColor::Max) {
76 return ValidationResult::InvalidColor;
77 }
78 if (gender > Gender::Max) {
79 return ValidationResult::InvalidGender;
80 }
81 if (height > MaxHeight) {
82 return ValidationResult::InvalidHeight;
83 }
84 if (build > MaxBuild) {
85 return ValidationResult::InvalidBuild;
86 }
87 if (type > MaxType) {
88 return ValidationResult::InvalidType;
89 }
90 if (region_move > MaxRegionMove) {
91 return ValidationResult::InvalidRegionMove;
92 }
93 if (faceline_type > FacelineType::Max) {
94 return ValidationResult::InvalidFacelineType;
95 }
96 if (faceline_color > FacelineColor::Max) {
97 return ValidationResult::InvalidFacelineColor;
98 }
99 if (faceline_wrinkle > FacelineWrinkle::Max) {
100 return ValidationResult::InvalidFacelineWrinkle;
101 }
102 if (faceline_make > FacelineMake::Max) {
103 return ValidationResult::InvalidFacelineMake;
104 }
105 if (hair_type > HairType::Max) {
106 return ValidationResult::InvalidHairType;
107 }
108 if (hair_color > CommonColor::Max) {
109 return ValidationResult::InvalidHairColor;
110 }
111 if (hair_flip > HairFlip::Max) {
112 return ValidationResult::InvalidHairFlip;
113 }
114 if (eye_type > EyeType::Max) {
115 return ValidationResult::InvalidEyeType;
116 }
117 if (eye_color > CommonColor::Max) {
118 return ValidationResult::InvalidEyeColor;
119 }
120 if (eye_scale > MaxEyeScale) {
121 return ValidationResult::InvalidEyeScale;
122 }
123 if (eye_aspect > MaxEyeAspect) {
124 return ValidationResult::InvalidEyeAspect;
125 }
126 if (eye_rotate > MaxEyeX) {
127 return ValidationResult::InvalidEyeRotate;
128 }
129 if (eye_x > MaxEyeX) {
130 return ValidationResult::InvalidEyeX;
131 }
132 if (eye_y > MaxEyeY) {
133 return ValidationResult::InvalidEyeY;
134 }
135 if (eyebrow_type > EyebrowType::Max) {
136 return ValidationResult::InvalidEyebrowType;
137 }
138 if (eyebrow_color > CommonColor::Max) {
139 return ValidationResult::InvalidEyebrowColor;
140 }
141 if (eyebrow_scale > MaxEyebrowScale) {
142 return ValidationResult::InvalidEyebrowScale;
143 }
144 if (eyebrow_aspect > MaxEyebrowAspect) {
145 return ValidationResult::InvalidEyebrowAspect;
146 }
147 if (eyebrow_rotate > MaxEyebrowRotate) {
148 return ValidationResult::InvalidEyebrowRotate;
149 }
150 if (eyebrow_x > MaxEyebrowX) {
151 return ValidationResult::InvalidEyebrowX;
152 }
153 if (eyebrow_y > MaxEyebrowY) {
154 return ValidationResult::InvalidEyebrowY;
155 }
156 if (nose_type > NoseType::Max) {
157 return ValidationResult::InvalidNoseType;
158 }
159 if (nose_scale > MaxNoseScale) {
160 return ValidationResult::InvalidNoseScale;
161 }
162 if (nose_y > MaxNoseY) {
163 return ValidationResult::InvalidNoseY;
164 }
165 if (mouth_type > MouthType::Max) {
166 return ValidationResult::InvalidMouthType;
167 }
168 if (mouth_color > CommonColor::Max) {
169 return ValidationResult::InvalidMouthColor;
170 }
171 if (mouth_scale > MaxMouthScale) {
172 return ValidationResult::InvalidMouthScale;
173 }
174 if (mouth_aspect > MaxMoutAspect) {
175 return ValidationResult::InvalidMouthAspect;
176 }
177 if (mouth_y > MaxMouthY) {
178 return ValidationResult::InvalidMoleY;
179 }
180 if (beard_color > CommonColor::Max) {
181 return ValidationResult::InvalidBeardColor;
182 }
183 if (beard_type > BeardType::Max) {
184 return ValidationResult::InvalidBeardType;
185 }
186 if (mustache_type > MustacheType::Max) {
187 return ValidationResult::InvalidMustacheType;
188 }
189 if (mustache_scale > MaxMustacheScale) {
190 return ValidationResult::InvalidMustacheScale;
191 }
192 if (mustache_y > MasMustacheY) {
193 return ValidationResult::InvalidMustacheY;
194 }
195 if (glass_type > GlassType::Max) {
196 return ValidationResult::InvalidGlassType;
197 }
198 if (glass_color > CommonColor::Max) {
199 return ValidationResult::InvalidGlassColor;
200 }
201 if (glass_scale > MaxGlassScale) {
202 return ValidationResult::InvalidGlassScale;
203 }
204 if (glass_y > MaxGlassY) {
205 return ValidationResult::InvalidGlassY;
206 }
207 if (mole_type > MoleType::Max) {
208 return ValidationResult::InvalidMoleType;
209 }
210 if (mole_scale > MaxMoleScale) {
211 return ValidationResult::InvalidMoleScale;
212 }
213 if (mole_x > MaxMoleX) {
214 return ValidationResult::InvalidMoleX;
215 }
216 if (mole_y > MaxMoleY) {
217 return ValidationResult::InvalidMoleY;
218 }
219 return ValidationResult::NoErrors;
220}
221
222Common::UUID CharInfo::GetCreateId() const {
223 return create_id;
224}
225
226Nickname CharInfo::GetNickname() const {
227 return name;
228}
229
230FontRegion CharInfo::GetFontRegion() const {
231 return font_region;
232}
233
234FavoriteColor CharInfo::GetFavoriteColor() const {
235 return favorite_color;
236}
237
238Gender CharInfo::GetGender() const {
239 return gender;
240}
241
242u8 CharInfo::GetHeight() const {
243 return height;
244}
245
246u8 CharInfo::GetBuild() const {
247 return build;
248}
249
250u8 CharInfo::GetType() const {
251 return type;
252}
253
254u8 CharInfo::GetRegionMove() const {
255 return region_move;
256}
257
258FacelineType CharInfo::GetFacelineType() const {
259 return faceline_type;
260}
261
262FacelineColor CharInfo::GetFacelineColor() const {
263 return faceline_color;
264}
265
266FacelineWrinkle CharInfo::GetFacelineWrinkle() const {
267 return faceline_wrinkle;
268}
269
270FacelineMake CharInfo::GetFacelineMake() const {
271 return faceline_make;
272}
273
274HairType CharInfo::GetHairType() const {
275 return hair_type;
276}
277
278CommonColor CharInfo::GetHairColor() const {
279 return hair_color;
280}
281
282HairFlip CharInfo::GetHairFlip() const {
283 return hair_flip;
284}
285
286EyeType CharInfo::GetEyeType() const {
287 return eye_type;
288}
289
290CommonColor CharInfo::GetEyeColor() const {
291 return eye_color;
292}
293
294u8 CharInfo::GetEyeScale() const {
295 return eye_scale;
296}
297
298u8 CharInfo::GetEyeAspect() const {
299 return eye_aspect;
300}
301
302u8 CharInfo::GetEyeRotate() const {
303 return eye_rotate;
304}
305
306u8 CharInfo::GetEyeX() const {
307 return eye_x;
308}
309
310u8 CharInfo::GetEyeY() const {
311 return eye_y;
312}
313
314EyebrowType CharInfo::GetEyebrowType() const {
315 return eyebrow_type;
316}
317
318CommonColor CharInfo::GetEyebrowColor() const {
319 return eyebrow_color;
320}
321
322u8 CharInfo::GetEyebrowScale() const {
323 return eyebrow_scale;
324}
325
326u8 CharInfo::GetEyebrowAspect() const {
327 return eyebrow_aspect;
328}
329
330u8 CharInfo::GetEyebrowRotate() const {
331 return eyebrow_rotate;
332}
333
334u8 CharInfo::GetEyebrowX() const {
335 return eyebrow_x;
336}
337
338u8 CharInfo::GetEyebrowY() const {
339 return eyebrow_y;
340}
341
342NoseType CharInfo::GetNoseType() const {
343 return nose_type;
344}
345
346u8 CharInfo::GetNoseScale() const {
347 return nose_scale;
348}
349
350u8 CharInfo::GetNoseY() const {
351 return nose_y;
352}
353
354MouthType CharInfo::GetMouthType() const {
355 return mouth_type;
356}
357
358CommonColor CharInfo::GetMouthColor() const {
359 return mouth_color;
360}
361
362u8 CharInfo::GetMouthScale() const {
363 return mouth_scale;
364}
365
366u8 CharInfo::GetMouthAspect() const {
367 return mouth_aspect;
368}
369
370u8 CharInfo::GetMouthY() const {
371 return mouth_y;
372}
373
374CommonColor CharInfo::GetBeardColor() const {
375 return beard_color;
376}
377
378BeardType CharInfo::GetBeardType() const {
379 return beard_type;
380}
381
382MustacheType CharInfo::GetMustacheType() const {
383 return mustache_type;
384}
385
386u8 CharInfo::GetMustacheScale() const {
387 return mustache_scale;
388}
389
390u8 CharInfo::GetMustacheY() const {
391 return mustache_y;
392}
393
394GlassType CharInfo::GetGlassType() const {
395 return glass_type;
396}
397
398CommonColor CharInfo::GetGlassColor() const {
399 return glass_color;
400}
401
402u8 CharInfo::GetGlassScale() const {
403 return glass_scale;
404}
405
406u8 CharInfo::GetGlassY() const {
407 return glass_y;
408}
409
410MoleType CharInfo::GetMoleType() const {
411 return mole_type;
412}
413
414u8 CharInfo::GetMoleScale() const {
415 return mole_scale;
416}
417
418u8 CharInfo::GetMoleX() const {
419 return mole_x;
420}
421
422u8 CharInfo::GetMoleY() const {
423 return mole_y;
424}
425
426bool CharInfo::operator==(const CharInfo& info) {
427 bool is_identical = info.Verify() == ValidationResult::NoErrors;
428 is_identical &= name.data == info.GetNickname().data;
429 is_identical &= create_id == info.GetCreateId();
430 is_identical &= font_region == info.GetFontRegion();
431 is_identical &= favorite_color == info.GetFavoriteColor();
432 is_identical &= gender == info.GetGender();
433 is_identical &= height == info.GetHeight();
434 is_identical &= build == info.GetBuild();
435 is_identical &= type == info.GetType();
436 is_identical &= region_move == info.GetRegionMove();
437 is_identical &= faceline_type == info.GetFacelineType();
438 is_identical &= faceline_color == info.GetFacelineColor();
439 is_identical &= faceline_wrinkle == info.GetFacelineWrinkle();
440 is_identical &= faceline_make == info.GetFacelineMake();
441 is_identical &= hair_type == info.GetHairType();
442 is_identical &= hair_color == info.GetHairColor();
443 is_identical &= hair_flip == info.GetHairFlip();
444 is_identical &= eye_type == info.GetEyeType();
445 is_identical &= eye_color == info.GetEyeColor();
446 is_identical &= eye_scale == info.GetEyeScale();
447 is_identical &= eye_aspect == info.GetEyeAspect();
448 is_identical &= eye_rotate == info.GetEyeRotate();
449 is_identical &= eye_x == info.GetEyeX();
450 is_identical &= eye_y == info.GetEyeY();
451 is_identical &= eyebrow_type == info.GetEyebrowType();
452 is_identical &= eyebrow_color == info.GetEyebrowColor();
453 is_identical &= eyebrow_scale == info.GetEyebrowScale();
454 is_identical &= eyebrow_aspect == info.GetEyebrowAspect();
455 is_identical &= eyebrow_rotate == info.GetEyebrowRotate();
456 is_identical &= eyebrow_x == info.GetEyebrowX();
457 is_identical &= eyebrow_y == info.GetEyebrowY();
458 is_identical &= nose_type == info.GetNoseType();
459 is_identical &= nose_scale == info.GetNoseScale();
460 is_identical &= nose_y == info.GetNoseY();
461 is_identical &= mouth_type == info.GetMouthType();
462 is_identical &= mouth_color == info.GetMouthColor();
463 is_identical &= mouth_scale == info.GetMouthScale();
464 is_identical &= mouth_aspect == info.GetMouthAspect();
465 is_identical &= mouth_y == info.GetMouthY();
466 is_identical &= beard_color == info.GetBeardColor();
467 is_identical &= beard_type == info.GetBeardType();
468 is_identical &= mustache_type == info.GetMustacheType();
469 is_identical &= mustache_scale == info.GetMustacheScale();
470 is_identical &= mustache_y == info.GetMustacheY();
471 is_identical &= glass_type == info.GetGlassType();
472 is_identical &= glass_color == info.GetGlassColor();
473 is_identical &= glass_scale == info.GetGlassScale();
474 is_identical &= glass_y == info.GetGlassY();
475 is_identical &= mole_type == info.GetMoleType();
476 is_identical &= mole_scale == info.GetMoleScale();
477 is_identical &= mole_x == info.GetMoleX();
478 is_identical &= mole_y == info.GetMoleY();
479 return is_identical;
480}
481
482} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h
new file mode 100644
index 000000000..d069b221f
--- /dev/null
+++ b/src/core/hle/service/mii/types/char_info.h
@@ -0,0 +1,137 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/service/mii/mii_types.h"
7
8namespace Service::Mii {
9class StoreData;
10
11// This is nn::mii::detail::CharInfoRaw
12class CharInfo {
13public:
14 void SetFromStoreData(const StoreData& store_data_raw);
15
16 ValidationResult Verify() const;
17
18 Common::UUID GetCreateId() const;
19 Nickname GetNickname() const;
20 FontRegion GetFontRegion() const;
21 FavoriteColor GetFavoriteColor() const;
22 Gender GetGender() const;
23 u8 GetHeight() const;
24 u8 GetBuild() const;
25 u8 GetType() const;
26 u8 GetRegionMove() const;
27 FacelineType GetFacelineType() const;
28 FacelineColor GetFacelineColor() const;
29 FacelineWrinkle GetFacelineWrinkle() const;
30 FacelineMake GetFacelineMake() const;
31 HairType GetHairType() const;
32 CommonColor GetHairColor() const;
33 HairFlip GetHairFlip() const;
34 EyeType GetEyeType() const;
35 CommonColor GetEyeColor() const;
36 u8 GetEyeScale() const;
37 u8 GetEyeAspect() const;
38 u8 GetEyeRotate() const;
39 u8 GetEyeX() const;
40 u8 GetEyeY() const;
41 EyebrowType GetEyebrowType() const;
42 CommonColor GetEyebrowColor() const;
43 u8 GetEyebrowScale() const;
44 u8 GetEyebrowAspect() const;
45 u8 GetEyebrowRotate() const;
46 u8 GetEyebrowX() const;
47 u8 GetEyebrowY() const;
48 NoseType GetNoseType() const;
49 u8 GetNoseScale() const;
50 u8 GetNoseY() const;
51 MouthType GetMouthType() const;
52 CommonColor GetMouthColor() const;
53 u8 GetMouthScale() const;
54 u8 GetMouthAspect() const;
55 u8 GetMouthY() const;
56 CommonColor GetBeardColor() const;
57 BeardType GetBeardType() const;
58 MustacheType GetMustacheType() const;
59 u8 GetMustacheScale() const;
60 u8 GetMustacheY() const;
61 GlassType GetGlassType() const;
62 CommonColor GetGlassColor() const;
63 u8 GetGlassScale() const;
64 u8 GetGlassY() const;
65 MoleType GetMoleType() const;
66 u8 GetMoleScale() const;
67 u8 GetMoleX() const;
68 u8 GetMoleY() const;
69
70 bool operator==(const CharInfo& info);
71
72private:
73 Common::UUID create_id;
74 Nickname name;
75 u16 null_terminator;
76 FontRegion font_region;
77 FavoriteColor favorite_color;
78 Gender gender;
79 u8 height;
80 u8 build;
81 u8 type;
82 u8 region_move;
83 FacelineType faceline_type;
84 FacelineColor faceline_color;
85 FacelineWrinkle faceline_wrinkle;
86 FacelineMake faceline_make;
87 HairType hair_type;
88 CommonColor hair_color;
89 HairFlip hair_flip;
90 EyeType eye_type;
91 CommonColor eye_color;
92 u8 eye_scale;
93 u8 eye_aspect;
94 u8 eye_rotate;
95 u8 eye_x;
96 u8 eye_y;
97 EyebrowType eyebrow_type;
98 CommonColor eyebrow_color;
99 u8 eyebrow_scale;
100 u8 eyebrow_aspect;
101 u8 eyebrow_rotate;
102 u8 eyebrow_x;
103 u8 eyebrow_y;
104 NoseType nose_type;
105 u8 nose_scale;
106 u8 nose_y;
107 MouthType mouth_type;
108 CommonColor mouth_color;
109 u8 mouth_scale;
110 u8 mouth_aspect;
111 u8 mouth_y;
112 CommonColor beard_color;
113 BeardType beard_type;
114 MustacheType mustache_type;
115 u8 mustache_scale;
116 u8 mustache_y;
117 GlassType glass_type;
118 CommonColor glass_color;
119 u8 glass_scale;
120 u8 glass_y;
121 MoleType mole_type;
122 u8 mole_scale;
123 u8 mole_x;
124 u8 mole_y;
125 u8 padding;
126};
127static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
128static_assert(std::has_unique_object_representations_v<CharInfo>,
129 "All bits of CharInfo must contribute to its value.");
130
131struct CharInfoElement {
132 CharInfo char_info{};
133 Source source{};
134};
135static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size.");
136
137}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp
new file mode 100644
index 000000000..659288b51
--- /dev/null
+++ b/src/core/hle/service/mii/types/core_data.cpp
@@ -0,0 +1,601 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/assert.h"
5#include "core/hle/service/mii/mii_util.h"
6#include "core/hle/service/mii/types/core_data.h"
7#include "core/hle/service/mii/types/raw_data.h"
8
9namespace Service::Mii {
10
11void CoreData::SetDefault() {
12 data = {};
13 name = GetDefaultNickname();
14}
15
16void CoreData::BuildRandom(Age age, Gender gender, Race race) {
17 if (gender == Gender::All) {
18 gender = MiiUtil::GetRandomValue(Gender::Max);
19 }
20
21 if (age == Age::All) {
22 const auto random{MiiUtil::GetRandomValue<int>(10)};
23 if (random >= 8) {
24 age = Age::Old;
25 } else if (random >= 4) {
26 age = Age::Normal;
27 } else {
28 age = Age::Young;
29 }
30 }
31
32 if (race == Race::All) {
33 const auto random{MiiUtil::GetRandomValue<int>(10)};
34 if (random >= 8) {
35 race = Race::Black;
36 } else if (random >= 4) {
37 race = Race::White;
38 } else {
39 race = Race::Asian;
40 }
41 }
42
43 SetGender(gender);
44 SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max));
45 SetRegionMove(0);
46 SetFontRegion(FontRegion::Standard);
47 SetType(0);
48 SetHeight(64);
49 SetBuild(64);
50
51 u32 axis_y{};
52 if (gender == Gender::Female && age == Age::Young) {
53 axis_y = MiiUtil::GetRandomValue<u32>(3);
54 }
55
56 const std::size_t index{3 * static_cast<std::size_t>(age) +
57 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
58
59 const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)};
60 const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at(
61 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
62 const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
63 const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
64 const auto& hair_type_info{RawData::RandomMiiHairType.at(index)};
65 const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
66 static_cast<std::size_t>(age))};
67 const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)};
68 const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
69 const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
70 const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)};
71 const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)};
72 const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
73
74 data.faceline_type.Assign(
75 faceline_type_info
76 .values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
77 data.faceline_color.Assign(
78 faceline_color_info
79 .values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
80 data.faceline_wrinkle.Assign(
81 faceline_wrinkle_info
82 .values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
83 data.faceline_makeup.Assign(
84 faceline_makeup_info
85 .values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
86
87 data.hair_type.Assign(
88 hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]);
89 SetHairColor(RawData::GetHairColorFromVer3(
90 hair_color_info
91 .values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)]));
92 SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max));
93
94 data.eye_type.Assign(
95 eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]);
96
97 const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
98 const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
99 const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
100 const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]};
101
102 SetEyeColor(RawData::GetEyeColorFromVer3(
103 eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)]));
104 SetEyeScale(4);
105 SetEyeAspect(3);
106 SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate));
107 SetEyeX(2);
108 SetEyeY(static_cast<u8>(axis_y + 12));
109
110 data.eyebrow_type.Assign(
111 eyebrow_type_info
112 .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
113
114 const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
115 const auto eyebrow_y{race == Race::Asian ? 9 : 10};
116 const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6};
117 const auto eyebrow_rotate{
118 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]};
119
120 SetEyebrowColor(GetHairColor());
121 SetEyebrowScale(4);
122 SetEyebrowAspect(3);
123 SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate));
124 SetEyebrowX(2);
125 SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y));
126
127 data.nose_type.Assign(
128 nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]);
129 SetNoseScale(gender == Gender::Female ? 3 : 4);
130 SetNoseY(static_cast<u8>(axis_y + 9));
131
132 const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0};
133
134 data.mouth_type.Assign(
135 mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
136 SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color));
137 SetMouthScale(4);
138 SetMouthAspect(3);
139 SetMouthY(static_cast<u8>(axis_y + 13));
140
141 SetBeardColor(GetHairColor());
142 SetMustacheScale(4);
143
144 if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) {
145 const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)};
146
147 auto beard_type{BeardType::None};
148 auto mustache_type{MustacheType::None};
149
150 if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
151 BeardAndMustacheFlag::Beard) {
152 beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max);
153 }
154
155 if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
156 BeardAndMustacheFlag::Mustache) {
157 mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max);
158 }
159
160 SetMustacheType(mustache_type);
161 SetBeardType(beard_type);
162 SetMustacheY(10);
163 } else {
164 SetMustacheType(MustacheType::None);
165 SetBeardType(BeardType::None);
166 SetMustacheY(static_cast<u8>(axis_y + 10));
167 }
168
169 const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)};
170 u8 glasses_type{};
171 while (glasses_type_start < glasses_type_info.values[glasses_type]) {
172 if (++glasses_type >= glasses_type_info.values_count) {
173 ASSERT(false);
174 break;
175 }
176 }
177
178 SetGlassType(static_cast<GlassType>(glasses_type));
179 SetGlassColor(RawData::GetGlassColorFromVer3(0));
180 SetGlassScale(4);
181
182 SetMoleType(MoleType::None);
183 SetMoleScale(4);
184 SetMoleX(2);
185 SetMoleY(20);
186}
187
188u32 CoreData::IsValid() const {
189 // TODO: Complete this
190 return 0;
191}
192
193void CoreData::SetFontRegion(FontRegion value) {
194 data.font_region.Assign(static_cast<u32>(value));
195}
196
197void CoreData::SetFavoriteColor(FavoriteColor value) {
198 data.favorite_color.Assign(static_cast<u32>(value));
199}
200
201void CoreData::SetGender(Gender value) {
202 data.gender.Assign(static_cast<u32>(value));
203}
204
205void CoreData::SetHeight(u8 value) {
206 data.height.Assign(value);
207}
208
209void CoreData::SetBuild(u8 value) {
210 data.build.Assign(value);
211}
212
213void CoreData::SetType(u8 value) {
214 data.type.Assign(value);
215}
216
217void CoreData::SetRegionMove(u8 value) {
218 data.region_move.Assign(value);
219}
220
221void CoreData::SetFacelineType(FacelineType value) {
222 data.faceline_type.Assign(static_cast<u32>(value));
223}
224
225void CoreData::SetFacelineColor(FacelineColor value) {
226 data.faceline_color.Assign(static_cast<u32>(value));
227}
228
229void CoreData::SetFacelineWrinkle(FacelineWrinkle value) {
230 data.faceline_wrinkle.Assign(static_cast<u32>(value));
231}
232
233void CoreData::SetFacelineMake(FacelineMake value) {
234 data.faceline_makeup.Assign(static_cast<u32>(value));
235}
236
237void CoreData::SetHairType(HairType value) {
238 data.hair_type.Assign(static_cast<u32>(value));
239}
240
241void CoreData::SetHairColor(CommonColor value) {
242 data.hair_color.Assign(static_cast<u32>(value));
243}
244
245void CoreData::SetHairFlip(HairFlip value) {
246 data.hair_flip.Assign(static_cast<u32>(value));
247}
248
249void CoreData::SetEyeType(EyeType value) {
250 data.eye_type.Assign(static_cast<u32>(value));
251}
252
253void CoreData::SetEyeColor(CommonColor value) {
254 data.eye_color.Assign(static_cast<u32>(value));
255}
256
257void CoreData::SetEyeScale(u8 value) {
258 data.eye_scale.Assign(value);
259}
260
261void CoreData::SetEyeAspect(u8 value) {
262 data.eye_aspect.Assign(value);
263}
264
265void CoreData::SetEyeRotate(u8 value) {
266 data.eye_rotate.Assign(value);
267}
268
269void CoreData::SetEyeX(u8 value) {
270 data.eye_x.Assign(value);
271}
272
273void CoreData::SetEyeY(u8 value) {
274 data.eye_y.Assign(value);
275}
276
277void CoreData::SetEyebrowType(EyebrowType value) {
278 data.eyebrow_type.Assign(static_cast<u32>(value));
279}
280
281void CoreData::SetEyebrowColor(CommonColor value) {
282 data.eyebrow_color.Assign(static_cast<u32>(value));
283}
284
285void CoreData::SetEyebrowScale(u8 value) {
286 data.eyebrow_scale.Assign(value);
287}
288
289void CoreData::SetEyebrowAspect(u8 value) {
290 data.eyebrow_aspect.Assign(value);
291}
292
293void CoreData::SetEyebrowRotate(u8 value) {
294 data.eyebrow_rotate.Assign(value);
295}
296
297void CoreData::SetEyebrowX(u8 value) {
298 data.eyebrow_x.Assign(value);
299}
300
301void CoreData::SetEyebrowY(u8 value) {
302 data.eyebrow_y.Assign(value);
303}
304
305void CoreData::SetNoseType(NoseType value) {
306 data.nose_type.Assign(static_cast<u32>(value));
307}
308
309void CoreData::SetNoseScale(u8 value) {
310 data.nose_scale.Assign(value);
311}
312
313void CoreData::SetNoseY(u8 value) {
314 data.nose_y.Assign(value);
315}
316
317void CoreData::SetMouthType(u8 value) {
318 data.mouth_type.Assign(value);
319}
320
321void CoreData::SetMouthColor(CommonColor value) {
322 data.mouth_color.Assign(static_cast<u32>(value));
323}
324
325void CoreData::SetMouthScale(u8 value) {
326 data.mouth_scale.Assign(value);
327}
328
329void CoreData::SetMouthAspect(u8 value) {
330 data.mouth_aspect.Assign(value);
331}
332
333void CoreData::SetMouthY(u8 value) {
334 data.mouth_y.Assign(value);
335}
336
337void CoreData::SetBeardColor(CommonColor value) {
338 data.beard_color.Assign(static_cast<u32>(value));
339}
340
341void CoreData::SetBeardType(BeardType value) {
342 data.beard_type.Assign(static_cast<u32>(value));
343}
344
345void CoreData::SetMustacheType(MustacheType value) {
346 data.mustache_type.Assign(static_cast<u32>(value));
347}
348
349void CoreData::SetMustacheScale(u8 value) {
350 data.mustache_scale.Assign(value);
351}
352
353void CoreData::SetMustacheY(u8 value) {
354 data.mustache_y.Assign(value);
355}
356
357void CoreData::SetGlassType(GlassType value) {
358 data.glasses_type.Assign(static_cast<u32>(value));
359}
360
361void CoreData::SetGlassColor(CommonColor value) {
362 data.glasses_color.Assign(static_cast<u32>(value));
363}
364
365void CoreData::SetGlassScale(u8 value) {
366 data.glasses_scale.Assign(value);
367}
368
369void CoreData::SetGlassY(u8 value) {
370 data.glasses_y.Assign(value);
371}
372
373void CoreData::SetMoleType(MoleType value) {
374 data.mole_type.Assign(static_cast<u32>(value));
375}
376
377void CoreData::SetMoleScale(u8 value) {
378 data.mole_scale.Assign(value);
379}
380
381void CoreData::SetMoleX(u8 value) {
382 data.mole_x.Assign(value);
383}
384
385void CoreData::SetMoleY(u8 value) {
386 data.mole_y.Assign(value);
387}
388
389void CoreData::SetNickname(Nickname nickname) {
390 name = nickname;
391}
392
393FontRegion CoreData::GetFontRegion() const {
394 return static_cast<FontRegion>(data.font_region.Value());
395}
396
397FavoriteColor CoreData::GetFavoriteColor() const {
398 return static_cast<FavoriteColor>(data.favorite_color.Value());
399}
400
401Gender CoreData::GetGender() const {
402 return static_cast<Gender>(data.gender.Value());
403}
404
405u8 CoreData::GetHeight() const {
406 return static_cast<u8>(data.height.Value());
407}
408
409u8 CoreData::GetBuild() const {
410 return static_cast<u8>(data.build.Value());
411}
412
413u8 CoreData::GetType() const {
414 return static_cast<u8>(data.type.Value());
415}
416
417u8 CoreData::GetRegionMove() const {
418 return static_cast<u8>(data.region_move.Value());
419}
420
421FacelineType CoreData::GetFacelineType() const {
422 return static_cast<FacelineType>(data.faceline_type.Value());
423}
424
425FacelineColor CoreData::GetFacelineColor() const {
426 return static_cast<FacelineColor>(data.faceline_color.Value());
427}
428
429FacelineWrinkle CoreData::GetFacelineWrinkle() const {
430 return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value());
431}
432
433FacelineMake CoreData::GetFacelineMake() const {
434 return static_cast<FacelineMake>(data.faceline_makeup.Value());
435}
436
437HairType CoreData::GetHairType() const {
438 return static_cast<HairType>(data.hair_type.Value());
439}
440
441CommonColor CoreData::GetHairColor() const {
442 return static_cast<CommonColor>(data.hair_color.Value());
443}
444
445HairFlip CoreData::GetHairFlip() const {
446 return static_cast<HairFlip>(data.hair_flip.Value());
447}
448
449EyeType CoreData::GetEyeType() const {
450 return static_cast<EyeType>(data.eye_type.Value());
451}
452
453CommonColor CoreData::GetEyeColor() const {
454 return static_cast<CommonColor>(data.eye_color.Value());
455}
456
457u8 CoreData::GetEyeScale() const {
458 return static_cast<u8>(data.eye_scale.Value());
459}
460
461u8 CoreData::GetEyeAspect() const {
462 return static_cast<u8>(data.eye_aspect.Value());
463}
464
465u8 CoreData::GetEyeRotate() const {
466 return static_cast<u8>(data.eye_rotate.Value());
467}
468
469u8 CoreData::GetEyeX() const {
470 return static_cast<u8>(data.eye_x.Value());
471}
472
473u8 CoreData::GetEyeY() const {
474 return static_cast<u8>(data.eye_y.Value());
475}
476
477EyebrowType CoreData::GetEyebrowType() const {
478 return static_cast<EyebrowType>(data.eyebrow_type.Value());
479}
480
481CommonColor CoreData::GetEyebrowColor() const {
482 return static_cast<CommonColor>(data.eyebrow_color.Value());
483}
484
485u8 CoreData::GetEyebrowScale() const {
486 return static_cast<u8>(data.eyebrow_scale.Value());
487}
488
489u8 CoreData::GetEyebrowAspect() const {
490 return static_cast<u8>(data.eyebrow_aspect.Value());
491}
492
493u8 CoreData::GetEyebrowRotate() const {
494 return static_cast<u8>(data.eyebrow_rotate.Value());
495}
496
497u8 CoreData::GetEyebrowX() const {
498 return static_cast<u8>(data.eyebrow_x.Value());
499}
500
501u8 CoreData::GetEyebrowY() const {
502 return static_cast<u8>(data.eyebrow_y.Value());
503}
504
505NoseType CoreData::GetNoseType() const {
506 return static_cast<NoseType>(data.nose_type.Value());
507}
508
509u8 CoreData::GetNoseScale() const {
510 return static_cast<u8>(data.nose_scale.Value());
511}
512
513u8 CoreData::GetNoseY() const {
514 return static_cast<u8>(data.nose_y.Value());
515}
516
517MouthType CoreData::GetMouthType() const {
518 return static_cast<MouthType>(data.mouth_type.Value());
519}
520
521CommonColor CoreData::GetMouthColor() const {
522 return static_cast<CommonColor>(data.mouth_color.Value());
523}
524
525u8 CoreData::GetMouthScale() const {
526 return static_cast<u8>(data.mouth_scale.Value());
527}
528
529u8 CoreData::GetMouthAspect() const {
530 return static_cast<u8>(data.mouth_aspect.Value());
531}
532
533u8 CoreData::GetMouthY() const {
534 return static_cast<u8>(data.mouth_y.Value());
535}
536
537CommonColor CoreData::GetBeardColor() const {
538 return static_cast<CommonColor>(data.beard_color.Value());
539}
540
541BeardType CoreData::GetBeardType() const {
542 return static_cast<BeardType>(data.beard_type.Value());
543}
544
545MustacheType CoreData::GetMustacheType() const {
546 return static_cast<MustacheType>(data.mustache_type.Value());
547}
548
549u8 CoreData::GetMustacheScale() const {
550 return static_cast<u8>(data.mustache_scale.Value());
551}
552
553u8 CoreData::GetMustacheY() const {
554 return static_cast<u8>(data.mustache_y.Value());
555}
556
557GlassType CoreData::GetGlassType() const {
558 return static_cast<GlassType>(data.glasses_type.Value());
559}
560
561CommonColor CoreData::GetGlassColor() const {
562 return static_cast<CommonColor>(data.glasses_color.Value());
563}
564
565u8 CoreData::GetGlassScale() const {
566 return static_cast<u8>(data.glasses_scale.Value());
567}
568
569u8 CoreData::GetGlassY() const {
570 return static_cast<u8>(data.glasses_y.Value());
571}
572
573MoleType CoreData::GetMoleType() const {
574 return static_cast<MoleType>(data.mole_type.Value());
575}
576
577u8 CoreData::GetMoleScale() const {
578 return static_cast<u8>(data.mole_scale.Value());
579}
580
581u8 CoreData::GetMoleX() const {
582 return static_cast<u8>(data.mole_x.Value());
583}
584
585u8 CoreData::GetMoleY() const {
586 return static_cast<u8>(data.mole_y.Value());
587}
588
589Nickname CoreData::GetNickname() const {
590 return name;
591}
592
593Nickname CoreData::GetDefaultNickname() const {
594 return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'};
595}
596
597Nickname CoreData::GetInvalidNickname() const {
598 return {u'?', u'?', u'?'};
599}
600
601} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h
new file mode 100644
index 000000000..cebcd2ee4
--- /dev/null
+++ b/src/core/hle/service/mii/types/core_data.h
@@ -0,0 +1,216 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/service/mii/mii_types.h"
7
8namespace Service::Mii {
9
10struct StoreDataBitFields {
11 union {
12 u32 word_0{};
13
14 BitField<0, 8, u32> hair_type;
15 BitField<8, 7, u32> height;
16 BitField<15, 1, u32> mole_type;
17 BitField<16, 7, u32> build;
18 BitField<23, 1, u32> hair_flip;
19 BitField<24, 7, u32> hair_color;
20 BitField<31, 1, u32> type;
21 };
22
23 union {
24 u32 word_1{};
25
26 BitField<0, 7, u32> eye_color;
27 BitField<7, 1, u32> gender;
28 BitField<8, 7, u32> eyebrow_color;
29 BitField<16, 7, u32> mouth_color;
30 BitField<24, 7, u32> beard_color;
31 };
32
33 union {
34 u32 word_2{};
35
36 BitField<0, 7, u32> glasses_color;
37 BitField<8, 6, u32> eye_type;
38 BitField<14, 2, u32> region_move;
39 BitField<16, 6, u32> mouth_type;
40 BitField<22, 2, u32> font_region;
41 BitField<24, 5, u32> eye_y;
42 BitField<29, 3, u32> glasses_scale;
43 };
44
45 union {
46 u32 word_3{};
47
48 BitField<0, 5, u32> eyebrow_type;
49 BitField<5, 3, u32> mustache_type;
50 BitField<8, 5, u32> nose_type;
51 BitField<13, 3, u32> beard_type;
52 BitField<16, 5, u32> nose_y;
53 BitField<21, 3, u32> mouth_aspect;
54 BitField<24, 5, u32> mouth_y;
55 BitField<29, 3, u32> eyebrow_aspect;
56 };
57
58 union {
59 u32 word_4{};
60
61 BitField<0, 5, u32> mustache_y;
62 BitField<5, 3, u32> eye_rotate;
63 BitField<8, 5, u32> glasses_y;
64 BitField<13, 3, u32> eye_aspect;
65 BitField<16, 5, u32> mole_x;
66 BitField<21, 3, u32> eye_scale;
67 BitField<24, 5, u32> mole_y;
68 };
69
70 union {
71 u32 word_5{};
72
73 BitField<0, 5, u32> glasses_type;
74 BitField<8, 4, u32> favorite_color;
75 BitField<12, 4, u32> faceline_type;
76 BitField<16, 4, u32> faceline_color;
77 BitField<20, 4, u32> faceline_wrinkle;
78 BitField<24, 4, u32> faceline_makeup;
79 BitField<28, 4, u32> eye_x;
80 };
81
82 union {
83 u32 word_6{};
84
85 BitField<0, 4, u32> eyebrow_scale;
86 BitField<4, 4, u32> eyebrow_rotate;
87 BitField<8, 4, u32> eyebrow_x;
88 BitField<12, 4, u32> eyebrow_y;
89 BitField<16, 4, u32> nose_scale;
90 BitField<20, 4, u32> mouth_scale;
91 BitField<24, 4, u32> mustache_scale;
92 BitField<28, 4, u32> mole_scale;
93 };
94};
95static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size.");
96static_assert(std::is_trivially_copyable_v<StoreDataBitFields>,
97 "StoreDataBitFields is not trivially copyable.");
98
99class CoreData {
100public:
101 void SetDefault();
102 void BuildRandom(Age age, Gender gender, Race race);
103
104 u32 IsValid() const;
105
106 void SetFontRegion(FontRegion value);
107 void SetFavoriteColor(FavoriteColor value);
108 void SetGender(Gender value);
109 void SetHeight(u8 value);
110 void SetBuild(u8 value);
111 void SetType(u8 value);
112 void SetRegionMove(u8 value);
113 void SetFacelineType(FacelineType value);
114 void SetFacelineColor(FacelineColor value);
115 void SetFacelineWrinkle(FacelineWrinkle value);
116 void SetFacelineMake(FacelineMake value);
117 void SetHairType(HairType value);
118 void SetHairColor(CommonColor value);
119 void SetHairFlip(HairFlip value);
120 void SetEyeType(EyeType value);
121 void SetEyeColor(CommonColor value);
122 void SetEyeScale(u8 value);
123 void SetEyeAspect(u8 value);
124 void SetEyeRotate(u8 value);
125 void SetEyeX(u8 value);
126 void SetEyeY(u8 value);
127 void SetEyebrowType(EyebrowType value);
128 void SetEyebrowColor(CommonColor value);
129 void SetEyebrowScale(u8 value);
130 void SetEyebrowAspect(u8 value);
131 void SetEyebrowRotate(u8 value);
132 void SetEyebrowX(u8 value);
133 void SetEyebrowY(u8 value);
134 void SetNoseType(NoseType value);
135 void SetNoseScale(u8 value);
136 void SetNoseY(u8 value);
137 void SetMouthType(u8 value);
138 void SetMouthColor(CommonColor value);
139 void SetMouthScale(u8 value);
140 void SetMouthAspect(u8 value);
141 void SetMouthY(u8 value);
142 void SetBeardColor(CommonColor value);
143 void SetBeardType(BeardType value);
144 void SetMustacheType(MustacheType value);
145 void SetMustacheScale(u8 value);
146 void SetMustacheY(u8 value);
147 void SetGlassType(GlassType value);
148 void SetGlassColor(CommonColor value);
149 void SetGlassScale(u8 value);
150 void SetGlassY(u8 value);
151 void SetMoleType(MoleType value);
152 void SetMoleScale(u8 value);
153 void SetMoleX(u8 value);
154 void SetMoleY(u8 value);
155 void SetNickname(Nickname nickname);
156
157 FontRegion GetFontRegion() const;
158 FavoriteColor GetFavoriteColor() const;
159 Gender GetGender() const;
160 u8 GetHeight() const;
161 u8 GetBuild() const;
162 u8 GetType() const;
163 u8 GetRegionMove() const;
164 FacelineType GetFacelineType() const;
165 FacelineColor GetFacelineColor() const;
166 FacelineWrinkle GetFacelineWrinkle() const;
167 FacelineMake GetFacelineMake() const;
168 HairType GetHairType() const;
169 CommonColor GetHairColor() const;
170 HairFlip GetHairFlip() const;
171 EyeType GetEyeType() const;
172 CommonColor GetEyeColor() const;
173 u8 GetEyeScale() const;
174 u8 GetEyeAspect() const;
175 u8 GetEyeRotate() const;
176 u8 GetEyeX() const;
177 u8 GetEyeY() const;
178 EyebrowType GetEyebrowType() const;
179 CommonColor GetEyebrowColor() const;
180 u8 GetEyebrowScale() const;
181 u8 GetEyebrowAspect() const;
182 u8 GetEyebrowRotate() const;
183 u8 GetEyebrowX() const;
184 u8 GetEyebrowY() const;
185 NoseType GetNoseType() const;
186 u8 GetNoseScale() const;
187 u8 GetNoseY() const;
188 MouthType GetMouthType() const;
189 CommonColor GetMouthColor() const;
190 u8 GetMouthScale() const;
191 u8 GetMouthAspect() const;
192 u8 GetMouthY() const;
193 CommonColor GetBeardColor() const;
194 BeardType GetBeardType() const;
195 MustacheType GetMustacheType() const;
196 u8 GetMustacheScale() const;
197 u8 GetMustacheY() const;
198 GlassType GetGlassType() const;
199 CommonColor GetGlassColor() const;
200 u8 GetGlassScale() const;
201 u8 GetGlassY() const;
202 MoleType GetMoleType() const;
203 u8 GetMoleScale() const;
204 u8 GetMoleX() const;
205 u8 GetMoleY() const;
206 Nickname GetNickname() const;
207 Nickname GetDefaultNickname() const;
208 Nickname GetInvalidNickname() const;
209
210private:
211 StoreDataBitFields data{};
212 Nickname name{};
213};
214static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
215
216}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp
index 1442280c8..5143abcc8 100644
--- a/src/core/hle/service/mii/raw_data.cpp
+++ b/src/core/hle/service/mii/types/raw_data.cpp
@@ -1,11 +1,88 @@
1// SPDX-FileCopyrightText: Ryujinx Team and Contributors 1// SPDX-FileCopyrightText: Ryujinx Team and Contributors
2// SPDX-License-Identifier: MIT 2// SPDX-License-Identifier: MIT
3 3
4#include "core/hle/service/mii/raw_data.h" 4#include "core/hle/service/mii/types/raw_data.h"
5 5
6namespace Service::Mii::RawData { 6namespace Service::Mii::RawData {
7 7
8const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ 8constexpr std::array<u8, static_cast<u8>(FacelineColor::Count)> FromVer3FacelineColorTable{
9 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
10};
11
12constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3HairColorTable{
13 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0,
14 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
15 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4,
16 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5,
17 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7,
18 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4,
19};
20
21constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3EyeColorTable{
22 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4,
23 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
24 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4,
25 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
26 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2,
27 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
28};
29
30constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3MouthlineColorTable{
31 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
32 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
33 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
34 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
35 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3,
36 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3,
37};
38
39constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3GlassColorTable{
40 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3,
41 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
42 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
43 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5,
44 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4,
45 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
46};
47
48constexpr std::array<u8, static_cast<u8>(GlassType::Count)> FromVer3GlassTypeTable{
49 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1,
50 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7,
51};
52
53constexpr std::array<u8, 8> Ver3FacelineColorTable{
54 0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
55};
56
57constexpr std::array<u8, 8> Ver3HairColorTable{
58 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
59};
60
61constexpr std::array<u8, 6> Ver3EyeColorTable{
62 0x8, 0x9, 0xa, 0xb, 0xc, 0xd,
63};
64
65constexpr std::array<u8, 5> Ver3MouthColorTable{
66 0x13, 0x14, 0x15, 0x16, 0x17,
67};
68
69constexpr std::array<u8, 7> Ver3GlassColorTable{
70 0x8, 0xe, 0xf, 0x10, 0x11, 0x12, 0x0,
71};
72
73const std::array<u8, 62> EyeRotateLookup{
74 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
75 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
76 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
77 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
78};
79
80const std::array<u8, 24> EyebrowRotateLookup{
81 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08,
82 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05,
83};
84
85const std::array<Service::Mii::DefaultMii, 2> BaseMii{
9 Service::Mii::DefaultMii{ 86 Service::Mii::DefaultMii{
10 .face_type = 0, 87 .face_type = 0,
11 .face_color = 0, 88 .face_color = 0,
@@ -51,11 +128,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
51 .mole_y = 20, 128 .mole_y = 20,
52 .height = 64, 129 .height = 64,
53 .weight = 64, 130 .weight = 64,
54 .gender = Gender::Male, 131 .gender = 0,
55 .favorite_color = 0, 132 .favorite_color = 0,
56 .region = 0, 133 .region_move = 0,
57 .font_region = FontRegion::Standard, 134 .font_region = 0,
58 .type = 0, 135 .type = 0,
136 .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
59 }, 137 },
60 Service::Mii::DefaultMii{ 138 Service::Mii::DefaultMii{
61 .face_type = 0, 139 .face_type = 0,
@@ -102,12 +180,16 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
102 .mole_y = 20, 180 .mole_y = 20,
103 .height = 64, 181 .height = 64,
104 .weight = 64, 182 .weight = 64,
105 .gender = Gender::Female, 183 .gender = 1,
106 .favorite_color = 0, 184 .favorite_color = 0,
107 .region = 0, 185 .region_move = 0,
108 .font_region = FontRegion::Standard, 186 .font_region = 0,
109 .type = 0, 187 .type = 0,
188 .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
110 }, 189 },
190};
191
192const std::array<Service::Mii::DefaultMii, 6> DefaultMii{
111 Service::Mii::DefaultMii{ 193 Service::Mii::DefaultMii{
112 .face_type = 0, 194 .face_type = 0,
113 .face_color = 4, 195 .face_color = 4,
@@ -153,11 +235,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
153 .mole_y = 20, 235 .mole_y = 20,
154 .height = 64, 236 .height = 64,
155 .weight = 64, 237 .weight = 64,
156 .gender = Gender::Male, 238 .gender = 0,
157 .favorite_color = 4, 239 .favorite_color = 4,
158 .region = 0, 240 .region_move = 0,
159 .font_region = FontRegion::Standard, 241 .font_region = 0,
160 .type = 0, 242 .type = 0,
243 .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
161 }, 244 },
162 Service::Mii::DefaultMii{ 245 Service::Mii::DefaultMii{
163 .face_type = 0, 246 .face_type = 0,
@@ -204,11 +287,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
204 .mole_y = 20, 287 .mole_y = 20,
205 .height = 64, 288 .height = 64,
206 .weight = 64, 289 .weight = 64,
207 .gender = Gender::Male, 290 .gender = 0,
208 .favorite_color = 5, 291 .favorite_color = 5,
209 .region = 0, 292 .region_move = 0,
210 .font_region = FontRegion::Standard, 293 .font_region = 0,
211 .type = 0, 294 .type = 0,
295 .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
212 }, 296 },
213 Service::Mii::DefaultMii{ 297 Service::Mii::DefaultMii{
214 .face_type = 0, 298 .face_type = 0,
@@ -255,11 +339,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
255 .mole_y = 20, 339 .mole_y = 20,
256 .height = 64, 340 .height = 64,
257 .weight = 64, 341 .weight = 64,
258 .gender = Gender::Male, 342 .gender = 0,
259 .favorite_color = 0, 343 .favorite_color = 0,
260 .region = 0, 344 .region_move = 0,
261 .font_region = FontRegion::Standard, 345 .font_region = 0,
262 .type = 0, 346 .type = 0,
347 .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
263 }, 348 },
264 Service::Mii::DefaultMii{ 349 Service::Mii::DefaultMii{
265 .face_type = 0, 350 .face_type = 0,
@@ -306,11 +391,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
306 .mole_y = 20, 391 .mole_y = 20,
307 .height = 64, 392 .height = 64,
308 .weight = 64, 393 .weight = 64,
309 .gender = Gender::Female, 394 .gender = 1,
310 .favorite_color = 2, 395 .favorite_color = 2,
311 .region = 0, 396 .region_move = 0,
312 .font_region = FontRegion::Standard, 397 .font_region = 0,
313 .type = 0, 398 .type = 0,
399 .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
314 }, 400 },
315 Service::Mii::DefaultMii{ 401 Service::Mii::DefaultMii{
316 .face_type = 0, 402 .face_type = 0,
@@ -357,11 +443,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
357 .mole_y = 20, 443 .mole_y = 20,
358 .height = 64, 444 .height = 64,
359 .weight = 64, 445 .weight = 64,
360 .gender = Gender::Female, 446 .gender = 1,
361 .favorite_color = 6, 447 .favorite_color = 6,
362 .region = 0, 448 .region_move = 0,
363 .font_region = FontRegion::Standard, 449 .font_region = 0,
364 .type = 0, 450 .type = 0,
451 .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
365 }, 452 },
366 Service::Mii::DefaultMii{ 453 Service::Mii::DefaultMii{
367 .face_type = 0, 454 .face_type = 0,
@@ -408,176 +495,177 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
408 .mole_y = 20, 495 .mole_y = 20,
409 .height = 64, 496 .height = 64,
410 .weight = 64, 497 .weight = 64,
411 .gender = Gender::Female, 498 .gender = 1,
412 .favorite_color = 7, 499 .favorite_color = 7,
413 .region = 0, 500 .region_move = 0,
414 .font_region = FontRegion::Standard, 501 .font_region = 0,
415 .type = 0, 502 .type = 0,
503 .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
416 }, 504 },
417 505
418}; 506};
419 507
420const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{ 508const std::array<RandomMiiData4, 18> RandomMiiFaceline{
421 Service::Mii::RandomMiiData4{ 509 RandomMiiData4{
422 .gender = Gender::Male, 510 .gender = static_cast<u32>(Gender::Male),
423 .age = Age::Young, 511 .age = static_cast<u32>(Age::Young),
424 .race = Race::Black, 512 .race = static_cast<u32>(Race::Black),
425 .values_count = 10, 513 .values_count = 10,
426 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, 514 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
427 }, 515 },
428 Service::Mii::RandomMiiData4{ 516 RandomMiiData4{
429 .gender = Gender::Male, 517 .gender = static_cast<u32>(Gender::Male),
430 .age = Age::Normal, 518 .age = static_cast<u32>(Age::Normal),
431 .race = Race::Black, 519 .race = static_cast<u32>(Race::Black),
432 .values_count = 10, 520 .values_count = 10,
433 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, 521 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
434 }, 522 },
435 Service::Mii::RandomMiiData4{ 523 RandomMiiData4{
436 .gender = Gender::Male, 524 .gender = static_cast<u32>(Gender::Male),
437 .age = Age::Old, 525 .age = static_cast<u32>(Age::Old),
438 .race = Race::Black, 526 .race = static_cast<u32>(Race::Black),
439 .values_count = 10, 527 .values_count = 10,
440 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, 528 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
441 }, 529 },
442 Service::Mii::RandomMiiData4{ 530 RandomMiiData4{
443 .gender = Gender::Male, 531 .gender = static_cast<u32>(Gender::Male),
444 .age = Age::Young, 532 .age = static_cast<u32>(Age::Young),
445 .race = Race::White, 533 .race = static_cast<u32>(Race::White),
446 .values_count = 12, 534 .values_count = 12,
447 .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, 535 .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
448 }, 536 },
449 Service::Mii::RandomMiiData4{ 537 RandomMiiData4{
450 .gender = Gender::Male, 538 .gender = static_cast<u32>(Gender::Male),
451 .age = Age::Normal, 539 .age = static_cast<u32>(Age::Normal),
452 .race = Race::White, 540 .race = static_cast<u32>(Race::White),
453 .values_count = 13, 541 .values_count = 13,
454 .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, 542 .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11},
455 }, 543 },
456 Service::Mii::RandomMiiData4{ 544 RandomMiiData4{
457 .gender = Gender::Male, 545 .gender = static_cast<u32>(Gender::Male),
458 .age = Age::Old, 546 .age = static_cast<u32>(Age::Old),
459 .race = Race::White, 547 .race = static_cast<u32>(Race::White),
460 .values_count = 12, 548 .values_count = 12,
461 .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, 549 .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
462 }, 550 },
463 Service::Mii::RandomMiiData4{ 551 RandomMiiData4{
464 .gender = Gender::Male, 552 .gender = static_cast<u32>(Gender::Male),
465 .age = Age::Young, 553 .age = static_cast<u32>(Age::Young),
466 .race = Race::Asian, 554 .race = static_cast<u32>(Race::Asian),
467 .values_count = 12, 555 .values_count = 12,
468 .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, 556 .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
469 }, 557 },
470 Service::Mii::RandomMiiData4{ 558 RandomMiiData4{
471 .gender = Gender::Male, 559 .gender = static_cast<u32>(Gender::Male),
472 .age = Age::Normal, 560 .age = static_cast<u32>(Age::Normal),
473 .race = Race::Asian, 561 .race = static_cast<u32>(Race::Asian),
474 .values_count = 13, 562 .values_count = 13,
475 .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, 563 .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11},
476 }, 564 },
477 Service::Mii::RandomMiiData4{ 565 RandomMiiData4{
478 .gender = Gender::Male, 566 .gender = static_cast<u32>(Gender::Male),
479 .age = Age::Old, 567 .age = static_cast<u32>(Age::Old),
480 .race = Race::Asian, 568 .race = static_cast<u32>(Race::Asian),
481 .values_count = 12, 569 .values_count = 12,
482 .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, 570 .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
483 }, 571 },
484 Service::Mii::RandomMiiData4{ 572 RandomMiiData4{
485 .gender = Gender::Female, 573 .gender = static_cast<u32>(Gender::Female),
486 .age = Age::Young, 574 .age = static_cast<u32>(Age::Young),
487 .race = Race::Black, 575 .race = static_cast<u32>(Race::Black),
488 .values_count = 10, 576 .values_count = 10,
489 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, 577 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
490 }, 578 },
491 Service::Mii::RandomMiiData4{ 579 RandomMiiData4{
492 .gender = Gender::Female, 580 .gender = static_cast<u32>(Gender::Female),
493 .age = Age::Normal, 581 .age = static_cast<u32>(Age::Normal),
494 .race = Race::Black, 582 .race = static_cast<u32>(Race::Black),
495 .values_count = 10, 583 .values_count = 10,
496 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, 584 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
497 }, 585 },
498 Service::Mii::RandomMiiData4{ 586 RandomMiiData4{
499 .gender = Gender::Female, 587 .gender = static_cast<u32>(Gender::Female),
500 .age = Age::Old, 588 .age = static_cast<u32>(Age::Old),
501 .race = Race::Black, 589 .race = static_cast<u32>(Race::Black),
502 .values_count = 10, 590 .values_count = 10,
503 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, 591 .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
504 }, 592 },
505 Service::Mii::RandomMiiData4{ 593 RandomMiiData4{
506 .gender = Gender::Female, 594 .gender = static_cast<u32>(Gender::Female),
507 .age = Age::Young, 595 .age = static_cast<u32>(Age::Young),
508 .race = Race::White, 596 .race = static_cast<u32>(Race::White),
509 .values_count = 12, 597 .values_count = 12,
510 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, 598 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
511 }, 599 },
512 Service::Mii::RandomMiiData4{ 600 RandomMiiData4{
513 .gender = Gender::Female, 601 .gender = static_cast<u32>(Gender::Female),
514 .age = Age::Normal, 602 .age = static_cast<u32>(Age::Normal),
515 .race = Race::White, 603 .race = static_cast<u32>(Race::White),
516 .values_count = 12, 604 .values_count = 12,
517 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, 605 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
518 }, 606 },
519 Service::Mii::RandomMiiData4{ 607 RandomMiiData4{
520 .gender = Gender::Female, 608 .gender = static_cast<u32>(Gender::Female),
521 .age = Age::Old, 609 .age = static_cast<u32>(Age::Old),
522 .race = Race::White, 610 .race = static_cast<u32>(Race::White),
523 .values_count = 12, 611 .values_count = 12,
524 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, 612 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
525 }, 613 },
526 Service::Mii::RandomMiiData4{ 614 RandomMiiData4{
527 .gender = Gender::Female, 615 .gender = static_cast<u32>(Gender::Female),
528 .age = Age::Young, 616 .age = static_cast<u32>(Age::Young),
529 .race = Race::Asian, 617 .race = static_cast<u32>(Race::Asian),
530 .values_count = 12, 618 .values_count = 12,
531 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, 619 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
532 }, 620 },
533 Service::Mii::RandomMiiData4{ 621 RandomMiiData4{
534 .gender = Gender::Female, 622 .gender = static_cast<u32>(Gender::Female),
535 .age = Age::Normal, 623 .age = static_cast<u32>(Age::Normal),
536 .race = Race::Asian, 624 .race = static_cast<u32>(Race::Asian),
537 .values_count = 12, 625 .values_count = 12,
538 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, 626 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
539 }, 627 },
540 Service::Mii::RandomMiiData4{ 628 RandomMiiData4{
541 .gender = Gender::Female, 629 .gender = static_cast<u32>(Gender::Female),
542 .age = Age::Old, 630 .age = static_cast<u32>(Age::Old),
543 .race = Race::Asian, 631 .race = static_cast<u32>(Race::Asian),
544 .values_count = 12, 632 .values_count = 12,
545 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, 633 .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
546 }, 634 },
547}; 635};
548 636
549const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ 637const std::array<RandomMiiData3, 6> RandomMiiFacelineColor{
550 Service::Mii::RandomMiiData3{ 638 RandomMiiData3{
551 .arg_1 = 0, 639 .arg_1 = 0,
552 .arg_2 = 0, 640 .arg_2 = 0,
553 .values_count = 10, 641 .values_count = 10,
554 .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, 642 .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5},
555 }, 643 },
556 Service::Mii::RandomMiiData3{ 644 RandomMiiData3{
557 .arg_1 = 0, 645 .arg_1 = 0,
558 .arg_2 = 1, 646 .arg_2 = 1,
559 .values_count = 10, 647 .values_count = 10,
560 .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3}, 648 .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3},
561 }, 649 },
562 Service::Mii::RandomMiiData3{ 650 RandomMiiData3{
563 .arg_1 = 0, 651 .arg_1 = 0,
564 .arg_2 = 2, 652 .arg_2 = 2,
565 .values_count = 10, 653 .values_count = 10,
566 .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2}, 654 .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2},
567 }, 655 },
568 Service::Mii::RandomMiiData3{ 656 RandomMiiData3{
569 .arg_1 = 1, 657 .arg_1 = 1,
570 .arg_2 = 0, 658 .arg_2 = 0,
571 .values_count = 10, 659 .values_count = 10,
572 .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, 660 .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5},
573 }, 661 },
574 Service::Mii::RandomMiiData3{ 662 RandomMiiData3{
575 .arg_1 = 1, 663 .arg_1 = 1,
576 .arg_2 = 1, 664 .arg_2 = 1,
577 .values_count = 10, 665 .values_count = 10,
578 .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3}, 666 .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3},
579 }, 667 },
580 Service::Mii::RandomMiiData3{ 668 RandomMiiData3{
581 .arg_1 = 1, 669 .arg_1 = 1,
582 .arg_2 = 2, 670 .arg_2 = 2,
583 .values_count = 10, 671 .values_count = 10,
@@ -585,407 +673,407 @@ const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{
585 }, 673 },
586}; 674};
587 675
588const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{ 676const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle{
589 Service::Mii::RandomMiiData4{ 677 RandomMiiData4{
590 .gender = Gender::Male, 678 .gender = static_cast<u32>(Gender::Male),
591 .age = Age::Young, 679 .age = static_cast<u32>(Age::Young),
592 .race = Race::Black, 680 .race = static_cast<u32>(Race::Black),
593 .values_count = 20, 681 .values_count = 20,
594 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, 682 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
595 }, 683 },
596 Service::Mii::RandomMiiData4{ 684 RandomMiiData4{
597 .gender = Gender::Male, 685 .gender = static_cast<u32>(Gender::Male),
598 .age = Age::Normal, 686 .age = static_cast<u32>(Age::Normal),
599 .race = Race::Black, 687 .race = static_cast<u32>(Race::Black),
600 .values_count = 20, 688 .values_count = 20,
601 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, 689 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
602 }, 690 },
603 Service::Mii::RandomMiiData4{ 691 RandomMiiData4{
604 .gender = Gender::Male, 692 .gender = static_cast<u32>(Gender::Male),
605 .age = Age::Old, 693 .age = static_cast<u32>(Age::Old),
606 .race = Race::Black, 694 .race = static_cast<u32>(Race::Black),
607 .values_count = 20, 695 .values_count = 20,
608 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8}, 696 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8},
609 }, 697 },
610 Service::Mii::RandomMiiData4{ 698 RandomMiiData4{
611 .gender = Gender::Male, 699 .gender = static_cast<u32>(Gender::Male),
612 .age = Age::Young, 700 .age = static_cast<u32>(Age::Young),
613 .race = Race::White, 701 .race = static_cast<u32>(Race::White),
614 .values_count = 20, 702 .values_count = 20,
615 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, 703 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9},
616 }, 704 },
617 Service::Mii::RandomMiiData4{ 705 RandomMiiData4{
618 .gender = Gender::Male, 706 .gender = static_cast<u32>(Gender::Male),
619 .age = Age::Normal, 707 .age = static_cast<u32>(Age::Normal),
620 .race = Race::White, 708 .race = static_cast<u32>(Race::White),
621 .values_count = 20, 709 .values_count = 20,
622 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, 710 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9},
623 }, 711 },
624 Service::Mii::RandomMiiData4{ 712 RandomMiiData4{
625 .gender = Gender::Male, 713 .gender = static_cast<u32>(Gender::Male),
626 .age = Age::Old, 714 .age = static_cast<u32>(Age::Old),
627 .race = Race::White, 715 .race = static_cast<u32>(Race::White),
628 .values_count = 20, 716 .values_count = 20,
629 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 717 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
630 }, 718 },
631 Service::Mii::RandomMiiData4{ 719 RandomMiiData4{
632 .gender = Gender::Male, 720 .gender = static_cast<u32>(Gender::Male),
633 .age = Age::Young, 721 .age = static_cast<u32>(Age::Young),
634 .race = Race::Asian, 722 .race = static_cast<u32>(Race::Asian),
635 .values_count = 20, 723 .values_count = 20,
636 .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, 724 .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
637 }, 725 },
638 Service::Mii::RandomMiiData4{ 726 RandomMiiData4{
639 .gender = Gender::Male, 727 .gender = static_cast<u32>(Gender::Male),
640 .age = Age::Normal, 728 .age = static_cast<u32>(Age::Normal),
641 .race = Race::Asian, 729 .race = static_cast<u32>(Race::Asian),
642 .values_count = 20, 730 .values_count = 20,
643 .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, 731 .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
644 }, 732 },
645 Service::Mii::RandomMiiData4{ 733 RandomMiiData4{
646 .gender = Gender::Male, 734 .gender = static_cast<u32>(Gender::Male),
647 .age = Age::Old, 735 .age = static_cast<u32>(Age::Old),
648 .race = Race::Asian, 736 .race = static_cast<u32>(Race::Asian),
649 .values_count = 20, 737 .values_count = 20,
650 .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, 738 .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
651 }, 739 },
652 Service::Mii::RandomMiiData4{ 740 RandomMiiData4{
653 .gender = Gender::Female, 741 .gender = static_cast<u32>(Gender::Female),
654 .age = Age::Young, 742 .age = static_cast<u32>(Age::Young),
655 .race = Race::Black, 743 .race = static_cast<u32>(Race::Black),
656 .values_count = 20, 744 .values_count = 20,
657 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, 745 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
658 }, 746 },
659 Service::Mii::RandomMiiData4{ 747 RandomMiiData4{
660 .gender = Gender::Female, 748 .gender = static_cast<u32>(Gender::Female),
661 .age = Age::Normal, 749 .age = static_cast<u32>(Age::Normal),
662 .race = Race::Black, 750 .race = static_cast<u32>(Race::Black),
663 .values_count = 20, 751 .values_count = 20,
664 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, 752 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
665 }, 753 },
666 Service::Mii::RandomMiiData4{ 754 RandomMiiData4{
667 .gender = Gender::Female, 755 .gender = static_cast<u32>(Gender::Female),
668 .age = Age::Old, 756 .age = static_cast<u32>(Age::Old),
669 .race = Race::Black, 757 .race = static_cast<u32>(Race::Black),
670 .values_count = 20, 758 .values_count = 20,
671 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, 759 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
672 }, 760 },
673 Service::Mii::RandomMiiData4{ 761 RandomMiiData4{
674 .gender = Gender::Female, 762 .gender = static_cast<u32>(Gender::Female),
675 .age = Age::Young, 763 .age = static_cast<u32>(Age::Young),
676 .race = Race::White, 764 .race = static_cast<u32>(Race::White),
677 .values_count = 20, 765 .values_count = 20,
678 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, 766 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8},
679 }, 767 },
680 Service::Mii::RandomMiiData4{ 768 RandomMiiData4{
681 .gender = Gender::Female, 769 .gender = static_cast<u32>(Gender::Female),
682 .age = Age::Normal, 770 .age = static_cast<u32>(Age::Normal),
683 .race = Race::White, 771 .race = static_cast<u32>(Race::White),
684 .values_count = 20, 772 .values_count = 20,
685 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, 773 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8},
686 }, 774 },
687 Service::Mii::RandomMiiData4{ 775 RandomMiiData4{
688 .gender = Gender::Female, 776 .gender = static_cast<u32>(Gender::Female),
689 .age = Age::Old, 777 .age = static_cast<u32>(Age::Old),
690 .race = Race::White, 778 .race = static_cast<u32>(Race::White),
691 .values_count = 20, 779 .values_count = 20,
692 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4}, 780 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4},
693 }, 781 },
694 Service::Mii::RandomMiiData4{ 782 RandomMiiData4{
695 .gender = Gender::Female, 783 .gender = static_cast<u32>(Gender::Female),
696 .age = Age::Young, 784 .age = static_cast<u32>(Age::Young),
697 .race = Race::Asian, 785 .race = static_cast<u32>(Race::Asian),
698 .values_count = 20, 786 .values_count = 20,
699 .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, 787 .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
700 }, 788 },
701 Service::Mii::RandomMiiData4{ 789 RandomMiiData4{
702 .gender = Gender::Female, 790 .gender = static_cast<u32>(Gender::Female),
703 .age = Age::Normal, 791 .age = static_cast<u32>(Age::Normal),
704 .race = Race::Asian, 792 .race = static_cast<u32>(Race::Asian),
705 .values_count = 20, 793 .values_count = 20,
706 .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, 794 .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
707 }, 795 },
708 Service::Mii::RandomMiiData4{ 796 RandomMiiData4{
709 .gender = Gender::Female, 797 .gender = static_cast<u32>(Gender::Female),
710 .age = Age::Old, 798 .age = static_cast<u32>(Age::Old),
711 .race = Race::Asian, 799 .race = static_cast<u32>(Race::Asian),
712 .values_count = 20, 800 .values_count = 20,
713 .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, 801 .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
714 }, 802 },
715}; 803};
716 804
717const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{ 805const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup{
718 Service::Mii::RandomMiiData4{ 806 RandomMiiData4{
719 .gender = Gender::Male, 807 .gender = static_cast<u32>(Gender::Male),
720 .age = Age::Young, 808 .age = static_cast<u32>(Age::Young),
721 .race = Race::Black, 809 .race = static_cast<u32>(Race::Black),
722 .values_count = 20, 810 .values_count = 20,
723 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 811 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
724 }, 812 },
725 Service::Mii::RandomMiiData4{ 813 RandomMiiData4{
726 .gender = Gender::Male, 814 .gender = static_cast<u32>(Gender::Male),
727 .age = Age::Normal, 815 .age = static_cast<u32>(Age::Normal),
728 .race = Race::Black, 816 .race = static_cast<u32>(Race::Black),
729 .values_count = 20, 817 .values_count = 20,
730 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9}, 818 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9},
731 }, 819 },
732 Service::Mii::RandomMiiData4{ 820 RandomMiiData4{
733 .gender = Gender::Male, 821 .gender = static_cast<u32>(Gender::Male),
734 .age = Age::Old, 822 .age = static_cast<u32>(Age::Old),
735 .race = Race::Black, 823 .race = static_cast<u32>(Race::Black),
736 .values_count = 20, 824 .values_count = 20,
737 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9}, 825 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9},
738 }, 826 },
739 Service::Mii::RandomMiiData4{ 827 RandomMiiData4{
740 .gender = Gender::Male, 828 .gender = static_cast<u32>(Gender::Male),
741 .age = Age::Young, 829 .age = static_cast<u32>(Age::Young),
742 .race = Race::White, 830 .race = static_cast<u32>(Race::White),
743 .values_count = 20, 831 .values_count = 20,
744 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, 832 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
745 }, 833 },
746 Service::Mii::RandomMiiData4{ 834 RandomMiiData4{
747 .gender = Gender::Male, 835 .gender = static_cast<u32>(Gender::Male),
748 .age = Age::Normal, 836 .age = static_cast<u32>(Age::Normal),
749 .race = Race::White, 837 .race = static_cast<u32>(Race::White),
750 .values_count = 20, 838 .values_count = 20,
751 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, 839 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
752 }, 840 },
753 Service::Mii::RandomMiiData4{ 841 RandomMiiData4{
754 .gender = Gender::Male, 842 .gender = static_cast<u32>(Gender::Male),
755 .age = Age::Old, 843 .age = static_cast<u32>(Age::Old),
756 .race = Race::White, 844 .race = static_cast<u32>(Race::White),
757 .values_count = 20, 845 .values_count = 20,
758 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, 846 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
759 }, 847 },
760 Service::Mii::RandomMiiData4{ 848 RandomMiiData4{
761 .gender = Gender::Male, 849 .gender = static_cast<u32>(Gender::Male),
762 .age = Age::Young, 850 .age = static_cast<u32>(Age::Young),
763 .race = Race::Asian, 851 .race = static_cast<u32>(Race::Asian),
764 .values_count = 20, 852 .values_count = 20,
765 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, 853 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
766 }, 854 },
767 Service::Mii::RandomMiiData4{ 855 RandomMiiData4{
768 .gender = Gender::Male, 856 .gender = static_cast<u32>(Gender::Male),
769 .age = Age::Normal, 857 .age = static_cast<u32>(Age::Normal),
770 .race = Race::Asian, 858 .race = static_cast<u32>(Race::Asian),
771 .values_count = 20, 859 .values_count = 20,
772 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, 860 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
773 }, 861 },
774 Service::Mii::RandomMiiData4{ 862 RandomMiiData4{
775 .gender = Gender::Male, 863 .gender = static_cast<u32>(Gender::Male),
776 .age = Age::Old, 864 .age = static_cast<u32>(Age::Old),
777 .race = Race::Asian, 865 .race = static_cast<u32>(Race::Asian),
778 .values_count = 20, 866 .values_count = 20,
779 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, 867 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
780 }, 868 },
781 Service::Mii::RandomMiiData4{ 869 RandomMiiData4{
782 .gender = Gender::Female, 870 .gender = static_cast<u32>(Gender::Female),
783 .age = Age::Young, 871 .age = static_cast<u32>(Age::Young),
784 .race = Race::Black, 872 .race = static_cast<u32>(Race::Black),
785 .values_count = 20, 873 .values_count = 20,
786 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2}, 874 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2},
787 }, 875 },
788 Service::Mii::RandomMiiData4{ 876 RandomMiiData4{
789 .gender = Gender::Female, 877 .gender = static_cast<u32>(Gender::Female),
790 .age = Age::Normal, 878 .age = static_cast<u32>(Age::Normal),
791 .race = Race::Black, 879 .race = static_cast<u32>(Race::Black),
792 .values_count = 20, 880 .values_count = 20,
793 .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, 881 .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9},
794 }, 882 },
795 Service::Mii::RandomMiiData4{ 883 RandomMiiData4{
796 .gender = Gender::Female, 884 .gender = static_cast<u32>(Gender::Female),
797 .age = Age::Old, 885 .age = static_cast<u32>(Age::Old),
798 .race = Race::Black, 886 .race = static_cast<u32>(Race::Black),
799 .values_count = 20, 887 .values_count = 20,
800 .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, 888 .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9},
801 }, 889 },
802 Service::Mii::RandomMiiData4{ 890 RandomMiiData4{
803 .gender = Gender::Female, 891 .gender = static_cast<u32>(Gender::Female),
804 .age = Age::Young, 892 .age = static_cast<u32>(Age::Young),
805 .race = Race::White, 893 .race = static_cast<u32>(Race::White),
806 .values_count = 20, 894 .values_count = 20,
807 .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9}, 895 .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9},
808 }, 896 },
809 Service::Mii::RandomMiiData4{ 897 RandomMiiData4{
810 .gender = Gender::Female, 898 .gender = static_cast<u32>(Gender::Female),
811 .age = Age::Normal, 899 .age = static_cast<u32>(Age::Normal),
812 .race = Race::White, 900 .race = static_cast<u32>(Race::White),
813 .values_count = 20, 901 .values_count = 20,
814 .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, 902 .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9},
815 }, 903 },
816 Service::Mii::RandomMiiData4{ 904 RandomMiiData4{
817 .gender = Gender::Female, 905 .gender = static_cast<u32>(Gender::Female),
818 .age = Age::Old, 906 .age = static_cast<u32>(Age::Old),
819 .race = Race::White, 907 .race = static_cast<u32>(Race::White),
820 .values_count = 20, 908 .values_count = 20,
821 .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9}, 909 .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9},
822 }, 910 },
823 Service::Mii::RandomMiiData4{ 911 RandomMiiData4{
824 .gender = Gender::Female, 912 .gender = static_cast<u32>(Gender::Female),
825 .age = Age::Young, 913 .age = static_cast<u32>(Age::Young),
826 .race = Race::Asian, 914 .race = static_cast<u32>(Race::Asian),
827 .values_count = 20, 915 .values_count = 20,
828 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 916 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
829 }, 917 },
830 Service::Mii::RandomMiiData4{ 918 RandomMiiData4{
831 .gender = Gender::Female, 919 .gender = static_cast<u32>(Gender::Female),
832 .age = Age::Normal, 920 .age = static_cast<u32>(Age::Normal),
833 .race = Race::Asian, 921 .race = static_cast<u32>(Race::Asian),
834 .values_count = 20, 922 .values_count = 20,
835 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 923 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
836 }, 924 },
837 Service::Mii::RandomMiiData4{ 925 RandomMiiData4{
838 .gender = Gender::Female, 926 .gender = static_cast<u32>(Gender::Female),
839 .age = Age::Old, 927 .age = static_cast<u32>(Age::Old),
840 .race = Race::Asian, 928 .race = static_cast<u32>(Race::Asian),
841 .values_count = 20, 929 .values_count = 20,
842 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 930 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
843 }, 931 },
844}; 932};
845 933
846const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ 934const std::array<RandomMiiData4, 18> RandomMiiHairType{
847 Service::Mii::RandomMiiData4{ 935 RandomMiiData4{
848 .gender = Gender::Male, 936 .gender = static_cast<u32>(Gender::Male),
849 .age = Age::Young, 937 .age = static_cast<u32>(Age::Young),
850 .race = Race::Black, 938 .race = static_cast<u32>(Race::Black),
851 .values_count = 30, 939 .values_count = 30,
852 .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 940 .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45,
853 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89}, 941 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89},
854 }, 942 },
855 Service::Mii::RandomMiiData4{ 943 RandomMiiData4{
856 .gender = Gender::Male, 944 .gender = static_cast<u32>(Gender::Male),
857 .age = Age::Normal, 945 .age = static_cast<u32>(Age::Normal),
858 .race = Race::Black, 946 .race = static_cast<u32>(Race::Black),
859 .values_count = 31, 947 .values_count = 31,
860 .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, 948 .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47,
861 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, 949 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87},
862 }, 950 },
863 Service::Mii::RandomMiiData4{ 951 RandomMiiData4{
864 .gender = Gender::Male, 952 .gender = static_cast<u32>(Gender::Male),
865 .age = Age::Old, 953 .age = static_cast<u32>(Age::Old),
866 .race = Race::Black, 954 .race = static_cast<u32>(Race::Black),
867 .values_count = 31, 955 .values_count = 31,
868 .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, 956 .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47,
869 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, 957 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87},
870 }, 958 },
871 Service::Mii::RandomMiiData4{ 959 RandomMiiData4{
872 .gender = Gender::Male, 960 .gender = static_cast<u32>(Gender::Male),
873 .age = Age::Young, 961 .age = static_cast<u32>(Age::Young),
874 .race = Race::White, 962 .race = static_cast<u32>(Race::White),
875 .values_count = 38, 963 .values_count = 38,
876 .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50, 964 .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50,
877 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89}, 965 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89},
878 }, 966 },
879 Service::Mii::RandomMiiData4{ 967 RandomMiiData4{
880 .gender = Gender::Male, 968 .gender = static_cast<u32>(Gender::Male),
881 .age = Age::Normal, 969 .age = static_cast<u32>(Age::Normal),
882 .race = Race::White, 970 .race = static_cast<u32>(Race::White),
883 .values_count = 39, 971 .values_count = 39,
884 .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, 972 .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51,
885 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, 973 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87},
886 }, 974 },
887 Service::Mii::RandomMiiData4{ 975 RandomMiiData4{
888 .gender = Gender::Male, 976 .gender = static_cast<u32>(Gender::Male),
889 .age = Age::Old, 977 .age = static_cast<u32>(Age::Old),
890 .race = Race::White, 978 .race = static_cast<u32>(Race::White),
891 .values_count = 39, 979 .values_count = 39,
892 .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, 980 .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51,
893 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, 981 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87},
894 }, 982 },
895 Service::Mii::RandomMiiData4{ 983 RandomMiiData4{
896 .gender = Gender::Male, 984 .gender = static_cast<u32>(Gender::Male),
897 .age = Age::Young, 985 .age = static_cast<u32>(Age::Young),
898 .race = Race::Asian, 986 .race = static_cast<u32>(Race::Asian),
899 .values_count = 18, 987 .values_count = 18,
900 .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, 988 .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
901 }, 989 },
902 Service::Mii::RandomMiiData4{ 990 RandomMiiData4{
903 .gender = Gender::Male, 991 .gender = static_cast<u32>(Gender::Male),
904 .age = Age::Normal, 992 .age = static_cast<u32>(Age::Normal),
905 .race = Race::Asian, 993 .race = static_cast<u32>(Race::Asian),
906 .values_count = 19, 994 .values_count = 19,
907 .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, 995 .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
908 }, 996 },
909 Service::Mii::RandomMiiData4{ 997 RandomMiiData4{
910 .gender = Gender::Male, 998 .gender = static_cast<u32>(Gender::Male),
911 .age = Age::Old, 999 .age = static_cast<u32>(Age::Old),
912 .race = Race::Asian, 1000 .race = static_cast<u32>(Race::Asian),
913 .values_count = 19, 1001 .values_count = 19,
914 .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, 1002 .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
915 }, 1003 },
916 Service::Mii::RandomMiiData4{ 1004 RandomMiiData4{
917 .gender = Gender::Female, 1005 .gender = static_cast<u32>(Gender::Female),
918 .age = Age::Young, 1006 .age = static_cast<u32>(Age::Young),
919 .race = Race::Black, 1007 .race = static_cast<u32>(Race::Black),
920 .values_count = 39, 1008 .values_count = 39,
921 .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1009 .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
922 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85}, 1010 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85},
923 }, 1011 },
924 Service::Mii::RandomMiiData4{ 1012 RandomMiiData4{
925 .gender = Gender::Female, 1013 .gender = static_cast<u32>(Gender::Female),
926 .age = Age::Normal, 1014 .age = static_cast<u32>(Age::Normal),
927 .race = Race::Black, 1015 .race = static_cast<u32>(Race::Black),
928 .values_count = 42, 1016 .values_count = 42,
929 .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 1017 .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14,
930 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, 1018 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50,
931 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, 1019 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87},
932 }, 1020 },
933 Service::Mii::RandomMiiData4{ 1021 RandomMiiData4{
934 .gender = Gender::Female, 1022 .gender = static_cast<u32>(Gender::Female),
935 .age = Age::Old, 1023 .age = static_cast<u32>(Age::Old),
936 .race = Race::Black, 1024 .race = static_cast<u32>(Race::Black),
937 .values_count = 42, 1025 .values_count = 42,
938 .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 1026 .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14,
939 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, 1027 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50,
940 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, 1028 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87},
941 }, 1029 },
942 Service::Mii::RandomMiiData4{ 1030 RandomMiiData4{
943 .gender = Gender::Female, 1031 .gender = static_cast<u32>(Gender::Female),
944 .age = Age::Young, 1032 .age = static_cast<u32>(Age::Young),
945 .race = Race::White, 1033 .race = static_cast<u32>(Race::White),
946 .values_count = 44, 1034 .values_count = 44,
947 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1035 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
948 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50, 1036 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50,
949 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86}, 1037 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86},
950 }, 1038 },
951 Service::Mii::RandomMiiData4{ 1039 RandomMiiData4{
952 .gender = Gender::Female, 1040 .gender = static_cast<u32>(Gender::Female),
953 .age = Age::Normal, 1041 .age = static_cast<u32>(Age::Normal),
954 .race = Race::White, 1042 .race = static_cast<u32>(Race::White),
955 .values_count = 44, 1043 .values_count = 44,
956 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1044 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
957 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, 1045 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58,
958 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, 1046 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85},
959 }, 1047 },
960 Service::Mii::RandomMiiData4{ 1048 RandomMiiData4{
961 .gender = Gender::Female, 1049 .gender = static_cast<u32>(Gender::Female),
962 .age = Age::Old, 1050 .age = static_cast<u32>(Age::Old),
963 .race = Race::White, 1051 .race = static_cast<u32>(Race::White),
964 .values_count = 44, 1052 .values_count = 44,
965 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1053 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
966 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, 1054 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58,
967 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, 1055 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85},
968 }, 1056 },
969 Service::Mii::RandomMiiData4{ 1057 RandomMiiData4{
970 .gender = Gender::Female, 1058 .gender = static_cast<u32>(Gender::Female),
971 .age = Age::Young, 1059 .age = static_cast<u32>(Age::Young),
972 .race = Race::Asian, 1060 .race = static_cast<u32>(Race::Asian),
973 .values_count = 24, 1061 .values_count = 24,
974 .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 1062 .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14,
975 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83}, 1063 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83},
976 }, 1064 },
977 Service::Mii::RandomMiiData4{ 1065 RandomMiiData4{
978 .gender = Gender::Female, 1066 .gender = static_cast<u32>(Gender::Female),
979 .age = Age::Normal, 1067 .age = static_cast<u32>(Age::Normal),
980 .race = Race::Asian, 1068 .race = static_cast<u32>(Race::Asian),
981 .values_count = 27, 1069 .values_count = 27,
982 .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 1070 .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17,
983 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, 1071 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85},
984 }, 1072 },
985 Service::Mii::RandomMiiData4{ 1073 RandomMiiData4{
986 .gender = Gender::Female, 1074 .gender = static_cast<u32>(Gender::Female),
987 .age = Age::Old, 1075 .age = static_cast<u32>(Age::Old),
988 .race = Race::Asian, 1076 .race = static_cast<u32>(Race::Asian),
989 .values_count = 27, 1077 .values_count = 27,
990 .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 1078 .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17,
991 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, 1079 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85},
@@ -993,55 +1081,55 @@ const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{
993}; 1081};
994 1082
995const std::array<RandomMiiData3, 9> RandomMiiHairColor{ 1083const std::array<RandomMiiData3, 9> RandomMiiHairColor{
996 Service::Mii::RandomMiiData3{ 1084 RandomMiiData3{
997 .arg_1 = 0, 1085 .arg_1 = 0,
998 .arg_2 = 0, 1086 .arg_2 = 0,
999 .values_count = 20, 1087 .values_count = 20,
1000 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 1088 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
1001 }, 1089 },
1002 Service::Mii::RandomMiiData3{ 1090 RandomMiiData3{
1003 .arg_1 = 0, 1091 .arg_1 = 0,
1004 .arg_2 = 1, 1092 .arg_2 = 1,
1005 .values_count = 20, 1093 .values_count = 20,
1006 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 1094 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
1007 }, 1095 },
1008 Service::Mii::RandomMiiData3{ 1096 RandomMiiData3{
1009 .arg_1 = 0, 1097 .arg_1 = 0,
1010 .arg_2 = 2, 1098 .arg_2 = 2,
1011 .values_count = 20, 1099 .values_count = 20,
1012 .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, 1100 .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
1013 }, 1101 },
1014 Service::Mii::RandomMiiData3{ 1102 RandomMiiData3{
1015 .arg_1 = 1, 1103 .arg_1 = 1,
1016 .arg_2 = 0, 1104 .arg_2 = 0,
1017 .values_count = 20, 1105 .values_count = 20,
1018 .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7}, 1106 .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7},
1019 }, 1107 },
1020 Service::Mii::RandomMiiData3{ 1108 RandomMiiData3{
1021 .arg_1 = 1, 1109 .arg_1 = 1,
1022 .arg_2 = 1, 1110 .arg_2 = 1,
1023 .values_count = 20, 1111 .values_count = 20,
1024 .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, 1112 .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7},
1025 }, 1113 },
1026 Service::Mii::RandomMiiData3{ 1114 RandomMiiData3{
1027 .arg_1 = 1, 1115 .arg_1 = 1,
1028 .arg_2 = 2, 1116 .arg_2 = 2,
1029 .values_count = 20, 1117 .values_count = 20,
1030 .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, 1118 .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7},
1031 }, 1119 },
1032 Service::Mii::RandomMiiData3{ 1120 RandomMiiData3{
1033 .arg_1 = 2, 1121 .arg_1 = 2,
1034 .arg_2 = 0, 1122 .arg_2 = 0,
1035 .values_count = 20, 1123 .values_count = 20,
1036 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, 1124 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
1037 }, 1125 },
1038 Service::Mii::RandomMiiData3{ 1126 RandomMiiData3{
1039 .arg_1 = 2, 1127 .arg_1 = 2,
1040 .arg_2 = 1, 1128 .arg_2 = 1,
1041 .values_count = 20, 1129 .values_count = 20,
1042 .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}, 1130 .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3},
1043 }, 1131 },
1044 Service::Mii::RandomMiiData3{ 1132 RandomMiiData3{
1045 .arg_1 = 2, 1133 .arg_1 = 2,
1046 .arg_2 = 2, 1134 .arg_2 = 2,
1047 .values_count = 20, 1135 .values_count = 20,
@@ -1049,598 +1137,642 @@ const std::array<RandomMiiData3, 9> RandomMiiHairColor{
1049 }, 1137 },
1050}; 1138};
1051 1139
1052const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{ 1140const std::array<RandomMiiData4, 18> RandomMiiEyeType{
1053 Service::Mii::RandomMiiData4{ 1141 RandomMiiData4{
1054 .gender = Gender::Male, 1142 .gender = static_cast<u32>(Gender::Male),
1055 .age = Age::Young, 1143 .age = static_cast<u32>(Age::Young),
1056 .race = Race::Black, 1144 .race = static_cast<u32>(Race::Black),
1057 .values_count = 26, 1145 .values_count = 26,
1058 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, 1146 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27,
1059 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, 1147 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57},
1060 }, 1148 },
1061 Service::Mii::RandomMiiData4{ 1149 RandomMiiData4{
1062 .gender = Gender::Male, 1150 .gender = static_cast<u32>(Gender::Male),
1063 .age = Age::Normal, 1151 .age = static_cast<u32>(Age::Normal),
1064 .race = Race::Black, 1152 .race = static_cast<u32>(Race::Black),
1065 .values_count = 26, 1153 .values_count = 26,
1066 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, 1154 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27,
1067 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, 1155 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57},
1068 }, 1156 },
1069 Service::Mii::RandomMiiData4{ 1157 RandomMiiData4{
1070 .gender = Gender::Male, 1158 .gender = static_cast<u32>(Gender::Male),
1071 .age = Age::Old, 1159 .age = static_cast<u32>(Age::Old),
1072 .race = Race::Black, 1160 .race = static_cast<u32>(Race::Black),
1073 .values_count = 27, 1161 .values_count = 27,
1074 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27, 1162 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27,
1075 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57}, 1163 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57},
1076 }, 1164 },
1077 Service::Mii::RandomMiiData4{ 1165 RandomMiiData4{
1078 .gender = Gender::Male, 1166 .gender = static_cast<u32>(Gender::Male),
1079 .age = Age::Young, 1167 .age = static_cast<u32>(Age::Young),
1080 .race = Race::White, 1168 .race = static_cast<u32>(Race::White),
1081 .values_count = 35, 1169 .values_count = 35,
1082 .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, 1170 .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29,
1083 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, 1171 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57},
1084 }, 1172 },
1085 Service::Mii::RandomMiiData4{ 1173 RandomMiiData4{
1086 .gender = Gender::Male, 1174 .gender = static_cast<u32>(Gender::Male),
1087 .age = Age::Normal, 1175 .age = static_cast<u32>(Age::Normal),
1088 .race = Race::White, 1176 .race = static_cast<u32>(Race::White),
1089 .values_count = 35, 1177 .values_count = 35,
1090 .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, 1178 .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29,
1091 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, 1179 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57},
1092 }, 1180 },
1093 Service::Mii::RandomMiiData4{ 1181 RandomMiiData4{
1094 .gender = Gender::Male, 1182 .gender = static_cast<u32>(Gender::Male),
1095 .age = Age::Old, 1183 .age = static_cast<u32>(Age::Old),
1096 .race = Race::White, 1184 .race = static_cast<u32>(Race::White),
1097 .values_count = 35, 1185 .values_count = 35,
1098 .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29, 1186 .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29,
1099 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57}, 1187 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57},
1100 }, 1188 },
1101 Service::Mii::RandomMiiData4{ 1189 RandomMiiData4{
1102 .gender = Gender::Male, 1190 .gender = static_cast<u32>(Gender::Male),
1103 .age = Age::Young, 1191 .age = static_cast<u32>(Age::Young),
1104 .race = Race::Asian, 1192 .race = static_cast<u32>(Race::Asian),
1105 .values_count = 30, 1193 .values_count = 30,
1106 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, 1194 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21,
1107 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, 1195 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57},
1108 }, 1196 },
1109 Service::Mii::RandomMiiData4{ 1197 RandomMiiData4{
1110 .gender = Gender::Male, 1198 .gender = static_cast<u32>(Gender::Male),
1111 .age = Age::Normal, 1199 .age = static_cast<u32>(Age::Normal),
1112 .race = Race::Asian, 1200 .race = static_cast<u32>(Race::Asian),
1113 .values_count = 30, 1201 .values_count = 30,
1114 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, 1202 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21,
1115 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, 1203 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57},
1116 }, 1204 },
1117 Service::Mii::RandomMiiData4{ 1205 RandomMiiData4{
1118 .gender = Gender::Male, 1206 .gender = static_cast<u32>(Gender::Male),
1119 .age = Age::Old, 1207 .age = static_cast<u32>(Age::Old),
1120 .race = Race::Asian, 1208 .race = static_cast<u32>(Race::Asian),
1121 .values_count = 30, 1209 .values_count = 30,
1122 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22, 1210 .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22,
1123 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57}, 1211 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57},
1124 }, 1212 },
1125 Service::Mii::RandomMiiData4{ 1213 RandomMiiData4{
1126 .gender = Gender::Female, 1214 .gender = static_cast<u32>(Gender::Female),
1127 .age = Age::Young, 1215 .age = static_cast<u32>(Age::Young),
1128 .race = Race::Black, 1216 .race = static_cast<u32>(Race::Black),
1129 .values_count = 39, 1217 .values_count = 39,
1130 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 1218 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27,
1131 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, 1219 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
1132 }, 1220 },
1133 Service::Mii::RandomMiiData4{ 1221 RandomMiiData4{
1134 .gender = Gender::Female, 1222 .gender = static_cast<u32>(Gender::Female),
1135 .age = Age::Normal, 1223 .age = static_cast<u32>(Age::Normal),
1136 .race = Race::Black, 1224 .race = static_cast<u32>(Race::Black),
1137 .values_count = 39, 1225 .values_count = 39,
1138 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 1226 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27,
1139 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, 1227 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
1140 }, 1228 },
1141 Service::Mii::RandomMiiData4{ 1229 RandomMiiData4{
1142 .gender = Gender::Female, 1230 .gender = static_cast<u32>(Gender::Female),
1143 .age = Age::Old, 1231 .age = static_cast<u32>(Age::Old),
1144 .race = Race::Black, 1232 .race = static_cast<u32>(Race::Black),
1145 .values_count = 40, 1233 .values_count = 40,
1146 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, 1234 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26,
1147 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, 1235 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
1148 }, 1236 },
1149 Service::Mii::RandomMiiData4{ 1237 RandomMiiData4{
1150 .gender = Gender::Female, 1238 .gender = static_cast<u32>(Gender::Female),
1151 .age = Age::Young, 1239 .age = static_cast<u32>(Age::Young),
1152 .race = Race::White, 1240 .race = static_cast<u32>(Race::White),
1153 .values_count = 46, 1241 .values_count = 46,
1154 .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 1242 .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
1155 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, 1243 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37,
1156 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, 1244 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
1157 }, 1245 },
1158 Service::Mii::RandomMiiData4{ 1246 RandomMiiData4{
1159 .gender = Gender::Female, 1247 .gender = static_cast<u32>(Gender::Female),
1160 .age = Age::Normal, 1248 .age = static_cast<u32>(Age::Normal),
1161 .race = Race::White, 1249 .race = static_cast<u32>(Race::White),
1162 .values_count = 46, 1250 .values_count = 46,
1163 .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 1251 .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
1164 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, 1252 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37,
1165 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, 1253 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
1166 }, 1254 },
1167 Service::Mii::RandomMiiData4{ 1255 RandomMiiData4{
1168 .gender = Gender::Female, 1256 .gender = static_cast<u32>(Gender::Female),
1169 .age = Age::Old, 1257 .age = static_cast<u32>(Age::Old),
1170 .race = Race::White, 1258 .race = static_cast<u32>(Race::White),
1171 .values_count = 46, 1259 .values_count = 46,
1172 .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 1260 .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18,
1173 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, 1261 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37,
1174 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, 1262 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
1175 }, 1263 },
1176 Service::Mii::RandomMiiData4{ 1264 RandomMiiData4{
1177 .gender = Gender::Female, 1265 .gender = static_cast<u32>(Gender::Female),
1178 .age = Age::Young, 1266 .age = static_cast<u32>(Age::Young),
1179 .race = Race::Asian, 1267 .race = static_cast<u32>(Race::Asian),
1180 .values_count = 34, 1268 .values_count = 34,
1181 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 1269 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23,
1182 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, 1270 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
1183 }, 1271 },
1184 Service::Mii::RandomMiiData4{ 1272 RandomMiiData4{
1185 .gender = Gender::Female, 1273 .gender = static_cast<u32>(Gender::Female),
1186 .age = Age::Normal, 1274 .age = static_cast<u32>(Age::Normal),
1187 .race = Race::Asian, 1275 .race = static_cast<u32>(Race::Asian),
1188 .values_count = 34, 1276 .values_count = 34,
1189 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 1277 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23,
1190 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, 1278 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
1191 }, 1279 },
1192 Service::Mii::RandomMiiData4{ 1280 RandomMiiData4{
1193 .gender = Gender::Female, 1281 .gender = static_cast<u32>(Gender::Female),
1194 .age = Age::Old, 1282 .age = static_cast<u32>(Age::Old),
1195 .race = Race::Asian, 1283 .race = static_cast<u32>(Race::Asian),
1196 .values_count = 35, 1284 .values_count = 35,
1197 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 1285 .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24,
1198 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, 1286 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
1199 }, 1287 },
1200}; 1288};
1201 1289
1202const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{ 1290const std::array<RandomMiiData2, 3> RandomMiiEyeColor{
1203 Service::Mii::RandomMiiData2{ 1291 RandomMiiData2{
1204 .arg_1 = 0, 1292 .arg_1 = 0,
1205 .values_count = 10, 1293 .values_count = 10,
1206 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 1294 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
1207 }, 1295 },
1208 Service::Mii::RandomMiiData2{ 1296 RandomMiiData2{
1209 .arg_1 = 1, 1297 .arg_1 = 1,
1210 .values_count = 10, 1298 .values_count = 10,
1211 .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5}, 1299 .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5},
1212 }, 1300 },
1213 Service::Mii::RandomMiiData2{ 1301 RandomMiiData2{
1214 .arg_1 = 2, 1302 .arg_1 = 2,
1215 .values_count = 10, 1303 .values_count = 10,
1216 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 1304 .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
1217 }, 1305 },
1218}; 1306};
1219 1307
1220const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{ 1308const std::array<RandomMiiData4, 18> RandomMiiEyebrowType{
1221 Service::Mii::RandomMiiData4{ 1309 RandomMiiData4{
1222 .gender = Gender::Male, 1310 .gender = static_cast<u32>(Gender::Male),
1223 .age = Age::Young, 1311 .age = static_cast<u32>(Age::Young),
1224 .race = Race::Black, 1312 .race = static_cast<u32>(Race::Black),
1225 .values_count = 18, 1313 .values_count = 18,
1226 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, 1314 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
1227 }, 1315 },
1228 Service::Mii::RandomMiiData4{ 1316 RandomMiiData4{
1229 .gender = Gender::Male, 1317 .gender = static_cast<u32>(Gender::Male),
1230 .age = Age::Normal, 1318 .age = static_cast<u32>(Age::Normal),
1231 .race = Race::Black, 1319 .race = static_cast<u32>(Race::Black),
1232 .values_count = 18, 1320 .values_count = 18,
1233 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, 1321 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
1234 }, 1322 },
1235 Service::Mii::RandomMiiData4{ 1323 RandomMiiData4{
1236 .gender = Gender::Male, 1324 .gender = static_cast<u32>(Gender::Male),
1237 .age = Age::Old, 1325 .age = static_cast<u32>(Age::Old),
1238 .race = Race::Black, 1326 .race = static_cast<u32>(Race::Black),
1239 .values_count = 18, 1327 .values_count = 18,
1240 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, 1328 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
1241 }, 1329 },
1242 Service::Mii::RandomMiiData4{ 1330 RandomMiiData4{
1243 .gender = Gender::Male, 1331 .gender = static_cast<u32>(Gender::Male),
1244 .age = Age::Young, 1332 .age = static_cast<u32>(Age::Young),
1245 .race = Race::White, 1333 .race = static_cast<u32>(Race::White),
1246 .values_count = 23, 1334 .values_count = 23,
1247 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1335 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
1248 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, 1336 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
1249 }, 1337 },
1250 Service::Mii::RandomMiiData4{ 1338 RandomMiiData4{
1251 .gender = Gender::Male, 1339 .gender = static_cast<u32>(Gender::Male),
1252 .age = Age::Normal, 1340 .age = static_cast<u32>(Age::Normal),
1253 .race = Race::White, 1341 .race = static_cast<u32>(Race::White),
1254 .values_count = 23, 1342 .values_count = 23,
1255 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1343 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
1256 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, 1344 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
1257 }, 1345 },
1258 Service::Mii::RandomMiiData4{ 1346 RandomMiiData4{
1259 .gender = Gender::Male, 1347 .gender = static_cast<u32>(Gender::Male),
1260 .age = Age::Old, 1348 .age = static_cast<u32>(Age::Old),
1261 .race = Race::White, 1349 .race = static_cast<u32>(Race::White),
1262 .values_count = 23, 1350 .values_count = 23,
1263 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1351 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
1264 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, 1352 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
1265 }, 1353 },
1266 Service::Mii::RandomMiiData4{ 1354 RandomMiiData4{
1267 .gender = Gender::Male, 1355 .gender = static_cast<u32>(Gender::Male),
1268 .age = Age::Young, 1356 .age = static_cast<u32>(Age::Young),
1269 .race = Race::Asian, 1357 .race = static_cast<u32>(Race::Asian),
1270 .values_count = 21, 1358 .values_count = 21,
1271 .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, 1359 .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
1272 }, 1360 },
1273 Service::Mii::RandomMiiData4{ 1361 RandomMiiData4{
1274 .gender = Gender::Male, 1362 .gender = static_cast<u32>(Gender::Male),
1275 .age = Age::Normal, 1363 .age = static_cast<u32>(Age::Normal),
1276 .race = Race::Asian, 1364 .race = static_cast<u32>(Race::Asian),
1277 .values_count = 21, 1365 .values_count = 21,
1278 .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, 1366 .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
1279 }, 1367 },
1280 Service::Mii::RandomMiiData4{ 1368 RandomMiiData4{
1281 .gender = Gender::Male, 1369 .gender = static_cast<u32>(Gender::Male),
1282 .age = Age::Old, 1370 .age = static_cast<u32>(Age::Old),
1283 .race = Race::Asian, 1371 .race = static_cast<u32>(Race::Asian),
1284 .values_count = 21, 1372 .values_count = 21,
1285 .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, 1373 .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
1286 }, 1374 },
1287 Service::Mii::RandomMiiData4{ 1375 RandomMiiData4{
1288 .gender = Gender::Female, 1376 .gender = static_cast<u32>(Gender::Female),
1289 .age = Age::Young, 1377 .age = static_cast<u32>(Age::Young),
1290 .race = Race::Black, 1378 .race = static_cast<u32>(Race::Black),
1291 .values_count = 9, 1379 .values_count = 9,
1292 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, 1380 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
1293 }, 1381 },
1294 Service::Mii::RandomMiiData4{ 1382 RandomMiiData4{
1295 .gender = Gender::Female, 1383 .gender = static_cast<u32>(Gender::Female),
1296 .age = Age::Normal, 1384 .age = static_cast<u32>(Age::Normal),
1297 .race = Race::Black, 1385 .race = static_cast<u32>(Race::Black),
1298 .values_count = 9, 1386 .values_count = 9,
1299 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, 1387 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
1300 }, 1388 },
1301 Service::Mii::RandomMiiData4{ 1389 RandomMiiData4{
1302 .gender = Gender::Female, 1390 .gender = static_cast<u32>(Gender::Female),
1303 .age = Age::Old, 1391 .age = static_cast<u32>(Age::Old),
1304 .race = Race::Black, 1392 .race = static_cast<u32>(Race::Black),
1305 .values_count = 9, 1393 .values_count = 9,
1306 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, 1394 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
1307 }, 1395 },
1308 Service::Mii::RandomMiiData4{ 1396 RandomMiiData4{
1309 .gender = Gender::Female, 1397 .gender = static_cast<u32>(Gender::Female),
1310 .age = Age::Young, 1398 .age = static_cast<u32>(Age::Young),
1311 .race = Race::White, 1399 .race = static_cast<u32>(Race::White),
1312 .values_count = 11, 1400 .values_count = 11,
1313 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, 1401 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
1314 }, 1402 },
1315 Service::Mii::RandomMiiData4{ 1403 RandomMiiData4{
1316 .gender = Gender::Female, 1404 .gender = static_cast<u32>(Gender::Female),
1317 .age = Age::Normal, 1405 .age = static_cast<u32>(Age::Normal),
1318 .race = Race::White, 1406 .race = static_cast<u32>(Race::White),
1319 .values_count = 11, 1407 .values_count = 11,
1320 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, 1408 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
1321 }, 1409 },
1322 Service::Mii::RandomMiiData4{ 1410 RandomMiiData4{
1323 .gender = Gender::Female, 1411 .gender = static_cast<u32>(Gender::Female),
1324 .age = Age::Old, 1412 .age = static_cast<u32>(Age::Old),
1325 .race = Race::White, 1413 .race = static_cast<u32>(Race::White),
1326 .values_count = 11, 1414 .values_count = 11,
1327 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, 1415 .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
1328 }, 1416 },
1329 Service::Mii::RandomMiiData4{ 1417 RandomMiiData4{
1330 .gender = Gender::Female, 1418 .gender = static_cast<u32>(Gender::Female),
1331 .age = Age::Young, 1419 .age = static_cast<u32>(Age::Young),
1332 .race = Race::Asian, 1420 .race = static_cast<u32>(Race::Asian),
1333 .values_count = 9, 1421 .values_count = 9,
1334 .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, 1422 .values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
1335 }, 1423 },
1336 Service::Mii::RandomMiiData4{ 1424 RandomMiiData4{
1337 .gender = Gender::Female, 1425 .gender = static_cast<u32>(Gender::Female),
1338 .age = Age::Normal, 1426 .age = static_cast<u32>(Age::Normal),
1339 .race = Race::Asian, 1427 .race = static_cast<u32>(Race::Asian),
1340 .values_count = 9, 1428 .values_count = 9,
1341 .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, 1429 .values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
1342 }, 1430 },
1343 Service::Mii::RandomMiiData4{ 1431 RandomMiiData4{
1344 .gender = Gender::Female, 1432 .gender = static_cast<u32>(Gender::Female),
1345 .age = Age::Old, 1433 .age = static_cast<u32>(Age::Old),
1346 .race = Race::Asian, 1434 .race = static_cast<u32>(Race::Asian),
1347 .values_count = 9, 1435 .values_count = 9,
1348 .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, 1436 .values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
1349 }, 1437 },
1350}; 1438};
1351 1439
1352const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{ 1440const std::array<RandomMiiData4, 18> RandomMiiNoseType{
1353 Service::Mii::RandomMiiData4{ 1441 RandomMiiData4{
1354 .gender = Gender::Male, 1442 .gender = static_cast<u32>(Gender::Male),
1355 .age = Age::Young, 1443 .age = static_cast<u32>(Age::Young),
1356 .race = Race::Black, 1444 .race = static_cast<u32>(Race::Black),
1357 .values_count = 11, 1445 .values_count = 11,
1358 .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, 1446 .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
1359 }, 1447 },
1360 Service::Mii::RandomMiiData4{ 1448 RandomMiiData4{
1361 .gender = Gender::Male, 1449 .gender = static_cast<u32>(Gender::Male),
1362 .age = Age::Normal, 1450 .age = static_cast<u32>(Age::Normal),
1363 .race = Race::Black, 1451 .race = static_cast<u32>(Race::Black),
1364 .values_count = 11, 1452 .values_count = 11,
1365 .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, 1453 .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
1366 }, 1454 },
1367 Service::Mii::RandomMiiData4{ 1455 RandomMiiData4{
1368 .gender = Gender::Male, 1456 .gender = static_cast<u32>(Gender::Male),
1369 .age = Age::Old, 1457 .age = static_cast<u32>(Age::Old),
1370 .race = Race::Black, 1458 .race = static_cast<u32>(Race::Black),
1371 .values_count = 11, 1459 .values_count = 11,
1372 .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, 1460 .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
1373 }, 1461 },
1374 Service::Mii::RandomMiiData4{ 1462 RandomMiiData4{
1375 .gender = Gender::Male, 1463 .gender = static_cast<u32>(Gender::Male),
1376 .age = Age::Young, 1464 .age = static_cast<u32>(Age::Young),
1377 .race = Race::White, 1465 .race = static_cast<u32>(Race::White),
1378 .values_count = 18, 1466 .values_count = 18,
1379 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, 1467 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
1380 }, 1468 },
1381 Service::Mii::RandomMiiData4{ 1469 RandomMiiData4{
1382 .gender = Gender::Male, 1470 .gender = static_cast<u32>(Gender::Male),
1383 .age = Age::Normal, 1471 .age = static_cast<u32>(Age::Normal),
1384 .race = Race::White, 1472 .race = static_cast<u32>(Race::White),
1385 .values_count = 18, 1473 .values_count = 18,
1386 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, 1474 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
1387 }, 1475 },
1388 Service::Mii::RandomMiiData4{ 1476 RandomMiiData4{
1389 .gender = Gender::Male, 1477 .gender = static_cast<u32>(Gender::Male),
1390 .age = Age::Old, 1478 .age = static_cast<u32>(Age::Old),
1391 .race = Race::White, 1479 .race = static_cast<u32>(Race::White),
1392 .values_count = 15, 1480 .values_count = 15,
1393 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, 1481 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16},
1394 }, 1482 },
1395 Service::Mii::RandomMiiData4{ 1483 RandomMiiData4{
1396 .gender = Gender::Male, 1484 .gender = static_cast<u32>(Gender::Male),
1397 .age = Age::Young, 1485 .age = static_cast<u32>(Age::Young),
1398 .race = Race::Asian, 1486 .race = static_cast<u32>(Race::Asian),
1399 .values_count = 18, 1487 .values_count = 18,
1400 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, 1488 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
1401 }, 1489 },
1402 Service::Mii::RandomMiiData4{ 1490 RandomMiiData4{
1403 .gender = Gender::Male, 1491 .gender = static_cast<u32>(Gender::Male),
1404 .age = Age::Normal, 1492 .age = static_cast<u32>(Age::Normal),
1405 .race = Race::Asian, 1493 .race = static_cast<u32>(Race::Asian),
1406 .values_count = 18, 1494 .values_count = 18,
1407 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, 1495 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
1408 }, 1496 },
1409 Service::Mii::RandomMiiData4{ 1497 RandomMiiData4{
1410 .gender = Gender::Male, 1498 .gender = static_cast<u32>(Gender::Male),
1411 .age = Age::Old, 1499 .age = static_cast<u32>(Age::Old),
1412 .race = Race::Asian, 1500 .race = static_cast<u32>(Race::Asian),
1413 .values_count = 15, 1501 .values_count = 15,
1414 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, 1502 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16},
1415 }, 1503 },
1416 Service::Mii::RandomMiiData4{ 1504 RandomMiiData4{
1417 .gender = Gender::Female, 1505 .gender = static_cast<u32>(Gender::Female),
1418 .age = Age::Young, 1506 .age = static_cast<u32>(Age::Young),
1419 .race = Race::Black, 1507 .race = static_cast<u32>(Race::Black),
1420 .values_count = 8, 1508 .values_count = 8,
1421 .values = {0, 1, 3, 4, 8, 10, 13, 14}, 1509 .values = {0, 1, 3, 4, 8, 10, 13, 14},
1422 }, 1510 },
1423 Service::Mii::RandomMiiData4{ 1511 RandomMiiData4{
1424 .gender = Gender::Female, 1512 .gender = static_cast<u32>(Gender::Female),
1425 .age = Age::Normal, 1513 .age = static_cast<u32>(Age::Normal),
1426 .race = Race::Black, 1514 .race = static_cast<u32>(Race::Black),
1427 .values_count = 8, 1515 .values_count = 8,
1428 .values = {0, 1, 3, 4, 8, 10, 13, 14}, 1516 .values = {0, 1, 3, 4, 8, 10, 13, 14},
1429 }, 1517 },
1430 Service::Mii::RandomMiiData4{ 1518 RandomMiiData4{
1431 .gender = Gender::Female, 1519 .gender = static_cast<u32>(Gender::Female),
1432 .age = Age::Old, 1520 .age = static_cast<u32>(Age::Old),
1433 .race = Race::Black, 1521 .race = static_cast<u32>(Race::Black),
1434 .values_count = 8, 1522 .values_count = 8,
1435 .values = {0, 1, 3, 4, 8, 10, 13, 14}, 1523 .values = {0, 1, 3, 4, 8, 10, 13, 14},
1436 }, 1524 },
1437 Service::Mii::RandomMiiData4{ 1525 RandomMiiData4{
1438 .gender = Gender::Female, 1526 .gender = static_cast<u32>(Gender::Female),
1439 .age = Age::Young, 1527 .age = static_cast<u32>(Age::Young),
1440 .race = Race::White, 1528 .race = static_cast<u32>(Race::White),
1441 .values_count = 12, 1529 .values_count = 12,
1442 .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, 1530 .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15},
1443 }, 1531 },
1444 Service::Mii::RandomMiiData4{ 1532 RandomMiiData4{
1445 .gender = Gender::Female, 1533 .gender = static_cast<u32>(Gender::Female),
1446 .age = Age::Normal, 1534 .age = static_cast<u32>(Age::Normal),
1447 .race = Race::White, 1535 .race = static_cast<u32>(Race::White),
1448 .values_count = 11, 1536 .values_count = 11,
1449 .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, 1537 .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15},
1450 }, 1538 },
1451 Service::Mii::RandomMiiData4{ 1539 RandomMiiData4{
1452 .gender = Gender::Female, 1540 .gender = static_cast<u32>(Gender::Female),
1453 .age = Age::Old, 1541 .age = static_cast<u32>(Age::Old),
1454 .race = Race::White, 1542 .race = static_cast<u32>(Race::White),
1455 .values_count = 10, 1543 .values_count = 10,
1456 .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, 1544 .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14},
1457 }, 1545 },
1458 Service::Mii::RandomMiiData4{ 1546 RandomMiiData4{
1459 .gender = Gender::Female, 1547 .gender = static_cast<u32>(Gender::Female),
1460 .age = Age::Young, 1548 .age = static_cast<u32>(Age::Young),
1461 .race = Race::Asian, 1549 .race = static_cast<u32>(Race::Asian),
1462 .values_count = 12, 1550 .values_count = 12,
1463 .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, 1551 .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15},
1464 }, 1552 },
1465 Service::Mii::RandomMiiData4{ 1553 RandomMiiData4{
1466 .gender = Gender::Female, 1554 .gender = static_cast<u32>(Gender::Female),
1467 .age = Age::Normal, 1555 .age = static_cast<u32>(Age::Normal),
1468 .race = Race::Asian, 1556 .race = static_cast<u32>(Race::Asian),
1469 .values_count = 11, 1557 .values_count = 11,
1470 .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, 1558 .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15},
1471 }, 1559 },
1472 Service::Mii::RandomMiiData4{ 1560 RandomMiiData4{
1473 .gender = Gender::Female, 1561 .gender = static_cast<u32>(Gender::Female),
1474 .age = Age::Old, 1562 .age = static_cast<u32>(Age::Old),
1475 .race = Race::Asian, 1563 .race = static_cast<u32>(Race::Asian),
1476 .values_count = 10, 1564 .values_count = 10,
1477 .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, 1565 .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14},
1478 }, 1566 },
1479}; 1567};
1480 1568
1481const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{ 1569const std::array<RandomMiiData4, 18> RandomMiiMouthType{
1482 Service::Mii::RandomMiiData4{ 1570 RandomMiiData4{
1483 .gender = Gender::Male, 1571 .gender = static_cast<u32>(Gender::Male),
1484 .age = Age::Young, 1572 .age = static_cast<u32>(Age::Young),
1485 .race = Race::Black, 1573 .race = static_cast<u32>(Race::Black),
1486 .values_count = 25, 1574 .values_count = 25,
1487 .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, 1575 .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18,
1488 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, 1576 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35},
1489 }, 1577 },
1490 Service::Mii::RandomMiiData4{ 1578 RandomMiiData4{
1491 .gender = Gender::Male, 1579 .gender = static_cast<u32>(Gender::Male),
1492 .age = Age::Normal, 1580 .age = static_cast<u32>(Age::Normal),
1493 .race = Race::Black, 1581 .race = static_cast<u32>(Race::Black),
1494 .values_count = 27, 1582 .values_count = 27,
1495 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 1583 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17,
1496 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, 1584 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35},
1497 }, 1585 },
1498 Service::Mii::RandomMiiData4{ 1586 RandomMiiData4{
1499 .gender = Gender::Male, 1587 .gender = static_cast<u32>(Gender::Male),
1500 .age = Age::Old, 1588 .age = static_cast<u32>(Age::Old),
1501 .race = Race::Black, 1589 .race = static_cast<u32>(Race::Black),
1502 .values_count = 28, 1590 .values_count = 28,
1503 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 1591 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17,
1504 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35}, 1592 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35},
1505 }, 1593 },
1506 Service::Mii::RandomMiiData4{ 1594 RandomMiiData4{
1507 .gender = Gender::Male, 1595 .gender = static_cast<u32>(Gender::Male),
1508 .age = Age::Young, 1596 .age = static_cast<u32>(Age::Young),
1509 .race = Race::White, 1597 .race = static_cast<u32>(Race::White),
1510 .values_count = 24, 1598 .values_count = 24,
1511 .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, 1599 .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16,
1512 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, 1600 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
1513 }, 1601 },
1514 Service::Mii::RandomMiiData4{ 1602 RandomMiiData4{
1515 .gender = Gender::Male, 1603 .gender = static_cast<u32>(Gender::Male),
1516 .age = Age::Normal, 1604 .age = static_cast<u32>(Age::Normal),
1517 .race = Race::White, 1605 .race = static_cast<u32>(Race::White),
1518 .values_count = 26, 1606 .values_count = 26,
1519 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1607 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
1520 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, 1608 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
1521 }, 1609 },
1522 Service::Mii::RandomMiiData4{ 1610 RandomMiiData4{
1523 .gender = Gender::Male, 1611 .gender = static_cast<u32>(Gender::Male),
1524 .age = Age::Old, 1612 .age = static_cast<u32>(Age::Old),
1525 .race = Race::White, 1613 .race = static_cast<u32>(Race::White),
1526 .values_count = 26, 1614 .values_count = 26,
1527 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1615 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
1528 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, 1616 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
1529 }, 1617 },
1530 Service::Mii::RandomMiiData4{ 1618 RandomMiiData4{
1531 .gender = Gender::Male, 1619 .gender = static_cast<u32>(Gender::Male),
1532 .age = Age::Young, 1620 .age = static_cast<u32>(Age::Young),
1533 .race = Race::Asian, 1621 .race = static_cast<u32>(Race::Asian),
1534 .values_count = 24, 1622 .values_count = 24,
1535 .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, 1623 .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16,
1536 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, 1624 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
1537 }, 1625 },
1538 Service::Mii::RandomMiiData4{ 1626 RandomMiiData4{
1539 .gender = Gender::Male, 1627 .gender = static_cast<u32>(Gender::Male),
1540 .age = Age::Normal, 1628 .age = static_cast<u32>(Age::Normal),
1541 .race = Race::Asian, 1629 .race = static_cast<u32>(Race::Asian),
1542 .values_count = 26, 1630 .values_count = 26,
1543 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1631 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
1544 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, 1632 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
1545 }, 1633 },
1546 Service::Mii::RandomMiiData4{ 1634 RandomMiiData4{
1547 .gender = Gender::Male, 1635 .gender = static_cast<u32>(Gender::Male),
1548 .age = Age::Old, 1636 .age = static_cast<u32>(Age::Old),
1549 .race = Race::Asian, 1637 .race = static_cast<u32>(Race::Asian),
1550 .values_count = 26, 1638 .values_count = 26,
1551 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1639 .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
1552 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, 1640 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
1553 }, 1641 },
1554 Service::Mii::RandomMiiData4{ 1642 RandomMiiData4{
1555 .gender = Gender::Female, 1643 .gender = static_cast<u32>(Gender::Female),
1556 .age = Age::Young, 1644 .age = static_cast<u32>(Age::Young),
1557 .race = Race::Black, 1645 .race = static_cast<u32>(Race::Black),
1558 .values_count = 25, 1646 .values_count = 25,
1559 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 1647 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15,
1560 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, 1648 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
1561 }, 1649 },
1562 Service::Mii::RandomMiiData4{ 1650 RandomMiiData4{
1563 .gender = Gender::Female, 1651 .gender = static_cast<u32>(Gender::Female),
1564 .age = Age::Normal, 1652 .age = static_cast<u32>(Age::Normal),
1565 .race = Race::Black, 1653 .race = static_cast<u32>(Race::Black),
1566 .values_count = 26, 1654 .values_count = 26,
1567 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 1655 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
1568 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, 1656 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
1569 }, 1657 },
1570 Service::Mii::RandomMiiData4{ 1658 RandomMiiData4{
1571 .gender = Gender::Female, 1659 .gender = static_cast<u32>(Gender::Female),
1572 .age = Age::Old, 1660 .age = static_cast<u32>(Age::Old),
1573 .race = Race::Black, 1661 .race = static_cast<u32>(Race::Black),
1574 .values_count = 26, 1662 .values_count = 26,
1575 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 1663 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
1576 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, 1664 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
1577 }, 1665 },
1578 Service::Mii::RandomMiiData4{ 1666 RandomMiiData4{
1579 .gender = Gender::Female, 1667 .gender = static_cast<u32>(Gender::Female),
1580 .age = Age::Young, 1668 .age = static_cast<u32>(Age::Young),
1581 .race = Race::White, 1669 .race = static_cast<u32>(Race::White),
1582 .values_count = 25, 1670 .values_count = 25,
1583 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 1671 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15,
1584 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, 1672 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35},
1585 }, 1673 },
1586 Service::Mii::RandomMiiData4{ 1674 RandomMiiData4{
1587 .gender = Gender::Female, 1675 .gender = static_cast<u32>(Gender::Female),
1588 .age = Age::Normal, 1676 .age = static_cast<u32>(Age::Normal),
1589 .race = Race::White, 1677 .race = static_cast<u32>(Race::White),
1590 .values_count = 26, 1678 .values_count = 26,
1591 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 1679 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
1592 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, 1680 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35},
1593 }, 1681 },
1594 Service::Mii::RandomMiiData4{ 1682 RandomMiiData4{
1595 .gender = Gender::Female, 1683 .gender = static_cast<u32>(Gender::Female),
1596 .age = Age::Old, 1684 .age = static_cast<u32>(Age::Old),
1597 .race = Race::White, 1685 .race = static_cast<u32>(Race::White),
1598 .values_count = 25, 1686 .values_count = 25,
1599 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 1687 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
1600 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35}, 1688 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35},
1601 }, 1689 },
1602 Service::Mii::RandomMiiData4{ 1690 RandomMiiData4{
1603 .gender = Gender::Female, 1691 .gender = static_cast<u32>(Gender::Female),
1604 .age = Age::Young, 1692 .age = static_cast<u32>(Age::Young),
1605 .race = Race::Asian, 1693 .race = static_cast<u32>(Race::Asian),
1606 .values_count = 24, 1694 .values_count = 24,
1607 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 1695 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14,
1608 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, 1696 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
1609 }, 1697 },
1610 Service::Mii::RandomMiiData4{ 1698 RandomMiiData4{
1611 .gender = Gender::Female, 1699 .gender = static_cast<u32>(Gender::Female),
1612 .age = Age::Normal, 1700 .age = static_cast<u32>(Age::Normal),
1613 .race = Race::Asian, 1701 .race = static_cast<u32>(Race::Asian),
1614 .values_count = 25, 1702 .values_count = 25,
1615 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 1703 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
1616 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, 1704 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
1617 }, 1705 },
1618 Service::Mii::RandomMiiData4{ 1706 RandomMiiData4{
1619 .gender = Gender::Female, 1707 .gender = static_cast<u32>(Gender::Female),
1620 .age = Age::Old, 1708 .age = static_cast<u32>(Age::Old),
1621 .race = Race::Asian, 1709 .race = static_cast<u32>(Race::Asian),
1622 .values_count = 25, 1710 .values_count = 25,
1623 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 1711 .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
1624 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, 1712 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
1625 }, 1713 },
1626}; 1714};
1627 1715
1628const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{ 1716const std::array<RandomMiiData2, 3> RandomMiiGlassType{
1629 Service::Mii::RandomMiiData2{ 1717 RandomMiiData2{
1630 .arg_1 = 0, 1718 .arg_1 = 0,
1631 .values_count = 9, 1719 .values_count = 9,
1632 .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, 1720 .values = {90, 94, 96, 100, 0, 0, 0, 0, 0},
1633 }, 1721 },
1634 Service::Mii::RandomMiiData2{ 1722 RandomMiiData2{
1635 .arg_1 = 1, 1723 .arg_1 = 1,
1636 .values_count = 9, 1724 .values_count = 9,
1637 .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, 1725 .values = {83, 86, 90, 93, 94, 96, 98, 100, 0},
1638 }, 1726 },
1639 Service::Mii::RandomMiiData2{ 1727 RandomMiiData2{
1640 .arg_1 = 2, 1728 .arg_1 = 2,
1641 .values_count = 9, 1729 .values_count = 9,
1642 .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, 1730 .values = {78, 83, 0, 93, 0, 0, 98, 100, 0},
1643 }, 1731 },
1644}; 1732};
1645 1733
1734u8 FromVer3GetFacelineColor(u8 color) {
1735 return FromVer3FacelineColorTable[color];
1736}
1737
1738u8 FromVer3GetHairColor(u8 color) {
1739 return FromVer3HairColorTable[color];
1740}
1741
1742u8 FromVer3GetEyeColor(u8 color) {
1743 return FromVer3EyeColorTable[color];
1744}
1745
1746u8 FromVer3GetMouthlineColor(u8 color) {
1747 return FromVer3MouthlineColorTable[color];
1748}
1749
1750u8 FromVer3GetGlassColor(u8 color) {
1751 return FromVer3GlassColorTable[color];
1752}
1753
1754u8 FromVer3GetGlassType(u8 type) {
1755 return FromVer3GlassTypeTable[type];
1756}
1757
1758FacelineColor GetFacelineColorFromVer3(u32 color) {
1759 return static_cast<FacelineColor>(Ver3FacelineColorTable[color]);
1760}
1761
1762CommonColor GetHairColorFromVer3(u32 color) {
1763 return static_cast<CommonColor>(Ver3HairColorTable[color]);
1764}
1765
1766CommonColor GetEyeColorFromVer3(u32 color) {
1767 return static_cast<CommonColor>(Ver3EyeColorTable[color]);
1768}
1769
1770CommonColor GetMouthColorFromVer3(u32 color) {
1771 return static_cast<CommonColor>(Ver3MouthColorTable[color]);
1772}
1773
1774CommonColor GetGlassColorFromVer3(u32 color) {
1775 return static_cast<CommonColor>(Ver3GlassColorTable[color]);
1776}
1777
1646} // namespace Service::Mii::RawData 1778} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/types/raw_data.h b/src/core/hle/service/mii/types/raw_data.h
new file mode 100644
index 000000000..9a4cfa738
--- /dev/null
+++ b/src/core/hle/service/mii/types/raw_data.h
@@ -0,0 +1,73 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "core/hle/service/mii/mii_types.h"
9
10namespace Service::Mii::RawData {
11
12struct RandomMiiValues {
13 std::array<u8, 188> values{};
14};
15static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
16
17struct RandomMiiData4 {
18 u32 gender{};
19 u32 age{};
20 u32 race{};
21 u32 values_count{};
22 std::array<u32, 47> values{};
23};
24static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
25
26struct RandomMiiData3 {
27 u32 arg_1;
28 u32 arg_2;
29 u32 values_count;
30 std::array<u32, 47> values{};
31};
32static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
33
34struct RandomMiiData2 {
35 u32 arg_1;
36 u32 values_count;
37 std::array<u32, 47> values{};
38};
39static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
40
41extern const std::array<Service::Mii::DefaultMii, 2> BaseMii;
42extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii;
43
44extern const std::array<u8, 62> EyeRotateLookup;
45extern const std::array<u8, 24> EyebrowRotateLookup;
46
47extern const std::array<RandomMiiData4, 18> RandomMiiFaceline;
48extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor;
49extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle;
50extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup;
51extern const std::array<RandomMiiData4, 18> RandomMiiHairType;
52extern const std::array<RandomMiiData3, 9> RandomMiiHairColor;
53extern const std::array<RandomMiiData4, 18> RandomMiiEyeType;
54extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor;
55extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType;
56extern const std::array<RandomMiiData4, 18> RandomMiiNoseType;
57extern const std::array<RandomMiiData4, 18> RandomMiiMouthType;
58extern const std::array<RandomMiiData2, 3> RandomMiiGlassType;
59
60u8 FromVer3GetFacelineColor(u8 color);
61u8 FromVer3GetHairColor(u8 color);
62u8 FromVer3GetEyeColor(u8 color);
63u8 FromVer3GetMouthlineColor(u8 color);
64u8 FromVer3GetGlassColor(u8 color);
65u8 FromVer3GetGlassType(u8 type);
66
67FacelineColor GetFacelineColorFromVer3(u32 color);
68CommonColor GetHairColorFromVer3(u32 color);
69CommonColor GetEyeColorFromVer3(u32 color);
70CommonColor GetMouthColorFromVer3(u32 color);
71CommonColor GetGlassColorFromVer3(u32 color);
72
73} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp
new file mode 100644
index 000000000..8fce636c7
--- /dev/null
+++ b/src/core/hle/service/mii/types/store_data.cpp
@@ -0,0 +1,643 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/mii/mii_util.h"
5#include "core/hle/service/mii/types/raw_data.h"
6#include "core/hle/service/mii/types/store_data.h"
7
8namespace Service::Mii {
9
10void StoreData::BuildDefault(u32 mii_index) {
11 const auto& default_mii = RawData::DefaultMii[mii_index];
12 core_data.SetDefault();
13
14 core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
15 core_data.SetFacelineColor(
16 RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
17 core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
18 core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
19
20 core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
21 core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
22 core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
23 core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
24 core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
25 core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
26 core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
27 core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
28 core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
29 core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
30
31 core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
32 core_data.SetEyebrowColor(
33 RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
34 core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
35 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
36 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
37 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
38 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y));
39
40 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
41 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
42 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
43
44 core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type));
45 core_data.SetMouthColor(
46 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
47 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
48 core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
49 core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
50
51 core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
52 core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
53 core_data.SetBeardColor(
54 RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
55 core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
56 core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
57
58 core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
59 core_data.SetGlassColor(
60 RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
61 core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
62 core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
63
64 core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
65 core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
66 core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
67 core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
68
69 core_data.SetHeight(static_cast<u8>(default_mii.height));
70 core_data.SetBuild(static_cast<u8>(default_mii.weight));
71 core_data.SetGender(static_cast<Gender>(default_mii.gender));
72 core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
73 core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
74 core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
75 core_data.SetType(static_cast<u8>(default_mii.type));
76 core_data.SetNickname(default_mii.nickname);
77
78 const auto device_id = MiiUtil::GetDeviceId();
79 create_id = MiiUtil::MakeCreateId();
80 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
81 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
82}
83
84void StoreData::BuildBase(Gender gender) {
85 const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0];
86 core_data.SetDefault();
87
88 core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
89 core_data.SetFacelineColor(
90 RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
91 core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
92 core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
93
94 core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
95 core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
96 core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
97 core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
98 core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
99 core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
100 core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
101 core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
102 core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
103 core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
104
105 core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
106 core_data.SetEyebrowColor(
107 RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
108 core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
109 core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
110 core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
111 core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
112 core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y));
113
114 core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
115 core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
116 core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
117
118 core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type));
119 core_data.SetMouthColor(
120 RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
121 core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
122 core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
123 core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
124
125 core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
126 core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
127 core_data.SetBeardColor(
128 RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
129 core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
130 core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
131
132 core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
133 core_data.SetGlassColor(
134 RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
135 core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
136 core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
137
138 core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
139 core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
140 core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
141 core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
142
143 core_data.SetHeight(static_cast<u8>(default_mii.height));
144 core_data.SetBuild(static_cast<u8>(default_mii.weight));
145 core_data.SetGender(static_cast<Gender>(default_mii.gender));
146 core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
147 core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
148 core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
149 core_data.SetType(static_cast<u8>(default_mii.type));
150 core_data.SetNickname(default_mii.nickname);
151
152 const auto device_id = MiiUtil::GetDeviceId();
153 create_id = MiiUtil::MakeCreateId();
154 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
155 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
156}
157
158void StoreData::BuildRandom(Age age, Gender gender, Race race) {
159 core_data.BuildRandom(age, gender, race);
160 const auto device_id = MiiUtil::GetDeviceId();
161 create_id = MiiUtil::MakeCreateId();
162 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
163 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
164}
165
166void StoreData::SetInvalidName() {
167 const auto& invalid_name = core_data.GetInvalidNickname();
168 const auto device_id = MiiUtil::GetDeviceId();
169 core_data.SetNickname(invalid_name);
170 device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
171 data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
172}
173
174bool StoreData::IsSpecial() const {
175 return GetType() == 1;
176}
177
178u32 StoreData::IsValid() const {
179 // TODO: complete this
180 return 0;
181}
182
183void StoreData::SetFontRegion(FontRegion value) {
184 core_data.SetFontRegion(value);
185}
186
187void StoreData::SetFavoriteColor(FavoriteColor value) {
188 core_data.SetFavoriteColor(value);
189}
190
191void StoreData::SetGender(Gender value) {
192 core_data.SetGender(value);
193}
194
195void StoreData::SetHeight(u8 value) {
196 core_data.SetHeight(value);
197}
198
199void StoreData::SetBuild(u8 value) {
200 core_data.SetBuild(value);
201}
202
203void StoreData::SetType(u8 value) {
204 core_data.SetType(value);
205}
206
207void StoreData::SetRegionMove(u8 value) {
208 core_data.SetRegionMove(value);
209}
210
211void StoreData::SetFacelineType(FacelineType value) {
212 core_data.SetFacelineType(value);
213}
214
215void StoreData::SetFacelineColor(FacelineColor value) {
216 core_data.SetFacelineColor(value);
217}
218
219void StoreData::SetFacelineWrinkle(FacelineWrinkle value) {
220 core_data.SetFacelineWrinkle(value);
221}
222
223void StoreData::SetFacelineMake(FacelineMake value) {
224 core_data.SetFacelineMake(value);
225}
226
227void StoreData::SetHairType(HairType value) {
228 core_data.SetHairType(value);
229}
230
231void StoreData::SetHairColor(CommonColor value) {
232 core_data.SetHairColor(value);
233}
234
235void StoreData::SetHairFlip(HairFlip value) {
236 core_data.SetHairFlip(value);
237}
238
239void StoreData::SetEyeType(EyeType value) {
240 core_data.SetEyeType(value);
241}
242
243void StoreData::SetEyeColor(CommonColor value) {
244 core_data.SetEyeColor(value);
245}
246
247void StoreData::SetEyeScale(u8 value) {
248 core_data.SetEyeScale(value);
249}
250
251void StoreData::SetEyeAspect(u8 value) {
252 core_data.SetEyeAspect(value);
253}
254
255void StoreData::SetEyeRotate(u8 value) {
256 core_data.SetEyeRotate(value);
257}
258
259void StoreData::SetEyeX(u8 value) {
260 core_data.SetEyeX(value);
261}
262
263void StoreData::SetEyeY(u8 value) {
264 core_data.SetEyeY(value);
265}
266
267void StoreData::SetEyebrowType(EyebrowType value) {
268 core_data.SetEyebrowType(value);
269}
270
271void StoreData::SetEyebrowColor(CommonColor value) {
272 core_data.SetEyebrowColor(value);
273}
274
275void StoreData::SetEyebrowScale(u8 value) {
276 core_data.SetEyebrowScale(value);
277}
278
279void StoreData::SetEyebrowAspect(u8 value) {
280 core_data.SetEyebrowAspect(value);
281}
282
283void StoreData::SetEyebrowRotate(u8 value) {
284 core_data.SetEyebrowRotate(value);
285}
286
287void StoreData::SetEyebrowX(u8 value) {
288 core_data.SetEyebrowX(value);
289}
290
291void StoreData::SetEyebrowY(u8 value) {
292 core_data.SetEyebrowY(value);
293}
294
295void StoreData::SetNoseType(NoseType value) {
296 core_data.SetNoseType(value);
297}
298
299void StoreData::SetNoseScale(u8 value) {
300 core_data.SetNoseScale(value);
301}
302
303void StoreData::SetNoseY(u8 value) {
304 core_data.SetNoseY(value);
305}
306
307void StoreData::SetMouthType(u8 value) {
308 core_data.SetMouthType(value);
309}
310
311void StoreData::SetMouthColor(CommonColor value) {
312 core_data.SetMouthColor(value);
313}
314
315void StoreData::SetMouthScale(u8 value) {
316 core_data.SetMouthScale(value);
317}
318
319void StoreData::SetMouthAspect(u8 value) {
320 core_data.SetMouthAspect(value);
321}
322
323void StoreData::SetMouthY(u8 value) {
324 core_data.SetMouthY(value);
325}
326
327void StoreData::SetBeardColor(CommonColor value) {
328 core_data.SetBeardColor(value);
329}
330
331void StoreData::SetBeardType(BeardType value) {
332 core_data.SetBeardType(value);
333}
334
335void StoreData::SetMustacheType(MustacheType value) {
336 core_data.SetMustacheType(value);
337}
338
339void StoreData::SetMustacheScale(u8 value) {
340 core_data.SetMustacheScale(value);
341}
342
343void StoreData::SetMustacheY(u8 value) {
344 core_data.SetMustacheY(value);
345}
346
347void StoreData::SetGlassType(GlassType value) {
348 core_data.SetGlassType(value);
349}
350
351void StoreData::SetGlassColor(CommonColor value) {
352 core_data.SetGlassColor(value);
353}
354
355void StoreData::SetGlassScale(u8 value) {
356 core_data.SetGlassScale(value);
357}
358
359void StoreData::SetGlassY(u8 value) {
360 core_data.SetGlassY(value);
361}
362
363void StoreData::SetMoleType(MoleType value) {
364 core_data.SetMoleType(value);
365}
366
367void StoreData::SetMoleScale(u8 value) {
368 core_data.SetMoleScale(value);
369}
370
371void StoreData::SetMoleX(u8 value) {
372 core_data.SetMoleX(value);
373}
374
375void StoreData::SetMoleY(u8 value) {
376 core_data.SetMoleY(value);
377}
378
379void StoreData::SetNickname(Nickname value) {
380 core_data.SetNickname(value);
381}
382
383Common::UUID StoreData::GetCreateId() const {
384 return create_id;
385}
386
387FontRegion StoreData::GetFontRegion() const {
388 return static_cast<FontRegion>(core_data.GetFontRegion());
389}
390
391FavoriteColor StoreData::GetFavoriteColor() const {
392 return core_data.GetFavoriteColor();
393}
394
395Gender StoreData::GetGender() const {
396 return core_data.GetGender();
397}
398
399u8 StoreData::GetHeight() const {
400 return core_data.GetHeight();
401}
402
403u8 StoreData::GetBuild() const {
404 return core_data.GetBuild();
405}
406
407u8 StoreData::GetType() const {
408 return core_data.GetType();
409}
410
411u8 StoreData::GetRegionMove() const {
412 return core_data.GetRegionMove();
413}
414
415FacelineType StoreData::GetFacelineType() const {
416 return core_data.GetFacelineType();
417}
418
419FacelineColor StoreData::GetFacelineColor() const {
420 return core_data.GetFacelineColor();
421}
422
423FacelineWrinkle StoreData::GetFacelineWrinkle() const {
424 return core_data.GetFacelineWrinkle();
425}
426
427FacelineMake StoreData::GetFacelineMake() const {
428 return core_data.GetFacelineMake();
429}
430
431HairType StoreData::GetHairType() const {
432 return core_data.GetHairType();
433}
434
435CommonColor StoreData::GetHairColor() const {
436 return core_data.GetHairColor();
437}
438
439HairFlip StoreData::GetHairFlip() const {
440 return core_data.GetHairFlip();
441}
442
443EyeType StoreData::GetEyeType() const {
444 return core_data.GetEyeType();
445}
446
447CommonColor StoreData::GetEyeColor() const {
448 return core_data.GetEyeColor();
449}
450
451u8 StoreData::GetEyeScale() const {
452 return core_data.GetEyeScale();
453}
454
455u8 StoreData::GetEyeAspect() const {
456 return core_data.GetEyeAspect();
457}
458
459u8 StoreData::GetEyeRotate() const {
460 return core_data.GetEyeRotate();
461}
462
463u8 StoreData::GetEyeX() const {
464 return core_data.GetEyeX();
465}
466
467u8 StoreData::GetEyeY() const {
468 return core_data.GetEyeY();
469}
470
471EyebrowType StoreData::GetEyebrowType() const {
472 return core_data.GetEyebrowType();
473}
474
475CommonColor StoreData::GetEyebrowColor() const {
476 return core_data.GetEyebrowColor();
477}
478
479u8 StoreData::GetEyebrowScale() const {
480 return core_data.GetEyebrowScale();
481}
482
483u8 StoreData::GetEyebrowAspect() const {
484 return core_data.GetEyebrowAspect();
485}
486
487u8 StoreData::GetEyebrowRotate() const {
488 return core_data.GetEyebrowRotate();
489}
490
491u8 StoreData::GetEyebrowX() const {
492 return core_data.GetEyebrowX();
493}
494
495u8 StoreData::GetEyebrowY() const {
496 return core_data.GetEyebrowY();
497}
498
499NoseType StoreData::GetNoseType() const {
500 return core_data.GetNoseType();
501}
502
503u8 StoreData::GetNoseScale() const {
504 return core_data.GetNoseScale();
505}
506
507u8 StoreData::GetNoseY() const {
508 return core_data.GetNoseY();
509}
510
511MouthType StoreData::GetMouthType() const {
512 return core_data.GetMouthType();
513}
514
515CommonColor StoreData::GetMouthColor() const {
516 return core_data.GetMouthColor();
517}
518
519u8 StoreData::GetMouthScale() const {
520 return core_data.GetMouthScale();
521}
522
523u8 StoreData::GetMouthAspect() const {
524 return core_data.GetMouthAspect();
525}
526
527u8 StoreData::GetMouthY() const {
528 return core_data.GetMouthY();
529}
530
531CommonColor StoreData::GetBeardColor() const {
532 return core_data.GetBeardColor();
533}
534
535BeardType StoreData::GetBeardType() const {
536 return core_data.GetBeardType();
537}
538
539MustacheType StoreData::GetMustacheType() const {
540 return core_data.GetMustacheType();
541}
542
543u8 StoreData::GetMustacheScale() const {
544 return core_data.GetMustacheScale();
545}
546
547u8 StoreData::GetMustacheY() const {
548 return core_data.GetMustacheY();
549}
550
551GlassType StoreData::GetGlassType() const {
552 return core_data.GetGlassType();
553}
554
555CommonColor StoreData::GetGlassColor() const {
556 return core_data.GetGlassColor();
557}
558
559u8 StoreData::GetGlassScale() const {
560 return core_data.GetGlassScale();
561}
562
563u8 StoreData::GetGlassY() const {
564 return core_data.GetGlassY();
565}
566
567MoleType StoreData::GetMoleType() const {
568 return core_data.GetMoleType();
569}
570
571u8 StoreData::GetMoleScale() const {
572 return core_data.GetMoleScale();
573}
574
575u8 StoreData::GetMoleX() const {
576 return core_data.GetMoleX();
577}
578
579u8 StoreData::GetMoleY() const {
580 return core_data.GetMoleY();
581}
582
583Nickname StoreData::GetNickname() const {
584 return core_data.GetNickname();
585}
586
587bool StoreData::operator==(const StoreData& data) {
588 bool is_identical = data.core_data.IsValid() == 0;
589 is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
590 is_identical &= GetCreateId() == data.GetCreateId();
591 is_identical &= GetFontRegion() == data.GetFontRegion();
592 is_identical &= GetFavoriteColor() == data.GetFavoriteColor();
593 is_identical &= GetGender() == data.GetGender();
594 is_identical &= GetHeight() == data.GetHeight();
595 is_identical &= GetBuild() == data.GetBuild();
596 is_identical &= GetType() == data.GetType();
597 is_identical &= GetRegionMove() == data.GetRegionMove();
598 is_identical &= GetFacelineType() == data.GetFacelineType();
599 is_identical &= GetFacelineColor() == data.GetFacelineColor();
600 is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle();
601 is_identical &= GetFacelineMake() == data.GetFacelineMake();
602 is_identical &= GetHairType() == data.GetHairType();
603 is_identical &= GetHairColor() == data.GetHairColor();
604 is_identical &= GetHairFlip() == data.GetHairFlip();
605 is_identical &= GetEyeType() == data.GetEyeType();
606 is_identical &= GetEyeColor() == data.GetEyeColor();
607 is_identical &= GetEyeScale() == data.GetEyeScale();
608 is_identical &= GetEyeAspect() == data.GetEyeAspect();
609 is_identical &= GetEyeRotate() == data.GetEyeRotate();
610 is_identical &= GetEyeX() == data.GetEyeX();
611 is_identical &= GetEyeY() == data.GetEyeY();
612 is_identical &= GetEyebrowType() == data.GetEyebrowType();
613 is_identical &= GetEyebrowColor() == data.GetEyebrowColor();
614 is_identical &= GetEyebrowScale() == data.GetEyebrowScale();
615 is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect();
616 is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate();
617 is_identical &= GetEyebrowX() == data.GetEyebrowX();
618 is_identical &= GetEyebrowY() == data.GetEyebrowY();
619 is_identical &= GetNoseType() == data.GetNoseType();
620 is_identical &= GetNoseScale() == data.GetNoseScale();
621 is_identical &= GetNoseY() == data.GetNoseY();
622 is_identical &= GetMouthType() == data.GetMouthType();
623 is_identical &= GetMouthColor() == data.GetMouthColor();
624 is_identical &= GetMouthScale() == data.GetMouthScale();
625 is_identical &= GetMouthAspect() == data.GetMouthAspect();
626 is_identical &= GetMouthY() == data.GetMouthY();
627 is_identical &= GetBeardColor() == data.GetBeardColor();
628 is_identical &= GetBeardType() == data.GetBeardType();
629 is_identical &= GetMustacheType() == data.GetMustacheType();
630 is_identical &= GetMustacheScale() == data.GetMustacheScale();
631 is_identical &= GetMustacheY() == data.GetMustacheY();
632 is_identical &= GetGlassType() == data.GetGlassType();
633 is_identical &= GetGlassColor() == data.GetGlassColor();
634 is_identical &= GetGlassScale() == data.GetGlassScale();
635 is_identical &= GetGlassY() == data.GetGlassY();
636 is_identical &= GetMoleType() == data.GetMoleType();
637 is_identical &= GetMoleScale() == data.GetMoleScale();
638 is_identical &= GetMoleX() == data.GetMoleX();
639 is_identical &= data.GetMoleY() == data.GetMoleY();
640 return is_identical;
641}
642
643} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h
new file mode 100644
index 000000000..224c32cf8
--- /dev/null
+++ b/src/core/hle/service/mii/types/store_data.h
@@ -0,0 +1,145 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/service/mii/mii_types.h"
7#include "core/hle/service/mii/types/core_data.h"
8
9namespace Service::Mii {
10
11class StoreData {
12public:
13 // nn::mii::detail::StoreDataRaw::BuildDefault
14 void BuildDefault(u32 mii_index);
15 // nn::mii::detail::StoreDataRaw::BuildDefault
16
17 void BuildBase(Gender gender);
18 // nn::mii::detail::StoreDataRaw::BuildRandom
19 void BuildRandom(Age age, Gender gender, Race race);
20
21 bool IsSpecial() const;
22
23 u32 IsValid() const;
24
25 void SetFontRegion(FontRegion value);
26 void SetFavoriteColor(FavoriteColor value);
27 void SetGender(Gender value);
28 void SetHeight(u8 value);
29 void SetBuild(u8 value);
30 void SetType(u8 value);
31 void SetRegionMove(u8 value);
32 void SetFacelineType(FacelineType value);
33 void SetFacelineColor(FacelineColor value);
34 void SetFacelineWrinkle(FacelineWrinkle value);
35 void SetFacelineMake(FacelineMake value);
36 void SetHairType(HairType value);
37 void SetHairColor(CommonColor value);
38 void SetHairFlip(HairFlip value);
39 void SetEyeType(EyeType value);
40 void SetEyeColor(CommonColor value);
41 void SetEyeScale(u8 value);
42 void SetEyeAspect(u8 value);
43 void SetEyeRotate(u8 value);
44 void SetEyeX(u8 value);
45 void SetEyeY(u8 value);
46 void SetEyebrowType(EyebrowType value);
47 void SetEyebrowColor(CommonColor value);
48 void SetEyebrowScale(u8 value);
49 void SetEyebrowAspect(u8 value);
50 void SetEyebrowRotate(u8 value);
51 void SetEyebrowX(u8 value);
52 void SetEyebrowY(u8 value);
53 void SetNoseType(NoseType value);
54 void SetNoseScale(u8 value);
55 void SetNoseY(u8 value);
56 void SetMouthType(u8 value);
57 void SetMouthColor(CommonColor value);
58 void SetMouthScale(u8 value);
59 void SetMouthAspect(u8 value);
60 void SetMouthY(u8 value);
61 void SetBeardColor(CommonColor value);
62 void SetBeardType(BeardType value);
63 void SetMustacheType(MustacheType value);
64 void SetMustacheScale(u8 value);
65 void SetMustacheY(u8 value);
66 void SetGlassType(GlassType value);
67 void SetGlassColor(CommonColor value);
68 void SetGlassScale(u8 value);
69 void SetGlassY(u8 value);
70 void SetMoleType(MoleType value);
71 void SetMoleScale(u8 value);
72 void SetMoleX(u8 value);
73 void SetMoleY(u8 value);
74 void SetNickname(Nickname nickname);
75 void SetInvalidName();
76
77 Common::UUID GetCreateId() const;
78 FontRegion GetFontRegion() const;
79 FavoriteColor GetFavoriteColor() const;
80 Gender GetGender() const;
81 u8 GetHeight() const;
82 u8 GetBuild() const;
83 u8 GetType() const;
84 u8 GetRegionMove() const;
85 FacelineType GetFacelineType() const;
86 FacelineColor GetFacelineColor() const;
87 FacelineWrinkle GetFacelineWrinkle() const;
88 FacelineMake GetFacelineMake() const;
89 HairType GetHairType() const;
90 CommonColor GetHairColor() const;
91 HairFlip GetHairFlip() const;
92 EyeType GetEyeType() const;
93 CommonColor GetEyeColor() const;
94 u8 GetEyeScale() const;
95 u8 GetEyeAspect() const;
96 u8 GetEyeRotate() const;
97 u8 GetEyeX() const;
98 u8 GetEyeY() const;
99 EyebrowType GetEyebrowType() const;
100 CommonColor GetEyebrowColor() const;
101 u8 GetEyebrowScale() const;
102 u8 GetEyebrowAspect() const;
103 u8 GetEyebrowRotate() const;
104 u8 GetEyebrowX() const;
105 u8 GetEyebrowY() const;
106 NoseType GetNoseType() const;
107 u8 GetNoseScale() const;
108 u8 GetNoseY() const;
109 MouthType GetMouthType() const;
110 CommonColor GetMouthColor() const;
111 u8 GetMouthScale() const;
112 u8 GetMouthAspect() const;
113 u8 GetMouthY() const;
114 CommonColor GetBeardColor() const;
115 BeardType GetBeardType() const;
116 MustacheType GetMustacheType() const;
117 u8 GetMustacheScale() const;
118 u8 GetMustacheY() const;
119 GlassType GetGlassType() const;
120 CommonColor GetGlassColor() const;
121 u8 GetGlassScale() const;
122 u8 GetGlassY() const;
123 MoleType GetMoleType() const;
124 u8 GetMoleScale() const;
125 u8 GetMoleX() const;
126 u8 GetMoleY() const;
127 Nickname GetNickname() const;
128
129 bool operator==(const StoreData& data);
130
131private:
132 CoreData core_data{};
133 Common::UUID create_id{};
134 u16 data_crc{};
135 u16 device_crc{};
136};
137static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
138
139struct StoreDataElement {
140 StoreData store_data{};
141 Source source{};
142};
143static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size.");
144
145}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp
new file mode 100644
index 000000000..1c28e0b1b
--- /dev/null
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -0,0 +1,241 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/mii/mii_util.h"
5#include "core/hle/service/mii/types/raw_data.h"
6#include "core/hle/service/mii/types/store_data.h"
7#include "core/hle/service/mii/types/ver3_store_data.h"
8
9namespace Service::Mii {
10
11void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
12 faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf;
13 hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f;
14 eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f;
15 eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f;
16 mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f;
17 beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f;
18 glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f;
19 glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f;
20}
21
22void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
23 out_store_data.BuildBase(Gender::Male);
24
25 if (!IsValid()) {
26 return;
27 }
28
29 // TODO: We are ignoring a bunch of data from the mii_v3
30
31 out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value()));
32 out_store_data.SetFavoriteColor(
33 static_cast<FavoriteColor>(mii_information.favorite_color.Value()));
34 out_store_data.SetHeight(height);
35 out_store_data.SetBuild(build);
36
37 out_store_data.SetNickname(mii_name);
38 out_store_data.SetFontRegion(
39 static_cast<FontRegion>(static_cast<u8>(region_information.font_region)));
40
41 out_store_data.SetFacelineType(
42 static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
43 out_store_data.SetFacelineColor(
44 static_cast<FacelineColor>(appearance_bits1.faceline_color.Value()));
45 out_store_data.SetFacelineWrinkle(
46 static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value()));
47 out_store_data.SetFacelineMake(
48 static_cast<FacelineMake>(appearance_bits2.faceline_make.Value()));
49
50 out_store_data.SetHairType(static_cast<HairType>(hair_type));
51 out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value()));
52 out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value()));
53
54 out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value()));
55 out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value()));
56 out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale));
57 out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect));
58 out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate));
59 out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x));
60 out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y));
61
62 out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value()));
63 out_store_data.SetEyebrowColor(
64 static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value()));
65 out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale));
66 out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect));
67 out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate));
68 out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x));
69 out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y));
70
71 out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value()));
72 out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale));
73 out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y));
74
75 out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type));
76 out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value()));
77 out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale));
78 out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect));
79 out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y));
80
81 out_store_data.SetMustacheType(
82 static_cast<MustacheType>(appearance_bits8.mustache_type.Value()));
83 out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale));
84 out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y));
85
86 out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value()));
87 out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value()));
88
89 out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value()));
90 out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value()));
91 out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale));
92 out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y));
93
94 out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value()));
95 out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale));
96 out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x));
97 out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y));
98}
99
100void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
101 version = 1;
102 mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
103 mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
104 height = store_data.GetHeight();
105 build = store_data.GetBuild();
106
107 mii_name = store_data.GetNickname();
108 region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion()));
109
110 appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType()));
111 appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle()));
112 appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake()));
113
114 hair_type = static_cast<u8>(store_data.GetHairType());
115 appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip()));
116
117 appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType()));
118 appearance_bits4.eye_scale.Assign(store_data.GetEyeScale());
119 appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect());
120 appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate());
121 appearance_bits4.eye_x.Assign(store_data.GetEyeX());
122 appearance_bits4.eye_y.Assign(store_data.GetEyeY());
123
124 appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType()));
125 appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale());
126 appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect());
127 appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate());
128 appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX());
129 appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY());
130
131 appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType()));
132 appearance_bits6.nose_scale.Assign(store_data.GetNoseScale());
133 appearance_bits6.nose_y.Assign(store_data.GetNoseY());
134
135 appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType()));
136 appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale());
137 appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect());
138 appearance_bits8.mouth_y.Assign(store_data.GetMouthY());
139
140 appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType()));
141 appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale());
142 appearance_bits9.mustache_y.Assign(store_data.GetMustacheY());
143
144 appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType()));
145
146 appearance_bits10.glass_scale.Assign(store_data.GetGlassScale());
147 appearance_bits10.glass_y.Assign(store_data.GetGlassY());
148
149 appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType()));
150 appearance_bits11.mole_scale.Assign(store_data.GetMoleScale());
151 appearance_bits11.mole_x.Assign(store_data.GetMoleX());
152 appearance_bits11.mole_y.Assign(store_data.GetMoleY());
153
154 // These types are converted to V3 from a table
155 appearance_bits1.faceline_color.Assign(
156 RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor())));
157 appearance_bits3.hair_color.Assign(
158 RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor())));
159 appearance_bits4.eye_color.Assign(
160 RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor())));
161 appearance_bits5.eyebrow_color.Assign(
162 RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor())));
163 appearance_bits7.mouth_color.Assign(
164 RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor())));
165 appearance_bits9.beard_color.Assign(
166 RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor())));
167 appearance_bits10.glass_color.Assign(
168 RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor())));
169 appearance_bits10.glass_type.Assign(
170 RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType())));
171
172 crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16));
173}
174
175u32 Ver3StoreData::IsValid() const {
176 bool is_valid = version == 0 || version == 3;
177
178 is_valid = is_valid && (mii_name.data[0] != '\0');
179
180 is_valid = is_valid && (mii_information.birth_month < 13);
181 is_valid = is_valid && (mii_information.birth_day < 32);
182 is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max));
183 is_valid = is_valid && (height <= MaxHeight);
184 is_valid = is_valid && (build <= MaxBuild);
185
186 is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max));
187 is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2);
188 is_valid =
189 is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max));
190 is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max));
191
192 is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max));
193 is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor);
194
195 is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max));
196 is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2);
197 is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale);
198 is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect);
199 is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate);
200 is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX);
201 is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY);
202
203 is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max));
204 is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor);
205 is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale);
206 is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect);
207 is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate);
208 is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX);
209 is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY);
210
211 is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max));
212 is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale);
213 is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY);
214
215 is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max));
216 is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3);
217 is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale);
218 is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect);
219 is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY);
220
221 is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max));
222 is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale);
223 is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY);
224
225 is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
226 is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
227
228 is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType);
229 is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2);
230 is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale);
231 is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale);
232
233 is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
234 is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);
235 is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX);
236 is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY);
237
238 return is_valid;
239}
240
241} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/ver3_store_data.h b/src/core/hle/service/mii/types/ver3_store_data.h
new file mode 100644
index 000000000..47907bf7d
--- /dev/null
+++ b/src/core/hle/service/mii/types/ver3_store_data.h
@@ -0,0 +1,160 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/service/mii/mii_types.h"
7
8namespace Service::Mii {
9class StoreData;
10
11// This is nn::mii::Ver3StoreData
12// Based on citra HLE::Applets::MiiData and PretendoNetwork.
13// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
14// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
15
16struct NfpStoreDataExtension {
17 void SetFromStoreData(const StoreData& store_data);
18
19 u8 faceline_color;
20 u8 hair_color;
21 u8 eye_color;
22 u8 eyebrow_color;
23 u8 mouth_color;
24 u8 beard_color;
25 u8 glass_color;
26 u8 glass_type;
27};
28static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
29
30#pragma pack(push, 4)
31class Ver3StoreData {
32public:
33 void BuildToStoreData(StoreData& out_store_data) const;
34 void BuildFromStoreData(const StoreData& store_data);
35
36 u32 IsValid() const;
37
38 u8 version;
39 union {
40 u8 raw;
41
42 BitField<0, 1, u8> allow_copying;
43 BitField<1, 1, u8> profanity_flag;
44 BitField<2, 2, u8> region_lock;
45 BitField<4, 2, u8> font_region;
46 } region_information;
47 u16_be mii_id;
48 u64_be system_id;
49 u32_be specialness_and_creation_date;
50 std::array<u8, 6> creator_mac;
51 u16_be padding;
52 union {
53 u16 raw;
54
55 BitField<0, 1, u16> gender;
56 BitField<1, 4, u16> birth_month;
57 BitField<5, 5, u16> birth_day;
58 BitField<10, 4, u16> favorite_color;
59 BitField<14, 1, u16> favorite;
60 } mii_information;
61 Nickname mii_name;
62 u8 height;
63 u8 build;
64 union {
65 u8 raw;
66
67 BitField<0, 1, u8> disable_sharing;
68 BitField<1, 4, u8> faceline_type;
69 BitField<5, 3, u8> faceline_color;
70 } appearance_bits1;
71 union {
72 u8 raw;
73
74 BitField<0, 4, u8> faceline_wrinkle;
75 BitField<4, 4, u8> faceline_make;
76 } appearance_bits2;
77 u8 hair_type;
78 union {
79 u8 raw;
80
81 BitField<0, 3, u8> hair_color;
82 BitField<3, 1, u8> hair_flip;
83 } appearance_bits3;
84 union {
85 u32 raw;
86
87 BitField<0, 6, u32> eye_type;
88 BitField<6, 3, u32> eye_color;
89 BitField<9, 4, u32> eye_scale;
90 BitField<13, 3, u32> eye_aspect;
91 BitField<16, 5, u32> eye_rotate;
92 BitField<21, 4, u32> eye_x;
93 BitField<25, 5, u32> eye_y;
94 } appearance_bits4;
95 union {
96 u32 raw;
97
98 BitField<0, 5, u32> eyebrow_type;
99 BitField<5, 3, u32> eyebrow_color;
100 BitField<8, 4, u32> eyebrow_scale;
101 BitField<12, 3, u32> eyebrow_aspect;
102 BitField<16, 4, u32> eyebrow_rotate;
103 BitField<21, 4, u32> eyebrow_x;
104 BitField<25, 5, u32> eyebrow_y;
105 } appearance_bits5;
106 union {
107 u16 raw;
108
109 BitField<0, 5, u16> nose_type;
110 BitField<5, 4, u16> nose_scale;
111 BitField<9, 5, u16> nose_y;
112 } appearance_bits6;
113 union {
114 u16 raw;
115
116 BitField<0, 6, u16> mouth_type;
117 BitField<6, 3, u16> mouth_color;
118 BitField<9, 4, u16> mouth_scale;
119 BitField<13, 3, u16> mouth_aspect;
120 } appearance_bits7;
121 union {
122 u8 raw;
123
124 BitField<0, 5, u8> mouth_y;
125 BitField<5, 3, u8> mustache_type;
126 } appearance_bits8;
127 u8 allow_copying;
128 union {
129 u16 raw;
130
131 BitField<0, 3, u16> beard_type;
132 BitField<3, 3, u16> beard_color;
133 BitField<6, 4, u16> mustache_scale;
134 BitField<10, 5, u16> mustache_y;
135 } appearance_bits9;
136 union {
137 u16 raw;
138
139 BitField<0, 4, u16> glass_type;
140 BitField<4, 3, u16> glass_color;
141 BitField<7, 4, u16> glass_scale;
142 BitField<11, 5, u16> glass_y;
143 } appearance_bits10;
144 union {
145 u16 raw;
146
147 BitField<0, 1, u16> mole_type;
148 BitField<1, 4, u16> mole_scale;
149 BitField<5, 5, u16> mole_x;
150 BitField<10, 5, u16> mole_y;
151 } appearance_bits11;
152
153 Nickname author_name;
154 INSERT_PADDING_BYTES(0x2);
155 u16_be crc;
156};
157static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
158#pragma pack(pop)
159
160}; // namespace Service::Mii
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 49446bc42..674d2e4b2 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -28,7 +28,6 @@
28#include "core/hle/kernel/k_event.h" 28#include "core/hle/kernel/k_event.h"
29#include "core/hle/service/ipc_helpers.h" 29#include "core/hle/service/ipc_helpers.h"
30#include "core/hle/service/mii/mii_manager.h" 30#include "core/hle/service/mii/mii_manager.h"
31#include "core/hle/service/mii/types.h"
32#include "core/hle/service/nfc/common/amiibo_crypto.h" 31#include "core/hle/service/nfc/common/amiibo_crypto.h"
33#include "core/hle/service/nfc/common/device.h" 32#include "core/hle/service/nfc/common/device.h"
34#include "core/hle/service/nfc/mifare_result.h" 33#include "core/hle/service/nfc/mifare_result.h"
@@ -681,12 +680,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const {
681 return ResultRegistrationIsNotInitialized; 680 return ResultRegistrationIsNotInitialized;
682 } 681 }
683 682
684 Service::Mii::MiiManager manager; 683 Mii::CharInfo char_info{};
684 Mii::StoreData store_data{};
685 tag_data.owner_mii.BuildToStoreData(store_data);
686 char_info.SetFromStoreData(store_data);
687
685 const auto& settings = tag_data.settings; 688 const auto& settings = tag_data.settings;
686 689
687 // TODO: Validate this data 690 // TODO: Validate this data
688 register_info = { 691 register_info = {
689 .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), 692 .mii_char_info = char_info,
690 .creation_date = settings.init_date.GetWriteDate(), 693 .creation_date = settings.init_date.GetWriteDate(),
691 .amiibo_name = GetAmiiboName(settings), 694 .amiibo_name = GetAmiiboName(settings),
692 .font_region = settings.settings.font_region, 695 .font_region = settings.settings.font_region,
@@ -825,8 +828,11 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
825 return ResultWrongDeviceState; 828 return ResultWrongDeviceState;
826 } 829 }
827 830
828 Service::Mii::MiiManager manager; 831 Service::Mii::StoreData store_data{};
829 const auto mii = manager.BuildDefault(0); 832 Service::Mii::NfpStoreDataExtension extension{};
833 store_data.BuildBase(Mii::Gender::Male);
834 extension.SetFromStoreData(store_data);
835
830 auto& settings = tag_data.settings; 836 auto& settings = tag_data.settings;
831 837
832 if (tag_data.settings.settings.amiibo_initialized == 0) { 838 if (tag_data.settings.settings.amiibo_initialized == 0) {
@@ -835,8 +841,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
835 } 841 }
836 842
837 SetAmiiboName(settings, register_info.amiibo_name); 843 SetAmiiboName(settings, register_info.amiibo_name);
838 tag_data.owner_mii = manager.BuildFromStoreData(mii); 844 tag_data.owner_mii.BuildFromStoreData(store_data);
839 tag_data.mii_extension = manager.SetFromStoreData(mii); 845 tag_data.mii_extension = extension;
840 tag_data.unknown = 0; 846 tag_data.unknown = 0;
841 tag_data.unknown2 = {}; 847 tag_data.unknown2 = {};
842 settings.country_code_id = 0; 848 settings.country_code_id = 0;
@@ -868,17 +874,19 @@ Result NfcDevice::RestoreAmiibo() {
868} 874}
869 875
870Result NfcDevice::Format() { 876Result NfcDevice::Format() {
871 auto result1 = DeleteApplicationArea(); 877 Result result = ResultSuccess;
872 auto result2 = DeleteRegisterInfo();
873 878
874 if (result1.IsError()) { 879 if (device_state == DeviceState::TagFound) {
875 return result1; 880 result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
876 } 881 }
877 882
878 if (result2.IsError()) { 883 if (result.IsError()) {
879 return result2; 884 return result;
880 } 885 }
881 886
887 DeleteApplicationArea();
888 DeleteRegisterInfo();
889
882 return Flush(); 890 return Flush();
883} 891}
884 892
@@ -1453,7 +1461,7 @@ void NfcDevice::UpdateRegisterInfoCrc() {
1453 1461
1454void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, 1462void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
1455 const NFP::EncryptedNTAG215File& encrypted_file) const { 1463 const NFP::EncryptedNTAG215File& encrypted_file) const {
1456 Service::Mii::MiiManager manager; 1464 Service::Mii::StoreData store_data{};
1457 auto& settings = stubbed_tag_data.settings; 1465 auto& settings = stubbed_tag_data.settings;
1458 1466
1459 stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); 1467 stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
@@ -1467,7 +1475,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
1467 SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); 1475 SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
1468 settings.settings.font_region.Assign(0); 1476 settings.settings.font_region.Assign(0);
1469 settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); 1477 settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
1470 stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); 1478 store_data.BuildBase(Mii::Gender::Male);
1479 stubbed_tag_data.owner_mii.BuildFromStoreData(store_data);
1471 1480
1472 // Admin info 1481 // Admin info
1473 settings.settings.amiibo_initialized.Assign(1); 1482 settings.settings.amiibo_initialized.Assign(1);
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index aed12a7f8..f96d21220 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -6,7 +6,9 @@
6#include <array> 6#include <array>
7 7
8#include "common/swap.h" 8#include "common/swap.h"
9#include "core/hle/service/mii/types.h" 9#include "core/hle/service/mii/types/char_info.h"
10#include "core/hle/service/mii/types/store_data.h"
11#include "core/hle/service/mii/types/ver3_store_data.h"
10#include "core/hle/service/nfc/nfc_types.h" 12#include "core/hle/service/nfc/nfc_types.h"
11 13
12namespace Service::NFP { 14namespace Service::NFP {
@@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
322 324
323// This is nn::nfp::RegisterInfoPrivate 325// This is nn::nfp::RegisterInfoPrivate
324struct RegisterInfoPrivate { 326struct RegisterInfoPrivate {
325 Service::Mii::MiiStoreData mii_store_data; 327 Service::Mii::StoreData mii_store_data;
326 WriteDate creation_date; 328 WriteDate creation_date;
327 AmiiboName amiibo_name; 329 AmiiboName amiibo_name;
328 u8 font_region; 330 u8 font_region;
diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp
new file mode 100644
index 000000000..c26019ec0
--- /dev/null
+++ b/src/core/hle/service/ngc/ngc.cpp
@@ -0,0 +1,150 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/string_util.h"
5#include "core/core.h"
6#include "core/hle/service/ipc_helpers.h"
7#include "core/hle/service/ngc/ngc.h"
8#include "core/hle/service/server_manager.h"
9#include "core/hle/service/service.h"
10
11namespace Service::NGC {
12
13class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
14public:
15 explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {0, &NgctServiceImpl::Match, "Match"},
19 {1, &NgctServiceImpl::Filter, "Filter"},
20 };
21 // clang-format on
22
23 RegisterHandlers(functions);
24 }
25
26private:
27 void Match(HLERequestContext& ctx) {
28 const auto buffer = ctx.ReadBuffer();
29 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
30 reinterpret_cast<const char*>(buffer.data()), buffer.size());
31
32 LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
33
34 IPC::ResponseBuilder rb{ctx, 3};
35 rb.Push(ResultSuccess);
36 // Return false since we don't censor anything
37 rb.Push(false);
38 }
39
40 void Filter(HLERequestContext& ctx) {
41 const auto buffer = ctx.ReadBuffer();
42 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
43 reinterpret_cast<const char*>(buffer.data()), buffer.size());
44
45 LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
46
47 // Return the same string since we don't censor anything
48 ctx.WriteBuffer(buffer);
49
50 IPC::ResponseBuilder rb{ctx, 2};
51 rb.Push(ResultSuccess);
52 }
53};
54
55class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
56public:
57 explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") {
58 // clang-format off
59 static const FunctionInfo functions[] = {
60 {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"},
61 {1, &NgcServiceImpl::Check, "Check"},
62 {2, &NgcServiceImpl::Mask, "Mask"},
63 {3, &NgcServiceImpl::Reload, "Reload"},
64 };
65 // clang-format on
66
67 RegisterHandlers(functions);
68 }
69
70private:
71 static constexpr u32 NgcContentVersion = 1;
72
73 // This is nn::ngc::detail::ProfanityFilterOption
74 struct ProfanityFilterOption {
75 INSERT_PADDING_BYTES_NOINIT(0x20);
76 };
77 static_assert(sizeof(ProfanityFilterOption) == 0x20,
78 "ProfanityFilterOption has incorrect size");
79
80 void GetContentVersion(HLERequestContext& ctx) {
81 LOG_INFO(Service_NGC, "(STUBBED) called");
82
83 // This calls nn::ngc::ProfanityFilter::GetContentVersion
84 const u32 version = NgcContentVersion;
85
86 IPC::ResponseBuilder rb{ctx, 3};
87 rb.Push(ResultSuccess);
88 rb.Push(version);
89 }
90
91 void Check(HLERequestContext& ctx) {
92 LOG_INFO(Service_NGC, "(STUBBED) called");
93
94 struct InputParameters {
95 u32 flags;
96 ProfanityFilterOption option;
97 };
98
99 IPC::RequestParser rp{ctx};
100 [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
101 [[maybe_unused]] const auto input = ctx.ReadBuffer(0);
102
103 // This calls nn::ngc::ProfanityFilter::CheckProfanityWords
104 const u32 out_flags = 0;
105
106 IPC::ResponseBuilder rb{ctx, 3};
107 rb.Push(ResultSuccess);
108 rb.Push(out_flags);
109 }
110
111 void Mask(HLERequestContext& ctx) {
112 LOG_INFO(Service_NGC, "(STUBBED) called");
113
114 struct InputParameters {
115 u32 flags;
116 ProfanityFilterOption option;
117 };
118
119 IPC::RequestParser rp{ctx};
120 [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
121 const auto input = ctx.ReadBuffer(0);
122
123 // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText
124 const u32 out_flags = 0;
125 ctx.WriteBuffer(input);
126
127 IPC::ResponseBuilder rb{ctx, 3};
128 rb.Push(ResultSuccess);
129 rb.Push(out_flags);
130 }
131
132 void Reload(HLERequestContext& ctx) {
133 LOG_INFO(Service_NGC, "(STUBBED) called");
134
135 // This reloads the database.
136
137 IPC::ResponseBuilder rb{ctx, 2};
138 rb.Push(ResultSuccess);
139 }
140};
141
142void LoopProcess(Core::System& system) {
143 auto server_manager = std::make_unique<ServerManager>(system);
144
145 server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
146 server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
147 ServerManager::RunServer(std::move(server_manager));
148}
149
150} // namespace Service::NGC
diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngc/ngc.h
index 27c34dad4..823b1aa81 100644
--- a/src/core/hle/service/ngct/ngct.h
+++ b/src/core/hle/service/ngc/ngc.h
@@ -7,8 +7,8 @@ namespace Core {
7class System; 7class System;
8} 8}
9 9
10namespace Service::NGCT { 10namespace Service::NGC {
11 11
12void LoopProcess(Core::System& system); 12void LoopProcess(Core::System& system);
13 13
14} // namespace Service::NGCT 14} // namespace Service::NGC
diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp
deleted file mode 100644
index 493c80ed2..000000000
--- a/src/core/hle/service/ngct/ngct.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/string_util.h"
5#include "core/core.h"
6#include "core/hle/service/ipc_helpers.h"
7#include "core/hle/service/ngct/ngct.h"
8#include "core/hle/service/server_manager.h"
9#include "core/hle/service/service.h"
10
11namespace Service::NGCT {
12
13class IService final : public ServiceFramework<IService> {
14public:
15 explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {0, &IService::Match, "Match"},
19 {1, &IService::Filter, "Filter"},
20 };
21 // clang-format on
22
23 RegisterHandlers(functions);
24 }
25
26private:
27 void Match(HLERequestContext& ctx) {
28 const auto buffer = ctx.ReadBuffer();
29 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
30 reinterpret_cast<const char*>(buffer.data()), buffer.size());
31
32 LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
33
34 IPC::ResponseBuilder rb{ctx, 3};
35 rb.Push(ResultSuccess);
36 // Return false since we don't censor anything
37 rb.Push(false);
38 }
39
40 void Filter(HLERequestContext& ctx) {
41 const auto buffer = ctx.ReadBuffer();
42 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
43 reinterpret_cast<const char*>(buffer.data()), buffer.size());
44
45 LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
46
47 // Return the same string since we don't censor anything
48 ctx.WriteBuffer(buffer);
49
50 IPC::ResponseBuilder rb{ctx, 2};
51 rb.Push(ResultSuccess);
52 }
53};
54
55void LoopProcess(Core::System& system) {
56 auto server_manager = std::make_unique<ServerManager>(system);
57
58 server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system));
59 ServerManager::RunServer(std::move(server_manager));
60}
61
62} // namespace Service::NGCT
diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp
index a51ca5444..0ca05257e 100644
--- a/src/core/hle/service/nvdrv/core/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/core/nvmap.cpp
@@ -160,8 +160,8 @@ u32 NvMap::PinHandle(NvMap::Handle::Id handle) {
160 u32 address{}; 160 u32 address{};
161 auto& smmu_allocator = host1x.Allocator(); 161 auto& smmu_allocator = host1x.Allocator();
162 auto& smmu_memory_manager = host1x.MemoryManager(); 162 auto& smmu_memory_manager = host1x.MemoryManager();
163 while (!(address = 163 while ((address = smmu_allocator.Allocate(
164 smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) { 164 static_cast<u32>(handle_description->aligned_size))) == 0) {
165 // Free handles until the allocation succeeds 165 // Free handles until the allocation succeeds
166 std::scoped_lock queueLock(unmap_queue_lock); 166 std::scoped_lock queueLock(unmap_queue_lock);
167 if (auto freeHandleDesc{unmap_queue.front()}) { 167 if (auto freeHandleDesc{unmap_queue.front()}) {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index 07e570a9f..7d7bb8687 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -204,9 +204,11 @@ void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
204 if (!mapping->fixed) { 204 if (!mapping->fixed) {
205 auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; 205 auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
206 u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; 206 u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
207 u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
208 u64 aligned_size{Common::AlignUp(mapping->size, page_size)};
207 209
208 allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits), 210 allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
209 static_cast<u32>(mapping->size >> page_size_bits)); 211 static_cast<u32>(aligned_size >> page_size_bits));
210 } 212 }
211 213
212 // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state 214 // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
index b16f9933f..dc6917d5d 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
@@ -449,6 +449,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
449 case NativeWindowScalingMode::ScaleToWindow: 449 case NativeWindowScalingMode::ScaleToWindow:
450 case NativeWindowScalingMode::ScaleCrop: 450 case NativeWindowScalingMode::ScaleCrop:
451 case NativeWindowScalingMode::NoScaleCrop: 451 case NativeWindowScalingMode::NoScaleCrop:
452 case NativeWindowScalingMode::PreserveAspectRatio:
452 break; 453 break;
453 default: 454 default:
454 LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode); 455 LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode);
diff --git a/src/core/hle/service/nvnflinger/window.h b/src/core/hle/service/nvnflinger/window.h
index 61cca5b01..36d6cde3d 100644
--- a/src/core/hle/service/nvnflinger/window.h
+++ b/src/core/hle/service/nvnflinger/window.h
@@ -41,6 +41,7 @@ enum class NativeWindowScalingMode : s32 {
41 ScaleToWindow = 1, 41 ScaleToWindow = 1,
42 ScaleCrop = 2, 42 ScaleCrop = 2,
43 NoScaleCrop = 3, 43 NoScaleCrop = 3,
44 PreserveAspectRatio = 4,
44}; 45};
45 46
46/// Transform parameter for QueueBuffer 47/// Transform parameter for QueueBuffer
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 69cdb5918..0ad607391 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -43,7 +43,7 @@
43#include "core/hle/service/ncm/ncm.h" 43#include "core/hle/service/ncm/ncm.h"
44#include "core/hle/service/nfc/nfc.h" 44#include "core/hle/service/nfc/nfc.h"
45#include "core/hle/service/nfp/nfp.h" 45#include "core/hle/service/nfp/nfp.h"
46#include "core/hle/service/ngct/ngct.h" 46#include "core/hle/service/ngc/ngc.h"
47#include "core/hle/service/nifm/nifm.h" 47#include "core/hle/service/nifm/nifm.h"
48#include "core/hle/service/nim/nim.h" 48#include "core/hle/service/nim/nim.h"
49#include "core/hle/service/npns/npns.h" 49#include "core/hle/service/npns/npns.h"
@@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
257 kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); 257 kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
258 kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); 258 kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
259 kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); 259 kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
260 kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); }); 260 kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
261 kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); 261 kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
262 kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); 262 kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
263 kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); 263 kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 45b2c43b7..d539ed0f4 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -79,8 +79,8 @@ protected:
79 using HandlerFnP = void (Self::*)(HLERequestContext&); 79 using HandlerFnP = void (Self::*)(HLERequestContext&);
80 80
81 /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. 81 /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
82 [[nodiscard]] std::scoped_lock<std::mutex> LockService() { 82 [[nodiscard]] virtual std::unique_lock<std::mutex> LockService() {
83 return std::scoped_lock{lock_service}; 83 return std::unique_lock{lock_service};
84 } 84 }
85 85
86 /// System context that the service operates under. 86 /// System context that the service operates under.
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 11f8efbac..85849d5f3 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) {
170} 170}
171 171
172void BSD::Select(HLERequestContext& ctx) { 172void BSD::Select(HLERequestContext& ctx) {
173 LOG_WARNING(Service, "(STUBBED) called"); 173 LOG_DEBUG(Service, "(STUBBED) called");
174 174
175 IPC::ResponseBuilder rb{ctx, 4}; 175 IPC::ResponseBuilder rb{ctx, 4};
176 176
@@ -1029,6 +1029,11 @@ BSD::~BSD() {
1029 } 1029 }
1030} 1030}
1031 1031
1032std::unique_lock<std::mutex> BSD::LockService() {
1033 // Do not lock socket IClient instances.
1034 return {};
1035}
1036
1032BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { 1037BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} {
1033 // clang-format off 1038 // clang-format off
1034 static const FunctionInfo functions[] = { 1039 static const FunctionInfo functions[] = {
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 430edb97c..161f22b9b 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -186,6 +186,9 @@ private:
186 186
187 // Callback identifier for the OnProxyPacketReceived event. 187 // Callback identifier for the OnProxyPacketReceived event.
188 Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received; 188 Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received;
189
190protected:
191 virtual std::unique_lock<std::mutex> LockService() override;
189}; 192};
190 193
191class BSDCFG final : public ServiceFramework<BSDCFG> { 194class BSDCFG final : public ServiceFramework<BSDCFG> {
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
index bac21752a..491b76d48 100644
--- a/src/core/hle/service/sockets/nsd.cpp
+++ b/src/core/hle/service/sockets/nsd.cpp
@@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 {
19 Dp, 19 Dp,
20}; 20};
21 21
22// This is nn::nsd::EnvironmentIdentifier
23struct EnvironmentIdentifier {
24 std::array<u8, 8> identifier;
25};
26static_assert(sizeof(EnvironmentIdentifier) == 0x8);
27
22NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { 28NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
23 // clang-format off 29 // clang-format off
24 static const FunctionInfo functions[] = { 30 static const FunctionInfo functions[] = {
@@ -101,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) {
101} 107}
102 108
103void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { 109void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) {
104 const std::string environment_identifier = "lp1"; 110 constexpr EnvironmentIdentifier lp1 = {
105 ctx.WriteBuffer(environment_identifier); 111 .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}};
112 ctx.WriteBuffer(lp1);
106 113
107 IPC::ResponseBuilder rb{ctx, 2}; 114 IPC::ResponseBuilder rb{ctx, 2};
108 rb.Push(ResultSuccess); 115 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index 22e4a6f49..c657c4efd 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -150,6 +150,12 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
150 const std::string host = Common::StringFromBuffer(host_buffer); 150 const std::string host = Common::StringFromBuffer(host_buffer);
151 // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. 151 // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
152 152
153 // Prevent resolution of Nintendo servers
154 if (host.find("srv.nintendo.net") != std::string::npos) {
155 LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
156 return {0, GetAddrInfoError::AGAIN};
157 }
158
153 auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); 159 auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt);
154 if (!res.has_value()) { 160 if (!res.has_value()) {
155 return {0, Translate(res.error())}; 161 return {0, Translate(res.error())};
@@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
261 const auto host_buffer = ctx.ReadBuffer(0); 267 const auto host_buffer = ctx.ReadBuffer(0);
262 const std::string host = Common::StringFromBuffer(host_buffer); 268 const std::string host = Common::StringFromBuffer(host_buffer);
263 269
270 // Prevent resolution of Nintendo servers
271 if (host.find("srv.nintendo.net") != std::string::npos) {
272 LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
273 return {0, GetAddrInfoError::AGAIN};
274 }
275
264 std::optional<std::string> service = std::nullopt; 276 std::optional<std::string> service = std::nullopt;
265 if (ctx.CanReadBuffer(1)) { 277 if (ctx.CanReadBuffer(1)) {
266 const std::span<const u8> service_buffer = ctx.ReadBuffer(1); 278 const std::span<const u8> service_buffer = ctx.ReadBuffer(1);
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index 77426c46e..f86af01a4 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -18,7 +18,9 @@ enum class Errno : u32 {
18 AGAIN = 11, 18 AGAIN = 11,
19 INVAL = 22, 19 INVAL = 22,
20 MFILE = 24, 20 MFILE = 24,
21 PIPE = 32,
21 MSGSIZE = 90, 22 MSGSIZE = 90,
23 CONNABORTED = 103,
22 CONNRESET = 104, 24 CONNRESET = 104,
23 NOTCONN = 107, 25 NOTCONN = 107,
24 TIMEDOUT = 110, 26 TIMEDOUT = 110,
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
index c1187209f..aed05250c 100644
--- a/src/core/hle/service/sockets/sockets_translate.cpp
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) {
23 return Errno::INVAL; 23 return Errno::INVAL;
24 case Network::Errno::MFILE: 24 case Network::Errno::MFILE:
25 return Errno::MFILE; 25 return Errno::MFILE;
26 case Network::Errno::PIPE:
27 return Errno::PIPE;
26 case Network::Errno::NOTCONN: 28 case Network::Errno::NOTCONN:
27 return Errno::NOTCONN; 29 return Errno::NOTCONN;
28 case Network::Errno::TIMEDOUT: 30 case Network::Errno::TIMEDOUT:
29 return Errno::TIMEDOUT; 31 return Errno::TIMEDOUT;
32 case Network::Errno::CONNABORTED:
33 return Errno::CONNABORTED;
30 case Network::Errno::CONNRESET: 34 case Network::Errno::CONNRESET:
31 return Errno::CONNRESET; 35 return Errno::CONNRESET;
32 case Network::Errno::INPROGRESS: 36 case Network::Errno::INPROGRESS:
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index 2cba9e5c9..6c8427b0d 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -139,7 +139,6 @@ private:
139 bool do_not_close_socket = false; 139 bool do_not_close_socket = false;
140 bool get_server_cert_chain = false; 140 bool get_server_cert_chain = false;
141 std::shared_ptr<Network::SocketBase> socket; 141 std::shared_ptr<Network::SocketBase> socket;
142 bool did_set_host_name = false;
143 bool did_handshake = false; 142 bool did_handshake = false;
144 143
145 Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { 144 Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
@@ -174,11 +173,7 @@ private:
174 Result SetHostNameImpl(const std::string& hostname) { 173 Result SetHostNameImpl(const std::string& hostname) {
175 LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); 174 LOG_DEBUG(Service_SSL, "called. hostname={}", hostname);
176 ASSERT(!did_handshake); 175 ASSERT(!did_handshake);
177 Result res = backend->SetHostName(hostname); 176 return backend->SetHostName(hostname);
178 if (res == ResultSuccess) {
179 did_set_host_name = true;
180 }
181 return res;
182 } 177 }
183 178
184 Result SetVerifyOptionImpl(u32 option) { 179 Result SetVerifyOptionImpl(u32 option) {
@@ -208,9 +203,6 @@ private:
208 203
209 Result DoHandshakeImpl() { 204 Result DoHandshakeImpl() {
210 ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); 205 ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; });
211 ASSERT_OR_EXECUTE_MSG(
212 did_set_host_name, { return ResultInternalError; },
213 "Expected SetHostName before DoHandshake");
214 Result res = backend->DoHandshake(); 206 Result res = backend->DoHandshake();
215 did_handshake = res.IsSuccess(); 207 did_handshake = res.IsSuccess();
216 return res; 208 return res;
diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp
index b2dd37cd4..5714e6f3c 100644
--- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp
@@ -167,9 +167,8 @@ public:
167 } 167 }
168 168
169 ~SSLConnectionBackendOpenSSL() { 169 ~SSLConnectionBackendOpenSSL() {
170 // these are null-tolerant: 170 // this is null-tolerant:
171 SSL_free(ssl); 171 SSL_free(ssl);
172 BIO_free(bio);
173 } 172 }
174 173
175 static void KeyLogCallback(const SSL* ssl, const char* line) { 174 static void KeyLogCallback(const SSL* ssl, const char* line) {
diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp
index bda12b761..212057cfc 100644
--- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp
@@ -31,9 +31,9 @@ CredHandle cred_handle;
31static void OneTimeInit() { 31static void OneTimeInit() {
32 schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; 32 schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
33 schannel_cred.dwFlags = 33 schannel_cred.dwFlags =
34 SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols 34 SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
35 SCH_CRED_AUTO_CRED_VALIDATION | // validate certs 35 SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names
36 SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate 36 SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
37 // ^ I'm assuming that nobody would want to connect Yuzu to a 37 // ^ I'm assuming that nobody would want to connect Yuzu to a
38 // service that requires some OS-provided corporate client 38 // service that requires some OS-provided corporate client
39 // certificate, and presenting one to some arbitrary server 39 // certificate, and presenting one to some arbitrary server
@@ -227,16 +227,15 @@ public:
227 ciphertext_read_buf.size()); 227 ciphertext_read_buf.size());
228 } 228 }
229 229
230 const SECURITY_STATUS ret = 230 char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr;
231 InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt : nullptr, 231 const SECURITY_STATUS ret = InitializeSecurityContextA(
232 // Caller ensured we have set a hostname: 232 &cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req,
233 const_cast<char*>(hostname.value().c_str()), req, 233 0, // Reserved1
234 0, // Reserved1 234 0, // TargetDataRep not used with Schannel
235 0, // TargetDataRep not used with Schannel 235 initial_call_done ? &input_desc : nullptr,
236 initial_call_done ? &input_desc : nullptr, 236 0, // Reserved2
237 0, // Reserved2 237 initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
238 initial_call_done ? nullptr : &ctxt, &output_desc, &attr, 238 nullptr); // ptsExpiry
239 nullptr); // ptsExpiry
240 239
241 if (output_buffers[0].pvBuffer) { 240 if (output_buffers[0].pvBuffer) {
242 const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer), 241 const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
@@ -478,7 +477,8 @@ public:
478 return ResultInternalError; 477 return ResultInternalError;
479 } 478 }
480 PCCERT_CONTEXT some_cert = nullptr; 479 PCCERT_CONTEXT some_cert = nullptr;
481 while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) { 480 while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) !=
481 nullptr) {
482 out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), 482 out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
483 static_cast<u8*>(some_cert->pbCertEncoded) + 483 static_cast<u8*>(some_cert->pbCertEncoded) +
484 some_cert->cbCertEncoded); 484 some_cert->cbCertEncoded);
diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
index 370678f48..c48914f64 100644
--- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
@@ -100,7 +100,7 @@ public:
100 100
101 Result DoHandshake() override { 101 Result DoHandshake() override {
102 OSStatus status = SSLHandshake(context); 102 OSStatus status = SSLHandshake(context);
103 return HandleReturn("SSLHandshake", 0, status).Code(); 103 return HandleReturn("SSLHandshake", 0, status);
104 } 104 }
105 105
106 Result Read(size_t* out_size, std::span<u8> data) override { 106 Result Read(size_t* out_size, std::span<u8> data) override {
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 6bb02393c..2eb978379 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -217,7 +217,7 @@ private:
217 IPC::ResponseBuilder rb{ctx, 6}; 217 IPC::ResponseBuilder rb{ctx, 6};
218 rb.Push(ResultSuccess); 218 rb.Push(ResultSuccess);
219 219
220 if (Settings::values.use_docked_mode.GetValue()) { 220 if (Settings::IsDockedMode()) {
221 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); 221 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth));
222 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); 222 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight));
223 } else { 223 } else {
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index 28f89c599..a983f23ea 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -39,19 +39,41 @@ namespace Network {
39 39
40namespace { 40namespace {
41 41
42enum class CallType {
43 Send,
44 Other,
45};
46
42#ifdef _WIN32 47#ifdef _WIN32
43 48
44using socklen_t = int; 49using socklen_t = int;
45 50
51SOCKET interrupt_socket = static_cast<SOCKET>(-1);
52
53void InterruptSocketOperations() {
54 closesocket(interrupt_socket);
55}
56
57void AcknowledgeInterrupt() {
58 interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
59}
60
46void Initialize() { 61void Initialize() {
47 WSADATA wsa_data; 62 WSADATA wsa_data;
48 (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); 63 (void)WSAStartup(MAKEWORD(2, 2), &wsa_data);
64
65 AcknowledgeInterrupt();
49} 66}
50 67
51void Finalize() { 68void Finalize() {
69 InterruptSocketOperations();
52 WSACleanup(); 70 WSACleanup();
53} 71}
54 72
73SOCKET GetInterruptSocket() {
74 return interrupt_socket;
75}
76
55sockaddr TranslateFromSockAddrIn(SockAddrIn input) { 77sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
56 sockaddr_in result; 78 sockaddr_in result;
57 79
@@ -96,7 +118,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) {
96 return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; 118 return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
97} 119}
98 120
99Errno TranslateNativeError(int e) { 121Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
100 switch (e) { 122 switch (e) {
101 case 0: 123 case 0:
102 return Errno::SUCCESS; 124 return Errno::SUCCESS;
@@ -112,6 +134,14 @@ Errno TranslateNativeError(int e) {
112 return Errno::AGAIN; 134 return Errno::AGAIN;
113 case WSAECONNREFUSED: 135 case WSAECONNREFUSED:
114 return Errno::CONNREFUSED; 136 return Errno::CONNREFUSED;
137 case WSAECONNABORTED:
138 if (call_type == CallType::Send) {
139 // Winsock yields WSAECONNABORTED from `send` in situations where Unix
140 // systems, and actual Switches, yield EPIPE.
141 return Errno::PIPE;
142 } else {
143 return Errno::CONNABORTED;
144 }
115 case WSAECONNRESET: 145 case WSAECONNRESET:
116 return Errno::CONNRESET; 146 return Errno::CONNRESET;
117 case WSAEHOSTUNREACH: 147 case WSAEHOSTUNREACH:
@@ -144,9 +174,42 @@ constexpr int SD_RECEIVE = SHUT_RD;
144constexpr int SD_SEND = SHUT_WR; 174constexpr int SD_SEND = SHUT_WR;
145constexpr int SD_BOTH = SHUT_RDWR; 175constexpr int SD_BOTH = SHUT_RDWR;
146 176
147void Initialize() {} 177int interrupt_pipe_fd[2] = {-1, -1};
148 178
149void Finalize() {} 179void Initialize() {
180 if (pipe(interrupt_pipe_fd) != 0) {
181 LOG_ERROR(Network, "Failed to create interrupt pipe!");
182 }
183 int flags = fcntl(interrupt_pipe_fd[0], F_GETFL);
184 ASSERT_MSG(fcntl(interrupt_pipe_fd[0], F_SETFL, flags | O_NONBLOCK) == 0,
185 "Failed to set nonblocking state for interrupt pipe");
186}
187
188void Finalize() {
189 if (interrupt_pipe_fd[0] >= 0) {
190 close(interrupt_pipe_fd[0]);
191 }
192 if (interrupt_pipe_fd[1] >= 0) {
193 close(interrupt_pipe_fd[1]);
194 }
195}
196
197void InterruptSocketOperations() {
198 u8 value = 0;
199 ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1);
200}
201
202void AcknowledgeInterrupt() {
203 u8 value = 0;
204 ssize_t ret = read(interrupt_pipe_fd[0], &value, sizeof(value));
205 if (ret != 1 && errno != EAGAIN && errno != EWOULDBLOCK) {
206 LOG_ERROR(Network, "Failed to acknowledge interrupt on shutdown");
207 }
208}
209
210SOCKET GetInterruptSocket() {
211 return interrupt_pipe_fd[0];
212}
150 213
151sockaddr TranslateFromSockAddrIn(SockAddrIn input) { 214sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
152 sockaddr_in result; 215 sockaddr_in result;
@@ -198,7 +261,7 @@ bool EnableNonBlock(int fd, bool enable) {
198 return fcntl(fd, F_SETFL, flags) == 0; 261 return fcntl(fd, F_SETFL, flags) == 0;
199} 262}
200 263
201Errno TranslateNativeError(int e) { 264Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
202 switch (e) { 265 switch (e) {
203 case 0: 266 case 0:
204 return Errno::SUCCESS; 267 return Errno::SUCCESS;
@@ -208,6 +271,10 @@ Errno TranslateNativeError(int e) {
208 return Errno::INVAL; 271 return Errno::INVAL;
209 case EMFILE: 272 case EMFILE:
210 return Errno::MFILE; 273 return Errno::MFILE;
274 case EPIPE:
275 return Errno::PIPE;
276 case ECONNABORTED:
277 return Errno::CONNABORTED;
211 case ENOTCONN: 278 case ENOTCONN:
212 return Errno::NOTCONN; 279 return Errno::NOTCONN;
213 case EAGAIN: 280 case EAGAIN:
@@ -236,13 +303,13 @@ Errno TranslateNativeError(int e) {
236 303
237#endif 304#endif
238 305
239Errno GetAndLogLastError() { 306Errno GetAndLogLastError(CallType call_type = CallType::Other) {
240#ifdef _WIN32 307#ifdef _WIN32
241 int e = WSAGetLastError(); 308 int e = WSAGetLastError();
242#else 309#else
243 int e = errno; 310 int e = errno;
244#endif 311#endif
245 const Errno err = TranslateNativeError(e); 312 const Errno err = TranslateNativeError(e, call_type);
246 if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { 313 if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) {
247 // These happen during normal operation, so only log them at debug level. 314 // These happen during normal operation, so only log them at debug level.
248 LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); 315 LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
@@ -473,10 +540,24 @@ NetworkInstance::~NetworkInstance() {
473 Finalize(); 540 Finalize();
474} 541}
475 542
543void CancelPendingSocketOperations() {
544 InterruptSocketOperations();
545}
546
547void RestartSocketOperations() {
548 AcknowledgeInterrupt();
549}
550
476std::optional<IPv4Address> GetHostIPv4Address() { 551std::optional<IPv4Address> GetHostIPv4Address() {
477 const auto network_interface = Network::GetSelectedNetworkInterface(); 552 const auto network_interface = Network::GetSelectedNetworkInterface();
478 if (!network_interface.has_value()) { 553 if (!network_interface.has_value()) {
479 LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); 554 // Only print the error once to avoid log spam
555 static bool print_error = true;
556 if (print_error) {
557 LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface");
558 print_error = false;
559 }
560
480 return {}; 561 return {};
481 } 562 }
482 563
@@ -537,7 +618,14 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
537 return result; 618 return result;
538 }); 619 });
539 620
540 const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout); 621 host_pollfds.push_back(WSAPOLLFD{
622 .fd = GetInterruptSocket(),
623 .events = POLLIN,
624 .revents = 0,
625 });
626
627 const int result =
628 WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), timeout);
541 if (result == 0) { 629 if (result == 0) {
542 ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), 630 ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(),
543 [](WSAPOLLFD fd) { return fd.revents == 0; })); 631 [](WSAPOLLFD fd) { return fd.revents == 0; }));
@@ -604,6 +692,24 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
604std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { 692std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
605 sockaddr_in addr; 693 sockaddr_in addr;
606 socklen_t addrlen = sizeof(addr); 694 socklen_t addrlen = sizeof(addr);
695
696 std::vector<WSAPOLLFD> host_pollfds{
697 WSAPOLLFD{fd, POLLIN, 0},
698 WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
699 };
700
701 while (true) {
702 const int pollres =
703 WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), -1);
704 if (host_pollfds[1].revents != 0) {
705 // Interrupt signaled before a client could be accepted, break
706 return {AcceptResult{}, Errno::AGAIN};
707 }
708 if (pollres > 0) {
709 break;
710 }
711 }
712
607 const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen); 713 const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen);
608 714
609 if (new_socket == INVALID_SOCKET) { 715 if (new_socket == INVALID_SOCKET) {
@@ -725,13 +831,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) {
725 ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); 831 ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
726 ASSERT(flags == 0); 832 ASSERT(flags == 0);
727 833
834 int native_flags = 0;
835#if YUZU_UNIX
836 native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE
837#endif
728 const auto result = send(fd, reinterpret_cast<const char*>(message.data()), 838 const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
729 static_cast<int>(message.size()), 0); 839 static_cast<int>(message.size()), native_flags);
730 if (result != SOCKET_ERROR) { 840 if (result != SOCKET_ERROR) {
731 return {static_cast<s32>(result), Errno::SUCCESS}; 841 return {static_cast<s32>(result), Errno::SUCCESS};
732 } 842 }
733 843
734 return {-1, GetAndLogLastError()}; 844 return {-1, GetAndLogLastError(CallType::Send)};
735} 845}
736 846
737std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, 847std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
@@ -753,7 +863,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
753 return {static_cast<s32>(result), Errno::SUCCESS}; 863 return {static_cast<s32>(result), Errno::SUCCESS};
754 } 864 }
755 865
756 return {-1, GetAndLogLastError()}; 866 return {-1, GetAndLogLastError(CallType::Send)};
757} 867}
758 868
759Errno Socket::Close() { 869Errno Socket::Close() {
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
index badcb8369..b7b7d773a 100644
--- a/src/core/internal_network/network.h
+++ b/src/core/internal_network/network.h
@@ -33,10 +33,12 @@ enum class Errno {
33 BADF, 33 BADF,
34 INVAL, 34 INVAL,
35 MFILE, 35 MFILE,
36 PIPE,
36 NOTCONN, 37 NOTCONN,
37 AGAIN, 38 AGAIN,
38 CONNREFUSED, 39 CONNREFUSED,
39 CONNRESET, 40 CONNRESET,
41 CONNABORTED,
40 HOSTUNREACH, 42 HOSTUNREACH,
41 NETDOWN, 43 NETDOWN,
42 NETUNREACH, 44 NETUNREACH,
@@ -94,6 +96,9 @@ public:
94 ~NetworkInstance(); 96 ~NetworkInstance();
95}; 97};
96 98
99void CancelPendingSocketOperations();
100void RestartSocketOperations();
101
97#ifdef _WIN32 102#ifdef _WIN32
98constexpr IPv4Address TranslateIPv4(in_addr addr) { 103constexpr IPv4Address TranslateIPv4(in_addr addr) {
99 auto& bytes = addr.S_un.S_un_b; 104 auto& bytes = addr.S_un.S_un_b;
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp
index 4c909a6d3..7c37f660b 100644
--- a/src/core/internal_network/network_interface.cpp
+++ b/src/core/internal_network/network_interface.cpp
@@ -200,7 +200,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() {
200 }); 200 });
201 201
202 if (res == network_interfaces.end()) { 202 if (res == network_interfaces.end()) {
203 LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); 203 // Only print the error once to avoid log spam
204 static bool print_error = true;
205 if (print_error) {
206 LOG_ERROR(Network, "Couldn't find selected interface \"{}\"",
207 selected_network_interface);
208 print_error = false;
209 }
210
204 return std::nullopt; 211 return std::nullopt;
205 } 212 }
206 213
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index e04ad19db..5a42dea48 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -18,7 +18,7 @@ namespace Loader {
18 18
19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, 19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
20 bool override_update_) 20 bool override_update_)
21 : AppLoader(std::move(file_)), override_update(override_update_) { 21 : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
22 const auto file_dir = file->GetContainingDirectory(); 22 const auto file_dir = file->GetContainingDirectory();
23 23
24 // Title ID 24 // Title ID
@@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
69} 69}
70 70
71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( 71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
72 FileSys::VirtualDir directory, bool override_update_) 72 FileSys::VirtualDir directory, bool override_update_, bool is_hbl_)
73 : AppLoader(directory->GetFile("main")), dir(std::move(directory)), 73 : AppLoader(directory->GetFile("main")), dir(std::move(directory)),
74 override_update(override_update_) {} 74 override_update(override_update_), is_hbl(is_hbl_) {}
75 75
76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { 76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) {
77 if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { 77 if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) {
@@ -147,13 +147,13 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
147 } 147 }
148 148
149 // Setup the process code layout 149 // Setup the process code layout
150 if (process.LoadFromMetadata(metadata, code_size).IsError()) { 150 if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) {
151 return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; 151 return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
152 } 152 }
153 153
154 // Load NSO modules 154 // Load NSO modules
155 modules.clear(); 155 modules.clear();
156 const VAddr base_address{GetInteger(process.GetPageTable().GetCodeRegionStart())}; 156 const VAddr base_address{GetInteger(process.GetEntryPoint())};
157 VAddr next_load_addr{base_address}; 157 VAddr next_load_addr{base_address};
158 const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(), 158 const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
159 system.GetContentProvider()}; 159 system.GetContentProvider()};
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index f7702225e..1e9f765c9 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -27,7 +27,8 @@ public:
27 27
28 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' 28 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm'
29 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, 29 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory,
30 bool override_update_ = false); 30 bool override_update_ = false,
31 bool is_hbl_ = false);
31 32
32 /** 33 /**
33 * Identifies whether or not the given file is a deconstructed ROM directory. 34 * Identifies whether or not the given file is a deconstructed ROM directory.
@@ -62,6 +63,7 @@ private:
62 std::string name; 63 std::string name;
63 u64 title_id{}; 64 u64 title_id{};
64 bool override_update; 65 bool override_update;
66 bool is_hbl;
65 67
66 Modules modules; 68 Modules modules;
67}; 69};
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index ffe976b94..bf56a08b4 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -90,13 +90,14 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
90 codeset.DataSegment().size += kip->GetBSSSize(); 90 codeset.DataSegment().size += kip->GetBSSSize();
91 91
92 // Setup the process code layout 92 // Setup the process code layout
93 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) 93 if (process
94 .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
94 .IsError()) { 95 .IsError()) {
95 return {ResultStatus::ErrorNotInitialized, {}}; 96 return {ResultStatus::ErrorNotInitialized, {}};
96 } 97 }
97 98
98 codeset.memory = std::move(program_image); 99 codeset.memory = std::move(program_image);
99 const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart()); 100 const VAddr base_address = GetInteger(process.GetEntryPoint());
100 process.LoadModule(std::move(codeset), base_address); 101 process.LoadModule(std::move(codeset), base_address);
101 102
102 LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address); 103 LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address);
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index f24474ed8..b6e355622 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) {
108 return "unknown"; 108 return "unknown";
109} 109}
110 110
111constexpr std::array<const char*, 66> RESULT_MESSAGES{ 111constexpr std::array<const char*, 68> RESULT_MESSAGES{
112 "The operation completed successfully.", 112 "The operation completed successfully.",
113 "The loader requested to load is already loaded.", 113 "The loader requested to load is already loaded.",
114 "The operation is not implemented.", 114 "The operation is not implemented.",
@@ -135,7 +135,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
135 "The titlekey and/or titlekek is incorrect or the section header is invalid.", 135 "The titlekey and/or titlekek is incorrect or the section header is invalid.",
136 "The XCI file is missing a Program-type NCA.", 136 "The XCI file is missing a Program-type NCA.",
137 "The NCA file is not an application.", 137 "The NCA file is not an application.",
138 "The ExeFS partition could not be found.", 138 "The Program-type NCA contains no executable. An update may be required.",
139 "The XCI file has a bad header.", 139 "The XCI file has a bad header.",
140 "The XCI file is missing a partition.", 140 "The XCI file is missing a partition.",
141 "The file could not be found or does not exist.", 141 "The file could not be found or does not exist.",
@@ -169,12 +169,14 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
169 "The BKTR-type NCA has a bad Subsection block.", 169 "The BKTR-type NCA has a bad Subsection block.",
170 "The BKTR-type NCA has a bad Relocation bucket.", 170 "The BKTR-type NCA has a bad Relocation bucket.",
171 "The BKTR-type NCA has a bad Subsection bucket.", 171 "The BKTR-type NCA has a bad Subsection bucket.",
172 "The BKTR-type NCA is missing the base RomFS.", 172 "Game updates cannot be loaded directly. Load the base game instead.",
173 "The NSP or XCI does not contain an update in addition to the base game.", 173 "The NSP or XCI does not contain an update in addition to the base game.",
174 "The KIP file has a bad header.", 174 "The KIP file has a bad header.",
175 "The KIP BLZ decompression of the section failed unexpectedly.", 175 "The KIP BLZ decompression of the section failed unexpectedly.",
176 "The INI file has a bad header.", 176 "The INI file has a bad header.",
177 "The INI file contains more than the maximum allowable number of KIP files.", 177 "The INI file contains more than the maximum allowable number of KIP files.",
178 "Integrity verification could not be performed for this file.",
179 "Integrity verification failed.",
178}; 180};
179 181
180std::string GetResultStatusString(ResultStatus status) { 182std::string GetResultStatusString(ResultStatus status) {
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 7a2a52fd4..b4828f7cd 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <functional>
6#include <iosfwd> 7#include <iosfwd>
7#include <memory> 8#include <memory>
8#include <optional> 9#include <optional>
@@ -79,8 +80,6 @@ enum class ResultStatus : u16 {
79 ErrorBadPFSHeader, 80 ErrorBadPFSHeader,
80 ErrorIncorrectPFSFileSize, 81 ErrorIncorrectPFSFileSize,
81 ErrorBadNCAHeader, 82 ErrorBadNCAHeader,
82 ErrorCompressedNCA,
83 ErrorSparseNCA,
84 ErrorMissingProductionKeyFile, 83 ErrorMissingProductionKeyFile,
85 ErrorMissingHeaderKey, 84 ErrorMissingHeaderKey,
86 ErrorIncorrectHeaderKey, 85 ErrorIncorrectHeaderKey,
@@ -134,6 +133,8 @@ enum class ResultStatus : u16 {
134 ErrorBLZDecompressionFailed, 133 ErrorBLZDecompressionFailed,
135 ErrorBadINIHeader, 134 ErrorBadINIHeader,
136 ErrorINITooManyKIPs, 135 ErrorINITooManyKIPs,
136 ErrorIntegrityVerificationNotImplemented,
137 ErrorIntegrityVerificationFailed,
137}; 138};
138 139
139std::string GetResultStatusString(ResultStatus status); 140std::string GetResultStatusString(ResultStatus status);
@@ -172,6 +173,13 @@ public:
172 virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; 173 virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0;
173 174
174 /** 175 /**
176 * Try to verify the integrity of the file.
177 */
178 virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
179 return ResultStatus::ErrorIntegrityVerificationNotImplemented;
180 }
181
182 /**
175 * Get the code (typically .code section) of the application 183 * Get the code (typically .code section) of the application
176 * 184 *
177 * @param[out] buffer Reference to buffer to store data 185 * @param[out] buffer Reference to buffer to store data
@@ -276,16 +284,6 @@ public:
276 } 284 }
277 285
278 /** 286 /**
279 * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS)
280 * data. Needed for BKTR patching.
281 *
282 * @return IVFC offset for RomFS.
283 */
284 virtual u64 ReadRomFSIVFCOffset() const {
285 return 0;
286 }
287
288 /**
289 * Get the title of the application 287 * Get the title of the application
290 * 288 *
291 * @param[out] title Reference to store the application title into 289 * @param[out] title Reference to store the application title into
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index cf35b1249..3b7b005ff 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -76,10 +76,6 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
76 return nca_loader->ReadRomFS(dir); 76 return nca_loader->ReadRomFS(dir);
77} 77}
78 78
79u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
80 return nca_loader->ReadRomFSIVFCOffset();
81}
82
83ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { 79ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
84 return nca_loader->ReadProgramId(out_program_id); 80 return nca_loader->ReadProgramId(out_program_id);
85} 81}
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index d7f70db43..81df2bbcd 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -39,7 +39,6 @@ public:
39 LoadResult Load(Kernel::KProcess& process, Core::System& system) override; 39 LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
40 40
41 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 41 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
42 u64 ReadRomFSIVFCOffset() const override;
43 ResultStatus ReadProgramId(u64& out_program_id) override; 42 ResultStatus ReadProgramId(u64& out_program_id) override;
44 43
45 ResultStatus ReadBanner(std::vector<u8>& buffer) override; 44 ResultStatus ReadBanner(std::vector<u8>& buffer) override;
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 513af194d..4feb6968a 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -3,13 +3,18 @@
3 3
4#include <utility> 4#include <utility>
5 5
6#include "common/hex_util.h"
7#include "common/scope_exit.h"
6#include "core/core.h" 8#include "core/core.h"
7#include "core/file_sys/content_archive.h" 9#include "core/file_sys/content_archive.h"
10#include "core/file_sys/nca_metadata.h"
11#include "core/file_sys/registered_cache.h"
8#include "core/file_sys/romfs_factory.h" 12#include "core/file_sys/romfs_factory.h"
9#include "core/hle/kernel/k_process.h" 13#include "core/hle/kernel/k_process.h"
10#include "core/hle/service/filesystem/filesystem.h" 14#include "core/hle/service/filesystem/filesystem.h"
11#include "core/loader/deconstructed_rom_directory.h" 15#include "core/loader/deconstructed_rom_directory.h"
12#include "core/loader/nca.h" 16#include "core/loader/nca.h"
17#include "mbedtls/sha256.h"
13 18
14namespace Loader { 19namespace Loader {
15 20
@@ -43,9 +48,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
43 return {ResultStatus::ErrorNCANotProgram, {}}; 48 return {ResultStatus::ErrorNCANotProgram, {}};
44 } 49 }
45 50
46 const auto exefs = nca->GetExeFS(); 51 auto exefs = nca->GetExeFS();
47 if (exefs == nullptr) { 52 if (exefs == nullptr) {
48 return {ResultStatus::ErrorNoExeFS, {}}; 53 LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update");
54
55 // This NCA may be a sparse base of an installed title.
56 // Try to fetch the ExeFS from the installed update.
57 const auto& installed = system.GetContentProvider();
58 const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()),
59 FileSys::ContentRecordType::Program);
60
61 if (update_nca) {
62 exefs = update_nca->GetExeFS();
63 }
64
65 if (exefs == nullptr) {
66 return {ResultStatus::ErrorNoExeFS, {}};
67 }
49 } 68 }
50 69
51 directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); 70 directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
@@ -64,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
64 return load_result; 83 return load_result;
65} 84}
66 85
86ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
87 using namespace Common::Literals;
88
89 constexpr size_t NcaFileNameWithHashLength = 36;
90 constexpr size_t NcaFileNameHashLength = 32;
91 constexpr size_t NcaSha256HashLength = 32;
92 constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2;
93
94 // Get the file name.
95 const auto name = file->GetName();
96
97 // We won't try to verify meta NCAs.
98 if (name.ends_with(".cnmt.nca")) {
99 return ResultStatus::Success;
100 }
101
102 // Check if we can verify this file. NCAs should be named after their hashes.
103 if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) {
104 LOG_WARNING(Loader, "Unable to validate NCA with name {}", name);
105 return ResultStatus::ErrorIntegrityVerificationNotImplemented;
106 }
107
108 // Get the expected truncated hash of the NCA.
109 const auto input_hash =
110 Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false);
111
112 // Declare buffer to read into.
113 std::vector<u8> buffer(4_MiB);
114
115 // Initialize sha256 verification context.
116 mbedtls_sha256_context ctx;
117 mbedtls_sha256_init(&ctx);
118 mbedtls_sha256_starts_ret(&ctx, 0);
119
120 // Ensure we maintain a clean state on exit.
121 SCOPE_EXIT({ mbedtls_sha256_free(&ctx); });
122
123 // Declare counters.
124 const size_t total_size = file->GetSize();
125 size_t processed_size = 0;
126
127 // Begin iterating the file.
128 while (processed_size < total_size) {
129 // Refill the buffer.
130 const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size);
131 const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size);
132
133 // Update the hash function with the buffer contents.
134 mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size);
135
136 // Update counters.
137 processed_size += read_size;
138
139 // Call the progress function.
140 if (!progress_callback(processed_size, total_size)) {
141 return ResultStatus::ErrorIntegrityVerificationFailed;
142 }
143 }
144
145 // Finalize context and compute the output hash.
146 std::array<u8, NcaSha256HashLength> output_hash;
147 mbedtls_sha256_finish_ret(&ctx, output_hash.data());
148
149 // Compare to expected.
150 if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) {
151 LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name);
152 return ResultStatus::ErrorIntegrityVerificationFailed;
153 }
154
155 // File verified.
156 return ResultStatus::Success;
157}
158
67ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { 159ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
68 if (nca == nullptr) { 160 if (nca == nullptr) {
69 return ResultStatus::ErrorNotInitialized; 161 return ResultStatus::ErrorNotInitialized;
@@ -77,14 +169,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
77 return ResultStatus::Success; 169 return ResultStatus::Success;
78} 170}
79 171
80u64 AppLoader_NCA::ReadRomFSIVFCOffset() const {
81 if (nca == nullptr) {
82 return 0;
83 }
84
85 return nca->GetBaseIVFCOffset();
86}
87
88ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { 172ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
89 if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { 173 if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) {
90 return ResultStatus::ErrorNotInitialized; 174 return ResultStatus::ErrorNotInitialized;
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index d22d9146e..96779e27f 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -39,8 +39,9 @@ public:
39 39
40 LoadResult Load(Kernel::KProcess& process, Core::System& system) override; 40 LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
41 41
42 ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
43
42 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 44 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
43 u64 ReadRomFSIVFCOffset() const override;
44 ResultStatus ReadProgramId(u64& out_program_id) override; 45 ResultStatus ReadProgramId(u64& out_program_id) override;
45 46
46 ResultStatus ReadBanner(std::vector<u8>& buffer) override; 47 ResultStatus ReadBanner(std::vector<u8>& buffer) override;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 506808b5d..69f1a54ed 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -196,14 +196,15 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
196 program_image.resize(static_cast<u32>(program_image.size()) + bss_size); 196 program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
197 197
198 // Setup the process code layout 198 // Setup the process code layout
199 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) 199 if (process
200 .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
200 .IsError()) { 201 .IsError()) {
201 return false; 202 return false;
202 } 203 }
203 204
204 // Load codeset for current process 205 // Load codeset for current process
205 codeset.memory = std::move(program_image); 206 codeset.memory = std::move(program_image);
206 process.LoadModule(std::move(codeset), process.GetPageTable().GetCodeRegionStart()); 207 process.LoadModule(std::move(codeset), process.GetEntryPoint());
207 208
208 return true; 209 return true;
209} 210}
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 74cc9579f..1350da8dc 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
127 } 127 }
128 128
129 // Apply patches if necessary 129 // Apply patches if necessary
130 if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { 130 const auto name = nso_file.GetName();
131 if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
131 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); 132 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
132 std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); 133 std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
133 std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), 134 std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
134 program_image.size()); 135 program_image.size());
135 136
136 pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); 137 pi_header = pm->PatchNSO(pi_header, name);
137 138
138 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); 139 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
139 } 140 }
@@ -167,7 +168,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::S
167 modules.clear(); 168 modules.clear();
168 169
169 // Load module 170 // Load module
170 const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart()); 171 const VAddr base_address = GetInteger(process.GetEntryPoint());
171 if (!LoadModule(process, system, *file, base_address, true, true)) { 172 if (!LoadModule(process, system, *file, base_address, true, true)) {
172 return {ResultStatus::ErrorLoadingNSO, {}}; 173 return {ResultStatus::ErrorLoadingNSO, {}};
173 } 174 }
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 80663e0e0..f4ab75b77 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_,
30 } 30 }
31 31
32 if (nsp->IsExtractedType()) { 32 if (nsp->IsExtractedType()) {
33 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); 33 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(
34 nsp->GetExeFS(), false, file->GetName() == "hbl.nsp");
34 } else { 35 } else {
35 const auto control_nca = 36 const auto control_nca =
36 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); 37 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
@@ -117,12 +118,44 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
117 return result; 118 return result;
118} 119}
119 120
120ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { 121ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
121 return secondary_loader->ReadRomFS(out_file); 122 // Extracted-type NSPs can't be verified.
123 if (nsp->IsExtractedType()) {
124 return ResultStatus::ErrorIntegrityVerificationNotImplemented;
125 }
126
127 // Get list of all NCAs.
128 const auto ncas = nsp->GetNCAsCollapsed();
129
130 size_t total_size = 0;
131 size_t processed_size = 0;
132
133 // Loop over NCAs, collecting the total size to verify.
134 for (const auto& nca : ncas) {
135 total_size += nca->GetBaseFile()->GetSize();
136 }
137
138 // Loop over NCAs again, verifying each.
139 for (const auto& nca : ncas) {
140 AppLoader_NCA loader_nca(nca->GetBaseFile());
141
142 const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) {
143 return progress_callback(processed_size + nca_processed_size, total_size);
144 };
145
146 const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback);
147 if (verification_result != ResultStatus::Success) {
148 return verification_result;
149 }
150
151 processed_size += nca->GetBaseFile()->GetSize();
152 }
153
154 return ResultStatus::Success;
122} 155}
123 156
124u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { 157ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
125 return secondary_loader->ReadRomFSIVFCOffset(); 158 return secondary_loader->ReadRomFS(out_file);
126} 159}
127 160
128ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { 161ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 003cc345c..7ce436c67 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -45,8 +45,9 @@ public:
45 45
46 LoadResult Load(Kernel::KProcess& process, Core::System& system) override; 46 LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
47 47
48 ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
49
48 ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; 50 ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override;
49 u64 ReadRomFSIVFCOffset() const override;
50 ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; 51 ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
51 ResultStatus ReadProgramId(u64& out_program_id) override; 52 ResultStatus ReadProgramId(u64& out_program_id) override;
52 ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; 53 ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index c7b1b3815..12d72c380 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -85,12 +85,42 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S
85 return result; 85 return result;
86} 86}
87 87
88ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { 88ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
89 return nca_loader->ReadRomFS(out_file); 89 // Verify secure partition, as it is the only thing we can process.
90 auto secure_partition = xci->GetSecurePartitionNSP();
91
92 // Get list of all NCAs.
93 const auto ncas = secure_partition->GetNCAsCollapsed();
94
95 size_t total_size = 0;
96 size_t processed_size = 0;
97
98 // Loop over NCAs, collecting the total size to verify.
99 for (const auto& nca : ncas) {
100 total_size += nca->GetBaseFile()->GetSize();
101 }
102
103 // Loop over NCAs again, verifying each.
104 for (const auto& nca : ncas) {
105 AppLoader_NCA loader_nca(nca->GetBaseFile());
106
107 const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) {
108 return progress_callback(processed_size + nca_processed_size, total_size);
109 };
110
111 const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback);
112 if (verification_result != ResultStatus::Success) {
113 return verification_result;
114 }
115
116 processed_size += nca->GetBaseFile()->GetSize();
117 }
118
119 return ResultStatus::Success;
90} 120}
91 121
92u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { 122ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
93 return nca_loader->ReadRomFSIVFCOffset(); 123 return nca_loader->ReadRomFS(out_file);
94} 124}
95 125
96ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { 126ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 2affb6c6e..b02e136d3 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -45,8 +45,9 @@ public:
45 45
46 LoadResult Load(Kernel::KProcess& process, Core::System& system) override; 46 LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
47 47
48 ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
49
48 ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; 50 ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override;
49 u64 ReadRomFSIVFCOffset() const override;
50 ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; 51 ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
51 ResultStatus ReadProgramId(u64& out_program_id) override; 52 ResultStatus ReadProgramId(u64& out_program_id) override;
52 ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; 53 ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override;
diff --git a/src/core/memory.h b/src/core/memory.h
index 2eb61ffd3..13047a545 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -509,9 +509,9 @@ class GuestMemory {
509 509
510public: 510public:
511 GuestMemory() = delete; 511 GuestMemory() = delete;
512 explicit GuestMemory(M& memory_, u64 addr_, std::size_t size_, 512 explicit GuestMemory(M& memory, u64 addr, std::size_t size,
513 Common::ScratchBuffer<T>* backup = nullptr) 513 Common::ScratchBuffer<T>* backup = nullptr)
514 : memory{memory_}, addr{addr_}, size{size_} { 514 : m_memory{memory}, m_addr{addr}, m_size{size} {
515 static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write); 515 static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write);
516 if constexpr (FLAGS & GuestMemoryFlags::Read) { 516 if constexpr (FLAGS & GuestMemoryFlags::Read) {
517 Read(addr, size, backup); 517 Read(addr, size, backup);
@@ -521,89 +521,97 @@ public:
521 ~GuestMemory() = default; 521 ~GuestMemory() = default;
522 522
523 T* data() noexcept { 523 T* data() noexcept {
524 return data_span.data(); 524 return m_data_span.data();
525 } 525 }
526 526
527 const T* data() const noexcept { 527 const T* data() const noexcept {
528 return data_span.data(); 528 return m_data_span.data();
529 }
530
531 size_t size() const noexcept {
532 return m_size;
533 }
534
535 size_t size_bytes() const noexcept {
536 return this->size() * sizeof(T);
529 } 537 }
530 538
531 [[nodiscard]] T* begin() noexcept { 539 [[nodiscard]] T* begin() noexcept {
532 return data(); 540 return this->data();
533 } 541 }
534 542
535 [[nodiscard]] const T* begin() const noexcept { 543 [[nodiscard]] const T* begin() const noexcept {
536 return data(); 544 return this->data();
537 } 545 }
538 546
539 [[nodiscard]] T* end() noexcept { 547 [[nodiscard]] T* end() noexcept {
540 return data() + size; 548 return this->data() + this->size();
541 } 549 }
542 550
543 [[nodiscard]] const T* end() const noexcept { 551 [[nodiscard]] const T* end() const noexcept {
544 return data() + size; 552 return this->data() + this->size();
545 } 553 }
546 554
547 T& operator[](size_t index) noexcept { 555 T& operator[](size_t index) noexcept {
548 return data_span[index]; 556 return m_data_span[index];
549 } 557 }
550 558
551 const T& operator[](size_t index) const noexcept { 559 const T& operator[](size_t index) const noexcept {
552 return data_span[index]; 560 return m_data_span[index];
553 } 561 }
554 562
555 void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept { 563 void SetAddressAndSize(u64 addr, std::size_t size) noexcept {
556 addr = addr_; 564 m_addr = addr;
557 size = size_; 565 m_size = size;
558 addr_changed = true; 566 m_addr_changed = true;
559 } 567 }
560 568
561 std::span<T> Read(u64 addr_, std::size_t size_, 569 std::span<T> Read(u64 addr, std::size_t size,
562 Common::ScratchBuffer<T>* backup = nullptr) noexcept { 570 Common::ScratchBuffer<T>* backup = nullptr) noexcept {
563 addr = addr_; 571 m_addr = addr;
564 size = size_; 572 m_size = size;
565 if (size == 0) { 573 if (m_size == 0) {
566 is_data_copy = true; 574 m_is_data_copy = true;
567 return {}; 575 return {};
568 } 576 }
569 577
570 if (TrySetSpan()) { 578 if (this->TrySetSpan()) {
571 if constexpr (FLAGS & GuestMemoryFlags::Safe) { 579 if constexpr (FLAGS & GuestMemoryFlags::Safe) {
572 memory.FlushRegion(addr, size * sizeof(T)); 580 m_memory.FlushRegion(m_addr, this->size_bytes());
573 } 581 }
574 } else { 582 } else {
575 if (backup) { 583 if (backup) {
576 backup->resize_destructive(size); 584 backup->resize_destructive(this->size());
577 data_span = *backup; 585 m_data_span = *backup;
578 } else { 586 } else {
579 data_copy.resize(size); 587 m_data_copy.resize(this->size());
580 data_span = std::span(data_copy); 588 m_data_span = std::span(m_data_copy);
581 } 589 }
582 is_data_copy = true; 590 m_is_data_copy = true;
583 span_valid = true; 591 m_span_valid = true;
584 if constexpr (FLAGS & GuestMemoryFlags::Safe) { 592 if constexpr (FLAGS & GuestMemoryFlags::Safe) {
585 memory.ReadBlock(addr, data_span.data(), size * sizeof(T)); 593 m_memory.ReadBlock(m_addr, this->data(), this->size_bytes());
586 } else { 594 } else {
587 memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T)); 595 m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes());
588 } 596 }
589 } 597 }
590 return data_span; 598 return m_data_span;
591 } 599 }
592 600
593 void Write(std::span<T> write_data) noexcept { 601 void Write(std::span<T> write_data) noexcept {
594 if constexpr (FLAGS & GuestMemoryFlags::Cached) { 602 if constexpr (FLAGS & GuestMemoryFlags::Cached) {
595 memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T)); 603 m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes());
596 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { 604 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
597 memory.WriteBlock(addr, write_data.data(), size * sizeof(T)); 605 m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes());
598 } else { 606 } else {
599 memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T)); 607 m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes());
600 } 608 }
601 } 609 }
602 610
603 bool TrySetSpan() noexcept { 611 bool TrySetSpan() noexcept {
604 if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) { 612 if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) {
605 data_span = {reinterpret_cast<T*>(ptr), size}; 613 m_data_span = {reinterpret_cast<T*>(ptr), this->size()};
606 span_valid = true; 614 m_span_valid = true;
607 return true; 615 return true;
608 } 616 }
609 return false; 617 return false;
@@ -611,36 +619,36 @@ public:
611 619
612protected: 620protected:
613 bool IsDataCopy() const noexcept { 621 bool IsDataCopy() const noexcept {
614 return is_data_copy; 622 return m_is_data_copy;
615 } 623 }
616 624
617 bool AddressChanged() const noexcept { 625 bool AddressChanged() const noexcept {
618 return addr_changed; 626 return m_addr_changed;
619 } 627 }
620 628
621 M& memory; 629 M& m_memory;
622 u64 addr; 630 u64 m_addr{};
623 size_t size; 631 size_t m_size{};
624 std::span<T> data_span{}; 632 std::span<T> m_data_span{};
625 std::vector<T> data_copy; 633 std::vector<T> m_data_copy{};
626 bool span_valid{false}; 634 bool m_span_valid{false};
627 bool is_data_copy{false}; 635 bool m_is_data_copy{false};
628 bool addr_changed{false}; 636 bool m_addr_changed{false};
629}; 637};
630 638
631template <typename M, typename T, GuestMemoryFlags FLAGS> 639template <typename M, typename T, GuestMemoryFlags FLAGS>
632class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { 640class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> {
633public: 641public:
634 GuestMemoryScoped() = delete; 642 GuestMemoryScoped() = delete;
635 explicit GuestMemoryScoped(M& memory_, u64 addr_, std::size_t size_, 643 explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size,
636 Common::ScratchBuffer<T>* backup = nullptr) 644 Common::ScratchBuffer<T>* backup = nullptr)
637 : GuestMemory<M, T, FLAGS>(memory_, addr_, size_, backup) { 645 : GuestMemory<M, T, FLAGS>(memory, addr, size, backup) {
638 if constexpr (!(FLAGS & GuestMemoryFlags::Read)) { 646 if constexpr (!(FLAGS & GuestMemoryFlags::Read)) {
639 if (!this->TrySetSpan()) { 647 if (!this->TrySetSpan()) {
640 if (backup) { 648 if (backup) {
641 this->data_span = *backup; 649 this->m_data_span = *backup;
642 this->span_valid = true; 650 this->m_span_valid = true;
643 this->is_data_copy = true; 651 this->m_is_data_copy = true;
644 } 652 }
645 } 653 }
646 } 654 }
@@ -648,24 +656,21 @@ public:
648 656
649 ~GuestMemoryScoped() { 657 ~GuestMemoryScoped() {
650 if constexpr (FLAGS & GuestMemoryFlags::Write) { 658 if constexpr (FLAGS & GuestMemoryFlags::Write) {
651 if (this->size == 0) [[unlikely]] { 659 if (this->size() == 0) [[unlikely]] {
652 return; 660 return;
653 } 661 }
654 662
655 if (this->AddressChanged() || this->IsDataCopy()) { 663 if (this->AddressChanged() || this->IsDataCopy()) {
656 ASSERT(this->span_valid); 664 ASSERT(this->m_span_valid);
657 if constexpr (FLAGS & GuestMemoryFlags::Cached) { 665 if constexpr (FLAGS & GuestMemoryFlags::Cached) {
658 this->memory.WriteBlockCached(this->addr, this->data_span.data(), 666 this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes());
659 this->size * sizeof(T));
660 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { 667 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
661 this->memory.WriteBlock(this->addr, this->data_span.data(), 668 this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes());
662 this->size * sizeof(T));
663 } else { 669 } else {
664 this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(), 670 this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes());
665 this->size * sizeof(T));
666 } 671 }
667 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { 672 } else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
668 this->memory.InvalidateRegion(this->addr, this->size * sizeof(T)); 673 this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes());
669 } 674 }
670 } 675 }
671 } 676 }
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 7b52f61a7..a06e99166 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
154 return {}; 154 return {};
155 } 155 }
156 156
157 const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); 157 const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = 158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
159 value; 159 value;
160 160
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index b5b3e7eda..ed875d444 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -117,8 +117,8 @@ json GetProcessorStateDataAuto(Core::System& system) {
117 arm.SaveContext(context); 117 arm.SaveContext(context);
118 118
119 return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", 119 return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
120 GetInteger(process->GetPageTable().GetCodeRegionStart()), 120 GetInteger(process->GetEntryPoint()), context.sp, context.pc,
121 context.sp, context.pc, context.pstate, context.cpu_registers); 121 context.pstate, context.cpu_registers);
122} 122}
123 123
124json GetBacktraceData(Core::System& system) { 124json GetBacktraceData(Core::System& system) {
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 62b3f6636..c26179e03 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -14,6 +14,7 @@
14#include "common/logging/log.h" 14#include "common/logging/log.h"
15 15
16#include "common/settings.h" 16#include "common/settings.h"
17#include "common/settings_enums.h"
17#include "core/file_sys/control_metadata.h" 18#include "core/file_sys/control_metadata.h"
18#include "core/file_sys/patch_manager.h" 19#include "core/file_sys/patch_manager.h"
19#include "core/loader/loader.h" 20#include "core/loader/loader.h"
@@ -275,7 +276,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
275 static_cast<u32>(Settings::values.shader_backend.GetValue())); 276 static_cast<u32>(Settings::values.shader_backend.GetValue()));
276 AddField(field_type, "Renderer_UseAsynchronousShaders", 277 AddField(field_type, "Renderer_UseAsynchronousShaders",
277 Settings::values.use_asynchronous_shaders.GetValue()); 278 Settings::values.use_asynchronous_shaders.GetValue());
278 AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue()); 279 AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode());
279} 280}
280 281
281bool TelemetrySession::SubmitTestcase() { 282bool TelemetrySession::SubmitTestcase() {
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp
new file mode 100644
index 000000000..44d24822a
--- /dev/null
+++ b/src/core/tools/renderdoc.cpp
@@ -0,0 +1,55 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <renderdoc_app.h>
5
6#include "common/assert.h"
7#include "common/dynamic_library.h"
8#include "core/tools/renderdoc.h"
9
10#ifdef WIN32
11#include <windows.h>
12#else
13#include <dlfcn.h>
14#endif
15
16namespace Tools {
17
18RenderdocAPI::RenderdocAPI() {
19#ifdef WIN32
20 if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
21 const auto RENDERDOC_GetAPI =
22 reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI"));
23 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
24 ASSERT(ret == 1);
25 }
26#else
27#ifdef ANDROID
28 static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
29#else
30 static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
31#endif
32 if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
33 const auto RENDERDOC_GetAPI =
34 reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
35 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
36 ASSERT(ret == 1);
37 }
38#endif
39}
40
41RenderdocAPI::~RenderdocAPI() = default;
42
43void RenderdocAPI::ToggleCapture() {
44 if (!rdoc_api) [[unlikely]] {
45 return;
46 }
47 if (!is_capturing) {
48 rdoc_api->StartFrameCapture(NULL, NULL);
49 } else {
50 rdoc_api->EndFrameCapture(NULL, NULL);
51 }
52 is_capturing = !is_capturing;
53}
54
55} // namespace Tools
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h
new file mode 100644
index 000000000..0e5e43da5
--- /dev/null
+++ b/src/core/tools/renderdoc.h
@@ -0,0 +1,22 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6struct RENDERDOC_API_1_6_0;
7
8namespace Tools {
9
10class RenderdocAPI {
11public:
12 explicit RenderdocAPI();
13 ~RenderdocAPI();
14
15 void ToggleCapture();
16
17private:
18 RENDERDOC_API_1_6_0* rdoc_api{};
19 bool is_capturing{false};
20};
21
22} // namespace Tools
diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp
index d707dabe2..93038f161 100644
--- a/src/dedicated_room/yuzu_room.cpp
+++ b/src/dedicated_room/yuzu_room.cpp
@@ -368,9 +368,9 @@ int main(int argc, char** argv) {
368 if (auto room = network.GetRoom().lock()) { 368 if (auto room = network.GetRoom().lock()) {
369 AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, 369 AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
370 .id = preferred_game_id}; 370 .id = preferred_game_id};
371 if (!room->Create(room_name, room_description, bind_address, port, password, max_members, 371 if (!room->Create(room_name, room_description, bind_address, static_cast<u16>(port),
372 username, preferred_game_info, std::move(verify_backend), ban_list, 372 password, max_members, username, preferred_game_info,
373 enable_yuzu_mods)) { 373 std::move(verify_backend), ban_list, enable_yuzu_mods)) {
374 LOG_INFO(Network, "Failed to create room: "); 374 LOG_INFO(Network, "Failed to create room: ");
375 return -1; 375 return -1;
376 } 376 }
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 322c29065..5c127c8ef 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -37,8 +37,6 @@ add_library(input_common STATIC
37 37
38if (MSVC) 38if (MSVC)
39 target_compile_options(input_common PRIVATE 39 target_compile_options(input_common PRIVATE
40 /W4
41
42 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data 40 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
43 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 41 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
44 /we4800 # Implicit conversion from 'type' to bool. Possible information loss 42 /we4800 # Implicit conversion from 'type' to bool. Possible information loss
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 870e76ab0..188e862d7 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -835,15 +835,15 @@ public:
835 return input_engine->SupportsNfc(identifier); 835 return input_engine->SupportsNfc(identifier);
836 } 836 }
837 837
838 Common::Input::NfcState StartNfcPolling() { 838 Common::Input::NfcState StartNfcPolling() override {
839 return input_engine->StartNfcPolling(identifier); 839 return input_engine->StartNfcPolling(identifier);
840 } 840 }
841 841
842 Common::Input::NfcState StopNfcPolling() { 842 Common::Input::NfcState StopNfcPolling() override {
843 return input_engine->StopNfcPolling(identifier); 843 return input_engine->StopNfcPolling(identifier);
844 } 844 }
845 845
846 Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) { 846 Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) override {
847 return input_engine->ReadAmiiboData(identifier, out_data); 847 return input_engine->ReadAmiiboData(identifier, out_data);
848 } 848 }
849 849
@@ -852,11 +852,11 @@ public:
852 } 852 }
853 853
854 Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request, 854 Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request,
855 Common::Input::MifareRequest& out_data) { 855 Common::Input::MifareRequest& out_data) override {
856 return input_engine->ReadMifareData(identifier, request, out_data); 856 return input_engine->ReadMifareData(identifier, request, out_data);
857 } 857 }
858 858
859 Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) { 859 Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) override {
860 return input_engine->WriteMifareData(identifier, request); 860 return input_engine->WriteMifareData(identifier, request);
861 } 861 }
862 862
diff --git a/src/network/room.cpp b/src/network/room.cpp
index e456ea09c..d87db37de 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -805,7 +805,7 @@ IPv4Address Room::RoomImpl::GenerateFakeIPAddress() {
805 std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE 805 std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE
806 do { 806 do {
807 for (std::size_t i = 2; i < result_ip.size(); ++i) { 807 for (std::size_t i = 2; i < result_ip.size(); ++i) {
808 result_ip[i] = dis(random_gen); 808 result_ip[i] = static_cast<u8>(dis(random_gen));
809 } 809 }
810 } while (!IsValidFakeIPAddress(result_ip)); 810 } while (!IsValidFakeIPAddress(result_ip));
811 811
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt
index 07e75f9d8..83b763447 100644
--- a/src/shader_recompiler/CMakeLists.txt
+++ b/src/shader_recompiler/CMakeLists.txt
@@ -245,8 +245,6 @@ target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit)
245 245
246if (MSVC) 246if (MSVC)
247 target_compile_options(shader_recompiler PRIVATE 247 target_compile_options(shader_recompiler PRIVATE
248 /W4
249
250 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data 248 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
251 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 249 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
252 /we4800 # Implicit conversion from 'type' to bool. Possible information loss 250 /we4800 # Implicit conversion from 'type' to bool. Possible information loss
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
index 85ee27333..d0e308124 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
@@ -558,12 +558,15 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
558 const IR::Value& coord, const IR::Value& derivatives, 558 const IR::Value& coord, const IR::Value& derivatives,
559 const IR::Value& offset, const IR::Value& lod_clamp) { 559 const IR::Value& offset, const IR::Value& lod_clamp) {
560 const auto info{inst.Flags<IR::TextureInstInfo>()}; 560 const auto info{inst.Flags<IR::TextureInstInfo>()};
561 ScopedRegister dpdx, dpdy; 561 ScopedRegister dpdx, dpdy, coords;
562 const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; 562 const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp};
563 if (multi_component) { 563 if (multi_component) {
564 // Allocate this early to avoid aliasing other registers 564 // Allocate this early to avoid aliasing other registers
565 dpdx = ScopedRegister{ctx.reg_alloc}; 565 dpdx = ScopedRegister{ctx.reg_alloc};
566 dpdy = ScopedRegister{ctx.reg_alloc}; 566 dpdy = ScopedRegister{ctx.reg_alloc};
567 if (info.num_derivates >= 3) {
568 coords = ScopedRegister{ctx.reg_alloc};
569 }
567 } 570 }
568 const auto sparse_inst{PrepareSparse(inst)}; 571 const auto sparse_inst{PrepareSparse(inst)};
569 const std::string_view sparse_mod{sparse_inst ? ".SPARSE" : ""}; 572 const std::string_view sparse_mod{sparse_inst ? ".SPARSE" : ""};
@@ -580,15 +583,27 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
580 "MOV.F {}.y,{}.w;", 583 "MOV.F {}.y,{}.w;",
581 dpdx.reg, derivatives_vec, dpdx.reg, derivatives_vec, dpdy.reg, derivatives_vec, 584 dpdx.reg, derivatives_vec, dpdx.reg, derivatives_vec, dpdy.reg, derivatives_vec,
582 dpdy.reg, derivatives_vec); 585 dpdy.reg, derivatives_vec);
586 Register final_coord;
587 if (info.num_derivates >= 3) {
588 ctx.Add("MOV.F {}.z,{}.x;"
589 "MOV.F {}.z,{}.y;",
590 dpdx.reg, coord_vec, dpdy.reg, coord_vec);
591 ctx.Add("MOV.F {}.x,0;"
592 "MOV.F {}.y,0;",
593 "MOV.F {}.z,0;", coords.reg, coords.reg, coords.reg);
594 final_coord = coords.reg;
595 } else {
596 final_coord = coord_vec;
597 }
583 if (info.has_lod_clamp) { 598 if (info.has_lod_clamp) {
584 const ScalarF32 lod_clamp_value{ctx.reg_alloc.Consume(lod_clamp)}; 599 const ScalarF32 lod_clamp_value{ctx.reg_alloc.Consume(lod_clamp)};
585 ctx.Add("MOV.F {}.w,{};" 600 ctx.Add("MOV.F {}.w,{};"
586 "TXD.F.LODCLAMP{} {},{},{},{},{},{}{};", 601 "TXD.F.LODCLAMP{} {},{},{},{},{},{}{};",
587 dpdy.reg, lod_clamp_value, sparse_mod, ret, coord_vec, dpdx.reg, dpdy.reg, 602 dpdy.reg, lod_clamp_value, sparse_mod, ret, final_coord, dpdx.reg, dpdy.reg,
588 texture, type, offset_vec); 603 texture, type, offset_vec);
589 } else { 604 } else {
590 ctx.Add("TXD.F{} {},{},{},{},{},{}{};", sparse_mod, ret, coord_vec, dpdx.reg, dpdy.reg, 605 ctx.Add("TXD.F{} {},{},{},{},{},{}{};", sparse_mod, ret, final_coord, dpdx.reg,
591 texture, type, offset_vec); 606 dpdy.reg, texture, type, offset_vec);
592 } 607 }
593 } else { 608 } else {
594 ctx.Add("TXD.F{} {},{},{}.x,{}.y,{},{}{};", sparse_mod, ret, coord_vec, derivatives_vec, 609 ctx.Add("TXD.F{} {},{},{}.x,{}.y,{},{}{};", sparse_mod, ret, coord_vec, derivatives_vec,
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
index 418505475..d9872ecc2 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
@@ -548,7 +548,7 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
548 if (sparse_inst) { 548 if (sparse_inst) {
549 throw NotImplementedException("EmitImageGradient Sparse"); 549 throw NotImplementedException("EmitImageGradient Sparse");
550 } 550 }
551 if (!offset.IsEmpty()) { 551 if (!offset.IsEmpty() && info.num_derivates <= 2) {
552 throw NotImplementedException("EmitImageGradient offset"); 552 throw NotImplementedException("EmitImageGradient offset");
553 } 553 }
554 const auto texture{Texture(ctx, info, index)}; 554 const auto texture{Texture(ctx, info, index)};
@@ -556,6 +556,12 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
556 const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp}; 556 const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp};
557 const auto derivatives_vec{ctx.var_alloc.Consume(derivatives)}; 557 const auto derivatives_vec{ctx.var_alloc.Consume(derivatives)};
558 if (multi_component) { 558 if (multi_component) {
559 if (info.num_derivates >= 3) {
560 const auto offset_vec{ctx.var_alloc.Consume(offset)};
561 ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yw, {}.y));", texel, texture,
562 coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec);
563 return;
564 }
559 ctx.Add("{}=textureGrad({},{},vec2({}.xz),vec2({}.yz));", texel, texture, coords, 565 ctx.Add("{}=textureGrad({},{},vec2({}.xz),vec2({}.yz));", texel, texture, coords,
560 derivatives_vec, derivatives_vec); 566 derivatives_vec, derivatives_vec);
561 } else { 567 } else {
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index 7d901c04b..8decdf399 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -91,6 +91,34 @@ public:
91 } 91 }
92 } 92 }
93 93
94 explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivates_1, Id derivates_2,
95 Id offset, Id lod_clamp) {
96 if (!Sirit::ValidId(derivates_1) || !Sirit::ValidId(derivates_2)) {
97 throw LogicError("Derivates must be present");
98 }
99 boost::container::static_vector<Id, 3> deriv_1_accum{
100 ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 0),
101 ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 2),
102 ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 0),
103 };
104 boost::container::static_vector<Id, 3> deriv_2_accum{
105 ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 1),
106 ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 3),
107 ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 1),
108 };
109 const Id derivates_id1{ctx.OpCompositeConstruct(
110 ctx.F32[3], std::span{deriv_1_accum.data(), deriv_1_accum.size()})};
111 const Id derivates_id2{ctx.OpCompositeConstruct(
112 ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})};
113 Add(spv::ImageOperandsMask::Grad, derivates_id1, derivates_id2);
114 if (Sirit::ValidId(offset)) {
115 Add(spv::ImageOperandsMask::Offset, offset);
116 }
117 if (has_lod_clamp) {
118 Add(spv::ImageOperandsMask::MinLod, lod_clamp);
119 }
120 }
121
94 std::span<const Id> Span() const noexcept { 122 std::span<const Id> Span() const noexcept {
95 return std::span{operands.data(), operands.size()}; 123 return std::span{operands.data(), operands.size()};
96 } 124 }
@@ -176,9 +204,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
176 if (def.count > 1) { 204 if (def.count > 1) {
177 throw NotImplementedException("Indirect texture sample"); 205 throw NotImplementedException("Indirect texture sample");
178 } 206 }
179 const Id sampler_id{def.id}; 207 return ctx.OpLoad(ctx.image_buffer_type, def.id);
180 const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)};
181 return ctx.OpImage(ctx.image_buffer_type, id);
182 } else { 208 } else {
183 const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; 209 const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
184 if (def.count > 1) { 210 if (def.count > 1) {
@@ -524,8 +550,11 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
524Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, 550Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
525 Id derivates, Id offset, Id lod_clamp) { 551 Id derivates, Id offset, Id lod_clamp) {
526 const auto info{inst->Flags<IR::TextureInstInfo>()}; 552 const auto info{inst->Flags<IR::TextureInstInfo>()};
527 const ImageOperands operands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, 553 const auto operands =
528 offset, lod_clamp); 554 info.num_derivates == 3
555 ? ImageOperands(ctx, info.has_lod_clamp != 0, derivates, offset, {}, lod_clamp)
556 : ImageOperands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, offset,
557 lod_clamp);
529 return Emit(&EmitContext::OpImageSparseSampleExplicitLod, 558 return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
530 &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], 559 &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
531 Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); 560 Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index bec5db173..72f69b7aa 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -74,6 +74,11 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
74 throw InvalidArgument("Invalid image format {}", format); 74 throw InvalidArgument("Invalid image format {}", format);
75} 75}
76 76
77spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) {
78 const auto spv_format = GetImageFormat(format);
79 return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format;
80}
81
77Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { 82Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) {
78 const spv::ImageFormat format{GetImageFormat(desc.format)}; 83 const spv::ImageFormat format{GetImageFormat(desc.format)};
79 const Id type{ctx.U32[1]}; 84 const Id type{ctx.U32[1]};
@@ -1242,9 +1247,8 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
1242 } 1247 }
1243 const spv::ImageFormat format{spv::ImageFormat::Unknown}; 1248 const spv::ImageFormat format{spv::ImageFormat::Unknown};
1244 image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format); 1249 image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
1245 sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
1246 1250
1247 const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)}; 1251 const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
1248 texture_buffers.reserve(info.texture_buffer_descriptors.size()); 1252 texture_buffers.reserve(info.texture_buffer_descriptors.size());
1249 for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) { 1253 for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
1250 if (desc.count != 1) { 1254 if (desc.count != 1) {
@@ -1271,7 +1275,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
1271 if (desc.count != 1) { 1275 if (desc.count != 1) {
1272 throw NotImplementedException("Array of image buffers"); 1276 throw NotImplementedException("Array of image buffers");
1273 } 1277 }
1274 const spv::ImageFormat format{GetImageFormat(desc.format)}; 1278 const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)};
1275 const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; 1279 const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)};
1276 const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; 1280 const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
1277 const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; 1281 const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index e63330f11..7c49fd504 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -206,7 +206,6 @@ public:
206 Id output_u32{}; 206 Id output_u32{};
207 207
208 Id image_buffer_type{}; 208 Id image_buffer_type{};
209 Id sampled_texture_buffer_type{};
210 Id image_u32{}; 209 Id image_u32{};
211 210
212 std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{}; 211 std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};
diff --git a/src/shader_recompiler/frontend/ir/modifiers.h b/src/shader_recompiler/frontend/ir/modifiers.h
index 69035d462..1e9e8c8f5 100644
--- a/src/shader_recompiler/frontend/ir/modifiers.h
+++ b/src/shader_recompiler/frontend/ir/modifiers.h
@@ -42,6 +42,7 @@ union TextureInstInfo {
42 BitField<23, 2, u32> gather_component; 42 BitField<23, 2, u32> gather_component;
43 BitField<25, 2, u32> num_derivates; 43 BitField<25, 2, u32> num_derivates;
44 BitField<27, 3, ImageFormat> image_format; 44 BitField<27, 3, ImageFormat> image_format;
45 BitField<30, 1, u32> ndv_is_active;
45}; 46};
46static_assert(sizeof(TextureInstInfo) <= sizeof(u32)); 47static_assert(sizeof(TextureInstInfo) <= sizeof(u32));
47 48
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp
index ef4ffa54b..f00e20023 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_swizzled_add.cpp
@@ -19,7 +19,7 @@ void TranslatorVisitor::FSWZADD(u64 insn) {
19 } const fswzadd{insn}; 19 } const fswzadd{insn};
20 20
21 if (fswzadd.ndv != 0) { 21 if (fswzadd.ndv != 0) {
22 throw NotImplementedException("FSWZADD NDV"); 22 LOG_WARNING(Shader, "(STUBBED) FSWZADD - NDV mode");
23 } 23 }
24 24
25 const IR::F32 src_a{GetFloatReg8(insn)}; 25 const IR::F32 src_a{GetFloatReg8(insn)};
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp
index 82aec3b73..1ddfeab06 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_register.cpp
@@ -16,8 +16,10 @@ void MOV(TranslatorVisitor& v, u64 insn, const IR::U32& src, bool is_mov32i = fa
16 BitField<12, 4, u64> mov32i_mask; 16 BitField<12, 4, u64> mov32i_mask;
17 } const mov{insn}; 17 } const mov{insn};
18 18
19 if ((is_mov32i ? mov.mov32i_mask : mov.mask) != 0xf) { 19 u64 mask = is_mov32i ? mov.mov32i_mask : mov.mask;
20 throw NotImplementedException("Non-full move mask"); 20 if (mask != 0xf && mask != 0x1) {
21 LOG_WARNING(Shader, "(STUBBED) Masked Mov");
22 return;
21 } 23 }
22 v.X(mov.dest_reg, src); 24 v.X(mov.dest_reg, src);
23} 25}
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
index 753c62098..e593132e6 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
@@ -161,7 +161,8 @@ enum class SpecialRegister : u64 {
161 LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY"); 161 LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY");
162 return ir.Imm32(0); // This is the default value hardware returns. 162 return ir.Imm32(0); // This is the default value hardware returns.
163 default: 163 default:
164 throw NotImplementedException("S2R special register {}", special_register); 164 LOG_CRITICAL(Shader, "(STUBBED) Special register {}", special_register);
165 return ir.Imm32(0); // This is the default value hardware returns.
165 } 166 }
166} 167}
167} // Anonymous namespace 168} // Anonymous namespace
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
index 2f930f1ea..6203003b3 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
@@ -209,7 +209,7 @@ void TranslatorVisitor::R2B(u64) {
209} 209}
210 210
211void TranslatorVisitor::RAM(u64) { 211void TranslatorVisitor::RAM(u64) {
212 ThrowNotImplemented(Opcode::RAM); 212 LOG_WARNING(Shader, "(STUBBED) RAM Instruction");
213} 213}
214 214
215void TranslatorVisitor::RET(u64) { 215void TranslatorVisitor::RET(u64) {
@@ -221,7 +221,7 @@ void TranslatorVisitor::RTT(u64) {
221} 221}
222 222
223void TranslatorVisitor::SAM(u64) { 223void TranslatorVisitor::SAM(u64) {
224 ThrowNotImplemented(Opcode::SAM); 224 LOG_WARNING(Shader, "(STUBBED) SAM Instruction");
225} 225}
226 226
227void TranslatorVisitor::SETCRSPTR(u64) { 227void TranslatorVisitor::SETCRSPTR(u64) {
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp
index 2459fc30d..7a9b7fff8 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_fetch.cpp
@@ -172,6 +172,7 @@ void Impl(TranslatorVisitor& v, u64 insn, bool aoffi, Blod blod, bool lc,
172 info.is_depth.Assign(tex.dc != 0 ? 1 : 0); 172 info.is_depth.Assign(tex.dc != 0 ? 1 : 0);
173 info.has_bias.Assign(blod == Blod::LB || blod == Blod::LBA ? 1 : 0); 173 info.has_bias.Assign(blod == Blod::LB || blod == Blod::LBA ? 1 : 0);
174 info.has_lod_clamp.Assign(lc ? 1 : 0); 174 info.has_lod_clamp.Assign(lc ? 1 : 0);
175 info.ndv_is_active.Assign(tex.ndv != 0 ? 1 : 0);
175 176
176 const IR::Value sample{[&]() -> IR::Value { 177 const IR::Value sample{[&]() -> IR::Value {
177 if (tex.dc == 0) { 178 if (tex.dc == 0) {
diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
index 4d81e9336..f46e55122 100644
--- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
+++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
@@ -10,6 +10,7 @@
10#include "shader_recompiler/environment.h" 10#include "shader_recompiler/environment.h"
11#include "shader_recompiler/exception.h" 11#include "shader_recompiler/exception.h"
12#include "shader_recompiler/frontend/ir/ir_emitter.h" 12#include "shader_recompiler/frontend/ir/ir_emitter.h"
13#include "shader_recompiler/frontend/ir/modifiers.h"
13#include "shader_recompiler/frontend/ir/value.h" 14#include "shader_recompiler/frontend/ir/value.h"
14#include "shader_recompiler/ir_opt/passes.h" 15#include "shader_recompiler/ir_opt/passes.h"
15 16
@@ -410,7 +411,49 @@ void FoldSelect(IR::Inst& inst) {
410 } 411 }
411} 412}
412 413
414void FoldFPAdd32(IR::Inst& inst) {
415 if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a + b; })) {
416 return;
417 }
418 const IR::Value lhs_value{inst.Arg(0)};
419 const IR::Value rhs_value{inst.Arg(1)};
420 const auto check_neutral = [](const IR::Value& one_operand) {
421 return one_operand.IsImmediate() && std::abs(one_operand.F32()) == 0.0f;
422 };
423 if (check_neutral(lhs_value)) {
424 inst.ReplaceUsesWith(rhs_value);
425 }
426 if (check_neutral(rhs_value)) {
427 inst.ReplaceUsesWith(lhs_value);
428 }
429}
430
431bool FoldDerivateYFromCorrection(IR::Inst& inst) {
432 const IR::Value lhs_value{inst.Arg(0)};
433 const IR::Value rhs_value{inst.Arg(1)};
434 IR::Inst* const lhs_op{lhs_value.InstRecursive()};
435 IR::Inst* const rhs_op{rhs_value.InstRecursive()};
436 if (lhs_op->GetOpcode() == IR::Opcode::YDirection) {
437 if (rhs_op->GetOpcode() != IR::Opcode::DPdyFine) {
438 return false;
439 }
440 inst.ReplaceUsesWith(rhs_value);
441 return true;
442 }
443 if (rhs_op->GetOpcode() != IR::Opcode::YDirection) {
444 return false;
445 }
446 if (lhs_op->GetOpcode() != IR::Opcode::DPdyFine) {
447 return false;
448 }
449 inst.ReplaceUsesWith(lhs_value);
450 return true;
451}
452
413void FoldFPMul32(IR::Inst& inst) { 453void FoldFPMul32(IR::Inst& inst) {
454 if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a * b; })) {
455 return;
456 }
414 const auto control{inst.Flags<IR::FpControl>()}; 457 const auto control{inst.Flags<IR::FpControl>()};
415 if (control.no_contraction) { 458 if (control.no_contraction) {
416 return; 459 return;
@@ -421,6 +464,9 @@ void FoldFPMul32(IR::Inst& inst) {
421 if (lhs_value.IsImmediate() || rhs_value.IsImmediate()) { 464 if (lhs_value.IsImmediate() || rhs_value.IsImmediate()) {
422 return; 465 return;
423 } 466 }
467 if (FoldDerivateYFromCorrection(inst)) {
468 return;
469 }
424 IR::Inst* const lhs_op{lhs_value.InstRecursive()}; 470 IR::Inst* const lhs_op{lhs_value.InstRecursive()};
425 IR::Inst* const rhs_op{rhs_value.InstRecursive()}; 471 IR::Inst* const rhs_op{rhs_value.InstRecursive()};
426 if (lhs_op->GetOpcode() != IR::Opcode::FPMul32 || 472 if (lhs_op->GetOpcode() != IR::Opcode::FPMul32 ||
@@ -622,7 +668,12 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) {
622 } 668 }
623 const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)}; 669 const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)};
624 if (value_2 != value_3) { 670 if (value_2 != value_3) {
625 return; 671 if (!value_2.IsImmediate() || !value_3.IsImmediate()) {
672 return;
673 }
674 if (Common::BitCast<u32>(value_2.F32()) != value_3.U32()) {
675 return;
676 }
626 } 677 }
627 const IR::Value index{inst2->Arg(1)}; 678 const IR::Value index{inst2->Arg(1)};
628 const IR::Value clamp{inst2->Arg(2)}; 679 const IR::Value clamp{inst2->Arg(2)};
@@ -648,6 +699,169 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) {
648 } 699 }
649} 700}
650 701
702bool FindGradient3DDerivates(std::array<IR::Value, 3>& results, IR::Value coord) {
703 if (coord.IsImmediate()) {
704 return false;
705 }
706 const auto check_through_shuffle = [](IR::Value input, IR::Value& result) {
707 const IR::Value value_1{GetThroughCast(input.Resolve(), IR::Opcode::BitCastF32U32)};
708 IR::Inst* const inst2{value_1.InstRecursive()};
709 if (inst2->GetOpcode() != IR::Opcode::ShuffleIndex) {
710 return false;
711 }
712 const IR::Value index{inst2->Arg(1).Resolve()};
713 const IR::Value clamp{inst2->Arg(2).Resolve()};
714 const IR::Value segmentation_mask{inst2->Arg(3).Resolve()};
715 if (!index.IsImmediate() || !clamp.IsImmediate() || !segmentation_mask.IsImmediate()) {
716 return false;
717 }
718 if (index.U32() != 3 && clamp.U32() != 3) {
719 return false;
720 }
721 result = GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32);
722 return true;
723 };
724 IR::Inst* const inst = coord.InstRecursive();
725 if (inst->GetOpcode() != IR::Opcode::FSwizzleAdd) {
726 return false;
727 }
728 std::array<IR::Value, 3> temporary_values;
729 IR::Value value_1 = inst->Arg(0).Resolve();
730 IR::Value value_2 = inst->Arg(1).Resolve();
731 IR::Value value_3 = inst->Arg(2).Resolve();
732 std::array<u32, 4> swizzles_mask_a{};
733 std::array<u32, 4> swizzles_mask_b{};
734 const auto resolve_mask = [](std::array<u32, 4>& mask_results, IR::Value mask) {
735 u32 value = mask.U32();
736 for (size_t i = 0; i < 4; i++) {
737 mask_results[i] = (value >> (i * 2)) & 0x3;
738 }
739 };
740 resolve_mask(swizzles_mask_a, value_3);
741 size_t coordinate_index = 0;
742 const auto resolve_pending = [&](IR::Value resolve_v) {
743 IR::Inst* const inst_r = resolve_v.InstRecursive();
744 if (inst_r->GetOpcode() != IR::Opcode::FSwizzleAdd) {
745 return false;
746 }
747 if (!check_through_shuffle(inst_r->Arg(0).Resolve(), temporary_values[1])) {
748 return false;
749 }
750 if (!check_through_shuffle(inst_r->Arg(1).Resolve(), temporary_values[2])) {
751 return false;
752 }
753 resolve_mask(swizzles_mask_b, inst_r->Arg(2).Resolve());
754 return true;
755 };
756 if (value_1.IsImmediate() || value_2.IsImmediate()) {
757 return false;
758 }
759 bool should_continue = false;
760 if (resolve_pending(value_1)) {
761 should_continue = check_through_shuffle(value_2, temporary_values[0]);
762 coordinate_index = 0;
763 }
764 if (resolve_pending(value_2)) {
765 should_continue = check_through_shuffle(value_1, temporary_values[0]);
766 coordinate_index = 2;
767 }
768 if (!should_continue) {
769 return false;
770 }
771 // figure which is which
772 size_t zero_mask_a = 0;
773 size_t zero_mask_b = 0;
774 for (size_t i = 0; i < 4; i++) {
775 if (swizzles_mask_a[i] == 2 || swizzles_mask_b[i] == 2) {
776 // last operand can be inversed, we cannot determine a result.
777 return false;
778 }
779 zero_mask_a |= static_cast<size_t>(swizzles_mask_a[i] == 3 ? 1 : 0) << i;
780 zero_mask_b |= static_cast<size_t>(swizzles_mask_b[i] == 3 ? 1 : 0) << i;
781 }
782 static constexpr size_t ddx_pattern = 0b1010;
783 static constexpr size_t ddx_pattern_inv = ~ddx_pattern & 0b00001111;
784 if (std::popcount(zero_mask_a) != 2) {
785 return false;
786 }
787 if (std::popcount(zero_mask_b) != 2) {
788 return false;
789 }
790 if (zero_mask_a == zero_mask_b) {
791 return false;
792 }
793 results[0] = temporary_values[coordinate_index];
794
795 if (coordinate_index == 0) {
796 if (zero_mask_b == ddx_pattern || zero_mask_b == ddx_pattern_inv) {
797 results[1] = temporary_values[1];
798 results[2] = temporary_values[2];
799 return true;
800 }
801 results[2] = temporary_values[1];
802 results[1] = temporary_values[2];
803 } else {
804 const auto assign_result = [&results](IR::Value temporary_value, size_t mask) {
805 if (mask == ddx_pattern || mask == ddx_pattern_inv) {
806 results[1] = temporary_value;
807 return;
808 }
809 results[2] = temporary_value;
810 };
811 assign_result(temporary_values[1], zero_mask_b);
812 assign_result(temporary_values[0], zero_mask_a);
813 }
814
815 return true;
816}
817
818void FoldImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
819 IR::TextureInstInfo info = inst.Flags<IR::TextureInstInfo>();
820 auto orig_opcode = inst.GetOpcode();
821 if (info.ndv_is_active == 0) {
822 return;
823 }
824 if (info.type != TextureType::Color3D) {
825 return;
826 }
827 const IR::Value handle{inst.Arg(0)};
828 const IR::Value coords{inst.Arg(1)};
829 const IR::Value bias_lc{inst.Arg(2)};
830 const IR::Value offset{inst.Arg(3)};
831 if (!offset.IsImmediate()) {
832 return;
833 }
834 IR::Inst* const inst2 = coords.InstRecursive();
835 std::array<std::array<IR::Value, 3>, 3> results_matrix;
836 for (size_t i = 0; i < 3; i++) {
837 if (!FindGradient3DDerivates(results_matrix[i], inst2->Arg(i).Resolve())) {
838 return;
839 }
840 }
841 IR::F32 lod_clamp{};
842 if (info.has_lod_clamp != 0) {
843 if (!bias_lc.IsImmediate()) {
844 lod_clamp = IR::F32{bias_lc.InstRecursive()->Arg(1).Resolve()};
845 } else {
846 lod_clamp = IR::F32{bias_lc};
847 }
848 }
849 IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
850 IR::Value new_coords =
851 ir.CompositeConstruct(results_matrix[0][0], results_matrix[1][0], results_matrix[2][0]);
852 IR::Value derivatives_1 = ir.CompositeConstruct(results_matrix[0][1], results_matrix[0][2],
853 results_matrix[1][1], results_matrix[1][2]);
854 IR::Value derivatives_2 = ir.CompositeConstruct(results_matrix[2][1], results_matrix[2][2]);
855 info.num_derivates.Assign(3);
856 IR::Value new_gradient_instruction =
857 ir.ImageGradient(handle, new_coords, derivatives_1, derivatives_2, lod_clamp, info);
858 IR::Inst* const new_inst = new_gradient_instruction.InstRecursive();
859 if (orig_opcode == IR::Opcode::ImageSampleImplicitLod) {
860 new_inst->ReplaceOpcode(IR::Opcode::ImageGradient);
861 }
862 inst.ReplaceUsesWith(new_gradient_instruction);
863}
864
651void FoldConstBuffer(Environment& env, IR::Block& block, IR::Inst& inst) { 865void FoldConstBuffer(Environment& env, IR::Block& block, IR::Inst& inst) {
652 const IR::Value bank{inst.Arg(0)}; 866 const IR::Value bank{inst.Arg(0)};
653 const IR::Value offset{inst.Arg(1)}; 867 const IR::Value offset{inst.Arg(1)};
@@ -743,6 +957,12 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) {
743 case IR::Opcode::SelectF32: 957 case IR::Opcode::SelectF32:
744 case IR::Opcode::SelectF64: 958 case IR::Opcode::SelectF64:
745 return FoldSelect(inst); 959 return FoldSelect(inst);
960 case IR::Opcode::FPNeg32:
961 FoldWhenAllImmediates(inst, [](f32 a) { return -a; });
962 return;
963 case IR::Opcode::FPAdd32:
964 FoldFPAdd32(inst);
965 return;
746 case IR::Opcode::FPMul32: 966 case IR::Opcode::FPMul32:
747 return FoldFPMul32(inst); 967 return FoldFPMul32(inst);
748 case IR::Opcode::LogicalAnd: 968 case IR::Opcode::LogicalAnd:
@@ -858,6 +1078,11 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) {
858 FoldDriverConstBuffer(env, block, inst, 1); 1078 FoldDriverConstBuffer(env, block, inst, 1);
859 } 1079 }
860 break; 1080 break;
1081 case IR::Opcode::BindlessImageSampleImplicitLod:
1082 case IR::Opcode::BoundImageSampleImplicitLod:
1083 case IR::Opcode::ImageSampleImplicitLod:
1084 FoldImageSampleImplicitLod(block, inst);
1085 break;
861 default: 1086 default:
862 break; 1087 break;
863 } 1088 }
diff --git a/src/tests/common/ring_buffer.cpp b/src/tests/common/ring_buffer.cpp
index e85f9977b..b6e3bc875 100644
--- a/src/tests/common/ring_buffer.cpp
+++ b/src/tests/common/ring_buffer.cpp
@@ -55,7 +55,7 @@ TEST_CASE("RingBuffer: Basic Tests", "[common]") {
55 // Pushing more values than space available should partially succeed. 55 // Pushing more values than space available should partially succeed.
56 { 56 {
57 std::vector<char> to_push(6); 57 std::vector<char> to_push(6);
58 std::iota(to_push.begin(), to_push.end(), 88); 58 std::iota(to_push.begin(), to_push.end(), static_cast<char>(88));
59 const std::size_t count = buf.Push(to_push); 59 const std::size_t count = buf.Push(to_push);
60 REQUIRE(count == 3U); 60 REQUIRE(count == 3U);
61 } 61 }
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index f0f450edb..8be7bd594 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -289,8 +289,11 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
289 MarkWrittenBuffer(buffer_id, *cpu_addr, size); 289 MarkWrittenBuffer(buffer_id, *cpu_addr, size);
290 break; 290 break;
291 case ObtainBufferOperation::DiscardWrite: { 291 case ObtainBufferOperation::DiscardWrite: {
292 IntervalType interval{*cpu_addr, size}; 292 VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64);
293 VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64);
294 IntervalType interval{cpu_addr_start, cpu_addr_end};
293 ClearDownload(interval); 295 ClearDownload(interval);
296 common_ranges.subtract(interval);
294 break; 297 break;
295 } 298 }
296 default: 299 default:
@@ -1159,6 +1162,11 @@ void BufferCache<P>::UpdateDrawIndirect() {
1159 .size = static_cast<u32>(size), 1162 .size = static_cast<u32>(size),
1160 .buffer_id = FindBuffer(*cpu_addr, static_cast<u32>(size)), 1163 .buffer_id = FindBuffer(*cpu_addr, static_cast<u32>(size)),
1161 }; 1164 };
1165 VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64);
1166 VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64);
1167 IntervalType interval{cpu_addr_start, cpu_addr_end};
1168 ClearDownload(interval);
1169 common_ranges.subtract(interval);
1162 }; 1170 };
1163 if (current_draw_indirect->include_count) { 1171 if (current_draw_indirect->include_count) {
1164 update(current_draw_indirect->count_start_address, sizeof(u32), 1172 update(current_draw_indirect->count_start_address, sizeof(u32),
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 9f1b340a9..58ce0d8c2 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -14,6 +14,7 @@
14namespace Tegra { 14namespace Tegra {
15 15
16constexpr u32 MacroRegistersStart = 0xE00; 16constexpr u32 MacroRegistersStart = 0xE00;
17constexpr u32 ComputeInline = 0x6D;
17 18
18DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, 19DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
19 Control::ChannelState& channel_state_) 20 Control::ChannelState& channel_state_)
@@ -83,12 +84,35 @@ bool DmaPusher::Step() {
83 dma_state.dma_get, command_list_header.size * sizeof(u32)); 84 dma_state.dma_get, command_list_header.size * sizeof(u32));
84 } 85 }
85 } 86 }
86 Core::Memory::GpuGuestMemory<Tegra::CommandHeader, 87 const auto safe_process = [&] {
87 Core::Memory::GuestMemoryFlags::UnsafeRead> 88 Core::Memory::GpuGuestMemory<Tegra::CommandHeader,
88 headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers); 89 Core::Memory::GuestMemoryFlags::SafeRead>
89 ProcessCommands(headers); 90 headers(memory_manager, dma_state.dma_get, command_list_header.size,
91 &command_headers);
92 ProcessCommands(headers);
93 };
94 const auto unsafe_process = [&] {
95 Core::Memory::GpuGuestMemory<Tegra::CommandHeader,
96 Core::Memory::GuestMemoryFlags::UnsafeRead>
97 headers(memory_manager, dma_state.dma_get, command_list_header.size,
98 &command_headers);
99 ProcessCommands(headers);
100 };
101 if (Settings::IsGPULevelHigh()) {
102 if (dma_state.method >= MacroRegistersStart) {
103 unsafe_process();
104 return true;
105 }
106 if (subchannel_type[dma_state.subchannel] == Engines::EngineTypes::KeplerCompute &&
107 dma_state.method == ComputeInline) {
108 unsafe_process();
109 return true;
110 }
111 safe_process();
112 return true;
113 }
114 unsafe_process();
90 } 115 }
91
92 return true; 116 return true;
93} 117}
94 118
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 8a2784cdc..c9fab2d90 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -130,8 +130,10 @@ public:
130 130
131 void DispatchCalls(); 131 void DispatchCalls();
132 132
133 void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id) { 133 void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id,
134 Engines::EngineTypes engine_type) {
134 subchannels[subchannel_id] = engine; 135 subchannels[subchannel_id] = engine;
136 subchannel_type[subchannel_id] = engine_type;
135 } 137 }
136 138
137 void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); 139 void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
@@ -170,6 +172,7 @@ private:
170 const bool ib_enable{true}; ///< IB mode enabled 172 const bool ib_enable{true}; ///< IB mode enabled
171 173
172 std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; 174 std::array<Engines::EngineInterface*, max_subchannels> subchannels{};
175 std::array<Engines::EngineTypes, max_subchannels> subchannel_type;
173 176
174 GPU& gpu; 177 GPU& gpu;
175 Core::System& system; 178 Core::System& system;
diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h
index 392322358..54631ee6c 100644
--- a/src/video_core/engines/engine_interface.h
+++ b/src/video_core/engines/engine_interface.h
@@ -11,6 +11,14 @@
11 11
12namespace Tegra::Engines { 12namespace Tegra::Engines {
13 13
14enum class EngineTypes : u32 {
15 KeplerCompute,
16 Maxwell3D,
17 Fermi2D,
18 MaxwellDMA,
19 KeplerMemory,
20};
21
14class EngineInterface { 22class EngineInterface {
15public: 23public:
16 virtual ~EngineInterface() = default; 24 virtual ~EngineInterface() = default;
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h
index 7242d2529..21bf8aeb4 100644
--- a/src/video_core/engines/engine_upload.h
+++ b/src/video_core/engines/engine_upload.h
@@ -69,6 +69,14 @@ public:
69 /// Binds a rasterizer to this engine. 69 /// Binds a rasterizer to this engine.
70 void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); 70 void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
71 71
72 GPUVAddr ExecTargetAddress() const {
73 return regs.dest.Address();
74 }
75
76 u32 GetUploadSize() const {
77 return copy_size;
78 }
79
72private: 80private:
73 void ProcessData(std::span<const u8> read_buffer); 81 void ProcessData(std::span<const u8> read_buffer);
74 82
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index a38d9528a..cd61ab222 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -43,16 +43,33 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
43 43
44 switch (method) { 44 switch (method) {
45 case KEPLER_COMPUTE_REG_INDEX(exec_upload): { 45 case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
46 UploadInfo info{.upload_address = upload_address,
47 .exec_address = upload_state.ExecTargetAddress(),
48 .copy_size = upload_state.GetUploadSize()};
49 uploads.push_back(info);
46 upload_state.ProcessExec(regs.exec_upload.linear != 0); 50 upload_state.ProcessExec(regs.exec_upload.linear != 0);
47 break; 51 break;
48 } 52 }
49 case KEPLER_COMPUTE_REG_INDEX(data_upload): { 53 case KEPLER_COMPUTE_REG_INDEX(data_upload): {
54 upload_address = current_dma_segment;
50 upload_state.ProcessData(method_argument, is_last_call); 55 upload_state.ProcessData(method_argument, is_last_call);
51 break; 56 break;
52 } 57 }
53 case KEPLER_COMPUTE_REG_INDEX(launch): 58 case KEPLER_COMPUTE_REG_INDEX(launch): {
59 const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
60
61 for (auto& data : uploads) {
62 const GPUVAddr offset = data.exec_address - launch_desc_loc;
63 if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) &&
64 memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) {
65 indirect_compute = {data.upload_address};
66 }
67 }
68 uploads.clear();
54 ProcessLaunch(); 69 ProcessLaunch();
70 indirect_compute = std::nullopt;
55 break; 71 break;
72 }
56 default: 73 default:
57 break; 74 break;
58 } 75 }
@@ -62,6 +79,7 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun
62 u32 methods_pending) { 79 u32 methods_pending) {
63 switch (method) { 80 switch (method) {
64 case KEPLER_COMPUTE_REG_INDEX(data_upload): 81 case KEPLER_COMPUTE_REG_INDEX(data_upload):
82 upload_address = current_dma_segment;
65 upload_state.ProcessData(base_start, amount); 83 upload_state.ProcessData(base_start, amount);
66 return; 84 return;
67 default: 85 default:
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 2092e685f..735e05fb4 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -5,6 +5,7 @@
5 5
6#include <array> 6#include <array>
7#include <cstddef> 7#include <cstddef>
8#include <optional>
8#include <vector> 9#include <vector>
9#include "common/bit_field.h" 10#include "common/bit_field.h"
10#include "common/common_funcs.h" 11#include "common/common_funcs.h"
@@ -36,6 +37,9 @@ namespace Tegra::Engines {
36#define KEPLER_COMPUTE_REG_INDEX(field_name) \ 37#define KEPLER_COMPUTE_REG_INDEX(field_name) \
37 (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32)) 38 (offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32))
38 39
40#define LAUNCH_REG_INDEX(field_name) \
41 (offsetof(Tegra::Engines::KeplerCompute::LaunchParams, field_name) / sizeof(u32))
42
39class KeplerCompute final : public EngineInterface { 43class KeplerCompute final : public EngineInterface {
40public: 44public:
41 explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); 45 explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager);
@@ -201,6 +205,10 @@ public:
201 void CallMultiMethod(u32 method, const u32* base_start, u32 amount, 205 void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
202 u32 methods_pending) override; 206 u32 methods_pending) override;
203 207
208 std::optional<GPUVAddr> GetIndirectComputeAddress() const {
209 return indirect_compute;
210 }
211
204private: 212private:
205 void ProcessLaunch(); 213 void ProcessLaunch();
206 214
@@ -216,6 +224,15 @@ private:
216 MemoryManager& memory_manager; 224 MemoryManager& memory_manager;
217 VideoCore::RasterizerInterface* rasterizer = nullptr; 225 VideoCore::RasterizerInterface* rasterizer = nullptr;
218 Upload::State upload_state; 226 Upload::State upload_state;
227 GPUVAddr upload_address;
228
229 struct UploadInfo {
230 GPUVAddr upload_address;
231 GPUVAddr exec_address;
232 u32 copy_size;
233 };
234 std::vector<UploadInfo> uploads;
235 std::optional<GPUVAddr> indirect_compute{};
219}; 236};
220 237
221#define ASSERT_REG_POSITION(field_name, position) \ 238#define ASSERT_REG_POSITION(field_name, position) \
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index c3696096d..06e349e43 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -257,6 +257,7 @@ u32 Maxwell3D::GetMaxCurrentVertices() {
257 const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); 257 const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
258 num_vertices = std::max( 258 num_vertices = std::max(
259 num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value())); 259 num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value()));
260 break;
260 } 261 }
261 return num_vertices; 262 return num_vertices;
262} 263}
@@ -269,10 +270,13 @@ size_t Maxwell3D::EstimateIndexBufferSize() {
269 std::numeric_limits<u32>::max()}; 270 std::numeric_limits<u32>::max()};
270 const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); 271 const size_t byte_size = regs.index_buffer.FormatSizeInBytes();
271 const size_t log2_byte_size = Common::Log2Ceil64(byte_size); 272 const size_t log2_byte_size = Common::Log2Ceil64(byte_size);
273 const size_t cap{GetMaxCurrentVertices() * 3 * byte_size};
274 const size_t lower_cap =
275 std::min<size_t>(static_cast<size_t>(end_address - start_address), cap);
272 return std::min<size_t>( 276 return std::min<size_t>(
273 memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / 277 memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) /
274 byte_size, 278 byte_size,
275 static_cast<size_t>(end_address - start_address)); 279 lower_cap);
276} 280}
277 281
278u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { 282u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index cd8e24b0b..da8eab7ee 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -5,6 +5,7 @@
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/logging/log.h" 6#include "common/logging/log.h"
7#include "common/microprofile.h" 7#include "common/microprofile.h"
8#include "common/polyfill_ranges.h"
8#include "common/settings.h" 9#include "common/settings.h"
9#include "core/core.h" 10#include "core/core.h"
10#include "core/memory.h" 11#include "core/memory.h"
diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp
index 7718a09b3..6de2543b7 100644
--- a/src/video_core/engines/puller.cpp
+++ b/src/video_core/engines/puller.cpp
@@ -34,19 +34,24 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {
34 bound_engines[method_call.subchannel] = engine_id; 34 bound_engines[method_call.subchannel] = engine_id;
35 switch (engine_id) { 35 switch (engine_id) {
36 case EngineID::FERMI_TWOD_A: 36 case EngineID::FERMI_TWOD_A:
37 dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel); 37 dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel,
38 EngineTypes::Fermi2D);
38 break; 39 break;
39 case EngineID::MAXWELL_B: 40 case EngineID::MAXWELL_B:
40 dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel); 41 dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel,
42 EngineTypes::Maxwell3D);
41 break; 43 break;
42 case EngineID::KEPLER_COMPUTE_B: 44 case EngineID::KEPLER_COMPUTE_B:
43 dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel); 45 dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel,
46 EngineTypes::KeplerCompute);
44 break; 47 break;
45 case EngineID::MAXWELL_DMA_COPY_A: 48 case EngineID::MAXWELL_DMA_COPY_A:
46 dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel); 49 dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel,
50 EngineTypes::MaxwellDMA);
47 break; 51 break;
48 case EngineID::KEPLER_INLINE_TO_MEMORY_B: 52 case EngineID::KEPLER_INLINE_TO_MEMORY_B:
49 dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel); 53 dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel,
54 EngineTypes::KeplerMemory);
50 break; 55 break;
51 default: 56 default:
52 UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); 57 UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index 220cce28a..8d7da50fc 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -319,6 +319,7 @@ void Codec::Decode() {
319 LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); 319 LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
320 return; 320 return;
321 } 321 }
322 bool is_interlaced = initial_frame->interlaced_frame != 0;
322 if (av_codec_ctx->hw_device_ctx) { 323 if (av_codec_ctx->hw_device_ctx) {
323 final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; 324 final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
324 ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed"); 325 ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed");
@@ -334,7 +335,7 @@ void Codec::Decode() {
334 UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); 335 UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
335 return; 336 return;
336 } 337 }
337 if (!final_frame->interlaced_frame) { 338 if (!is_interlaced) {
338 av_frames.push(std::move(final_frame)); 339 av_frames.push(std::move(final_frame));
339 } else { 340 } else {
340 if (!filters_initialized) { 341 if (!filters_initialized) {
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index e61d9af80..c4d459077 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -50,6 +50,7 @@ set(SHADER_FILES
50 vulkan_blit_depth_stencil.frag 50 vulkan_blit_depth_stencil.frag
51 vulkan_color_clear.frag 51 vulkan_color_clear.frag
52 vulkan_color_clear.vert 52 vulkan_color_clear.vert
53 vulkan_depthstencil_clear.frag
53 vulkan_fidelityfx_fsr_easu_fp16.comp 54 vulkan_fidelityfx_fsr_easu_fp16.comp
54 vulkan_fidelityfx_fsr_easu_fp32.comp 55 vulkan_fidelityfx_fsr_easu_fp32.comp
55 vulkan_fidelityfx_fsr_rcas_fp16.comp 56 vulkan_fidelityfx_fsr_rcas_fp16.comp
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp
index bf2693559..5ff17cd0c 100644
--- a/src/video_core/host_shaders/astc_decoder.comp
+++ b/src/video_core/host_shaders/astc_decoder.comp
@@ -33,26 +33,14 @@ UNIFORM(6) uint block_height_mask;
33END_PUSH_CONSTANTS 33END_PUSH_CONSTANTS
34 34
35struct EncodingData { 35struct EncodingData {
36 uint encoding; 36 uint data;
37 uint num_bits;
38 uint bit_value;
39 uint quint_trit_value;
40}; 37};
41 38
42struct TexelWeightParams { 39layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBufferU32 {
43 uvec2 size;
44 uint max_weight;
45 bool dual_plane;
46 bool error_state;
47 bool void_extent_ldr;
48 bool void_extent_hdr;
49};
50
51layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 {
52 uvec4 astc_data[]; 40 uvec4 astc_data[];
53}; 41};
54 42
55layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly image2DArray dest_image; 43layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image;
56 44
57const uint GOB_SIZE_X_SHIFT = 6; 45const uint GOB_SIZE_X_SHIFT = 6;
58const uint GOB_SIZE_Y_SHIFT = 3; 46const uint GOB_SIZE_Y_SHIFT = 3;
@@ -60,64 +48,21 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
60 48
61const uint BYTES_PER_BLOCK_LOG2 = 4; 49const uint BYTES_PER_BLOCK_LOG2 = 4;
62 50
63const int JUST_BITS = 0; 51const uint JUST_BITS = 0u;
64const int QUINT = 1; 52const uint QUINT = 1u;
65const int TRIT = 2; 53const uint TRIT = 2u;
66 54
67// ASTC Encodings data, sorted in ascending order based on their BitLength value 55// ASTC Encodings data, sorted in ascending order based on their BitLength value
68// (see GetBitLength() function) 56// (see GetBitLength() function)
69EncodingData encoding_values[22] = EncodingData[]( 57const uint encoding_values[22] = uint[](
70 EncodingData(JUST_BITS, 0, 0, 0), EncodingData(JUST_BITS, 1, 0, 0), EncodingData(TRIT, 0, 0, 0), 58 (JUST_BITS), (JUST_BITS | (1u << 8u)), (TRIT), (JUST_BITS | (2u << 8u)),
71 EncodingData(JUST_BITS, 2, 0, 0), EncodingData(QUINT, 0, 0, 0), EncodingData(TRIT, 1, 0, 0), 59 (QUINT), (TRIT | (1u << 8u)), (JUST_BITS | (3u << 8u)), (QUINT | (1u << 8u)),
72 EncodingData(JUST_BITS, 3, 0, 0), EncodingData(QUINT, 1, 0, 0), EncodingData(TRIT, 2, 0, 0), 60 (TRIT | (2u << 8u)), (JUST_BITS | (4u << 8u)), (QUINT | (2u << 8u)), (TRIT | (3u << 8u)),
73 EncodingData(JUST_BITS, 4, 0, 0), EncodingData(QUINT, 2, 0, 0), EncodingData(TRIT, 3, 0, 0), 61 (JUST_BITS | (5u << 8u)), (QUINT | (3u << 8u)), (TRIT | (4u << 8u)), (JUST_BITS | (6u << 8u)),
74 EncodingData(JUST_BITS, 5, 0, 0), EncodingData(QUINT, 3, 0, 0), EncodingData(TRIT, 4, 0, 0), 62 (QUINT | (4u << 8u)), (TRIT | (5u << 8u)), (JUST_BITS | (7u << 8u)), (QUINT | (5u << 8u)),
75 EncodingData(JUST_BITS, 6, 0, 0), EncodingData(QUINT, 4, 0, 0), EncodingData(TRIT, 5, 0, 0), 63 (TRIT | (6u << 8u)), (JUST_BITS | (8u << 8u)));
76 EncodingData(JUST_BITS, 7, 0, 0), EncodingData(QUINT, 5, 0, 0), EncodingData(TRIT, 6, 0, 0),
77 EncodingData(JUST_BITS, 8, 0, 0)
78);
79
80// The following constants are expanded variants of the Replicate()
81// function calls corresponding to the following arguments:
82// value: index into the generated table
83// num_bits: the after "REPLICATE" in the table name. i.e. 4 is num_bits in REPLICATE_4.
84// to_bit: the integer after "TO_"
85const uint REPLICATE_BIT_TO_7_TABLE[2] = uint[](0, 127);
86const uint REPLICATE_1_BIT_TO_9_TABLE[2] = uint[](0, 511);
87
88const uint REPLICATE_1_BIT_TO_8_TABLE[2] = uint[](0, 255);
89const uint REPLICATE_2_BIT_TO_8_TABLE[4] = uint[](0, 85, 170, 255);
90const uint REPLICATE_3_BIT_TO_8_TABLE[8] = uint[](0, 36, 73, 109, 146, 182, 219, 255);
91const uint REPLICATE_4_BIT_TO_8_TABLE[16] =
92 uint[](0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255);
93const uint REPLICATE_5_BIT_TO_8_TABLE[32] =
94 uint[](0, 8, 16, 24, 33, 41, 49, 57, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165,
95 173, 181, 189, 198, 206, 214, 222, 231, 239, 247, 255);
96const uint REPLICATE_1_BIT_TO_6_TABLE[2] = uint[](0, 63);
97const uint REPLICATE_2_BIT_TO_6_TABLE[4] = uint[](0, 21, 42, 63);
98const uint REPLICATE_3_BIT_TO_6_TABLE[8] = uint[](0, 9, 18, 27, 36, 45, 54, 63);
99const uint REPLICATE_4_BIT_TO_6_TABLE[16] =
100 uint[](0, 4, 8, 12, 17, 21, 25, 29, 34, 38, 42, 46, 51, 55, 59, 63);
101const uint REPLICATE_5_BIT_TO_6_TABLE[32] =
102 uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 33, 35, 37, 39, 41, 43, 45,
103 47, 49, 51, 53, 55, 57, 59, 61, 63);
104const uint REPLICATE_6_BIT_TO_8_TABLE[64] =
105 uint[](0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 65, 69, 73, 77, 81, 85, 89,
106 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162,
107 166, 170, 174, 178, 182, 186, 190, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235,
108 239, 243, 247, 251, 255);
109const uint REPLICATE_7_BIT_TO_8_TABLE[128] =
110 uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44,
111 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88,
112 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126,
113 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163,
114 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199,
115 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235,
116 237, 239, 241, 243, 245, 247, 249, 251, 253, 255);
117 64
118// Input ASTC texture globals 65// Input ASTC texture globals
119uint current_index = 0;
120int bitsread = 0;
121int total_bitsread = 0; 66int total_bitsread = 0;
122uvec4 local_buff; 67uvec4 local_buff;
123 68
@@ -125,50 +70,60 @@ uvec4 local_buff;
125uvec4 color_endpoint_data; 70uvec4 color_endpoint_data;
126int color_bitsread = 0; 71int color_bitsread = 0;
127 72
128// Four values, two endpoints, four maximum partitions 73// Global "vector" to be pushed into when decoding
129uint color_values[32]; 74// At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode
130int colvals_index = 0; 75// At most will require BLOCK_WIDTH x BLOCK_HEIGHT x 2 in dual plane mode
131 76// So the maximum would be 144 (12 x 12) elements, x 2 for two planes
132// Weight data globals 77#define DIVCEIL(number, divisor) (number + divisor - 1) / divisor
133uvec4 texel_weight_data; 78#define ARRAY_NUM_ELEMENTS 144
134int texel_bitsread = 0; 79#define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4)
80uint result_vector[ARRAY_NUM_ELEMENTS * 2];
135 81
136bool texel_flag = false;
137
138// Global "vectors" to be pushed into when decoding
139EncodingData result_vector[144];
140int result_index = 0; 82int result_index = 0;
83uint result_vector_max_index;
84bool result_limit_reached = false;
141 85
142EncodingData texel_vector[144]; 86// EncodingData helpers
143int texel_vector_index = 0; 87uint Encoding(EncodingData val) {
88 return bitfieldExtract(val.data, 0, 8);
89}
90uint NumBits(EncodingData val) {
91 return bitfieldExtract(val.data, 8, 8);
92}
93uint BitValue(EncodingData val) {
94 return bitfieldExtract(val.data, 16, 8);
95}
96uint QuintTritValue(EncodingData val) {
97 return bitfieldExtract(val.data, 24, 8);
98}
144 99
145uint unquantized_texel_weights[2][144]; 100void Encoding(inout EncodingData val, uint v) {
101 val.data = bitfieldInsert(val.data, v, 0, 8);
102}
103void NumBits(inout EncodingData val, uint v) {
104 val.data = bitfieldInsert(val.data, v, 8, 8);
105}
106void BitValue(inout EncodingData val, uint v) {
107 val.data = bitfieldInsert(val.data, v, 16, 8);
108}
109void QuintTritValue(inout EncodingData val, uint v) {
110 val.data = bitfieldInsert(val.data, v, 24, 8);
111}
146 112
147uint SwizzleOffset(uvec2 pos) { 113EncodingData CreateEncodingData(uint encoding, uint num_bits, uint bit_val, uint quint_trit_val) {
148 uint x = pos.x; 114 return EncodingData(((encoding) << 0u) | ((num_bits) << 8u) |
149 uint y = pos.y; 115 ((bit_val) << 16u) | ((quint_trit_val) << 24u));
150 return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
151 (y % 2) * 16 + (x % 16);
152} 116}
153 117
154// Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)] 118
155// is the same as [(num_bits - 1):0] and repeats all the way down. 119void ResultEmplaceBack(EncodingData val) {
156uint Replicate(uint val, uint num_bits, uint to_bit) { 120 if (result_index >= result_vector_max_index) {
157 const uint v = val & uint((1 << num_bits) - 1); 121 // Alert callers to avoid decoding more than needed by this phase
158 uint res = v; 122 result_limit_reached = true;
159 uint reslen = num_bits; 123 return;
160 while (reslen < to_bit) {
161 uint comp = 0;
162 if (num_bits > to_bit - reslen) {
163 uint newshift = to_bit - reslen;
164 comp = num_bits - newshift;
165 num_bits = newshift;
166 }
167 res = uint(res << num_bits);
168 res = uint(res | (v >> comp));
169 reslen += num_bits;
170 } 124 }
171 return res; 125 result_vector[result_index] = val.data;
126 ++result_index;
172} 127}
173 128
174uvec4 ReplicateByteTo16(uvec4 value) { 129uvec4 ReplicateByteTo16(uvec4 value) {
@@ -176,64 +131,40 @@ uvec4 ReplicateByteTo16(uvec4 value) {
176} 131}
177 132
178uint ReplicateBitTo7(uint value) { 133uint ReplicateBitTo7(uint value) {
179 return REPLICATE_BIT_TO_7_TABLE[value]; 134 return value * 127;
180} 135}
181 136
182uint ReplicateBitTo9(uint value) { 137uint ReplicateBitTo9(uint value) {
183 return REPLICATE_1_BIT_TO_9_TABLE[value]; 138 return value * 511;
184} 139}
185 140
186uint FastReplicate(uint value, uint num_bits, uint to_bit) { 141uint ReplicateBits(uint value, uint num_bits, uint to_bit) {
187 if (num_bits == 0) { 142 if (value == 0 || num_bits == 0) {
188 return 0; 143 return 0;
189 } 144 }
190 if (num_bits == to_bit) { 145 if (num_bits >= to_bit) {
191 return value; 146 return value;
192 } 147 }
193 if (to_bit == 6) { 148 const uint v = value & uint((1 << num_bits) - 1);
194 switch (num_bits) { 149 uint res = v;
195 case 1: 150 uint reslen = num_bits;
196 return REPLICATE_1_BIT_TO_6_TABLE[value]; 151 while (reslen < to_bit) {
197 case 2: 152 const uint num_dst_bits_to_shift_up = min(num_bits, to_bit - reslen);
198 return REPLICATE_2_BIT_TO_6_TABLE[value]; 153 const uint num_src_bits_to_shift_down = num_bits - num_dst_bits_to_shift_up;
199 case 3: 154
200 return REPLICATE_3_BIT_TO_6_TABLE[value]; 155 res <<= num_dst_bits_to_shift_up;
201 case 4: 156 res |= (v >> num_src_bits_to_shift_down);
202 return REPLICATE_4_BIT_TO_6_TABLE[value]; 157 reslen += num_bits;
203 case 5:
204 return REPLICATE_5_BIT_TO_6_TABLE[value];
205 default:
206 break;
207 }
208 } else { /* if (to_bit == 8) */
209 switch (num_bits) {
210 case 1:
211 return REPLICATE_1_BIT_TO_8_TABLE[value];
212 case 2:
213 return REPLICATE_2_BIT_TO_8_TABLE[value];
214 case 3:
215 return REPLICATE_3_BIT_TO_8_TABLE[value];
216 case 4:
217 return REPLICATE_4_BIT_TO_8_TABLE[value];
218 case 5:
219 return REPLICATE_5_BIT_TO_8_TABLE[value];
220 case 6:
221 return REPLICATE_6_BIT_TO_8_TABLE[value];
222 case 7:
223 return REPLICATE_7_BIT_TO_8_TABLE[value];
224 default:
225 break;
226 }
227 } 158 }
228 return Replicate(value, num_bits, to_bit); 159 return res;
229} 160}
230 161
231uint FastReplicateTo8(uint value, uint num_bits) { 162uint FastReplicateTo8(uint value, uint num_bits) {
232 return FastReplicate(value, num_bits, 8); 163 return ReplicateBits(value, num_bits, 8);
233} 164}
234 165
235uint FastReplicateTo6(uint value, uint num_bits) { 166uint FastReplicateTo6(uint value, uint num_bits) {
236 return FastReplicate(value, num_bits, 6); 167 return ReplicateBits(value, num_bits, 6);
237} 168}
238 169
239uint Div3Floor(uint v) { 170uint Div3Floor(uint v) {
@@ -266,15 +197,15 @@ uint Hash52(uint p) {
266 return p; 197 return p;
267} 198}
268 199
269uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) { 200uint Select2DPartition(uint seed, uint x, uint y, uint partition_count) {
270 if (small_block) { 201 if ((block_dims.y * block_dims.x) < 32) {
271 x <<= 1; 202 x <<= 1;
272 y <<= 1; 203 y <<= 1;
273 } 204 }
274 205
275 seed += (partition_count - 1) * 1024; 206 seed += (partition_count - 1) * 1024;
276 207
277 uint rnum = Hash52(uint(seed)); 208 const uint rnum = Hash52(uint(seed));
278 uint seed1 = uint(rnum & 0xF); 209 uint seed1 = uint(rnum & 0xF);
279 uint seed2 = uint((rnum >> 4) & 0xF); 210 uint seed2 = uint((rnum >> 4) & 0xF);
280 uint seed3 = uint((rnum >> 8) & 0xF); 211 uint seed3 = uint((rnum >> 8) & 0xF);
@@ -342,53 +273,52 @@ uint ExtractBits(uvec4 payload, int offset, int bits) {
342 if (bits <= 0) { 273 if (bits <= 0) {
343 return 0; 274 return 0;
344 } 275 }
345 int last_offset = offset + bits - 1; 276 if (bits > 32) {
346 int shifted_offset = offset >> 5; 277 return 0;
278 }
279 const int last_offset = offset + bits - 1;
280 const int shifted_offset = offset >> 5;
347 if ((last_offset >> 5) == shifted_offset) { 281 if ((last_offset >> 5) == shifted_offset) {
348 return bitfieldExtract(payload[shifted_offset], offset & 31, bits); 282 return bitfieldExtract(payload[shifted_offset], offset & 31, bits);
349 } 283 }
350 int first_bits = 32 - (offset & 31); 284 const int first_bits = 32 - (offset & 31);
351 int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits)); 285 const int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits));
352 int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits)); 286 const int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits));
353 return result_first | (result_second << first_bits); 287 return result_first | (result_second << first_bits);
354} 288}
355 289
356uint StreamBits(uint num_bits) { 290uint StreamBits(uint num_bits) {
357 int int_bits = int(num_bits); 291 const int int_bits = int(num_bits);
358 uint ret = ExtractBits(local_buff, total_bitsread, int_bits); 292 const uint ret = ExtractBits(local_buff, total_bitsread, int_bits);
359 total_bitsread += int_bits; 293 total_bitsread += int_bits;
360 return ret; 294 return ret;
361} 295}
362 296
297void SkipBits(uint num_bits) {
298 const int int_bits = int(num_bits);
299 total_bitsread += int_bits;
300}
301
363uint StreamColorBits(uint num_bits) { 302uint StreamColorBits(uint num_bits) {
364 uint ret = 0; 303 const int int_bits = int(num_bits);
365 int int_bits = int(num_bits); 304 const uint ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits);
366 if (texel_flag) { 305 color_bitsread += int_bits;
367 ret = ExtractBits(texel_weight_data, texel_bitsread, int_bits);
368 texel_bitsread += int_bits;
369 } else {
370 ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits);
371 color_bitsread += int_bits;
372 }
373 return ret; 306 return ret;
374} 307}
375 308
376void ResultEmplaceBack(EncodingData val) { 309EncodingData GetEncodingFromVector(uint index) {
377 if (texel_flag) { 310 const uint data = result_vector[index];
378 texel_vector[texel_vector_index] = val; 311 return EncodingData(data);
379 ++texel_vector_index;
380 } else {
381 result_vector[result_index] = val;
382 ++result_index;
383 }
384} 312}
385 313
386// Returns the number of bits required to encode n_vals values. 314// Returns the number of bits required to encode n_vals values.
387uint GetBitLength(uint n_vals, uint encoding_index) { 315uint GetBitLength(uint n_vals, uint encoding_index) {
388 uint total_bits = encoding_values[encoding_index].num_bits * n_vals; 316 const EncodingData encoding_value = EncodingData(encoding_values[encoding_index]);
389 if (encoding_values[encoding_index].encoding == TRIT) { 317 const uint encoding = Encoding(encoding_value);
318 uint total_bits = NumBits(encoding_value) * n_vals;
319 if (encoding == TRIT) {
390 total_bits += Div5Ceil(n_vals * 8); 320 total_bits += Div5Ceil(n_vals * 8);
391 } else if (encoding_values[encoding_index].encoding == QUINT) { 321 } else if (encoding == QUINT) {
392 total_bits += Div3Ceil(n_vals * 7); 322 total_bits += Div3Ceil(n_vals * 7);
393 } 323 }
394 return total_bits; 324 return total_bits;
@@ -403,7 +333,7 @@ uint GetNumWeightValues(uvec2 size, bool dual_plane) {
403} 333}
404 334
405uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) { 335uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) {
406 uint n_vals = GetNumWeightValues(size, dual_plane); 336 const uint n_vals = GetNumWeightValues(size, dual_plane);
407 return GetBitLength(n_vals, max_weight); 337 return GetBitLength(n_vals, max_weight);
408} 338}
409 339
@@ -412,87 +342,74 @@ uint BitsBracket(uint bits, uint pos) {
412} 342}
413 343
414uint BitsOp(uint bits, uint start, uint end) { 344uint BitsOp(uint bits, uint start, uint end) {
415 if (start == end) { 345 const uint mask = (1 << (end - start + 1)) - 1;
416 return BitsBracket(bits, start);
417 } else if (start > end) {
418 uint t = start;
419 start = end;
420 end = t;
421 }
422
423 uint mask = (1 << (end - start + 1)) - 1;
424 return ((bits >> start) & mask); 346 return ((bits >> start) & mask);
425} 347}
426 348
427void DecodeQuintBlock(uint num_bits) { 349void DecodeQuintBlock(uint num_bits) {
428 uint m[3]; 350 uvec3 m;
429 uint q[3]; 351 uvec4 qQ;
430 uint Q;
431 m[0] = StreamColorBits(num_bits); 352 m[0] = StreamColorBits(num_bits);
432 Q = StreamColorBits(3); 353 qQ.w = StreamColorBits(3);
433 m[1] = StreamColorBits(num_bits); 354 m[1] = StreamColorBits(num_bits);
434 Q |= StreamColorBits(2) << 3; 355 qQ.w |= StreamColorBits(2) << 3;
435 m[2] = StreamColorBits(num_bits); 356 m[2] = StreamColorBits(num_bits);
436 Q |= StreamColorBits(2) << 5; 357 qQ.w |= StreamColorBits(2) << 5;
437 if (BitsOp(Q, 1, 2) == 3 && BitsOp(Q, 5, 6) == 0) { 358 if (BitsOp(qQ.w, 1, 2) == 3 && BitsOp(qQ.w, 5, 6) == 0) {
438 q[0] = 4; 359 qQ.x = 4;
439 q[1] = 4; 360 qQ.y = 4;
440 q[2] = (BitsBracket(Q, 0) << 2) | ((BitsBracket(Q, 4) & ~BitsBracket(Q, 0)) << 1) | 361 qQ.z = (BitsBracket(qQ.w, 0) << 2) | ((BitsBracket(qQ.w, 4) & ~BitsBracket(qQ.w, 0)) << 1) |
441 (BitsBracket(Q, 3) & ~BitsBracket(Q, 0)); 362 (BitsBracket(qQ.w, 3) & ~BitsBracket(qQ.w, 0));
442 } else { 363 } else {
443 uint C = 0; 364 uint C = 0;
444 if (BitsOp(Q, 1, 2) == 3) { 365 if (BitsOp(qQ.w, 1, 2) == 3) {
445 q[2] = 4; 366 qQ.z = 4;
446 C = (BitsOp(Q, 3, 4) << 3) | ((~BitsOp(Q, 5, 6) & 3) << 1) | BitsBracket(Q, 0); 367 C = (BitsOp(qQ.w, 3, 4) << 3) | ((~BitsOp(qQ.w, 5, 6) & 3) << 1) | BitsBracket(qQ.w, 0);
447 } else { 368 } else {
448 q[2] = BitsOp(Q, 5, 6); 369 qQ.z = BitsOp(qQ.w, 5, 6);
449 C = BitsOp(Q, 0, 4); 370 C = BitsOp(qQ.w, 0, 4);
450 } 371 }
451 if (BitsOp(C, 0, 2) == 5) { 372 if (BitsOp(C, 0, 2) == 5) {
452 q[1] = 4; 373 qQ.y = 4;
453 q[0] = BitsOp(C, 3, 4); 374 qQ.x = BitsOp(C, 3, 4);
454 } else { 375 } else {
455 q[1] = BitsOp(C, 3, 4); 376 qQ.y = BitsOp(C, 3, 4);
456 q[0] = BitsOp(C, 0, 2); 377 qQ.x = BitsOp(C, 0, 2);
457 } 378 }
458 } 379 }
459 for (uint i = 0; i < 3; i++) { 380 for (uint i = 0; i < 3; i++) {
460 EncodingData val; 381 const EncodingData val = CreateEncodingData(QUINT, num_bits, m[i], qQ[i]);
461 val.encoding = QUINT;
462 val.num_bits = num_bits;
463 val.bit_value = m[i];
464 val.quint_trit_value = q[i];
465 ResultEmplaceBack(val); 382 ResultEmplaceBack(val);
466 } 383 }
467} 384}
468 385
469void DecodeTritBlock(uint num_bits) { 386void DecodeTritBlock(uint num_bits) {
470 uint m[5]; 387 uvec4 m;
471 uint t[5]; 388 uvec4 t;
472 uint T; 389 uvec3 Tm5t5;
473 m[0] = StreamColorBits(num_bits); 390 m[0] = StreamColorBits(num_bits);
474 T = StreamColorBits(2); 391 Tm5t5.x = StreamColorBits(2);
475 m[1] = StreamColorBits(num_bits); 392 m[1] = StreamColorBits(num_bits);
476 T |= StreamColorBits(2) << 2; 393 Tm5t5.x |= StreamColorBits(2) << 2;
477 m[2] = StreamColorBits(num_bits); 394 m[2] = StreamColorBits(num_bits);
478 T |= StreamColorBits(1) << 4; 395 Tm5t5.x |= StreamColorBits(1) << 4;
479 m[3] = StreamColorBits(num_bits); 396 m[3] = StreamColorBits(num_bits);
480 T |= StreamColorBits(2) << 5; 397 Tm5t5.x |= StreamColorBits(2) << 5;
481 m[4] = StreamColorBits(num_bits); 398 Tm5t5.y = StreamColorBits(num_bits);
482 T |= StreamColorBits(1) << 7; 399 Tm5t5.x |= StreamColorBits(1) << 7;
483 uint C = 0; 400 uint C = 0;
484 if (BitsOp(T, 2, 4) == 7) { 401 if (BitsOp(Tm5t5.x, 2, 4) == 7) {
485 C = (BitsOp(T, 5, 7) << 2) | BitsOp(T, 0, 1); 402 C = (BitsOp(Tm5t5.x, 5, 7) << 2) | BitsOp(Tm5t5.x, 0, 1);
486 t[4] = 2; 403 Tm5t5.z = 2;
487 t[3] = 2; 404 t[3] = 2;
488 } else { 405 } else {
489 C = BitsOp(T, 0, 4); 406 C = BitsOp(Tm5t5.x, 0, 4);
490 if (BitsOp(T, 5, 6) == 3) { 407 if (BitsOp(Tm5t5.x, 5, 6) == 3) {
491 t[4] = 2; 408 Tm5t5.z = 2;
492 t[3] = BitsBracket(T, 7); 409 t[3] = BitsBracket(Tm5t5.x, 7);
493 } else { 410 } else {
494 t[4] = BitsBracket(T, 7); 411 Tm5t5.z = BitsBracket(Tm5t5.x, 7);
495 t[3] = BitsOp(T, 5, 6); 412 t[3] = BitsOp(Tm5t5.x, 5, 6);
496 } 413 }
497 } 414 }
498 if (BitsOp(C, 0, 1) == 3) { 415 if (BitsOp(C, 0, 1) == 3) {
@@ -508,31 +425,31 @@ void DecodeTritBlock(uint num_bits) {
508 t[1] = BitsOp(C, 2, 3); 425 t[1] = BitsOp(C, 2, 3);
509 t[0] = (BitsBracket(C, 1) << 1) | (BitsBracket(C, 0) & ~BitsBracket(C, 1)); 426 t[0] = (BitsBracket(C, 1) << 1) | (BitsBracket(C, 0) & ~BitsBracket(C, 1));
510 } 427 }
511 for (uint i = 0; i < 5; i++) { 428 for (uint i = 0; i < 4; i++) {
512 EncodingData val; 429 const EncodingData val = CreateEncodingData(TRIT, num_bits, m[i], t[i]);
513 val.encoding = TRIT;
514 val.num_bits = num_bits;
515 val.bit_value = m[i];
516 val.quint_trit_value = t[i];
517 ResultEmplaceBack(val); 430 ResultEmplaceBack(val);
518 } 431 }
432 const EncodingData val = CreateEncodingData(TRIT, num_bits, Tm5t5.y, Tm5t5.z);
433 ResultEmplaceBack(val);
519} 434}
520 435
521void DecodeIntegerSequence(uint max_range, uint num_values) { 436void DecodeIntegerSequence(uint max_range, uint num_values) {
522 EncodingData val = encoding_values[max_range]; 437 EncodingData val = EncodingData(encoding_values[max_range]);
438 const uint encoding = Encoding(val);
439 const uint num_bits = NumBits(val);
523 uint vals_decoded = 0; 440 uint vals_decoded = 0;
524 while (vals_decoded < num_values) { 441 while (vals_decoded < num_values && !result_limit_reached) {
525 switch (val.encoding) { 442 switch (encoding) {
526 case QUINT: 443 case QUINT:
527 DecodeQuintBlock(val.num_bits); 444 DecodeQuintBlock(num_bits);
528 vals_decoded += 3; 445 vals_decoded += 3;
529 break; 446 break;
530 case TRIT: 447 case TRIT:
531 DecodeTritBlock(val.num_bits); 448 DecodeTritBlock(num_bits);
532 vals_decoded += 5; 449 vals_decoded += 5;
533 break; 450 break;
534 case JUST_BITS: 451 case JUST_BITS:
535 val.bit_value = StreamColorBits(val.num_bits); 452 BitValue(val, StreamColorBits(num_bits));
536 ResultEmplaceBack(val); 453 ResultEmplaceBack(val);
537 vals_decoded++; 454 vals_decoded++;
538 break; 455 break;
@@ -540,7 +457,7 @@ void DecodeIntegerSequence(uint max_range, uint num_values) {
540 } 457 }
541} 458}
542 459
543void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) { 460void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, out uint color_values[32]) {
544 uint num_values = 0; 461 uint num_values = 0;
545 for (uint i = 0; i < num_partitions; i++) { 462 for (uint i = 0; i < num_partitions; i++) {
546 num_values += ((modes[i] >> 2) + 1) << 1; 463 num_values += ((modes[i] >> 2) + 1) << 1;
@@ -549,7 +466,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
549 // TODO(ameerj): profile with binary search 466 // TODO(ameerj): profile with binary search
550 int range = 0; 467 int range = 0;
551 while (++range < encoding_values.length()) { 468 while (++range < encoding_values.length()) {
552 uint bit_length = GetBitLength(num_values, range); 469 const uint bit_length = GetBitLength(num_values, range);
553 if (bit_length > color_data_bits) { 470 if (bit_length > color_data_bits) {
554 break; 471 break;
555 } 472 }
@@ -560,48 +477,49 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
560 if (out_index >= num_values) { 477 if (out_index >= num_values) {
561 break; 478 break;
562 } 479 }
563 EncodingData val = result_vector[itr]; 480 const EncodingData val = GetEncodingFromVector(itr);
564 uint bitlen = val.num_bits; 481 const uint encoding = Encoding(val);
565 uint bitval = val.bit_value; 482 const uint bitlen = NumBits(val);
483 const uint bitval = BitValue(val);
566 uint A = 0, B = 0, C = 0, D = 0; 484 uint A = 0, B = 0, C = 0, D = 0;
567 A = ReplicateBitTo9((bitval & 1)); 485 A = ReplicateBitTo9((bitval & 1));
568 switch (val.encoding) { 486 switch (encoding) {
569 case JUST_BITS: 487 case JUST_BITS:
570 color_values[out_index++] = FastReplicateTo8(bitval, bitlen); 488 color_values[++out_index] = FastReplicateTo8(bitval, bitlen);
571 break; 489 break;
572 case TRIT: { 490 case TRIT: {
573 D = val.quint_trit_value; 491 D = QuintTritValue(val);
574 switch (bitlen) { 492 switch (bitlen) {
575 case 1: 493 case 1:
576 C = 204; 494 C = 204;
577 break; 495 break;
578 case 2: { 496 case 2: {
579 C = 93; 497 C = 93;
580 uint b = (bitval >> 1) & 1; 498 const uint b = (bitval >> 1) & 1;
581 B = (b << 8) | (b << 4) | (b << 2) | (b << 1); 499 B = (b << 8) | (b << 4) | (b << 2) | (b << 1);
582 break; 500 break;
583 } 501 }
584 case 3: { 502 case 3: {
585 C = 44; 503 C = 44;
586 uint cb = (bitval >> 1) & 3; 504 const uint cb = (bitval >> 1) & 3;
587 B = (cb << 7) | (cb << 2) | cb; 505 B = (cb << 7) | (cb << 2) | cb;
588 break; 506 break;
589 } 507 }
590 case 4: { 508 case 4: {
591 C = 22; 509 C = 22;
592 uint dcb = (bitval >> 1) & 7; 510 const uint dcb = (bitval >> 1) & 7;
593 B = (dcb << 6) | dcb; 511 B = (dcb << 6) | dcb;
594 break; 512 break;
595 } 513 }
596 case 5: { 514 case 5: {
597 C = 11; 515 C = 11;
598 uint edcb = (bitval >> 1) & 0xF; 516 const uint edcb = (bitval >> 1) & 0xF;
599 B = (edcb << 5) | (edcb >> 2); 517 B = (edcb << 5) | (edcb >> 2);
600 break; 518 break;
601 } 519 }
602 case 6: { 520 case 6: {
603 C = 5; 521 C = 5;
604 uint fedcb = (bitval >> 1) & 0x1F; 522 const uint fedcb = (bitval >> 1) & 0x1F;
605 B = (fedcb << 4) | (fedcb >> 4); 523 B = (fedcb << 4) | (fedcb >> 4);
606 break; 524 break;
607 } 525 }
@@ -609,32 +527,32 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
609 break; 527 break;
610 } 528 }
611 case QUINT: { 529 case QUINT: {
612 D = val.quint_trit_value; 530 D = QuintTritValue(val);
613 switch (bitlen) { 531 switch (bitlen) {
614 case 1: 532 case 1:
615 C = 113; 533 C = 113;
616 break; 534 break;
617 case 2: { 535 case 2: {
618 C = 54; 536 C = 54;
619 uint b = (bitval >> 1) & 1; 537 const uint b = (bitval >> 1) & 1;
620 B = (b << 8) | (b << 3) | (b << 2); 538 B = (b << 8) | (b << 3) | (b << 2);
621 break; 539 break;
622 } 540 }
623 case 3: { 541 case 3: {
624 C = 26; 542 C = 26;
625 uint cb = (bitval >> 1) & 3; 543 const uint cb = (bitval >> 1) & 3;
626 B = (cb << 7) | (cb << 1) | (cb >> 1); 544 B = (cb << 7) | (cb << 1) | (cb >> 1);
627 break; 545 break;
628 } 546 }
629 case 4: { 547 case 4: {
630 C = 13; 548 C = 13;
631 uint dcb = (bitval >> 1) & 7; 549 const uint dcb = (bitval >> 1) & 7;
632 B = (dcb << 6) | (dcb >> 1); 550 B = (dcb << 6) | (dcb >> 1);
633 break; 551 break;
634 } 552 }
635 case 5: { 553 case 5: {
636 C = 6; 554 C = 6;
637 uint edcb = (bitval >> 1) & 0xF; 555 const uint edcb = (bitval >> 1) & 0xF;
638 B = (edcb << 5) | (edcb >> 3); 556 B = (edcb << 5) | (edcb >> 3);
639 break; 557 break;
640 } 558 }
@@ -642,11 +560,11 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
642 break; 560 break;
643 } 561 }
644 } 562 }
645 if (val.encoding != JUST_BITS) { 563 if (encoding != JUST_BITS) {
646 uint T = (D * C) + B; 564 uint T = (D * C) + B;
647 T ^= A; 565 T ^= A;
648 T = (A & 0x80) | (T >> 2); 566 T = (A & 0x80) | (T >> 2);
649 color_values[out_index++] = T; 567 color_values[++out_index] = T;
650 } 568 }
651 } 569 }
652} 570}
@@ -664,139 +582,136 @@ ivec2 BitTransferSigned(int a, int b) {
664} 582}
665 583
666uvec4 ClampByte(ivec4 color) { 584uvec4 ClampByte(ivec4 color) {
667 for (uint i = 0; i < 4; ++i) { 585 return uvec4(clamp(color, 0, 255));
668 color[i] = (color[i] < 0) ? 0 : ((color[i] > 255) ? 255 : color[i]);
669 }
670 return uvec4(color);
671} 586}
672 587
673ivec4 BlueContract(int a, int r, int g, int b) { 588ivec4 BlueContract(int a, int r, int g, int b) {
674 return ivec4(a, (r + b) >> 1, (g + b) >> 1, b); 589 return ivec4(a, (r + b) >> 1, (g + b) >> 1, b);
675} 590}
676 591
677void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) { 592void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, uint color_values[32],
593 inout uint colvals_index) {
678#define READ_UINT_VALUES(N) \ 594#define READ_UINT_VALUES(N) \
679 uint v[N]; \ 595 uvec4 V[2]; \
680 for (uint i = 0; i < N; i++) { \ 596 for (uint i = 0; i < N; i++) { \
681 v[i] = color_values[colvals_index++]; \ 597 V[i / 4][i % 4] = color_values[++colvals_index]; \
682 } 598 }
683
684#define READ_INT_VALUES(N) \ 599#define READ_INT_VALUES(N) \
685 int v[N]; \ 600 ivec4 V[2]; \
686 for (uint i = 0; i < N; i++) { \ 601 for (uint i = 0; i < N; i++) { \
687 v[i] = int(color_values[colvals_index++]); \ 602 V[i / 4][i % 4] = int(color_values[++colvals_index]); \
688 } 603 }
689 604
690 switch (color_endpoint_mode) { 605 switch (color_endpoint_mode) {
691 case 0: { 606 case 0: {
692 READ_UINT_VALUES(2) 607 READ_UINT_VALUES(2)
693 ep1 = uvec4(0xFF, v[0], v[0], v[0]); 608 ep1 = uvec4(0xFF, V[0].x, V[0].x, V[0].x);
694 ep2 = uvec4(0xFF, v[1], v[1], v[1]); 609 ep2 = uvec4(0xFF, V[0].y, V[0].y, V[0].y);
695 break; 610 break;
696 } 611 }
697 case 1: { 612 case 1: {
698 READ_UINT_VALUES(2) 613 READ_UINT_VALUES(2)
699 uint L0 = (v[0] >> 2) | (v[1] & 0xC0); 614 const uint L0 = (V[0].x >> 2) | (V[0].y & 0xC0);
700 uint L1 = min(L0 + (v[1] & 0x3F), 0xFFU); 615 const uint L1 = min(L0 + (V[0].y & 0x3F), 0xFFU);
701 ep1 = uvec4(0xFF, L0, L0, L0); 616 ep1 = uvec4(0xFF, L0, L0, L0);
702 ep2 = uvec4(0xFF, L1, L1, L1); 617 ep2 = uvec4(0xFF, L1, L1, L1);
703 break; 618 break;
704 } 619 }
705 case 4: { 620 case 4: {
706 READ_UINT_VALUES(4) 621 READ_UINT_VALUES(4)
707 ep1 = uvec4(v[2], v[0], v[0], v[0]); 622 ep1 = uvec4(V[0].z, V[0].x, V[0].x, V[0].x);
708 ep2 = uvec4(v[3], v[1], v[1], v[1]); 623 ep2 = uvec4(V[0].w, V[0].y, V[0].y, V[0].y);
709 break; 624 break;
710 } 625 }
711 case 5: { 626 case 5: {
712 READ_INT_VALUES(4) 627 READ_INT_VALUES(4)
713 ivec2 transferred = BitTransferSigned(v[1], v[0]); 628 ivec2 transferred = BitTransferSigned(V[0].y, V[0].x);
714 v[1] = transferred.x; 629 V[0].y = transferred.x;
715 v[0] = transferred.y; 630 V[0].x = transferred.y;
716 transferred = BitTransferSigned(v[3], v[2]); 631 transferred = BitTransferSigned(V[0].w, V[0].z);
717 v[3] = transferred.x; 632 V[0].w = transferred.x;
718 v[2] = transferred.y; 633 V[0].z = transferred.y;
719 ep1 = ClampByte(ivec4(v[2], v[0], v[0], v[0])); 634 ep1 = ClampByte(ivec4(V[0].z, V[0].x, V[0].x, V[0].x));
720 ep2 = ClampByte(ivec4(v[2] + v[3], v[0] + v[1], v[0] + v[1], v[0] + v[1])); 635 ep2 = ClampByte(ivec4(V[0].z + V[0].w, V[0].x + V[0].y, V[0].x + V[0].y, V[0].x + V[0].y));
721 break; 636 break;
722 } 637 }
723 case 6: { 638 case 6: {
724 READ_UINT_VALUES(4) 639 READ_UINT_VALUES(4)
725 ep1 = uvec4(0xFF, (v[0] * v[3]) >> 8, (v[1] * v[3]) >> 8, (v[2] * v[3]) >> 8); 640 ep1 = uvec4(0xFF, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8);
726 ep2 = uvec4(0xFF, v[0], v[1], v[2]); 641 ep2 = uvec4(0xFF, V[0].x, V[0].y, V[0].z);
727 break; 642 break;
728 } 643 }
729 case 8: { 644 case 8: {
730 READ_UINT_VALUES(6) 645 READ_UINT_VALUES(6)
731 if ((v[1] + v[3] + v[5]) >= (v[0] + v[2] + v[4])) { 646 if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) {
732 ep1 = uvec4(0xFF, v[0], v[2], v[4]); 647 ep1 = uvec4(0xFF, V[0].x, V[0].z, V[1].x);
733 ep2 = uvec4(0xFF, v[1], v[3], v[5]); 648 ep2 = uvec4(0xFF, V[0].y, V[0].w, V[1].y);
734 } else { 649 } else {
735 ep1 = uvec4(BlueContract(0xFF, int(v[1]), int(v[3]), int(v[5]))); 650 ep1 = uvec4(BlueContract(0xFF, int(V[0].y), int(V[0].w), int(V[1].y)));
736 ep2 = uvec4(BlueContract(0xFF, int(v[0]), int(v[2]), int(v[4]))); 651 ep2 = uvec4(BlueContract(0xFF, int(V[0].x), int(V[0].z), int(V[1].x)));
737 } 652 }
738 break; 653 break;
739 } 654 }
740 case 9: { 655 case 9: {
741 READ_INT_VALUES(6) 656 READ_INT_VALUES(6)
742 ivec2 transferred = BitTransferSigned(v[1], v[0]); 657 ivec2 transferred = BitTransferSigned(V[0].y, V[0].x);
743 v[1] = transferred.x; 658 V[0].y = transferred.x;
744 v[0] = transferred.y; 659 V[0].x = transferred.y;
745 transferred = BitTransferSigned(v[3], v[2]); 660 transferred = BitTransferSigned(V[0].w, V[0].z);
746 v[3] = transferred.x; 661 V[0].w = transferred.x;
747 v[2] = transferred.y; 662 V[0].z = transferred.y;
748 transferred = BitTransferSigned(v[5], v[4]); 663 transferred = BitTransferSigned(V[1].y, V[1].x);
749 v[5] = transferred.x; 664 V[1].y = transferred.x;
750 v[4] = transferred.y; 665 V[1].x = transferred.y;
751 if ((v[1] + v[3] + v[5]) >= 0) { 666 if ((V[0].y + V[0].w + V[1].y) >= 0) {
752 ep1 = ClampByte(ivec4(0xFF, v[0], v[2], v[4])); 667 ep1 = ClampByte(ivec4(0xFF, V[0].x, V[0].z, V[1].x));
753 ep2 = ClampByte(ivec4(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5])); 668 ep2 = ClampByte(ivec4(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y));
754 } else { 669 } else {
755 ep1 = ClampByte(BlueContract(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5])); 670 ep1 = ClampByte(BlueContract(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y));
756 ep2 = ClampByte(BlueContract(0xFF, v[0], v[2], v[4])); 671 ep2 = ClampByte(BlueContract(0xFF, V[0].x, V[0].z, V[1].x));
757 } 672 }
758 break; 673 break;
759 } 674 }
760 case 10: { 675 case 10: {
761 READ_UINT_VALUES(6) 676 READ_UINT_VALUES(6)
762 ep1 = uvec4(v[4], (v[0] * v[3]) >> 8, (v[1] * v[3]) >> 8, (v[2] * v[3]) >> 8); 677 ep1 = uvec4(V[1].x, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8);
763 ep2 = uvec4(v[5], v[0], v[1], v[2]); 678 ep2 = uvec4(V[1].y, V[0].x, V[0].y, V[0].z);
764 break; 679 break;
765 } 680 }
766 case 12: { 681 case 12: {
767 READ_UINT_VALUES(8) 682 READ_UINT_VALUES(8)
768 if ((v[1] + v[3] + v[5]) >= (v[0] + v[2] + v[4])) { 683 if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) {
769 ep1 = uvec4(v[6], v[0], v[2], v[4]); 684 ep1 = uvec4(V[1].z, V[0].x, V[0].z, V[1].x);
770 ep2 = uvec4(v[7], v[1], v[3], v[5]); 685 ep2 = uvec4(V[1].w, V[0].y, V[0].w, V[1].y);
771 } else { 686 } else {
772 ep1 = uvec4(BlueContract(int(v[7]), int(v[1]), int(v[3]), int(v[5]))); 687 ep1 = uvec4(BlueContract(int(V[1].w), int(V[0].y), int(V[0].w), int(V[1].y)));
773 ep2 = uvec4(BlueContract(int(v[6]), int(v[0]), int(v[2]), int(v[4]))); 688 ep2 = uvec4(BlueContract(int(V[1].z), int(V[0].x), int(V[0].z), int(V[1].x)));
774 } 689 }
775 break; 690 break;
776 } 691 }
777 case 13: { 692 case 13: {
778 READ_INT_VALUES(8) 693 READ_INT_VALUES(8)
779 ivec2 transferred = BitTransferSigned(v[1], v[0]); 694 ivec2 transferred = BitTransferSigned(V[0].y, V[0].x);
780 v[1] = transferred.x; 695 V[0].y = transferred.x;
781 v[0] = transferred.y; 696 V[0].x = transferred.y;
782 transferred = BitTransferSigned(v[3], v[2]); 697 transferred = BitTransferSigned(V[0].w, V[0].z);
783 v[3] = transferred.x; 698 V[0].w = transferred.x;
784 v[2] = transferred.y; 699 V[0].z = transferred.y;
785 700
786 transferred = BitTransferSigned(v[5], v[4]); 701 transferred = BitTransferSigned(V[1].y, V[1].x);
787 v[5] = transferred.x; 702 V[1].y = transferred.x;
788 v[4] = transferred.y; 703 V[1].x = transferred.y;
789 704
790 transferred = BitTransferSigned(v[7], v[6]); 705 transferred = BitTransferSigned(V[1].w, V[1].z);
791 v[7] = transferred.x; 706 V[1].w = transferred.x;
792 v[6] = transferred.y; 707 V[1].z = transferred.y;
793 708
794 if ((v[1] + v[3] + v[5]) >= 0) { 709 if ((V[0].y + V[0].w + V[1].y) >= 0) {
795 ep1 = ClampByte(ivec4(v[6], v[0], v[2], v[4])); 710 ep1 = ClampByte(ivec4(V[1].z, V[0].x, V[0].z, V[1].x));
796 ep2 = ClampByte(ivec4(v[7] + v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5])); 711 ep2 = ClampByte(ivec4(V[1].w + V[1].z, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y));
797 } else { 712 } else {
798 ep1 = ClampByte(BlueContract(v[6] + v[7], v[0] + v[1], v[2] + v[3], v[4] + v[5])); 713 ep1 = ClampByte(BlueContract(V[1].z + V[1].w, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y));
799 ep2 = ClampByte(BlueContract(v[6], v[0], v[2], v[4])); 714 ep2 = ClampByte(BlueContract(V[1].z, V[0].x, V[0].z, V[1].x));
800 } 715 }
801 break; 716 break;
802 } 717 }
@@ -812,36 +727,34 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) {
812} 727}
813 728
814uint UnquantizeTexelWeight(EncodingData val) { 729uint UnquantizeTexelWeight(EncodingData val) {
815 uint bitval = val.bit_value; 730 const uint encoding = Encoding(val);
816 uint bitlen = val.num_bits; 731 const uint bitlen = NumBits(val);
817 uint A = ReplicateBitTo7((bitval & 1)); 732 const uint bitval = BitValue(val);
733 const uint A = ReplicateBitTo7((bitval & 1));
818 uint B = 0, C = 0, D = 0; 734 uint B = 0, C = 0, D = 0;
819 uint result = 0; 735 uint result = 0;
820 switch (val.encoding) { 736 const uint bitlen_0_results[5] = {0, 16, 32, 48, 64};
737 switch (encoding) {
821 case JUST_BITS: 738 case JUST_BITS:
822 result = FastReplicateTo6(bitval, bitlen); 739 return FastReplicateTo6(bitval, bitlen);
823 break;
824 case TRIT: { 740 case TRIT: {
825 D = val.quint_trit_value; 741 D = QuintTritValue(val);
826 switch (bitlen) { 742 switch (bitlen) {
827 case 0: { 743 case 0:
828 uint results[3] = {0, 32, 63}; 744 return bitlen_0_results[D * 2];
829 result = results[D];
830 break;
831 }
832 case 1: { 745 case 1: {
833 C = 50; 746 C = 50;
834 break; 747 break;
835 } 748 }
836 case 2: { 749 case 2: {
837 C = 23; 750 C = 23;
838 uint b = (bitval >> 1) & 1; 751 const uint b = (bitval >> 1) & 1;
839 B = (b << 6) | (b << 2) | b; 752 B = (b << 6) | (b << 2) | b;
840 break; 753 break;
841 } 754 }
842 case 3: { 755 case 3: {
843 C = 11; 756 C = 11;
844 uint cb = (bitval >> 1) & 3; 757 const uint cb = (bitval >> 1) & 3;
845 B = (cb << 5) | cb; 758 B = (cb << 5) | cb;
846 break; 759 break;
847 } 760 }
@@ -851,20 +764,17 @@ uint UnquantizeTexelWeight(EncodingData val) {
851 break; 764 break;
852 } 765 }
853 case QUINT: { 766 case QUINT: {
854 D = val.quint_trit_value; 767 D = QuintTritValue(val);
855 switch (bitlen) { 768 switch (bitlen) {
856 case 0: { 769 case 0:
857 uint results[5] = {0, 16, 32, 47, 63}; 770 return bitlen_0_results[D];
858 result = results[D];
859 break;
860 }
861 case 1: { 771 case 1: {
862 C = 28; 772 C = 28;
863 break; 773 break;
864 } 774 }
865 case 2: { 775 case 2: {
866 C = 13; 776 C = 13;
867 uint b = (bitval >> 1) & 1; 777 const uint b = (bitval >> 1) & 1;
868 B = (b << 6) | (b << 1); 778 B = (b << 6) | (b << 1);
869 break; 779 break;
870 } 780 }
@@ -872,7 +782,7 @@ uint UnquantizeTexelWeight(EncodingData val) {
872 break; 782 break;
873 } 783 }
874 } 784 }
875 if (val.encoding != JUST_BITS && bitlen > 0) { 785 if (encoding != JUST_BITS && bitlen > 0) {
876 result = D * C + B; 786 result = D * C + B;
877 result ^= A; 787 result ^= A;
878 result = (A & 0x20) | (result >> 2); 788 result = (A & 0x20) | (result >> 2);
@@ -883,61 +793,77 @@ uint UnquantizeTexelWeight(EncodingData val) {
883 return result; 793 return result;
884} 794}
885 795
886void UnquantizeTexelWeights(bool dual_plane, uvec2 size) { 796void UnquantizeTexelWeights(uvec2 size, bool is_dual_plane) {
887 uint weight_idx = 0; 797 const uint num_planes = is_dual_plane ? 2 : 1;
888 uint unquantized[2][144]; 798 const uint area = size.x * size.y;
889 uint area = size.x * size.y; 799 const uint loop_count = min(result_index, area * num_planes);
890 for (uint itr = 0; itr < texel_vector_index; itr++) { 800 for (uint itr = 0; itr < loop_count; ++itr) {
891 unquantized[0][weight_idx] = UnquantizeTexelWeight(texel_vector[itr]); 801 result_vector[itr] =
892 if (dual_plane) { 802 UnquantizeTexelWeight(GetEncodingFromVector(itr));
893 ++itr;
894 unquantized[1][weight_idx] = UnquantizeTexelWeight(texel_vector[itr]);
895 if (itr == texel_vector_index) {
896 break;
897 }
898 }
899 if (++weight_idx >= (area))
900 break;
901 } 803 }
804}
805
806uint GetUnquantizedTexelWieght(uint offset_base, uint plane, bool is_dual_plane) {
807 const uint offset = is_dual_plane ? 2 * offset_base + plane : offset_base;
808 return result_vector[offset];
809}
902 810
811uvec4 GetUnquantizedWeightVector(uint t, uint s, uvec2 size, uint plane_index, bool is_dual_plane) {
903 const uint Ds = uint((block_dims.x * 0.5f + 1024) / (block_dims.x - 1)); 812 const uint Ds = uint((block_dims.x * 0.5f + 1024) / (block_dims.x - 1));
904 const uint Dt = uint((block_dims.y * 0.5f + 1024) / (block_dims.y - 1)); 813 const uint Dt = uint((block_dims.y * 0.5f + 1024) / (block_dims.y - 1));
905 const uint k_plane_scale = dual_plane ? 2 : 1; 814 const uint area = size.x * size.y;
906 for (uint plane = 0; plane < k_plane_scale; plane++) { 815
907 for (uint t = 0; t < block_dims.y; t++) { 816 const uint cs = Ds * s;
908 for (uint s = 0; s < block_dims.x; s++) { 817 const uint ct = Dt * t;
909 uint cs = Ds * s; 818 const uint gs = (cs * (size.x - 1) + 32) >> 6;
910 uint ct = Dt * t; 819 const uint gt = (ct * (size.y - 1) + 32) >> 6;
911 uint gs = (cs * (size.x - 1) + 32) >> 6; 820 const uint js = gs >> 4;
912 uint gt = (ct * (size.y - 1) + 32) >> 6; 821 const uint fs = gs & 0xF;
913 uint js = gs >> 4; 822 const uint jt = gt >> 4;
914 uint fs = gs & 0xF; 823 const uint ft = gt & 0x0F;
915 uint jt = gt >> 4; 824 const uint w11 = (fs * ft + 8) >> 4;
916 uint ft = gt & 0x0F; 825 const uint w10 = ft - w11;
917 uint w11 = (fs * ft + 8) >> 4; 826 const uint w01 = fs - w11;
918 uint w10 = ft - w11; 827 const uint w00 = 16 - fs - ft + w11;
919 uint w01 = fs - w11; 828 const uvec4 w = uvec4(w00, w01, w10, w11);
920 uint w00 = 16 - fs - ft + w11; 829 const uint v0 = jt * size.x + js;
921 uvec4 w = uvec4(w00, w01, w10, w11); 830
922 uint v0 = jt * size.x + js; 831 uvec4 p0 = uvec4(0);
923 832 uvec4 p1 = uvec4(0);
924 uvec4 p = uvec4(0); 833
925 if (v0 < area) { 834 if (v0 < area) {
926 p.x = unquantized[plane][v0]; 835 const uint offset_base = v0;
927 } 836 p0.x = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane);
928 if ((v0 + 1) < (area)) { 837 p1.x = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane);
929 p.y = unquantized[plane][v0 + 1]; 838 }
930 } 839 if ((v0 + 1) < (area)) {
931 if ((v0 + size.x) < (area)) { 840 const uint offset_base = v0 + 1;
932 p.z = unquantized[plane][(v0 + size.x)]; 841 p0.y = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane);
933 } 842 p1.y = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane);
934 if ((v0 + size.x + 1) < (area)) { 843 }
935 p.w = unquantized[plane][(v0 + size.x + 1)]; 844 if ((v0 + size.x) < (area)) {
936 } 845 const uint offset_base = v0 + size.x;
937 unquantized_texel_weights[plane][t * block_dims.x + s] = (uint(dot(p, w)) + 8) >> 4; 846 p0.z = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane);
938 } 847 p1.z = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane);
848 }
849 if ((v0 + size.x + 1) < (area)) {
850 const uint offset_base = v0 + size.x + 1;
851 p0.w = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane);
852 p1.w = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane);
853 }
854
855 const uint primary_weight = (uint(dot(p0, w)) + 8) >> 4;
856
857 uvec4 weight_vec = uvec4(primary_weight);
858
859 if (is_dual_plane) {
860 const uint secondary_weight = (uint(dot(p1, w)) + 8) >> 4;
861 for (uint c = 0; c < 4; c++) {
862 const bool is_secondary = ((plane_index + 1u) & 3u) == c;
863 weight_vec[c] = is_secondary ? secondary_weight : primary_weight;
939 } 864 }
940 } 865 }
866 return weight_vec;
941} 867}
942 868
943int FindLayout(uint mode) { 869int FindLayout(uint mode) {
@@ -971,80 +897,96 @@ int FindLayout(uint mode) {
971 return 5; 897 return 5;
972} 898}
973 899
974TexelWeightParams DecodeBlockInfo() { 900
975 TexelWeightParams params = TexelWeightParams(uvec2(0), 0, false, false, false, false); 901void FillError(ivec3 coord) {
976 uint mode = StreamBits(11); 902 for (uint j = 0; j < block_dims.y; j++) {
903 for (uint i = 0; i < block_dims.x; i++) {
904 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
905 }
906 }
907}
908
909void FillVoidExtentLDR(ivec3 coord) {
910 SkipBits(52);
911 const uint r_u = StreamBits(16);
912 const uint g_u = StreamBits(16);
913 const uint b_u = StreamBits(16);
914 const uint a_u = StreamBits(16);
915 const float a = float(a_u) / 65535.0f;
916 const float r = float(r_u) / 65535.0f;
917 const float g = float(g_u) / 65535.0f;
918 const float b = float(b_u) / 65535.0f;
919 for (uint j = 0; j < block_dims.y; j++) {
920 for (uint i = 0; i < block_dims.x; i++) {
921 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(r, g, b, a));
922 }
923 }
924}
925
926bool IsError(uint mode) {
977 if ((mode & 0x1ff) == 0x1fc) { 927 if ((mode & 0x1ff) == 0x1fc) {
978 if ((mode & 0x200) != 0) { 928 if ((mode & 0x200) != 0) {
979 params.void_extent_hdr = true; 929 // params.void_extent_hdr = true;
980 } else { 930 return true;
981 params.void_extent_ldr = true;
982 } 931 }
983 if ((mode & 0x400) == 0 || StreamBits(1) == 0) { 932 if ((mode & 0x400) == 0 || StreamBits(1) == 0) {
984 params.error_state = true; 933 return true;
985 } 934 }
986 return params; 935 return false;
987 } 936 }
988 if ((mode & 0xf) == 0) { 937 if ((mode & 0xf) == 0) {
989 params.error_state = true; 938 return true;
990 return params;
991 } 939 }
992 if ((mode & 3) == 0 && (mode & 0x1c0) == 0x1c0) { 940 if ((mode & 3) == 0 && (mode & 0x1c0) == 0x1c0) {
993 params.error_state = true; 941 return true;
994 return params;
995 } 942 }
943 return false;
944}
945
946uvec2 DecodeBlockSize(uint mode) {
996 uint A, B; 947 uint A, B;
997 uint mode_layout = FindLayout(mode); 948 switch (FindLayout(mode)) {
998 switch (mode_layout) {
999 case 0: 949 case 0:
1000 A = (mode >> 5) & 0x3; 950 A = (mode >> 5) & 0x3;
1001 B = (mode >> 7) & 0x3; 951 B = (mode >> 7) & 0x3;
1002 params.size = uvec2(B + 4, A + 2); 952 return uvec2(B + 4, A + 2);
1003 break;
1004 case 1: 953 case 1:
1005 A = (mode >> 5) & 0x3; 954 A = (mode >> 5) & 0x3;
1006 B = (mode >> 7) & 0x3; 955 B = (mode >> 7) & 0x3;
1007 params.size = uvec2(B + 8, A + 2); 956 return uvec2(B + 8, A + 2);
1008 break;
1009 case 2: 957 case 2:
1010 A = (mode >> 5) & 0x3; 958 A = (mode >> 5) & 0x3;
1011 B = (mode >> 7) & 0x3; 959 B = (mode >> 7) & 0x3;
1012 params.size = uvec2(A + 2, B + 8); 960 return uvec2(A + 2, B + 8);
1013 break;
1014 case 3: 961 case 3:
1015 A = (mode >> 5) & 0x3; 962 A = (mode >> 5) & 0x3;
1016 B = (mode >> 7) & 0x1; 963 B = (mode >> 7) & 0x1;
1017 params.size = uvec2(A + 2, B + 6); 964 return uvec2(A + 2, B + 6);
1018 break;
1019 case 4: 965 case 4:
1020 A = (mode >> 5) & 0x3; 966 A = (mode >> 5) & 0x3;
1021 B = (mode >> 7) & 0x1; 967 B = (mode >> 7) & 0x1;
1022 params.size = uvec2(B + 2, A + 2); 968 return uvec2(B + 2, A + 2);
1023 break;
1024 case 5: 969 case 5:
1025 A = (mode >> 5) & 0x3; 970 A = (mode >> 5) & 0x3;
1026 params.size = uvec2(12, A + 2); 971 return uvec2(12, A + 2);
1027 break;
1028 case 6: 972 case 6:
1029 A = (mode >> 5) & 0x3; 973 A = (mode >> 5) & 0x3;
1030 params.size = uvec2(A + 2, 12); 974 return uvec2(A + 2, 12);
1031 break;
1032 case 7: 975 case 7:
1033 params.size = uvec2(6, 10); 976 return uvec2(6, 10);
1034 break;
1035 case 8: 977 case 8:
1036 params.size = uvec2(10, 6); 978 return uvec2(10, 6);
1037 break;
1038 case 9: 979 case 9:
1039 A = (mode >> 5) & 0x3; 980 A = (mode >> 5) & 0x3;
1040 B = (mode >> 9) & 0x3; 981 B = (mode >> 9) & 0x3;
1041 params.size = uvec2(A + 6, B + 6); 982 return uvec2(A + 6, B + 6);
1042 break;
1043 default: 983 default:
1044 params.error_state = true; 984 return uvec2(0);
1045 break;
1046 } 985 }
1047 params.dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0); 986}
987
988uint DecodeMaxWeight(uint mode) {
989 const uint mode_layout = FindLayout(mode);
1048 uint weight_index = (mode & 0x10) != 0 ? 1 : 0; 990 uint weight_index = (mode & 0x10) != 0 ? 1 : 0;
1049 if (mode_layout < 5) { 991 if (mode_layout < 5) {
1050 weight_index |= (mode & 0x3) << 1; 992 weight_index |= (mode & 0x3) << 1;
@@ -1053,64 +995,34 @@ TexelWeightParams DecodeBlockInfo() {
1053 } 995 }
1054 weight_index -= 2; 996 weight_index -= 2;
1055 if ((mode_layout != 9) && ((mode & 0x200) != 0)) { 997 if ((mode_layout != 9) && ((mode & 0x200) != 0)) {
1056 const int max_weights[6] = int[6](7, 8, 9, 10, 11, 12); 998 weight_index += 6;
1057 params.max_weight = max_weights[weight_index];
1058 } else {
1059 const int max_weights[6] = int[6](1, 2, 3, 4, 5, 6);
1060 params.max_weight = max_weights[weight_index];
1061 }
1062 return params;
1063}
1064
1065void FillError(ivec3 coord) {
1066 for (uint j = 0; j < block_dims.y; j++) {
1067 for (uint i = 0; i < block_dims.x; i++) {
1068 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
1069 }
1070 }
1071}
1072
1073void FillVoidExtentLDR(ivec3 coord) {
1074 StreamBits(52);
1075 uint r_u = StreamBits(16);
1076 uint g_u = StreamBits(16);
1077 uint b_u = StreamBits(16);
1078 uint a_u = StreamBits(16);
1079 float a = float(a_u) / 65535.0f;
1080 float r = float(r_u) / 65535.0f;
1081 float g = float(g_u) / 65535.0f;
1082 float b = float(b_u) / 65535.0f;
1083 for (uint j = 0; j < block_dims.y; j++) {
1084 for (uint i = 0; i < block_dims.x; i++) {
1085 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(r, g, b, a));
1086 }
1087 } 999 }
1000 return weight_index + 1;
1088} 1001}
1089 1002
1090void DecompressBlock(ivec3 coord) { 1003void DecompressBlock(ivec3 coord) {
1091 TexelWeightParams params = DecodeBlockInfo(); 1004 uint mode = StreamBits(11);
1092 if (params.error_state) { 1005 if (IsError(mode)) {
1093 FillError(coord);
1094 return;
1095 }
1096 if (params.void_extent_hdr) {
1097 FillError(coord); 1006 FillError(coord);
1098 return; 1007 return;
1099 } 1008 }
1100 if (params.void_extent_ldr) { 1009 if ((mode & 0x1ff) == 0x1fc) {
1010 // params.void_extent_ldr = true;
1101 FillVoidExtentLDR(coord); 1011 FillVoidExtentLDR(coord);
1102 return; 1012 return;
1103 } 1013 }
1104 if ((params.size.x > block_dims.x) || (params.size.y > block_dims.y)) { 1014 const uvec2 size_params = DecodeBlockSize(mode);
1015 if ((size_params.x > block_dims.x) || (size_params.y > block_dims.y)) {
1105 FillError(coord); 1016 FillError(coord);
1106 return; 1017 return;
1107 } 1018 }
1108 uint num_partitions = StreamBits(2) + 1; 1019 const uint num_partitions = StreamBits(2) + 1;
1109 if (num_partitions > 4 || (num_partitions == 4 && params.dual_plane)) { 1020 const uint mode_layout = FindLayout(mode);
1021 const bool dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0);
1022 if (num_partitions > 4 || (num_partitions == 4 && dual_plane)) {
1110 FillError(coord); 1023 FillError(coord);
1111 return; 1024 return;
1112 } 1025 }
1113 int plane_index = -1;
1114 uint partition_index = 1; 1026 uint partition_index = 1;
1115 uvec4 color_endpoint_mode = uvec4(0); 1027 uvec4 color_endpoint_mode = uvec4(0);
1116 uint ced_pointer = 0; 1028 uint ced_pointer = 0;
@@ -1122,8 +1034,9 @@ void DecompressBlock(ivec3 coord) {
1122 partition_index = StreamBits(10); 1034 partition_index = StreamBits(10);
1123 base_cem = StreamBits(6); 1035 base_cem = StreamBits(6);
1124 } 1036 }
1125 uint base_mode = base_cem & 3; 1037 const uint base_mode = base_cem & 3;
1126 uint weight_bits = GetPackedBitSize(params.size, params.dual_plane, params.max_weight); 1038 const uint max_weight = DecodeMaxWeight(mode);
1039 const uint weight_bits = GetPackedBitSize(size_params, dual_plane, max_weight);
1127 uint remaining_bits = 128 - weight_bits - total_bitsread; 1040 uint remaining_bits = 128 - weight_bits - total_bitsread;
1128 uint extra_cem_bits = 0; 1041 uint extra_cem_bits = 0;
1129 if (base_mode > 0) { 1042 if (base_mode > 0) {
@@ -1142,10 +1055,7 @@ void DecompressBlock(ivec3 coord) {
1142 } 1055 }
1143 } 1056 }
1144 remaining_bits -= extra_cem_bits; 1057 remaining_bits -= extra_cem_bits;
1145 uint plane_selector_bits = 0; 1058 const uint plane_selector_bits = dual_plane ? 2 : 0;
1146 if (params.dual_plane) {
1147 plane_selector_bits = 2;
1148 }
1149 remaining_bits -= plane_selector_bits; 1059 remaining_bits -= plane_selector_bits;
1150 if (remaining_bits > 128) { 1060 if (remaining_bits > 128) {
1151 // Bad data, more remaining bits than 4 bytes 1061 // Bad data, more remaining bits than 4 bytes
@@ -1153,17 +1063,17 @@ void DecompressBlock(ivec3 coord) {
1153 return; 1063 return;
1154 } 1064 }
1155 // Read color data... 1065 // Read color data...
1156 uint color_data_bits = remaining_bits; 1066 const uint color_data_bits = remaining_bits;
1157 while (remaining_bits > 0) { 1067 while (remaining_bits > 0) {
1158 int nb = int(min(remaining_bits, 32U)); 1068 const int nb = int(min(remaining_bits, 32U));
1159 uint b = StreamBits(nb); 1069 const uint b = StreamBits(nb);
1160 color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb)); 1070 color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb));
1161 ++ced_pointer; 1071 ++ced_pointer;
1162 remaining_bits -= nb; 1072 remaining_bits -= nb;
1163 } 1073 }
1164 plane_index = int(StreamBits(plane_selector_bits)); 1074 const uint plane_index = uint(StreamBits(plane_selector_bits));
1165 if (base_mode > 0) { 1075 if (base_mode > 0) {
1166 uint extra_cem = StreamBits(extra_cem_bits); 1076 const uint extra_cem = StreamBits(extra_cem_bits);
1167 uint cem = (extra_cem << 6) | base_cem; 1077 uint cem = (extra_cem << 6) | base_cem;
1168 cem >>= 2; 1078 cem >>= 2;
1169 uvec4 C = uvec4(0); 1079 uvec4 C = uvec4(0);
@@ -1185,70 +1095,80 @@ void DecompressBlock(ivec3 coord) {
1185 color_endpoint_mode[i] |= M[i]; 1095 color_endpoint_mode[i] |= M[i];
1186 } 1096 }
1187 } else if (num_partitions > 1) { 1097 } else if (num_partitions > 1) {
1188 uint cem = base_cem >> 2; 1098 const uint cem = base_cem >> 2;
1189 for (uint i = 0; i < num_partitions; i++) { 1099 for (uint i = 0; i < num_partitions; i++) {
1190 color_endpoint_mode[i] = cem; 1100 color_endpoint_mode[i] = cem;
1191 } 1101 }
1192 } 1102 }
1193 DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits);
1194 1103
1195 uvec4 endpoints[4][2]; 1104 uvec4 endpoints0[4];
1196 for (uint i = 0; i < num_partitions; i++) { 1105 uvec4 endpoints1[4];
1197 ComputeEndpoints(endpoints[i][0], endpoints[i][1], color_endpoint_mode[i]); 1106 {
1107 // This decode phase should at most push 32 elements into the vector
1108 result_vector_max_index = 32;
1109 uint color_values[32];
1110 uint colvals_index = 0;
1111 DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits, color_values);
1112 for (uint i = 0; i < num_partitions; i++) {
1113 ComputeEndpoints(endpoints0[i], endpoints1[i], color_endpoint_mode[i], color_values,
1114 colvals_index);
1115 }
1198 } 1116 }
1117 color_endpoint_data = local_buff;
1118 color_endpoint_data = bitfieldReverse(color_endpoint_data).wzyx;
1119 const uint clear_byte_start = (weight_bits >> 3) + 1;
1199 1120
1200 texel_weight_data = local_buff; 1121 const uint byte_insert = ExtractBits(color_endpoint_data, int(clear_byte_start - 1) * 8, 8) &
1201 texel_weight_data = bitfieldReverse(texel_weight_data).wzyx; 1122 uint(((1 << (weight_bits % 8)) - 1));
1202 uint clear_byte_start = 1123 const uint vec_index = (clear_byte_start - 1) >> 2;
1203 (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) >> 3) + 1; 1124 color_endpoint_data[vec_index] = bitfieldInsert(color_endpoint_data[vec_index], byte_insert,
1204 1125 int((clear_byte_start - 1) % 4) * 8, 8);
1205 uint byte_insert = ExtractBits(texel_weight_data, int(clear_byte_start - 1) * 8, 8) &
1206 uint(
1207 ((1 << (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) % 8)) - 1));
1208 uint vec_index = (clear_byte_start - 1) >> 2;
1209 texel_weight_data[vec_index] =
1210 bitfieldInsert(texel_weight_data[vec_index], byte_insert, int((clear_byte_start - 1) % 4) * 8, 8);
1211 for (uint i = clear_byte_start; i < 16; ++i) { 1126 for (uint i = clear_byte_start; i < 16; ++i) {
1212 uint idx = i >> 2; 1127 const uint idx = i >> 2;
1213 texel_weight_data[idx] = bitfieldInsert(texel_weight_data[idx], 0, int(i % 4) * 8, 8); 1128 color_endpoint_data[idx] = bitfieldInsert(color_endpoint_data[idx], 0, int(i % 4) * 8, 8);
1214 } 1129 }
1215 texel_flag = true; // use texel "vector" and bit stream in integer decoding
1216 DecodeIntegerSequence(params.max_weight, GetNumWeightValues(params.size, params.dual_plane));
1217 1130
1218 UnquantizeTexelWeights(params.dual_plane, params.size); 1131 // Re-init vector variables for next decode phase
1132 result_index = 0;
1133 color_bitsread = 0;
1134 result_limit_reached = false;
1219 1135
1136 // The limit for the Unquantize phase, avoids decoding more data than needed.
1137 result_vector_max_index = size_params.x * size_params.y;
1138 if (dual_plane) {
1139 result_vector_max_index *= 2;
1140 }
1141 DecodeIntegerSequence(max_weight, GetNumWeightValues(size_params, dual_plane));
1142
1143 UnquantizeTexelWeights(size_params, dual_plane);
1220 for (uint j = 0; j < block_dims.y; j++) { 1144 for (uint j = 0; j < block_dims.y; j++) {
1221 for (uint i = 0; i < block_dims.x; i++) { 1145 for (uint i = 0; i < block_dims.x; i++) {
1222 uint local_partition = 0; 1146 uint local_partition = 0;
1223 if (num_partitions > 1) { 1147 if (num_partitions > 1) {
1224 local_partition = Select2DPartition(partition_index, i, j, num_partitions, 1148 local_partition = Select2DPartition(partition_index, i, j, num_partitions);
1225 (block_dims.y * block_dims.x) < 32);
1226 }
1227 vec4 p;
1228 uvec4 C0 = ReplicateByteTo16(endpoints[local_partition][0]);
1229 uvec4 C1 = ReplicateByteTo16(endpoints[local_partition][1]);
1230 uvec4 plane_vec = uvec4(0);
1231 uvec4 weight_vec = uvec4(0);
1232 for (uint c = 0; c < 4; c++) {
1233 if (params.dual_plane && (((plane_index + 1) & 3) == c)) {
1234 plane_vec[c] = 1;
1235 }
1236 weight_vec[c] = unquantized_texel_weights[plane_vec[c]][j * block_dims.x + i];
1237 } 1149 }
1238 vec4 Cf = vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); 1150 const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]);
1239 p = (Cf / 65535.0); 1151 const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]);
1152 const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane);
1153 const vec4 Cf =
1154 vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64);
1155 const vec4 p = (Cf / 65535.0f);
1240 imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar); 1156 imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar);
1241 } 1157 }
1242 } 1158 }
1243} 1159}
1244 1160
1161uint SwizzleOffset(uvec2 pos) {
1162 const uint x = pos.x;
1163 const uint y = pos.y;
1164 return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 +
1165 ((x % 32) / 16) * 32 + (y % 2) * 16 + (x % 16);
1166}
1167
1245void main() { 1168void main() {
1246 uvec3 pos = gl_GlobalInvocationID; 1169 uvec3 pos = gl_GlobalInvocationID;
1247 pos.x <<= BYTES_PER_BLOCK_LOG2; 1170 pos.x <<= BYTES_PER_BLOCK_LOG2;
1248
1249 // Read as soon as possible due to its latency
1250 const uint swizzle = SwizzleOffset(pos.xy); 1171 const uint swizzle = SwizzleOffset(pos.xy);
1251
1252 const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; 1172 const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
1253 1173
1254 uint offset = 0; 1174 uint offset = 0;
@@ -1262,8 +1182,6 @@ void main() {
1262 if (any(greaterThanEqual(coord, imageSize(dest_image)))) { 1182 if (any(greaterThanEqual(coord, imageSize(dest_image)))) {
1263 return; 1183 return;
1264 } 1184 }
1265 current_index = 0;
1266 bitsread = 0;
1267 local_buff = astc_data[offset / 16]; 1185 local_buff = astc_data[offset / 16];
1268 DecompressBlock(coord); 1186 DecompressBlock(coord);
1269} 1187}
diff --git a/src/video_core/host_shaders/vulkan_depthstencil_clear.frag b/src/video_core/host_shaders/vulkan_depthstencil_clear.frag
new file mode 100644
index 000000000..1ac177c7e
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_depthstencil_clear.frag
@@ -0,0 +1,12 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#version 460 core
5
6layout (push_constant) uniform PushConstants {
7 vec4 clear_depth;
8};
9
10void main() {
11 gl_FragDepth = clear_depth.x;
12}
diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp
index 905505ca1..5d0bb9cc4 100644
--- a/src/video_core/macro/macro.cpp
+++ b/src/video_core/macro/macro.cpp
@@ -27,14 +27,24 @@ MICROPROFILE_DEFINE(MacroHLE, "GPU", "Execute macro HLE", MP_RGB(128, 192, 192))
27 27
28namespace Tegra { 28namespace Tegra {
29 29
30static void Dump(u64 hash, std::span<const u32> code) { 30static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) {
31 const auto base_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)}; 31 const auto base_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)};
32 const auto macro_dir{base_dir / "macros"}; 32 const auto macro_dir{base_dir / "macros"};
33 if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) { 33 if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) {
34 LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories"); 34 LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories");
35 return; 35 return;
36 } 36 }
37 const auto name{macro_dir / fmt::format("{:016x}.macro", hash)}; 37 auto name{macro_dir / fmt::format("{:016x}.macro", hash)};
38
39 if (decompiled) {
40 auto new_name{macro_dir / fmt::format("decompiled_{:016x}.macro", hash)};
41 if (Common::FS::Exists(name)) {
42 (void)Common::FS::RenameFile(name, new_name);
43 return;
44 }
45 name = new_name;
46 }
47
38 std::fstream macro_file(name, std::ios::out | std::ios::binary); 48 std::fstream macro_file(name, std::ios::out | std::ios::binary);
39 if (!macro_file) { 49 if (!macro_file) {
40 LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}", 50 LOG_ERROR(Common_Filesystem, "Unable to open or create file at {}",
@@ -90,9 +100,6 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
90 if (!mid_method.has_value()) { 100 if (!mid_method.has_value()) {
91 cache_info.lle_program = Compile(macro_code->second); 101 cache_info.lle_program = Compile(macro_code->second);
92 cache_info.hash = Common::HashValue(macro_code->second); 102 cache_info.hash = Common::HashValue(macro_code->second);
93 if (Settings::values.dump_macros) {
94 Dump(cache_info.hash, macro_code->second);
95 }
96 } else { 103 } else {
97 const auto& macro_cached = uploaded_macro_code[mid_method.value()]; 104 const auto& macro_cached = uploaded_macro_code[mid_method.value()];
98 const auto rebased_method = method - mid_method.value(); 105 const auto rebased_method = method - mid_method.value();
@@ -102,9 +109,6 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
102 code.size() * sizeof(u32)); 109 code.size() * sizeof(u32));
103 cache_info.hash = Common::HashValue(code); 110 cache_info.hash = Common::HashValue(code);
104 cache_info.lle_program = Compile(code); 111 cache_info.lle_program = Compile(code);
105 if (Settings::values.dump_macros) {
106 Dump(cache_info.hash, code);
107 }
108 } 112 }
109 113
110 auto hle_program = hle_macros->GetHLEProgram(cache_info.hash); 114 auto hle_program = hle_macros->GetHLEProgram(cache_info.hash);
@@ -117,6 +121,10 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
117 MICROPROFILE_SCOPE(MacroHLE); 121 MICROPROFILE_SCOPE(MacroHLE);
118 cache_info.hle_program->Execute(parameters, method); 122 cache_info.hle_program->Execute(parameters, method);
119 } 123 }
124
125 if (Settings::values.dump_macros) {
126 Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program);
127 }
120 } 128 }
121} 129}
122 130
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index f822fa856..44a771d65 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -220,7 +220,8 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
220 ASSERT(num_textures <= MAX_TEXTURES); 220 ASSERT(num_textures <= MAX_TEXTURES);
221 ASSERT(num_images <= MAX_IMAGES); 221 ASSERT(num_images <= MAX_IMAGES);
222 222
223 const bool assembly_shaders{assembly_programs[0].handle != 0}; 223 const auto backend = device.GetShaderBackend();
224 const bool assembly_shaders{backend == Settings::ShaderBackend::Glasm};
224 use_storage_buffers = 225 use_storage_buffers =
225 !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks(); 226 !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();
226 writes_global_memory &= !use_storage_buffers; 227 writes_global_memory &= !use_storage_buffers;
@@ -230,7 +231,6 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
230 GenerateTransformFeedbackState(); 231 GenerateTransformFeedbackState();
231 } 232 }
232 const bool in_parallel = thread_worker != nullptr; 233 const bool in_parallel = thread_worker != nullptr;
233 const auto backend = device.GetShaderBackend();
234 auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv), 234 auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv),
235 shader_notify, backend, in_parallel, 235 shader_notify, backend, in_parallel,
236 force_context_flush](ShaderContext::Context*) mutable { 236 force_context_flush](ShaderContext::Context*) mutable {
@@ -559,15 +559,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
559} 559}
560 560
561void GraphicsPipeline::ConfigureTransformFeedbackImpl() const { 561void GraphicsPipeline::ConfigureTransformFeedbackImpl() const {
562 glTransformFeedbackStreamAttribsNV(num_xfb_attribs, xfb_attribs.data(), num_xfb_strides, 562 glTransformFeedbackAttribsNV(num_xfb_attribs, xfb_attribs.data(), GL_SEPARATE_ATTRIBS);
563 xfb_streams.data(), GL_INTERLEAVED_ATTRIBS);
564} 563}
565 564
566void GraphicsPipeline::GenerateTransformFeedbackState() { 565void GraphicsPipeline::GenerateTransformFeedbackState() {
567 // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal 566 // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal
568 // when this is required. 567 // when this is required.
569 GLint* cursor{xfb_attribs.data()}; 568 GLint* cursor{xfb_attribs.data()};
570 GLint* current_stream{xfb_streams.data()};
571 569
572 for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) { 570 for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) {
573 const auto& layout = key.xfb_state.layouts[feedback]; 571 const auto& layout = key.xfb_state.layouts[feedback];
@@ -575,15 +573,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() {
575 if (layout.varying_count == 0) { 573 if (layout.varying_count == 0) {
576 continue; 574 continue;
577 } 575 }
578 *current_stream = static_cast<GLint>(feedback);
579 if (current_stream != xfb_streams.data()) {
580 // When stepping one stream, push the expected token
581 cursor[0] = GL_NEXT_BUFFER_NV;
582 cursor[1] = 0;
583 cursor[2] = 0;
584 cursor += XFB_ENTRY_STRIDE;
585 }
586 ++current_stream;
587 576
588 const auto& locations = key.xfb_state.varyings[feedback]; 577 const auto& locations = key.xfb_state.varyings[feedback];
589 std::optional<u32> current_index; 578 std::optional<u32> current_index;
@@ -619,7 +608,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() {
619 } 608 }
620 } 609 }
621 num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE); 610 num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE);
622 num_xfb_strides = static_cast<GLsizei>(current_stream - xfb_streams.data());
623} 611}
624 612
625void GraphicsPipeline::WaitForBuild() { 613void GraphicsPipeline::WaitForBuild() {
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
index 7b3d7eae8..74fc9cc3d 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
@@ -154,9 +154,7 @@ private:
154 154
155 static constexpr std::size_t XFB_ENTRY_STRIDE = 3; 155 static constexpr std::size_t XFB_ENTRY_STRIDE = 3;
156 GLsizei num_xfb_attribs{}; 156 GLsizei num_xfb_attribs{};
157 GLsizei num_xfb_strides{};
158 std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{}; 157 std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{};
159 std::array<GLint, Maxwell::NumTransformFeedbackBuffers> xfb_streams{};
160 158
161 std::mutex built_mutex; 159 std::mutex built_mutex;
162 std::condition_variable built_condvar; 160 std::condition_variable built_condvar;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index aadd6967c..dd03efecd 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -380,6 +380,17 @@ void RasterizerOpenGL::DispatchCompute() {
380 pipeline->SetEngine(kepler_compute, gpu_memory); 380 pipeline->SetEngine(kepler_compute, gpu_memory);
381 pipeline->Configure(); 381 pipeline->Configure();
382 const auto& qmd{kepler_compute->launch_description}; 382 const auto& qmd{kepler_compute->launch_description};
383 auto indirect_address = kepler_compute->GetIndirectComputeAddress();
384 if (indirect_address) {
385 // DispatchIndirect
386 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
387 const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite;
388 const auto [buffer, offset] =
389 buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op);
390 glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer->Handle());
391 glDispatchComputeIndirect(static_cast<GLintptr>(offset));
392 return;
393 }
383 glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z); 394 glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z);
384 ++num_queued_commands; 395 ++num_queued_commands;
385 has_written_global_memory |= pipeline->WritesGlobalMemory(); 396 has_written_global_memory |= pipeline->WritesGlobalMemory();
@@ -1335,7 +1346,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
1335 } 1346 }
1336 const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); 1347 const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height);
1337 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; 1348 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
1338 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; 1349 const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing
1350 : VideoCommon::ObtainBufferOperation::MarkAsWritten;
1339 const auto [buffer, offset] = 1351 const auto [buffer, offset] =
1340 buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); 1352 buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op);
1341 1353
@@ -1344,8 +1356,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
1344 const std::span copy_span{&copy, 1}; 1356 const std::span copy_span{&copy, 1};
1345 1357
1346 if constexpr (IS_IMAGE_UPLOAD) { 1358 if constexpr (IS_IMAGE_UPLOAD) {
1359 texture_cache.PrepareImage(image_id, true, false);
1347 image->UploadMemory(buffer->Handle(), offset, copy_span); 1360 image->UploadMemory(buffer->Handle(), offset, copy_span);
1348 } else { 1361 } else {
1362 if (offset % BytesPerBlock(image->info.format)) {
1363 return false;
1364 }
1349 texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, 1365 texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span,
1350 buffer_operand.address, buffer_size); 1366 buffer_operand.address, buffer_size);
1351 } 1367 }
diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp
index 544982d18..c437013e6 100644
--- a/src/video_core/renderer_opengl/util_shaders.cpp
+++ b/src/video_core/renderer_opengl/util_shaders.cpp
@@ -68,6 +68,7 @@ void UtilShaders::ASTCDecode(Image& image, const StagingBufferMap& map,
68 std::span<const VideoCommon::SwizzleParameters> swizzles) { 68 std::span<const VideoCommon::SwizzleParameters> swizzles) {
69 static constexpr GLuint BINDING_INPUT_BUFFER = 0; 69 static constexpr GLuint BINDING_INPUT_BUFFER = 0;
70 static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; 70 static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
71 program_manager.LocalMemoryWarmup();
71 72
72 const Extent2D tile_size{ 73 const Extent2D tile_size{
73 .width = VideoCore::Surface::DefaultBlockWidth(image.info.format), 74 .width = VideoCore::Surface::DefaultBlockWidth(image.info.format),
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index f74ae972e..1032c9d12 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -16,6 +16,7 @@
16#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" 16#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
17#include "video_core/host_shaders/vulkan_color_clear_frag_spv.h" 17#include "video_core/host_shaders/vulkan_color_clear_frag_spv.h"
18#include "video_core/host_shaders/vulkan_color_clear_vert_spv.h" 18#include "video_core/host_shaders/vulkan_color_clear_vert_spv.h"
19#include "video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h"
19#include "video_core/renderer_vulkan/blit_image.h" 20#include "video_core/renderer_vulkan/blit_image.h"
20#include "video_core/renderer_vulkan/maxwell_to_vk.h" 21#include "video_core/renderer_vulkan/maxwell_to_vk.h"
21#include "video_core/renderer_vulkan/vk_scheduler.h" 22#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -428,6 +429,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
428 blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)), 429 blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)),
429 clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), 430 clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)),
430 clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), 431 clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)),
432 clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)),
431 convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), 433 convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
432 convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), 434 convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
433 convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), 435 convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
@@ -593,6 +595,28 @@ void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_ma
593 scheduler.InvalidateState(); 595 scheduler.InvalidateState();
594} 596}
595 597
598void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear,
599 f32 clear_depth, u8 stencil_mask, u32 stencil_ref,
600 u32 stencil_compare_mask, const Region2D& dst_region) {
601 const BlitDepthStencilPipelineKey key{
602 .renderpass = dst_framebuffer->RenderPass(),
603 .depth_clear = depth_clear,
604 .stencil_mask = stencil_mask,
605 .stencil_compare_mask = stencil_compare_mask,
606 .stencil_ref = stencil_ref,
607 };
608 const VkPipeline pipeline = FindOrEmplaceClearStencilPipeline(key);
609 const VkPipelineLayout layout = *clear_color_pipeline_layout;
610 scheduler.RequestRenderpass(dst_framebuffer);
611 scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
612 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
613 BindBlitState(cmdbuf, dst_region);
614 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
615 cmdbuf.Draw(3, 1, 0, 0);
616 });
617 scheduler.InvalidateState();
618}
619
596void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, 620void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
597 const ImageView& src_image_view) { 621 const ImageView& src_image_view) {
598 const VkPipelineLayout layout = *one_texture_pipeline_layout; 622 const VkPipelineLayout layout = *one_texture_pipeline_layout;
@@ -820,6 +844,61 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
820 return *clear_color_pipelines.back(); 844 return *clear_color_pipelines.back();
821} 845}
822 846
847VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
848 const BlitDepthStencilPipelineKey& key) {
849 const auto it = std::ranges::find(clear_stencil_keys, key);
850 if (it != clear_stencil_keys.end()) {
851 return *clear_stencil_pipelines[std::distance(clear_stencil_keys.begin(), it)];
852 }
853 clear_stencil_keys.push_back(key);
854 const std::array stages = MakeStages(*clear_color_vert, *clear_stencil_frag);
855 const auto stencil = VkStencilOpState{
856 .failOp = VK_STENCIL_OP_KEEP,
857 .passOp = VK_STENCIL_OP_REPLACE,
858 .depthFailOp = VK_STENCIL_OP_KEEP,
859 .compareOp = VK_COMPARE_OP_ALWAYS,
860 .compareMask = key.stencil_compare_mask,
861 .writeMask = key.stencil_mask,
862 .reference = key.stencil_ref,
863 };
864 const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
865 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
866 .pNext = nullptr,
867 .flags = 0,
868 .depthTestEnable = VK_FALSE,
869 .depthWriteEnable = key.depth_clear,
870 .depthCompareOp = VK_COMPARE_OP_ALWAYS,
871 .depthBoundsTestEnable = VK_FALSE,
872 .stencilTestEnable = VK_TRUE,
873 .front = stencil,
874 .back = stencil,
875 .minDepthBounds = 0.0f,
876 .maxDepthBounds = 0.0f,
877 };
878 clear_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
879 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
880 .pNext = nullptr,
881 .flags = 0,
882 .stageCount = static_cast<u32>(stages.size()),
883 .pStages = stages.data(),
884 .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
885 .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
886 .pTessellationState = nullptr,
887 .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
888 .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
889 .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
890 .pDepthStencilState = &depth_stencil_ci,
891 .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
892 .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
893 .layout = *clear_color_pipeline_layout,
894 .renderPass = key.renderpass,
895 .subpass = 0,
896 .basePipelineHandle = VK_NULL_HANDLE,
897 .basePipelineIndex = 0,
898 }));
899 return *clear_stencil_pipelines.back();
900}
901
823void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, 902void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass,
824 bool is_target_depth) { 903 bool is_target_depth) {
825 if (pipeline) { 904 if (pipeline) {
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index 2976a7d91..dcfe217aa 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -27,6 +27,16 @@ struct BlitImagePipelineKey {
27 Tegra::Engines::Fermi2D::Operation operation; 27 Tegra::Engines::Fermi2D::Operation operation;
28}; 28};
29 29
30struct BlitDepthStencilPipelineKey {
31 constexpr auto operator<=>(const BlitDepthStencilPipelineKey&) const noexcept = default;
32
33 VkRenderPass renderpass;
34 bool depth_clear;
35 u8 stencil_mask;
36 u32 stencil_compare_mask;
37 u32 stencil_ref;
38};
39
30class BlitImageHelper { 40class BlitImageHelper {
31public: 41public:
32 explicit BlitImageHelper(const Device& device, Scheduler& scheduler, 42 explicit BlitImageHelper(const Device& device, Scheduler& scheduler,
@@ -64,6 +74,10 @@ public:
64 void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, 74 void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
65 const std::array<f32, 4>& clear_color, const Region2D& dst_region); 75 const std::array<f32, 4>& clear_color, const Region2D& dst_region);
66 76
77 void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth,
78 u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask,
79 const Region2D& dst_region);
80
67private: 81private:
68 void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, 82 void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
69 const ImageView& src_image_view); 83 const ImageView& src_image_view);
@@ -76,6 +90,8 @@ private:
76 [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); 90 [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key);
77 91
78 [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); 92 [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key);
93 [[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline(
94 const BlitDepthStencilPipelineKey& key);
79 95
80 void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); 96 void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth);
81 97
@@ -108,6 +124,7 @@ private:
108 vk::ShaderModule blit_depth_stencil_frag; 124 vk::ShaderModule blit_depth_stencil_frag;
109 vk::ShaderModule clear_color_vert; 125 vk::ShaderModule clear_color_vert;
110 vk::ShaderModule clear_color_frag; 126 vk::ShaderModule clear_color_frag;
127 vk::ShaderModule clear_stencil_frag;
111 vk::ShaderModule convert_depth_to_float_frag; 128 vk::ShaderModule convert_depth_to_float_frag;
112 vk::ShaderModule convert_float_to_depth_frag; 129 vk::ShaderModule convert_float_to_depth_frag;
113 vk::ShaderModule convert_abgr8_to_d24s8_frag; 130 vk::ShaderModule convert_abgr8_to_d24s8_frag;
@@ -122,6 +139,8 @@ private:
122 std::vector<vk::Pipeline> blit_depth_stencil_pipelines; 139 std::vector<vk::Pipeline> blit_depth_stencil_pipelines;
123 std::vector<BlitImagePipelineKey> clear_color_keys; 140 std::vector<BlitImagePipelineKey> clear_color_keys;
124 std::vector<vk::Pipeline> clear_color_pipelines; 141 std::vector<vk::Pipeline> clear_color_pipelines;
142 std::vector<BlitDepthStencilPipelineKey> clear_stencil_keys;
143 std::vector<vk::Pipeline> clear_stencil_pipelines;
125 vk::Pipeline convert_d32_to_r32_pipeline; 144 vk::Pipeline convert_d32_to_r32_pipeline;
126 vk::Pipeline convert_r32_to_d32_pipeline; 145 vk::Pipeline convert_r32_to_d32_pipeline;
127 vk::Pipeline convert_d16_to_r16_pipeline; 146 vk::Pipeline convert_d16_to_r16_pipeline;
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index a8540339d..35bf80ea3 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -126,7 +126,7 @@ struct FormatTuple {
126 {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM 126 {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM
127 {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM 127 {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM
128 {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT 128 {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT
129 {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable | Storage}, // A2R10G10B10_UNORM 129 {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable}, // A2R10G10B10_UNORM
130 {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) 130 {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle)
131 {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled) 131 {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled)
132 {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM 132 {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 454bb66a4..c4c30d807 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -66,21 +66,6 @@ std::string BuildCommaSeparatedExtensions(
66 return fmt::format("{}", fmt::join(available_extensions, ",")); 66 return fmt::format("{}", fmt::join(available_extensions, ","));
67} 67}
68 68
69DebugCallback MakeDebugCallback(const vk::Instance& instance, const vk::InstanceDispatch& dld) {
70 if (!Settings::values.renderer_debug) {
71 return DebugCallback{};
72 }
73 const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld);
74 const auto it = std::ranges::find_if(*properties, [](const auto& prop) {
75 return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0;
76 });
77 if (it != properties->end()) {
78 return CreateDebugUtilsCallback(instance);
79 } else {
80 return CreateDebugReportCallback(instance);
81 }
82}
83
84} // Anonymous namespace 69} // Anonymous namespace
85 70
86Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, 71Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
@@ -103,7 +88,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
103 cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())), 88 cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())),
104 instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, 89 instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
105 Settings::values.renderer_debug.GetValue())), 90 Settings::values.renderer_debug.GetValue())),
106 debug_callback(MakeDebugCallback(instance, dld)), 91 debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance)
92 : vk::DebugUtilsMessenger{}),
107 surface(CreateSurface(instance, render_window.GetWindowInfo())), 93 surface(CreateSurface(instance, render_window.GetWindowInfo())),
108 device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(), 94 device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(),
109 scheduler(device, state_tracker), 95 scheduler(device, state_tracker),
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 89e98425e..590bc1c64 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -35,8 +35,6 @@ class GPU;
35 35
36namespace Vulkan { 36namespace Vulkan {
37 37
38using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>;
39
40Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, 38Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
41 VkSurfaceKHR surface); 39 VkSurfaceKHR surface);
42 40
@@ -75,7 +73,7 @@ private:
75 vk::InstanceDispatch dld; 73 vk::InstanceDispatch dld;
76 74
77 vk::Instance instance; 75 vk::Instance instance;
78 DebugCallback debug_callback; 76 vk::DebugUtilsMessenger debug_messenger;
79 vk::SurfaceKHR surface; 77 vk::SurfaceKHR surface;
80 78
81 ScreenInfo screen_info; 79 ScreenInfo screen_info;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 60a6ac651..e15865d16 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -529,17 +529,20 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
529 buffer_handles.push_back(handle); 529 buffer_handles.push_back(handle);
530 } 530 }
531 if (device.IsExtExtendedDynamicStateSupported()) { 531 if (device.IsExtExtendedDynamicStateSupported()) {
532 scheduler.Record([bindings_ = std::move(bindings), 532 scheduler.Record([this, bindings_ = std::move(bindings),
533 buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { 533 buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
534 cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, 534 cmdbuf.BindVertexBuffers2EXT(bindings_.min_index,
535 bindings_.max_index - bindings_.min_index, 535 std::min(bindings_.max_index - bindings_.min_index,
536 device.GetMaxVertexInputBindings()),
536 buffer_handles_.data(), bindings_.offsets.data(), 537 buffer_handles_.data(), bindings_.offsets.data(),
537 bindings_.sizes.data(), bindings_.strides.data()); 538 bindings_.sizes.data(), bindings_.strides.data());
538 }); 539 });
539 } else { 540 } else {
540 scheduler.Record([bindings_ = std::move(bindings), 541 scheduler.Record([this, bindings_ = std::move(bindings),
541 buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { 542 buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
542 cmdbuf.BindVertexBuffers(bindings_.min_index, bindings_.max_index - bindings_.min_index, 543 cmdbuf.BindVertexBuffers(bindings_.min_index,
544 std::min(bindings_.max_index - bindings_.min_index,
545 device.GetMaxVertexInputBindings()),
543 buffer_handles_.data(), bindings_.offsets.data()); 546 buffer_handles_.data(), bindings_.offsets.data());
544 }); 547 });
545 } 548 }
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index c1314ca99..a1ec1a100 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -294,10 +294,11 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
294 texture_cache{texture_cache_}, shader_notify{shader_notify_}, 294 texture_cache{texture_cache_}, shader_notify{shader_notify_},
295 use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, 295 use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
296 use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, 296 use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()},
297 workers(device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY 297#ifdef ANDROID
298 ? 1 298 workers(1, "VkPipelineBuilder"),
299 : (std::max(std::thread::hardware_concurrency(), 2U) - 1), 299#else
300 "VkPipelineBuilder"), 300 workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"),
301#endif
301 serialization_thread(1, "VkPipelineSerialization") { 302 serialization_thread(1, "VkPipelineSerialization") {
302 const auto& float_control{device.FloatControlProperties()}; 303 const auto& float_control{device.FloatControlProperties()};
303 const VkDriverId driver_id{device.GetDriverID()}; 304 const VkDriverId driver_id{device.GetDriverID()};
@@ -611,9 +612,6 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
611 612
612 const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))}; 613 const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
613 Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); 614 Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
614 if (Settings::values.dump_shaders) {
615 env.Dump(hash, key.unique_hashes[index]);
616 }
617 if (!uses_vertex_a || index != 1) { 615 if (!uses_vertex_a || index != 1) {
618 // Normal path 616 // Normal path
619 programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info); 617 programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
@@ -624,6 +622,10 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
624 programs[index] = MergeDualVertexPrograms(program_va, program_vb, env); 622 programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
625 } 623 }
626 624
625 if (Settings::values.dump_shaders) {
626 env.Dump(hash, key.unique_hashes[index]);
627 }
628
627 if (programs[index].info.requires_layer_emulation) { 629 if (programs[index].info.requires_layer_emulation) {
628 layer_source_program = &programs[index]; 630 layer_source_program = &programs[index];
629 } 631 }
@@ -664,6 +666,19 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
664 std::move(modules), infos); 666 std::move(modules), infos);
665 667
666} catch (const Shader::Exception& exception) { 668} catch (const Shader::Exception& exception) {
669 auto hash = key.Hash();
670 size_t env_index{0};
671 for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
672 if (key.unique_hashes[index] == 0) {
673 continue;
674 }
675 Shader::Environment& env{*envs[env_index]};
676 ++env_index;
677
678 const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
679 Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
680 env.Dump(hash, key.unique_hashes[index]);
681 }
667 LOG_ERROR(Render_Vulkan, "{}", exception.what()); 682 LOG_ERROR(Render_Vulkan, "{}", exception.what());
668 return nullptr; 683 return nullptr;
669} 684}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index aa59889bd..01e76a82c 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -428,15 +428,27 @@ void RasterizerVulkan::Clear(u32 layer_count) {
428 if (aspect_flags == 0) { 428 if (aspect_flags == 0) {
429 return; 429 return;
430 } 430 }
431 scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, 431
432 clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { 432 if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) {
433 VkClearAttachment attachment; 433 Region2D dst_region = {
434 attachment.aspectMask = aspect_flags; 434 Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
435 attachment.colorAttachment = 0; 435 Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
436 attachment.clearValue.depthStencil.depth = clear_depth; 436 .y = clear_rect.rect.offset.y +
437 attachment.clearValue.depthStencil.stencil = clear_stencil; 437 static_cast<s32>(clear_rect.rect.extent.height)}};
438 cmdbuf.ClearAttachments(attachment, clear_rect); 438 blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth,
439 }); 439 static_cast<u8>(regs.stencil_front_mask), regs.clear_stencil,
440 regs.stencil_front_func_mask, dst_region);
441 } else {
442 scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil,
443 clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) {
444 VkClearAttachment attachment;
445 attachment.aspectMask = aspect_flags;
446 attachment.colorAttachment = 0;
447 attachment.clearValue.depthStencil.depth = clear_depth;
448 attachment.clearValue.depthStencil.stencil = clear_stencil;
449 cmdbuf.ClearAttachments(attachment, clear_rect);
450 });
451 }
440} 452}
441 453
442void RasterizerVulkan::DispatchCompute() { 454void RasterizerVulkan::DispatchCompute() {
@@ -451,6 +463,20 @@ void RasterizerVulkan::DispatchCompute() {
451 pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); 463 pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache);
452 464
453 const auto& qmd{kepler_compute->launch_description}; 465 const auto& qmd{kepler_compute->launch_description};
466 auto indirect_address = kepler_compute->GetIndirectComputeAddress();
467 if (indirect_address) {
468 // DispatchIndirect
469 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
470 const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite;
471 const auto [buffer, offset] =
472 buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op);
473 scheduler.RequestOutsideRenderPassOperationContext();
474 scheduler.Record([indirect_buffer = buffer->Handle(),
475 indirect_offset = offset](vk::CommandBuffer cmdbuf) {
476 cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset);
477 });
478 return;
479 }
454 const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; 480 const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
455 scheduler.RequestOutsideRenderPassOperationContext(); 481 scheduler.RequestOutsideRenderPassOperationContext();
456 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); 482 scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
@@ -830,7 +856,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
830 } 856 }
831 const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height); 857 const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height);
832 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize; 858 static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
833 const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing; 859 const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing
860 : VideoCommon::ObtainBufferOperation::MarkAsWritten;
834 const auto [buffer, offset] = 861 const auto [buffer, offset] =
835 buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op); 862 buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op);
836 863
@@ -839,8 +866,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
839 const std::span copy_span{&copy, 1}; 866 const std::span copy_span{&copy, 1};
840 867
841 if constexpr (IS_IMAGE_UPLOAD) { 868 if constexpr (IS_IMAGE_UPLOAD) {
869 texture_cache.PrepareImage(image_id, true, false);
842 image->UploadMemory(buffer->Handle(), offset, copy_span); 870 image->UploadMemory(buffer->Handle(), offset, copy_span);
843 } else { 871 } else {
872 if (offset % BytesPerBlock(image->info.format)) {
873 return false;
874 }
844 texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span, 875 texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span,
845 buffer_operand.address, buffer_size); 876 buffer_operand.address, buffer_size);
846 } 877 }
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 4457b366f..1bdb0def5 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -719,6 +719,7 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
719 return nullptr; 719 return nullptr;
720 } 720 }
721 const auto& image_map_ids = it->second; 721 const auto& image_map_ids = it->second;
722 boost::container::small_vector<const ImageBase*, 4> valid_images;
722 for (const ImageMapId map_id : image_map_ids) { 723 for (const ImageMapId map_id : image_map_ids) {
723 const ImageMapView& map = slot_map_views[map_id]; 724 const ImageMapView& map = slot_map_views[map_id];
724 const ImageBase& image = slot_images[map.image_id]; 725 const ImageBase& image = slot_images[map.image_id];
@@ -728,8 +729,20 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
728 if (image.image_view_ids.empty()) { 729 if (image.image_view_ids.empty()) {
729 continue; 730 continue;
730 } 731 }
731 return &slot_image_views[image.image_view_ids.at(0)]; 732 valid_images.push_back(&image);
732 } 733 }
734
735 if (valid_images.size() == 1) [[likely]] {
736 return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
737 }
738
739 if (valid_images.size() > 0) [[unlikely]] {
740 std::ranges::sort(valid_images, [](const auto* a, const auto* b) {
741 return a->modification_tick > b->modification_tick;
742 });
743 return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
744 }
745
733 return nullptr; 746 return nullptr;
734} 747}
735 748
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index e9ec91265..a40825c9f 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -243,6 +243,9 @@ public:
243 /// Create channel state. 243 /// Create channel state.
244 void CreateChannel(Tegra::Control::ChannelState& channel) final override; 244 void CreateChannel(Tegra::Control::ChannelState& channel) final override;
245 245
246 /// Prepare an image to be used
247 void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
248
246 std::recursive_mutex mutex; 249 std::recursive_mutex mutex;
247 250
248private: 251private:
@@ -387,9 +390,6 @@ private:
387 /// Synchronize image aliases, copying data if needed 390 /// Synchronize image aliases, copying data if needed
388 void SynchronizeAliases(ImageId image_id); 391 void SynchronizeAliases(ImageId image_id);
389 392
390 /// Prepare an image to be used
391 void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
392
393 /// Prepare an image view to be used 393 /// Prepare an image view to be used
394 void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate); 394 void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate);
395 395
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
index 67e8065a4..448df2d3a 100644
--- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp
+++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
@@ -63,22 +63,6 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
63 return VK_FALSE; 63 return VK_FALSE;
64} 64}
65 65
66VkBool32 DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType,
67 uint64_t object, size_t location, int32_t messageCode,
68 const char* pLayerPrefix, const char* pMessage, void* pUserData) {
69 const VkDebugReportFlagBitsEXT severity = static_cast<VkDebugReportFlagBitsEXT>(flags);
70 const std::string_view message{pMessage};
71 if (severity & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
72 LOG_CRITICAL(Render_Vulkan, "{}", message);
73 } else if (severity & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
74 LOG_WARNING(Render_Vulkan, "{}", message);
75 } else if (severity & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
76 LOG_INFO(Render_Vulkan, "{}", message);
77 } else if (severity & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
78 LOG_DEBUG(Render_Vulkan, "{}", message);
79 }
80 return VK_FALSE;
81}
82} // Anonymous namespace 66} // Anonymous namespace
83 67
84vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { 68vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
@@ -98,15 +82,4 @@ vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
98 }); 82 });
99} 83}
100 84
101vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance) {
102 return instance.CreateDebugReportCallback({
103 .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
104 .pNext = nullptr,
105 .flags = VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
106 VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT,
107 .pfnCallback = DebugReportCallback,
108 .pUserData = nullptr,
109 });
110}
111
112} // namespace Vulkan 85} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.h b/src/video_core/vulkan_common/vulkan_debug_callback.h
index a8af7b406..5e940782f 100644
--- a/src/video_core/vulkan_common/vulkan_debug_callback.h
+++ b/src/video_core/vulkan_common/vulkan_debug_callback.h
@@ -9,6 +9,4 @@ namespace Vulkan {
9 9
10vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); 10vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance);
11 11
12vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance);
13
14} // namespace Vulkan 12} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 710929ac5..617417040 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -71,6 +71,11 @@ constexpr std::array R8G8B8_SSCALED{
71 VK_FORMAT_UNDEFINED, 71 VK_FORMAT_UNDEFINED,
72}; 72};
73 73
74constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{
75 VK_FORMAT_R32G32B32A32_SFLOAT,
76 VK_FORMAT_UNDEFINED,
77};
78
74} // namespace Alternatives 79} // namespace Alternatives
75 80
76enum class NvidiaArchitecture { 81enum class NvidiaArchitecture {
@@ -103,6 +108,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
103 return Alternatives::R16G16B16_SSCALED.data(); 108 return Alternatives::R16G16B16_SSCALED.data();
104 case VK_FORMAT_R8G8B8_SSCALED: 109 case VK_FORMAT_R8G8B8_SSCALED:
105 return Alternatives::R8G8B8_SSCALED.data(); 110 return Alternatives::R8G8B8_SSCALED.data();
111 case VK_FORMAT_R32G32B32_SFLOAT:
112 return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
106 default: 113 default:
107 return nullptr; 114 return nullptr;
108 } 115 }
@@ -130,6 +137,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
130 VK_FORMAT_A2B10G10R10_UINT_PACK32, 137 VK_FORMAT_A2B10G10R10_UINT_PACK32,
131 VK_FORMAT_A2B10G10R10_UNORM_PACK32, 138 VK_FORMAT_A2B10G10R10_UNORM_PACK32,
132 VK_FORMAT_A2B10G10R10_USCALED_PACK32, 139 VK_FORMAT_A2B10G10R10_USCALED_PACK32,
140 VK_FORMAT_A2R10G10B10_UNORM_PACK32,
133 VK_FORMAT_A8B8G8R8_SINT_PACK32, 141 VK_FORMAT_A8B8G8R8_SINT_PACK32,
134 VK_FORMAT_A8B8G8R8_SNORM_PACK32, 142 VK_FORMAT_A8B8G8R8_SNORM_PACK32,
135 VK_FORMAT_A8B8G8R8_SRGB_PACK32, 143 VK_FORMAT_A8B8G8R8_SRGB_PACK32,
@@ -326,6 +334,43 @@ std::vector<const char*> ExtensionListForVulkan(
326 334
327} // Anonymous namespace 335} // Anonymous namespace
328 336
337void Device::RemoveExtension(bool& extension, const std::string& extension_name) {
338 extension = false;
339 loaded_extensions.erase(extension_name);
340}
341
342void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) {
343 if (loaded_extensions.contains(extension_name) && !is_suitable) {
344 LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name);
345 this->RemoveExtension(is_suitable, extension_name);
346 }
347}
348
349template <typename Feature>
350void Device::RemoveExtensionFeature(bool& extension, Feature& feature,
351 const std::string& extension_name) {
352 // Unload extension.
353 this->RemoveExtension(extension, extension_name);
354
355 // Save sType and pNext for chain.
356 VkStructureType sType = feature.sType;
357 void* pNext = feature.pNext;
358
359 // Clear feature struct and restore chain.
360 feature = {};
361 feature.sType = sType;
362 feature.pNext = pNext;
363}
364
365template <typename Feature>
366void Device::RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature,
367 const std::string& extension_name) {
368 if (loaded_extensions.contains(extension_name) && !is_suitable) {
369 LOG_WARNING(Render_Vulkan, "Removing features for unsuitable extension {}", extension_name);
370 this->RemoveExtensionFeature(is_suitable, feature, extension_name);
371 }
372}
373
329Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface, 374Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface,
330 const vk::InstanceDispatch& dld_) 375 const vk::InstanceDispatch& dld_)
331 : instance{instance_}, dld{dld_}, physical{physical_}, 376 : instance{instance_}, dld{dld_}, physical{physical_},
@@ -397,21 +442,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
397 if (is_qualcomm || is_turnip) { 442 if (is_qualcomm || is_turnip) {
398 LOG_WARNING(Render_Vulkan, 443 LOG_WARNING(Render_Vulkan,
399 "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color"); 444 "Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color");
400 extensions.custom_border_color = false; 445 RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
401 loaded_extensions.erase(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); 446 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
402 } 447 }
403 448
404 if (is_qualcomm) { 449 if (is_qualcomm) {
405 must_emulate_scaled_formats = true; 450 must_emulate_scaled_formats = true;
406 451
407 LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state"); 452 LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state");
408 extensions.extended_dynamic_state = false; 453 RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
409 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 454 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
410 455
411 LOG_WARNING(Render_Vulkan, 456 LOG_WARNING(Render_Vulkan,
412 "Qualcomm drivers have a slow VK_KHR_push_descriptor implementation"); 457 "Qualcomm drivers have a slow VK_KHR_push_descriptor implementation");
413 extensions.push_descriptor = false; 458 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
414 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
415 459
416#if defined(ANDROID) && defined(ARCHITECTURE_arm64) 460#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
417 // Patch the driver to enable BCn textures. 461 // Patch the driver to enable BCn textures.
@@ -440,15 +484,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
440 must_emulate_scaled_formats = true; 484 must_emulate_scaled_formats = true;
441 485
442 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state"); 486 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
443 extensions.extended_dynamic_state = false; 487 RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
444 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 488 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
445 489
446 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2"); 490 LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2");
447 features.extended_dynamic_state2.extendedDynamicState2 = false; 491 RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
448 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; 492 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
449 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
450 extensions.extended_dynamic_state2 = false;
451 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
452 } 493 }
453 494
454 if (is_nvidia) { 495 if (is_nvidia) {
@@ -464,8 +505,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
464 case NvidiaArchitecture::VoltaOrOlder: 505 case NvidiaArchitecture::VoltaOrOlder:
465 if (nv_major_version < 527) { 506 if (nv_major_version < 527) {
466 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); 507 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
467 extensions.push_descriptor = false; 508 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
468 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
469 } 509 }
470 break; 510 break;
471 } 511 }
@@ -480,8 +520,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
480 if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) { 520 if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) {
481 LOG_WARNING(Render_Vulkan, 521 LOG_WARNING(Render_Vulkan,
482 "RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state"); 522 "RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state");
483 extensions.extended_dynamic_state = false; 523 RemoveExtensionFeature(extensions.extended_dynamic_state,
484 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 524 features.extended_dynamic_state,
525 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
485 } 526 }
486 } 527 }
487 if (extensions.extended_dynamic_state2 && is_radv) { 528 if (extensions.extended_dynamic_state2 && is_radv) {
@@ -490,11 +531,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
490 LOG_WARNING( 531 LOG_WARNING(
491 Render_Vulkan, 532 Render_Vulkan,
492 "RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2"); 533 "RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2");
493 features.extended_dynamic_state2.extendedDynamicState2 = false; 534 RemoveExtensionFeature(extensions.extended_dynamic_state2,
494 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; 535 features.extended_dynamic_state2,
495 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; 536 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
496 extensions.extended_dynamic_state2 = false;
497 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
498 } 537 }
499 } 538 }
500 if (extensions.extended_dynamic_state2 && is_qualcomm) { 539 if (extensions.extended_dynamic_state2 && is_qualcomm) {
@@ -504,11 +543,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
504 // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2. 543 // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2.
505 LOG_WARNING(Render_Vulkan, 544 LOG_WARNING(Render_Vulkan,
506 "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2"); 545 "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2");
507 features.extended_dynamic_state2.extendedDynamicState2 = false; 546 RemoveExtensionFeature(extensions.extended_dynamic_state2,
508 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; 547 features.extended_dynamic_state2,
509 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; 548 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
510 extensions.extended_dynamic_state2 = false;
511 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
512 } 549 }
513 } 550 }
514 if (extensions.extended_dynamic_state3 && is_radv) { 551 if (extensions.extended_dynamic_state3 && is_radv) {
@@ -540,9 +577,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
540 if (is_rdna2) { 577 if (is_rdna2) {
541 LOG_WARNING(Render_Vulkan, 578 LOG_WARNING(Render_Vulkan,
542 "RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware"); 579 "RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware");
543 features.vertex_input_dynamic_state.vertexInputDynamicState = false; 580 RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
544 extensions.vertex_input_dynamic_state = false; 581 features.vertex_input_dynamic_state,
545 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 582 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
546 } 583 }
547 } 584 }
548 if (extensions.vertex_input_dynamic_state && is_qualcomm) { 585 if (extensions.vertex_input_dynamic_state && is_qualcomm) {
@@ -553,9 +590,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
553 LOG_WARNING( 590 LOG_WARNING(
554 Render_Vulkan, 591 Render_Vulkan,
555 "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state"); 592 "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state");
556 features.vertex_input_dynamic_state.vertexInputDynamicState = false; 593 RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
557 extensions.vertex_input_dynamic_state = false; 594 features.vertex_input_dynamic_state,
558 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 595 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
559 } 596 }
560 } 597 }
561 598
@@ -575,8 +612,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
575 if (!features.shader_float16_int8.shaderFloat16) { 612 if (!features.shader_float16_int8.shaderFloat16) {
576 LOG_WARNING(Render_Vulkan, 613 LOG_WARNING(Render_Vulkan,
577 "AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax"); 614 "AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax");
578 extensions.sampler_filter_minmax = false; 615 RemoveExtension(extensions.sampler_filter_minmax,
579 loaded_extensions.erase(VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME); 616 VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME);
580 } 617 }
581 } 618 }
582 619
@@ -584,8 +621,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
584 const u32 version = (properties.properties.driverVersion << 3) >> 3; 621 const u32 version = (properties.properties.driverVersion << 3) >> 3;
585 if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) { 622 if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) {
586 LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state"); 623 LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state");
587 extensions.vertex_input_dynamic_state = false; 624 RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
588 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 625 features.vertex_input_dynamic_state,
626 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
589 } 627 }
590 } 628 }
591 if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) { 629 if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) {
@@ -612,8 +650,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
612 // mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc 650 // mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc
613 LOG_WARNING(Render_Vulkan, 651 LOG_WARNING(Render_Vulkan,
614 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); 652 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
615 extensions.push_descriptor = false; 653 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
616 loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
617 } 654 }
618 } 655 }
619 if (is_mvk) { 656 if (is_mvk) {
@@ -1007,34 +1044,29 @@ bool Device::GetSuitability(bool requires_swapchain) {
1007 return suitable; 1044 return suitable;
1008} 1045}
1009 1046
1010void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) {
1011 if (loaded_extensions.contains(extension_name) && !is_suitable) {
1012 LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name);
1013 loaded_extensions.erase(extension_name);
1014 }
1015}
1016
1017void Device::RemoveUnsuitableExtensions() { 1047void Device::RemoveUnsuitableExtensions() {
1018 // VK_EXT_custom_border_color 1048 // VK_EXT_custom_border_color
1019 extensions.custom_border_color = features.custom_border_color.customBorderColors && 1049 extensions.custom_border_color = features.custom_border_color.customBorderColors &&
1020 features.custom_border_color.customBorderColorWithoutFormat; 1050 features.custom_border_color.customBorderColorWithoutFormat;
1021 RemoveExtensionIfUnsuitable(extensions.custom_border_color, 1051 RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
1022 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); 1052 VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
1023 1053
1024 // VK_EXT_depth_clip_control 1054 // VK_EXT_depth_clip_control
1025 extensions.depth_clip_control = features.depth_clip_control.depthClipControl; 1055 extensions.depth_clip_control = features.depth_clip_control.depthClipControl;
1026 RemoveExtensionIfUnsuitable(extensions.depth_clip_control, 1056 RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
1027 VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); 1057 VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
1028 1058
1029 // VK_EXT_extended_dynamic_state 1059 // VK_EXT_extended_dynamic_state
1030 extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; 1060 extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState;
1031 RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state, 1061 RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state,
1032 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 1062 features.extended_dynamic_state,
1063 VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
1033 1064
1034 // VK_EXT_extended_dynamic_state2 1065 // VK_EXT_extended_dynamic_state2
1035 extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2; 1066 extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2;
1036 RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state2, 1067 RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state2,
1037 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); 1068 features.extended_dynamic_state2,
1069 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
1038 1070
1039 // VK_EXT_extended_dynamic_state3 1071 // VK_EXT_extended_dynamic_state3
1040 dynamic_state3_blending = 1072 dynamic_state3_blending =
@@ -1048,35 +1080,38 @@ void Device::RemoveUnsuitableExtensions() {
1048 extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables; 1080 extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables;
1049 dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3; 1081 dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3;
1050 dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3; 1082 dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3;
1051 RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state3, 1083 RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state3,
1052 VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); 1084 features.extended_dynamic_state3,
1085 VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
1053 1086
1054 // VK_EXT_provoking_vertex 1087 // VK_EXT_provoking_vertex
1055 extensions.provoking_vertex = 1088 extensions.provoking_vertex =
1056 features.provoking_vertex.provokingVertexLast && 1089 features.provoking_vertex.provokingVertexLast &&
1057 features.provoking_vertex.transformFeedbackPreservesProvokingVertex; 1090 features.provoking_vertex.transformFeedbackPreservesProvokingVertex;
1058 RemoveExtensionIfUnsuitable(extensions.provoking_vertex, 1091 RemoveExtensionFeatureIfUnsuitable(extensions.provoking_vertex, features.provoking_vertex,
1059 VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); 1092 VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
1060 1093
1061 // VK_KHR_shader_atomic_int64 1094 // VK_KHR_shader_atomic_int64
1062 extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics && 1095 extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics &&
1063 features.shader_atomic_int64.shaderSharedInt64Atomics; 1096 features.shader_atomic_int64.shaderSharedInt64Atomics;
1064 RemoveExtensionIfUnsuitable(extensions.shader_atomic_int64, 1097 RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64,
1065 VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); 1098 VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
1066 1099
1067 // VK_EXT_shader_demote_to_helper_invocation 1100 // VK_EXT_shader_demote_to_helper_invocation
1068 extensions.shader_demote_to_helper_invocation = 1101 extensions.shader_demote_to_helper_invocation =
1069 features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation; 1102 features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation;
1070 RemoveExtensionIfUnsuitable(extensions.shader_demote_to_helper_invocation, 1103 RemoveExtensionFeatureIfUnsuitable(extensions.shader_demote_to_helper_invocation,
1071 VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); 1104 features.shader_demote_to_helper_invocation,
1105 VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME);
1072 1106
1073 // VK_EXT_subgroup_size_control 1107 // VK_EXT_subgroup_size_control
1074 extensions.subgroup_size_control = 1108 extensions.subgroup_size_control =
1075 features.subgroup_size_control.subgroupSizeControl && 1109 features.subgroup_size_control.subgroupSizeControl &&
1076 properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize && 1110 properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize &&
1077 properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize; 1111 properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize;
1078 RemoveExtensionIfUnsuitable(extensions.subgroup_size_control, 1112 RemoveExtensionFeatureIfUnsuitable(extensions.subgroup_size_control,
1079 VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME); 1113 features.subgroup_size_control,
1114 VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
1080 1115
1081 // VK_EXT_transform_feedback 1116 // VK_EXT_transform_feedback
1082 extensions.transform_feedback = 1117 extensions.transform_feedback =
@@ -1086,24 +1121,27 @@ void Device::RemoveUnsuitableExtensions() {
1086 properties.transform_feedback.maxTransformFeedbackBuffers > 0 && 1121 properties.transform_feedback.maxTransformFeedbackBuffers > 0 &&
1087 properties.transform_feedback.transformFeedbackQueries && 1122 properties.transform_feedback.transformFeedbackQueries &&
1088 properties.transform_feedback.transformFeedbackDraw; 1123 properties.transform_feedback.transformFeedbackDraw;
1089 RemoveExtensionIfUnsuitable(extensions.transform_feedback, 1124 RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback,
1090 VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); 1125 VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
1091 1126
1092 // VK_EXT_vertex_input_dynamic_state 1127 // VK_EXT_vertex_input_dynamic_state
1093 extensions.vertex_input_dynamic_state = 1128 extensions.vertex_input_dynamic_state =
1094 features.vertex_input_dynamic_state.vertexInputDynamicState; 1129 features.vertex_input_dynamic_state.vertexInputDynamicState;
1095 RemoveExtensionIfUnsuitable(extensions.vertex_input_dynamic_state, 1130 RemoveExtensionFeatureIfUnsuitable(extensions.vertex_input_dynamic_state,
1096 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 1131 features.vertex_input_dynamic_state,
1132 VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
1097 1133
1098 // VK_KHR_pipeline_executable_properties 1134 // VK_KHR_pipeline_executable_properties
1099 if (Settings::values.renderer_shader_feedback.GetValue()) { 1135 if (Settings::values.renderer_shader_feedback.GetValue()) {
1100 extensions.pipeline_executable_properties = 1136 extensions.pipeline_executable_properties =
1101 features.pipeline_executable_properties.pipelineExecutableInfo; 1137 features.pipeline_executable_properties.pipelineExecutableInfo;
1102 RemoveExtensionIfUnsuitable(extensions.pipeline_executable_properties, 1138 RemoveExtensionFeatureIfUnsuitable(extensions.pipeline_executable_properties,
1103 VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); 1139 features.pipeline_executable_properties,
1140 VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
1104 } else { 1141 } else {
1105 extensions.pipeline_executable_properties = false; 1142 RemoveExtensionFeature(extensions.pipeline_executable_properties,
1106 loaded_extensions.erase(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); 1143 features.pipeline_executable_properties,
1144 VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
1107 } 1145 }
1108 1146
1109 // VK_KHR_workgroup_memory_explicit_layout 1147 // VK_KHR_workgroup_memory_explicit_layout
@@ -1113,8 +1151,9 @@ void Device::RemoveUnsuitableExtensions() {
1113 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && 1151 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess &&
1114 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && 1152 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess &&
1115 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; 1153 features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout;
1116 RemoveExtensionIfUnsuitable(extensions.workgroup_memory_explicit_layout, 1154 RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout,
1117 VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); 1155 features.workgroup_memory_explicit_layout,
1156 VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME);
1118} 1157}
1119 1158
1120void Device::SetupFamilies(VkSurfaceKHR surface) { 1159void Device::SetupFamilies(VkSurfaceKHR surface) {
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index d8dd41e51..488fdd313 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -639,8 +639,17 @@ private:
639 639
640 // Remove extensions which have incomplete feature support. 640 // Remove extensions which have incomplete feature support.
641 void RemoveUnsuitableExtensions(); 641 void RemoveUnsuitableExtensions();
642
643 void RemoveExtension(bool& extension, const std::string& extension_name);
642 void RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name); 644 void RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name);
643 645
646 template <typename Feature>
647 void RemoveExtensionFeature(bool& extension, Feature& feature,
648 const std::string& extension_name);
649 template <typename Feature>
650 void RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature,
651 const std::string& extension_name);
652
644 /// Sets up queue families. 653 /// Sets up queue families.
645 void SetupFamilies(VkSurfaceKHR surface); 654 void SetupFamilies(VkSurfaceKHR surface);
646 655
diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp
index 72aedb8d8..180657a75 100644
--- a/src/video_core/vulkan_common/vulkan_instance.cpp
+++ b/src/video_core/vulkan_common/vulkan_instance.cpp
@@ -41,9 +41,6 @@ namespace {
41 bool enable_validation) { 41 bool enable_validation) {
42 std::vector<const char*> extensions; 42 std::vector<const char*> extensions;
43 extensions.reserve(6); 43 extensions.reserve(6);
44#ifdef __APPLE__
45 extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
46#endif
47 switch (window_type) { 44 switch (window_type) {
48 case Core::Frontend::WindowSystemType::Headless: 45 case Core::Frontend::WindowSystemType::Headless:
49 break; 46 break;
@@ -74,11 +71,14 @@ namespace {
74 if (window_type != Core::Frontend::WindowSystemType::Headless) { 71 if (window_type != Core::Frontend::WindowSystemType::Headless) {
75 extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); 72 extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
76 } 73 }
77 if (enable_validation) { 74#ifdef __APPLE__
78 const bool debug_utils = 75 if (AreExtensionsSupported(dld, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME})) {
79 AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}); 76 extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
80 extensions.push_back(debug_utils ? VK_EXT_DEBUG_UTILS_EXTENSION_NAME 77 }
81 : VK_EXT_DEBUG_REPORT_EXTENSION_NAME); 78#endif
79 if (enable_validation &&
80 AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) {
81 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
82 } 82 }
83 return extensions; 83 return extensions;
84} 84}
diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp
index 47f6f2a03..0130f6a0d 100644
--- a/src/video_core/vulkan_common/vulkan_library.cpp
+++ b/src/video_core/vulkan_common/vulkan_library.cpp
@@ -19,13 +19,17 @@ std::shared_ptr<Common::DynamicLibrary> OpenLibrary(
19#else 19#else
20 auto library = std::make_shared<Common::DynamicLibrary>(); 20 auto library = std::make_shared<Common::DynamicLibrary>();
21#ifdef __APPLE__ 21#ifdef __APPLE__
22 const auto libvulkan_filename =
23 Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.1.dylib";
24 const auto libmoltenvk_filename =
25 Common::FS::GetBundleDirectory() / "Contents/Frameworks/libMoltenVK.dylib";
26 const char* library_paths[] = {std::getenv("LIBVULKAN_PATH"), libvulkan_filename.c_str(),
27 libmoltenvk_filename.c_str()};
22 // Check if a path to a specific Vulkan library has been specified. 28 // Check if a path to a specific Vulkan library has been specified.
23 char* const libvulkan_env = std::getenv("LIBVULKAN_PATH"); 29 for (const auto& library_path : library_paths) {
24 if (!libvulkan_env || !library->Open(libvulkan_env)) { 30 if (library_path && library->Open(library_path)) {
25 // Use the libvulkan.dylib from the application bundle. 31 break;
26 const auto filename = 32 }
27 Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.dylib";
28 void(library->Open(Common::FS::PathToUTF8String(filename).c_str()));
29 } 33 }
30#else 34#else
31 std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); 35 std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 78e5a248f..c3f388d89 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -92,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
92 X(vkCmdCopyImage); 92 X(vkCmdCopyImage);
93 X(vkCmdCopyImageToBuffer); 93 X(vkCmdCopyImageToBuffer);
94 X(vkCmdDispatch); 94 X(vkCmdDispatch);
95 X(vkCmdDispatchIndirect);
95 X(vkCmdDraw); 96 X(vkCmdDraw);
96 X(vkCmdDrawIndexed); 97 X(vkCmdDrawIndexed);
97 X(vkCmdDrawIndirect); 98 X(vkCmdDrawIndirect);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index c226a2a29..049fa8038 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -203,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch {
203 PFN_vkCmdCopyImage vkCmdCopyImage{}; 203 PFN_vkCmdCopyImage vkCmdCopyImage{};
204 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{}; 204 PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{};
205 PFN_vkCmdDispatch vkCmdDispatch{}; 205 PFN_vkCmdDispatch vkCmdDispatch{};
206 PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{};
206 PFN_vkCmdDraw vkCmdDraw{}; 207 PFN_vkCmdDraw vkCmdDraw{};
207 PFN_vkCmdDrawIndexed vkCmdDrawIndexed{}; 208 PFN_vkCmdDrawIndexed vkCmdDrawIndexed{};
208 PFN_vkCmdDrawIndirect vkCmdDrawIndirect{}; 209 PFN_vkCmdDrawIndirect vkCmdDrawIndirect{};
@@ -1209,6 +1210,10 @@ public:
1209 dld->vkCmdDispatch(handle, x, y, z); 1210 dld->vkCmdDispatch(handle, x, y, z);
1210 } 1211 }
1211 1212
1213 void DispatchIndirect(VkBuffer indirect_buffer, VkDeviceSize offset) const noexcept {
1214 dld->vkCmdDispatchIndirect(handle, indirect_buffer, offset);
1215 }
1216
1212 void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, 1217 void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
1213 VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers, 1218 VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers,
1214 Span<VkBufferMemoryBarrier> buffer_barriers, 1219 Span<VkBufferMemoryBarrier> buffer_barriers,
diff --git a/src/web_service/verify_user_jwt.cpp b/src/web_service/verify_user_jwt.cpp
index 129eb1968..f88f67620 100644
--- a/src/web_service/verify_user_jwt.cpp
+++ b/src/web_service/verify_user_jwt.cpp
@@ -4,6 +4,7 @@
4#if defined(__GNUC__) || defined(__clang__) 4#if defined(__GNUC__) || defined(__clang__)
5#pragma GCC diagnostic push 5#pragma GCC diagnostic push
6#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" 6#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
7#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // for deprecated OpenSSL functions
7#endif 8#endif
8#include <jwt/jwt.hpp> 9#include <jwt/jwt.hpp>
9#if defined(__GNUC__) || defined(__clang__) 10#if defined(__GNUC__) || defined(__clang__)
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 2e4da696c..8f86a1553 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -313,6 +313,18 @@ if (APPLE)
313 target_sources(yuzu PRIVATE ${MACOSX_ICON}) 313 target_sources(yuzu PRIVATE ${MACOSX_ICON})
314 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE) 314 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE)
315 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) 315 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
316
317 if (NOT USE_SYSTEM_MOLTENVK)
318 set(MOLTENVK_PLATFORM "macOS")
319 set(MOLTENVK_VERSION "v1.2.5")
320 download_moltenvk_external(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION})
321 endif()
322 find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
323 message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
324 set_source_files_properties(${MOLTENVK_LIBRARY} PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks
325 XCODE_FILE_ATTRIBUTES "CodeSignOnCopy")
326 target_sources(yuzu PRIVATE ${MOLTENVK_LIBRARY})
327
316elseif(WIN32) 328elseif(WIN32)
317 # compile as a win32 gui application instead of a console application 329 # compile as a win32 gui application instead of a console application
318 if (QT_VERSION VERSION_GREATER_EQUAL 6) 330 if (QT_VERSION VERSION_GREATER_EQUAL 6)
diff --git a/src/yuzu/applets/qt_amiibo_settings.cpp b/src/yuzu/applets/qt_amiibo_settings.cpp
index 4988fcc83..b457a736a 100644
--- a/src/yuzu/applets/qt_amiibo_settings.cpp
+++ b/src/yuzu/applets/qt_amiibo_settings.cpp
@@ -160,7 +160,8 @@ void QtAmiiboSettingsDialog::LoadAmiiboData() {
160 } 160 }
161 161
162 const auto amiibo_name = std::string(register_info.amiibo_name.data()); 162 const auto amiibo_name = std::string(register_info.amiibo_name.data());
163 const auto owner_name = Common::UTF16ToUTF8(register_info.mii_char_info.name.data()); 163 const auto owner_name =
164 Common::UTF16ToUTF8(register_info.mii_char_info.GetNickname().data.data());
164 const auto creation_date = 165 const auto creation_date =
165 QDate(register_info.creation_date.year, register_info.creation_date.month, 166 QDate(register_info.creation_date.year, register_info.creation_date.month,
166 register_info.creation_date.day); 167 register_info.creation_date.day);
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 00aafb8f8..d15559518 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -5,6 +5,8 @@
5#include <thread> 5#include <thread>
6 6
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/settings.h"
9#include "common/settings_enums.h"
8#include "common/string_util.h" 10#include "common/string_util.h"
9#include "core/core.h" 11#include "core/core.h"
10#include "core/hid/emulated_controller.h" 12#include "core/hid/emulated_controller.h"
@@ -226,9 +228,11 @@ int QtControllerSelectorDialog::exec() {
226} 228}
227 229
228void QtControllerSelectorDialog::ApplyConfiguration() { 230void QtControllerSelectorDialog::ApplyConfiguration() {
229 const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); 231 const bool pre_docked_mode = Settings::IsDockedMode();
230 Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); 232 const bool docked_mode_selected = ui->radioDocked->isChecked();
231 OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system); 233 Settings::values.use_docked_mode.SetValue(
234 docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
235 OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
232 236
233 Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); 237 Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
234 Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); 238 Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
@@ -616,8 +620,8 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
616 ui->radioDocked->setEnabled(!is_handheld); 620 ui->radioDocked->setEnabled(!is_handheld);
617 ui->radioUndocked->setEnabled(!is_handheld); 621 ui->radioUndocked->setEnabled(!is_handheld);
618 622
619 ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); 623 ui->radioDocked->setChecked(Settings::IsDockedMode());
620 ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); 624 ui->radioUndocked->setChecked(!Settings::IsDockedMode());
621 625
622 // Also force into undocked mode if the controller type is handheld. 626 // Also force into undocked mode if the controller type is handheld.
623 if (is_handheld) { 627 if (is_handheld) {
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index bdd1497b5..2afa72140 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -11,6 +11,8 @@
11#include <glad/glad.h> 11#include <glad/glad.h>
12 12
13#include <QtCore/qglobal.h> 13#include <QtCore/qglobal.h>
14#include "common/settings_enums.h"
15#include "uisettings.h"
14#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA 16#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA
15#include <QCamera> 17#include <QCamera>
16#include <QCameraImageCapture> 18#include <QCameraImageCapture>
@@ -916,7 +918,6 @@ void GRenderWindow::ReleaseRenderTarget() {
916 918
917void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { 919void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
918 auto& renderer = system.Renderer(); 920 auto& renderer = system.Renderer();
919 const f32 res_scale = Settings::values.resolution_info.up_factor;
920 921
921 if (renderer.IsScreenshotPending()) { 922 if (renderer.IsScreenshotPending()) {
922 LOG_WARNING(Render, 923 LOG_WARNING(Render,
@@ -924,7 +925,18 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
924 return; 925 return;
925 } 926 }
926 927
927 const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)}; 928 const Layout::FramebufferLayout layout{[]() {
929 u32 height = UISettings::values.screenshot_height.GetValue();
930 if (height == 0) {
931 height = Settings::IsDockedMode() ? Layout::ScreenDocked::Height
932 : Layout::ScreenUndocked::Height;
933 height *= Settings::values.resolution_info.up_factor;
934 }
935 const u32 width =
936 UISettings::CalculateWidth(height, Settings::values.aspect_ratio.GetValue());
937 return Layout::DefaultFrameLayout(width, height);
938 }()};
939
928 screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); 940 screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
929 renderer.RequestScreenshot( 941 renderer.RequestScreenshot(
930 screenshot_image.bits(), 942 screenshot_image.bits(),
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index b2405f9b8..1de093447 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -9,6 +9,7 @@
9#include "common/fs/path_util.h" 9#include "common/fs/path_util.h"
10#include "common/settings.h" 10#include "common/settings.h"
11#include "common/settings_common.h" 11#include "common/settings_common.h"
12#include "common/settings_enums.h"
12#include "core/core.h" 13#include "core/core.h"
13#include "core/hle/service/acc/profile_manager.h" 14#include "core/hle/service/acc/profile_manager.h"
14#include "core/hle/service/hid/controllers/npad.h" 15#include "core/hle/service/hid/controllers/npad.h"
@@ -85,9 +86,9 @@ const std::map<Settings::ScalingFilter, QString> Config::scaling_filter_texts_ma
85 {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))}, 86 {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
86}; 87};
87 88
88const std::map<bool, QString> Config::use_docked_mode_texts_map = { 89const std::map<Settings::ConsoleMode, QString> Config::use_docked_mode_texts_map = {
89 {true, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))}, 90 {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
90 {false, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))}, 91 {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
91}; 92};
92 93
93const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = { 94const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = {
@@ -376,7 +377,7 @@ void Config::ReadControlValues() {
376 const auto controller_type = Settings::values.players.GetValue()[0].controller_type; 377 const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
377 if (controller_type == Settings::ControllerType::Handheld) { 378 if (controller_type == Settings::ControllerType::Handheld) {
378 Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig()); 379 Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
379 Settings::values.use_docked_mode.SetValue(false); 380 Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
380 } 381 }
381 382
382 if (IsCustomConfig()) { 383 if (IsCustomConfig()) {
@@ -592,8 +593,7 @@ void Config::ReadRendererValues() {
592void Config::ReadScreenshotValues() { 593void Config::ReadScreenshotValues() {
593 qt_config->beginGroup(QStringLiteral("Screenshots")); 594 qt_config->beginGroup(QStringLiteral("Screenshots"));
594 595
595 UISettings::values.enable_screenshot_save_as = 596 ReadCategory(Settings::Category::Screenshots);
596 ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool();
597 FS::SetYuzuPath( 597 FS::SetYuzuPath(
598 FS::YuzuPath::ScreenshotsDir, 598 FS::YuzuPath::ScreenshotsDir,
599 qt_config 599 qt_config
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 0ac74c8e7..727feebfb 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -9,6 +9,7 @@
9#include <QMetaType> 9#include <QMetaType>
10#include <QVariant> 10#include <QVariant>
11#include "common/settings.h" 11#include "common/settings.h"
12#include "common/settings_enums.h"
12#include "yuzu/uisettings.h" 13#include "yuzu/uisettings.h"
13 14
14class QSettings; 15class QSettings;
@@ -51,7 +52,7 @@ public:
51 52
52 static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map; 53 static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map;
53 static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map; 54 static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map;
54 static const std::map<bool, QString> use_docked_mode_texts_map; 55 static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map;
55 static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map; 56 static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map;
56 static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map; 57 static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
57 static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map; 58 static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index cbeb8f168..b22fda746 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -59,6 +59,8 @@ void ConfigureDebug::SetConfiguration() {
59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); 59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); 60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); 61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
62 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
63 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
62 ui->enable_graphics_debugging->setEnabled(runtime_lock); 64 ui->enable_graphics_debugging->setEnabled(runtime_lock);
63 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); 65 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
64 ui->enable_shader_feedback->setEnabled(runtime_lock); 66 ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() {
111 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); 113 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
112 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); 114 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
113 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); 115 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
116 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
114 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); 117 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
115 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); 118 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
116 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); 119 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 97c7d9022..66b8b7459 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -18,8 +18,8 @@
18 <rect> 18 <rect>
19 <x>0</x> 19 <x>0</x>
20 <y>0</y> 20 <y>0</y>
21 <width>829</width> 21 <width>842</width>
22 <height>758</height> 22 <height>741</height>
23 </rect> 23 </rect>
24 </property> 24 </property>
25 <layout class="QVBoxLayout" name="verticalLayout_1"> 25 <layout class="QVBoxLayout" name="verticalLayout_1">
@@ -260,7 +260,7 @@
260 <string>Graphics</string> 260 <string>Graphics</string>
261 </property> 261 </property>
262 <layout class="QGridLayout" name="gridLayout_2"> 262 <layout class="QGridLayout" name="gridLayout_2">
263 <item row="3" column="0"> 263 <item row="4" column="0">
264 <widget class="QCheckBox" name="disable_loop_safety_checks"> 264 <widget class="QCheckBox" name="disable_loop_safety_checks">
265 <property name="toolTip"> 265 <property name="toolTip">
266 <string>When checked, it executes shaders without loop logic changes</string> 266 <string>When checked, it executes shaders without loop logic changes</string>
@@ -270,33 +270,53 @@
270 </property> 270 </property>
271 </widget> 271 </widget>
272 </item> 272 </item>
273 <item row="4" column="0"> 273 <item row="8" column="0">
274 <widget class="QCheckBox" name="dump_shaders"> 274 <widget class="QCheckBox" name="disable_macro_hle">
275 <property name="enabled"> 275 <property name="enabled">
276 <bool>true</bool> 276 <bool>true</bool>
277 </property> 277 </property>
278 <property name="toolTip"> 278 <property name="toolTip">
279 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> 279 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
280 </property> 280 </property>
281 <property name="text"> 281 <property name="text">
282 <string>Dump Game Shaders</string> 282 <string>Disable Macro HLE</string>
283 </property> 283 </property>
284 </widget> 284 </widget>
285 </item> 285 </item>
286 <item row="7" column="0"> 286 <item row="7" column="0">
287 <widget class="QCheckBox" name="disable_macro_hle"> 287 <widget class="QCheckBox" name="dump_macros">
288 <property name="enabled"> 288 <property name="enabled">
289 <bool>true</bool> 289 <bool>true</bool>
290 </property> 290 </property>
291 <property name="toolTip"> 291 <property name="toolTip">
292 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> 292 <string>When checked, it will dump all the macro programs of the GPU</string>
293 </property> 293 </property>
294 <property name="text"> 294 <property name="text">
295 <string>Disable Macro HLE</string> 295 <string>Dump Maxwell Macros</string>
296 </property> 296 </property>
297 </widget> 297 </widget>
298 </item> 298 </item>
299 <item row="5" column="0"> 299 <item row="3" column="0">
300 <widget class="QCheckBox" name="enable_nsight_aftermath">
301 <property name="toolTip">
302 <string>When checked, it enables Nsight Aftermath crash dumps</string>
303 </property>
304 <property name="text">
305 <string>Enable Nsight Aftermath</string>
306 </property>
307 </widget>
308 </item>
309 <item row="2" column="0">
310 <widget class="QCheckBox" name="enable_shader_feedback">
311 <property name="toolTip">
312 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
313 </property>
314 <property name="text">
315 <string>Enable Shader Feedback</string>
316 </property>
317 </widget>
318 </item>
319 <item row="6" column="0">
300 <widget class="QCheckBox" name="disable_macro_jit"> 320 <widget class="QCheckBox" name="disable_macro_jit">
301 <property name="enabled"> 321 <property name="enabled">
302 <bool>true</bool> 322 <bool>true</bool>
@@ -309,6 +329,22 @@
309 </property> 329 </property>
310 </widget> 330 </widget>
311 </item> 331 </item>
332 <item row="9" column="0">
333 <spacer name="verticalSpacer_5">
334 <property name="orientation">
335 <enum>Qt::Vertical</enum>
336 </property>
337 <property name="sizeType">
338 <enum>QSizePolicy::Preferred</enum>
339 </property>
340 <property name="sizeHint" stdset="0">
341 <size>
342 <width>20</width>
343 <height>0</height>
344 </size>
345 </property>
346 </spacer>
347 </item>
312 <item row="0" column="0"> 348 <item row="0" column="0">
313 <widget class="QCheckBox" name="enable_graphics_debugging"> 349 <widget class="QCheckBox" name="enable_graphics_debugging">
314 <property name="enabled"> 350 <property name="enabled">
@@ -322,55 +358,26 @@
322 </property> 358 </property>
323 </widget> 359 </widget>
324 </item> 360 </item>
325 <item row="6" column="0"> 361 <item row="5" column="0">
326 <widget class="QCheckBox" name="dump_macros"> 362 <widget class="QCheckBox" name="dump_shaders">
327 <property name="enabled"> 363 <property name="enabled">
328 <bool>true</bool> 364 <bool>true</bool>
329 </property> 365 </property>
330 <property name="toolTip"> 366 <property name="toolTip">
331 <string>When checked, it will dump all the macro programs of the GPU</string> 367 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
332 </property> 368 </property>
333 <property name="text"> 369 <property name="text">
334 <string>Dump Maxwell Macros</string> 370 <string>Dump Game Shaders</string>
335 </property> 371 </property>
336 </widget> 372 </widget>
337 </item> 373 </item>
338 <item row="1" column="0"> 374 <item row="1" column="0">
339 <widget class="QCheckBox" name="enable_shader_feedback"> 375 <widget class="QCheckBox" name="enable_renderdoc_hotkey">
340 <property name="toolTip">
341 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
342 </property>
343 <property name="text">
344 <string>Enable Shader Feedback</string>
345 </property>
346 </widget>
347 </item>
348 <item row="2" column="0">
349 <widget class="QCheckBox" name="enable_nsight_aftermath">
350 <property name="toolTip">
351 <string>When checked, it enables Nsight Aftermath crash dumps</string>
352 </property>
353 <property name="text"> 376 <property name="text">
354 <string>Enable Nsight Aftermath</string> 377 <string>Enable Renderdoc Hotkey</string>
355 </property> 378 </property>
356 </widget> 379 </widget>
357 </item> 380 </item>
358 <item row="8" column="0">
359 <spacer name="verticalSpacer_5">
360 <property name="orientation">
361 <enum>Qt::Vertical</enum>
362 </property>
363 <property name="sizeType">
364 <enum>QSizePolicy::Preferred</enum>
365 </property>
366 <property name="sizeHint" stdset="0">
367 <size>
368 <width>20</width>
369 <height>0</height>
370 </size>
371 </property>
372 </spacer>
373 </item>
374 </layout> 381 </layout>
375 </widget> 382 </widget>
376 </item> 383 </item>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 3c6bb3eb1..0ad95cc02 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -4,6 +4,7 @@
4#include <memory> 4#include <memory>
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "common/settings.h" 6#include "common/settings.h"
7#include "common/settings_enums.h"
7#include "core/core.h" 8#include "core/core.h"
8#include "ui_configure.h" 9#include "ui_configure.h"
9#include "vk_device_info.h" 10#include "vk_device_info.h"
@@ -41,16 +42,19 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
41 general_tab{std::make_unique<ConfigureGeneral>(system_, nullptr, *builder, this)}, 42 general_tab{std::make_unique<ConfigureGeneral>(system_, nullptr, *builder, this)},
42 graphics_advanced_tab{ 43 graphics_advanced_tab{
43 std::make_unique<ConfigureGraphicsAdvanced>(system_, nullptr, *builder, this)}, 44 std::make_unique<ConfigureGraphicsAdvanced>(system_, nullptr, *builder, this)},
45 ui_tab{std::make_unique<ConfigureUi>(system_, this)},
44 graphics_tab{std::make_unique<ConfigureGraphics>( 46 graphics_tab{std::make_unique<ConfigureGraphics>(
45 system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, 47 system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); },
48 [this](Settings::AspectRatio ratio, Settings::ResolutionSetup setup) {
49 ui_tab->UpdateScreenshotInfo(ratio, setup);
50 },
46 nullptr, *builder, this)}, 51 nullptr, *builder, this)},
47 hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)}, 52 hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)},
48 input_tab{std::make_unique<ConfigureInput>(system_, this)}, 53 input_tab{std::make_unique<ConfigureInput>(system_, this)},
49 network_tab{std::make_unique<ConfigureNetwork>(system_, this)}, 54 network_tab{std::make_unique<ConfigureNetwork>(system_, this)},
50 profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)}, 55 profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)},
51 system_tab{std::make_unique<ConfigureSystem>(system_, nullptr, *builder, this)}, 56 system_tab{std::make_unique<ConfigureSystem>(system_, nullptr, *builder, this)},
52 ui_tab{std::make_unique<ConfigureUi>(system_, this)}, web_tab{std::make_unique<ConfigureWeb>( 57 web_tab{std::make_unique<ConfigureWeb>(this)} {
53 this)} {
54 Settings::SetConfiguringGlobal(true); 58 Settings::SetConfiguringGlobal(true);
55 59
56 ui->setupUi(this); 60 ui->setupUi(this);
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 96e9a8c3e..b28ce288c 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -81,12 +81,12 @@ private:
81 std::unique_ptr<ConfigureFilesystem> filesystem_tab; 81 std::unique_ptr<ConfigureFilesystem> filesystem_tab;
82 std::unique_ptr<ConfigureGeneral> general_tab; 82 std::unique_ptr<ConfigureGeneral> general_tab;
83 std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab; 83 std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab;
84 std::unique_ptr<ConfigureUi> ui_tab;
84 std::unique_ptr<ConfigureGraphics> graphics_tab; 85 std::unique_ptr<ConfigureGraphics> graphics_tab;
85 std::unique_ptr<ConfigureHotkeys> hotkeys_tab; 86 std::unique_ptr<ConfigureHotkeys> hotkeys_tab;
86 std::unique_ptr<ConfigureInput> input_tab; 87 std::unique_ptr<ConfigureInput> input_tab;
87 std::unique_ptr<ConfigureNetwork> network_tab; 88 std::unique_ptr<ConfigureNetwork> network_tab;
88 std::unique_ptr<ConfigureProfileManager> profile_tab; 89 std::unique_ptr<ConfigureProfileManager> profile_tab;
89 std::unique_ptr<ConfigureSystem> system_tab; 90 std::unique_ptr<ConfigureSystem> system_tab;
90 std::unique_ptr<ConfigureUi> ui_tab;
91 std::unique_ptr<ConfigureWeb> web_tab; 91 std::unique_ptr<ConfigureWeb> web_tab;
92}; 92};
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index a94fbc89a..fd6bebf0f 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -24,6 +24,7 @@
24#include <QtCore/qobjectdefs.h> 24#include <QtCore/qobjectdefs.h>
25#include <qabstractbutton.h> 25#include <qabstractbutton.h>
26#include <qboxlayout.h> 26#include <qboxlayout.h>
27#include <qcombobox.h>
27#include <qcoreevent.h> 28#include <qcoreevent.h>
28#include <qglobal.h> 29#include <qglobal.h>
29#include <qgridlayout.h> 30#include <qgridlayout.h>
@@ -77,13 +78,16 @@ static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode)
77 } 78 }
78} 79}
79 80
80ConfigureGraphics::ConfigureGraphics(const Core::System& system_, 81ConfigureGraphics::ConfigureGraphics(
81 std::vector<VkDeviceInfo::Record>& records_, 82 const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_,
82 const std::function<void()>& expose_compute_option_, 83 const std::function<void()>& expose_compute_option_,
83 std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_, 84 const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>&
84 const ConfigurationShared::Builder& builder, QWidget* parent) 85 update_aspect_ratio_,
86 std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
87 const ConfigurationShared::Builder& builder, QWidget* parent)
85 : ConfigurationShared::Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, 88 : ConfigurationShared::Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphics>()},
86 records{records_}, expose_compute_option{expose_compute_option_}, system{system_}, 89 records{records_}, expose_compute_option{expose_compute_option_},
90 update_aspect_ratio{update_aspect_ratio_}, system{system_},
87 combobox_translations{builder.ComboboxTranslations()}, 91 combobox_translations{builder.ComboboxTranslations()},
88 shader_mapping{ 92 shader_mapping{
89 combobox_translations.at(Settings::EnumMetadata<Settings::ShaderBackend>::Index())} { 93 combobox_translations.at(Settings::EnumMetadata<Settings::ShaderBackend>::Index())} {
@@ -140,6 +144,26 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_,
140 UpdateBackgroundColorButton(new_bg_color); 144 UpdateBackgroundColorButton(new_bg_color);
141 }); 145 });
142 146
147 const auto& update_screenshot_info = [this, &builder]() {
148 const auto& combobox_enumerations = builder.ComboboxTranslations().at(
149 Settings::EnumMetadata<Settings::AspectRatio>::Index());
150 const auto index = aspect_ratio_combobox->currentIndex();
151 const auto ratio = static_cast<Settings::AspectRatio>(combobox_enumerations[index].first);
152
153 const auto& combobox_enumerations_resolution = builder.ComboboxTranslations().at(
154 Settings::EnumMetadata<Settings::ResolutionSetup>::Index());
155 const auto res_index = resolution_combobox->currentIndex();
156 const auto setup = static_cast<Settings::ResolutionSetup>(
157 combobox_enumerations_resolution[res_index].first);
158
159 update_aspect_ratio(ratio, setup);
160 };
161
162 connect(aspect_ratio_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
163 update_screenshot_info);
164 connect(resolution_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
165 update_screenshot_info);
166
143 api_combobox->setEnabled(!UISettings::values.has_broken_vulkan && api_combobox->isEnabled()); 167 api_combobox->setEnabled(!UISettings::values.has_broken_vulkan && api_combobox->isEnabled());
144 ui->api_widget->setEnabled( 168 ui->api_widget->setEnabled(
145 (!UISettings::values.has_broken_vulkan || Settings::IsConfiguringGlobal()) && 169 (!UISettings::values.has_broken_vulkan || Settings::IsConfiguringGlobal()) &&
@@ -169,14 +193,10 @@ void ConfigureGraphics::PopulateVSyncModeSelection() {
169 : vsync_mode_combobox_enum_map[current_index]; 193 : vsync_mode_combobox_enum_map[current_index];
170 int index{}; 194 int index{};
171 const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device 195 const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device
172 if (device == -1) {
173 // Invalid device
174 return;
175 }
176 196
177 const auto& present_modes = //< relevant vector of present modes for the selected device or API 197 const auto& present_modes = //< relevant vector of present modes for the selected device or API
178 backend == Settings::RendererBackend::Vulkan ? device_present_modes[device] 198 backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device]
179 : default_present_modes; 199 : default_present_modes;
180 200
181 vsync_mode_combobox->clear(); 201 vsync_mode_combobox->clear();
182 vsync_mode_combobox_enum_map.clear(); 202 vsync_mode_combobox_enum_map.clear();
@@ -280,6 +300,14 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
280 // Keep track of vsync_mode's combobox so we can populate it 300 // Keep track of vsync_mode's combobox so we can populate it
281 vsync_mode_combobox = widget->combobox; 301 vsync_mode_combobox = widget->combobox;
282 hold_graphics.emplace(setting->Id(), widget); 302 hold_graphics.emplace(setting->Id(), widget);
303 } else if (setting->Id() == Settings::values.aspect_ratio.Id()) {
304 // Keep track of the aspect ratio combobox to update other UI tabs that need it
305 aspect_ratio_combobox = widget->combobox;
306 hold_graphics.emplace(setting->Id(), widget);
307 } else if (setting->Id() == Settings::values.resolution_setup.Id()) {
308 // Keep track of the resolution combobox to update other UI tabs that need it
309 resolution_combobox = widget->combobox;
310 hold_graphics.emplace(setting->Id(), widget);
283 } else { 311 } else {
284 hold_graphics.emplace(setting->Id(), widget); 312 hold_graphics.emplace(setting->Id(), widget);
285 } 313 }
@@ -465,11 +493,19 @@ void ConfigureGraphics::RetrieveVulkanDevices() {
465} 493}
466 494
467Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { 495Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
468 if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { 496 const auto selected_backend = [&]() {
469 return Settings::values.renderer_backend.GetValue(true); 497 if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) {
498 return Settings::values.renderer_backend.GetValue(true);
499 }
500 return static_cast<Settings::RendererBackend>(
501 combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
502 .at(api_combobox->currentIndex())
503 .first);
504 }();
505
506 if (selected_backend == Settings::RendererBackend::Vulkan &&
507 UISettings::values.has_broken_vulkan) {
508 return Settings::RendererBackend::OpenGL;
470 } 509 }
471 return static_cast<Settings::RendererBackend>( 510 return selected_backend;
472 combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
473 .at(api_combobox->currentIndex())
474 .first);
475} 511}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 02d9b00f1..9c24a56db 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -14,6 +14,7 @@
14#include <qobjectdefs.h> 14#include <qobjectdefs.h>
15#include <vulkan/vulkan_core.h> 15#include <vulkan/vulkan_core.h>
16#include "common/common_types.h" 16#include "common/common_types.h"
17#include "common/settings_enums.h"
17#include "configuration/shared_translation.h" 18#include "configuration/shared_translation.h"
18#include "vk_device_info.h" 19#include "vk_device_info.h"
19#include "yuzu/configuration/configuration_shared.h" 20#include "yuzu/configuration/configuration_shared.h"
@@ -43,12 +44,13 @@ class Builder;
43 44
44class ConfigureGraphics : public ConfigurationShared::Tab { 45class ConfigureGraphics : public ConfigurationShared::Tab {
45public: 46public:
46 explicit ConfigureGraphics(const Core::System& system_, 47 explicit ConfigureGraphics(
47 std::vector<VkDeviceInfo::Record>& records, 48 const Core::System& system_, std::vector<VkDeviceInfo::Record>& records,
48 const std::function<void()>& expose_compute_option_, 49 const std::function<void()>& expose_compute_option,
49 std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group, 50 const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>&
50 const ConfigurationShared::Builder& builder, 51 update_aspect_ratio,
51 QWidget* parent = nullptr); 52 std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
53 const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
52 ~ConfigureGraphics() override; 54 ~ConfigureGraphics() override;
53 55
54 void ApplyConfiguration() override; 56 void ApplyConfiguration() override;
@@ -91,6 +93,7 @@ private:
91 u32 vulkan_device{}; 93 u32 vulkan_device{};
92 Settings::ShaderBackend shader_backend{}; 94 Settings::ShaderBackend shader_backend{};
93 const std::function<void()>& expose_compute_option; 95 const std::function<void()>& expose_compute_option;
96 const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)> update_aspect_ratio;
94 97
95 const Core::System& system; 98 const Core::System& system;
96 const ConfigurationShared::ComboboxTranslationMap& combobox_translations; 99 const ConfigurationShared::ComboboxTranslationMap& combobox_translations;
@@ -104,4 +107,6 @@ private:
104 QWidget* vulkan_device_widget; 107 QWidget* vulkan_device_widget;
105 QWidget* api_widget; 108 QWidget* api_widget;
106 QWidget* shader_backend_widget; 109 QWidget* shader_backend_widget;
110 QComboBox* aspect_ratio_combobox;
111 QComboBox* resolution_combobox;
107}; 112};
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 7fce85bca..e8f9ebfd8 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -4,6 +4,8 @@
4#include <memory> 4#include <memory>
5#include <thread> 5#include <thread>
6 6
7#include "common/settings.h"
8#include "common/settings_enums.h"
7#include "core/core.h" 9#include "core/core.h"
8#include "core/hid/emulated_controller.h" 10#include "core/hid/emulated_controller.h"
9#include "core/hid/hid_core.h" 11#include "core/hid/hid_core.h"
@@ -197,9 +199,11 @@ void ConfigureInput::ApplyConfiguration() {
197 199
198 advanced->ApplyConfiguration(); 200 advanced->ApplyConfiguration();
199 201
200 const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue(); 202 const bool pre_docked_mode = Settings::IsDockedMode();
201 Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked()); 203 const bool docked_mode_selected = ui->radioDocked->isChecked();
202 OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system); 204 Settings::values.use_docked_mode.SetValue(
205 docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
206 OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
203 207
204 Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked()); 208 Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
205 Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked()); 209 Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
@@ -267,8 +271,8 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
267 ui->radioDocked->setEnabled(!is_handheld); 271 ui->radioDocked->setEnabled(!is_handheld);
268 ui->radioUndocked->setEnabled(!is_handheld); 272 ui->radioUndocked->setEnabled(!is_handheld);
269 273
270 ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue()); 274 ui->radioDocked->setChecked(Settings::IsDockedMode());
271 ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue()); 275 ui->radioUndocked->setChecked(!Settings::IsDockedMode());
272 276
273 // Also force into undocked mode if the controller type is handheld. 277 // Also force into undocked mode if the controller type is handheld.
274 if (is_handheld) { 278 if (is_handheld) {
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index cd8b3012e..b91d6ad4a 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -17,6 +17,8 @@
17#include <QTimer> 17#include <QTimer>
18 18
19#include "common/fs/fs_util.h" 19#include "common/fs/fs_util.h"
20#include "common/settings_enums.h"
21#include "common/settings_input.h"
20#include "configuration/shared_widget.h" 22#include "configuration/shared_widget.h"
21#include "core/core.h" 23#include "core/core.h"
22#include "core/file_sys/control_metadata.h" 24#include "core/file_sys/control_metadata.h"
@@ -57,7 +59,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
57 std::make_unique<ConfigureGraphicsAdvanced>(system_, tab_group, *builder, this); 59 std::make_unique<ConfigureGraphicsAdvanced>(system_, tab_group, *builder, this);
58 graphics_tab = std::make_unique<ConfigureGraphics>( 60 graphics_tab = std::make_unique<ConfigureGraphics>(
59 system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, 61 system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); },
60 tab_group, *builder, this); 62 [](Settings::AspectRatio, Settings::ResolutionSetup) {}, tab_group, *builder, this);
61 input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this); 63 input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this);
62 system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this); 64 system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this);
63 65
@@ -97,6 +99,12 @@ void ConfigurePerGame::ApplyConfiguration() {
97 addons_tab->ApplyConfiguration(); 99 addons_tab->ApplyConfiguration();
98 input_tab->ApplyConfiguration(); 100 input_tab->ApplyConfiguration();
99 101
102 if (Settings::IsDockedMode() && Settings::values.players.GetValue()[0].controller_type ==
103 Settings::ControllerType::Handheld) {
104 Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
105 Settings::values.use_docked_mode.SetGlobal(true);
106 }
107
100 system.ApplySettings(); 108 system.ApplySettings();
101 Settings::LogSettings(); 109 Settings::LogSettings();
102 110
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index c4833f4e7..0c8e5c8b4 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -106,6 +106,11 @@ void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) {
106 push(Settings::values.linkage.by_category[Settings::Category::System]); 106 push(Settings::values.linkage.by_category[Settings::Category::System]);
107 107
108 for (auto setting : settings) { 108 for (auto setting : settings) {
109 if (setting->Id() == Settings::values.use_docked_mode.Id() &&
110 Settings::IsConfiguringGlobal()) {
111 continue;
112 }
113
109 ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs); 114 ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
110 115
111 if (widget == nullptr) { 116 if (widget == nullptr) {
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 2ebb80302..a9fde9f4f 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -1,18 +1,32 @@
1// SPDX-FileCopyrightText: 2016 Citra Emulator Project 1// SPDX-FileCopyrightText: 2016 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "yuzu/configuration/configure_ui.h"
5
4#include <array> 6#include <array>
7#include <cstdlib>
8#include <set>
9#include <stdexcept>
10#include <string>
5#include <utility> 11#include <utility>
6#include <QFileDialog>
7 12
13#include <QCheckBox>
14#include <QComboBox>
15#include <QCoreApplication>
8#include <QDirIterator> 16#include <QDirIterator>
17#include <QFileDialog>
18#include <QString>
19#include <QToolButton>
20#include <QVariant>
21
9#include "common/common_types.h" 22#include "common/common_types.h"
10#include "common/fs/path_util.h" 23#include "common/fs/path_util.h"
11#include "common/logging/log.h" 24#include "common/logging/log.h"
12#include "common/settings.h" 25#include "common/settings.h"
26#include "common/settings_enums.h"
13#include "core/core.h" 27#include "core/core.h"
28#include "core/frontend/framebuffer_layout.h"
14#include "ui_configure_ui.h" 29#include "ui_configure_ui.h"
15#include "yuzu/configuration/configure_ui.h"
16#include "yuzu/uisettings.h" 30#include "yuzu/uisettings.h"
17 31
18namespace { 32namespace {
@@ -54,8 +68,40 @@ QString GetTranslatedRowTextName(size_t index) {
54} 68}
55} // Anonymous namespace 69} // Anonymous namespace
56 70
71static float GetUpFactor(Settings::ResolutionSetup res_setup) {
72 Settings::ResolutionScalingInfo info{};
73 Settings::TranslateResolutionInfo(res_setup, info);
74 return info.up_factor;
75}
76
77static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* parent) {
78 screenshot_height->clear();
79
80 const auto& enumeration =
81 Settings::EnumMetadata<Settings::ResolutionSetup>::Canonicalizations();
82 std::set<u32> resolutions{};
83 for (const auto& [name, value] : enumeration) {
84 const float up_factor = GetUpFactor(value);
85 u32 height_undocked = Layout::ScreenUndocked::Height * up_factor;
86 u32 height_docked = Layout::ScreenDocked::Height * up_factor;
87 resolutions.emplace(height_undocked);
88 resolutions.emplace(height_docked);
89 }
90
91 screenshot_height->addItem(parent->tr("Auto", "Screenshot height option"));
92 for (const auto res : resolutions) {
93 screenshot_height->addItem(QString::fromStdString(std::to_string(res)));
94 }
95}
96
97static u32 ScreenshotDimensionToInt(const QString& height) {
98 return std::strtoul(height.toUtf8(), nullptr, 0);
99}
100
57ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) 101ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
58 : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()}, system{system_} { 102 : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()},
103 ratio{Settings::values.aspect_ratio.GetValue()},
104 resolution_setting{Settings::values.resolution_setup.GetValue()}, system{system_} {
59 ui->setupUi(this); 105 ui->setupUi(this);
60 106
61 InitializeLanguageComboBox(); 107 InitializeLanguageComboBox();
@@ -68,6 +114,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
68 InitializeIconSizeComboBox(); 114 InitializeIconSizeComboBox();
69 InitializeRowComboBoxes(); 115 InitializeRowComboBoxes();
70 116
117 PopulateResolutionComboBox(ui->screenshot_height, this);
118
71 SetConfiguration(); 119 SetConfiguration();
72 120
73 // Force game list reload if any of the relevant settings are changed. 121 // Force game list reload if any of the relevant settings are changed.
@@ -104,6 +152,10 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
104 ui->screenshot_path_edit->setText(dir); 152 ui->screenshot_path_edit->setText(dir);
105 } 153 }
106 }); 154 });
155
156 connect(ui->screenshot_height, &QComboBox::currentTextChanged, [this]() { UpdateWidthText(); });
157
158 UpdateWidthText();
107} 159}
108 160
109ConfigureUi::~ConfigureUi() = default; 161ConfigureUi::~ConfigureUi() = default;
@@ -123,6 +175,10 @@ void ConfigureUi::ApplyConfiguration() {
123 UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); 175 UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
124 Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir, 176 Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir,
125 ui->screenshot_path_edit->text().toStdString()); 177 ui->screenshot_path_edit->text().toStdString());
178
179 const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
180 UISettings::values.screenshot_height.SetValue(height);
181
126 system.ApplySettings(); 182 system.ApplySettings();
127} 183}
128 184
@@ -147,6 +203,13 @@ void ConfigureUi::SetConfiguration() {
147 UISettings::values.enable_screenshot_save_as.GetValue()); 203 UISettings::values.enable_screenshot_save_as.GetValue());
148 ui->screenshot_path_edit->setText(QString::fromStdString( 204 ui->screenshot_path_edit->setText(QString::fromStdString(
149 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir))); 205 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)));
206
207 const auto height = UISettings::values.screenshot_height.GetValue();
208 if (height == 0) {
209 ui->screenshot_height->setCurrentIndex(0);
210 } else {
211 ui->screenshot_height->setCurrentText(QStringLiteral("%1").arg(height));
212 }
150} 213}
151 214
152void ConfigureUi::changeEvent(QEvent* event) { 215void ConfigureUi::changeEvent(QEvent* event) {
@@ -317,3 +380,29 @@ void ConfigureUi::OnLanguageChanged(int index) {
317 380
318 emit LanguageChanged(ui->language_combobox->itemData(index).toString()); 381 emit LanguageChanged(ui->language_combobox->itemData(index).toString());
319} 382}
383
384void ConfigureUi::UpdateWidthText() {
385 const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
386 const u32 width = UISettings::CalculateWidth(height, ratio);
387 if (height == 0) {
388 const auto up_factor = GetUpFactor(resolution_setting);
389 const u32 height_docked = Layout::ScreenDocked::Height * up_factor;
390 const u32 width_docked = UISettings::CalculateWidth(height_docked, ratio);
391 const u32 height_undocked = Layout::ScreenUndocked::Height * up_factor;
392 const u32 width_undocked = UISettings::CalculateWidth(height_undocked, ratio);
393 ui->screenshot_width->setText(tr("Auto (%1 x %2, %3 x %4)", "Screenshot width value")
394 .arg(width_undocked)
395 .arg(height_undocked)
396 .arg(width_docked)
397 .arg(height_docked));
398 } else {
399 ui->screenshot_width->setText(QStringLiteral("%1 x").arg(width));
400 }
401}
402
403void ConfigureUi::UpdateScreenshotInfo(Settings::AspectRatio ratio_,
404 Settings::ResolutionSetup resolution_setting_) {
405 ratio = ratio_;
406 resolution_setting = resolution_setting_;
407 UpdateWidthText();
408}
diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h
index 95af8370e..2a2563a13 100644
--- a/src/yuzu/configuration/configure_ui.h
+++ b/src/yuzu/configuration/configure_ui.h
@@ -5,6 +5,7 @@
5 5
6#include <memory> 6#include <memory>
7#include <QWidget> 7#include <QWidget>
8#include "common/settings_enums.h"
8 9
9namespace Core { 10namespace Core {
10class System; 11class System;
@@ -23,6 +24,9 @@ public:
23 24
24 void ApplyConfiguration(); 25 void ApplyConfiguration();
25 26
27 void UpdateScreenshotInfo(Settings::AspectRatio ratio,
28 Settings::ResolutionSetup resolution_info);
29
26private slots: 30private slots:
27 void OnLanguageChanged(int index); 31 void OnLanguageChanged(int index);
28 32
@@ -44,7 +48,11 @@ private:
44 void UpdateFirstRowComboBox(bool init = false); 48 void UpdateFirstRowComboBox(bool init = false);
45 void UpdateSecondRowComboBox(bool init = false); 49 void UpdateSecondRowComboBox(bool init = false);
46 50
51 void UpdateWidthText();
52
47 std::unique_ptr<Ui::ConfigureUi> ui; 53 std::unique_ptr<Ui::ConfigureUi> ui;
48 54
55 Settings::AspectRatio ratio;
56 Settings::ResolutionSetup resolution_setting;
49 Core::System& system; 57 Core::System& system;
50}; 58};
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index 10bb27312..cb66ef104 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -7,7 +7,7 @@
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>363</width> 9 <width>363</width>
10 <height>562</height> 10 <height>603</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
@@ -201,6 +201,41 @@
201 </item> 201 </item>
202 </layout> 202 </layout>
203 </item> 203 </item>
204 <item>
205 <layout class="QGridLayout" name="gridLayout">
206 <property name="spacing">
207 <number>6</number>
208 </property>
209 <item row="0" column="1">
210 <layout class="QHBoxLayout" name="horizontalLayout_5">
211 <item>
212 <widget class="QLabel" name="screenshot_width">
213 <property name="text">
214 <string>TextLabel</string>
215 </property>
216 <property name="alignment">
217 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
218 </property>
219 </widget>
220 </item>
221 <item>
222 <widget class="QComboBox" name="screenshot_height">
223 <property name="editable">
224 <bool>true</bool>
225 </property>
226 </widget>
227 </item>
228 </layout>
229 </item>
230 <item row="0" column="0">
231 <widget class="QLabel" name="label_3">
232 <property name="text">
233 <string>Resolution:</string>
234 </property>
235 </widget>
236 </item>
237 </layout>
238 </item>
204 </layout> 239 </layout>
205 </item> 240 </item>
206 </layout> 241 </layout>
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 335810788..276bdbaba 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -135,7 +135,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
135 INSERT(Settings, region_index, "Region:", ""); 135 INSERT(Settings, region_index, "Region:", "");
136 INSERT(Settings, time_zone_index, "Time Zone:", ""); 136 INSERT(Settings, time_zone_index, "Time Zone:", "");
137 INSERT(Settings, sound_index, "Sound Output Mode:", ""); 137 INSERT(Settings, sound_index, "Sound Output Mode:", "");
138 INSERT(Settings, use_docked_mode, "", ""); 138 INSERT(Settings, use_docked_mode, "Console Mode:", "");
139 INSERT(Settings, current_user, "", ""); 139 INSERT(Settings, current_user, "", "");
140 140
141 // Controls 141 // Controls
@@ -379,6 +379,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
379 PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"), 379 PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"),
380 PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"), 380 PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"),
381 }}); 381 }});
382 translations->insert(
383 {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
384 {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
382 385
383#undef PAIR 386#undef PAIR
384#undef CTX_PAIR 387#undef CTX_PAIR
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index bdb38c8ea..ea8d7add4 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -23,6 +23,7 @@
23#include <QLineEdit> 23#include <QLineEdit>
24#include <QObject> 24#include <QObject>
25#include <QPushButton> 25#include <QPushButton>
26#include <QRadioButton>
26#include <QRegularExpression> 27#include <QRegularExpression>
27#include <QSizePolicy> 28#include <QSizePolicy>
28#include <QSlider> 29#include <QSlider>
@@ -62,7 +63,7 @@ static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) {
62 return tr("%", context.c_str()); 63 return tr("%", context.c_str());
63 } 64 }
64 65
65 return QStringLiteral(""); 66 return default_suffix;
66} 67}
67 68
68QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { 69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) {
@@ -70,7 +71,7 @@ QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* paren
70 71
71 QStyle* style = parent->style(); 72 QStyle* style = parent->style();
72 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); 73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton));
73 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(""), parent); 74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent);
74 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); 75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count));
75 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
76 77
@@ -150,7 +151,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
150 return -1; 151 return -1;
151 }; 152 };
152 153
153 const u32 setting_value = std::stoi(setting.ToString()); 154 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
154 combobox->setCurrentIndex(find_index(setting_value)); 155 combobox->setCurrentIndex(find_index(setting_value));
155 156
156 serializer = [this, enumeration]() { 157 serializer = [this, enumeration]() {
@@ -159,7 +160,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
159 }; 160 };
160 161
161 restore_func = [this, find_index]() { 162 restore_func = [this, find_index]() {
162 const u32 global_value = std::stoi(RelevantDefault(setting)); 163 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
163 combobox->setCurrentIndex(find_index(global_value)); 164 combobox->setCurrentIndex(find_index(global_value));
164 }; 165 };
165 166
@@ -171,6 +172,65 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
171 return combobox; 172 return combobox;
172} 173}
173 174
175QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
176 std::function<void()>& restore_func,
177 const std::function<void()>& touch) {
178 const auto type = setting.EnumIndex();
179
180 QWidget* group = new QWidget(this);
181 QHBoxLayout* layout = new QHBoxLayout(group);
182 layout->setContentsMargins(0, 0, 0, 0);
183 group->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
184
185 const ComboboxTranslations* enumeration{nullptr};
186 if (combobox_enumerations.contains(type)) {
187 enumeration = &combobox_enumerations.at(type);
188 for (const auto& [id, name] : *enumeration) {
189 QRadioButton* radio_button = new QRadioButton(name, group);
190 layout->addWidget(radio_button);
191 radio_buttons.push_back({id, radio_button});
192 }
193 } else {
194 return group;
195 }
196
197 const auto get_selected = [=]() -> int {
198 for (const auto& [id, button] : radio_buttons) {
199 if (button->isChecked()) {
200 return id;
201 }
202 }
203 return -1;
204 };
205
206 const auto set_index = [=](u32 value) {
207 for (const auto& [id, button] : radio_buttons) {
208 button->setChecked(id == value);
209 }
210 };
211
212 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
213 set_index(setting_value);
214
215 serializer = [get_selected]() {
216 int current = get_selected();
217 return std::to_string(current);
218 };
219
220 restore_func = [this, set_index]() {
221 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
222 set_index(global_value);
223 };
224
225 if (!Settings::IsConfiguringGlobal()) {
226 for (const auto& [id, button] : radio_buttons) {
227 QObject::connect(button, &QAbstractButton::clicked, [touch]() { touch(); });
228 }
229 }
230
231 return group;
232}
233
174QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer, 234QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
175 std::function<void()>& restore_func, 235 std::function<void()>& restore_func,
176 const std::function<void()>& touch, bool managed) { 236 const std::function<void()>& touch, bool managed) {
@@ -195,6 +255,59 @@ QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
195 return line_edit; 255 return line_edit;
196} 256}
197 257
258static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
259 QLabel* feedback, const QString& use_format, QSlider* slider,
260 std::function<std::string()>& serializer,
261 std::function<void()>& restore_func) {
262 const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
263
264 const auto update_feedback = [=](int value) {
265 int present = (reversed ? max_val - value : value) * multiplier + 0.5f;
266 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
267 };
268
269 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
270 update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0));
271
272 slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0));
273 slider->setMaximum(max_val);
274 slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0));
275
276 serializer = [slider]() { return std::to_string(slider->value()); };
277 restore_func = [slider, &setting]() {
278 slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0));
279 };
280}
281
282static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
283 QLabel* feedback, const QString& use_format, QSlider* slider,
284 std::function<std::string()>& serializer,
285 std::function<void()>& restore_func) {
286 const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr);
287 const float min_val = std::strtof(setting.MinVal().c_str(), nullptr);
288 const float use_multiplier =
289 multiplier == default_multiplier ? default_float_multiplier : multiplier;
290
291 const auto update_feedback = [=](float value) {
292 int present = (reversed ? max_val - value : value) + 0.5f;
293 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
294 };
295
296 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
297 update_feedback(std::strtof(setting.ToString().c_str(), nullptr));
298
299 slider->setMinimum(min_val * use_multiplier);
300 slider->setMaximum(max_val * use_multiplier);
301 slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier);
302
303 serializer = [slider, use_multiplier]() {
304 return std::to_string(slider->value() / use_multiplier);
305 };
306 restore_func = [slider, &setting, use_multiplier]() {
307 slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier);
308 };
309}
310
198QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, 311QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix,
199 std::function<std::string()>& serializer, 312 std::function<std::string()>& serializer,
200 std::function<void()>& restore_func, 313 std::function<void()>& restore_func,
@@ -218,27 +331,20 @@ QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& gi
218 331
219 layout->setContentsMargins(0, 0, 0, 0); 332 layout->setContentsMargins(0, 0, 0, 0);
220 333
221 int max_val = std::stoi(setting.MaxVal()); 334 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
222
223 QString suffix =
224 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
225 335
226 const QString use_format = QStringLiteral("%1").append(suffix); 336 const QString use_format = QStringLiteral("%1").append(suffix);
227 337
228 QObject::connect(slider, &QAbstractSlider::valueChanged, [=](int value) { 338 if (setting.IsIntegral()) {
229 int present = (reversed ? max_val - value : value) * multiplier + 0.5f; 339 CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
230 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); 340 restore_func);
231 }); 341 } else {
232 342 CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
233 slider->setMinimum(std::stoi(setting.MinVal())); 343 restore_func);
234 slider->setMaximum(max_val); 344 }
235 slider->setValue(std::stoi(setting.ToString()));
236 345
237 slider->setInvertedAppearance(reversed); 346 slider->setInvertedAppearance(reversed);
238 347
239 serializer = [this]() { return std::to_string(slider->value()); };
240 restore_func = [this]() { slider->setValue(std::stoi(RelevantDefault(setting))); };
241
242 if (!Settings::IsConfiguringGlobal()) { 348 if (!Settings::IsConfiguringGlobal()) {
243 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); 349 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); });
244 } 350 }
@@ -250,14 +356,11 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
250 std::function<std::string()>& serializer, 356 std::function<std::string()>& serializer,
251 std::function<void()>& restore_func, 357 std::function<void()>& restore_func,
252 const std::function<void()>& touch) { 358 const std::function<void()>& touch) {
253 const int min_val = 359 const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0);
254 setting.Ranged() ? std::stoi(setting.MinVal()) : std::numeric_limits<int>::min(); 360 const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
255 const int max_val = 361 const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0);
256 setting.Ranged() ? std::stoi(setting.MaxVal()) : std::numeric_limits<int>::max();
257 const int default_val = std::stoi(setting.ToString());
258 362
259 QString suffix = 363 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
260 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
261 364
262 spinbox = new QSpinBox(this); 365 spinbox = new QSpinBox(this);
263 spinbox->setRange(min_val, max_val); 366 spinbox->setRange(min_val, max_val);
@@ -268,13 +371,13 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
268 serializer = [this]() { return std::to_string(spinbox->value()); }; 371 serializer = [this]() { return std::to_string(spinbox->value()); };
269 372
270 restore_func = [this]() { 373 restore_func = [this]() {
271 auto value{std::stol(RelevantDefault(setting))}; 374 auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)};
272 spinbox->setValue(value); 375 spinbox->setValue(value);
273 }; 376 };
274 377
275 if (!Settings::IsConfiguringGlobal()) { 378 if (!Settings::IsConfiguringGlobal()) {
276 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { 379 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() {
277 if (spinbox->value() != std::stoi(setting.ToStringGlobal())) { 380 if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) {
278 touch(); 381 touch();
279 } 382 }
280 }); 383 });
@@ -283,6 +386,42 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
283 return spinbox; 386 return spinbox;
284} 387}
285 388
389QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix,
390 std::function<std::string()>& serializer,
391 std::function<void()>& restore_func,
392 const std::function<void()>& touch) {
393 const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr);
394 const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr);
395 const auto default_val = std::strtod(setting.ToString().c_str(), nullptr);
396
397 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
398
399 double_spinbox = new QDoubleSpinBox(this);
400 double_spinbox->setRange(min_val, max_val);
401 double_spinbox->setValue(default_val);
402 double_spinbox->setSuffix(suffix);
403 double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
404
405 serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); };
406
407 restore_func = [this]() {
408 auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)};
409 double_spinbox->setValue(value);
410 };
411
412 if (!Settings::IsConfiguringGlobal()) {
413 QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
414 [this, touch]() {
415 if (double_spinbox->value() !=
416 std::strtod(setting.ToStringGlobal().c_str(), nullptr)) {
417 touch();
418 }
419 });
420 }
421
422 return double_spinbox;
423}
424
286QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, 425QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
287 std::function<void()>& restore_func, 426 std::function<void()>& restore_func,
288 const std::function<void()>& touch) { 427 const std::function<void()>& touch) {
@@ -292,7 +431,8 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
292 } 431 }
293 432
294 auto to_hex = [=](const std::string& input) { 433 auto to_hex = [=](const std::string& input) {
295 return QString::fromStdString(fmt::format("{:08x}", std::stoul(input))); 434 return QString::fromStdString(
435 fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0)));
296 }; 436 };
297 437
298 QRegularExpressionValidator* regex = new QRegularExpressionValidator( 438 QRegularExpressionValidator* regex = new QRegularExpressionValidator(
@@ -305,7 +445,7 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
305 line_edit->setValidator(regex); 445 line_edit->setValidator(regex);
306 446
307 auto hex_to_dec = [this]() -> std::string { 447 auto hex_to_dec = [this]() -> std::string {
308 return std::to_string(std::stoul(line_edit->text().toStdString(), nullptr, 16)); 448 return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16));
309 }; 449 };
310 450
311 serializer = [hex_to_dec]() { return hex_to_dec(); }; 451 serializer = [hex_to_dec]() { return hex_to_dec(); };
@@ -325,7 +465,8 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
325 std::function<void()>& restore_func, 465 std::function<void()>& restore_func,
326 const std::function<void()>& touch) { 466 const std::function<void()>& touch) {
327 const long long current_time = QDateTime::currentSecsSinceEpoch(); 467 const long long current_time = QDateTime::currentSecsSinceEpoch();
328 const s64 the_time = disabled ? current_time : std::stoll(setting.ToString()); 468 const s64 the_time =
469 disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0);
329 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); 470 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time);
330 471
331 date_time_edit = new QDateTimeEdit(this); 472 date_time_edit = new QDateTimeEdit(this);
@@ -338,7 +479,7 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
338 auto get_clear_val = [this, restrict, current_time]() { 479 auto get_clear_val = [this, restrict, current_time]() {
339 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { 480 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() {
340 if (restrict && checkbox->checkState() == Qt::Checked) { 481 if (restrict && checkbox->checkState() == Qt::Checked) {
341 return std::stoll(RelevantDefault(setting)); 482 return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0);
342 } 483 }
343 return current_time; 484 return current_time;
344 }()); 485 }());
@@ -410,6 +551,8 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
410 return RequestType::Slider; 551 return RequestType::Slider;
411 case Settings::Specialization::Countable: 552 case Settings::Specialization::Countable:
412 return RequestType::SpinBox; 553 return RequestType::SpinBox;
554 case Settings::Specialization::Radio:
555 return RequestType::RadioGroup;
413 default: 556 default:
414 break; 557 break;
415 } 558 }
@@ -438,9 +581,12 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
438 if (setting.TypeId() == typeid(bool)) { 581 if (setting.TypeId() == typeid(bool)) {
439 data_component = CreateCheckBox(&setting, label, serializer, restore_func, touch); 582 data_component = CreateCheckBox(&setting, label, serializer, restore_func, touch);
440 } else if (setting.IsEnum()) { 583 } else if (setting.IsEnum()) {
441 data_component = CreateCombobox(serializer, restore_func, touch); 584 if (request == RequestType::RadioGroup) {
442 } else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) || 585 data_component = CreateRadioGroup(serializer, restore_func, touch);
443 type == typeid(s64) || type == typeid(u8)) { 586 } else {
587 data_component = CreateCombobox(serializer, restore_func, touch);
588 }
589 } else if (setting.IsIntegral()) {
444 switch (request) { 590 switch (request) {
445 case RequestType::Slider: 591 case RequestType::Slider:
446 case RequestType::ReverseSlider: 592 case RequestType::ReverseSlider:
@@ -467,6 +613,20 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
467 default: 613 default:
468 UNIMPLEMENTED(); 614 UNIMPLEMENTED();
469 } 615 }
616 } else if (setting.IsFloatingPoint()) {
617 switch (request) {
618 case RequestType::Default:
619 case RequestType::SpinBox:
620 data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch);
621 break;
622 case RequestType::Slider:
623 case RequestType::ReverseSlider:
624 data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
625 serializer, restore_func, touch);
626 break;
627 default:
628 UNIMPLEMENTED();
629 }
470 } else if (type == typeid(std::string)) { 630 } else if (type == typeid(std::string)) {
471 switch (request) { 631 switch (request) {
472 case RequestType::Default: 632 case RequestType::Default:
@@ -571,10 +731,10 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati
571 return std::pair{translations.at(id).first, translations.at(id).second}; 731 return std::pair{translations.at(id).first, translations.at(id).second};
572 } 732 }
573 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); 733 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label);
574 return std::pair{QString::fromStdString(setting_label), QStringLiteral("")}; 734 return std::pair{QString::fromStdString(setting_label), QStringLiteral()};
575 }(); 735 }();
576 736
577 if (label == QStringLiteral("")) { 737 if (label == QStringLiteral()) {
578 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", 738 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...",
579 setting.GetLabel()); 739 setting.GetLabel());
580 return; 740 return;
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
index e64693bab..226284cf3 100644
--- a/src/yuzu/configuration/shared_widget.h
+++ b/src/yuzu/configuration/shared_widget.h
@@ -22,6 +22,8 @@ class QObject;
22class QPushButton; 22class QPushButton;
23class QSlider; 23class QSlider;
24class QSpinBox; 24class QSpinBox;
25class QDoubleSpinBox;
26class QRadioButton;
25 27
26namespace Settings { 28namespace Settings {
27class BasicSetting; 29class BasicSetting;
@@ -38,9 +40,14 @@ enum class RequestType {
38 LineEdit, 40 LineEdit,
39 HexEdit, 41 HexEdit,
40 DateTimeEdit, 42 DateTimeEdit,
43 RadioGroup,
41 MaxEnum, 44 MaxEnum,
42}; 45};
43 46
47constexpr float default_multiplier{1.f};
48constexpr float default_float_multiplier{100.f};
49static const QString default_suffix = QStringLiteral();
50
44class Widget : public QWidget { 51class Widget : public QWidget {
45 Q_OBJECT 52 Q_OBJECT
46 53
@@ -64,8 +71,9 @@ public:
64 const ComboboxTranslationMap& combobox_translations, QWidget* parent, 71 const ComboboxTranslationMap& combobox_translations, QWidget* parent,
65 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, 72 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_,
66 RequestType request = RequestType::Default, bool managed = true, 73 RequestType request = RequestType::Default, bool managed = true,
67 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 74 float multiplier = default_multiplier,
68 const QString& suffix = QStringLiteral("")); 75 Settings::BasicSetting* other_setting = nullptr,
76 const QString& suffix = default_suffix);
69 virtual ~Widget(); 77 virtual ~Widget();
70 78
71 /** 79 /**
@@ -87,10 +95,12 @@ public:
87 QPushButton* restore_button{}; ///< Restore button for custom configurations 95 QPushButton* restore_button{}; ///< Restore button for custom configurations
88 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit 96 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit
89 QSpinBox* spinbox{}; 97 QSpinBox* spinbox{};
98 QDoubleSpinBox* double_spinbox{};
90 QCheckBox* checkbox{}; 99 QCheckBox* checkbox{};
91 QSlider* slider{}; 100 QSlider* slider{};
92 QComboBox* combobox{}; 101 QComboBox* combobox{};
93 QDateTimeEdit* date_time_edit{}; 102 QDateTimeEdit* date_time_edit{};
103 std::vector<std::pair<u32, QRadioButton*>> radio_buttons{};
94 104
95private: 105private:
96 void SetupComponent(const QString& label, std::function<void()>& load_func, bool managed, 106 void SetupComponent(const QString& label, std::function<void()>& load_func, bool managed,
@@ -106,6 +116,9 @@ private:
106 QWidget* CreateCombobox(std::function<std::string()>& serializer, 116 QWidget* CreateCombobox(std::function<std::string()>& serializer,
107 std::function<void()>& restore_func, 117 std::function<void()>& restore_func,
108 const std::function<void()>& touch); 118 const std::function<void()>& touch);
119 QWidget* CreateRadioGroup(std::function<std::string()>& serializer,
120 std::function<void()>& restore_func,
121 const std::function<void()>& touch);
109 QWidget* CreateLineEdit(std::function<std::string()>& serializer, 122 QWidget* CreateLineEdit(std::function<std::string()>& serializer,
110 std::function<void()>& restore_func, const std::function<void()>& touch, 123 std::function<void()>& restore_func, const std::function<void()>& touch,
111 bool managed = true); 124 bool managed = true);
@@ -120,6 +133,9 @@ private:
120 const std::function<void()>& touch); 133 const std::function<void()>& touch);
121 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, 134 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer,
122 std::function<void()>& restore_func, const std::function<void()>& touch); 135 std::function<void()>& restore_func, const std::function<void()>& touch);
136 QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer,
137 std::function<void()>& restore_func,
138 const std::function<void()>& touch);
123 139
124 QWidget* parent; 140 QWidget* parent;
125 const TranslationMap& translations; 141 const TranslationMap& translations;
@@ -139,14 +155,15 @@ public:
139 Widget* BuildWidget(Settings::BasicSetting* setting, 155 Widget* BuildWidget(Settings::BasicSetting* setting,
140 std::vector<std::function<void(bool)>>& apply_funcs, 156 std::vector<std::function<void(bool)>>& apply_funcs,
141 RequestType request = RequestType::Default, bool managed = true, 157 RequestType request = RequestType::Default, bool managed = true,
142 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 158 float multiplier = default_multiplier,
143 const QString& suffix = QStringLiteral("")) const; 159 Settings::BasicSetting* other_setting = nullptr,
160 const QString& suffix = default_suffix) const;
144 161
145 Widget* BuildWidget(Settings::BasicSetting* setting, 162 Widget* BuildWidget(Settings::BasicSetting* setting,
146 std::vector<std::function<void(bool)>>& apply_funcs, 163 std::vector<std::function<void(bool)>>& apply_funcs,
147 Settings::BasicSetting* other_setting, 164 Settings::BasicSetting* other_setting,
148 RequestType request = RequestType::Default, 165 RequestType request = RequestType::Default,
149 const QString& suffix = QStringLiteral("")) const; 166 const QString& suffix = default_suffix) const;
150 167
151 const ComboboxTranslationMap& ComboboxTranslations() const; 168 const ComboboxTranslationMap& ComboboxTranslations() const;
152 169
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 465084fea..f254c1e1c 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -214,13 +214,17 @@ void GameList::OnTextChanged(const QString& new_text) {
214 const int children_count = folder->rowCount(); 214 const int children_count = folder->rowCount();
215 for (int j = 0; j < children_count; ++j) { 215 for (int j = 0; j < children_count; ++j) {
216 ++children_total; 216 ++children_total;
217
217 const QStandardItem* child = folder->child(j, 0); 218 const QStandardItem* child = folder->child(j, 0);
219
220 const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong();
221
218 const QString file_path = 222 const QString file_path =
219 child->data(GameListItemPath::FullPathRole).toString().toLower(); 223 child->data(GameListItemPath::FullPathRole).toString().toLower();
220 const QString file_title = 224 const QString file_title =
221 child->data(GameListItemPath::TitleRole).toString().toLower(); 225 child->data(GameListItemPath::TitleRole).toString().toLower();
222 const QString file_program_id = 226 const QString file_program_id =
223 child->data(GameListItemPath::ProgramIdRole).toString().toLower(); 227 QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'});
224 228
225 // Only items which filename in combination with its title contains all words 229 // Only items which filename in combination with its title contains all words
226 // that are in the searchfield will be visible in the gamelist 230 // that are in the searchfield will be visible in the gamelist
@@ -231,7 +235,7 @@ void GameList::OnTextChanged(const QString& new_text) {
231 file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + 235 file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
232 file_title; 236 file_title;
233 if (ContainsAllWords(file_name, edit_filter_text) || 237 if (ContainsAllWords(file_name, edit_filter_text) ||
234 (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { 238 (file_program_id.count() == 16 && file_program_id.contains(edit_filter_text))) {
235 tree_view->setRowHidden(j, folder_index, false); 239 tree_view->setRowHidden(j, folder_index, false);
236 ++result_count; 240 ++result_count;
237 } else { 241 } else {
@@ -553,6 +557,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
553 QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); 557 QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS"));
554 QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); 558 QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS"));
555 QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); 559 QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
560 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
556 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 561 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
557 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 562 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
558#ifndef WIN32 563#ifndef WIN32
@@ -584,10 +589,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
584 emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); 589 emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
585 }); 590 });
586 connect(start_game, &QAction::triggered, [this, path]() { 591 connect(start_game, &QAction::triggered, [this, path]() {
587 emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal); 592 emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal,
593 AmLaunchType::UserInitiated);
588 }); 594 });
589 connect(start_game_global, &QAction::triggered, [this, path]() { 595 connect(start_game_global, &QAction::triggered, [this, path]() {
590 emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global); 596 emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global,
597 AmLaunchType::UserInitiated);
591 }); 598 });
592 connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { 599 connect(open_mod_location, &QAction::triggered, [this, program_id, path]() {
593 emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path); 600 emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
@@ -624,6 +631,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
624 connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { 631 connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() {
625 emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); 632 emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC);
626 }); 633 });
634 connect(verify_integrity, &QAction::triggered,
635 [this, path]() { emit VerifyIntegrityRequested(path); });
627 connect(copy_tid, &QAction::triggered, 636 connect(copy_tid, &QAction::triggered,
628 [this, program_id]() { emit CopyTIDRequested(program_id); }); 637 [this, program_id]() { emit CopyTIDRequested(program_id); });
629 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 638 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 6c2f75e53..1fcbbf0ba 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -28,6 +28,7 @@ class GameListWorker;
28class GameListSearchField; 28class GameListSearchField;
29class GameListDir; 29class GameListDir;
30class GMainWindow; 30class GMainWindow;
31enum class AmLaunchType;
31enum class StartGameType; 32enum class StartGameType;
32 33
33namespace FileSys { 34namespace FileSys {
@@ -103,7 +104,7 @@ public:
103 104
104signals: 105signals:
105 void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, 106 void BootGame(const QString& game_path, u64 program_id, std::size_t program_index,
106 StartGameType type); 107 StartGameType type, AmLaunchType launch_type);
107 void GameChosen(const QString& game_path, const u64 title_id = 0); 108 void GameChosen(const QString& game_path, const u64 title_id = 0);
108 void ShouldCancelWorker(); 109 void ShouldCancelWorker();
109 void OpenFolderRequested(u64 program_id, GameListOpenTarget target, 110 void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
@@ -113,6 +114,7 @@ signals:
113 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, 114 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
114 const std::string& game_path); 115 const std::string& game_path);
115 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 116 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
117 void VerifyIntegrityRequested(const std::string& game_path);
116 void CopyTIDRequested(u64 program_id); 118 void CopyTIDRequested(u64 program_id);
117 void CreateShortcut(u64 program_id, const std::string& game_path, 119 void CreateShortcut(u64 program_id, const std::string& game_path,
118 GameListShortcutTarget target); 120 GameListShortcutTarget target);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 9404365b4..e7fb8a282 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -191,8 +191,9 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
191} 191}
192 192
193QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name, 193QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
194 const std::vector<u8>& icon, Loader::AppLoader& loader, 194 const std::size_t size, const std::vector<u8>& icon,
195 u64 program_id, const CompatibilityList& compatibility_list, 195 Loader::AppLoader& loader, u64 program_id,
196 const CompatibilityList& compatibility_list,
196 const FileSys::PatchManager& patch) { 197 const FileSys::PatchManager& patch) {
197 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 198 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
198 199
@@ -210,7 +211,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
210 file_type_string, program_id), 211 file_type_string, program_id),
211 new GameListItemCompat(compatibility), 212 new GameListItemCompat(compatibility),
212 new GameListItem(file_type_string), 213 new GameListItem(file_type_string),
213 new GameListItemSize(Common::FS::GetSize(path)), 214 new GameListItemSize(size),
214 }; 215 };
215 216
216 const auto patch_versions = GetGameListCachedObject( 217 const auto patch_versions = GetGameListCachedObject(
@@ -278,8 +279,8 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
278 GetMetadataFromControlNCA(patch, *control, icon, name); 279 GetMetadataFromControlNCA(patch, *control, icon, name);
279 } 280 }
280 281
281 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, 282 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
282 compatibility_list, patch), 283 program_id, compatibility_list, patch),
283 parent_dir); 284 parent_dir);
284 } 285 }
285} 286}
@@ -354,8 +355,9 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
354 const FileSys::PatchManager patch{id, system.GetFileSystemController(), 355 const FileSys::PatchManager patch{id, system.GetFileSystemController(),
355 system.GetContentProvider()}; 356 system.GetContentProvider()};
356 357
357 emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, id, 358 emit EntryReady(MakeGameListEntry(physical_name, name,
358 compatibility_list, patch), 359 Common::FS::GetSize(physical_name), icon,
360 *loader, id, compatibility_list, patch),
359 parent_dir); 361 parent_dir);
360 } 362 }
361 } else { 363 } else {
@@ -368,9 +370,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
368 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), 370 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
369 system.GetContentProvider()}; 371 system.GetContentProvider()};
370 372
371 emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, 373 emit EntryReady(
372 program_id, compatibility_list, patch), 374 MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name),
373 parent_dir); 375 icon, *loader, program_id, compatibility_list, patch),
376 parent_dir);
374 } 377 }
375 } 378 }
376 } else if (is_dir) { 379 } else if (is_dir) {
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 848239c35..56eee8d82 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -4,10 +4,12 @@
4#pragma once 4#pragma once
5 5
6#include <map> 6#include <map>
7#include <QKeySequence>
8#include <QString>
9#include <QWidget>
7#include "core/hid/hid_types.h" 10#include "core/hid/hid_types.h"
8 11
9class QDialog; 12class QDialog;
10class QKeySequence;
11class QSettings; 13class QSettings;
12class QShortcut; 14class QShortcut;
13class ControllerShortcut; 15class ControllerShortcut;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 97ae9e49a..d32aa9615 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -8,6 +8,8 @@
8#include <iostream> 8#include <iostream>
9#include <memory> 9#include <memory>
10#include <thread> 10#include <thread>
11#include "core/loader/nca.h"
12#include "core/tools/renderdoc.h"
11#ifdef __APPLE__ 13#ifdef __APPLE__
12#include <unistd.h> // for chdir 14#include <unistd.h> // for chdir
13#endif 15#endif
@@ -442,8 +444,13 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
442 "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" 444 "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
443 "here for instructions to fix the issue</a>.")); 445 "here for instructions to fix the issue</a>."));
444 446
447#ifdef HAS_OPENGL
445 Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; 448 Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
449#else
450 Settings::values.renderer_backend = Settings::RendererBackend::Null;
451#endif
446 452
453 UpdateAPIText();
447 renderer_status_button->setDisabled(true); 454 renderer_status_button->setDisabled(true);
448 renderer_status_button->setChecked(false); 455 renderer_status_button->setChecked(false);
449 } else { 456 } else {
@@ -1158,9 +1165,9 @@ void GMainWindow::InitializeWidgets() {
1158 [this](const QPoint& menu_location) { 1165 [this](const QPoint& menu_location) {
1159 QMenu context_menu; 1166 QMenu context_menu;
1160 1167
1161 for (auto const& docked_mode_pair : Config::use_docked_mode_texts_map) { 1168 for (auto const& pair : Config::use_docked_mode_texts_map) {
1162 context_menu.addAction(docked_mode_pair.second, [this, docked_mode_pair] { 1169 context_menu.addAction(pair.second, [this, &pair] {
1163 if (docked_mode_pair.first != Settings::values.use_docked_mode.GetValue()) { 1170 if (pair.first != Settings::values.use_docked_mode.GetValue()) {
1164 OnToggleDockedMode(); 1171 OnToggleDockedMode();
1165 } 1172 }
1166 }); 1173 });
@@ -1342,6 +1349,11 @@ void GMainWindow::InitializeHotkeys() {
1342 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { 1349 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
1343 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); 1350 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
1344 }); 1351 });
1352 connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] {
1353 if (Settings::values.enable_renderdoc_hotkey) {
1354 system->GetRenderdocAPI().ToggleCapture();
1355 }
1356 });
1345 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { 1357 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
1346 if (Settings::values.mouse_enabled) { 1358 if (Settings::values.mouse_enabled) {
1347 Settings::values.mouse_panning = false; 1359 Settings::values.mouse_panning = false;
@@ -1447,6 +1459,8 @@ void GMainWindow::ConnectWidgetEvents() {
1447 &GMainWindow::OnGameListRemoveInstalledEntry); 1459 &GMainWindow::OnGameListRemoveInstalledEntry);
1448 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); 1460 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
1449 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); 1461 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
1462 connect(game_list, &GameList::VerifyIntegrityRequested, this,
1463 &GMainWindow::OnGameListVerifyIntegrity);
1450 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); 1464 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
1451 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, 1465 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
1452 &GMainWindow::OnGameListNavigateToGamedbEntry); 1466 &GMainWindow::OnGameListNavigateToGamedbEntry);
@@ -1547,6 +1561,7 @@ void GMainWindow::ConnectMenuEvents() {
1547 1561
1548 // Help 1562 // Help
1549 connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); 1563 connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
1564 connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
1550 connect_menu(ui->action_About, &GMainWindow::OnAbout); 1565 connect_menu(ui->action_About, &GMainWindow::OnAbout);
1551} 1566}
1552 1567
@@ -1698,7 +1713,8 @@ void GMainWindow::AllowOSSleep() {
1698#endif 1713#endif
1699} 1714}
1700 1715
1701bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) { 1716bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index,
1717 AmLaunchType launch_type) {
1702 // Shutdown previous session if the emu thread is still active... 1718 // Shutdown previous session if the emu thread is still active...
1703 if (emu_thread != nullptr) { 1719 if (emu_thread != nullptr) {
1704 ShutdownGame(); 1720 ShutdownGame();
@@ -1710,6 +1726,10 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
1710 1726
1711 system->SetFilesystem(vfs); 1727 system->SetFilesystem(vfs);
1712 1728
1729 if (launch_type == AmLaunchType::UserInitiated) {
1730 system->GetUserChannel().clear();
1731 }
1732
1713 system->SetAppletFrontendSet({ 1733 system->SetAppletFrontendSet({
1714 std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings 1734 std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
1715 (UISettings::values.controller_applet_disabled.GetValue() == true) 1735 (UISettings::values.controller_applet_disabled.GetValue() == true)
@@ -1811,8 +1831,45 @@ bool GMainWindow::SelectAndSetCurrentUser(
1811 return true; 1831 return true;
1812} 1832}
1813 1833
1834void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
1835 // Ensure all NCAs are registered before launching the game
1836 const auto file = vfs->OpenFile(filepath, FileSys::Mode::Read);
1837 if (!file) {
1838 return;
1839 }
1840
1841 auto loader = Loader::GetLoader(*system, file);
1842 if (!loader) {
1843 return;
1844 }
1845
1846 const auto file_type = loader->GetFileType();
1847 if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
1848 return;
1849 }
1850
1851 u64 program_id = 0;
1852 const auto res2 = loader->ReadProgramId(program_id);
1853 if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
1854 provider->AddEntry(FileSys::TitleType::Application,
1855 FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id,
1856 file);
1857 } else if (res2 == Loader::ResultStatus::Success &&
1858 (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
1859 const auto nsp = file_type == Loader::FileType::NSP
1860 ? std::make_shared<FileSys::NSP>(file)
1861 : FileSys::XCI{file}.GetSecurePartitionNSP();
1862 for (const auto& title : nsp->GetNCAs()) {
1863 for (const auto& entry : title.second) {
1864 provider->AddEntry(entry.first.first, entry.first.second, title.first,
1865 entry.second->GetBaseFile());
1866 }
1867 }
1868 }
1869}
1870
1814void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index, 1871void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index,
1815 StartGameType type) { 1872 StartGameType type, AmLaunchType launch_type) {
1816 LOG_INFO(Frontend, "yuzu starting..."); 1873 LOG_INFO(Frontend, "yuzu starting...");
1817 StoreRecentFile(filename); // Put the filename on top of the list 1874 StoreRecentFile(filename); // Put the filename on top of the list
1818 1875
@@ -1825,6 +1882,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1825 1882
1826 last_filename_booted = filename; 1883 last_filename_booted = filename;
1827 1884
1885 ConfigureFilesystemProvider(filename.toStdString());
1828 const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); 1886 const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData());
1829 const auto loader = Loader::GetLoader(*system, v_file, program_id, program_index); 1887 const auto loader = Loader::GetLoader(*system, v_file, program_id, program_index);
1830 1888
@@ -1855,7 +1913,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1855 } 1913 }
1856 } 1914 }
1857 1915
1858 if (!LoadROM(filename, program_id, program_index)) { 1916 if (!LoadROM(filename, program_id, program_index, launch_type)) {
1859 return; 1917 return;
1860 } 1918 }
1861 1919
@@ -1972,8 +2030,16 @@ bool GMainWindow::OnShutdownBegin() {
1972 2030
1973 emit EmulationStopping(); 2031 emit EmulationStopping();
1974 2032
2033 int shutdown_time = 1000;
2034
2035 if (system->DebuggerEnabled()) {
2036 shutdown_time = 0;
2037 } else if (system->GetExitLocked()) {
2038 shutdown_time = 5000;
2039 }
2040
1975 shutdown_timer.setSingleShot(true); 2041 shutdown_timer.setSingleShot(true);
1976 shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000); 2042 shutdown_timer.start(shutdown_time);
1977 connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); 2043 connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired);
1978 connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); 2044 connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped);
1979 2045
@@ -2229,40 +2295,62 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
2229 QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); 2295 QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path));
2230} 2296}
2231 2297
2232static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) { 2298static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& dialog,
2233 std::size_t out = 0; 2299 const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest,
2234 2300 bool full) {
2235 for (const auto& subdir : dir->GetSubdirectories()) {
2236 out += 1 + CalculateRomFSEntrySize(subdir, full);
2237 }
2238
2239 return out + (full ? dir->GetFiles().size() : 0);
2240}
2241
2242static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src,
2243 const FileSys::VirtualDir& dest, std::size_t block_size, bool full) {
2244 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) 2301 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
2245 return false; 2302 return false;
2246 if (dialog.wasCanceled()) 2303 if (dialog.wasCanceled())
2247 return false; 2304 return false;
2248 2305
2306 std::vector<u8> buffer(CopyBufferSize);
2307 auto last_timestamp = std::chrono::steady_clock::now();
2308
2309 const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file,
2310 const FileSys::VirtualFile& dest_file) {
2311 if (src_file == nullptr || dest_file == nullptr) {
2312 return false;
2313 }
2314 if (!dest_file->Resize(src_file->GetSize())) {
2315 return false;
2316 }
2317
2318 for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) {
2319 if (dialog.wasCanceled()) {
2320 dest_file->Resize(0);
2321 return false;
2322 }
2323
2324 using namespace std::literals::chrono_literals;
2325 const auto new_timestamp = std::chrono::steady_clock::now();
2326
2327 if ((new_timestamp - last_timestamp) > 33ms) {
2328 last_timestamp = new_timestamp;
2329 dialog.setValue(
2330 static_cast<int>(std::min(read_size, total_size) * 100 / total_size));
2331 QCoreApplication::processEvents();
2332 }
2333
2334 const auto read = src_file->Read(buffer.data(), buffer.size(), i);
2335 dest_file->Write(buffer.data(), read, i);
2336
2337 read_size += read;
2338 }
2339
2340 return true;
2341 };
2342
2249 if (full) { 2343 if (full) {
2250 for (const auto& file : src->GetFiles()) { 2344 for (const auto& file : src->GetFiles()) {
2251 const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); 2345 const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
2252 if (!FileSys::VfsRawCopy(file, out, block_size)) 2346 if (!QtRawCopy(file, out))
2253 return false;
2254 dialog.setValue(dialog.value() + 1);
2255 if (dialog.wasCanceled())
2256 return false; 2347 return false;
2257 } 2348 }
2258 } 2349 }
2259 2350
2260 for (const auto& dir : src->GetSubdirectories()) { 2351 for (const auto& dir : src->GetSubdirectories()) {
2261 const auto out = dest->CreateSubdirectory(dir->GetName()); 2352 const auto out = dest->CreateSubdirectory(dir->GetName());
2262 if (!RomFSRawCopy(dialog, dir, out, block_size, full)) 2353 if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full))
2263 return false;
2264 dialog.setValue(dialog.value() + 1);
2265 if (dialog.wasCanceled())
2266 return false; 2354 return false;
2267 } 2355 }
2268 2356
@@ -2535,16 +2623,34 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
2535 return; 2623 return;
2536 } 2624 }
2537 2625
2538 FileSys::VirtualFile file; 2626 FileSys::VirtualFile packed_update_raw{};
2539 if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { 2627 loader->ReadUpdateRaw(packed_update_raw);
2628
2629 const auto& installed = system->GetContentProvider();
2630
2631 u64 title_id{};
2632 u8 raw_type{};
2633 if (!SelectRomFSDumpTarget(installed, program_id, &title_id, &raw_type)) {
2634 failed();
2635 return;
2636 }
2637
2638 const auto type = static_cast<FileSys::ContentRecordType>(raw_type);
2639 const auto base_nca = installed.GetEntry(title_id, type);
2640 if (!base_nca) {
2540 failed(); 2641 failed();
2541 return; 2642 return;
2542 } 2643 }
2543 2644
2544 const auto& installed = system->GetContentProvider(); 2645 const FileSys::NCA update_nca{packed_update_raw, nullptr};
2545 const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id); 2646 if (type != FileSys::ContentRecordType::Program ||
2647 update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS ||
2648 update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) {
2649 packed_update_raw = {};
2650 }
2546 2651
2547 if (!romfs_title_id) { 2652 const auto base_romfs = base_nca->GetRomFS();
2653 if (!base_romfs) {
2548 failed(); 2654 failed();
2549 return; 2655 return;
2550 } 2656 }
@@ -2553,26 +2659,12 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
2553 target == DumpRomFSTarget::Normal 2659 target == DumpRomFSTarget::Normal
2554 ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) 2660 ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)
2555 : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents"; 2661 : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents";
2556 const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); 2662 const auto romfs_dir = fmt::format("{:016X}/romfs", title_id);
2557 2663
2558 const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); 2664 const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir);
2559 2665
2560 FileSys::VirtualFile romfs; 2666 const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed};
2561 2667 auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false);
2562 if (*romfs_title_id == program_id) {
2563 const u64 ivfc_offset = loader->ReadRomFSIVFCOffset();
2564 const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed};
2565 romfs =
2566 pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false);
2567 } else {
2568 romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
2569 }
2570
2571 const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
2572 if (extracted == nullptr) {
2573 failed();
2574 return;
2575 }
2576 2668
2577 const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); 2669 const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
2578 2670
@@ -2596,11 +2688,16 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
2596 return; 2688 return;
2597 } 2689 }
2598 2690
2691 const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
2692 if (extracted == nullptr) {
2693 failed();
2694 return;
2695 }
2696
2599 const auto full = res == selections.constFirst(); 2697 const auto full = res == selections.constFirst();
2600 const auto entry_size = CalculateRomFSEntrySize(extracted, full);
2601 2698
2602 // The minimum required space is the size of the extracted RomFS + 1 GiB 2699 // The expected required space is the size of the RomFS + 1 GiB
2603 const auto minimum_free_space = extracted->GetSize() + 0x40000000; 2700 const auto minimum_free_space = romfs->GetSize() + 0x40000000;
2604 2701
2605 if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) { 2702 if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) {
2606 QMessageBox::warning(this, tr("RomFS Extraction Failed!"), 2703 QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
@@ -2611,12 +2708,15 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
2611 return; 2708 return;
2612 } 2709 }
2613 2710
2614 QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, 2711 QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, 100, this);
2615 static_cast<s32>(entry_size), this);
2616 progress.setWindowModality(Qt::WindowModal); 2712 progress.setWindowModality(Qt::WindowModal);
2617 progress.setMinimumDuration(100); 2713 progress.setMinimumDuration(100);
2714 progress.setAutoClose(false);
2715 progress.setAutoReset(false);
2716
2717 size_t read_size = 0;
2618 2718
2619 if (RomFSRawCopy(progress, extracted, out, 0x400000, full)) { 2719 if (RomFSRawCopy(romfs->GetSize(), read_size, progress, extracted, out, full)) {
2620 progress.close(); 2720 progress.close();
2621 QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), 2721 QMessageBox::information(this, tr("RomFS Extraction Succeeded!"),
2622 tr("The operation completed successfully.")); 2722 tr("The operation completed successfully."));
@@ -2628,6 +2728,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
2628 } 2728 }
2629} 2729}
2630 2730
2731void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) {
2732 const auto NotImplemented = [this] {
2733 QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"),
2734 tr("File contents were not checked for validity."));
2735 };
2736 const auto Failed = [this] {
2737 QMessageBox::critical(this, tr("Integrity verification failed!"),
2738 tr("File contents may be corrupt."));
2739 };
2740
2741 const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
2742 if (loader == nullptr) {
2743 NotImplemented();
2744 return;
2745 }
2746
2747 QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
2748 progress.setWindowModality(Qt::WindowModal);
2749 progress.setMinimumDuration(100);
2750 progress.setAutoClose(false);
2751 progress.setAutoReset(false);
2752
2753 const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) {
2754 if (progress.wasCanceled()) {
2755 return false;
2756 }
2757
2758 progress.setValue(static_cast<int>((processed_size * 100) / total_size));
2759 return true;
2760 };
2761
2762 const auto status = loader->VerifyIntegrity(QtProgressCallback);
2763 if (progress.wasCanceled() ||
2764 status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) {
2765 NotImplemented();
2766 return;
2767 }
2768
2769 if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) {
2770 Failed();
2771 return;
2772 }
2773
2774 progress.close();
2775 QMessageBox::information(this, tr("Integrity verification succeeded!"),
2776 tr("The operation completed successfully."));
2777}
2778
2631void GMainWindow::OnGameListCopyTID(u64 program_id) { 2779void GMainWindow::OnGameListCopyTID(u64 program_id) {
2632 QClipboard* clipboard = QGuiApplication::clipboard(); 2780 QClipboard* clipboard = QGuiApplication::clipboard();
2633 clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); 2781 clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
@@ -3217,7 +3365,7 @@ void GMainWindow::OnPauseContinueGame() {
3217} 3365}
3218 3366
3219void GMainWindow::OnStopGame() { 3367void GMainWindow::OnStopGame() {
3220 if (system->GetExitLock() && !ConfirmForceLockedExit()) { 3368 if (system->GetExitLocked() && !ConfirmForceLockedExit()) {
3221 return; 3369 return;
3222 } 3370 }
3223 3371
@@ -3234,7 +3382,8 @@ void GMainWindow::OnLoadComplete() {
3234 3382
3235void GMainWindow::OnExecuteProgram(std::size_t program_index) { 3383void GMainWindow::OnExecuteProgram(std::size_t program_index) {
3236 ShutdownGame(); 3384 ShutdownGame();
3237 BootGame(last_filename_booted, 0, program_index); 3385 BootGame(last_filename_booted, 0, program_index, StartGameType::Normal,
3386 AmLaunchType::ApplicationInitiated);
3238} 3387}
3239 3388
3240void GMainWindow::OnExit() { 3389void GMainWindow::OnExit() {
@@ -3630,7 +3779,7 @@ void GMainWindow::OnTasReset() {
3630} 3779}
3631 3780
3632void GMainWindow::OnToggleDockedMode() { 3781void GMainWindow::OnToggleDockedMode() {
3633 const bool is_docked = Settings::values.use_docked_mode.GetValue(); 3782 const bool is_docked = Settings::IsDockedMode();
3634 auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); 3783 auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
3635 auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); 3784 auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
3636 3785
@@ -3644,7 +3793,8 @@ void GMainWindow::OnToggleDockedMode() {
3644 controller_dialog->refreshConfiguration(); 3793 controller_dialog->refreshConfiguration();
3645 } 3794 }
3646 3795
3647 Settings::values.use_docked_mode.SetValue(!is_docked); 3796 Settings::values.use_docked_mode.SetValue(is_docked ? Settings::ConsoleMode::Handheld
3797 : Settings::ConsoleMode::Docked);
3648 UpdateDockedButton(); 3798 UpdateDockedButton();
3649 OnDockedModeChanged(is_docked, !is_docked, *system); 3799 OnDockedModeChanged(is_docked, !is_docked, *system);
3650} 3800}
@@ -3713,10 +3863,14 @@ void GMainWindow::OnToggleAdaptingFilter() {
3713 3863
3714void GMainWindow::OnToggleGraphicsAPI() { 3864void GMainWindow::OnToggleGraphicsAPI() {
3715 auto api = Settings::values.renderer_backend.GetValue(); 3865 auto api = Settings::values.renderer_backend.GetValue();
3716 if (api == Settings::RendererBackend::OpenGL) { 3866 if (api != Settings::RendererBackend::Vulkan) {
3717 api = Settings::RendererBackend::Vulkan; 3867 api = Settings::RendererBackend::Vulkan;
3718 } else { 3868 } else {
3869#ifdef HAS_OPENGL
3719 api = Settings::RendererBackend::OpenGL; 3870 api = Settings::RendererBackend::OpenGL;
3871#else
3872 api = Settings::RendererBackend::Null;
3873#endif
3720 } 3874 }
3721 Settings::values.renderer_backend.SetValue(api); 3875 Settings::values.renderer_backend.SetValue(api);
3722 renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan); 3876 renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan);
@@ -3860,6 +4014,108 @@ void GMainWindow::OnOpenYuzuFolder() {
3860 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); 4014 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir))));
3861} 4015}
3862 4016
4017void GMainWindow::OnVerifyInstalledContents() {
4018 // Declare sizes.
4019 size_t total_size = 0;
4020 size_t processed_size = 0;
4021
4022 // Initialize a progress dialog.
4023 QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
4024 progress.setWindowModality(Qt::WindowModal);
4025 progress.setMinimumDuration(100);
4026 progress.setAutoClose(false);
4027 progress.setAutoReset(false);
4028
4029 // Declare a list of file names which failed to verify.
4030 std::vector<std::string> failed;
4031
4032 // Declare progress callback.
4033 auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) {
4034 if (progress.wasCanceled()) {
4035 return false;
4036 }
4037 progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size));
4038 return true;
4039 };
4040
4041 // Get content registries.
4042 auto bis_contents = system->GetFileSystemController().GetSystemNANDContents();
4043 auto user_contents = system->GetFileSystemController().GetUserNANDContents();
4044
4045 std::vector<FileSys::RegisteredCache*> content_providers;
4046 if (bis_contents) {
4047 content_providers.push_back(bis_contents);
4048 }
4049 if (user_contents) {
4050 content_providers.push_back(user_contents);
4051 }
4052
4053 // Get associated NCA files.
4054 std::vector<FileSys::VirtualFile> nca_files;
4055
4056 // Get all installed IDs.
4057 for (auto nca_provider : content_providers) {
4058 const auto entries = nca_provider->ListEntriesFilter();
4059
4060 for (const auto& entry : entries) {
4061 auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type);
4062 if (!nca_file) {
4063 continue;
4064 }
4065
4066 total_size += nca_file->GetSize();
4067 nca_files.push_back(std::move(nca_file));
4068 }
4069 }
4070
4071 // Using the NCA loader, determine if all NCAs are valid.
4072 for (auto& nca_file : nca_files) {
4073 Loader::AppLoader_NCA nca_loader(nca_file);
4074
4075 auto status = nca_loader.VerifyIntegrity(QtProgressCallback);
4076 if (progress.wasCanceled()) {
4077 break;
4078 }
4079 if (status != Loader::ResultStatus::Success) {
4080 FileSys::NCA nca(nca_file);
4081 const auto title_id = nca.GetTitleId();
4082 std::string title_name = "unknown";
4083
4084 const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id),
4085 FileSys::ContentRecordType::Control);
4086 if (control && control->GetStatus() == Loader::ResultStatus::Success) {
4087 const FileSys::PatchManager pm{title_id, system->GetFileSystemController(),
4088 *provider};
4089 const auto [nacp, logo] = pm.ParseControlNCA(*control);
4090 if (nacp) {
4091 title_name = nacp->GetApplicationName();
4092 }
4093 }
4094
4095 if (title_id > 0) {
4096 failed.push_back(
4097 fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name));
4098 } else {
4099 failed.push_back(fmt::format("{} (unknown)", nca_file->GetName()));
4100 }
4101 }
4102
4103 processed_size += nca_file->GetSize();
4104 }
4105
4106 progress.close();
4107
4108 if (failed.size() > 0) {
4109 auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n")));
4110 QMessageBox::critical(
4111 this, tr("Integrity verification failed!"),
4112 tr("Verification failed for the following files:\n\n%1").arg(failed_names));
4113 } else {
4114 QMessageBox::information(this, tr("Integrity verification succeeded!"),
4115 tr("The operation completed successfully."));
4116 }
4117}
4118
3863void GMainWindow::OnAbout() { 4119void GMainWindow::OnAbout() {
3864 AboutDialog aboutDialog(this); 4120 AboutDialog aboutDialog(this);
3865 aboutDialog.exec(); 4121 aboutDialog.exec();
@@ -4074,10 +4330,10 @@ void GMainWindow::UpdateGPUAccuracyButton() {
4074} 4330}
4075 4331
4076void GMainWindow::UpdateDockedButton() { 4332void GMainWindow::UpdateDockedButton() {
4077 const bool is_docked = Settings::values.use_docked_mode.GetValue(); 4333 const auto console_mode = Settings::values.use_docked_mode.GetValue();
4078 dock_status_button->setChecked(is_docked); 4334 dock_status_button->setChecked(Settings::IsDockedMode());
4079 dock_status_button->setText( 4335 dock_status_button->setText(
4080 Config::use_docked_mode_texts_map.find(is_docked)->second.toUpper()); 4336 Config::use_docked_mode_texts_map.find(console_mode)->second.toUpper());
4081} 4337}
4082 4338
4083void GMainWindow::UpdateAPIText() { 4339void GMainWindow::UpdateAPIText() {
@@ -4305,28 +4561,41 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
4305 return mii_nca->GetRomFS().get() != nullptr; 4561 return mii_nca->GetRomFS().get() != nullptr;
4306} 4562}
4307 4563
4308std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, 4564bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4309 u64 program_id) { 4565 u64* selected_title_id, u8* selected_content_record_type) {
4310 const auto dlc_entries = 4566 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>;
4311 installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); 4567 boost::container::flat_map<u64, ContentInfo> available_title_ids;
4312 std::vector<FileSys::ContentProviderEntry> dlc_match; 4568
4313 dlc_match.reserve(dlc_entries.size()); 4569 const auto RetrieveEntries = [&](FileSys::TitleType title_type,
4314 std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), 4570 FileSys::ContentRecordType record_type) {
4315 [&program_id, &installed](const FileSys::ContentProviderEntry& entry) { 4571 const auto entries = installed.ListEntriesFilter(title_type, record_type);
4316 return FileSys::GetBaseTitleID(entry.title_id) == program_id && 4572 for (const auto& entry : entries) {
4317 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; 4573 if (FileSys::GetBaseTitleID(entry.title_id) == program_id &&
4318 }); 4574 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) {
4319 4575 available_title_ids[entry.title_id] = {title_type, record_type};
4320 std::vector<u64> romfs_tids; 4576 }
4321 romfs_tids.push_back(program_id); 4577 }
4322 for (const auto& entry : dlc_match) { 4578 };
4323 romfs_tids.push_back(entry.title_id); 4579
4324 } 4580 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
4325 4581 RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
4326 if (romfs_tids.size() > 1) { 4582
4327 QStringList list{QStringLiteral("Base")}; 4583 if (available_title_ids.empty()) {
4328 for (std::size_t i = 1; i < romfs_tids.size(); ++i) { 4584 return false;
4329 list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); 4585 }
4586
4587 size_t title_index = 0;
4588
4589 if (available_title_ids.size() > 1) {
4590 QStringList list;
4591 for (auto& [title_id, content_info] : available_title_ids) {
4592 const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
4593 if (content_info.first == FileSys::TitleType::Application) {
4594 list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id));
4595 } else {
4596 list.push_back(
4597 QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
4598 }
4330 } 4599 }
4331 4600
4332 bool ok; 4601 bool ok;
@@ -4334,13 +4603,16 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv
4334 this, tr("Select RomFS Dump Target"), 4603 this, tr("Select RomFS Dump Target"),
4335 tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); 4604 tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
4336 if (!ok) { 4605 if (!ok) {
4337 return {}; 4606 return false;
4338 } 4607 }
4339 4608
4340 return romfs_tids[list.indexOf(res)]; 4609 title_index = list.indexOf(res);
4341 } 4610 }
4342 4611
4343 return program_id; 4612 const auto selected_info = available_title_ids.nth(title_index);
4613 *selected_title_id = selected_info->first;
4614 *selected_content_record_type = static_cast<u8>(selected_info->second.second);
4615 return true;
4344} 4616}
4345 4617
4346bool GMainWindow::ConfirmClose() { 4618bool GMainWindow::ConfirmClose() {
@@ -4470,6 +4742,8 @@ void GMainWindow::RequestGameExit() {
4470 auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); 4742 auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
4471 bool has_signalled = false; 4743 bool has_signalled = false;
4472 4744
4745 system->SetExitRequested(true);
4746
4473 if (applet_oe != nullptr) { 4747 if (applet_oe != nullptr) {
4474 applet_oe->GetMessageQueue()->RequestExit(); 4748 applet_oe->GetMessageQueue()->RequestExit();
4475 has_signalled = true; 4749 has_signalled = true;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 2cfb96257..cf191f698 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -58,6 +58,11 @@ enum class StartGameType {
58 Global, // Only uses global configuration 58 Global, // Only uses global configuration
59}; 59};
60 60
61enum class AmLaunchType {
62 UserInitiated,
63 ApplicationInitiated,
64};
65
61namespace Core { 66namespace Core {
62enum class SystemResultStatus : u32; 67enum class SystemResultStatus : u32;
63class System; 68class System;
@@ -239,9 +244,11 @@ private:
239 void PreventOSSleep(); 244 void PreventOSSleep();
240 void AllowOSSleep(); 245 void AllowOSSleep();
241 246
242 bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index); 247 bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index,
248 AmLaunchType launch_type);
243 void BootGame(const QString& filename, u64 program_id = 0, std::size_t program_index = 0, 249 void BootGame(const QString& filename, u64 program_id = 0, std::size_t program_index = 0,
244 StartGameType with_config = StartGameType::Normal); 250 StartGameType with_config = StartGameType::Normal,
251 AmLaunchType launch_type = AmLaunchType::UserInitiated);
245 void ShutdownGame(); 252 void ShutdownGame();
246 253
247 void ShowTelemetryCallout(); 254 void ShowTelemetryCallout();
@@ -313,6 +320,7 @@ private slots:
313 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, 320 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
314 const std::string& game_path); 321 const std::string& game_path);
315 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 322 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
323 void OnGameListVerifyIntegrity(const std::string& game_path);
316 void OnGameListCopyTID(u64 program_id); 324 void OnGameListCopyTID(u64 program_id);
317 void OnGameListNavigateToGamedbEntry(u64 program_id, 325 void OnGameListNavigateToGamedbEntry(u64 program_id,
318 const CompatibilityList& compatibility_list); 326 const CompatibilityList& compatibility_list);
@@ -342,6 +350,7 @@ private slots:
342 void OnConfigurePerGame(); 350 void OnConfigurePerGame();
343 void OnLoadAmiibo(); 351 void OnLoadAmiibo();
344 void OnOpenYuzuFolder(); 352 void OnOpenYuzuFolder();
353 void OnVerifyInstalledContents();
345 void OnAbout(); 354 void OnAbout();
346 void OnToggleFilterBar(); 355 void OnToggleFilterBar();
347 void OnToggleStatusBar(); 356 void OnToggleStatusBar();
@@ -375,7 +384,8 @@ private:
375 void RemoveAllTransferableShaderCaches(u64 program_id); 384 void RemoveAllTransferableShaderCaches(u64 program_id);
376 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); 385 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
377 void RemoveCacheStorage(u64 program_id); 386 void RemoveCacheStorage(u64 program_id);
378 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); 387 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
388 u64* selected_title_id, u8* selected_content_record_type);
379 InstallResult InstallNSPXCI(const QString& filename); 389 InstallResult InstallNSPXCI(const QString& filename);
380 InstallResult InstallNCA(const QString& filename); 390 InstallResult InstallNCA(const QString& filename);
381 void MigrateConfigFiles(); 391 void MigrateConfigFiles();
@@ -399,6 +409,7 @@ private:
399 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 409 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
400 bool CheckDarkMode(); 410 bool CheckDarkMode();
401 bool CheckSystemArchiveDecryption(); 411 bool CheckSystemArchiveDecryption();
412 void ConfigureFilesystemProvider(const std::string& filepath);
402 413
403 QString GetTasStateDescription() const; 414 QString GetTasStateDescription() const;
404 bool CreateShortcut(const std::string& shortcut_path, const std::string& title, 415 bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 013ba0ceb..e54d7d75d 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -148,6 +148,7 @@
148 <addaction name="action_Configure_Tas"/> 148 <addaction name="action_Configure_Tas"/>
149 </widget> 149 </widget>
150 <addaction name="action_Rederive"/> 150 <addaction name="action_Rederive"/>
151 <addaction name="action_Verify_installed_contents"/>
151 <addaction name="separator"/> 152 <addaction name="separator"/>
152 <addaction name="action_Capture_Screenshot"/> 153 <addaction name="action_Capture_Screenshot"/>
153 <addaction name="menuTAS"/> 154 <addaction name="menuTAS"/>
@@ -214,6 +215,11 @@
214 <string>&amp;Reinitialize keys...</string> 215 <string>&amp;Reinitialize keys...</string>
215 </property> 216 </property>
216 </action> 217 </action>
218 <action name="action_Verify_installed_contents">
219 <property name="text">
220 <string>Verify installed contents</string>
221 </property>
222 </action>
217 <action name="action_About"> 223 <action name="action_About">
218 <property name="text"> 224 <property name="text">
219 <string>&amp;About yuzu</string> 225 <string>&amp;About yuzu</string>
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index f03dc01dd..1c833767b 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -36,4 +36,20 @@ bool IsDarkTheme() {
36 36
37Values values = {}; 37Values values = {};
38 38
39u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) {
40 switch (ratio) {
41 case Settings::AspectRatio::R4_3:
42 return height * 4 / 3;
43 case Settings::AspectRatio::R21_9:
44 return height * 21 / 9;
45 case Settings::AspectRatio::R16_10:
46 return height * 16 / 10;
47 case Settings::AspectRatio::R16_9:
48 case Settings::AspectRatio::Stretch:
49 // TODO: Move this function wherever appropriate to implement Stretched aspect
50 break;
51 }
52 return height * 16 / 9;
53}
54
39} // namespace UISettings 55} // namespace UISettings
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index c9c89cee4..8efd63f31 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -13,6 +13,7 @@
13#include <QVector> 13#include <QVector>
14#include "common/common_types.h" 14#include "common/common_types.h"
15#include "common/settings.h" 15#include "common/settings.h"
16#include "common/settings_enums.h"
16 17
17using Settings::Category; 18using Settings::Category;
18using Settings::Setting; 19using Settings::Setting;
@@ -127,8 +128,10 @@ struct Values {
127 // logging 128 // logging
128 Setting<bool> show_console{linkage, false, "showConsole", Category::Ui}; 129 Setting<bool> show_console{linkage, false, "showConsole", Category::Ui};
129 130
131 // Screenshots
130 Setting<bool> enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as", 132 Setting<bool> enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as",
131 Category::Screenshots}; 133 Category::Screenshots};
134 Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots};
132 135
133 QString roms_path; 136 QString roms_path;
134 QString symbols_path; 137 QString symbols_path;
@@ -187,6 +190,8 @@ struct Values {
187 190
188extern Values values; 191extern Values values;
189 192
193u32 CalculateWidth(u32 height, Settings::AspectRatio ratio);
194
190} // namespace UISettings 195} // namespace UISettings
191 196
192Q_DECLARE_METATYPE(UISettings::GameDir*); 197Q_DECLARE_METATYPE(UISettings::GameDir*);
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c42d98709..0d25ff400 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -259,7 +259,7 @@ void Config::ReadValues() {
259 std::stringstream ss(title_list); 259 std::stringstream ss(title_list);
260 std::string line; 260 std::string line;
261 while (std::getline(ss, line, '|')) { 261 while (std::getline(ss, line, '|')) {
262 const auto title_id = std::stoul(line, nullptr, 16); 262 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); 263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
264 264
265 std::stringstream inner_ss(disabled_list); 265 std::stringstream inner_ss(disabled_list);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index d0433ffc6..087cfaa26 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -264,8 +264,9 @@ int main(int argc, char** argv) {
264 nickname = match[1]; 264 nickname = match[1];
265 password = match[2]; 265 password = match[2];
266 address = match[3]; 266 address = match[3];
267 if (!match[4].str().empty()) 267 if (!match[4].str().empty()) {
268 port = std::stoi(match[4]); 268 port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0));
269 }
269 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); 270 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
270 if (!std::regex_match(nickname, nickname_re)) { 271 if (!std::regex_match(nickname, nickname_re)) {
271 std::cout 272 std::cout
@@ -358,6 +359,7 @@ int main(int argc, char** argv) {
358 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); 359 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
359 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); 360 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
360 system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); 361 system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
362 system.GetUserChannel().clear();
361 363
362 const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath)}; 364 const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath)};
363 365