summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt35
-rw-r--r--src/android/.gitignore3
-rw-r--r--src/android/app/build.gradle.kts28
-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.kt37
-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.kt37
-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/AboutFragment.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt220
-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.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt89
-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.kt51
-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/MessageDialogViewModel.kt14
-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.kt35
-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.kt446
-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.cpp57
-rw-r--r--src/android/app/src/main/jni/config.h24
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.cpp14
-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/ic_export.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_import.xml9
-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_progress_bar.xml28
-rw-r--r--src/android/app/src/main/res/layout/dialog_slider.xml13
-rw-r--r--src/android/app/src/main/res/layout/fragment_about.xml61
-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.xml21
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml19
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml19
-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.xml55
-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.h12
-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.cpp73
-rw-r--r--src/audio_core/sink/sink_details.h8
-rw-r--r--src/common/CMakeLists.txt26
-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/backend.cpp2
-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.cpp265
-rw-r--r--src/common/settings.h873
-rw-r--r--src/common/settings_common.cpp61
-rw-r--r--src/common/settings_common.h269
-rw-r--r--src/common/settings_enums.h216
-rw-r--r--src/common/settings_setting.h417
-rw-r--r--src/common/swap.h5
-rw-r--r--src/common/wall_clock.cpp4
-rw-r--r--src/core/CMakeLists.txt74
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp6
-rw-r--r--src/core/core.cpp72
-rw-r--r--src/core/core.h20
-rw-r--r--src/core/crypto/key_manager.cpp234
-rw-r--r--src/core/crypto/key_manager.h59
-rw-r--r--src/core/debugger/gdbstub.cpp17
-rw-r--r--src/core/file_sys/card_image.cpp54
-rw-r--r--src/core/file_sys/card_image.h1
-rw-r--r--src/core/file_sys/content_archive.cpp586
-rw-r--r--src/core/file_sys/content_archive.h66
-rw-r--r--src/core/file_sys/control_metadata.cpp3
-rw-r--r--src/core/file_sys/errors.h70
-rw-r--r--src/core/file_sys/fssystem/fs_i_storage.h58
-rw-r--r--src/core/file_sys/fssystem/fs_types.h46
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp251
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h114
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp129
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h43
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp112
-rw-r--r--src/core/file_sys/fssystem/fssystem_aes_xts_storage.h42
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h146
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp204
-rw-r--r--src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h21
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree.cpp598
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree.h416
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h170
-rw-r--r--src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h110
-rw-r--r--src/core/file_sys/fssystem/fssystem_compressed_storage.h963
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_common.h43
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_configuration.cpp36
-rw-r--r--src/core/file_sys/fssystem/fssystem_compression_configuration.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp65
-rw-r--r--src/core/file_sys/fssystem/fssystem_crypto_configuration.h12
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp127
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h164
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp80
-rw-r--r--src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h44
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.cpp119
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.h294
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp30
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h42
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp91
-rw-r--r--src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h65
-rw-r--r--src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h61
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp1351
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h364
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_header.cpp20
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_header.h338
-rw-r--r--src/core/file_sys/fssystem/fssystem_nca_reader.cpp531
-rw-r--r--src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp61
-rw-r--r--src/core/file_sys/fssystem/fssystem_pooled_buffer.h95
-rw-r--r--src/core/file_sys/fssystem/fssystem_sparse_storage.cpp39
-rw-r--r--src/core/file_sys/fssystem/fssystem_sparse_storage.h72
-rw-r--r--src/core/file_sys/fssystem/fssystem_switch_storage.h80
-rw-r--r--src/core/file_sys/fssystem/fssystem_utility.cpp27
-rw-r--r--src/core/file_sys/fssystem/fssystem_utility.h12
-rw-r--r--src/core/file_sys/ips_layer.cpp4
-rw-r--r--src/core/file_sys/nca_metadata.cpp4
-rw-r--r--src/core/file_sys/nca_metadata.h1
-rw-r--r--src/core/file_sys/nca_patch.cpp217
-rw-r--r--src/core/file_sys/nca_patch.h145
-rw-r--r--src/core/file_sys/partition_filesystem.cpp1
-rw-r--r--src/core/file_sys/patch_manager.cpp56
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/file_sys/registered_cache.cpp73
-rw-r--r--src/core/file_sys/registered_cache.h5
-rw-r--r--src/core/file_sys/romfs_factory.cpp34
-rw-r--r--src/core/file_sys/romfs_factory.h23
-rw-r--r--src/core/file_sys/savedata_factory.cpp22
-rw-r--r--src/core/file_sys/savedata_factory.h4
-rw-r--r--src/core/file_sys/sdmc_factory.cpp2
-rw-r--r--src/core/file_sys/sdmc_factory.h2
-rw-r--r--src/core/file_sys/submission_package.cpp39
-rw-r--r--src/core/file_sys/submission_package.h1
-rw-r--r--src/core/frontend/applets/controller.cpp4
-rw-r--r--src/core/frontend/framebuffer_layout.cpp3
-rw-r--r--src/core/hid/hid_core.cpp8
-rw-r--r--src/core/hid/hid_core.h7
-rw-r--r--src/core/hid/hid_types.h26
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp22
-rw-r--r--src/core/hle/kernel/k_capabilities.cpp1
-rw-r--r--src/core/hle/kernel/k_hardware_timer.h9
-rw-r--r--src/core/hle/kernel/k_page_table.cpp2
-rw-r--r--src/core/hle/kernel/k_process.cpp33
-rw-r--r--src/core/hle/kernel/k_process.h15
-rw-r--r--src/core/hle/kernel/k_resource_limit.cpp11
-rw-r--r--src/core/hle/kernel/k_resource_limit.h3
-rw-r--r--src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h2
-rw-r--r--src/core/hle/kernel/kernel.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_address_arbiter.cpp3
-rw-r--r--src/core/hle/kernel/svc/svc_condition_variable.cpp3
-rw-r--r--src/core/hle/kernel/svc/svc_debug_string.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_exception.cpp6
-rw-r--r--src/core/hle/kernel/svc/svc_ipc.cpp20
-rw-r--r--src/core/hle/kernel/svc/svc_resource_limit.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_synchronization.cpp16
-rw-r--r--src/core/hle/kernel/svc/svc_thread.cpp29
-rw-r--r--src/core/hle/result.h155
-rw-r--r--src/core/hle/service/acc/acc.cpp11
-rw-r--r--src/core/hle/service/am/am.cpp154
-rw-r--r--src/core/hle/service/am/am.h32
-rw-r--r--src/core/hle/service/am/applet_ae.cpp8
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp53
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.h7
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit_types.h3
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.cpp2
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp20
-rw-r--r--src/core/hle/service/apm/apm_controller.cpp4
-rw-r--r--src/core/hle/service/audio/audctl.cpp46
-rw-r--r--src/core/hle/service/audio/audctl.h22
-rw-r--r--src/core/hle/service/audio/audin_u.cpp4
-rw-r--r--src/core/hle/service/audio/audout_u.cpp2
-rw-r--r--src/core/hle/service/audio/audren_u.cpp7
-rw-r--r--src/core/hle/service/audio/audren_u.h2
-rw-r--r--src/core/hle/service/audio/errors.h12
-rw-r--r--src/core/hle/service/audio/hwopus.cpp697
-rw-r--r--src/core/hle/service/audio/hwopus.h21
-rw-r--r--src/core/hle/service/es/es.cpp10
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp162
-rw-r--r--src/core/hle/service/filesystem/filesystem.h44
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp76
-rw-r--r--src/core/hle/service/glue/arp.cpp36
-rw-r--r--src/core/hle/service/glue/glue_manager.cpp11
-rw-r--r--src/core/hle/service/glue/glue_manager.h4
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp68
-rw-r--r--src/core/hle/service/hid/controllers/npad.h22
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h16
-rw-r--r--src/core/hle/service/hid/hid.cpp95
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/ldr/ldr.cpp46
-rw-r--r--src/core/hle/service/mii/mii.cpp514
-rw-r--r--src/core/hle/service/mii/mii.h18
-rw-r--r--src/core/hle/service/mii/mii_database.cpp142
-rw-r--r--src/core/hle/service/mii/mii_database.h66
-rw-r--r--src/core/hle/service/mii/mii_database_manager.cpp420
-rw-r--r--src/core/hle/service/mii/mii_database_manager.h58
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp948
-rw-r--r--src/core/hle/service/mii/mii_manager.h84
-rw-r--r--src/core/hle/service/mii/mii_result.h27
-rw-r--r--src/core/hle/service/mii/mii_types.h692
-rw-r--r--src/core/hle/service/mii/mii_util.h85
-rw-r--r--src/core/hle/service/mii/raw_data.h26
-rw-r--r--src/core/hle/service/mii/types.h553
-rw-r--r--src/core/hle/service/mii/types/char_info.cpp482
-rw-r--r--src/core/hle/service/mii/types/char_info.h137
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp805
-rw-r--r--src/core/hle/service/mii/types/core_data.h219
-rw-r--r--src/core/hle/service/mii/types/raw_data.cpp (renamed from src/core/hle/service/mii/raw_data.cpp)1412
-rw-r--r--src/core/hle/service/mii/types/raw_data.h73
-rw-r--r--src/core/hle/service/mii/types/store_data.cpp676
-rw-r--r--src/core/hle/service/mii/types/store_data.h150
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp241
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.h160
-rw-r--r--src/core/hle/service/nfc/common/device.cpp47
-rw-r--r--src/core/hle/service/nfp/nfp_types.h6
-rw-r--r--src/core/hle/service/ngc/ngc.cpp150
-rw-r--r--src/core/hle/service/ngc/ngc.h (renamed from src/core/hle/service/ngct/ngct.h)4
-rw-r--r--src/core/hle/service/ngct/ngct.cpp62
-rw-r--r--src/core/hle/service/ns/ns.cpp43
-rw-r--r--src/core/hle/service/ns/ns.h5
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.cpp4
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp1
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h2
-rw-r--r--src/core/hle/service/nvnflinger/window.h1
-rw-r--r--src/core/hle/service/olsc/olsc.cpp158
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp144
-rw-r--r--src/core/hle/service/server_manager.cpp9
-rw-r--r--src/core/hle/service/service.cpp4
-rw-r--r--src/core/hle/service/service.h4
-rw-r--r--src/core/hle/service/set/set.cpp70
-rw-r--r--src/core/hle/service/set/set.h61
-rw-r--r--src/core/hle/service/set/set_sys.cpp375
-rw-r--r--src/core/hle/service/set/set_sys.h322
-rw-r--r--src/core/hle/service/sm/sm.cpp43
-rw-r--r--src/core/hle/service/sm/sm.h4
-rw-r--r--src/core/hle/service/sockets/bsd.cpp7
-rw-r--r--src/core/hle/service/sockets/bsd.h3
-rw-r--r--src/core/hle/service/sockets/nsd.cpp20
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp12
-rw-r--r--src/core/hle/service/sockets/sockets.h2
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp4
-rw-r--r--src/core/hle/service/spl/spl_module.cpp51
-rw-r--r--src/core/hle/service/spl/spl_module.h2
-rw-r--r--src/core/hle/service/ssl/ssl.cpp96
-rw-r--r--src/core/hle/service/ssl/ssl_backend.h8
-rw-r--r--src/core/hle/service/ssl/ssl_backend_none.cpp2
-rw-r--r--src/core/hle/service/ssl/ssl_backend_openssl.cpp48
-rw-r--r--src/core/hle/service/ssl/ssl_backend_schannel.cpp90
-rw-r--r--src/core/hle/service/ssl/ssl_backend_securetransport.cpp41
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp20
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.h2
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp5
-rw-r--r--src/core/hle/service/vi/display/vi_display.h2
-rw-r--r--src/core/hle/service/vi/vi.cpp37
-rw-r--r--src/core/internal_network/network.cpp132
-rw-r--r--src/core/internal_network/network.h5
-rw-r--r--src/core/internal_network/network_interface.cpp9
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp10
-rw-r--r--src/core/loader/deconstructed_rom_directory.h4
-rw-r--r--src/core/loader/kip.cpp5
-rw-r--r--src/core/loader/loader.cpp8
-rw-r--r--src/core/loader/loader.h22
-rw-r--r--src/core/loader/nax.cpp4
-rw-r--r--src/core/loader/nax.h1
-rw-r--r--src/core/loader/nca.cpp104
-rw-r--r--src/core/loader/nca.h3
-rw-r--r--src/core/loader/nro.cpp5
-rw-r--r--src/core/loader/nso.cpp7
-rw-r--r--src/core/loader/nsp.cpp43
-rw-r--r--src/core/loader/nsp.h3
-rw-r--r--src/core/loader/xci.cpp38
-rw-r--r--src/core/loader/xci.h3
-rw-r--r--src/core/memory.cpp21
-rw-r--r--src/core/memory.h125
-rw-r--r--src/core/memory/cheat_engine.cpp2
-rw-r--r--src/core/reporter.cpp4
-rw-r--r--src/core/telemetry_session.cpp37
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
-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/emit_spirv_image_atomic.cpp15
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp3
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h1
-rw-r--r--src/shader_recompiler/environment.h2
-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/CMakeLists.txt2
-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.cpp6
-rw-r--r--src/video_core/engines/maxwell_dma.h55
-rw-r--r--src/video_core/engines/puller.cpp15
-rw-r--r--src/video_core/host1x/codecs/codec.cpp5
-rw-r--r--src/video_core/host1x/codecs/h264.cpp2
-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_compute_pipeline.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp51
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp24
-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/gl_shader_cache.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp8
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp2
-rw-r--r--src/video_core/renderer_opengl/util_shaders.cpp1
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp82
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h19
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp4
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp18
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h7
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp39
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp54
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp12
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp32
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_turbo_mode.cpp2
-rw-r--r--src/video_core/shader_cache.cpp5
-rw-r--r--src/video_core/shader_cache.h2
-rw-r--r--src/video_core/shader_environment.cpp31
-rw-r--r--src/video_core/shader_environment.h6
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp2
-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/textures/texture.cpp4
-rw-r--r--src/video_core/vulkan_common/vma.cpp4
-rw-r--r--src/video_core/vulkan_common/vma.h11
-rw-r--r--src/video_core/vulkan_common/vulkan.h26
-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.cpp215
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h21
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.cpp29
-rw-r--r--src/video_core/vulkan_common/vulkan_library.cpp16
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp3
-rw-r--r--src/video_core/vulkan_common/vulkan_surface.cpp13
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp5
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h22
-rw-r--r--src/web_service/verify_user_jwt.cpp1
-rw-r--r--src/yuzu/CMakeLists.txt22
-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.cpp726
-rw-r--r--src/yuzu/configuration/config.h67
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp103
-rw-r--r--src/yuzu/configuration/configuration_shared.h78
-rw-r--r--src/yuzu/configuration/configure.ui31
-rw-r--r--src/yuzu/configuration/configure_audio.cpp205
-rw-r--r--src/yuzu/configuration/configure_audio.h32
-rw-r--r--src/yuzu/configuration/configure_audio.ui162
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp149
-rw-r--r--src/yuzu/configuration/configure_cpu.h37
-rw-r--r--src/yuzu/configuration/configure_cpu.ui179
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui865
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp27
-rw-r--r--src/yuzu/configuration/configure_dialog.h7
-rw-r--r--src/yuzu/configuration/configure_general.cpp116
-rw-r--r--src/yuzu/configuration/configure_general.h29
-rw-r--r--src/yuzu/configuration/configure_general.ui87
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp663
-rw-r--r--src/yuzu/configuration/configure_graphics.h63
-rw-r--r--src/yuzu/configuration/configure_graphics.ui690
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp187
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h39
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui231
-rw-r--r--src/yuzu/configuration/configure_input.cpp14
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp38
-rw-r--r--src/yuzu/configuration/configure_per_game.h7
-rw-r--r--src/yuzu/configuration/configure_per_game.ui47
-rw-r--r--src/yuzu/configuration/configure_system.cpp217
-rw-r--r--src/yuzu/configuration/configure_system.h36
-rw-r--r--src/yuzu/configuration/configure_system.ui537
-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.cpp391
-rw-r--r--src/yuzu/configuration/shared_translation.h25
-rw-r--r--src/yuzu/configuration/shared_widget.cpp802
-rw-r--r--src/yuzu/configuration/shared_widget.h178
-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.cpp475
-rw-r--r--src/yuzu/main.h17
-rw-r--r--src/yuzu/main.ui6
-rw-r--r--src/yuzu/multiplayer/direct_connect.cpp9
-rw-r--r--src/yuzu/multiplayer/host_room.cpp16
-rw-r--r--src/yuzu/multiplayer/lobby.cpp7
-rw-r--r--src/yuzu/uisettings.cpp28
-rw-r--r--src/yuzu/uisettings.h133
-rw-r--r--src/yuzu/vk_device_info.cpp4
-rw-r--r--src/yuzu_cmd/config.cpp176
-rw-r--r--src/yuzu_cmd/config.h1
-rw-r--r--src/yuzu_cmd/yuzu.cpp6
717 files changed, 31723 insertions, 15260 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0696201df..d7f68618c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -24,7 +24,7 @@ if (MSVC)
# Ensure that projects build with Unicode support.
add_definitions(-DUNICODE -D_UNICODE)
- # /W3 - Level 3 warnings
+ # /W4 - Level 4 warnings
# /MP - Multi-threaded compilation
# /Zi - Output debugging information
# /Zm - Specifies the precompiled header memory allocation limit
@@ -35,6 +35,7 @@ if (MSVC)
# /volatile:iso - Use strict standards-compliant volatile semantics.
# /Zc:externConstexpr - Allow extern constexpr variables to have external linkage, like the standard mandates
# /Zc:inline - Let codegen omit inline functions in object files
+ # /Zc:preprocessor - Enable standards-conforming preprocessor
# /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null
# /GT - Supports fiber safety for data allocated using static thread-local storage
add_compile_options(
@@ -48,6 +49,7 @@ if (MSVC)
/volatile:iso
/Zc:externConstexpr
/Zc:inline
+ /Zc:preprocessor
/Zc:throwingNew
/GT
@@ -59,7 +61,7 @@ if (MSVC)
/external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers
# Warnings
- /W3
+ /W4
/WX
/we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled
@@ -82,12 +84,17 @@ if (MSVC)
/wd4100 # 'identifier': unreferenced formal parameter
/wd4324 # 'struct_name': structure was padded due to __declspec(align())
+ /wd4201 # nonstandard extension used : nameless struct/union
+ /wd4702 # unreachable code (when used with LTO)
)
if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS)
# when caching, we need to use /Z7 to downgrade debug info to use an older but more cacheable format
# Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21
add_compile_options(/Z7)
+ # Avoid D9025 warning
+ string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
+ string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
else()
add_compile_options(/Zi)
endif()
@@ -103,6 +110,8 @@ if (MSVC)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
else()
add_compile_options(
+ -fwrapv
+
-Werror=all
-Werror=extra
-Werror=missing-declarations
@@ -112,19 +121,21 @@ else()
-Wno-attributes
-Wno-invalid-offsetof
-Wno-unused-parameter
-
- $<$<CXX_COMPILER_ID:Clang>:-Wno-braced-scalar-init>
- $<$<CXX_COMPILER_ID:Clang>:-Wno-unused-private-field>
- $<$<CXX_COMPILER_ID:Clang>:-Werror=shadow-uncaptured-local>
- $<$<CXX_COMPILER_ID:Clang>:-Werror=implicit-fallthrough>
- $<$<CXX_COMPILER_ID:Clang>:-Werror=type-limits>
- $<$<CXX_COMPILER_ID:AppleClang>:-Wno-braced-scalar-init>
- $<$<CXX_COMPILER_ID:AppleClang>:-Wno-unused-private-field>
)
+ if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang
+ add_compile_options(
+ -Wno-braced-scalar-init
+ -Wno-unused-private-field
+ -Wno-nullability-completeness
+ -Werror=shadow-uncaptured-local
+ -Werror=implicit-fallthrough
+ -Werror=type-limits
+ )
+ endif()
+
if (ARCHITECTURE_x86_64)
add_compile_options("-mcx16")
- add_compile_options("-fwrapv")
endif()
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
@@ -132,7 +143,7 @@ else()
endif()
# GCC bugs
- if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# These diagnostics would be great if they worked, but are just completely broken
# and produce bogus errors on external libraries like fmt.
add_compile_options(
diff --git a/src/android/.gitignore b/src/android/.gitignore
index 121cc8484..ff7121acd 100644
--- a/src/android/.gitignore
+++ b/src/android/.gitignore
@@ -63,3 +63,6 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
+
+# Autogenerated library for vulkan validation layers
+libVkLayer_khronos_validation.so
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 9a47e2bd8..84a3308b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -77,13 +77,30 @@ android {
buildConfigField("String", "BRANCH", "\"${getBranch()}\"")
}
+ val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
+ if (keystoreFile != null) {
+ signingConfigs {
+ create("release") {
+ storeFile = file(keystoreFile)
+ storePassword = System.getenv("ANDROID_KEYSTORE_PASS")
+ keyAlias = System.getenv("ANDROID_KEY_ALIAS")
+ keyPassword = System.getenv("ANDROID_KEYSTORE_PASS")
+ }
+ }
+ }
+
// Define build types, which are orthogonal to product flavors.
buildTypes {
// Signed by release key, allowing for upload to Play Store.
release {
+ signingConfig = if (keystoreFile != null) {
+ signingConfigs.getByName("release")
+ } else {
+ signingConfigs.getByName("debug")
+ }
+
resValue("string", "app_name_suffixed", "yuzu")
- signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
isDebuggable = false
proguardFiles(
@@ -95,6 +112,7 @@ android {
// builds a release build that doesn't need signing
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
register("relWithDebInfo") {
+ isDefault = true
resValue("string", "app_name_suffixed", "yuzu Debug Release")
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
@@ -122,6 +140,7 @@ android {
flavorDimensions.add("version")
productFlavors {
create("mainline") {
+ isDefault = true
dimension = "version"
buildConfigField("Boolean", "PREMIUM", "false")
}
@@ -160,6 +179,11 @@ android {
}
}
+tasks.create<Delete>("ktlintReset") {
+ delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
+}
+
+tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint {
@@ -190,7 +214,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1")
- implementation("androidx.window:window:1.1.0")
+ implementation("androidx.window:window:1.2.0-beta03")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
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
android:hasFragileUserData="false"
android:supportsRtl="true"
android:isGame="true"
+ android:appCategory="game"
android:localeConfig="@xml/locales_config"
android:banner="@drawable/tv_banner"
android:extractNativeLibs="true"
@@ -55,7 +56,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
android:theme="@style/Theme.Yuzu.Main"
android:launchMode="singleTop"
- android:screenOrientation="userLandscape"
android:supportsPictureInPicture="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
android:exported="true">
@@ -66,6 +66,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
<data android:mimeType="application/octet-stream" />
</intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data
+ android:mimeType="application/octet-stream"
+ android:scheme="content"/>
+ </intent-filter>
+
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
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
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
-import org.yuzu.yuzu_emu.utils.Log.error
-import org.yuzu.yuzu_emu.utils.Log.verbose
-import org.yuzu.yuzu_emu.utils.Log.warning
+import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
/**
@@ -219,10 +217,6 @@ object NativeLibrary {
external fun reloadSettings()
- external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
-
- external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
-
external fun initGameIni(gameID: String?)
/**
@@ -314,21 +308,6 @@ object NativeLibrary {
external fun isPaused(): Boolean
/**
- * Mutes emulation sound
- */
- external fun muteAudio(): Boolean
-
- /**
- * Unmutes emulation sound
- */
- external fun unmuteAudio(): Boolean
-
- /**
- * Returns true if emulation audio is muted.
- */
- external fun isMuted(): Boolean
-
- /**
* Returns the performance stats for the current game
*/
external fun getPerfStats(): DoubleArray
@@ -413,14 +392,17 @@ object NativeLibrary {
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
)
}
+
CoreError.ErrorSavestate -> {
title = emulationActivity.getString(R.string.save_load_error)
message = details
}
+
CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
}
+
else -> {
return true
}
@@ -454,6 +436,7 @@ object NativeLibrary {
captionId = R.string.loader_error_video_core
descriptionId = R.string.loader_error_video_core_description
}
+
else -> {
captionId = R.string.loader_error_encrypted
descriptionId = R.string.loader_error_encrypted_roms_description
@@ -465,7 +448,7 @@ object NativeLibrary {
val emulationActivity = sEmulationActivity.get()
if (emulationActivity == null) {
- warning("[NativeLibrary] EmulationActivity is null, can't exit.")
+ Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.")
return
}
@@ -490,15 +473,27 @@ object NativeLibrary {
}
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
- verbose("[NativeLibrary] Registering EmulationActivity.")
+ Log.verbose("[NativeLibrary] Registering EmulationActivity.")
sEmulationActivity = WeakReference(emulationActivity)
}
fun clearEmulationActivity() {
- verbose("[NativeLibrary] Unregistering EmulationActivity.")
+ Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
sEmulationActivity.clear()
}
+ @Keep
+ @JvmStatic
+ fun onEmulationStarted() {
+ sEmulationActivity.get()!!.onEmulationStarted()
+ }
+
+ @Keep
+ @JvmStatic
+ fun onEmulationStopped(status: Int) {
+ sEmulationActivity.get()!!.onEmulationStopped(status)
+ }
+
/**
* Logs the Yuzu version, Android version and, CPU.
*/
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() {
super.onCreate()
application = this
documentsTree = DocumentsTree()
- DirectoryInitialization.start(applicationContext)
+ DirectoryInitialization.start()
GpuDriverHelper.initializeDriverParameters(applicationContext)
NativeLibrary.logDeviceInfo()
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..e96a2059b 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
@@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.activities
+import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
import android.app.PictureInPictureParams
@@ -42,7 +43,7 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
+import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.ForegroundService
@@ -72,18 +73,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private val actionMute = "ACTION_EMULATOR_MUTE"
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
- private val settingsViewModel: SettingsViewModel by viewModels()
+ private val emulationViewModel: EmulationViewModel by viewModels()
override fun onDestroy() {
stopForegroundService(this)
+ emulationViewModel.clear()
super.onDestroy()
}
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
- settingsViewModel.settings.loadSettings()
-
super.onCreate(savedInstanceState)
binding = ActivityEmulationBinding.inflate(layoutInflater)
@@ -91,9 +91,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
- val navController = navHostFragment.navController
- navController
- .setGraph(R.navigation.emulation_navigation, intent.extras)
+ navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
isActivityRecreated = savedInstanceState != null
@@ -335,7 +333,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
pictureInPictureActions.add(pauseRemoteAction)
}
- if (NativeLibrary.isMuted()) {
+ if (BooleanSetting.AUDIO_MUTED.boolean) {
val unmuteIcon = Icon.createWithResource(
this@EmulationActivity,
R.drawable.ic_pip_unmute
@@ -392,14 +390,15 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
}
if (intent.action == actionUnmute) {
- if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
+ if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
} else if (intent.action == actionMute) {
- if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio()
+ if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
}
buildPictureInPictureParams()
}
}
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
@@ -412,7 +411,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
addAction(actionMute)
addAction(actionUnmute)
}.also {
- registerReceiver(pictureInPictureReceiver, it)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ registerReceiver(pictureInPictureReceiver, it, RECEIVER_EXPORTED)
+ } else {
+ registerReceiver(pictureInPictureReceiver, it)
+ }
}
} else {
try {
@@ -420,7 +423,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
} catch (ignored: Exception) {
}
// Always resume audio, since there is no UI button
- if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
+ if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
+ }
+ }
+
+ fun onEmulationStarted() {
+ emulationViewModel.setEmulationStarted(true)
+ }
+
+ fun onEmulationStopped(status: Int) {
+ if (status == 0) {
+ finish()
}
}
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 @@
package org.yuzu.yuzu_emu.adapters
+import android.content.Intent
import android.graphics.Bitmap
-import android.graphics.BitmapFactory
+import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.text.TextUtils
import android.view.LayoutInflater
@@ -13,25 +14,29 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.graphics.drawable.IconCompat
+import androidx.core.graphics.drawable.toBitmap
+import androidx.core.graphics.drawable.toDrawable
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
-import coil.load
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
-import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.utils.GameIconUtils
class GameAdapter(private val activity: AppCompatActivity) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
@@ -82,6 +87,34 @@ class GameAdapter(private val activity: AppCompatActivity) :
)
.apply()
+ val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
+ action = Intent.ACTION_VIEW
+ data = Uri.parse(holder.game.path)
+ }
+
+ val layerDrawable = ResourcesCompat.getDrawable(
+ YuzuApplication.appContext.resources,
+ R.drawable.shortcut,
+ null
+ ) as LayerDrawable
+ layerDrawable.setDrawableByLayerId(
+ R.id.shortcut_foreground,
+ GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
+ )
+ val inset = YuzuApplication.appContext.resources
+ .getDimensionPixelSize(R.dimen.icon_inset)
+ layerDrawable.setLayerInset(1, inset, inset, inset, inset)
+ val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
+ .setShortLabel(holder.game.title)
+ .setIcon(
+ IconCompat.createWithAdaptiveBitmap(
+ layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
+ )
+ )
+ .setIntent(openIntent)
+ .build()
+ ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
+
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
view.findNavController().navigate(action)
}
@@ -98,12 +131,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
this.game = game
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
- activity.lifecycleScope.launch {
- val bitmap = decodeGameIcon(game.path)
- binding.imageGameScreen.load(bitmap) {
- error(R.drawable.default_icon)
- }
- }
+ GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
@@ -126,14 +154,4 @@ class GameAdapter(private val activity: AppCompatActivity) :
return oldItem == newItem
}
}
-
- private fun decodeGameIcon(uri: String): Bitmap? {
- val data = NativeLibrary.getIcon(uri)
- return BitmapFactory.decodeByteArray(
- data,
- 0,
- data.size,
- BitmapFactory.Options()
- )
- }
}
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..58ce343f4 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 @@
package org.yuzu.yuzu_emu.adapters
+import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.HomeSetting
-class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) :
+class HomeSettingAdapter(
+ private val activity: AppCompatActivity,
+ private val viewLifecycle: LifecycleOwner,
+ var options: List<HomeSetting>
+) :
RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
@@ -39,8 +49,9 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
holder.option.onClick.invoke()
} else {
MessageDialogFragment.newInstance(
- holder.option.disabledTitleId,
- holder.option.disabledMessageId
+ activity,
+ titleId = holder.option.disabledTitleId,
+ descriptionId = holder.option.disabledMessageId
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
}
}
@@ -79,6 +90,26 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
binding.optionDescription.alpha = 0.5f
binding.optionIcon.alpha = 0.5f
}
+
+ viewLifecycle.lifecycleScope.launch {
+ viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
+ option.details.collect { updateOptionDetails(it) }
+ }
+ }
+ binding.optionDetail.postDelayed(
+ {
+ binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
+ binding.optionDetail.isSelected = true
+ },
+ 3000
+ )
+ }
+
+ private fun updateOptionDetails(detailString: String) {
+ if (detailString.isNotEmpty()) {
+ binding.optionDetail.text = detailString
+ binding.optionDetail.visibility = View.VISIBLE
+ }
}
}
}
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
val context = YuzuApplication.appContext
binding.textSettingName.text = context.getString(license.titleId)
binding.textSettingDescription.text = context.getString(license.descriptionId)
+ binding.textSettingValue.visibility = View.GONE
}
}
}
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
import android.text.Html
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
+import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import org.yuzu.yuzu_emu.databinding.PageSetupBinding
+import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage
+import org.yuzu.yuzu_emu.model.StepState
+import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
@@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
holder.bind(pages[position])
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
- RecyclerView.ViewHolder(binding.root) {
+ RecyclerView.ViewHolder(binding.root), SetupCallback {
lateinit var page: SetupPage
init {
@@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
fun bind(page: SetupPage) {
this.page = page
+
+ if (page.stepCompleted.invoke() == StepState.COMPLETE) {
+ binding.buttonAction.visibility = View.INVISIBLE
+ binding.textConfirmation.visibility = View.VISIBLE
+ }
+
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
activity.resources,
@@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
MaterialButton.ICON_GRAVITY_END
}
setOnClickListener {
- page.buttonAction.invoke()
+ page.buttonAction.invoke(this@SetupPageViewHolder)
}
}
}
+
+ override fun onStepCompleted() {
+ ViewUtils.hideView(binding.buttonAction, 200)
+ ViewUtils.showView(binding.textConfirmation, 200)
+ ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true)
+ }
}
}
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 @@
package org.yuzu.yuzu_emu.disk_shader_cache
import androidx.annotation.Keep
+import androidx.lifecycle.ViewModelProvider
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.disk_shader_cache.ui.ShaderProgressDialogFragment
+import org.yuzu.yuzu_emu.activities.EmulationActivity
+import org.yuzu.yuzu_emu.model.EmulationViewModel
+import org.yuzu.yuzu_emu.utils.Log
@Keep
object DiskShaderCacheProgress {
- val finishLock = Object()
- private lateinit var fragment: ShaderProgressDialogFragment
+ private lateinit var emulationViewModel: EmulationViewModel
- private fun prepareDialog() {
- val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
- emulationActivity.runOnUiThread {
- fragment = ShaderProgressDialogFragment.newInstance(
- emulationActivity.getString(R.string.loading),
- emulationActivity.getString(R.string.preparing_shaders)
- )
- fragment.show(
- emulationActivity.supportFragmentManager,
- ShaderProgressDialogFragment.TAG
- )
- }
- synchronized(finishLock) { finishLock.wait() }
+ private fun prepareViewModel() {
+ emulationViewModel =
+ ViewModelProvider(
+ NativeLibrary.sEmulationActivity.get() as EmulationActivity
+ )[EmulationViewModel::class.java]
}
@JvmStatic
fun loadProgress(stage: Int, progress: Int, max: Int) {
val emulationActivity = NativeLibrary.sEmulationActivity.get()
- ?: error("[DiskShaderCacheProgress] EmulationActivity not present")
-
- when (LoadCallbackStage.values()[stage]) {
- LoadCallbackStage.Prepare -> prepareDialog()
- LoadCallbackStage.Build -> fragment.onUpdateProgress(
- emulationActivity.getString(R.string.building_shaders),
- progress,
- max
- )
- LoadCallbackStage.Complete -> fragment.dismiss()
+ if (emulationActivity == null) {
+ Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
+ return
+ }
+
+ emulationActivity.runOnUiThread {
+ when (LoadCallbackStage.values()[stage]) {
+ LoadCallbackStage.Prepare -> prepareViewModel()
+ LoadCallbackStage.Build -> emulationViewModel.updateProgress(
+ emulationActivity.getString(R.string.building_shaders),
+ progress,
+ max
+ )
+
+ LoadCallbackStage.Complete -> {}
+ }
}
}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.disk_shader_cache
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-
-class ShaderProgressViewModel : ViewModel() {
- private val _progress = MutableLiveData(0)
- val progress: LiveData<Int> get() = _progress
-
- private val _max = MutableLiveData(0)
- val max: LiveData<Int> get() = _max
-
- private val _message = MutableLiveData("")
- val message: LiveData<String> get() = _message
-
- fun setProgress(progress: Int) {
- _progress.postValue(progress)
- }
-
- fun setMax(max: Int) {
- _max.postValue(max)
- }
-
- fun setMessage(msg: String) {
- _message.postValue(msg)
- }
-}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.disk_shader_cache.ui
-
-import android.app.Dialog
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.app.AlertDialog
-import androidx.fragment.app.DialogFragment
-import androidx.lifecycle.ViewModelProvider
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
-import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress
-import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel
-
-class ShaderProgressDialogFragment : DialogFragment() {
- private var _binding: DialogProgressBarBinding? = null
- private val binding get() = _binding!!
-
- private lateinit var alertDialog: AlertDialog
-
- private lateinit var shaderProgressViewModel: ShaderProgressViewModel
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- _binding = DialogProgressBarBinding.inflate(layoutInflater)
- shaderProgressViewModel =
- ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java]
-
- val title = requireArguments().getString(TITLE)
- val message = requireArguments().getString(MESSAGE)
-
- isCancelable = false
- alertDialog = MaterialAlertDialogBuilder(requireActivity())
- .setView(binding.root)
- .setTitle(title)
- .setMessage(message)
- .create()
- return alertDialog
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress ->
- binding.progressBar.progress = progress
- setUpdateText()
- }
- shaderProgressViewModel.max.observe(viewLifecycleOwner) { max ->
- binding.progressBar.max = max
- setUpdateText()
- }
- shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
- alertDialog.setMessage(msg)
- }
- synchronized(DiskShaderCacheProgress.finishLock) {
- DiskShaderCacheProgress.finishLock.notifyAll()
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-
- fun onUpdateProgress(msg: String, progress: Int, max: Int) {
- shaderProgressViewModel.setProgress(progress)
- shaderProgressViewModel.setMax(max)
- shaderProgressViewModel.setMessage(msg)
- }
-
- private fun setUpdateText() {
- binding.progressText.text = String.format(
- "%d/%d",
- shaderProgressViewModel.progress.value,
- shaderProgressViewModel.max.value
- )
- }
-
- companion object {
- const val TAG = "ProgressDialogFragment"
- const val TITLE = "title"
- const val MESSAGE = "message"
-
- fun newInstance(title: String, message: String): ShaderProgressDialogFragment {
- val frag = ShaderProgressDialogFragment()
- val args = Bundle()
- args.putString(TITLE, title)
- args.putString(MESSAGE, message)
- frag.arguments = args
- return frag
- }
- }
-}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractBooleanSetting : AbstractSetting {
- var boolean: Boolean
+ val boolean: Boolean
+
+ fun setBoolean(value: Boolean)
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
-import androidx.lifecycle.ViewModel
+interface AbstractByteSetting : AbstractSetting {
+ val byte: Byte
-class SettingsViewModel : ViewModel() {
- val settings = Settings()
+ fun setByte(value: Byte)
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractFloatSetting : AbstractSetting {
- var float: Float
+ val float: Float
+
+ fun setFloat(value: Float)
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractIntSetting : AbstractSetting {
- var int: Int
+ val int: Int
+
+ fun setInt(value: Int)
}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+interface AbstractLongSetting : AbstractSetting {
+ val long: Long
+
+ fun setLong(value: Long)
+}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
interface AbstractSetting {
- val key: String?
- val section: String?
- val isRuntimeEditable: Boolean
- val valueAsString: String
+ val key: String
+ val category: Settings.Category
val defaultValue: Any
+ val androidDefault: Any?
+ get() = null
+ val valueAsString: String
+ get() = ""
+
+ val isRuntimeModifiable: Boolean
+ get() = NativeConfig.getIsRuntimeModifiable(key)
+
+ val pairedSettingKey: String
+ get() = NativeConfig.getPairedSettingKey(key)
+
+ fun reset()
}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+interface AbstractShortSetting : AbstractSetting {
+ val short: Short
+
+ fun setShort(value: Short)
+}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractStringSetting : AbstractSetting {
- var string: String
+ val string: String
+
+ fun setString(value: String)
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
enum class BooleanSetting(
override val key: String,
- override val section: String,
- override val defaultValue: Boolean
+ override val category: Settings.Category,
+ override val androidDefault: Boolean? = null
) : AbstractBooleanSetting {
- CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
- FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
- FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
- PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
- USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
-
- override var boolean: Boolean = defaultValue
+ AUDIO_MUTED("audio_muted", Settings.Category.Audio),
+ CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
+ FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
+ FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
+ RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
+ USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false),
+ RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
+ RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
+ RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
+ RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false),
+ RENDERER_DEBUG("debug", Settings.Category.Renderer),
+ PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
+ USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
+
+ override val boolean: Boolean
+ get() = NativeConfig.getBoolean(key, false)
+
+ override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
+
+ override val defaultValue: Boolean by lazy {
+ androidDefault ?: NativeConfig.getBoolean(key, true)
+ }
override val valueAsString: String
- get() = boolean.toString()
-
- override val isRuntimeEditable: Boolean
- get() {
- for (setting in NOT_RUNTIME_EDITABLE) {
- if (setting == this) {
- return false
- }
- }
- return true
- }
-
- companion object {
- private val NOT_RUNTIME_EDITABLE = listOf(
- PICTURE_IN_PICTURE,
- USE_CUSTOM_RTC
- )
-
- fun from(key: String): BooleanSetting? =
- BooleanSetting.values().firstOrNull { it.key == key }
-
- fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
- }
+ get() = if (boolean) "1" else "0"
+
+ override fun reset() = NativeConfig.setBoolean(key, defaultValue)
}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class ByteSetting(
+ override val key: String,
+ override val category: Settings.Category
+) : AbstractByteSetting {
+ AUDIO_VOLUME("volume", Settings.Category.Audio);
+
+ override val byte: Byte
+ get() = NativeConfig.getByte(key, false)
+
+ override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
+
+ override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
+
+ override val valueAsString: String
+ get() = byte.toString()
+
+ override fun reset() = NativeConfig.setByte(key, defaultValue)
+}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
enum class FloatSetting(
override val key: String,
- override val section: String,
- override val defaultValue: Float
+ override val category: Settings.Category
) : AbstractFloatSetting {
// No float settings currently exist
- EMPTY_SETTING("", "", 0f);
-
- override var float: Float = defaultValue
+ EMPTY_SETTING("", Settings.Category.UiGeneral);
- override val valueAsString: String
- get() = float.toString()
+ override val float: Float
+ get() = NativeConfig.getFloat(key, false)
- override val isRuntimeEditable: Boolean
- get() {
- for (setting in NOT_RUNTIME_EDITABLE) {
- if (setting == this) {
- return false
- }
- }
- return true
- }
+ override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
- companion object {
- private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
+ override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
- fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
+ override val valueAsString: String
+ get() = float.toString()
- fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
- }
+ override fun reset() = NativeConfig.setFloat(key, defaultValue)
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
enum class IntSetting(
override val key: String,
- override val section: String,
- override val defaultValue: Int
+ override val category: Settings.Category,
+ override val androidDefault: Int? = null
) : AbstractIntSetting {
- RENDERER_USE_SPEED_LIMIT(
- "use_speed_limit",
- Settings.SECTION_RENDERER,
- 1
- ),
- USE_DOCKED_MODE(
- "use_docked_mode",
- Settings.SECTION_SYSTEM,
- 0
- ),
- RENDERER_USE_DISK_SHADER_CACHE(
- "use_disk_shader_cache",
- Settings.SECTION_RENDERER,
- 1
- ),
- RENDERER_FORCE_MAX_CLOCK(
- "force_max_clock",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_ASYNCHRONOUS_SHADERS(
- "use_asynchronous_shaders",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_REACTIVE_FLUSHING(
- "use_reactive_flushing",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_DEBUG(
- "debug",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_SPEED_LIMIT(
- "speed_limit",
- Settings.SECTION_RENDERER,
- 100
- ),
- CPU_ACCURACY(
- "cpu_accuracy",
- Settings.SECTION_CPU,
- 0
- ),
- REGION_INDEX(
- "region_index",
- Settings.SECTION_SYSTEM,
- -1
- ),
- LANGUAGE_INDEX(
- "language_index",
- Settings.SECTION_SYSTEM,
- 1
- ),
- RENDERER_BACKEND(
- "backend",
- Settings.SECTION_RENDERER,
- 1
- ),
- RENDERER_ACCURACY(
- "gpu_accuracy",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_RESOLUTION(
- "resolution_setup",
- Settings.SECTION_RENDERER,
- 2
- ),
- RENDERER_VSYNC(
- "use_vsync",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_SCALING_FILTER(
- "scaling_filter",
- Settings.SECTION_RENDERER,
- 1
- ),
- RENDERER_ANTI_ALIASING(
- "anti_aliasing",
- Settings.SECTION_RENDERER,
- 0
- ),
- RENDERER_SCREEN_LAYOUT(
- "screen_layout",
- Settings.SECTION_RENDERER,
- Settings.LayoutOption_MobileLandscape
- ),
- RENDERER_ASPECT_RATIO(
- "aspect_ratio",
- Settings.SECTION_RENDERER,
- 0
- ),
- AUDIO_VOLUME(
- "volume",
- Settings.SECTION_AUDIO,
- 100
- );
-
- override var int: Int = defaultValue
+ CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
+ REGION_INDEX("region_index", Settings.Category.System),
+ LANGUAGE_INDEX("language_index", Settings.Category.System),
+ RENDERER_BACKEND("backend", Settings.Category.Renderer),
+ RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0),
+ RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
+ RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
+ RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
+ RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
+ RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
+ RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
+ AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
+
+ override val int: Int
+ get() = NativeConfig.getInt(key, false)
+
+ override fun setInt(value: Int) = NativeConfig.setInt(key, value)
+
+ override val defaultValue: Int by lazy {
+ androidDefault ?: NativeConfig.getInt(key, true)
+ }
override val valueAsString: String
get() = int.toString()
- override val isRuntimeEditable: Boolean
- get() {
- for (setting in NOT_RUNTIME_EDITABLE) {
- if (setting == this) {
- return false
- }
- }
- return true
- }
-
- companion object {
- private val NOT_RUNTIME_EDITABLE = listOf(
- RENDERER_USE_DISK_SHADER_CACHE,
- RENDERER_ASYNCHRONOUS_SHADERS,
- RENDERER_DEBUG,
- RENDERER_BACKEND,
- RENDERER_RESOLUTION,
- RENDERER_VSYNC
- )
-
- fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
-
- fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
- }
+ override fun reset() = NativeConfig.setInt(key, defaultValue)
}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class LongSetting(
+ override val key: String,
+ override val category: Settings.Category
+) : AbstractLongSetting {
+ CUSTOM_RTC("custom_rtc", Settings.Category.System);
+
+ override val long: Long
+ get() = NativeConfig.getLong(key, false)
+
+ override fun setLong(value: Long) = NativeConfig.setLong(key, value)
+
+ override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
+
+ override val valueAsString: String
+ get() = long.toString()
+
+ override fun reset() = NativeConfig.setLong(key, defaultValue)
+}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.model
-
-/**
- * A semantically-related group of Settings objects. These Settings are
- * internally stored as a HashMap.
- */
-class SettingSection(val name: String) {
- val settings = HashMap<String, AbstractSetting>()
-
- /**
- * Convenience method; inserts a value directly into the backing HashMap.
- *
- * @param setting The Setting to be inserted.
- */
- fun putSetting(setting: AbstractSetting) {
- settings[setting.key!!] = setting
- }
-
- /**
- * Convenience method; gets a value directly from the backing HashMap.
- *
- * @param key Used to retrieve the Setting.
- * @return A Setting object (you should probably cast this before using)
- */
- fun getSetting(key: String): AbstractSetting? {
- return settings[key]
- }
-
- fun mergeSection(settingSection: SettingSection) {
- for (setting in settingSection.settings.values) {
- putSetting(setting)
- }
- }
-}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils
-import java.util.*
+import android.widget.Toast
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-class Settings {
- private var gameId: String? = null
+object Settings {
+ private val context get() = YuzuApplication.appContext
- var isLoaded = false
-
- /**
- * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
- * when getting a key not already in the map
- */
- class SettingsSectionMap : HashMap<String, SettingSection?>() {
- override operator fun get(key: String): SettingSection? {
- if (!super.containsKey(key)) {
- val section = SettingSection(key)
- super.put(key, section)
- return section
- }
- return super.get(key)
- }
- }
-
- var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
-
- fun getSection(sectionName: String): SettingSection? {
- return sections[sectionName]
- }
-
- val isEmpty: Boolean
- get() = sections.isEmpty()
-
- fun loadSettings(view: SettingsActivityView? = null) {
- sections = SettingsSectionMap()
- loadYuzuSettings(view)
- if (!TextUtils.isEmpty(gameId)) {
- loadCustomGameSettings(gameId!!, view)
- }
- isLoaded = true
- }
-
- private fun loadYuzuSettings(view: SettingsActivityView?) {
- for ((fileName) in configFileSectionsMap) {
- sections.putAll(SettingsFile.readFile(fileName, view))
- }
- }
-
- private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
- // Custom game settings
- mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
- }
-
- private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
- for ((key, updatedSection) in updatedSections) {
- if (sections.containsKey(key)) {
- val originalSection = sections[key]
- originalSection!!.mergeSection(updatedSection!!)
- } else {
- sections[key] = updatedSection
- }
- }
- }
-
- fun loadSettings(gameId: String, view: SettingsActivityView) {
- this.gameId = gameId
- loadSettings(view)
- }
-
- fun saveSettings(view: SettingsActivityView) {
+ fun saveSettings(gameId: String = "") {
if (TextUtils.isEmpty(gameId)) {
- view.showToastMessage(
- YuzuApplication.appContext.getString(R.string.ini_saved),
- false
- )
-
- for ((fileName, sectionNames) in configFileSectionsMap) {
- val iniSections = TreeMap<String, SettingSection>()
- for (section in sectionNames) {
- iniSections[section] = sections[section]!!
- }
-
- SettingsFile.saveFile(fileName, iniSections, view)
- }
+ Toast.makeText(
+ context,
+ context.getString(R.string.ini_saved),
+ Toast.LENGTH_SHORT
+ ).show()
+ SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
} else {
- // Custom game settings
- view.showToastMessage(
- YuzuApplication.appContext.getString(R.string.gameid_saved, gameId),
- false
- )
-
- SettingsFile.saveCustomGameSettings(gameId, sections)
+ // TODO: Save custom game settings
+ Toast.makeText(
+ context,
+ context.getString(R.string.gameid_saved, gameId),
+ Toast.LENGTH_SHORT
+ ).show()
}
}
- companion object {
- const val SECTION_GENERAL = "General"
- const val SECTION_SYSTEM = "System"
- const val SECTION_RENDERER = "Renderer"
- const val SECTION_AUDIO = "Audio"
- const val SECTION_CPU = "Cpu"
- const val SECTION_THEME = "Theme"
- const val SECTION_DEBUG = "Debug"
-
- const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
-
- const val PREF_OVERLAY_VERSION = "OverlayVersion"
- const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
- const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
- const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
- val overlayLayoutPrefs = listOf(
- PREF_LANDSCAPE_OVERLAY_VERSION,
- PREF_PORTRAIT_OVERLAY_VERSION,
- PREF_FOLDABLE_OVERLAY_VERSION
- )
-
- const val PREF_CONTROL_SCALE = "controlScale"
- const val PREF_CONTROL_OPACITY = "controlOpacity"
- const val PREF_TOUCH_ENABLED = "isTouchEnabled"
- const val PREF_BUTTON_A = "buttonToggle0"
- const val PREF_BUTTON_B = "buttonToggle1"
- const val PREF_BUTTON_X = "buttonToggle2"
- const val PREF_BUTTON_Y = "buttonToggle3"
- const val PREF_BUTTON_L = "buttonToggle4"
- const val PREF_BUTTON_R = "buttonToggle5"
- const val PREF_BUTTON_ZL = "buttonToggle6"
- const val PREF_BUTTON_ZR = "buttonToggle7"
- const val PREF_BUTTON_PLUS = "buttonToggle8"
- const val PREF_BUTTON_MINUS = "buttonToggle9"
- const val PREF_BUTTON_DPAD = "buttonToggle10"
- const val PREF_STICK_L = "buttonToggle11"
- const val PREF_STICK_R = "buttonToggle12"
- const val PREF_BUTTON_STICK_L = "buttonToggle13"
- const val PREF_BUTTON_STICK_R = "buttonToggle14"
- const val PREF_BUTTON_HOME = "buttonToggle15"
- const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
-
- const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
- const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
- const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
- const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
- const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
-
- const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
- const val PREF_THEME = "Theme"
- const val PREF_THEME_MODE = "ThemeMode"
- const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
-
- private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
-
- val overlayPreferences = listOf(
- PREF_OVERLAY_VERSION,
- PREF_CONTROL_SCALE,
- PREF_CONTROL_OPACITY,
- PREF_TOUCH_ENABLED,
- PREF_BUTTON_A,
- PREF_BUTTON_B,
- PREF_BUTTON_X,
- PREF_BUTTON_Y,
- PREF_BUTTON_L,
- PREF_BUTTON_R,
- PREF_BUTTON_ZL,
- PREF_BUTTON_ZR,
- PREF_BUTTON_PLUS,
- PREF_BUTTON_MINUS,
- PREF_BUTTON_DPAD,
- PREF_STICK_L,
- PREF_STICK_R,
- PREF_BUTTON_HOME,
- PREF_BUTTON_SCREENSHOT,
- PREF_BUTTON_STICK_L,
- PREF_BUTTON_STICK_R
- )
-
- const val LayoutOption_Unspecified = 0
- const val LayoutOption_MobilePortrait = 4
- const val LayoutOption_MobileLandscape = 5
+ enum class Category {
+ Android,
+ Audio,
+ Core,
+ Cpu,
+ CpuDebug,
+ CpuUnsafe,
+ Renderer,
+ RendererAdvanced,
+ RendererDebug,
+ System,
+ SystemAudio,
+ DataStorage,
+ Debugging,
+ DebuggingGraphics,
+ Miscellaneous,
+ Network,
+ WebService,
+ AddOns,
+ Controls,
+ Ui,
+ UiGeneral,
+ UiLayout,
+ UiGameList,
+ Screenshots,
+ Shortcuts,
+ Multiplayer,
+ Services,
+ Paths,
+ MaxEnum
+ }
- init {
- configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
- listOf(
- SECTION_GENERAL,
- SECTION_SYSTEM,
- SECTION_RENDERER,
- SECTION_AUDIO,
- SECTION_CPU
- )
- }
+ val settingsList = listOf<AbstractSetting>(
+ *BooleanSetting.values(),
+ *ByteSetting.values(),
+ *ShortSetting.values(),
+ *IntSetting.values(),
+ *FloatSetting.values(),
+ *LongSetting.values(),
+ *StringSetting.values()
+ )
+
+ const val SECTION_GENERAL = "General"
+ const val SECTION_SYSTEM = "System"
+ const val SECTION_RENDERER = "Renderer"
+ const val SECTION_AUDIO = "Audio"
+ const val SECTION_CPU = "Cpu"
+ const val SECTION_THEME = "Theme"
+ const val SECTION_DEBUG = "Debug"
+
+ enum class MenuTag(val titleId: Int) {
+ SECTION_ROOT(R.string.advanced_settings),
+ SECTION_GENERAL(R.string.preferences_general),
+ SECTION_SYSTEM(R.string.preferences_system),
+ SECTION_RENDERER(R.string.preferences_graphics),
+ SECTION_AUDIO(R.string.preferences_audio),
+ SECTION_CPU(R.string.cpu),
+ SECTION_THEME(R.string.preferences_theme),
+ SECTION_DEBUG(R.string.preferences_debug);
}
+
+ const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
+
+ const val PREF_OVERLAY_VERSION = "OverlayVersion"
+ const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
+ const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
+ const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
+ val overlayLayoutPrefs = listOf(
+ PREF_LANDSCAPE_OVERLAY_VERSION,
+ PREF_PORTRAIT_OVERLAY_VERSION,
+ PREF_FOLDABLE_OVERLAY_VERSION
+ )
+
+ const val PREF_CONTROL_SCALE = "controlScale"
+ const val PREF_CONTROL_OPACITY = "controlOpacity"
+ const val PREF_TOUCH_ENABLED = "isTouchEnabled"
+ const val PREF_BUTTON_A = "buttonToggle0"
+ const val PREF_BUTTON_B = "buttonToggle1"
+ const val PREF_BUTTON_X = "buttonToggle2"
+ const val PREF_BUTTON_Y = "buttonToggle3"
+ const val PREF_BUTTON_L = "buttonToggle4"
+ const val PREF_BUTTON_R = "buttonToggle5"
+ const val PREF_BUTTON_ZL = "buttonToggle6"
+ const val PREF_BUTTON_ZR = "buttonToggle7"
+ const val PREF_BUTTON_PLUS = "buttonToggle8"
+ const val PREF_BUTTON_MINUS = "buttonToggle9"
+ const val PREF_BUTTON_DPAD = "buttonToggle10"
+ const val PREF_STICK_L = "buttonToggle11"
+ const val PREF_STICK_R = "buttonToggle12"
+ const val PREF_BUTTON_STICK_L = "buttonToggle13"
+ const val PREF_BUTTON_STICK_R = "buttonToggle14"
+ const val PREF_BUTTON_HOME = "buttonToggle15"
+ const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
+
+ const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
+ const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
+ const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
+ const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
+ const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
+
+ const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
+ const val PREF_THEME = "Theme"
+ const val PREF_THEME_MODE = "ThemeMode"
+ const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
+
+ val overlayPreferences = listOf(
+ PREF_OVERLAY_VERSION,
+ PREF_CONTROL_SCALE,
+ PREF_CONTROL_OPACITY,
+ PREF_TOUCH_ENABLED,
+ PREF_BUTTON_A,
+ PREF_BUTTON_B,
+ PREF_BUTTON_X,
+ PREF_BUTTON_Y,
+ PREF_BUTTON_L,
+ PREF_BUTTON_R,
+ PREF_BUTTON_ZL,
+ PREF_BUTTON_ZR,
+ PREF_BUTTON_PLUS,
+ PREF_BUTTON_MINUS,
+ PREF_BUTTON_DPAD,
+ PREF_STICK_L,
+ PREF_STICK_R,
+ PREF_BUTTON_HOME,
+ PREF_BUTTON_SCREENSHOT,
+ PREF_BUTTON_STICK_L,
+ PREF_BUTTON_STICK_R
+ )
+
+ const val LayoutOption_Unspecified = 0
+ const val LayoutOption_MobilePortrait = 4
+ const val LayoutOption_MobileLandscape = 5
}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class ShortSetting(
+ override val key: String,
+ override val category: Settings.Category
+) : AbstractShortSetting {
+ RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
+
+ override val short: Short
+ get() = NativeConfig.getShort(key, false)
+
+ override fun setShort(value: Short) = NativeConfig.setShort(key, value)
+
+ override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
+
+ override val valueAsString: String
+ get() = short.toString()
+
+ override fun reset() = NativeConfig.setShort(key, defaultValue)
+}
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 @@
package org.yuzu.yuzu_emu.features.settings.model
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
enum class StringSetting(
override val key: String,
- override val section: String,
- override val defaultValue: String
+ override val category: Settings.Category
) : AbstractStringSetting {
- AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
- CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
+ // No string settings currently exist
+ EMPTY_SETTING("", Settings.Category.UiGeneral);
+
+ override val string: String
+ get() = NativeConfig.getString(key, false)
+
+ override fun setString(value: String) = NativeConfig.setString(key, value)
- override var string: String = defaultValue
+ override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
override val valueAsString: String
get() = string
- override val isRuntimeEditable: Boolean
- get() {
- for (setting in NOT_RUNTIME_EDITABLE) {
- if (setting == this) {
- return false
- }
- }
- return true
- }
-
- companion object {
- private val NOT_RUNTIME_EDITABLE = listOf(
- CUSTOM_RTC
- )
-
- fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
-
- fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
- }
+ override fun reset() = NativeConfig.setString(key, defaultValue)
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
class DateTimeSetting(
- setting: AbstractSetting?,
+ private val longSetting: AbstractLongSetting,
titleId: Int,
- descriptionId: Int,
- val key: String? = null,
- private val defaultValue: String? = null
-) : SettingsItem(setting, titleId, descriptionId) {
+ descriptionId: Int
+) : SettingsItem(longSetting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING
- val value: String
- get() = if (setting != null) {
- val setting = setting as AbstractStringSetting
- setting.string
- } else {
- defaultValue!!
- }
-
- fun setSelectedValue(datetime: String): AbstractStringSetting {
- val stringSetting = setting as AbstractStringSetting
- stringSetting.string = datetime
- return stringSetting
- }
+ var value: Long
+ get() = longSetting.long
+ set(value) = (setting as AbstractLongSetting).setLong(value)
}
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
class HeaderSetting(
titleId: Int
-) : SettingsItem(null, titleId, 0) {
+) : SettingsItem(emptySetting, titleId, 0) {
override val type = TYPE_HEADER
}
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(
descriptionId: Int,
val isRuntimeRunnable: Boolean,
val runnable: () -> Unit
-) : SettingsItem(null, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_RUNNABLE
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.LongSetting
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
/**
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -14,7 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
* file.)
*/
abstract class SettingsItem(
- var setting: AbstractSetting?,
+ val setting: AbstractSetting,
val nameId: Int,
val descriptionId: Int
) {
@@ -23,7 +31,7 @@ abstract class SettingsItem(
val isEditable: Boolean
get() {
if (!NativeLibrary.isRunning()) return true
- return setting?.isRuntimeEditable ?: false
+ return setting.isRuntimeModifiable
}
companion object {
@@ -35,5 +43,240 @@ abstract class SettingsItem(
const val TYPE_STRING_SINGLE_CHOICE = 5
const val TYPE_DATETIME_SETTING = 6
const val TYPE_RUNNABLE = 7
+
+ const val FASTMEM_COMBINED = "fastmem_combined"
+
+ val emptySetting = object : AbstractSetting {
+ override val key: String = ""
+ override val category: Settings.Category = Settings.Category.Ui
+ override val defaultValue: Any = false
+ override fun reset() {}
+ }
+
+ // Extension for putting SettingsItems into a hashmap without repeating yourself
+ fun HashMap<String, SettingsItem>.put(item: SettingsItem) {
+ put(item.setting.key, item)
+ }
+
+ // List of all general
+ val settingsItems = HashMap<String, SettingsItem>().apply {
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_USE_SPEED_LIMIT,
+ R.string.frame_limit_enable,
+ R.string.frame_limit_enable_description
+ )
+ )
+ put(
+ SliderSetting(
+ ShortSetting.RENDERER_SPEED_LIMIT,
+ R.string.frame_limit_slider,
+ R.string.frame_limit_slider_description,
+ 1,
+ 200,
+ "%"
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.CPU_ACCURACY,
+ R.string.cpu_accuracy,
+ 0,
+ R.array.cpuAccuracyNames,
+ R.array.cpuAccuracyValues
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.PICTURE_IN_PICTURE,
+ R.string.picture_in_picture,
+ R.string.picture_in_picture_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.USE_DOCKED_MODE,
+ R.string.use_docked_mode,
+ R.string.use_docked_mode_description
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.REGION_INDEX,
+ R.string.emulated_region,
+ 0,
+ R.array.regionNames,
+ R.array.regionValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.LANGUAGE_INDEX,
+ R.string.emulated_language,
+ 0,
+ R.array.languageNames,
+ R.array.languageValues
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.USE_CUSTOM_RTC,
+ R.string.use_custom_rtc,
+ R.string.use_custom_rtc_description
+ )
+ )
+ put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_ACCURACY,
+ R.string.renderer_accuracy,
+ 0,
+ R.array.rendererAccuracyNames,
+ R.array.rendererAccuracyValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_RESOLUTION,
+ R.string.renderer_resolution,
+ 0,
+ R.array.rendererResolutionNames,
+ R.array.rendererResolutionValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_VSYNC,
+ R.string.renderer_vsync,
+ 0,
+ R.array.rendererVSyncNames,
+ R.array.rendererVSyncValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_SCALING_FILTER,
+ R.string.renderer_scaling_filter,
+ 0,
+ R.array.rendererScalingFilterNames,
+ R.array.rendererScalingFilterValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_ANTI_ALIASING,
+ R.string.renderer_anti_aliasing,
+ 0,
+ R.array.rendererAntiAliasingNames,
+ R.array.rendererAntiAliasingValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_SCREEN_LAYOUT,
+ R.string.renderer_screen_layout,
+ 0,
+ R.array.rendererScreenLayoutNames,
+ R.array.rendererScreenLayoutValues
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_ASPECT_RATIO,
+ R.string.renderer_aspect_ratio,
+ 0,
+ R.array.rendererAspectRatioNames,
+ R.array.rendererAspectRatioValues
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
+ R.string.use_disk_shader_cache,
+ R.string.use_disk_shader_cache_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
+ R.string.renderer_force_max_clock,
+ R.string.renderer_force_max_clock_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
+ R.string.renderer_asynchronous_shaders,
+ R.string.renderer_asynchronous_shaders_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_REACTIVE_FLUSHING,
+ R.string.renderer_reactive_flushing,
+ R.string.renderer_reactive_flushing_description
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.AUDIO_OUTPUT_ENGINE,
+ R.string.audio_output_engine,
+ 0,
+ R.array.outputEngineEntries,
+ R.array.outputEngineValues
+ )
+ )
+ put(
+ SliderSetting(
+ ByteSetting.AUDIO_VOLUME,
+ R.string.audio_volume,
+ R.string.audio_volume_description,
+ 0,
+ 100,
+ "%"
+ )
+ )
+ put(
+ SingleChoiceSetting(
+ IntSetting.RENDERER_BACKEND,
+ R.string.renderer_api,
+ 0,
+ R.array.rendererApiNames,
+ R.array.rendererApiValues
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.RENDERER_DEBUG,
+ R.string.renderer_debug,
+ R.string.renderer_debug_description
+ )
+ )
+ put(
+ SwitchSetting(
+ BooleanSetting.CPU_DEBUG_MODE,
+ R.string.cpu_debug_mode,
+ R.string.cpu_debug_mode_description
+ )
+ )
+
+ val fastmem = object : AbstractBooleanSetting {
+ override val boolean: Boolean
+ get() =
+ BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
+
+ override fun setBoolean(value: Boolean) {
+ BooleanSetting.FASTMEM.setBoolean(value)
+ BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
+ }
+
+ override val key: String = FASTMEM_COMBINED
+ override val category = Settings.Category.Cpu
+ override val isRuntimeModifiable: Boolean = false
+ override val defaultValue: Boolean = true
+ override fun reset() = setBoolean(defaultValue)
+ }
+ put(SwitchSetting(fastmem, R.string.fastmem, 0))
+ }
}
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
class SingleChoiceSetting(
- setting: AbstractIntSetting?,
+ setting: AbstractSetting,
titleId: Int,
descriptionId: Int,
val choicesId: Int,
- val valuesId: Int,
- val key: String? = null,
- val defaultValue: Int? = null
+ val valuesId: Int
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SINGLE_CHOICE
- val selectedValue: Int
- get() = if (setting != null) {
- val setting = setting as AbstractIntSetting
- setting.int
- } else {
- defaultValue!!
+ var selectedValue: Int
+ get() {
+ return when (setting) {
+ is AbstractIntSetting -> setting.int
+ else -> -1
+ }
+ }
+ set(value) {
+ when (setting) {
+ is AbstractIntSetting -> setting.setInt(value)
+ }
}
-
- /**
- * Write a value to the backing int. If that int was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param selection New value of the int.
- * @return the existing setting with the new value applied.
- */
- fun setSelectedValue(selection: Int): AbstractIntSetting {
- val intSetting = setting as AbstractIntSetting
- intSetting.int = selection
- return intSetting
- }
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
-import kotlin.math.roundToInt
+import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.utils.Log
+import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
+import kotlin.math.roundToInt
class SliderSetting(
- setting: AbstractSetting?,
+ setting: AbstractSetting,
titleId: Int,
descriptionId: Int,
val min: Int,
val max: Int,
- val units: String,
- val key: String? = null,
- val defaultValue: Int? = null
+ val units: String
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER
- val selectedValue: Int
+ var selectedValue: Int
get() {
- val setting = setting ?: return defaultValue!!
return when (setting) {
+ is AbstractByteSetting -> setting.byte.toInt()
+ is AbstractShortSetting -> setting.short.toInt()
is AbstractIntSetting -> setting.int
is AbstractFloatSetting -> setting.float.roundToInt()
- else -> {
- Log.error("[SliderSetting] Error casting setting type.")
- -1
- }
+ else -> -1
+ }
+ }
+ set(value) {
+ when (setting) {
+ is AbstractByteSetting -> setting.setByte(value.toByte())
+ is AbstractShortSetting -> setting.setShort(value.toShort())
+ is AbstractIntSetting -> setting.setInt(value)
+ is AbstractFloatSetting -> setting.setFloat(value.toFloat())
}
}
-
- /**
- * Write a value to the backing int. If that int was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param selection New value of the int.
- * @return the existing setting with the new value applied.
- */
- fun setSelectedValue(selection: Int): AbstractIntSetting {
- val intSetting = setting as AbstractIntSetting
- intSetting.int = selection
- return intSetting
- }
-
- /**
- * Write a value to the backing float. If that float was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param selection New value of the float.
- * @return the existing setting with the new value applied.
- */
- fun setSelectedValue(selection: Float): AbstractFloatSetting {
- val floatSetting = setting as AbstractFloatSetting
- floatSetting.float = selection
- return floatSetting
- }
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
class StringSingleChoiceSetting(
- setting: AbstractSetting?,
+ private val stringSetting: AbstractStringSetting,
titleId: Int,
descriptionId: Int,
val choices: Array<String>,
- val values: Array<String>?,
- val key: String? = null,
- private val defaultValue: String? = null
-) : SettingsItem(setting, titleId, descriptionId) {
+ val values: Array<String>
+) : SettingsItem(stringSetting, titleId, descriptionId) {
override val type = TYPE_STRING_SINGLE_CHOICE
- fun getValueAt(index: Int): String? {
- if (values == null) return null
- return if (index >= 0 && index < values.size) {
- values[index]
- } else {
- ""
- }
- }
+ fun getValueAt(index: Int): String =
+ if (index >= 0 && index < values.size) values[index] else ""
+
+ var selectedValue: String
+ get() = stringSetting.string
+ set(value) = stringSetting.setString(value)
- val selectedValue: String
- get() = if (setting != null) {
- val setting = setting as AbstractStringSetting
- setting.string
- } else {
- defaultValue!!
- }
val selectValueIndex: Int
get() {
- val selectedValue = selectedValue
- for (i in values!!.indices) {
+ for (i in values.indices) {
if (values[i] == selectedValue) {
return i
}
}
return -1
}
-
- /**
- * Write a value to the backing int. If that int was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param selection New value of the int.
- * @return the existing setting with the new value applied.
- */
- fun setSelectedValue(selection: String): AbstractStringSetting {
- val stringSetting = setting as AbstractStringSetting
- stringSetting.string = selection
- return stringSetting
- }
}
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 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+
class SubmenuSetting(
titleId: Int,
descriptionId: Int,
- val menuKey: String
-) : SettingsItem(null, titleId, descriptionId) {
+ val menuKey: Settings.MenuTag
+) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_SUBMENU
}
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
class SwitchSetting(
setting: AbstractSetting,
titleId: Int,
- descriptionId: Int,
- val key: String? = null,
- val defaultValue: Any? = null
+ descriptionId: Int
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SWITCH
- val isChecked: Boolean
+ var checked: Boolean
get() {
- if (setting == null) {
- return defaultValue as Boolean
+ return when (setting) {
+ is AbstractIntSetting -> setting.int == 1
+ is AbstractBooleanSetting -> setting.boolean
+ else -> false
}
-
- // Try integer setting
- try {
- val setting = setting as AbstractIntSetting
- return setting.int == 1
- } catch (_: ClassCastException) {
- }
-
- // Try boolean setting
- try {
- val setting = setting as AbstractBooleanSetting
- return setting.boolean
- } catch (_: ClassCastException) {
- }
- return defaultValue as Boolean
}
-
- /**
- * Write a value to the backing boolean. If that boolean was previously null,
- * initializes a new one and returns it, so it can be added to the Hashmap.
- *
- * @param checked Pretty self explanatory.
- * @return the existing setting with the new value applied.
- */
- fun setChecked(checked: Boolean): AbstractSetting {
- // Try integer setting
- try {
- val setting = setting as AbstractIntSetting
- setting.int = if (checked) 1 else 0
- return setting
- } catch (_: ClassCastException) {
+ set(value) {
+ when (setting) {
+ is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
+ is AbstractBooleanSetting -> setting.setBoolean(value)
+ }
}
-
- // Try boolean setting
- val setting = setting as AbstractBooleanSetting
- setting.boolean = checked
- return setting
- }
}
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 @@
package org.yuzu.yuzu_emu.features.settings.ui
-import android.content.Context
-import android.content.Intent
import android.os.Bundle
-import android.view.Menu
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
-import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.updatePadding
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
import java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
-import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
-import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
-import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
+import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.*
-class SettingsActivity : AppCompatActivity(), SettingsActivityView {
- private val presenter = SettingsActivityPresenter(this)
-
+class SettingsActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsBinding
- private val settingsViewModel: SettingsViewModel by viewModels()
+ private val args by navArgs<SettingsActivityArgs>()
- override val settings: Settings get() = settingsViewModel.settings
+ private val settingsViewModel: SettingsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
@@ -48,16 +45,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
- WindowCompat.setDecorFitsSystemWindows(window, false)
+ settingsViewModel.game = args.game
- val launcher = intent
- val gameID = launcher.getStringExtra(ARG_GAME_ID)
- val menuTag = launcher.getStringExtra(ARG_MENU_TAG)
- presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
+ val navHostFragment =
+ supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+ navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras)
- // Show "Back" button in the action bar for navigation
- setSupportActionBar(binding.toolbarSettings)
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ if (savedInstanceState != null) {
+ settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
+ }
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
@@ -73,6 +71,42 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
)
}
+ lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.shouldRecreate.collectLatest {
+ if (it) {
+ settingsViewModel.setShouldRecreate(false)
+ recreate()
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.shouldNavigateBack.collectLatest {
+ if (it) {
+ settingsViewModel.setShouldNavigateBack(false)
+ navigateBack()
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
+ if (it) {
+ settingsViewModel.setShouldShowResetSettingsDialog(false)
+ ResetSettingsDialogFragment().show(
+ supportFragmentManager,
+ ResetSettingsDialogFragment.TAG
+ )
+ }
+ }
+ }
+ }
+ }
+
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
@@ -83,34 +117,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
setInsets()
}
- override fun onSupportNavigateUp(): Boolean {
- navigateBack()
- return true
- }
-
- private fun navigateBack() {
- if (supportFragmentManager.backStackEntryCount > 0) {
- supportFragmentManager.popBackStack()
+ fun navigateBack() {
+ val navHostFragment =
+ supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+ if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
+ navHostFragment.navController.popBackStack()
} else {
finish()
}
}
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- val inflater = menuInflater
- inflater.inflate(R.menu.menu_settings, menu)
- return true
- }
-
override fun onSaveInstanceState(outState: Bundle) {
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState)
- presenter.saveState(outState)
+ outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
}
override fun onStart() {
super.onStart()
- presenter.onStart()
+ // TODO: Load custom settings contextually
+ if (!DirectoryInitialization.areDirectoriesReady) {
+ DirectoryInitialization.start()
+ }
}
/**
@@ -120,143 +148,51 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
*/
override fun onStop() {
super.onStop()
- presenter.onStop(isFinishing)
- }
-
- override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
- if (!addToStack && settingsFragment != null) {
- return
+ if (isFinishing && settingsViewModel.shouldSave) {
+ Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
+ Settings.saveSettings()
}
-
- val transaction = supportFragmentManager.beginTransaction()
- if (addToStack) {
- if (areSystemAnimationsEnabled()) {
- transaction.setCustomAnimations(
- R.anim.anim_settings_fragment_in,
- R.anim.anim_settings_fragment_out,
- 0,
- R.anim.anim_pop_settings_fragment_out
- )
- }
- transaction.addToBackStack(null)
- }
- transaction.replace(
- R.id.frame_content,
- SettingsFragment.newInstance(menuTag, gameId),
- FRAGMENT_TAG
- )
- transaction.commit()
- }
-
- private fun areSystemAnimationsEnabled(): Boolean {
- val duration = android.provider.Settings.Global.getFloat(
- contentResolver,
- android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
- 1f
- )
- val transition = android.provider.Settings.Global.getFloat(
- contentResolver,
- android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
- 1f
- )
- return duration != 0f && transition != 0f
}
- override fun onSettingsFileLoaded() {
- val fragment: SettingsFragmentView? = settingsFragment
- fragment?.loadSettingsList()
- }
-
- override fun onSettingsFileNotFound() {
- val fragment: SettingsFragmentView? = settingsFragment
- fragment?.loadSettingsList()
- }
-
- override fun showToastMessage(message: String, is_long: Boolean) {
- Toast.makeText(
- this,
- message,
- if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
- ).show()
- }
-
- override fun onSettingChanged() {
- presenter.onSettingChanged()
+ override fun onDestroy() {
+ settingsViewModel.clear()
+ super.onDestroy()
}
fun onSettingsReset() {
// Prevents saving to a non-existent settings file
- presenter.onSettingsReset()
-
- // Reset the static memory representation of each setting
- BooleanSetting.clear()
- FloatSetting.clear()
- IntSetting.clear()
- StringSetting.clear()
+ settingsViewModel.shouldSave = false
// Delete settings file because the user may have changed values that do not exist in the UI
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
+ Settings.settingsList.forEach { it.reset() }
- showToastMessage(getString(R.string.settings_reset), true)
+ Toast.makeText(
+ applicationContext,
+ getString(R.string.settings_reset),
+ Toast.LENGTH_LONG
+ ).show()
finish()
}
- fun setToolbarTitle(title: String) {
- binding.toolbarSettingsLayout.title = title
- }
-
- private val settingsFragment: SettingsFragment?
- get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
-
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
- binding.frameContent
+ binding.navigationBarShade
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
- view.updatePadding(
- left = barInsets.left + cutoutInsets.left,
- right = barInsets.right + cutoutInsets.right
- )
- val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
- mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
- mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
- binding.appbarSettings.layoutParams = mlpAppBar
-
- val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
+ val mlpShade = view.layoutParams as MarginLayoutParams
mlpShade.height = barInsets.bottom
- binding.navigationBarShade.layoutParams = mlpShade
+ view.layoutParams = mlpShade
windowInsets
}
}
companion object {
- private const val ARG_MENU_TAG = "menu_tag"
- private const val ARG_GAME_ID = "game_id"
- private const val FRAGMENT_TAG = "settings"
-
- fun launch(context: Context, menuTag: String?, gameId: String?) {
- val settings = Intent(context, SettingsActivity::class.java)
- settings.putExtra(ARG_MENU_TAG, menuTag)
- settings.putExtra(ARG_GAME_ID, gameId)
- context.startActivity(settings)
- }
-
- fun launch(
- context: Context,
- launcher: ActivityResultLauncher<Intent>,
- menuTag: String?,
- gameId: String?
- ) {
- val settings = Intent(context, SettingsActivity::class.java)
- settings.putExtra(ARG_MENU_TAG, menuTag)
- settings.putExtra(ARG_GAME_ID, gameId)
- launcher.launch(settings)
- }
+ private const val KEY_SHOULD_SAVE = "should_save"
}
}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import android.content.Context
-import android.os.Bundle
-import android.text.TextUtils
-import java.io.File
-import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.utils.DirectoryInitialization
-import org.yuzu.yuzu_emu.utils.Log
-
-class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
- val settings: Settings get() = activityView.settings
-
- private var shouldSave = false
- private lateinit var menuTag: String
- private lateinit var gameId: String
-
- fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
- this.menuTag = menuTag
- this.gameId = gameId
- if (savedInstanceState != null) {
- shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
- }
- }
-
- fun onStart() {
- prepareDirectoriesIfNeeded()
- }
-
- private fun loadSettingsUI() {
- if (!settings.isLoaded) {
- if (!TextUtils.isEmpty(gameId)) {
- settings.loadSettings(gameId, activityView)
- } else {
- settings.loadSettings(activityView)
- }
- }
- activityView.showSettingsFragment(menuTag, false, gameId)
- activityView.onSettingsFileLoaded()
- }
-
- private fun prepareDirectoriesIfNeeded() {
- val configFile =
- File(
- "${DirectoryInitialization.userDirectory}/config/" +
- "${SettingsFile.FILE_NAME_CONFIG}.ini"
- )
- if (!configFile.exists()) {
- Log.error(
- "${DirectoryInitialization.userDirectory}/config/" +
- "${SettingsFile.FILE_NAME_CONFIG}.ini"
- )
- Log.error("yuzu config file could not be found!")
- }
-
- if (!DirectoryInitialization.areDirectoriesReady) {
- DirectoryInitialization.start(activityView as Context)
- }
- loadSettingsUI()
- }
-
- fun onStop(finishing: Boolean) {
- if (finishing && shouldSave) {
- Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
- settings.saveSettings(activityView)
- }
- NativeLibrary.reloadSettings()
- }
-
- fun onSettingChanged() {
- shouldSave = true
- }
-
- fun onSettingsReset() {
- shouldSave = false
- }
-
- fun saveState(outState: Bundle) {
- outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
- }
-
- companion object {
- private const val KEY_SHOULD_SAVE = "should_save"
- }
-}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-
-/**
- * Abstraction for the Activity that manages SettingsFragments.
- */
-interface SettingsActivityView {
- /**
- * Show a new SettingsFragment.
- *
- * @param menuTag Identifier for the settings group that should be displayed.
- * @param addToStack Whether or not this fragment should replace a previous one.
- */
- fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
-
- /**
- * Called by a contained Fragment to get access to the Setting HashMap
- * loaded from disk, so that each Fragment doesn't need to perform its own
- * read operation.
- *
- * @return A HashMap of Settings.
- */
- val settings: Settings
-
- /**
- * Called when a load operation completes.
- */
- fun onSettingsFileLoaded()
-
- /**
- * Called when a load operation fails.
- */
- fun onSettingsFileNotFound()
-
- /**
- * Display a popup text message on screen.
- *
- * @param message The contents of the onscreen message.
- * @param is_long Whether this should be a long Toast or short one.
- */
- fun showToastMessage(message: String, is_long: Boolean)
-
- /**
- * End the activity.
- */
- fun finish()
-
- /**
- * Called by a containing Fragment to tell the Activity that a setting was changed;
- * unless this has been called, the Activity will not save to disk.
- */
- fun onSettingChanged()
-}
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 @@
package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context
-import android.content.DialogInterface
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.ViewGroup
-import android.widget.TextView
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
-import androidx.recyclerview.widget.RecyclerView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
import com.google.android.material.datepicker.MaterialDatePicker
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.slider.Slider
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
+import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.SettingsNavigationDirections
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
-import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
+import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
+import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsAdapter(
- private val fragmentView: SettingsFragmentView,
+ private val fragment: Fragment,
private val context: Context
-) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
- private var settings: ArrayList<SettingsItem>? = null
- private var clickedItem: SettingsItem? = null
- private var clickedPosition: Int
- private var dialog: AlertDialog? = null
- private var sliderProgress = 0
- private var textSliderValue: TextView? = null
-
- private var defaultCancelListener =
- DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
+) : ListAdapter<SettingsItem, SettingViewHolder>(
+ AsyncDifferConfig.Builder(DiffCallback()).build()
+) {
+ private val settingsViewModel: SettingsViewModel
+ get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
init {
- clickedPosition = -1
+ fragment.viewLifecycleOwner.lifecycleScope.launch {
+ fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ settingsViewModel.adapterItemChanged.collect {
+ if (it != -1) {
+ notifyItemChanged(it)
+ settingsViewModel.setAdapterItemChanged(-1)
+ }
+ }
+ }
+ }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
@@ -90,67 +93,41 @@ class SettingsAdapter(
}
override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
- holder.bind(getItem(position))
+ holder.bind(currentList[position])
}
- private fun getItem(position: Int): SettingsItem {
- return settings!![position]
- }
-
- override fun getItemCount(): Int {
- return if (settings != null) {
- settings!!.size
- } else {
- 0
- }
- }
+ override fun getItemCount(): Int = currentList.size
override fun getItemViewType(position: Int): Int {
- return getItem(position).type
- }
-
- fun setSettingsList(settings: ArrayList<SettingsItem>?) {
- this.settings = settings
- notifyDataSetChanged()
- }
-
- fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
- val setting = item.setChecked(checked)
- fragmentView.putSetting(setting)
- fragmentView.onSettingChanged()
+ return currentList[position].type
}
- private fun onSingleChoiceClick(item: SingleChoiceSetting) {
- clickedItem = item
- val value = getSelectionForSingleChoiceValue(item)
- dialog = MaterialAlertDialogBuilder(context)
- .setTitle(item.nameId)
- .setSingleChoiceItems(item.choicesId, value, this)
- .show()
+ fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
+ item.checked = checked
+ settingsViewModel.setShouldReloadSettingsList(true)
+ settingsViewModel.shouldSave = true
}
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
- clickedPosition = position
- onSingleChoiceClick(item)
- }
-
- private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
- clickedItem = item
- dialog = MaterialAlertDialogBuilder(context)
- .setTitle(item.nameId)
- .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
- .show()
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_SINGLE_CHOICE,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
- clickedPosition = position
- onStringSingleChoiceClick(item)
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_STRING_SINGLE_CHOICE,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
- clickedItem = item
- clickedPosition = position
- val storedTime = java.lang.Long.decode(item.value) * 1000
+ val storedTime = item.value * 1000
// Helper to extract hour and minute from epoch time
val calendar: Calendar = Calendar.getInstance()
@@ -158,7 +135,7 @@ class SettingsAdapter(
calendar.timeZone = TimeZone.getTimeZone("UTC")
var timeFormat: Int = TimeFormat.CLOCK_12H
- if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) {
+ if (DateFormat.is24HourFormat(context)) {
timeFormat = TimeFormat.CLOCK_24H
}
@@ -175,7 +152,7 @@ class SettingsAdapter(
datePicker.addOnPositiveButtonClickListener {
timePicker.show(
- (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
+ fragment.childFragmentManager,
"TimePicker"
)
}
@@ -183,157 +160,50 @@ class SettingsAdapter(
var epochTime: Long = datePicker.selection!! / 1000
epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60
- val rtcString = epochTime.toString()
- if (item.value != rtcString) {
- fragmentView.onSettingChanged()
+ if (item.value != epochTime) {
+ settingsViewModel.shouldSave = true
+ notifyItemChanged(position)
+ item.value = epochTime
}
- notifyItemChanged(clickedPosition)
- val setting = item.setSelectedValue(rtcString)
- fragmentView.putSetting(setting)
- clickedItem = null
}
datePicker.show(
- (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
+ fragment.childFragmentManager,
"DatePicker"
)
}
fun onSliderClick(item: SliderSetting, position: Int) {
- clickedItem = item
- clickedPosition = position
- sliderProgress = item.selectedValue
-
- val inflater = LayoutInflater.from(context)
- val sliderBinding = DialogSliderBinding.inflate(inflater)
-
- textSliderValue = sliderBinding.textValue
- textSliderValue!!.text = sliderProgress.toString()
- sliderBinding.textUnits.text = item.units
-
- sliderBinding.slider.apply {
- valueFrom = item.min.toFloat()
- valueTo = item.max.toFloat()
- value = sliderProgress.toFloat()
- addOnChangeListener { _: Slider, value: Float, _: Boolean ->
- sliderProgress = value.toInt()
- textSliderValue!!.text = sliderProgress.toString()
- }
- }
-
- dialog = MaterialAlertDialogBuilder(context)
- .setTitle(item.nameId)
- .setView(sliderBinding.root)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, defaultCancelListener)
- .setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
- sliderBinding.slider.value = item.defaultValue!!.toFloat()
- onClick(dialog, which)
- }
- .show()
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_SLIDER,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
fun onSubmenuClick(item: SubmenuSetting) {
- fragmentView.loadSubMenu(item.menuKey)
+ val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
+ fragment.view?.findNavController()?.navigate(action)
}
- override fun onClick(dialog: DialogInterface, which: Int) {
- when (clickedItem) {
- is SingleChoiceSetting -> {
- val scSetting = clickedItem as SingleChoiceSetting
- val value = getValueForSingleChoiceSelection(scSetting, which)
- if (scSetting.selectedValue != value) {
- fragmentView.onSettingChanged()
- }
-
- // Get the backing Setting, which may be null (if for example it was missing from the file)
- val setting = scSetting.setSelectedValue(value)
- fragmentView.putSetting(setting)
- closeDialog()
- }
-
- is StringSingleChoiceSetting -> {
- val scSetting = clickedItem as StringSingleChoiceSetting
- val value = scSetting.getValueAt(which)
- if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
- val setting = scSetting.setSelectedValue(value!!)
- fragmentView.putSetting(setting)
- closeDialog()
- }
-
- is SliderSetting -> {
- val sliderSetting = clickedItem as SliderSetting
- if (sliderSetting.selectedValue != sliderProgress) {
- fragmentView.onSettingChanged()
- }
- if (sliderSetting.setting is FloatSetting) {
- val value = sliderProgress.toFloat()
- val setting = sliderSetting.setSelectedValue(value)
- fragmentView.putSetting(setting)
- } else {
- val setting = sliderSetting.setSelectedValue(sliderProgress)
- fragmentView.putSetting(setting)
- }
- closeDialog()
- }
- }
- clickedItem = null
- sliderProgress = -1
- }
-
- fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
- MaterialAlertDialogBuilder(context)
- .setMessage(R.string.reset_setting_confirmation)
- .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
- when (setting) {
- is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
- is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
- is AbstractIntSetting -> setting.int = setting.defaultValue as Int
- is AbstractStringSetting -> setting.string = setting.defaultValue as String
- }
- notifyItemChanged(position)
- fragmentView.onSettingChanged()
- }
- .setNegativeButton(android.R.string.cancel, null)
- .show()
+ fun onLongClick(item: SettingsItem, position: Int): Boolean {
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsDialogFragment.TYPE_RESET_SETTING,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
return true
}
- fun closeDialog() {
- if (dialog != null) {
- if (clickedPosition != -1) {
- notifyItemChanged(clickedPosition)
- clickedPosition = -1
- }
- dialog!!.dismiss()
- dialog = null
+ private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
+ override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
+ return oldItem.setting.key == newItem.setting.key
}
- }
- private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
- val valuesId = item.valuesId
- return if (valuesId > 0) {
- val valuesArray = context.resources.getIntArray(valuesId)
- valuesArray[which]
- } else {
- which
- }
- }
-
- private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
- val value = item.selectedValue
- val valuesId = item.valuesId
- if (valuesId > 0) {
- val valuesArray = context.resources.getIntArray(valuesId)
- for (index in valuesArray.indices) {
- val current = valuesArray[index]
- if (current == value) {
- return index
- }
- }
- } else {
- return value
+ override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
+ return oldItem.setting.key == newItem.setting.key
}
- return -1
}
}
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 @@
package org.yuzu.yuzu_emu.features.settings.ui
-import android.content.Context
+import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.model.SettingsViewModel
-class SettingsFragment : Fragment(), SettingsFragmentView {
- override var activityView: SettingsActivityView? = null
-
- private val fragmentPresenter = SettingsFragmentPresenter(this)
+class SettingsFragment : Fragment() {
+ private lateinit var presenter: SettingsFragmentPresenter
private var settingsAdapter: SettingsAdapter? = null
private var _binding: FragmentSettingsBinding? = null
private val binding get() = _binding!!
- override fun onAttach(context: Context) {
- super.onAttach(context)
- activityView = requireActivity() as SettingsActivityView
- }
+ private val args by navArgs<SettingsFragmentArgs>()
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
- val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
- fragmentPresenter.onCreate(menuTag!!, gameId!!)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
override fun onCreateView(
@@ -48,8 +57,17 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
return binding.root
}
+ // This is using the correct scope, lint is just acting up
+ @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- settingsAdapter = SettingsAdapter(this, requireActivity())
+ settingsAdapter = SettingsAdapter(this, requireContext())
+ presenter = SettingsFragmentPresenter(
+ settingsViewModel,
+ settingsAdapter!!,
+ args.menuTag
+ )
+
+ binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
val dividerDecoration = MaterialDividerItemDecoration(
requireContext(),
LinearLayoutManager.VERTICAL
@@ -57,71 +75,89 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
dividerDecoration.isLastItemDecorated = false
binding.listSettings.apply {
adapter = settingsAdapter
- layoutManager = LinearLayoutManager(activity)
+ layoutManager = LinearLayoutManager(requireContext())
addItemDecoration(dividerDecoration)
}
- fragmentPresenter.onViewCreated()
- setInsets()
- }
-
- override fun onDetach() {
- super.onDetach()
- activityView = null
- if (settingsAdapter != null) {
- settingsAdapter!!.closeDialog()
+ binding.toolbarSettings.setNavigationOnClickListener {
+ settingsViewModel.setShouldNavigateBack(true)
}
- }
-
- override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
- settingsAdapter!!.setSettingsList(settingsList)
- }
- override fun loadSettingsList() {
- fragmentPresenter.loadSettingsList()
- }
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.shouldReloadSettingsList.collectLatest {
+ if (it) {
+ settingsViewModel.setShouldReloadSettingsList(false)
+ presenter.loadSettingsList()
+ }
+ }
+ }
+ }
+ launch {
+ settingsViewModel.isUsingSearch.collectLatest {
+ if (it) {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ } else {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ }
+ }
+ }
+ }
- override fun loadSubMenu(menuKey: String) {
- activityView!!.showSettingsFragment(
- menuKey,
- true,
- requireArguments().getString(ARGUMENT_GAME_ID)!!
- )
- }
+ if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
+ binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
+ binding.toolbarSettings.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.action_search -> {
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ view.findNavController()
+ .navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
+ true
+ }
+
+ else -> false
+ }
+ }
+ }
- override fun showToastMessage(message: String?, is_long: Boolean) {
- activityView!!.showToastMessage(message!!, is_long)
- }
+ presenter.onViewCreated()
- override fun putSetting(setting: AbstractSetting) {
- fragmentPresenter.putSetting(setting)
+ setInsets()
}
- override fun onSettingChanged() {
- activityView!!.onSettingChanged()
+ override fun onResume() {
+ super.onResume()
+ settingsViewModel.setIsUsingSearch(false)
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
- binding.listSettings
- ) { view: View, windowInsets: WindowInsetsCompat ->
- val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- view.updatePadding(bottom = insets.bottom)
+ binding.root
+ ) { _: View, windowInsets: WindowInsetsCompat ->
+ val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+ val leftInsets = barInsets.left + cutoutInsets.left
+ val rightInsets = barInsets.right + cutoutInsets.right
+
+ val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+ val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
+ mlpSettingsList.leftMargin = sideMargin + leftInsets
+ mlpSettingsList.rightMargin = sideMargin + rightInsets
+ binding.listSettings.layoutParams = mlpSettingsList
+ binding.listSettings.updatePadding(
+ bottom = barInsets.bottom
+ )
+
+ val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
+ mlpAppBar.leftMargin = leftInsets
+ mlpAppBar.rightMargin = rightInsets
+ binding.appbarSettings.layoutParams = mlpAppBar
windowInsets
}
}
-
- companion object {
- private const val ARGUMENT_MENU_TAG = "menu_tag"
- private const val ARGUMENT_GAME_ID = "game_id"
-
- fun newInstance(menuTag: String?, gameId: String?): Fragment {
- val fragment = SettingsFragment()
- val arguments = Bundle()
- arguments.putString(ARGUMENT_MENU_TAG, menuTag)
- arguments.putString(ARGUMENT_GAME_ID, gameId)
- fragment.arguments = arguments
- return fragment
- }
- }
}
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 @@
package org.yuzu.yuzu_emu.features.settings.ui
+import android.content.Context
import android.content.SharedPreferences
import android.os.Build
-import android.text.TextUtils
+import android.widget.Toast
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
-import org.yuzu.yuzu_emu.utils.ThemeHelper
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
-class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
- private var menuTag: String? = null
- private lateinit var gameId: String
- private var settingsList: ArrayList<SettingsItem>? = null
+class SettingsFragmentPresenter(
+ private val settingsViewModel: SettingsViewModel,
+ private val adapter: SettingsAdapter,
+ private var menuTag: Settings.MenuTag
+) {
+ private var settingsList = ArrayList<SettingsItem>()
- private val settingsActivity get() = fragmentView.activityView as SettingsActivity
- private val settings get() = fragmentView.activityView!!.settings
+ private val preferences: SharedPreferences
+ get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- private lateinit var preferences: SharedPreferences
+ private val context: Context get() = YuzuApplication.appContext
- fun onCreate(menuTag: String, gameId: String) {
- this.gameId = gameId
- this.menuTag = menuTag
+ // Extension for populating settings list based on paired settings
+ fun ArrayList<SettingsItem>.add(key: String) {
+ val item = SettingsItem.settingsItems[key]!!
+ val pairedSettingKey = item.setting.pairedSettingKey
+ if (pairedSettingKey.isNotEmpty()) {
+ val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+ if (!pairedSettingValue) return
+ }
+ add(item)
}
fun onViewCreated() {
- preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
loadSettingsList()
}
- fun putSetting(setting: AbstractSetting) {
- if (setting.section == null || setting.key == null) {
- return
- }
-
- val section = settings.getSection(setting.section!!)!!
- if (section.getSetting(setting.key!!) == null) {
- section.putSetting(setting)
- }
- }
-
fun loadSettingsList() {
- if (!TextUtils.isEmpty(gameId)) {
- settingsActivity.setToolbarTitle("Game Settings: $gameId")
- }
val sl = ArrayList<SettingsItem>()
- if (menuTag == null) {
- return
- }
when (menuTag) {
- SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
- Settings.SECTION_GENERAL -> addGeneralSettings(sl)
- Settings.SECTION_SYSTEM -> addSystemSettings(sl)
- Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
- Settings.SECTION_AUDIO -> addAudioSettings(sl)
- Settings.SECTION_THEME -> addThemeSettings(sl)
- Settings.SECTION_DEBUG -> addDebugSettings(sl)
+ Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
+ Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
+ Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
+ Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
+ Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
+ Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
+ Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
else -> {
- fragmentView.showToastMessage("Unimplemented menu", false)
+ val context = YuzuApplication.appContext
+ Toast.makeText(
+ context,
+ context.getString(R.string.unimplemented_menu),
+ Toast.LENGTH_SHORT
+ ).show()
return
}
}
settingsList = sl
- fragmentView.showSettingsList(settingsList!!)
+ adapter.submitList(settingsList)
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
sl.apply {
- add(
- SubmenuSetting(
- R.string.preferences_general,
- 0,
- Settings.SECTION_GENERAL
- )
- )
- add(
- SubmenuSetting(
- R.string.preferences_system,
- 0,
- Settings.SECTION_SYSTEM
- )
- )
- add(
- SubmenuSetting(
- R.string.preferences_graphics,
- 0,
- Settings.SECTION_RENDERER
- )
- )
- add(
- SubmenuSetting(
- R.string.preferences_audio,
- 0,
- Settings.SECTION_AUDIO
- )
- )
- add(
- SubmenuSetting(
- R.string.preferences_debug,
- 0,
- Settings.SECTION_DEBUG
- )
- )
- add(
- RunnableSetting(
- R.string.reset_to_default,
- 0,
- false
- ) {
- ResetSettingsDialogFragment().show(
- settingsActivity.supportFragmentManager,
- ResetSettingsDialogFragment.TAG
- )
+ add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
+ add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
+ add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
+ add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
+ add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
+ add(
+ RunnableSetting(R.string.reset_to_default, 0, false) {
+ settingsViewModel.setShouldShowResetSettingsDialog(true)
}
)
}
}
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general))
sl.apply {
- add(
- SwitchSetting(
- IntSetting.RENDERER_USE_SPEED_LIMIT,
- R.string.frame_limit_enable,
- R.string.frame_limit_enable_description,
- IntSetting.RENDERER_USE_SPEED_LIMIT.key,
- IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
- )
- )
- add(
- SliderSetting(
- IntSetting.RENDERER_SPEED_LIMIT,
- R.string.frame_limit_slider,
- R.string.frame_limit_slider_description,
- 1,
- 200,
- "%",
- IntSetting.RENDERER_SPEED_LIMIT.key,
- IntSetting.RENDERER_SPEED_LIMIT.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.CPU_ACCURACY,
- R.string.cpu_accuracy,
- 0,
- R.array.cpuAccuracyNames,
- R.array.cpuAccuracyValues,
- IntSetting.CPU_ACCURACY.key,
- IntSetting.CPU_ACCURACY.defaultValue
- )
- )
- add(
- SwitchSetting(
- BooleanSetting.PICTURE_IN_PICTURE,
- R.string.picture_in_picture,
- R.string.picture_in_picture_description,
- BooleanSetting.PICTURE_IN_PICTURE.key,
- BooleanSetting.PICTURE_IN_PICTURE.defaultValue
- )
- )
+ add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
+ add(ShortSetting.RENDERER_SPEED_LIMIT.key)
+ add(IntSetting.CPU_ACCURACY.key)
+ add(BooleanSetting.PICTURE_IN_PICTURE.key)
}
}
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system))
sl.apply {
- add(
- SwitchSetting(
- IntSetting.USE_DOCKED_MODE,
- R.string.use_docked_mode,
- R.string.use_docked_mode_description,
- IntSetting.USE_DOCKED_MODE.key,
- IntSetting.USE_DOCKED_MODE.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.REGION_INDEX,
- R.string.emulated_region,
- 0,
- R.array.regionNames,
- R.array.regionValues,
- IntSetting.REGION_INDEX.key,
- IntSetting.REGION_INDEX.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.LANGUAGE_INDEX,
- R.string.emulated_language,
- 0,
- R.array.languageNames,
- R.array.languageValues,
- IntSetting.LANGUAGE_INDEX.key,
- IntSetting.LANGUAGE_INDEX.defaultValue
- )
- )
- add(
- SwitchSetting(
- BooleanSetting.USE_CUSTOM_RTC,
- R.string.use_custom_rtc,
- R.string.use_custom_rtc_description,
- BooleanSetting.USE_CUSTOM_RTC.key,
- BooleanSetting.USE_CUSTOM_RTC.defaultValue
- )
- )
- add(
- DateTimeSetting(
- StringSetting.CUSTOM_RTC,
- R.string.set_custom_rtc,
- 0,
- StringSetting.CUSTOM_RTC.key,
- StringSetting.CUSTOM_RTC.defaultValue
- )
- )
+ add(BooleanSetting.USE_DOCKED_MODE.key)
+ add(IntSetting.REGION_INDEX.key)
+ add(IntSetting.LANGUAGE_INDEX.key)
+ add(BooleanSetting.USE_CUSTOM_RTC.key)
+ add(LongSetting.CUSTOM_RTC.key)
}
}
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
sl.apply {
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_ACCURACY,
- R.string.renderer_accuracy,
- 0,
- R.array.rendererAccuracyNames,
- R.array.rendererAccuracyValues,
- IntSetting.RENDERER_ACCURACY.key,
- IntSetting.RENDERER_ACCURACY.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_RESOLUTION,
- R.string.renderer_resolution,
- 0,
- R.array.rendererResolutionNames,
- R.array.rendererResolutionValues,
- IntSetting.RENDERER_RESOLUTION.key,
- IntSetting.RENDERER_RESOLUTION.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_VSYNC,
- R.string.renderer_vsync,
- 0,
- R.array.rendererVSyncNames,
- R.array.rendererVSyncValues,
- IntSetting.RENDERER_VSYNC.key,
- IntSetting.RENDERER_VSYNC.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_SCALING_FILTER,
- R.string.renderer_scaling_filter,
- 0,
- R.array.rendererScalingFilterNames,
- R.array.rendererScalingFilterValues,
- IntSetting.RENDERER_SCALING_FILTER.key,
- IntSetting.RENDERER_SCALING_FILTER.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_ANTI_ALIASING,
- R.string.renderer_anti_aliasing,
- 0,
- R.array.rendererAntiAliasingNames,
- R.array.rendererAntiAliasingValues,
- IntSetting.RENDERER_ANTI_ALIASING.key,
- IntSetting.RENDERER_ANTI_ALIASING.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_SCREEN_LAYOUT,
- R.string.renderer_screen_layout,
- 0,
- R.array.rendererScreenLayoutNames,
- R.array.rendererScreenLayoutValues,
- IntSetting.RENDERER_SCREEN_LAYOUT.key,
- IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
- )
- )
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_ASPECT_RATIO,
- R.string.renderer_aspect_ratio,
- 0,
- R.array.rendererAspectRatioNames,
- R.array.rendererAspectRatioValues,
- IntSetting.RENDERER_ASPECT_RATIO.key,
- IntSetting.RENDERER_ASPECT_RATIO.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
- R.string.use_disk_shader_cache,
- R.string.use_disk_shader_cache_description,
- IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
- IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_FORCE_MAX_CLOCK,
- R.string.renderer_force_max_clock,
- R.string.renderer_force_max_clock_description,
- IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
- IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
- R.string.renderer_asynchronous_shaders,
- R.string.renderer_asynchronous_shaders_description,
- IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
- IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_REACTIVE_FLUSHING,
- R.string.renderer_reactive_flushing,
- R.string.renderer_reactive_flushing_description,
- IntSetting.RENDERER_REACTIVE_FLUSHING.key,
- IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
- )
- )
+ add(IntSetting.RENDERER_ACCURACY.key)
+ add(IntSetting.RENDERER_RESOLUTION.key)
+ add(IntSetting.RENDERER_VSYNC.key)
+ add(IntSetting.RENDERER_SCALING_FILTER.key)
+ add(IntSetting.RENDERER_ANTI_ALIASING.key)
+ add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
+ add(IntSetting.RENDERER_ASPECT_RATIO.key)
+ add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
+ add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
+ add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
+ add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
}
}
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
sl.apply {
- add(
- StringSingleChoiceSetting(
- StringSetting.AUDIO_OUTPUT_ENGINE,
- R.string.audio_output_engine,
- 0,
- settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
- settingsActivity.resources.getStringArray(R.array.outputEngineValues),
- StringSetting.AUDIO_OUTPUT_ENGINE.key,
- StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
- )
- )
- add(
- SliderSetting(
- IntSetting.AUDIO_VOLUME,
- R.string.audio_volume,
- R.string.audio_volume_description,
- 0,
- 100,
- "%",
- IntSetting.AUDIO_VOLUME.key,
- IntSetting.AUDIO_VOLUME.defaultValue
- )
- )
+ add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
+ add(ByteSetting.AUDIO_VOLUME.key)
}
}
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting {
- override var int: Int
+ override val int: Int
get() = preferences.getInt(Settings.PREF_THEME, 0)
- set(value) {
- preferences.edit()
- .putInt(Settings.PREF_THEME, value)
- .apply()
- settingsActivity.recreate()
- }
- override val key: String? = null
- override val section: String? = null
- override val isRuntimeEditable: Boolean = false
- override val valueAsString: String
- get() = preferences.getInt(Settings.PREF_THEME, 0).toString()
- override val defaultValue: Any = 0
+
+ override fun setInt(value: Int) {
+ preferences.edit()
+ .putInt(Settings.PREF_THEME, value)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
+
+ override val key: String = Settings.PREF_THEME
+ override val category = Settings.Category.UiGeneral
+ override val isRuntimeModifiable: Boolean = false
+ override val defaultValue: Int = 0
+ override fun reset() {
+ preferences.edit()
+ .putInt(Settings.PREF_THEME, defaultValue)
+ .apply()
+ }
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -423,20 +177,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
val themeMode: AbstractIntSetting = object : AbstractIntSetting {
- override var int: Int
+ override val int: Int
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
- set(value) {
- preferences.edit()
- .putInt(Settings.PREF_THEME_MODE, value)
- .apply()
- ThemeHelper.setThemeMode(settingsActivity)
- }
- override val key: String? = null
- override val section: String? = null
- override val isRuntimeEditable: Boolean = false
- override val valueAsString: String
- get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString()
- override val defaultValue: Any = -1
+
+ override fun setInt(value: Int) {
+ preferences.edit()
+ .putInt(Settings.PREF_THEME_MODE, value)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
+
+ override val key: String = Settings.PREF_THEME_MODE
+ override val category = Settings.Category.UiGeneral
+ override val isRuntimeModifiable: Boolean = false
+ override val defaultValue: Int = -1
+ override fun reset() {
+ preferences.edit()
+ .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
}
add(
@@ -450,21 +210,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
- override var boolean: Boolean
- get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
- set(value) {
- preferences.edit()
- .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
- .apply()
- settingsActivity.recreate()
- }
- override val key: String? = null
- override val section: String? = null
- override val isRuntimeEditable: Boolean = false
- override val valueAsString: String
+ override val boolean: Boolean
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
- .toString()
- override val defaultValue: Any = false
+
+ override fun setBoolean(value: Boolean) {
+ preferences.edit()
+ .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
+
+ override val key: String = Settings.PREF_BLACK_BACKGROUNDS
+ override val category = Settings.Category.UiGeneral
+ override val isRuntimeModifiable: Boolean = false
+ override val defaultValue: Boolean = false
+ override fun reset() {
+ preferences.edit()
+ .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
+ .apply()
+ settingsViewModel.setShouldRecreate(true)
+ }
}
add(
@@ -478,62 +243,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
- settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
sl.apply {
add(HeaderSetting(R.string.gpu))
- add(
- SingleChoiceSetting(
- IntSetting.RENDERER_BACKEND,
- R.string.renderer_api,
- 0,
- R.array.rendererApiNames,
- R.array.rendererApiValues,
- IntSetting.RENDERER_BACKEND.key,
- IntSetting.RENDERER_BACKEND.defaultValue
- )
- )
- add(
- SwitchSetting(
- IntSetting.RENDERER_DEBUG,
- R.string.renderer_debug,
- R.string.renderer_debug_description,
- IntSetting.RENDERER_DEBUG.key,
- IntSetting.RENDERER_DEBUG.defaultValue
- )
- )
+ add(IntSetting.RENDERER_BACKEND.key)
+ add(BooleanSetting.RENDERER_DEBUG.key)
add(HeaderSetting(R.string.cpu))
- add(
- SwitchSetting(
- BooleanSetting.CPU_DEBUG_MODE,
- R.string.cpu_debug_mode,
- R.string.cpu_debug_mode_description,
- BooleanSetting.CPU_DEBUG_MODE.key,
- BooleanSetting.CPU_DEBUG_MODE.defaultValue
- )
- )
-
- val fastmem = object : AbstractBooleanSetting {
- override var boolean: Boolean
- get() =
- BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
- set(value) {
- BooleanSetting.FASTMEM.boolean = value
- BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
- }
- override val key: String? = null
- override val section: String = Settings.SECTION_CPU
- override val isRuntimeEditable: Boolean = false
- override val valueAsString: String = ""
- override val defaultValue: Any = true
- }
- add(
- SwitchSetting(
- fastmem,
- R.string.fastmem,
- 0
- )
- )
+ add(BooleanSetting.CPU_DEBUG_MODE.key)
+ add(SettingsItem.FASTMEM_COMBINED)
}
}
}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
-
-/**
- * Abstraction for a screen showing a list of settings. Instances of
- * this type of view will each display a layer of the setting hierarchy.
- */
-interface SettingsFragmentView {
- /**
- * Pass an ArrayList to the View so that it can be displayed on screen.
- *
- * @param settingsList The result of converting the HashMap to an ArrayList
- */
- fun showSettingsList(settingsList: ArrayList<SettingsItem>)
-
- /**
- * Instructs the Fragment to load the settings screen.
- */
- fun loadSettingsList()
-
- /**
- * @return The Fragment's containing activity.
- */
- val activityView: SettingsActivityView?
-
- /**
- * Tell the Fragment to tell the containing Activity to show a new
- * Fragment containing a submenu of settings.
- *
- * @param menuKey Identifier for the settings group that should be shown.
- */
- fun loadSubMenu(menuKey: String)
-
- /**
- * Tell the Fragment to tell the containing activity to display a toast message.
- *
- * @param message Text to be shown in the Toast
- * @param is_long Whether this should be a long Toast or short one.
- */
- fun showToastMessage(message: String?, is_long: Boolean)
-
- /**
- * Have the fragment add a setting to the HashMap.
- *
- * @param setting The (possibly previously missing) new setting.
- */
- fun putSetting(setting: AbstractSetting)
-
- /**
- * Have the fragment tell the containing Activity that a setting was modified.
- */
- fun onSettingChanged()
-}
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
binding.textSettingDescription.setText(item.descriptionId)
binding.textSettingDescription.visibility = View.VISIBLE
} else {
- val epochTime = setting.value.toLong()
- val instant = Instant.ofEpochMilli(epochTime * 1000)
- val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
- val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
- binding.textSettingDescription.text = dateFormatter.format(zonedTime)
+ binding.textSettingDescription.visibility = View.GONE
}
+
+ binding.textSettingValue.visibility = View.VISIBLE
+ val epochTime = setting.value
+ val instant = Instant.ofEpochMilli(epochTime * 1000)
+ val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
+ val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
+ binding.textSettingValue.text = dateFormatter.format(zonedTime)
+
+ setStyle(setting.isEditable, binding)
}
override fun onClick(clicked: View) {
@@ -41,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
- return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+ return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
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
} else {
binding.textSettingDescription.visibility = View.GONE
}
+ binding.textSettingValue.visibility = View.GONE
+
+ setStyle(setting.isEditable, binding)
}
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
import android.view.View
import androidx.recyclerview.widget.RecyclerView
+import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
+import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
@@ -33,4 +35,18 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
abstract override fun onClick(clicked: View)
abstract override fun onLongClick(clicked: View): Boolean
+
+ fun setStyle(isEditable: Boolean, binding: ListItemSettingBinding) {
+ val opacity = if (isEditable) 1.0f else 0.5f
+ binding.textSettingName.alpha = opacity
+ binding.textSettingDescription.alpha = opacity
+ binding.textSettingValue.alpha = opacity
+ }
+
+ fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
+ binding.switchWidget.isEnabled = isEditable
+ val opacity = if (isEditable) 1.0f else 0.5f
+ binding.textSettingName.alpha = opacity
+ binding.textSettingDescription.alpha = opacity
+ }
}
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
override fun bind(item: SettingsItem) {
setting = item
binding.textSettingName.setText(item.nameId)
- binding.textSettingDescription.visibility = View.VISIBLE
if (item.descriptionId != 0) {
binding.textSettingDescription.setText(item.descriptionId)
- } else if (item is SingleChoiceSetting) {
- val resMgr = binding.textSettingDescription.context.resources
+ binding.textSettingDescription.visibility = View.VISIBLE
+ } else {
+ binding.textSettingDescription.visibility = View.GONE
+ }
+
+ binding.textSettingValue.visibility = View.VISIBLE
+ if (item is SingleChoiceSetting) {
+ val resMgr = binding.textSettingValue.context.resources
val values = resMgr.getIntArray(item.valuesId)
for (i in values.indices) {
if (values[i] == item.selectedValue) {
- binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i]
- return
+ binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
+ break
}
}
} else if (item is StringSingleChoiceSetting) {
- for (i in item.values!!.indices) {
+ for (i in item.values.indices) {
if (item.values[i] == item.selectedValue) {
- binding.textSettingDescription.text = item.choices[i]
- return
+ binding.textSettingValue.text = item.choices[i]
+ break
}
}
- } else {
- binding.textSettingDescription.visibility = View.GONE
}
+
+ setStyle(setting.isEditable, binding)
}
override fun onClick(clicked: View) {
@@ -61,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
- return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+ return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
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 @@
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
+import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
@@ -22,6 +23,14 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
} else {
binding.textSettingDescription.visibility = View.GONE
}
+ binding.textSettingValue.visibility = View.VISIBLE
+ binding.textSettingValue.text = String.format(
+ binding.textSettingValue.context.getString(R.string.value_with_units),
+ setting.selectedValue,
+ setting.units
+ )
+
+ setStyle(setting.isEditable, binding)
}
override fun onClick(clicked: View) {
@@ -32,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
- return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+ return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
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
} else {
binding.textSettingDescription.visibility = View.GONE
}
+ binding.textSettingValue.visibility = View.GONE
}
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
binding.textSettingDescription.text = ""
binding.textSettingDescription.visibility = View.GONE
}
- binding.switchWidget.isChecked = setting.isChecked
+
+ binding.switchWidget.setOnCheckedChangeListener(null)
+ binding.switchWidget.isChecked = setting.checked
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
- adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
+ adapter.onBooleanClick(item, binding.switchWidget.isChecked)
}
- binding.switchWidget.isEnabled = setting.isEditable
+ setStyle(setting.isEditable, binding)
}
override fun onClick(clicked: View) {
@@ -41,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
- return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+ return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
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 @@
package org.yuzu.yuzu_emu.features.settings.utils
+import android.widget.Toast
import java.io.*
-import java.util.*
import org.ini4j.Wini
-import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.*
-import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
-import org.yuzu.yuzu_emu.utils.BiMap
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log
+import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* Contains static methods for interacting with .ini files in which settings are stored.
@@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log
object SettingsFile {
const val FILE_NAME_CONFIG = "config"
- private var sectionsMap = BiMap<String?, String?>()
-
- /**
- * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
- * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
- * failed.
- *
- * @param ini The ini file to load the settings from
- * @param isCustomGame
- * @param view The current view.
- * @return An Observable that emits a HashMap of the file's contents, then completes.
- */
- private fun readFile(
- ini: File?,
- isCustomGame: Boolean,
- view: SettingsActivityView? = null
- ): HashMap<String, SettingSection?> {
- val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
- var reader: BufferedReader? = null
- try {
- reader = BufferedReader(FileReader(ini))
- var current: SettingSection? = null
- var line: String?
- while (reader.readLine().also { line = it } != null) {
- if (line!!.startsWith("[") && line!!.endsWith("]")) {
- current = sectionFromLine(line!!, isCustomGame)
- sections[current.name] = current
- } else if (current != null) {
- val setting = settingFromLine(line!!)
- if (setting != null) {
- current.putSetting(setting)
- }
- }
- }
- } catch (e: FileNotFoundException) {
- Log.error("[SettingsFile] File not found: " + e.message)
- view?.onSettingsFileNotFound()
- } catch (e: IOException) {
- Log.error("[SettingsFile] Error reading from: " + e.message)
- view?.onSettingsFileNotFound()
- } finally {
- if (reader != null) {
- try {
- reader.close()
- } catch (e: IOException) {
- Log.error("[SettingsFile] Error closing: " + e.message)
- }
- }
- }
- return sections
- }
-
- fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
- return readFile(getSettingsFile(fileName), false, view)
- }
-
- fun readFile(fileName: String): HashMap<String, SettingSection?> =
- readFile(getSettingsFile(fileName), false)
-
- /**
- * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
- * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
- * failed.
- *
- * @param gameId the id of the game to load it's settings.
- * @param view The current view.
- */
- fun readCustomGameSettings(
- gameId: String,
- view: SettingsActivityView?
- ): HashMap<String, SettingSection?> {
- return readFile(getCustomGameSettingsFile(gameId), true, view)
- }
-
/**
* Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
* telling why it failed.
*
* @param fileName The target filename without a path or extension.
- * @param sections The HashMap containing the Settings we want to serialize.
- * @param view The current view.
*/
- fun saveFile(
- fileName: String,
- sections: TreeMap<String, SettingSection>,
- view: SettingsActivityView
- ) {
+ fun saveFile(fileName: String) {
val ini = getSettingsFile(fileName)
try {
- val writer = Wini(ini)
- val keySet: Set<String> = sections.keys
- for (key in keySet) {
- val section = sections[key]
- writeSection(writer, section!!)
+ val wini = Wini(ini)
+ for (specificCategory in Settings.Category.values()) {
+ val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
+ for (setting in Settings.settingsList) {
+ if (setting.key!!.isEmpty()) continue
+
+ val settingCategoryHeader =
+ NativeConfig.getConfigHeader(setting.category.ordinal)
+ val iniSetting: String? = wini.get(categoryHeader, setting.key)
+ if (iniSetting != null || settingCategoryHeader == categoryHeader) {
+ wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
+ }
+ }
}
- writer.store()
+ wini.store()
} catch (e: IOException) {
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
- view.showToastMessage(
- YuzuApplication.appContext
- .getString(R.string.error_saving, fileName, e.message),
- false
- )
- }
- }
-
- fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
- val sortedSections: Set<String> = TreeSet(sections.keys)
- for (sectionKey in sortedSections) {
- val section = sections[sectionKey]
- val settings = section!!.settings
- val sortedKeySet: Set<String> = TreeSet(settings.keys)
- for (settingKey in sortedKeySet) {
- val setting = settings[settingKey]
- NativeLibrary.setUserSetting(
- gameId,
- mapSectionNameFromIni(
- section.name
- ),
- setting!!.key,
- setting.valueAsString
- )
- }
- }
- }
-
- private fun mapSectionNameFromIni(generalSectionName: String): String? {
- return if (sectionsMap.getForward(generalSectionName) != null) {
- sectionsMap.getForward(generalSectionName)
- } else {
- generalSectionName
- }
- }
-
- private fun mapSectionNameToIni(generalSectionName: String): String {
- return if (sectionsMap.getBackward(generalSectionName) != null) {
- sectionsMap.getBackward(generalSectionName).toString()
- } else {
- generalSectionName
- }
- }
-
- fun getSettingsFile(fileName: String): File {
- return File(
- DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
- )
- }
-
- private fun getCustomGameSettingsFile(gameId: String): File {
- return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
- }
-
- private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
- var sectionName: String = line.substring(1, line.length - 1)
- if (isCustomGame) {
- sectionName = mapSectionNameToIni(sectionName)
+ val context = YuzuApplication.appContext
+ Toast.makeText(
+ context,
+ context.getString(R.string.error_saving, fileName, e.message),
+ Toast.LENGTH_SHORT
+ ).show()
}
- return SettingSection(sectionName)
}
- /**
- * For a line of text, determines what type of data is being represented, and returns
- * a Setting object containing this data.
- *
- * @param line The line of text being parsed.
- * @return A typed Setting containing the key/value contained in the line.
- */
- private fun settingFromLine(line: String): AbstractSetting? {
- val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
- if (splitLine.size != 2) {
- return null
- }
- val key = splitLine[0].trim { it <= ' ' }
- val value = splitLine[1].trim { it <= ' ' }
- if (value.isEmpty()) {
- return null
- }
-
- val booleanSetting = BooleanSetting.from(key)
- if (booleanSetting != null) {
- booleanSetting.boolean = value.toBoolean()
- return booleanSetting
- }
-
- val intSetting = IntSetting.from(key)
- if (intSetting != null) {
- intSetting.int = value.toInt()
- return intSetting
- }
-
- val floatSetting = FloatSetting.from(key)
- if (floatSetting != null) {
- floatSetting.float = value.toFloat()
- return floatSetting
- }
-
- val stringSetting = StringSetting.from(key)
- if (stringSetting != null) {
- stringSetting.string = value
- return stringSetting
- }
-
- return null
- }
-
- /**
- * Writes the contents of a Section HashMap to disk.
- *
- * @param parser A Wini pointed at a file on disk.
- * @param section A section containing settings to be written to the file.
- */
- private fun writeSection(parser: Wini, section: SettingSection) {
- // Write the section header.
- val header = section.name
-
- // Write this section's values.
- val settings = section.settings
- val keySet: Set<String> = settings.keys
- for (key in keySet) {
- val setting = settings[key]
- parser.put(header, setting!!.key, setting.valueAsString)
- }
-
- BooleanSetting.values().forEach {
- if (!keySet.contains(it.key)) {
- parser.put(header, it.key, it.valueAsString)
- }
- }
- IntSetting.values().forEach {
- if (!keySet.contains(it.key)) {
- parser.put(header, it.key, it.valueAsString)
- }
- }
- StringSetting.values().forEach {
- if (!keySet.contains(it.key)) {
- parser.put(header, it.key, it.valueAsString)
- }
- }
- }
+ fun getSettingsFile(fileName: String): File =
+ File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
index 2ff827c6b..7b8f99872 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
@@ -26,6 +26,7 @@ import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.ui.main.MainActivity
class AboutFragment : Fragment() {
private var _binding: FragmentAboutBinding? = null
@@ -92,6 +93,12 @@ class AboutFragment : Fragment() {
}
}
+ val mainActivity = requireActivity() as MainActivity
+ binding.buttonExport.setOnClickListener { mainActivity.exportUserData.launch("export.zip") }
+ binding.buttonImport.setOnClickListener {
+ mainActivity.importUserData.launch(arrayOf("application/zip"))
+ }
+
binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.support_link)) }
binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) }
binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) }
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..750638bc9 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,30 +7,29 @@ import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
-import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Color
+import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
-import android.util.Rational
import android.view.*
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.isVisible
+import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import androidx.window.layout.FoldingFeature
@@ -39,7 +38,9 @@ import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -48,8 +49,8 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.InputOverlay
import org.yuzu.yuzu_emu.utils.*
@@ -62,11 +63,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!!
- val args by navArgs<EmulationFragmentArgs>()
+ private val args by navArgs<EmulationFragmentArgs>()
- private var isInFoldableLayout = false
+ private lateinit var game: Game
+
+ private val emulationViewModel: EmulationViewModel by activityViewModels()
- private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
+ private var isInFoldableLayout = false
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -81,11 +84,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.collect { updateFoldableLayout(context, it) }
}
}
-
- onReturnFromSettings = context.activityResultRegistry.register(
- "SettingsResult",
- ActivityResultContracts.StartActivityForResult()
- ) { updateScreenLayout() }
} else {
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
}
@@ -97,10 +95,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val intentUri: Uri? = requireActivity().intent.data
+ var intentGame: Game? = null
+ if (intentUri != null) {
+ intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
+ GameHelper.getGame(requireActivity().intent.data!!, false)
+ } else {
+ null
+ }
+ }
+ game = if (args.game != null) {
+ args.game!!
+ } else {
+ intentGame ?: error("[EmulationFragment] No bootable game present!")
+ }
+
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- emulationState = EmulationState(args.game.path)
+ emulationState = EmulationState(game.path)
}
/**
@@ -115,16 +128,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return binding.root
}
+ // This is using the correct scope, lint is just acting up
+ @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.surfaceEmulation.holder.addCallback(this)
binding.showFpsText.setTextColor(Color.YELLOW)
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
- // Setup overlay.
- updateShowFpsOverlay()
-
+ binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
- args.game.title
+ game.title
binding.inGameMenu.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.menu_pause_emulation -> {
@@ -149,12 +162,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
R.id.menu_settings -> {
- SettingsActivity.launch(
- requireContext(),
- onReturnFromSettings,
- SettingsFile.FILE_NAME_CONFIG,
- ""
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ Settings.MenuTag.SECTION_ROOT
)
+ binding.root.findNavController().navigate(action)
true
}
@@ -165,7 +177,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_exit -> {
emulationState.stop()
- requireActivity().finish()
+ emulationViewModel.setIsEmulationStopping(true)
+ binding.drawerLayout.close()
+ binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
true
}
@@ -179,6 +193,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
requireActivity(),
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
+ if (!NativeLibrary.isRunning()) {
+ return
+ }
+
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
} else {
@@ -188,27 +206,105 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
)
- viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
- lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
- WindowInfoTracker.getOrCreate(requireContext())
- .windowLayoutInfo(requireActivity())
- .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
+ GameIconUtils.loadGameIcon(game, binding.loadingImage)
+ binding.loadingTitle.text = game.title
+ binding.loadingTitle.isSelected = true
+ binding.loadingText.isSelected = true
+
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ WindowInfoTracker.getOrCreate(requireContext())
+ .windowLayoutInfo(requireActivity())
+ .collect {
+ updateFoldableLayout(requireActivity() as EmulationActivity, it)
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ emulationViewModel.shaderProgress.collectLatest {
+ if (it > 0 && it != emulationViewModel.totalShaders.value) {
+ binding.loadingProgressIndicator.isIndeterminate = false
+
+ if (it < binding.loadingProgressIndicator.max) {
+ binding.loadingProgressIndicator.progress = it
+ }
+ }
+
+ if (it == emulationViewModel.totalShaders.value) {
+ binding.loadingText.setText(R.string.loading)
+ binding.loadingProgressIndicator.isIndeterminate = true
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ emulationViewModel.totalShaders.collectLatest {
+ binding.loadingProgressIndicator.max = it
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ emulationViewModel.shaderMessage.collectLatest {
+ if (it.isNotEmpty()) {
+ binding.loadingText.text = it
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ emulationViewModel.emulationStarted.collectLatest {
+ if (it) {
+ binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
+ ViewUtils.showView(binding.surfaceInputOverlay)
+ ViewUtils.hideView(binding.loadingIndicator)
+
+ // Setup overlay
+ updateShowFpsOverlay()
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ emulationViewModel.isEmulationStopping.collectLatest {
+ if (it) {
+ binding.loadingText.setText(R.string.shutting_down)
+ ViewUtils.showView(binding.loadingIndicator)
+ ViewUtils.hideView(binding.inputContainer)
+ ViewUtils.hideView(binding.showFpsText)
+ }
+ }
+ }
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
+ updateScreenLayout()
if (emulationActivity?.isInPictureInPictureMode == true) {
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
}
if (EmulationMenuSettings.showOverlay) {
- binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
+ binding.surfaceInputOverlay.post {
+ binding.surfaceInputOverlay.visibility = View.INVISIBLE
+ }
}
} else {
- if (EmulationMenuSettings.showOverlay) {
- binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
+ if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
+ binding.surfaceInputOverlay.post {
+ binding.surfaceInputOverlay.visibility = View.VISIBLE
+ }
+ } else {
+ binding.surfaceInputOverlay.post {
+ binding.surfaceInputOverlay.visibility = View.INVISIBLE
+ }
}
if (!isInFoldableLayout) {
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -217,16 +313,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
}
}
- if (!binding.surfaceInputOverlay.isInEditMode) {
- refreshInputOverlay()
- }
}
}
override fun onResume() {
super.onResume()
if (!DirectoryInitialization.areDirectoriesReady) {
- DirectoryInitialization.start(requireContext())
+ DirectoryInitialization.start()
}
updateScreenLayout()
@@ -235,7 +328,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
override fun onPause() {
- if (emulationState.isRunning) {
+ if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
}
super.onPause()
@@ -251,10 +344,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onDetach()
}
- private fun refreshInputOverlay() {
- binding.surfaceInputOverlay.refreshControls()
- }
-
private fun resetInputOverlay() {
preferences.edit()
.remove(Settings.PREF_CONTROL_SCALE)
@@ -272,17 +361,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val FRAMETIME = 2
val SPEED = 3
perfStatsUpdater = {
- val perfStats = NativeLibrary.getPerfStats()
- if (perfStats[FPS] > 0 && _binding != null) {
- binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
- }
-
- if (!emulationState.isStopped) {
+ if (emulationViewModel.emulationStarted.value == true) {
+ val perfStats = NativeLibrary.getPerfStats()
+ if (perfStats[FPS] > 0 && _binding != null) {
+ binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
+ }
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
}
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
- binding.showFpsText.text = resources.getString(R.string.emulation_game_loading)
binding.showFpsText.visibility = View.VISIBLE
} else {
if (perfStatsUpdater != null) {
@@ -297,26 +384,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationActivity?.let {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
Settings.LayoutOption_MobileLandscape ->
- ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
Settings.LayoutOption_MobilePortrait ->
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
- else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
+ else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
}
}
}
private fun updateScreenLayout() {
- binding.surfaceEmulation.setAspectRatio(
- when (IntSetting.RENDERER_ASPECT_RATIO.int) {
- 0 -> Rational(16, 9)
- 1 -> Rational(4, 3)
- 2 -> Rational(21, 9)
- 3 -> Rational(16, 10)
- 4 -> null // Stretch
- else -> Rational(16, 9)
- }
- )
+ binding.surfaceEmulation.setAspectRatio(null)
emulationActivity?.buildPictureInPictureParams()
updateOrientation()
}
@@ -340,7 +418,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
isInFoldableLayout = true
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
- refreshInputOverlay()
}
}
it.isSeparating
@@ -428,7 +505,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.apply()
}
.setPositiveButton(android.R.string.ok) { _, _ ->
- refreshInputOverlay()
+ binding.surfaceInputOverlay.refreshControls()
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.emulation_toggle_all) { _, _ -> }
@@ -452,7 +529,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_show_overlay -> {
it.isChecked = !it.isChecked
EmulationMenuSettings.showOverlay = it.isChecked
- refreshInputOverlay()
+ binding.surfaceInputOverlay.refreshControls()
true
}
@@ -558,14 +635,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
preferences.edit()
.putInt(Settings.PREF_CONTROL_SCALE, scale)
.apply()
- refreshInputOverlay()
+ binding.surfaceInputOverlay.refreshControls()
}
private fun setControlOpacity(opacity: Int) {
preferences.edit()
.putInt(Settings.PREF_CONTROL_OPACITY, opacity)
.apply()
- refreshInputOverlay()
+ binding.surfaceInputOverlay.refreshControls()
}
private fun setInsets() {
@@ -607,7 +684,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private class EmulationState(private val gamePath: String) {
private var state: State
private var surface: Surface? = null
- private var runWhenSurfaceIsValid = false
init {
// Starting state is stopped.
@@ -665,8 +741,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// If the surface is set, run now. Otherwise, wait for it to get set.
if (surface != null) {
runWithValidSurface()
- } else {
- runWhenSurfaceIsValid = true
}
}
@@ -674,7 +748,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@Synchronized
fun newSurface(surface: Surface?) {
this.surface = surface
- if (runWhenSurfaceIsValid) {
+ if (this.surface != null) {
runWithValidSurface()
}
}
@@ -702,10 +776,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun runWithValidSurface() {
- runWhenSurfaceIsValid = false
+ NativeLibrary.surfaceChanged(surface)
when (state) {
State.STOPPED -> {
- NativeLibrary.surfaceChanged(surface)
val emulationThread = Thread({
Log.debug("[EmulationFragment] Starting emulation thread.")
NativeLibrary.run(gamePath)
@@ -715,7 +788,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
State.PAUSED -> {
Log.debug("[EmulationFragment] Resuming emulation.")
- NativeLibrary.surfaceChanged(surface)
NativeLibrary.unpauseEmulation()
}
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
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig
+import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -74,7 +74,13 @@ class HomeSettingsFragment : Fragment() {
R.string.advanced_settings,
R.string.settings_description,
R.drawable.ic_settings,
- { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
+ {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ Settings.MenuTag.SECTION_ROOT
+ )
+ binding.root.findNavController().navigate(action)
+ }
)
)
add(
@@ -90,7 +96,13 @@ class HomeSettingsFragment : Fragment() {
R.string.preferences_theme,
R.string.theme_and_color_description,
R.drawable.ic_palette,
- { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
+ {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ Settings.MenuTag.SECTION_THEME
+ )
+ binding.root.findNavController().navigate(action)
+ }
)
)
add(
@@ -129,7 +141,11 @@ class HomeSettingsFragment : Fragment() {
mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
- }
+ },
+ { true },
+ 0,
+ 0,
+ homeViewModel.gamesDir
)
)
add(
@@ -201,7 +217,11 @@ class HomeSettingsFragment : Fragment() {
binding.homeSettingsList.apply {
layoutManager = LinearLayoutManager(requireContext())
- adapter = HomeSettingAdapter(requireActivity() as AppCompatActivity, optionsList)
+ adapter = HomeSettingAdapter(
+ requireActivity() as AppCompatActivity,
+ viewLifecycleOwner,
+ optionsList
+ )
}
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..ee2d44718 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,9 @@ class ImportExportSavesFragment : DialogFragment() {
withContext(Dispatchers.Main) {
if (!validZip) {
MessageDialogFragment.newInstance(
- R.string.save_file_invalid_zip_structure,
- R.string.save_file_invalid_zip_structure_description
+ requireActivity(),
+ titleId = R.string.save_file_invalid_zip_structure,
+ descriptionId = R.string.save_file_invalid_zip_structure_description
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
return@withContext
}
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..0d16a7d37 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
@@ -4,64 +4,117 @@
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
+import android.content.DialogInterface
import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
class IndeterminateProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels()
+ private lateinit var binding: DialogProgressBarBinding
+
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE)
+ val cancellable = requireArguments().getBoolean(CANCELLABLE)
- val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
- progressBinding.progressBar.isIndeterminate = true
+ binding = DialogProgressBarBinding.inflate(layoutInflater)
+ binding.progressBar.isIndeterminate = true
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(titleId)
- .setView(progressBinding.root)
- .create()
- dialog.setCanceledOnTouchOutside(false)
+ .setView(binding.root)
- taskViewModel.isComplete.observe(this) { complete ->
- if (complete) {
- dialog.dismiss()
- when (val result = taskViewModel.result.value) {
- is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
- is MessageDialogFragment -> result.show(
- parentFragmentManager,
- MessageDialogFragment.TAG
- )
- }
- taskViewModel.clear()
+ if (cancellable) {
+ dialog.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
+ taskViewModel.setCancelled(true)
}
}
- if (taskViewModel.isRunning.value == false) {
+ val alertDialog = dialog.create()
+ alertDialog.setCanceledOnTouchOutside(false)
+
+ if (!taskViewModel.isRunning.value) {
taskViewModel.runTask()
}
- return dialog
+ return alertDialog
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ taskViewModel.isComplete.collect {
+ if (it) {
+ dismiss()
+ when (val result = taskViewModel.result.value) {
+ is String -> Toast.makeText(
+ requireContext(),
+ result,
+ Toast.LENGTH_LONG
+ ).show()
+
+ is MessageDialogFragment -> result.show(
+ requireActivity().supportFragmentManager,
+ MessageDialogFragment.TAG
+ )
+ }
+ taskViewModel.clear()
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ taskViewModel.cancelled.collect {
+ if (it) {
+ dialog?.setTitle(R.string.cancelling)
+ }
+ }
+ }
+ }
+ }
}
companion object {
const val TAG = "IndeterminateProgressDialogFragment"
private const val TITLE = "Title"
+ private const val CANCELLABLE = "Cancellable"
fun newInstance(
activity: AppCompatActivity,
titleId: Int,
+ cancellable: Boolean = false,
task: () -> Any
): IndeterminateProgressDialogFragment {
val dialog = IndeterminateProgressDialogFragment()
val args = Bundle()
ViewModelProvider(activity)[TaskViewModel::class.java].task = task
args.putInt(TITLE, titleId)
+ args.putBoolean(CANCELLABLE, cancellable)
dialog.arguments = args
return dialog
}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.fragments
-
-import android.app.Dialog
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import androidx.fragment.app.DialogFragment
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import org.yuzu.yuzu_emu.R
-
-class LongMessageDialogFragment : DialogFragment() {
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val titleId = requireArguments().getInt(TITLE)
- val description = requireArguments().getString(DESCRIPTION)
- val helpLinkId = requireArguments().getInt(HELP_LINK)
-
- val dialog = MaterialAlertDialogBuilder(requireContext())
- .setPositiveButton(R.string.close, null)
- .setTitle(titleId)
- .setMessage(description)
-
- if (helpLinkId != 0) {
- dialog.setNeutralButton(R.string.learn_more) { _, _ ->
- openLink(getString(helpLinkId))
- }
- }
-
- return dialog.show()
- }
-
- private fun openLink(link: String) {
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
- startActivity(intent)
- }
-
- companion object {
- const val TAG = "LongMessageDialogFragment"
-
- private const val TITLE = "Title"
- private const val DESCRIPTION = "Description"
- private const val HELP_LINK = "Link"
-
- fun newInstance(
- titleId: Int,
- description: String,
- helpLinkId: Int = 0
- ): LongMessageDialogFragment {
- val dialog = LongMessageDialogFragment()
- val bundle = Bundle()
- bundle.apply {
- putInt(TITLE, titleId)
- putString(DESCRIPTION, description)
- putInt(HELP_LINK, helpLinkId)
- }
- dialog.arguments = bundle
- return dialog
- }
- }
-}
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..541b22f47 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
@@ -4,23 +4,36 @@
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
+import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.model.MessageDialogViewModel
class MessageDialogFragment : DialogFragment() {
+ private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
+
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val titleId = requireArguments().getInt(TITLE)
- val descriptionId = requireArguments().getInt(DESCRIPTION)
+ val titleId = requireArguments().getInt(TITLE_ID)
+ val titleString = requireArguments().getString(TITLE_STRING)!!
+ val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
+ val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
val helpLinkId = requireArguments().getInt(HELP_LINK)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setPositiveButton(R.string.close, null)
- .setTitle(titleId)
- .setMessage(descriptionId)
+
+ if (titleId != 0) dialog.setTitle(titleId)
+ if (titleString.isNotEmpty()) dialog.setTitle(titleString)
+
+ if (descriptionId != 0) dialog.setMessage(descriptionId)
+ if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString)
if (helpLinkId != 0) {
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
@@ -31,6 +44,12 @@ class MessageDialogFragment : DialogFragment() {
return dialog.show()
}
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ messageDialogViewModel.dismissAction.invoke()
+ messageDialogViewModel.clear()
+ }
+
private fun openLink(link: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(intent)
@@ -39,22 +58,32 @@ class MessageDialogFragment : DialogFragment() {
companion object {
const val TAG = "MessageDialogFragment"
- private const val TITLE = "Title"
- private const val DESCRIPTION = "Description"
+ private const val TITLE_ID = "Title"
+ private const val TITLE_STRING = "TitleString"
+ private const val DESCRIPTION_ID = "DescriptionId"
+ private const val DESCRIPTION_STRING = "DescriptionString"
private const val HELP_LINK = "Link"
fun newInstance(
- titleId: Int,
- descriptionId: Int,
- helpLinkId: Int = 0
+ activity: FragmentActivity,
+ titleId: Int = 0,
+ titleString: String = "",
+ descriptionId: Int = 0,
+ descriptionString: String = "",
+ helpLinkId: Int = 0,
+ dismissAction: () -> Unit = {}
): MessageDialogFragment {
val dialog = MessageDialogFragment()
val bundle = Bundle()
bundle.apply {
- putInt(TITLE, titleId)
- putInt(DESCRIPTION, descriptionId)
+ putInt(TITLE_ID, titleId)
+ putString(TITLE_STRING, titleString)
+ putInt(DESCRIPTION_ID, descriptionId)
+ putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
}
+ ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction =
+ dismissAction
dialog.arguments = bundle
return dialog
}
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 @@
package org.yuzu.yuzu_emu.fragments
+import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
@@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler
+import kotlinx.coroutines.launch
import java.util.Locale
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
return binding.root
}
+ // This is using the correct scope, lint is just acting up
+ @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false)
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
filterAndSearch()
}
- gamesViewModel.apply {
- searchFocused.observe(viewLifecycleOwner) { searchFocused ->
- if (searchFocused) {
- focusSearch()
- gamesViewModel.setSearchFocused(false)
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ gamesViewModel.searchFocused.collect {
+ if (it) {
+ focusSearch()
+ gamesViewModel.setSearchFocused(false)
+ }
+ }
}
}
-
- games.observe(viewLifecycleOwner) { filterAndSearch() }
- searchedGames.observe(viewLifecycleOwner) {
- (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
- if (it.isEmpty()) {
- binding.noResultsView.visibility = View.VISIBLE
- } else {
- binding.noResultsView.visibility = View.GONE
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ gamesViewModel.games.collect { filterAndSearch() }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ gamesViewModel.searchedGames.collect {
+ (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
+ if (it.isEmpty()) {
+ binding.noResultsView.visibility = View.VISIBLE
+ } else {
+ binding.noResultsView.visibility = View.GONE
+ }
+ }
}
}
}
@@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
private inner class ScoredGame(val score: Double, val item: Game)
private fun filterAndSearch() {
- val baseList = gamesViewModel.games.value!!
+ val baseList = gamesViewModel.games.value
val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
R.id.chip_recently_played -> {
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.slider.Slider
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+
+class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
+ private var type = 0
+ private var position = 0
+
+ private var defaultCancelListener =
+ DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+ private lateinit var sliderBinding: DialogSliderBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ type = requireArguments().getInt(TYPE)
+ position = requireArguments().getInt(POSITION)
+
+ if (settingsViewModel.clickedItem == null) dismiss()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return when (type) {
+ TYPE_RESET_SETTING -> {
+ MaterialAlertDialogBuilder(requireContext())
+ .setMessage(R.string.reset_setting_confirmation)
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+ settingsViewModel.clickedItem!!.setting.reset()
+ settingsViewModel.setAdapterItemChanged(position)
+ settingsViewModel.shouldSave = true
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .create()
+ }
+
+ SettingsItem.TYPE_SINGLE_CHOICE -> {
+ val item = settingsViewModel.clickedItem as SingleChoiceSetting
+ val value = getSelectionForSingleChoiceValue(item)
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.nameId)
+ .setSingleChoiceItems(item.choicesId, value, this)
+ .create()
+ }
+
+ SettingsItem.TYPE_SLIDER -> {
+ sliderBinding = DialogSliderBinding.inflate(layoutInflater)
+ val item = settingsViewModel.clickedItem as SliderSetting
+
+ settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
+ sliderBinding.slider.apply {
+ valueFrom = item.min.toFloat()
+ valueTo = item.max.toFloat()
+ value = settingsViewModel.sliderProgress.value.toFloat()
+ addOnChangeListener { _: Slider, value: Float, _: Boolean ->
+ settingsViewModel.setSliderTextValue(value, item.units)
+ }
+ }
+
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.nameId)
+ .setView(sliderBinding.root)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, defaultCancelListener)
+ .create()
+ }
+
+ SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
+ val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.nameId)
+ .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
+ .create()
+ }
+
+ else -> super.onCreateDialog(savedInstanceState)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return when (type) {
+ SettingsItem.TYPE_SLIDER -> sliderBinding.root
+ else -> super.onCreateView(inflater, container, savedInstanceState)
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ when (type) {
+ SettingsItem.TYPE_SLIDER -> {
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.sliderTextValue.collect {
+ sliderBinding.textValue.text = it
+ }
+ }
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.sliderProgress.collect {
+ sliderBinding.slider.value = it.toFloat()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onClick(dialog: DialogInterface, which: Int) {
+ when (settingsViewModel.clickedItem) {
+ is SingleChoiceSetting -> {
+ val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
+ val value = getValueForSingleChoiceSelection(scSetting, which)
+ if (scSetting.selectedValue != value) {
+ settingsViewModel.shouldSave = true
+ }
+ scSetting.selectedValue = value
+ }
+
+ is StringSingleChoiceSetting -> {
+ val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
+ val value = scSetting.getValueAt(which)
+ if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
+ scSetting.selectedValue = value
+ }
+
+ is SliderSetting -> {
+ val sliderSetting = settingsViewModel.clickedItem as SliderSetting
+ if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
+ settingsViewModel.shouldSave = true
+ }
+ sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
+ }
+ }
+ closeDialog()
+ }
+
+ private fun closeDialog() {
+ settingsViewModel.setAdapterItemChanged(position)
+ settingsViewModel.clickedItem = null
+ settingsViewModel.setSliderProgress(-1f)
+ dismiss()
+ }
+
+ private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
+ val valuesId = item.valuesId
+ return if (valuesId > 0) {
+ val valuesArray = requireContext().resources.getIntArray(valuesId)
+ valuesArray[which]
+ } else {
+ which
+ }
+ }
+
+ private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
+ val value = item.selectedValue
+ val valuesId = item.valuesId
+ if (valuesId > 0) {
+ val valuesArray = requireContext().resources.getIntArray(valuesId)
+ for (index in valuesArray.indices) {
+ val current = valuesArray[index]
+ if (current == value) {
+ return index
+ }
+ }
+ } else {
+ return value
+ }
+ return -1
+ }
+
+ companion object {
+ const val TAG = "SettingsDialogFragment"
+
+ const val TYPE_RESET_SETTING = -1
+
+ const val TITLE = "Title"
+ const val TYPE = "Type"
+ const val POSITION = "Position"
+
+ fun newInstance(
+ settingsViewModel: SettingsViewModel,
+ clickedItem: SettingsItem,
+ type: Int,
+ position: Int
+ ): SettingsDialogFragment {
+ when (type) {
+ SettingsItem.TYPE_HEADER,
+ SettingsItem.TYPE_SWITCH,
+ SettingsItem.TYPE_SUBMENU,
+ SettingsItem.TYPE_DATETIME_SETTING,
+ SettingsItem.TYPE_RUNNABLE ->
+ throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
+
+ SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
+ (clickedItem as SliderSetting).selectedValue.toFloat()
+ )
+ }
+ settingsViewModel.clickedItem = clickedItem
+
+ val args = Bundle()
+ args.putInt(TYPE, type)
+ args.putInt(POSITION, position)
+ val fragment = SettingsDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import info.debatty.java.stringsimilarity.Cosine
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+class SettingsSearchFragment : Fragment() {
+ private var _binding: FragmentSettingsSearchBinding? = null
+ private val binding get() = _binding!!
+
+ private var settingsAdapter: SettingsAdapter? = null
+
+ private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ settingsViewModel.setIsUsingSearch(true)
+
+ if (savedInstanceState != null) {
+ binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
+ }
+
+ settingsAdapter = SettingsAdapter(this, requireContext())
+
+ val dividerDecoration = MaterialDividerItemDecoration(
+ requireContext(),
+ LinearLayoutManager.VERTICAL
+ )
+ dividerDecoration.isLastItemDecorated = false
+ binding.settingsList.apply {
+ adapter = settingsAdapter
+ layoutManager = LinearLayoutManager(requireContext())
+ addItemDecoration(dividerDecoration)
+ }
+
+ focusSearch()
+
+ binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
+ binding.searchBackground.setOnClickListener { focusSearch() }
+ binding.clearButton.setOnClickListener { binding.searchText.setText("") }
+ binding.searchText.doOnTextChanged { _, _, _, _ ->
+ search()
+ binding.settingsList.smoothScrollToPosition(0)
+ }
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ settingsViewModel.shouldReloadSettingsList.collect {
+ if (it) {
+ settingsViewModel.setShouldReloadSettingsList(false)
+ search()
+ }
+ }
+ }
+ }
+
+ search()
+
+ setInsets()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
+ }
+
+ private fun search() {
+ val searchTerm = binding.searchText.text.toString().lowercase()
+ binding.clearButton.visibility =
+ if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
+ if (searchTerm.isEmpty()) {
+ binding.noResultsView.visibility = View.VISIBLE
+ settingsAdapter?.submitList(emptyList())
+ return
+ }
+
+ val baseList = SettingsItem.settingsItems
+ val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
+ val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
+ val title = getString(item.value.nameId).lowercase()
+ val similarity = similarityAlgorithm.similarity(searchTerm, title)
+ if (similarity > 0.08) {
+ Pair(similarity, item)
+ } else {
+ null
+ }
+ }.sortedByDescending { it.first }.mapNotNull {
+ val item = it.second.value
+ val pairedSettingKey = item.setting.pairedSettingKey
+ val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
+ val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+ if (pairedSettingValue) it.second.value else null
+ } else {
+ it.second.value
+ }
+ optionalSetting
+ }
+ settingsAdapter?.submitList(sortedList)
+ binding.noResultsView.visibility =
+ if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
+ }
+
+ private fun focusSearch() {
+ binding.searchText.requestFocus()
+ val imm = requireActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
+ imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
+ }
+
+ private fun setInsets() =
+ ViewCompat.setOnApplyWindowInsetsListener(
+ binding.root
+ ) { _: View, windowInsets: WindowInsetsCompat ->
+ val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
+ val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+ val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
+
+ val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+ val leftInsets = barInsets.left + cutoutInsets.left
+ val rightInsets = barInsets.right + cutoutInsets.right
+
+ binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
+ binding.frameSearch.updatePadding(
+ left = leftInsets + sideMargin,
+ top = barInsets.top + topMargin,
+ right = rightInsets + sideMargin
+ )
+ binding.noResultsView.updatePadding(
+ left = leftInsets,
+ right = rightInsets,
+ bottom = barInsets.bottom
+ )
+
+ val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
+ mlpSettingsList.leftMargin = leftInsets + sideMargin
+ mlpSettingsList.rightMargin = rightInsets + sideMargin
+ binding.settingsList.layoutParams = mlpSettingsList
+
+ val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
+ mlpDivider.leftMargin = leftInsets + sideMargin
+ mlpDivider.rightMargin = rightInsets + sideMargin
+ binding.divider.layoutParams = mlpDivider
+
+ windowInsets
+ }
+
+ companion object {
+ const val SEARCH_TEXT = "SearchText"
+ }
+}
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
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
+import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.transition.MaterialFadeThrough
+import kotlinx.coroutines.launch
import java.io.File
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -32,10 +37,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter
import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage
+import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.GameHelper
+import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
@@ -112,14 +120,22 @@ class SetupFragment : Fragment() {
0,
false,
R.string.give_permission,
- { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) },
+ {
+ notificationCallback = it
+ permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
+ },
true,
R.string.notification_warning,
R.string.notification_warning_description,
0,
{
- NotificationManagerCompat.from(requireContext())
+ if (NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled()
+ ) {
+ StepState.COMPLETE
+ } else {
+ StepState.INCOMPLETE
+ }
}
)
)
@@ -133,12 +149,22 @@ class SetupFragment : Fragment() {
R.drawable.ic_add,
true,
R.string.select_keys,
- { mainActivity.getProdKey.launch(arrayOf("*/*")) },
+ {
+ keyCallback = it
+ getProdKey.launch(arrayOf("*/*"))
+ },
true,
R.string.install_prod_keys_warning,
R.string.install_prod_keys_warning_description,
R.string.install_prod_keys_warning_help,
- { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() }
+ {
+ val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
+ if (file.exists()) {
+ StepState.COMPLETE
+ } else {
+ StepState.INCOMPLETE
+ }
+ }
)
)
add(
@@ -150,9 +176,8 @@ class SetupFragment : Fragment() {
true,
R.string.add_games,
{
- mainActivity.getGamesDirectory.launch(
- Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
- )
+ gamesDirCallback = it
+ getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
},
true,
R.string.add_games_warning,
@@ -163,7 +188,11 @@ class SetupFragment : Fragment() {
PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
- preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
+ if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
+ StepState.COMPLETE
+ } else {
+ StepState.INCOMPLETE
+ }
}
)
)
@@ -181,6 +210,17 @@ class SetupFragment : Fragment() {
)
}
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ homeViewModel.shouldPageForward.collect {
+ if (it) {
+ pageForward()
+ homeViewModel.setShouldPageForward(false)
+ }
+ }
+ }
+ }
+
binding.viewPager2.apply {
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
offscreenPageLimit = 2
@@ -194,15 +234,15 @@ class SetupFragment : Fragment() {
super.onPageSelected(position)
if (position == 1 && previousPosition == 0) {
- showView(binding.buttonNext)
- showView(binding.buttonBack)
+ ViewUtils.showView(binding.buttonNext)
+ ViewUtils.showView(binding.buttonBack)
} else if (position == 0 && previousPosition == 1) {
- hideView(binding.buttonBack)
- hideView(binding.buttonNext)
+ ViewUtils.hideView(binding.buttonBack)
+ ViewUtils.hideView(binding.buttonNext)
} else if (position == pages.size - 1 && previousPosition == pages.size - 2) {
- hideView(binding.buttonNext)
+ ViewUtils.hideView(binding.buttonNext)
} else if (position == pages.size - 2 && previousPosition == pages.size - 1) {
- showView(binding.buttonNext)
+ ViewUtils.showView(binding.buttonNext)
}
previousPosition = position
@@ -215,7 +255,8 @@ class SetupFragment : Fragment() {
// Checks if the user has completed the task on the current page
if (currentPage.hasWarning) {
- if (currentPage.taskCompleted.invoke()) {
+ val stepState = currentPage.stepCompleted.invoke()
+ if (stepState != StepState.INCOMPLETE) {
pageForward()
return@setOnClickListener
}
@@ -264,9 +305,15 @@ class SetupFragment : Fragment() {
_binding = null
}
+ private lateinit var notificationCallback: SetupCallback
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
+ if (it) {
+ notificationCallback.onStepCompleted()
+ }
+
if (!it &&
!shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
) {
@@ -277,6 +324,27 @@ class SetupFragment : Fragment() {
}
}
+ private lateinit var keyCallback: SetupCallback
+
+ val getProdKey =
+ registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
+ if (result != null) {
+ if (mainActivity.processKey(result)) {
+ keyCallback.onStepCompleted()
+ }
+ }
+ }
+
+ private lateinit var gamesDirCallback: SetupCallback
+
+ val getGamesDirectory =
+ registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
+ if (result != null) {
+ mainActivity.processGamesDir(result)
+ gamesDirCallback.onStepCompleted()
+ }
+ }
+
private fun finishSetup() {
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
.putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
@@ -284,33 +352,6 @@ class SetupFragment : Fragment() {
mainActivity.finishSetup(binding.root.findNavController())
}
- private fun showView(view: View) {
- view.apply {
- alpha = 0f
- visibility = View.VISIBLE
- isClickable = true
- }.animate().apply {
- duration = 300
- alpha(1f)
- }.start()
- }
-
- private fun hideView(view: View) {
- if (view.visibility == View.INVISIBLE) {
- return
- }
-
- view.apply {
- alpha = 1f
- isClickable = false
- }.animate().apply {
- duration = 300
- alpha(0f)
- }.withEndAction {
- view.visibility = View.INVISIBLE
- }
- }
-
fun pageForward() {
binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
}
@@ -326,15 +367,29 @@ class SetupFragment : Fragment() {
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
- ) { view: View, windowInsets: WindowInsetsCompat ->
+ ) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
- view.setPadding(
- barInsets.left + cutoutInsets.left,
- barInsets.top + cutoutInsets.top,
- barInsets.right + cutoutInsets.right,
- barInsets.bottom + cutoutInsets.bottom
- )
+
+ val leftPadding = barInsets.left + cutoutInsets.left
+ val topPadding = barInsets.top + cutoutInsets.top
+ val rightPadding = barInsets.right + cutoutInsets.right
+ val bottomPadding = barInsets.bottom + cutoutInsets.bottom
+
+ if (resources.getBoolean(R.bool.small_layout)) {
+ binding.viewPager2
+ .updatePadding(left = leftPadding, top = topPadding, right = rightPadding)
+ binding.constraintButtons
+ .updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding)
+ } else {
+ binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding)
+ binding.constraintButtons
+ .updatePadding(
+ left = leftPadding,
+ right = rightPadding,
+ bottom = bottomPadding
+ )
+ }
windowInsets
}
}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class EmulationViewModel : ViewModel() {
+ val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
+ private val _emulationStarted = MutableStateFlow(false)
+
+ val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
+ private val _isEmulationStopping = MutableStateFlow(false)
+
+ val shaderProgress: StateFlow<Int> get() = _shaderProgress
+ private val _shaderProgress = MutableStateFlow(0)
+
+ val totalShaders: StateFlow<Int> get() = _totalShaders
+ private val _totalShaders = MutableStateFlow(0)
+
+ val shaderMessage: StateFlow<String> get() = _shaderMessage
+ private val _shaderMessage = MutableStateFlow("")
+
+ fun setEmulationStarted(started: Boolean) {
+ _emulationStarted.value = started
+ }
+
+ fun setIsEmulationStopping(value: Boolean) {
+ _isEmulationStopping.value = value
+ }
+
+ fun setShaderProgress(progress: Int) {
+ _shaderProgress.value = progress
+ }
+
+ fun setTotalShaders(max: Int) {
+ _totalShaders.value = max
+ }
+
+ fun setShaderMessage(msg: String) {
+ _shaderMessage.value = msg
+ }
+
+ fun updateProgress(msg: String, progress: Int, max: Int) {
+ setShaderMessage(msg)
+ setShaderProgress(progress)
+ setTotalShaders(max)
+ }
+
+ fun clear() {
+ setEmulationStarted(false)
+ setIsEmulationStopping(false)
+ setShaderProgress(0)
+ setTotalShaders(0)
+ setShaderMessage("")
+ }
+
+ companion object {
+ const val KEY_EMULATION_STARTED = "EmulationStarted"
+ const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
+ const val KEY_SHADER_PROGRESS = "ShaderProgress"
+ const val KEY_TOTAL_SHADERS = "TotalShaders"
+ const val KEY_SHADER_MESSAGE = "ShaderMessage"
+ }
+}
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
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import java.util.Locale
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
@@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() {
- private val _games = MutableLiveData<List<Game>>(emptyList())
- val games: LiveData<List<Game>> get() = _games
+ val games: StateFlow<List<Game>> get() = _games
+ private val _games = MutableStateFlow(emptyList<Game>())
- private val _searchedGames = MutableLiveData<List<Game>>(emptyList())
- val searchedGames: LiveData<List<Game>> get() = _searchedGames
+ val searchedGames: StateFlow<List<Game>> get() = _searchedGames
+ private val _searchedGames = MutableStateFlow(emptyList<Game>())
- private val _isReloading = MutableLiveData(false)
- val isReloading: LiveData<Boolean> get() = _isReloading
+ val isReloading: StateFlow<Boolean> get() = _isReloading
+ private val _isReloading = MutableStateFlow(false)
- private val _shouldSwapData = MutableLiveData(false)
- val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData
+ val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
+ private val _shouldSwapData = MutableStateFlow(false)
- private val _shouldScrollToTop = MutableLiveData(false)
- val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop
+ val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
+ private val _shouldScrollToTop = MutableStateFlow(false)
- private val _searchFocused = MutableLiveData(false)
- val searchFocused: LiveData<Boolean> get() = _searchFocused
+ val searchFocused: StateFlow<Boolean> get() = _searchFocused
+ private val _searchFocused = MutableStateFlow(false)
init {
// Ensure keys are loaded so that ROM metadata can be decrypted.
@@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
)
)
- _games.postValue(sortedList)
+ _games.value = sortedList
}
fun setSearchedGames(games: List<Game>) {
- _searchedGames.postValue(games)
+ _searchedGames.value = games
}
fun setShouldSwapData(shouldSwap: Boolean) {
- _shouldSwapData.postValue(shouldSwap)
+ _shouldSwapData.value = shouldSwap
}
fun setShouldScrollToTop(shouldScroll: Boolean) {
- _shouldScrollToTop.postValue(shouldScroll)
+ _shouldScrollToTop.value = shouldScroll
}
fun setSearchFocused(searchFocused: Boolean) {
- _searchFocused.postValue(searchFocused)
+ _searchFocused.value = searchFocused
}
fun reloadGames(directoryChanged: Boolean) {
- if (isReloading.value == true) {
+ if (isReloading.value) {
return
}
- _isReloading.postValue(true)
+ _isReloading.value = true
viewModelScope.launch {
withContext(Dispatchers.IO) {
NativeLibrary.resetRomMetadata()
setGames(GameHelper.getGames())
- _isReloading.postValue(false)
+ _isReloading.value = false
if (directoryChanged) {
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 @@
package org.yuzu.yuzu_emu.model
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
data class HomeSetting(
val titleId: Int,
val descriptionId: Int,
@@ -10,5 +13,6 @@ data class HomeSetting(
val onClick: () -> Unit,
val isEnabled: () -> Boolean = { true },
val disabledTitleId: Int = 0,
- val disabledMessageId: Int = 0
+ val disabledMessageId: Int = 0,
+ val details: StateFlow<String> = MutableStateFlow("")
)
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 @@
package org.yuzu.yuzu_emu.model
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
+import android.net.Uri
+import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.preference.PreferenceManager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() {
- private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
- val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible
+ val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
+ private val _navigationVisible = MutableStateFlow(Pair(false, false))
- private val _statusBarShadeVisible = MutableLiveData(true)
- val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
+ val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
+ private val _statusBarShadeVisible = MutableStateFlow(true)
- var navigatedToSetup = false
+ val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
+ private val _shouldPageForward = MutableStateFlow(false)
- init {
- _navigationVisible.value = Pair(false, false)
- }
+ val gamesDir: StateFlow<String> get() = _gamesDir
+ private val _gamesDir = MutableStateFlow(
+ Uri.parse(
+ PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+ .getString(GameHelper.KEY_GAME_PATH, "")
+ ).path ?: ""
+ )
+
+ var navigatedToSetup = false
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
- if (_navigationVisible.value?.first == visible) {
+ if (navigationVisible.value.first == visible) {
return
}
_navigationVisible.value = Pair(visible, animated)
}
fun setStatusBarShadeVisibility(visible: Boolean) {
- if (_statusBarShadeVisible.value == visible) {
+ if (statusBarShadeVisible.value == visible) {
return
}
_statusBarShadeVisible.value = visible
}
+
+ fun setShouldPageForward(pageForward: Boolean) {
+ _shouldPageForward.value = pageForward
+ }
+
+ fun setGamesDir(activity: FragmentActivity, dir: String) {
+ ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
+ _gamesDir.value = dir
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
new file mode 100644
index 000000000..36ffd08d2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.lifecycle.ViewModel
+
+class MessageDialogViewModel : ViewModel() {
+ var dismissAction: () -> Unit = {}
+
+ fun clear() {
+ dismissAction = {}
+ }
+}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+
+class SettingsViewModel : ViewModel() {
+ var game: Game? = null
+
+ var shouldSave = false
+
+ var clickedItem: SettingsItem? = null
+
+ val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
+ private val _shouldRecreate = MutableStateFlow(false)
+
+ val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
+ private val _shouldNavigateBack = MutableStateFlow(false)
+
+ val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
+ private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
+
+ val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
+ private val _shouldReloadSettingsList = MutableStateFlow(false)
+
+ val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
+ private val _isUsingSearch = MutableStateFlow(false)
+
+ val sliderProgress: StateFlow<Int> get() = _sliderProgress
+ private val _sliderProgress = MutableStateFlow(-1)
+
+ val sliderTextValue: StateFlow<String> get() = _sliderTextValue
+ private val _sliderTextValue = MutableStateFlow("")
+
+ val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
+ private val _adapterItemChanged = MutableStateFlow(-1)
+
+ fun setShouldRecreate(value: Boolean) {
+ _shouldRecreate.value = value
+ }
+
+ fun setShouldNavigateBack(value: Boolean) {
+ _shouldNavigateBack.value = value
+ }
+
+ fun setShouldShowResetSettingsDialog(value: Boolean) {
+ _shouldShowResetSettingsDialog.value = value
+ }
+
+ fun setShouldReloadSettingsList(value: Boolean) {
+ _shouldReloadSettingsList.value = value
+ }
+
+ fun setIsUsingSearch(value: Boolean) {
+ _isUsingSearch.value = value
+ }
+
+ fun setSliderTextValue(value: Float, units: String) {
+ _sliderProgress.value = value.toInt()
+ _sliderTextValue.value = String.format(
+ YuzuApplication.appContext.getString(R.string.value_with_units),
+ value.toInt().toString(),
+ units
+ )
+ }
+
+ fun setSliderProgress(value: Float) {
+ _sliderProgress.value = value.toInt()
+ }
+
+ fun setAdapterItemChanged(value: Int) {
+ _adapterItemChanged.value = value
+ }
+
+ fun clear() {
+ game = null
+ shouldSave = false
+ }
+}
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(
val buttonIconId: Int,
val leftAlignedIcon: Boolean,
val buttonTextId: Int,
- val buttonAction: () -> Unit,
+ val buttonAction: (callback: SetupCallback) -> Unit,
val hasWarning: Boolean,
val warningTitleId: Int = 0,
val warningDescriptionId: Int = 0,
val warningHelpLinkId: Int = 0,
- val taskCompleted: () -> Boolean = { true }
+ val stepCompleted: () -> StepState = { StepState.UNDEFINED }
)
+
+interface SetupCallback {
+ fun onStepCompleted()
+}
+
+enum class StepState {
+ COMPLETE,
+ INCOMPLETE,
+ UNDEFINED
+}
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..d6418a666 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,45 +3,50 @@
package org.yuzu.yuzu_emu.model
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() {
- private val _result = MutableLiveData<Any>()
- val result: LiveData<Any> = _result
+ val result: StateFlow<Any> get() = _result
+ private val _result = MutableStateFlow(Any())
- private val _isComplete = MutableLiveData<Boolean>()
- val isComplete: LiveData<Boolean> = _isComplete
+ val isComplete: StateFlow<Boolean> get() = _isComplete
+ private val _isComplete = MutableStateFlow(false)
- private val _isRunning = MutableLiveData<Boolean>()
- val isRunning: LiveData<Boolean> = _isRunning
+ val isRunning: StateFlow<Boolean> get() = _isRunning
+ private val _isRunning = MutableStateFlow(false)
- lateinit var task: () -> Any
+ val cancelled: StateFlow<Boolean> get() = _cancelled
+ private val _cancelled = MutableStateFlow(false)
- init {
- clear()
- }
+ lateinit var task: () -> Any
fun clear() {
_result.value = Any()
_isComplete.value = false
_isRunning.value = false
+ _cancelled.value = false
+ }
+
+ fun setCancelled(value: Boolean) {
+ _cancelled.value = value
}
fun runTask() {
- if (_isRunning.value == true) {
+ if (isRunning.value) {
return
}
_isRunning.value = true
viewModelScope.launch(Dispatchers.IO) {
val res = task()
- _result.postValue(res)
- _isComplete.postValue(true)
+ _result.value = res
+ _isComplete.value = true
+ _isRunning.value = false
}
}
}
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 @@
package org.yuzu.yuzu_emu.ui
+import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.color.MaterialColors
import com.google.android.material.transition.MaterialFadeThrough
+import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
return binding.root
}
+ // This is using the correct scope, lint is just acting up
+ @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false)
@@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
if (_binding == null) {
return@post
}
- binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!!
+ binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
}
}
- gamesViewModel.apply {
- // Watch for when we get updates to any of our games lists
- isReloading.observe(viewLifecycleOwner) { isReloading ->
- binding.swipeRefresh.isRefreshing = isReloading
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
+ }
}
- games.observe(viewLifecycleOwner) {
- (binding.gridGames.adapter as GameAdapter).submitList(it)
- if (it.isEmpty()) {
- binding.noticeText.visibility = View.VISIBLE
- } else {
- binding.noticeText.visibility = View.GONE
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ gamesViewModel.games.collect {
+ (binding.gridGames.adapter as GameAdapter).submitList(it)
+ if (it.isEmpty()) {
+ binding.noticeText.visibility = View.VISIBLE
+ } else {
+ binding.noticeText.visibility = View.GONE
+ }
+ }
}
}
- shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
- if (shouldSwapData) {
- (binding.gridGames.adapter as GameAdapter).submitList(
- gamesViewModel.games.value!!
- )
- gamesViewModel.setShouldSwapData(false)
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ gamesViewModel.shouldSwapData.collect {
+ if (it) {
+ (binding.gridGames.adapter as GameAdapter).submitList(
+ gamesViewModel.games.value
+ )
+ gamesViewModel.setShouldSwapData(false)
+ }
+ }
}
}
-
- // Check if the user reselected the games menu item and then scroll to top of the list
- shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll ->
- if (shouldScroll) {
- scrollToTop()
- gamesViewModel.setShouldScrollToTop(false)
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ gamesViewModel.shouldScrollToTop.collect {
+ if (it) {
+ scrollToTop()
+ gamesViewModel.setShouldScrollToTop(false)
+ }
+ }
}
}
}
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..6fa847631 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
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
@@ -33,28 +35,33 @@ import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
-import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+import java.util.zip.ZipOutputStream
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
private val homeViewModel: HomeViewModel by viewModels()
private val gamesViewModel: GamesViewModel by viewModels()
- private val settingsViewModel: SettingsViewModel by viewModels()
+ private val taskViewModel: TaskViewModel by viewModels()
override var themeId: Int = 0
@@ -62,8 +69,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
- settingsViewModel.settings.loadSettings()
-
ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState)
@@ -109,25 +114,33 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
when (it.itemId) {
R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
- R.id.homeSettingsFragment -> SettingsActivity.launch(
- this,
- SettingsFile.FILE_NAME_CONFIG,
- ""
- )
+ R.id.homeSettingsFragment -> {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ Settings.MenuTag.SECTION_ROOT
+ )
+ navHostFragment.navController.navigate(action)
+ }
}
}
// Prevents navigation from being drawn for a short time on recreation if set to hidden
- if (!homeViewModel.navigationVisible.value?.first!!) {
+ if (!homeViewModel.navigationVisible.value.first) {
binding.navigationView.visibility = View.INVISIBLE
binding.statusBarShade.visibility = View.INVISIBLE
}
- homeViewModel.navigationVisible.observe(this) {
- showNavigation(it.first, it.second)
- }
- homeViewModel.statusBarShadeVisible.observe(this) { visible ->
- showStatusBarShade(visible)
+ lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
+ }
+ }
}
// Dismiss previous notifications (should not happen unless a crash occurred)
@@ -266,73 +279,83 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
- if (result == null) {
- return@registerForActivityResult
+ if (result != null) {
+ processGamesDir(result)
}
+ }
- contentResolver.takePersistableUriPermission(
- result,
- Intent.FLAG_GRANT_READ_URI_PERMISSION
- )
+ fun processGamesDir(result: Uri) {
+ contentResolver.takePersistableUriPermission(
+ result,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ )
- // When a new directory is picked, we currently will reset the existing games
- // database. This effectively means that only one game directory is supported.
- PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
- .putString(GameHelper.KEY_GAME_PATH, result.toString())
- .apply()
+ // When a new directory is picked, we currently will reset the existing games
+ // database. This effectively means that only one game directory is supported.
+ PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
+ .putString(GameHelper.KEY_GAME_PATH, result.toString())
+ .apply()
- Toast.makeText(
- applicationContext,
- R.string.games_dir_selected,
- Toast.LENGTH_LONG
- ).show()
+ Toast.makeText(
+ applicationContext,
+ R.string.games_dir_selected,
+ Toast.LENGTH_LONG
+ ).show()
- gamesViewModel.reloadGames(true)
- }
+ gamesViewModel.reloadGames(true)
+ homeViewModel.setGamesDir(this, result.path!!)
+ }
val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
- if (result == null) {
- return@registerForActivityResult
+ if (result != null) {
+ processKey(result)
}
+ }
- if (FileUtil.getExtension(result) != "keys") {
- MessageDialogFragment.newInstance(
- R.string.reading_keys_failure,
- R.string.install_prod_keys_failure_extension_description
- ).show(supportFragmentManager, MessageDialogFragment.TAG)
- return@registerForActivityResult
- }
+ fun processKey(result: Uri): Boolean {
+ if (FileUtil.getExtension(result) != "keys") {
+ MessageDialogFragment.newInstance(
+ this,
+ titleId = R.string.reading_keys_failure,
+ descriptionId = R.string.install_prod_keys_failure_extension_description
+ ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ return false
+ }
- contentResolver.takePersistableUriPermission(
+ contentResolver.takePersistableUriPermission(
+ result,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ )
+
+ val dstPath = DirectoryInitialization.userDirectory + "/keys/"
+ if (FileUtil.copyUriToInternalStorage(
+ applicationContext,
result,
- Intent.FLAG_GRANT_READ_URI_PERMISSION
+ dstPath,
+ "prod.keys"
)
-
- val dstPath = DirectoryInitialization.userDirectory + "/keys/"
- if (FileUtil.copyUriToInternalStorage(
+ ) {
+ if (NativeLibrary.reloadKeys()) {
+ Toast.makeText(
applicationContext,
- result,
- dstPath,
- "prod.keys"
- )
- ) {
- if (NativeLibrary.reloadKeys()) {
- Toast.makeText(
- applicationContext,
- R.string.install_keys_success,
- Toast.LENGTH_SHORT
- ).show()
- gamesViewModel.reloadGames(true)
- } else {
- MessageDialogFragment.newInstance(
- R.string.invalid_keys_error,
- R.string.install_keys_failure_description,
- R.string.dumping_keys_quickstart_link
- ).show(supportFragmentManager, MessageDialogFragment.TAG)
- }
+ R.string.install_keys_success,
+ Toast.LENGTH_SHORT
+ ).show()
+ gamesViewModel.reloadGames(true)
+ return true
+ } else {
+ MessageDialogFragment.newInstance(
+ this,
+ titleId = R.string.invalid_keys_error,
+ descriptionId = R.string.install_keys_failure_description,
+ helpLinkId = R.string.dumping_keys_quickstart_link
+ ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ return false
}
}
+ return false
+ }
val getFirmware =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
@@ -364,8 +387,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
MessageDialogFragment.newInstance(
- R.string.firmware_installed_failure,
- R.string.firmware_installed_failure_description
+ this,
+ titleId = R.string.firmware_installed_failure,
+ descriptionId = R.string.firmware_installed_failure_description
)
} else {
firmwarePath.deleteRecursively()
@@ -383,7 +407,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.firmware_installing,
- task
+ task = task
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
@@ -395,8 +419,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (FileUtil.getExtension(result) != "bin") {
MessageDialogFragment.newInstance(
- R.string.reading_keys_failure,
- R.string.install_amiibo_keys_failure_extension_description
+ this,
+ titleId = R.string.reading_keys_failure,
+ descriptionId = R.string.install_amiibo_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}
@@ -422,9 +447,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
).show()
} else {
MessageDialogFragment.newInstance(
- R.string.invalid_keys_error,
- R.string.install_keys_failure_description,
- R.string.dumping_keys_quickstart_link
+ this,
+ titleId = R.string.invalid_keys_error,
+ descriptionId = R.string.install_keys_failure_description,
+ helpLinkId = R.string.dumping_keys_quickstart_link
).show(supportFragmentManager, MessageDialogFragment.TAG)
}
}
@@ -496,97 +522,209 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
var errorBaseGame = 0
var errorExtension = 0
var errorOther = 0
- var errorTotal = 0
- lifecycleScope.launch {
- documents.forEach {
- when (NativeLibrary.installFileToNand(it.toString())) {
- NativeLibrary.InstallFileToNandResult.Success -> {
- installSuccess += 1
- }
+ documents.forEach {
+ when (NativeLibrary.installFileToNand(it.toString())) {
+ NativeLibrary.InstallFileToNandResult.Success -> {
+ installSuccess += 1
+ }
- NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
- installOverwrite += 1
- }
+ NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
+ installOverwrite += 1
+ }
- NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
- errorBaseGame += 1
- }
+ NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
+ errorBaseGame += 1
+ }
- NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
- errorExtension += 1
- }
+ NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
+ errorExtension += 1
+ }
+
+ else -> {
+ errorOther += 1
+ }
+ }
+ }
+
+ val separator = System.getProperty("line.separator") ?: "\n"
+ val installResult = StringBuilder()
+ if (installSuccess > 0) {
+ installResult.append(
+ getString(
+ R.string.install_game_content_success_install,
+ installSuccess
+ )
+ )
+ installResult.append(separator)
+ }
+ if (installOverwrite > 0) {
+ installResult.append(
+ getString(
+ R.string.install_game_content_success_overwrite,
+ installOverwrite
+ )
+ )
+ installResult.append(separator)
+ }
+ val errorTotal: Int = errorBaseGame + errorExtension + errorOther
+ if (errorTotal > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(
+ R.string.install_game_content_failed_count,
+ errorTotal
+ )
+ )
+ installResult.append(separator)
+ if (errorBaseGame > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(R.string.install_game_content_failure_base)
+ )
+ installResult.append(separator)
+ }
+ if (errorExtension > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(R.string.install_game_content_failure_file_extension)
+ )
+ installResult.append(separator)
+ }
+ if (errorOther > 0) {
+ installResult.append(
+ getString(R.string.install_game_content_failure_description)
+ )
+ installResult.append(separator)
+ }
+ return@newInstance MessageDialogFragment.newInstance(
+ this,
+ titleId = R.string.install_game_content_failure,
+ descriptionString = installResult.toString().trim(),
+ helpLinkId = R.string.install_game_content_help_link
+ )
+ } else {
+ return@newInstance MessageDialogFragment.newInstance(
+ this,
+ titleId = R.string.install_game_content_success,
+ descriptionString = installResult.toString().trim()
+ )
+ }
+ }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }
+ }
+
+ val exportUserData = registerForActivityResult(
+ ActivityResultContracts.CreateDocument("application/zip")
+ ) { result ->
+ if (result == null) {
+ return@registerForActivityResult
+ }
+
+ IndeterminateProgressDialogFragment.newInstance(
+ this,
+ R.string.exporting_user_data,
+ true
+ ) {
+ val zos = ZipOutputStream(
+ BufferedOutputStream(contentResolver.openOutputStream(result))
+ )
+ zos.use { stream ->
+ File(DirectoryInitialization.userDirectory!!).walkTopDown().forEach { file ->
+ if (taskViewModel.cancelled.value) {
+ return@newInstance R.string.user_data_export_cancelled
+ }
+
+ if (!file.isDirectory) {
+ val newPath = file.path.substring(
+ DirectoryInitialization.userDirectory!!.length,
+ file.path.length
+ )
+ stream.putNextEntry(ZipEntry(newPath))
- else -> {
- errorOther += 1
+ val buffer = ByteArray(8096)
+ var read: Int
+ FileInputStream(file).use { fis ->
+ while (fis.read(buffer).also { read = it } != -1) {
+ stream.write(buffer, 0, read)
}
}
+
+ stream.closeEntry()
}
- withContext(Dispatchers.Main) {
- val separator = System.getProperty("line.separator") ?: "\n"
- val installResult = StringBuilder()
- if (installSuccess > 0) {
- installResult.append(
- getString(
- R.string.install_game_content_success_install,
- installSuccess
- )
- )
- installResult.append(separator)
+ }
+ }
+ return@newInstance getString(R.string.user_data_export_success)
+ }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }
+
+ val importUserData =
+ registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
+ if (result == null) {
+ return@registerForActivityResult
+ }
+
+ IndeterminateProgressDialogFragment.newInstance(
+ this,
+ R.string.importing_user_data
+ ) {
+ val checkStream =
+ ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
+ var isYuzuBackup = false
+ checkStream.use { stream ->
+ var ze: ZipEntry? = null
+ while (stream.nextEntry?.also { ze = it } != null) {
+ val itemName = ze!!.name.trim()
+ if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
+ isYuzuBackup = true
+ return@use
}
- if (installOverwrite > 0) {
- installResult.append(
- getString(
- R.string.install_game_content_success_overwrite,
- installOverwrite
- )
+ }
+ }
+ if (!isYuzuBackup) {
+ return@newInstance getString(R.string.invalid_yuzu_backup)
+ }
+
+ File(DirectoryInitialization.userDirectory!!).deleteRecursively()
+
+ val zis =
+ ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
+ val userDirectory = File(DirectoryInitialization.userDirectory!!)
+ val canonicalPath = userDirectory.canonicalPath + '/'
+ zis.use { stream ->
+ var ze: ZipEntry? = stream.nextEntry
+ while (ze != null) {
+ val newFile = File(userDirectory, ze!!.name)
+ val destinationDirectory =
+ if (ze!!.isDirectory) newFile else newFile.parentFile
+
+ if (!newFile.canonicalPath.startsWith(canonicalPath)) {
+ throw SecurityException(
+ "Zip file attempted path traversal! ${ze!!.name}"
)
- installResult.append(separator)
}
- errorTotal = errorBaseGame + errorExtension + errorOther
- if (errorTotal > 0) {
- installResult.append(separator)
- installResult.append(
- getString(
- R.string.install_game_content_failed_count,
- errorTotal
- )
- )
- installResult.append(separator)
- if (errorBaseGame > 0) {
- installResult.append(separator)
- installResult.append(
- getString(R.string.install_game_content_failure_base)
- )
- installResult.append(separator)
- }
- if (errorExtension > 0) {
- installResult.append(separator)
- installResult.append(
- getString(R.string.install_game_content_failure_file_extension)
- )
- installResult.append(separator)
- }
- if (errorOther > 0) {
- installResult.append(
- getString(R.string.install_game_content_failure_description)
- )
- installResult.append(separator)
+
+ if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
+ throw IOException("Failed to create directory $destinationDirectory")
+ }
+
+ if (!ze!!.isDirectory) {
+ val buffer = ByteArray(8096)
+ var read: Int
+ BufferedOutputStream(FileOutputStream(newFile)).use { bos ->
+ while (zis.read(buffer).also { read = it } != -1) {
+ bos.write(buffer, 0, read)
+ }
}
- LongMessageDialogFragment.newInstance(
- R.string.install_game_content_failure,
- installResult.toString().trim(),
- R.string.install_game_content_help_link
- ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
- } else {
- LongMessageDialogFragment.newInstance(
- R.string.install_game_content_success,
- installResult.toString().trim()
- ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
}
+ ze = stream.nextEntry
}
}
- return@newInstance installSuccess + installOverwrite + errorTotal
+
+ // Reinitialize relevant data
+ NativeLibrary.initializeEmulation()
+ gamesViewModel.reloadGames(false)
+
+ return@newInstance getString(R.string.user_data_import_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
- }
}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.utils
-
-class BiMap<K, V> {
- private val forward: MutableMap<K, V> = HashMap()
- private val backward: MutableMap<V, K> = HashMap()
-
- @Synchronized
- fun add(key: K, value: V) {
- forward[key] = value
- backward[value] = key
- }
-
- @Synchronized
- fun getForward(key: K): V? {
- return forward[key]
- }
-
- @Synchronized
- fun getBackward(key: V): K? {
- return backward[key]
- }
-}
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 @@
package org.yuzu.yuzu_emu.utils
-import android.content.Context
import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.YuzuApplication
object DirectoryInitialization {
private var userPath: String? = null
var areDirectoriesReady: Boolean = false
- fun start(context: Context) {
+ fun start() {
if (!areDirectoriesReady) {
- initializeInternalStorage(context)
+ initializeInternalStorage()
NativeLibrary.initializeEmulation()
areDirectoriesReady = true
}
@@ -26,9 +26,9 @@ object DirectoryInitialization {
return userPath
}
- private fun initializeInternalStorage(context: Context) {
+ private fun initializeInternalStorage() {
try {
- userPath = context.getExternalFilesDir(null)!!.canonicalPath
+ userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
NativeLibrary.setAppDirectory(userPath!!)
} catch (e: IOException) {
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
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.model.MinimalDocumentFile
object GameHelper {
const val KEY_GAME_PATH = "game_path"
@@ -29,15 +30,7 @@ object GameHelper {
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
- val children = FileUtil.listFiles(context, gamesUri)
- for (file in children) {
- if (!file.isDirectory) {
- // Check that the file has an extension we care about before trying to read out of it.
- if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
- games.add(getGame(file.uri))
- }
- }
- }
+ addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3)
// Cache list of games found on disk
val serializedGames = mutableSetOf<String>()
@@ -52,7 +45,31 @@ object GameHelper {
return games.toList()
}
- private fun getGame(uri: Uri): Game {
+ private fun addGamesRecursive(
+ games: MutableList<Game>,
+ files: Array<MinimalDocumentFile>,
+ depth: Int
+ ) {
+ if (depth <= 0) {
+ return
+ }
+
+ files.forEach {
+ if (it.isDirectory) {
+ addGamesRecursive(
+ games,
+ FileUtil.listFiles(YuzuApplication.appContext, it.uri),
+ depth - 1
+ )
+ } else {
+ if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
+ games.add(getGame(it.uri, true))
+ }
+ }
+ }
+ }
+
+ fun getGame(uri: Uri, addedToLibrary: Boolean): Game {
val filePath = uri.toString()
var name = NativeLibrary.getTitle(filePath)
@@ -77,11 +94,13 @@ object GameHelper {
NativeLibrary.isHomebrew(filePath)
)
- val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
- if (addedTime == 0L) {
- preferences.edit()
- .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis())
- .apply()
+ if (addedToLibrary) {
+ val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
+ if (addedTime == 0L) {
+ preferences.edit()
+ .putLong(newGame.keyAddedToLibraryTime, System.currentTimeMillis())
+ .apply()
+ }
}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.widget.ImageView
+import androidx.core.graphics.drawable.toBitmap
+import androidx.core.graphics.drawable.toDrawable
+import coil.ImageLoader
+import coil.decode.DataSource
+import coil.executeBlocking
+import coil.fetch.DrawableResult
+import coil.fetch.FetchResult
+import coil.fetch.Fetcher
+import coil.key.Keyer
+import coil.memory.MemoryCache
+import coil.request.ImageRequest
+import coil.request.Options
+import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.model.Game
+
+class GameIconFetcher(
+ private val game: Game,
+ private val options: Options
+) : Fetcher {
+ override suspend fun fetch(): FetchResult {
+ return DrawableResult(
+ drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources),
+ isSampled = false,
+ dataSource = DataSource.DISK
+ )
+ }
+
+ private fun decodeGameIcon(uri: String): Bitmap? {
+ val data = NativeLibrary.getIcon(uri)
+ return BitmapFactory.decodeByteArray(
+ data,
+ 0,
+ data.size,
+ BitmapFactory.Options()
+ )
+ }
+
+ class Factory : Fetcher.Factory<Game> {
+ override fun create(data: Game, options: Options, imageLoader: ImageLoader): Fetcher =
+ GameIconFetcher(data, options)
+ }
+}
+
+class GameIconKeyer : Keyer<Game> {
+ override fun key(data: Game, options: Options): String = data.path
+}
+
+object GameIconUtils {
+ private val imageLoader = ImageLoader.Builder(YuzuApplication.appContext)
+ .components {
+ add(GameIconKeyer())
+ add(GameIconFetcher.Factory())
+ }
+ .memoryCache {
+ MemoryCache.Builder(YuzuApplication.appContext)
+ .maxSizePercent(0.25)
+ .build()
+ }
+ .build()
+
+ fun loadGameIcon(game: Game, imageView: ImageView) {
+ val request = ImageRequest.Builder(YuzuApplication.appContext)
+ .data(game)
+ .target(imageView)
+ .error(R.drawable.default_icon)
+ .build()
+ imageLoader.enqueue(request)
+ }
+
+ fun getGameIcon(game: Game): Bitmap {
+ val request = ImageRequest.Builder(YuzuApplication.appContext)
+ .data(game)
+ .error(R.drawable.default_icon)
+ .build()
+ return imageLoader.executeBlocking(request)
+ .drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
+ }
+}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+object NativeConfig {
+ external fun getBoolean(key: String, getDefault: Boolean): Boolean
+ external fun setBoolean(key: String, value: Boolean)
+
+ external fun getByte(key: String, getDefault: Boolean): Byte
+ external fun setByte(key: String, value: Byte)
+
+ external fun getShort(key: String, getDefault: Boolean): Short
+ external fun setShort(key: String, value: Short)
+
+ external fun getInt(key: String, getDefault: Boolean): Int
+ external fun setInt(key: String, value: Int)
+
+ external fun getFloat(key: String, getDefault: Boolean): Float
+ external fun setFloat(key: String, value: Float)
+
+ external fun getLong(key: String, getDefault: Boolean): Long
+ external fun setLong(key: String, value: Long)
+
+ external fun getString(key: String, getDefault: Boolean): String
+ external fun setString(key: String, value: String)
+
+ external fun getIsRuntimeModifiable(key: String): Boolean
+
+ external fun getConfigHeader(category: Int): String
+
+ external fun getPairedSettingKey(key: String): String
+}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import android.view.View
+
+object ViewUtils {
+ fun showView(view: View, length: Long = 300) {
+ view.apply {
+ alpha = 0f
+ visibility = View.VISIBLE
+ isClickable = true
+ }.animate().apply {
+ duration = length
+ alpha(1f)
+ }.start()
+ }
+
+ fun hideView(view: View, length: Long = 300) {
+ if (view.visibility == View.INVISIBLE) {
+ return
+ }
+
+ view.apply {
+ alpha = 1f
+ isClickable = false
+ }.animate().apply {
+ duration = length
+ alpha(0f)
+ }.withEndAction {
+ view.visibility = View.INVISIBLE
+ }.start()
+ }
+}
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
import android.util.AttributeSet
import android.util.Rational
import android.view.SurfaceView
-import kotlin.math.roundToInt
class FixedRatioSurfaceView @JvmOverloads constructor(
context: Context,
@@ -22,27 +21,44 @@ class FixedRatioSurfaceView @JvmOverloads constructor(
*/
fun setAspectRatio(ratio: Rational?) {
aspectRatio = ratio?.toFloat() ?: 0f
+ requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- val width = MeasureSpec.getSize(widthMeasureSpec)
- val height = MeasureSpec.getSize(heightMeasureSpec)
+ val displayWidth: Float = MeasureSpec.getSize(widthMeasureSpec).toFloat()
+ val displayHeight: Float = MeasureSpec.getSize(heightMeasureSpec).toFloat()
if (aspectRatio != 0f) {
- val newWidth: Int
- val newHeight: Int
- if (height * aspectRatio < width) {
- newWidth = (height * aspectRatio).roundToInt()
- newHeight = height
+ val displayAspect = displayWidth / displayHeight
+ if (displayAspect < aspectRatio) {
+ // Max out width
+ val halfHeight = displayHeight / 2
+ val surfaceHeight = displayWidth / aspectRatio
+ val newTop: Float = halfHeight - (surfaceHeight / 2)
+ val newBottom: Float = halfHeight + (surfaceHeight / 2)
+ super.onMeasure(
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(
+ newBottom.toInt() - newTop.toInt(),
+ MeasureSpec.EXACTLY
+ )
+ )
+ return
} else {
- newWidth = width
- newHeight = (width / aspectRatio).roundToInt()
+ // Max out height
+ val halfWidth = displayWidth / 2
+ val surfaceWidth = displayHeight * aspectRatio
+ val newLeft: Float = halfWidth - (surfaceWidth / 2)
+ val newRight: Float = halfWidth + (surfaceWidth / 2)
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(
+ newRight.toInt() - newLeft.toInt(),
+ MeasureSpec.EXACTLY
+ ),
+ heightMeasureSpec
+ )
+ return
}
- val left = (width - newWidth) / 2
- val top = (height - newHeight) / 2
- setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
- } else {
- setLeftTopRightBottom(0, 0, width, height)
}
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
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
id_cache.cpp
id_cache.h
native.cpp
+ native_config.cpp
+ uisettings.cpp
)
set_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 43e8aa72a..81120ab0f 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -11,22 +11,25 @@
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/hle/service/acc/profile_manager.h"
#include "input_common/main.h"
#include "jni/config.h"
#include "jni/default_ini.h"
+#include "uisettings.h"
namespace FS = Common::FS;
-Config::Config(std::optional<std::filesystem::path> config_path)
- : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")},
- config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} {
- Reload();
+Config::Config(const std::string& config_name, ConfigType config_type)
+ : type(config_type), global{config_type == ConfigType::GlobalConfig} {
+ Initialize(config_name);
}
Config::~Config() = default;
bool Config::LoadINI(const std::string& default_contents, bool retry) {
+ void(FS::CreateParentDir(config_loc));
+ config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
const auto config_loc_str = FS::PathToUTF8String(config_loc);
if (config->ParseError() < 0) {
if (retry) {
@@ -144,21 +147,25 @@ void Config::ReadValues() {
Service::Account::MAX_USERS - 1);
// Disable docked mode by default on Android
- Settings::values.use_docked_mode = config->GetBoolean("System", "use_docked_mode", false);
+ Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false)
+ ? Settings::ConsoleMode::Docked
+ : Settings::ConsoleMode::Handheld);
const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
if (rng_seed_enabled) {
Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0));
} else {
- Settings::values.rng_seed.SetValue(std::nullopt);
+ Settings::values.rng_seed.SetValue(0);
}
+ Settings::values.rng_seed_enabled.SetValue(rng_seed_enabled);
const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false);
if (custom_rtc_enabled) {
Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0);
} else {
- Settings::values.custom_rtc = std::nullopt;
+ Settings::values.custom_rtc = 0;
}
+ Settings::values.custom_rtc_enabled = custom_rtc_enabled;
ReadSetting("System", Settings::values.language_index);
ReadSetting("System", Settings::values.region_index);
@@ -167,7 +174,7 @@ void Config::ReadValues() {
// Core
ReadSetting("Core", Settings::values.use_multi_core);
- ReadSetting("Core", Settings::values.use_unsafe_extended_memory_layout);
+ ReadSetting("Core", Settings::values.memory_layout_mode);
// Cpu
ReadSetting("Cpu", Settings::values.cpu_accuracy);
@@ -222,14 +229,17 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.bg_blue);
// Use GPU accuracy normal by default on Android
- Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(config->GetInteger(
- "Renderer", "gpu_accuracy", static_cast<u32>(Settings::GPUAccuracy::Normal)));
+ Settings::values.gpu_accuracy = static_cast<Settings::GpuAccuracy>(config->GetInteger(
+ "Renderer", "gpu_accuracy", static_cast<u32>(Settings::GpuAccuracy::Normal)));
// Use GPU default anisotropic filtering on Android
- Settings::values.max_anisotropy = config->GetInteger("Renderer", "max_anisotropy", 1);
+ Settings::values.max_anisotropy =
+ static_cast<Settings::AnisotropyMode>(config->GetInteger("Renderer", "max_anisotropy", 1));
// Disable ASTC compute by default on Android
- Settings::values.accelerate_astc = config->GetBoolean("Renderer", "accelerate_astc", false);
+ Settings::values.accelerate_astc.SetValue(
+ config->GetBoolean("Renderer", "accelerate_astc", false) ? Settings::AstcDecodeMode::Gpu
+ : Settings::AstcDecodeMode::Cpu);
// Enable asynchronous presentation by default on Android
Settings::values.async_presentation =
@@ -272,7 +282,7 @@ void Config::ReadValues() {
std::stringstream ss(title_list);
std::string line;
while (std::getline(ss, line, '|')) {
- const auto title_id = std::stoul(line, nullptr, 16);
+ const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
std::stringstream inner_ss(disabled_list);
@@ -293,9 +303,28 @@ void Config::ReadValues() {
// Network
ReadSetting("Network", Settings::values.network_interface);
+
+ // Android
+ ReadSetting("Android", AndroidSettings::values.picture_in_picture);
+ ReadSetting("Android", AndroidSettings::values.screen_layout);
}
-void Config::Reload() {
+void Config::Initialize(const std::string& config_name) {
+ const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
+ const auto config_file = fmt::format("{}.ini", config_name);
+
+ switch (type) {
+ case ConfigType::GlobalConfig:
+ config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
+ break;
+ case ConfigType::PerGameConfig:
+ config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
+ break;
+ case ConfigType::InputProfile:
+ config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
+ LoadINI(DefaultINI::android_config_file);
+ return;
+ }
LoadINI(DefaultINI::android_config_file);
ReadValues();
}
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 @@
class INIReader;
class Config {
- std::filesystem::path config_loc;
- std::unique_ptr<INIReader> config;
-
bool LoadINI(const std::string& default_contents = "", bool retry = true);
- void ReadValues();
public:
- explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt);
+ enum class ConfigType {
+ GlobalConfig,
+ PerGameConfig,
+ InputProfile,
+ };
+
+ explicit Config(const std::string& config_name = "config",
+ ConfigType config_type = ConfigType::GlobalConfig);
~Config();
- void Reload();
+ void Initialize(const std::string& config_name);
private:
/**
- * Applies a value read from the sdl2_config to a Setting.
+ * Applies a value read from the config to a Setting.
*
* @param group The name of the INI group
* @param setting The yuzu setting to modify
*/
template <typename Type, bool ranged>
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
+
+ void ReadValues();
+
+ const ConfigType type;
+ std::unique_ptr<INIReader> config;
+ std::string config_loc;
+ const bool global;
};
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp
index a890c6604..a7e414b81 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.cpp
+++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp
@@ -11,6 +11,12 @@
#include "jni/emu_window/emu_window.h"
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
+ m_window_width = ANativeWindow_getWidth(surface);
+ m_window_height = ANativeWindow_getHeight(surface);
+
+ // Ensures that we emulate with the correct aspect ratio.
+ UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
+
window_info.render_surface = reinterpret_cast<void*>(surface);
}
@@ -62,14 +68,8 @@ EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsyste
return;
}
- m_window_width = ANativeWindow_getWidth(surface);
- m_window_height = ANativeWindow_getHeight(surface);
-
- // Ensures that we emulate with the correct aspect ratio.
- UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
-
+ OnSurfaceChanged(surface);
window_info.type = Core::Frontend::WindowSystemType::Android;
- window_info.render_surface = reinterpret_cast<void*>(surface);
m_input_subsystem->Initialize();
}
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;
static jclass s_load_callback_stage_class;
static jmethodID s_exit_emulation_activity;
static jmethodID s_disk_cache_load_progress;
+static jmethodID s_on_emulation_started;
+static jmethodID s_on_emulation_stopped;
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
@@ -59,6 +61,14 @@ jmethodID GetDiskCacheLoadProgress() {
return s_disk_cache_load_progress;
}
+jmethodID GetOnEmulationStarted() {
+ return s_on_emulation_started;
+}
+
+jmethodID GetOnEmulationStopped() {
+ return s_on_emulation_stopped;
+}
+
} // namespace IDCache
#ifdef __cplusplus
@@ -85,6 +95,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
s_disk_cache_load_progress =
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
+ s_on_emulation_started =
+ env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
+ s_on_emulation_stopped =
+ env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
// Initialize Android Storage
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();
jclass GetDiskCacheLoadCallbackStageClass();
jmethodID GetExitEmulationActivity();
jmethodID GetDiskCacheLoadProgress();
+jmethodID GetOnEmulationStarted();
+jmethodID GetOnEmulationStopped();
} // 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 @@
#include "core/cpu_manager.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
+#include "core/file_sys/content_archive.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs.h"
@@ -202,12 +203,10 @@ public:
}
bool IsRunning() const {
- std::scoped_lock lock(m_mutex);
return m_is_running;
}
bool IsPaused() const {
- std::scoped_lock lock(m_mutex);
return m_is_running && m_is_paused;
}
@@ -224,17 +223,51 @@ public:
m_system.Renderer().NotifySurfaceChanged();
}
+ void ConfigureFilesystemProvider(const std::string& filepath) {
+ const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
+ if (!file) {
+ return;
+ }
+
+ auto loader = Loader::GetLoader(m_system, file);
+ if (!loader) {
+ return;
+ }
+
+ const auto file_type = loader->GetFileType();
+ if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
+ return;
+ }
+
+ u64 program_id = 0;
+ const auto res2 = loader->ReadProgramId(program_id);
+ if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
+ m_manual_provider->AddEntry(FileSys::TitleType::Application,
+ FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
+ program_id, file);
+ } else if (res2 == Loader::ResultStatus::Success &&
+ (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
+ const auto nsp = file_type == Loader::FileType::NSP
+ ? std::make_shared<FileSys::NSP>(file)
+ : FileSys::XCI{file}.GetSecurePartitionNSP();
+ for (const auto& title : nsp->GetNCAs()) {
+ for (const auto& entry : title.second) {
+ m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
+ entry.second->GetBaseFile());
+ }
+ }
+ }
+ }
+
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
std::scoped_lock lock(m_mutex);
- // Loads the configuration.
- Config{};
-
// Create the render window.
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
m_vulkan_library);
m_system.SetFilesystem(m_vfs);
+ m_system.GetUserChannel().clear();
// Initialize system.
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
@@ -254,8 +287,14 @@ public:
std::move(android_keyboard), // Software Keyboard
nullptr, // Web Browser
});
+
+ // Initialize filesystem.
+ m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
+ m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
+ m_manual_provider.get());
m_system.GetFileSystemController().CreateFactories(*m_vfs);
+ ConfigureFilesystemProvider(filepath);
// Initialize account manager
m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
@@ -288,6 +327,9 @@ public:
m_system.ShutdownMainProcess();
m_detached_tasks.WaitForAllTasks();
m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
+ m_window.reset();
+ OnEmulationStopped(Core::SystemResultStatus::Success);
+ return;
}
// Tear down the render window.
@@ -333,6 +375,8 @@ public:
m_system.InitializeDebugger();
}
+ OnEmulationStarted();
+
while (true) {
{
[[maybe_unused]] std::unique_lock lock(m_mutex);
@@ -377,7 +421,7 @@ public:
return false;
}
- return !Settings::values.use_docked_mode.GetValue();
+ return !Settings::IsDockedMode();
}
void SetDeviceType([[maybe_unused]] int index, int type) {
@@ -468,6 +512,18 @@ private:
static_cast<jint>(progress), static_cast<jint>(max));
}
+ static void OnEmulationStarted() {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
+ IDCache::GetOnEmulationStarted());
+ }
+
+ static void OnEmulationStopped(Core::SystemResultStatus result) {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
+ IDCache::GetOnEmulationStopped(), static_cast<jint>(result));
+ }
+
private:
static EmulationSession s_instance;
@@ -485,10 +541,11 @@ private:
Core::PerfStatsResults m_perf_stats{};
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
- bool m_is_running{};
- bool m_is_paused{};
+ std::atomic<bool> m_is_running = false;
+ std::atomic<bool> m_is_paused = false;
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
+ std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
// GPU driver parameters
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
@@ -613,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
}
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
- Settings::values.audio_muted = true;
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
- Settings::values.audio_muted = false;
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
- return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
-}
-
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
return EmulationSession::GetInstance().IsHandheldOnly();
}
@@ -780,34 +825,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
Config{};
}
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
- jstring j_game_id, jstring j_section,
- jstring j_key) {
- std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
- std::string_view section = env->GetStringUTFChars(j_section, 0);
- std::string_view key = env->GetStringUTFChars(j_key, 0);
-
- env->ReleaseStringUTFChars(j_game_id, game_id.data());
- env->ReleaseStringUTFChars(j_section, section.data());
- env->ReleaseStringUTFChars(j_key, key.data());
-
- return env->NewStringUTF("");
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
- jstring j_game_id, jstring j_section,
- jstring j_key, jstring j_value) {
- std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
- std::string_view section = env->GetStringUTFChars(j_section, 0);
- std::string_view key = env->GetStringUTFChars(j_key, 0);
- std::string_view value = env->GetStringUTFChars(j_value, 0);
-
- env->ReleaseStringUTFChars(j_game_id, game_id.data());
- env->ReleaseStringUTFChars(j_section, section.data());
- env->ReleaseStringUTFChars(j_key, key.data());
- env->ReleaseStringUTFChars(j_value, value.data());
-}
-
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
jstring j_game_id) {
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <string>
+
+#include <jni.h>
+
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "jni/android_common/android_common.h"
+#include "jni/config.h"
+#include "uisettings.h"
+
+template <typename T>
+Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
+ auto key = GetJString(env, jkey);
+ auto basicSetting = Settings::values.linkage.by_key[key];
+ auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
+ if (basicSetting != 0) {
+ return static_cast<Settings::Setting<T>*>(basicSetting);
+ }
+ if (basicAndroidSetting != 0) {
+ return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
+ }
+ LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
+ return nullptr;
+}
+
+extern "C" {
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
+ jstring jkey, jboolean getDefault) {
+ auto setting = getSetting<bool>(env, jkey);
+ if (setting == nullptr) {
+ return false;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean value) {
+ auto setting = getSetting<bool>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(static_cast<bool>(value));
+}
+
+jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<u8>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey,
+ jbyte value) {
+ auto setting = getSetting<u8>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<u16>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey,
+ jshort value) {
+ auto setting = getSetting<u16>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<int>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey,
+ jint value) {
+ auto setting = getSetting<int>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<float>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey,
+ jfloat value) {
+ auto setting = getSetting<float>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<long>(env, jkey);
+ if (setting == nullptr) {
+ return -1;
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return setting->GetDefault();
+ }
+
+ return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey,
+ jlong value) {
+ auto setting = getSetting<long>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+ setting->SetGlobal(true);
+ setting->SetValue(value);
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey,
+ jboolean getDefault) {
+ auto setting = getSetting<std::string>(env, jkey);
+ if (setting == nullptr) {
+ return ToJString(env, "");
+ }
+ setting->SetGlobal(true);
+
+ if (static_cast<bool>(getDefault)) {
+ return ToJString(env, setting->GetDefault());
+ }
+
+ return ToJString(env, setting->GetValue());
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
+ jstring value) {
+ auto setting = getSetting<std::string>(env, jkey);
+ if (setting == nullptr) {
+ return;
+ }
+
+ setting->SetGlobal(true);
+ setting->SetValue(GetJString(env, value));
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
+ jstring jkey) {
+ auto key = GetJString(env, jkey);
+ auto setting = Settings::values.linkage.by_key[key];
+ if (setting != 0) {
+ return setting->RuntimeModfiable();
+ }
+ LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
+ return true;
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
+ jint jcategory) {
+ auto category = static_cast<Settings::Category>(jcategory);
+ return ToJString(env, Settings::TranslateCategory(category));
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj,
+ jstring jkey) {
+ auto setting = getSetting<std::string>(env, jkey);
+ if (setting == nullptr) {
+ return ToJString(env, "");
+ }
+ if (setting->PairedSetting() == nullptr) {
+ return ToJString(env, "");
+ }
+
+ return ToJString(env, setting->PairedSetting()->GetLabel());
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "uisettings.h"
+
+namespace AndroidSettings {
+
+Values values;
+
+} // 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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <common/settings_common.h>
+#include "common/common_types.h"
+#include "common/settings_setting.h"
+
+namespace AndroidSettings {
+
+struct Values {
+ Settings::Linkage linkage;
+
+ // Android
+ Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
+ Settings::Category::Android};
+ Settings::Setting<s32> screen_layout{linkage,
+ 5,
+ "screen_layout",
+ Settings::Category::Android,
+ Settings::Specialization::Default,
+ true,
+ true};
+};
+
+extern Values values;
+
+} // 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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="125"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="1"
- android:toAlpha="0" />
-
- <translate
- android:duration="125"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromXDelta="0"
- android:toXDelta="-75" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="0"
- android:toAlpha="1" />
-
- <translate
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromXDelta="-200"
- android:toXDelta="0" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="125"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="1"
- android:toAlpha="0" />
-
- <translate
- android:duration="125"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromXDelta="0"
- android:toXDelta="75" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="0"
- android:toAlpha="1" />
-
- <translate
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromXDelta="200"
- android:toXDelta="0" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <alpha
- android:duration="@android:integer/config_shortAnimTime"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="1"
- android:toAlpha="0" />
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <objectAnimator
- android:propertyName="translationX"
- android:valueType="floatType"
- android:valueFrom="-1280dp"
- android:valueTo="0"
- android:interpolator="@android:interpolator/decelerate_quad"
- android:duration="300"/>
-
- <objectAnimator
- android:propertyName="alpha"
- android:valueType="floatType"
- android:valueFrom="0"
- android:valueTo="1"
- android:interpolator="@android:interpolator/accelerate_quad"
- android:duration="300"/>
-
-</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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- This animation is used ONLY when a submenu is replaced. -->
- <objectAnimator
- android:propertyName="translationX"
- android:valueType="floatType"
- android:valueFrom="0"
- android:valueTo="-1280dp"
- android:interpolator="@android:interpolator/decelerate_quad"
- android:duration="200"/>
-
- <objectAnimator
- android:propertyName="alpha"
- android:valueType="floatType"
- android:valueFrom="1"
- android:valueTo="0"
- android:interpolator="@android:interpolator/decelerate_quad"
- android:duration="200"/>
-
-</set> \ No newline at end of file
diff --git a/src/android/app/src/main/res/drawable/ic_export.xml b/src/android/app/src/main/res/drawable/ic_export.xml
new file mode 100644
index 000000000..463d2f41c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_export.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_import.xml b/src/android/app/src/main/res/drawable/ic_import.xml
new file mode 100644
index 000000000..3a99dd5e6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_import.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
+</vector>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item>
+ <color android:color="@android:color/white" />
+ </item>
+ <item android:id="@+id/shortcut_foreground">
+ <bitmap android:src="@drawable/default_icon" />
+ </item>
+
+</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 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout
+<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_root"
@@ -8,33 +8,39 @@
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentBottom="true"
+ android:clipToPadding="false" />
- <com.google.android.material.button.MaterialButton
- style="@style/Widget.Material3.Button.TextButton"
- android:id="@+id/button_next"
- android:layout_width="wrap_content"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/constraint_buttons"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="16dp"
- android:text="@string/next"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent" />
+ android:layout_alignParentBottom="true"
+ android:layout_margin="8dp">
- <com.google.android.material.button.MaterialButton
- android:id="@+id/button_back"
- style="@style/Widget.Material3.Button.TextButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="16dp"
- android:text="@string/back"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent" />
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button_next"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/next"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button_back"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
-</androidx.constraintlayout.widget.ConstraintLayout>
+</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 @@
</LinearLayout>
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="vertical"
- android:gravity="center">
+ android:layout_weight="1">
<com.google.android.material.textview.MaterialTextView
- style="@style/TextAppearance.Material3.DisplaySmall"
android:id="@+id/text_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="center"
+ style="@style/TextAppearance.Material3.DisplaySmall"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:gravity="center"
android:textColor="?attr/colorOnSurface"
android:textStyle="bold"
+ app:layout_constraintBottom_toTopOf="@+id/text_description"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_weight="2"
tools:text="@string/welcome" />
<com.google.android.material.textview.MaterialTextView
- style="@style/TextAppearance.Material3.TitleLarge"
android:id="@+id/text_description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:paddingHorizontal="32dp"
- android:textAlignment="center"
- android:textSize="26sp"
- app:lineHeight="40sp"
+ style="@style/TextAppearance.Material3.TitleLarge"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:gravity="center"
+ android:textSize="20sp"
+ android:paddingHorizontal="16dp"
+ app:layout_constraintBottom_toTopOf="@+id/button_action"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/text_title"
+ app:layout_constraintVertical_weight="2"
+ app:lineHeight="30sp"
tools:text="@string/welcome_description" />
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_confirmation"
+ style="@style/TextAppearance.Material3.TitleLarge"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:paddingHorizontal="16dp"
+ android:paddingBottom="20dp"
+ android:gravity="center"
+ android:textSize="30sp"
+ android:visibility="invisible"
+ android:text="@string/step_complete"
+ android:textStyle="bold"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/text_description"
+ app:layout_constraintVertical_weight="1"
+ app:lineHeight="30sp" />
+
<com.google.android.material.button.MaterialButton
android:id="@+id/button_action"
android:layout_width="wrap_content"
android:layout_height="56dp"
- android:layout_marginTop="32dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="48dp"
android:textSize="20sp"
- app:iconSize="24sp"
app:iconGravity="end"
+ app:iconSize="24sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/text_description"
tools:text="Get started" />
- </LinearLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
</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 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout
- android:id="@+id/coordinator_main"
+<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/constraint_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
- <com.google.android.material.appbar.AppBarLayout
- android:id="@+id/appbar_settings"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fitsSystemWindows="true"
- app:elevation="0dp">
-
- <com.google.android.material.appbar.CollapsingToolbarLayout
- style="?attr/collapsingToolbarLayoutMediumStyle"
- android:id="@+id/toolbar_settings_layout"
- android:layout_width="match_parent"
- android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
- app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
-
- <com.google.android.material.appbar.MaterialToolbar
- android:id="@+id/toolbar_settings"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- app:layout_collapseMode="pin" />
-
- </com.google.android.material.appbar.CollapsingToolbarLayout>
-
- </com.google.android.material.appbar.AppBarLayout>
-
- <FrameLayout
- android:id="@+id/frame_content"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginHorizontal="12dp"
- app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+ <androidx.fragment.app.FragmentContainerView
+ android:id="@+id/fragment_container"
+ android:name="androidx.navigation.fragment.NavHostFragment"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:defaultNavHost="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:layout="@layout/fragment_settings" />
<View
android:id="@+id/navigation_bar_shade"
@@ -45,6 +27,8 @@
android:background="@android:color/transparent"
android:clickable="false"
android:focusable="false"
- android:layout_gravity="bottom|center_horizontal" />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+</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 @@
android:layout_marginTop="5dp"
tools:text="@string/install_prod_keys_description" />
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.LabelMedium"
+ android:id="@+id/option_detail"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:ellipsize="none"
+ android:requiresFadingEdge="horizontal"
+ android:layout_marginTop="5dp"
+ android:visibility="gone"
+ tools:visibility="visible"
+ tools:text="/tree/primary:Games" />
+
</LinearLayout>
</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_progress_bar.xml b/src/android/app/src/main/res/layout/dialog_progress_bar.xml
index d17711a65..0209ea082 100644
--- a/src/android/app/src/main/res/layout/dialog_progress_bar.xml
+++ b/src/android/app/src/main/res/layout/dialog_progress_bar.xml
@@ -1,24 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+<com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:orientation="vertical">
-
- <com.google.android.material.progressindicator.LinearProgressIndicator
- android:id="@+id/progress_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="24dp"
- app:trackCornerRadius="4dp" />
-
- <TextView
- android:id="@+id/progress_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="24dp"
- android:layout_marginRight="24dp"
- android:layout_marginBottom="24dp"
- android:gravity="end" />
-
-</LinearLayout>
+ android:id="@+id/progress_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ app:trackCornerRadius="4dp" />
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 @@
android:layout_height="wrap_content"
android:orientation="vertical">
- <TextView
+ <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_value"
+ style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/spacing_medlarge"
android:layout_marginTop="@dimen/spacing_medlarge"
- tools:text="75" />
-
- <TextView
- android:id="@+id/text_units"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignTop="@+id/text_value"
- android:layout_toEndOf="@+id/text_value"
- tools:text="%" />
+ tools:text="75%" />
<com.google.android.material.slider.Slider
android:id="@+id/slider"
diff --git a/src/android/app/src/main/res/layout/fragment_about.xml b/src/android/app/src/main/res/layout/fragment_about.xml
index 3e1d98451..36b350338 100644
--- a/src/android/app/src/main/res/layout/fragment_about.xml
+++ b/src/android/app/src/main/res/layout/fragment_about.xml
@@ -184,6 +184,67 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingVertical="16dp"
+ android:paddingHorizontal="16dp"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:textAlignment="viewStart"
+ android:text="@string/user_data" />
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:textAlignment="viewStart"
+ android:text="@string/user_data_description" />
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/button_import"
+ style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/string_import"
+ android:tooltipText="@string/string_import"
+ app:icon="@drawable/ic_import" />
+
+ <Button
+ android:id="@+id/button_export"
+ style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="24dp"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/export"
+ android:tooltipText="@string/export"
+ app:icon="@drawable/ic_export" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal"
android:layout_marginTop="12dp"
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 @@
android:focusable="false"
android:focusableInTouchMode="false" />
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/loading_indicator"
+ style="?attr/materialCardViewOutlinedStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:focusable="false">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/loading_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal">
+
+ <ImageView
+ android:id="@+id/loading_image"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/linearLayout"
+ tools:src="@drawable/default_icon" />
+
+ <LinearLayout
+ android:id="@+id/linearLayout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingHorizontal="24dp"
+ android:paddingVertical="36dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/loading_image"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/loading_title"
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="@string/games" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/loading_text"
+ style="@style/TextAppearance.Material3.TitleSmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:text="@string/loading"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.progressindicator.LinearProgressIndicator
+ android:id="@+id/loading_progress_indicator"
+ android:layout_width="192dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:indeterminate="true"
+ app:trackCornerRadius="8dp" />
+
+ </LinearLayout>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ </com.google.android.material.card.MaterialCardView>
+
</FrameLayout>
<FrameLayout
@@ -41,11 +116,12 @@
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="true"
- android:focusableInTouchMode="true" />
+ android:focusableInTouchMode="true"
+ android:visibility="invisible" />
<Button
- style="@style/Widget.Material3.Button.ElevatedButton"
android:id="@+id/done_control_config"
+ style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@@ -81,6 +157,7 @@
android:layout_height="match_parent"
android:layout_gravity="start|bottom"
app:headerLayout="@layout/header_in_game"
- app:menu="@menu/menu_in_game" />
+ app:menu="@menu/menu_in_game"
+ tools:visibility="gone" />
</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 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/coordinator_main"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_settings"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ app:elevation="0dp">
+
+ <com.google.android.material.appbar.CollapsingToolbarLayout
+ android:id="@+id/toolbar_settings_layout"
+ style="?attr/collapsingToolbarLayoutMediumStyle"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_settings"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:layout_collapseMode="pin"
+ app:navigationIcon="@drawable/ic_back" />
+
+ </com.google.android.material.appbar.CollapsingToolbarLayout>
+
+ </com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?attr/colorSurface"
- android:clipToPadding="false" />
+ android:clipToPadding="false"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
-</FrameLayout>
+</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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <RelativeLayout
+ android:id="@+id/relativeLayout"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/divider">
+
+ <LinearLayout
+ android:id="@+id/no_results_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/icon_no_results"
+ android:layout_width="match_parent"
+ android:layout_height="80dp"
+ android:src="@drawable/ic_search" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/notice_text"
+ style="@style/TextAppearance.Material3.TitleLarge"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingTop="8dp"
+ android:text="@string/search_settings"
+ tools:visibility="visible" />
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/settings_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false" />
+
+ </RelativeLayout>
+
+ <FrameLayout
+ android:id="@+id/frame_search"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/search_background"
+ style="?attr/materialCardViewFilledStyle"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ app:cardCornerRadius="28dp">
+
+ <LinearLayout
+ android:id="@+id/search_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="56dp"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/back_button"
+ style="?attr/materialIconButtonFilledTonalStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="8dp"
+ app:backgroundTint="@android:color/transparent"
+ app:icon="@drawable/ic_back" />
+
+ <EditText
+ android:id="@+id/search_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:hint="@string/search_settings"
+ android:imeOptions="flagNoFullscreen"
+ android:inputType="text"
+ android:maxLines="1" />
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/clear_button"
+ style="?attr/materialIconButtonFilledTonalStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginEnd="8dp"
+ android:visibility="invisible"
+ app:backgroundTint="@android:color/transparent"
+ app:icon="@drawable/ic_clear"
+ tools:visibility="visible" />
+
+ </com.google.android.material.card.MaterialCardView>
+
+ </FrameLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/frame_search" />
+
+</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 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout
+<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_root"
@@ -8,35 +8,39 @@
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:clipToPadding="false"
- android:layout_marginBottom="16dp"
- app:layout_constraintBottom_toTopOf="@+id/button_next"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <com.google.android.material.button.MaterialButton
- style="@style/Widget.Material3.Button.TextButton"
- android:id="@+id/button_next"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="12dp"
- android:text="@string/next"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent" />
+ android:layout_above="@+id/constraint_buttons"
+ android:layout_alignParentTop="true"
+ android:clipToPadding="false" />
- <com.google.android.material.button.MaterialButton
- style="@style/Widget.Material3.Button.TextButton"
- android:id="@+id/button_back"
- android:layout_width="wrap_content"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/constraint_buttons"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="12dp"
- android:text="@string/back"
- android:visibility="invisible"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent" />
+ android:layout_margin="8dp"
+ android:layout_alignParentBottom="true">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button_next"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/next"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button_back"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
-</androidx.constraintlayout.widget.ConstraintLayout>
+</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 @@
<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
@@ -11,31 +12,40 @@
android:minHeight="72dp"
android:padding="@dimen/spacing_large">
- <com.google.android.material.textview.MaterialTextView
- style="@style/TextAppearance.Material3.HeadlineMedium"
- android:id="@+id/text_setting_name"
- android:layout_width="0dp"
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_alignParentStart="true"
- android:layout_alignParentTop="true"
- android:textSize="16sp"
- android:textAlignment="viewStart"
- app:lineHeight="28dp"
- tools:text="Setting Name" />
+ android:orientation="vertical">
- <TextView
- style="@style/TextAppearance.Material3.BodySmall"
- android:id="@+id/text_setting_description"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_alignParentStart="true"
- android:layout_alignStart="@+id/text_setting_name"
- android:layout_below="@+id/text_setting_name"
- android:layout_marginTop="@dimen/spacing_small"
- android:visibility="visible"
- android:textAlignment="viewStart"
- tools:text="@string/app_disclaimer" />
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_name"
+ style="@style/TextAppearance.Material3.HeadlineMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="16sp"
+ app:lineHeight="22dp"
+ tools:text="Setting Name" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_description"
+ style="@style/TextAppearance.Material3.BodySmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ tools:text="@string/app_disclaimer" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_value"
+ style="@style/TextAppearance.Material3.LabelMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ android:textStyle="bold"
+ tools:text="1x" />
+
+ </LinearLayout>
</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 @@
app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintWidth_max="220dp"
app:layout_constraintWidth_min="110dp"
- app:layout_constraintVertical_weight="3" />
+ app:layout_constraintVertical_weight="3"
+ tools:src="@drawable/ic_notification" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_title"
- style="@style/TextAppearance.Material3.DisplayMedium"
+ style="@style/TextAppearance.Material3.DisplaySmall"
android:layout_width="0dp"
android:layout_height="0dp"
android:textAlignment="center"
@@ -44,23 +45,42 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:textAlignment="center"
- android:textSize="26sp"
+ android:textSize="20sp"
android:paddingHorizontal="16dp"
app:layout_constraintBottom_toTopOf="@+id/button_action"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title"
app:layout_constraintVertical_weight="2"
- app:lineHeight="40sp"
+ app:lineHeight="30sp"
tools:text="@string/welcome_description" />
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_confirmation"
+ style="@style/TextAppearance.Material3.TitleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:paddingHorizontal="16dp"
+ android:paddingTop="24dp"
+ android:textAlignment="center"
+ android:textSize="30sp"
+ android:visibility="invisible"
+ android:text="@string/step_complete"
+ android:textStyle="bold"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/text_description"
+ app:layout_constraintVertical_weight="1"
+ app:lineHeight="30sp" />
+
<com.google.android.material.button.MaterialButton
android:id="@+id/button_action"
android:layout_width="wrap_content"
android:layout_height="56dp"
- android:textSize="20sp"
android:layout_marginTop="16dp"
android:layout_marginBottom="48dp"
+ android:textSize="20sp"
app:iconGravity="end"
app:iconSize="24sp"
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 @@
<?xml version="1.0" encoding="utf-8"?>
-<menu /> \ No newline at end of file
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/action_search"
+ android:icon="@drawable/ic_search"
+ android:title="@string/home_search"
+ app:showAsAction="always" />
+
+</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 @@
tools:layout="@layout/fragment_emulation" >
<argument
android:name="game"
- app:argType="org.yuzu.yuzu_emu.model.Game" />
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true"
+ android:defaultValue="@null" />
</fragment>
+ <activity
+ android:id="@+id/settingsActivity"
+ android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
+ android:label="SettingsActivity">
+ <argument
+ android:name="game"
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true" />
+ <argument
+ android:name="menuTag"
+ app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
+ </activity>
+
+ <action
+ android:id="@+id/action_global_settingsActivity"
+ app:destination="@id/settingsActivity" />
+
</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 @@
android:label="EmulationActivity">
<argument
android:name="game"
- app:argType="org.yuzu.yuzu_emu.model.Game" />
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true"
+ android:defaultValue="@null" />
</activity>
<action
@@ -70,4 +72,21 @@
app:destination="@id/emulationActivity"
app:launchSingleTop="true" />
+ <activity
+ android:id="@+id/settingsActivity"
+ android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
+ android:label="SettingsActivity">
+ <argument
+ android:name="game"
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true" />
+ <argument
+ android:name="menuTag"
+ app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
+ </activity>
+
+ <action
+ android:id="@+id/action_global_settingsActivity"
+ app:destination="@id/settingsActivity" />
+
</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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/settings_navigation"
+ app:startDestination="@id/settingsFragment">
+
+ <fragment
+ android:id="@+id/settingsFragment"
+ android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment"
+ android:label="SettingsFragment">
+ <argument
+ android:name="menuTag"
+ app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
+ <argument
+ android:name="game"
+ app:argType="org.yuzu.yuzu_emu.model.Game"
+ app:nullable="true" />
+ <action
+ android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
+ app:destination="@id/settingsSearchFragment" />
+ </fragment>
+
+ <action
+ android:id="@+id/action_global_settingsFragment"
+ app:destination="@id/settingsFragment" />
+
+ <fragment
+ android:id="@+id/settingsSearchFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
+ android:label="SettingsSearchFragment" />
+
+</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 969223ef8..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 @@
<string name="emulation_pause">Emulation pausieren</string>
<string name="emulation_unpause">Emulation fortsetzen</string>
<string name="emulation_input_overlay">Overlay-Optionen</string>
- <string name="emulation_game_loading">Spiel lädt…</string>
<string name="load_settings">Lädt Einstellungen...</string>
@@ -235,26 +234,6 @@
<string name="region_korea">Korea</string>
<string name="region_taiwan">Taiwan</string>
- <!-- Language Names -->
- <string name="language_japanese">Japanisch (日本語)</string>
- <string name="language_english">Englisch</string>
- <string name="language_french">Französisch (Français)</string>
- <string name="langauge_german">Deutsch (German)</string>
- <string name="language_italian">Italienisch (Italiano)</string>
- <string name="language_spanish">Spanisch (Español)</string>
- <string name="language_chinese">Chinesisch (简体中文)</string>
- <string name="language_korean">Koreanisch (한국어)</string>
- <string name="language_dutch">Niederländisch (Nederlands)</string>
- <string name="language_portuguese">Portugiesisch (Português)</string>
- <string name="language_russian">Russisch (Русский)</string>
- <string name="language_taiwanese">Taiwanesisch (台湾)</string>
- <string name="language_british_english">Britisches Englisch</string>
- <string name="language_canadian_french">Kanadisches Französisch (Français canadien)</string>
- <string name="language_latin_american_spanish">Lateinamerikanisches Spanisch (Español latinoamericano)</string>
- <string name="language_simplified_chinese">Vereinfachtes Chinesisch (简体中文)</string>
- <string name="language_traditional_chinese">Traditionelles Chinesisch (正體中文)</string>
- <string name="language_brazilian_portuguese">Brasilianisches Portugiesisch (Português do Brasil)</string>
-
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
<string name="renderer_none">Keiner</string>
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 986e80e50..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 @@
<string name="emulation_pause">Pausar Emulación</string>
<string name="emulation_unpause">Reanudar Emulación</string>
<string name="emulation_input_overlay">Opciones de pantalla </string>
- <string name="emulation_game_loading">Cargando juego...</string>
<string name="load_settings">Cargando configuración...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Taiwán</string>
<!-- Language Names -->
- <string name="language_japanese">Japonés (日本語)</string>
- <string name="language_english">Inglés (English)</string>
- <string name="language_french">Francés (Français)</string>
- <string name="langauge_german">Alemán (deutsch)</string>
- <string name="language_italian">Italiano (Italiano)</string>
- <string name="language_spanish">Español (Español)</string>
- <string name="language_chinese">Chino (简体中文)</string>
- <string name="language_korean">Coreano (한국어)</string>
- <string name="language_dutch">Holandés (nederlands)</string>
- <string name="language_portuguese">Portugués (Português)</string>
- <string name="language_russian">Ruso (Русский)</string>
- <string name="language_taiwanese">Taiwanés (台湾)</string>
- <string name="language_british_english">Inglés británico</string>
- <string name="language_canadian_french">Francés Canadiense (Français canadien)</string>
- <string name="language_latin_american_spanish">Español Latinoamericano (Español latinoamericano)</string>
- <string name="language_simplified_chinese">Chino Simplificado (简体中文)</string>
- <string name="language_traditional_chinese">Chino tradicional (正體中文)</string>
- <string name="language_brazilian_portuguese">Portugués Brasileño (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 14a9b2d5c..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 @@
<string name="emulation_pause">Mettre en pause l\'émulation</string>
<string name="emulation_unpause">Reprendre l\'émulation</string>
<string name="emulation_input_overlay">Options de l\'overlay</string>
- <string name="emulation_game_loading">Chargement du jeu...</string>
<string name="load_settings">Chargement des paramètres…</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Taïwan</string>
<!-- Language Names -->
- <string name="language_japanese">Japonais (日本語)</string>
- <string name="language_english">Anglais</string>
- <string name="language_french">Français (Français)</string>
- <string name="langauge_german">Allemand (Deutsch)</string>
- <string name="language_italian">Italien (Italiano)</string>
- <string name="language_spanish">Espagnol (Español)</string>
- <string name="language_chinese">Chinois (简体中文)</string>
- <string name="language_korean">Coréen (한국어)</string>
- <string name="language_dutch">Néerlandais (Nederlands)</string>
- <string name="language_portuguese">Portugais (Português)</string>
- <string name="language_russian">Russe (Русский)</string>
- <string name="language_taiwanese">Taïwanais (台湾)</string>
- <string name="language_british_english">Anglais Britannique</string>
- <string name="language_canadian_french">Français canadien (Français canadien)</string>
- <string name="language_latin_american_spanish">Espagnol latino-américain (Español latinoamericano)</string>
- <string name="language_simplified_chinese">Chinois simplifié (简体中文)</string>
- <string name="language_traditional_chinese">Chinois Traditionnel (正體中文)</string>
- <string name="language_brazilian_portuguese">Portugais brésilien (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 47a4cfa31..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 @@
<string name="emulation_pause">Metti in pausa l\'emulazione</string>
<string name="emulation_unpause">Riprendi Emulazione</string>
<string name="emulation_input_overlay">Impostazioni Overlay</string>
- <string name="emulation_game_loading">Caricamento del gioco...</string>
<string name="load_settings">Caricamento delle impostazioni...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Taiwan</string>
<!-- Language Names -->
- <string name="language_japanese">Giapponese (日本語)</string>
- <string name="language_english">Inglese (English)</string>
- <string name="language_french">Francese (Français)</string>
- <string name="langauge_german">Tedesco (Deutsch)</string>
- <string name="language_italian">Italiano (Italiano)</string>
- <string name="language_spanish">Spagnolo (Español)</string>
- <string name="language_chinese">Cinese (简体中文)</string>
- <string name="language_korean">Coreano (한국어)</string>
- <string name="language_dutch">Olandese (Nederlands)</string>
- <string name="language_portuguese">Portoghese (Português)</string>
- <string name="language_russian">Russo (Русский)</string>
- <string name="language_taiwanese">Taiwanese (台湾)</string>
- <string name="language_british_english">Inglese britannico</string>
- <string name="language_canadian_french">Francese Canadese (Français canadien)</string>
- <string name="language_latin_american_spanish">Spagnolo Latino Americano (Español latinoamericano)</string>
- <string name="language_simplified_chinese">Cinese Semplificato (简体中文)</string>
- <string name="language_traditional_chinese">Cinese tradizionale (正體中文)</string>
- <string name="language_brazilian_portuguese">Portoghese (Português)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 46eda9ef7..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 @@
<string name="emulation_pause">エミュレーションを一時停止</string>
<string name="emulation_unpause">エミュレーションを再開</string>
<string name="emulation_input_overlay">オーバーレイオプション</string>
- <string name="emulation_game_loading">ロード中…</string>
<string name="load_settings">設定をロード中…</string>
@@ -239,24 +238,6 @@
<string name="region_taiwan">台湾</string>
<!-- Language Names -->
- <string name="language_japanese">日本語</string>
- <string name="language_english">英語</string>
- <string name="language_french">フランス語 (Français)</string>
- <string name="langauge_german">ドイツ語 (Deutsch)</string>
- <string name="language_italian">イタリア語 (Italiano)</string>
- <string name="language_spanish">スペイン語 (Español)</string>
- <string name="language_chinese">中国語 (简体中文)</string>
- <string name="language_korean">韓国語 (한국어)</string>
- <string name="language_dutch">オランダ語 (Nederlands)</string>
- <string name="language_portuguese">ポルトガル語 (Português)</string>
- <string name="language_russian">ロシア語 (Русский)</string>
- <string name="language_taiwanese">台湾語 (台湾)</string>
- <string name="language_british_english">イギリス英語</string>
- <string name="language_canadian_french">フランス語(カナダ) (Français canadien)</string>
- <string name="language_latin_american_spanish">スペイン語(ラテンアメリカ) (Español latinoamericano)</string>
- <string name="language_simplified_chinese">中国語 (简体中文)</string>
- <string name="language_traditional_chinese">繁体字中国語 (正體中文)</string>
- <string name="language_brazilian_portuguese">ポルトガル語(ブラジル) (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 5da80ab4b..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 @@
<string name="emulation_pause">에뮬레이션 일시 중지</string>
<string name="emulation_unpause">에뮬레이션 일시 중지 해제</string>
<string name="emulation_input_overlay">오버레이 옵션</string>
- <string name="emulation_game_loading">게임 불러오기 중...</string>
<string name="load_settings">설정 불러오기 중...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">타이완</string>
<!-- Language Names -->
- <string name="language_japanese">일본어 (日本語)</string>
- <string name="language_english">영어 (English)</string>
- <string name="language_french">프랑스어 (Français)</string>
- <string name="langauge_german">독일어(Deutsch)</string>
- <string name="language_italian">이탈리아어 (Italiano)</string>
- <string name="language_spanish">스페인어 (Español)</string>
- <string name="language_chinese">중국어 (简体中文)</string>
- <string name="language_korean">한국어 (Korean)</string>
- <string name="language_dutch">네덜란드어 (Nederlands)</string>
- <string name="language_portuguese">포르투갈어 (Português)</string>
- <string name="language_russian">러시아어 (Русский)</string>
- <string name="language_taiwanese">대만어 (台湾)</string>
- <string name="language_british_english">영어 (British English)</string>
- <string name="language_canadian_french">캐나다 프랑스어 (Français canadien)</string>
- <string name="language_latin_american_spanish">라틴 아메리카 스페인어 (Español latinoamericano)</string>
- <string name="language_simplified_chinese">중국어 간체 (简体中文)</string>
- <string name="language_traditional_chinese">중국어 번체 (正體中文)</string>
- <string name="language_brazilian_portuguese">브라질 포르투갈어 (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">불칸</string>
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 3e1f9bce5..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 @@
<string name="emulation_pause">Pause Emulering</string>
<string name="emulation_unpause">Opphev pausing av emulering</string>
<string name="emulation_input_overlay">Alternativer for overlegg</string>
- <string name="emulation_game_loading">Spillet lastes inn...</string>
<string name="load_settings">Laster inn innstillinger...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Taiwan</string>
<!-- Language Names -->
- <string name="language_japanese">Japansk (日本語)</string>
- <string name="language_english">Engelsk</string>
- <string name="language_french">Fransk (Français)</string>
- <string name="langauge_german">Tysk (Deutsch)</string>
- <string name="language_italian">Italiensk (Italiano)</string>
- <string name="language_spanish">Spansk (Español)</string>
- <string name="language_chinese">Kinesisk (简体中文)</string>
- <string name="language_korean">Koreansk (한국어)</string>
- <string name="language_dutch">Nederlandsk (Nederlands)</string>
- <string name="language_portuguese">Portugisisk (Português)</string>
- <string name="language_russian">Russisk (Русский)</string>
- <string name="language_taiwanese">Taiwansk (台湾)</string>
- <string name="language_british_english">Britisk Engelsk</string>
- <string name="language_canadian_french">Kanadisk fransk (Français canadien)</string>
- <string name="language_latin_american_spanish">Latinamerikansk spansk (Español latinoamericano)</string>
- <string name="language_simplified_chinese">Forenklet kinesisk (简体中文)</string>
- <string name="language_traditional_chinese">Tradisjonell Kinesisk (正體中文)</string>
- <string name="language_brazilian_portuguese">Brasiliansk portugisisk (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 1cd1a8f87..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 @@
<string name="emulation_pause">Wstrzymaj emulację</string>
<string name="emulation_unpause">Wznów emulację</string>
<string name="emulation_input_overlay">Opcje nakładki</string>
- <string name="emulation_game_loading">Wczytywanie gry...</string>
<string name="load_settings">Wczytywanie ustawień...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Tajwan</string>
<!-- Language Names -->
- <string name="language_japanese">Japoński (日本語)</string>
- <string name="language_english">Angielski</string>
- <string name="language_french">Francuski (Francja)</string>
- <string name="langauge_german">Niemiecki (Niemcy)</string>
- <string name="language_italian">Włoski (Włochy)</string>
- <string name="language_spanish">Hiszpański (Hiszpania)</string>
- <string name="language_chinese">Chiński (简体中文)</string>
- <string name="language_korean">Koreański (한국어)</string>
- <string name="language_dutch">Duński (Holandia)</string>
- <string name="language_portuguese">Portugalski (Portugalia)</string>
- <string name="language_russian">Rosyjski (Русский)</string>
- <string name="language_taiwanese">Tajwański (台湾)</string>
- <string name="language_british_english">Angielski Brytyjski</string>
- <string name="language_canadian_french">Francuski (Kanada)</string>
- <string name="language_latin_american_spanish">Hiszpański (Ameryka Latynoska)</string>
- <string name="language_simplified_chinese">Chiński uproszczony (简体中文)</string>
- <string name="language_traditional_chinese">Chiński tradycyjny (正體中文)</string>
- <string name="language_brazilian_portuguese">Portugalski (Brazylia)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 35197c280..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 @@
<string name="emulation_pause">Pausa emulação</string>
<string name="emulation_unpause">Retomar emulação</string>
<string name="emulation_input_overlay">Opções de sobreposição </string>
- <string name="emulation_game_loading">Jogo a carregar...</string>
<string name="load_settings">Configurações a carregar...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Taiwan</string>
<!-- Language Names -->
- <string name="language_japanese">Japônes (日本語)</string>
- <string name="language_english">Português do Brasil</string>
- <string name="language_french">Francês (Français)</string>
- <string name="langauge_german">Alemão (Deutsch)</string>
- <string name="language_italian">Italiano (Italiano)</string>
- <string name="language_spanish">Espanhol (Español)</string>
- <string name="language_chinese">Mandarim (简体中文)</string>
- <string name="language_korean">Coreano (한국어)</string>
- <string name="language_dutch">Holandês (Nederlands)</string>
- <string name="language_portuguese">Português (Português)</string>
- <string name="language_russian">Russo (Русский)</string>
- <string name="language_taiwanese">Taiwanês (台湾)</string>
- <string name="language_british_english">Inglês britânico (British English)</string>
- <string name="language_canadian_french">Fracês Canadiano (Français canadien)</string>
- <string name="language_latin_american_spanish">Espanhol da América Latina (Español latino-americano)</string>
- <string name="language_simplified_chinese">Chinês Simplificado (简体中文)</string>
- <string name="language_traditional_chinese">Chinês tradicional (正體中文)</string>
- <string name="language_brazilian_portuguese">Português do Brasil (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulcano</string>
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 8761e2374..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 @@
<string name="emulation_pause">Pausa emulação</string>
<string name="emulation_unpause">Retomar emulação</string>
<string name="emulation_input_overlay">Opções de sobreposição </string>
- <string name="emulation_game_loading">Jogo a carregar...</string>
<string name="load_settings">Configurações a carregar...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Taiwan</string>
<!-- Language Names -->
- <string name="language_japanese">Japonês (日本語)</string>
- <string name="language_english">Inglês</string>
- <string name="language_french">Francês (Français)</string>
- <string name="langauge_german">Alemão (Deutsch)</string>
- <string name="language_italian">Italiano (Italiano)</string>
- <string name="language_spanish">Espanhol (Español)</string>
- <string name="language_chinese">Chinês simplificado (简体中文)</string>
- <string name="language_korean">Coreano (한국어)</string>
- <string name="language_dutch">Holandês (Nederlands)</string>
- <string name="language_portuguese">Português (Português)</string>
- <string name="language_russian">Russo (Русский)</string>
- <string name="language_taiwanese">Taiwanês (台湾)</string>
- <string name="language_british_english">Inglês Britânico</string>
- <string name="language_canadian_french">Fracês Canadiano (Français canadien)</string>
- <string name="language_latin_american_spanish">Espanhol da América Latina (Español latino-americano)</string>
- <string name="language_simplified_chinese">Chinês Simplificado (简体中文)</string>
- <string name="language_traditional_chinese">Chinês Tradicional (正 體 中文)</string>
- <string name="language_brazilian_portuguese">Português do Brasil (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulcano</string>
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 0fb4908f7..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 @@
<string name="emulation_pause">Пауза эмуляции</string>
<string name="emulation_unpause">Возобновление эмуляции</string>
<string name="emulation_input_overlay">Настройки оверлея</string>
- <string name="emulation_game_loading">Загрузка игры...</string>
<string name="load_settings">Загрузка настроек...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Тайвань</string>
<!-- Language Names -->
- <string name="language_japanese">Японский (日本語)</string>
- <string name="language_english">Английский (English)</string>
- <string name="language_french">Французский (Français)</string>
- <string name="langauge_german">Немецкий (Deutsch)</string>
- <string name="language_italian">Итальянский (Italiano)</string>
- <string name="language_spanish">Испанский (Español)</string>
- <string name="language_chinese">Китайский (简体中文)</string>
- <string name="language_korean">Корейский (한국어)</string>
- <string name="language_dutch">Голландский (Nederlands)</string>
- <string name="language_portuguese">Португальский (Português)</string>
- <string name="language_russian">Русский</string>
- <string name="language_taiwanese">Тайваньский (台湾)</string>
- <string name="language_british_english">Британский английский</string>
- <string name="language_canadian_french">Канадский французский (Français canadien)</string>
- <string name="language_latin_american_spanish">Латиноамериканский испанский (Español latinoamericano)</string>
- <string name="language_simplified_chinese">Упрощенный китайский (简体中文)</string>
- <string name="language_traditional_chinese">Традиционный китайский (正體中文)</string>
- <string name="language_brazilian_portuguese">Бразильский португальский (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 0d11eb2d2..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 @@
<string name="emulation_pause">Пауза емуляції</string>
<string name="emulation_unpause">Відновлення емуляції</string>
<string name="emulation_input_overlay">Налаштування оверлея</string>
- <string name="emulation_game_loading">Завантаження гри...</string>
<string name="load_settings">Завантаження налаштувань...</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">Тайвань</string>
<!-- Language Names -->
- <string name="language_japanese">Японська (日本語)</string>
- <string name="language_english">Англійська (English)</string>
- <string name="language_french">Французька (Français)</string>
- <string name="langauge_german">Німецька (Deutsch)</string>
- <string name="language_italian">Італійська (Italiano)</string>
- <string name="language_spanish">Іспанська (Español)</string>
- <string name="language_chinese">Китайскька (简体中文)</string>
- <string name="language_korean">Корейська (한국어)</string>
- <string name="language_dutch">Голландська (Nederlands)</string>
- <string name="language_portuguese">Португальська (Português)</string>
- <string name="language_russian">Російська (Русский)</string>
- <string name="language_taiwanese">Тайванська (台湾)</string>
- <string name="language_british_english">Британська англійська</string>
- <string name="language_canadian_french">Канадська французька (Français canadien)</string>
- <string name="language_latin_american_spanish">Латиноамериканська іспанська (Español latinoamericano)</string>
- <string name="language_simplified_chinese">Спрощена китайська (简体中文)</string>
- <string name="language_traditional_chinese">Традиційна китайська (正體中文)</string>
- <string name="language_brazilian_portuguese">Бразильська португальська (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 e00bbaa2e..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 @@
<string name="emulation_pause">暂停模拟</string>
<string name="emulation_unpause">继续模拟</string>
<string name="emulation_input_overlay">虚拟按键选项</string>
- <string name="emulation_game_loading">载入游戏中…</string>
<string name="load_settings">正在载入设定…</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">中国台湾</string>
<!-- Language Names -->
- <string name="language_japanese">日语 (日本語)</string>
- <string name="language_english">英语 (English)</string>
- <string name="language_french">法语 (Français)</string>
- <string name="langauge_german">德语 (Deutsch)</string>
- <string name="language_italian">意大利语 (Italiano)</string>
- <string name="language_spanish">西班牙语 (Español)</string>
- <string name="language_chinese">中文 (简体中文)</string>
- <string name="language_korean">韩语 (한국어)</string>
- <string name="language_dutch">荷兰语 (Nederlands)</string>
- <string name="language_portuguese">葡萄牙语 (Português)</string>
- <string name="language_russian">俄语 (Русский)</string>
- <string name="language_taiwanese">台湾中文 (台灣)</string>
- <string name="language_british_english">英式英语</string>
- <string name="language_canadian_french">加拿大法语 (Français canadien)</string>
- <string name="language_latin_american_spanish">拉丁美洲西班牙语 (Español latinoamericano)</string>
- <string name="language_simplified_chinese">简体中文 (简体中文)</string>
- <string name="language_traditional_chinese">繁体中文 (正體中文)</string>
- <string name="language_brazilian_portuguese">巴西葡萄牙语 (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 a54d04248..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 @@
<string name="emulation_pause">暫停模擬</string>
<string name="emulation_unpause">取消暫停模擬</string>
<string name="emulation_input_overlay">覆疊選項</string>
- <string name="emulation_game_loading">遊戲正在載入…</string>
<string name="load_settings">正在載入設定…</string>
@@ -241,24 +240,6 @@
<string name="region_taiwan">台灣</string>
<!-- Language Names -->
- <string name="language_japanese">日文 (日本語)</string>
- <string name="language_english">英文</string>
- <string name="language_french">法文 (Français)</string>
- <string name="langauge_german">德文 (Deutsch)</string>
- <string name="language_italian">義大利文 (Italiano)</string>
- <string name="language_spanish">西班牙文 (Español)</string>
- <string name="language_chinese">中文 (简体中文)</string>
- <string name="language_korean">韓文 (한국어)</string>
- <string name="language_dutch">荷蘭文 (Nederlands)</string>
- <string name="language_portuguese">葡萄牙文 (Português)</string>
- <string name="language_russian">俄文 (Русский)</string>
- <string name="language_taiwanese">台文 (台灣)</string>
- <string name="language_british_english">英式英文</string>
- <string name="language_canadian_french">加拿大法文 (Français canadien)</string>
- <string name="language_latin_american_spanish">拉丁美洲西班牙文 (Español latinoamericano)</string>
- <string name="language_simplified_chinese">簡體中文 (简体中文)</string>
- <string name="language_traditional_chinese">正體中文 (正體中文)</string>
- <string name="language_brazilian_portuguese">巴西葡萄牙文 (Português do Brasil)</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
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 @@
<item>@string/cubeb</item>
<item>@string/string_null</item>
</string-array>
- <string-array name="outputEngineValues">
- <item>auto</item>
- <item>cubeb</item>
- <item>null</item>
- </string-array>
+ <integer-array name="outputEngineValues">
+ <item>0</item>
+ <item>1</item>
+ <item>3</item>
+ </integer-array>
</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 @@
<dimen name="spacing_refresh_end">72dp</dimen>
<dimen name="menu_width">256dp</dimen>
<dimen name="card_width">165dp</dimen>
+ <dimen name="icon_inset">24dp</dimen>
<dimen name="dialog_margin">20dp</dimen>
<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 bfdebd35b..0730143bd 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 @@
<string name="back">Back</string>
<string name="add_games">Add Games</string>
<string name="add_games_description">Select your games folder</string>
+ <string name="step_complete">Complete!</string>
<!-- Home strings -->
<string name="home_games">Games</string>
@@ -42,6 +43,7 @@
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Search games</string>
+ <string name="search_settings">Search settings</string>
<string name="games_dir_selected">Games directory selected</string>
<string name="install_prod_keys">Install prod.keys</string>
<string name="install_prod_keys_description">Required to decrypt retail games</string>
@@ -73,6 +75,7 @@
<string name="install_gpu_driver">Install GPU driver</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="advanced_settings">Advanced settings</string>
+ <string name="advanced_settings_game">Advanced settings: %1$s</string>
<string name="settings_description">Configure emulator settings</string>
<string name="search_recently_played">Recently played</string>
<string name="search_recently_added">Recently added</string>
@@ -125,6 +128,15 @@
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
<string name="licenses_description">Projects that make yuzu for Android possible</string>
<string name="build">Build</string>
+ <string name="user_data">User data</string>
+ <string name="user_data_description">Import/export all app data.\n\nWhen importing user data, all existing user data will be deleted!</string>
+ <string name="exporting_user_data">Exporting user data…</string>
+ <string name="importing_user_data">Importing user data…</string>
+ <string name="import_user_data">Import user data</string>
+ <string name="invalid_yuzu_backup">Invalid yuzu backup</string>
+ <string name="user_data_export_success">User data exported successfully</string>
+ <string name="user_data_import_success">User data imported successfully</string>
+ <string name="user_data_export_cancelled">Export cancelled</string>
<string name="support_link">https://discord.gg/u77vRWY</string>
<string name="website_link">https://yuzu-emu.org/</string>
<string name="github_link">https://github.com/yuzu-emu</string>
@@ -149,6 +161,7 @@
<string name="frame_limit_slider">Limit speed percent</string>
<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>
<string name="cpu_accuracy">CPU accuracy</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
<string name="use_docked_mode">Docked Mode</string>
@@ -198,7 +211,9 @@
<string name="ini_saved">Saved settings</string>
<string name="gameid_saved">Saved settings for %1$s</string>
<string name="error_saving">Error saving %1$s.ini: %2$s</string>
+ <string name="unimplemented_menu">Unimplemented Menu</string>
<string name="loading">Loading…</string>
+ <string name="shutting_down">Shutting down…</string>
<string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
<string name="reset_to_default">Reset to default</string>
<string name="reset_all_settings">Reset all settings?</string>
@@ -209,6 +224,9 @@
<string name="auto">Auto</string>
<string name="submit">Submit</string>
<string name="string_null">Null</string>
+ <string name="string_import">Import</string>
+ <string name="export">Export</string>
+ <string name="cancelling">Cancelling</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
@@ -257,7 +275,6 @@
<string name="emulation_pause">Pause emulation</string>
<string name="emulation_unpause">Unpause emulation</string>
<string name="emulation_input_overlay">Overlay options</string>
- <string name="emulation_game_loading">Game loading…</string>
<string name="load_settings">Loading settings…</string>
@@ -287,24 +304,24 @@
<string name="region_taiwan">Taiwan</string>
<!-- Language Names -->
- <string name="language_japanese">Japanese (日本語)</string>
- <string name="language_english">English</string>
- <string name="language_french">French (Français)</string>
- <string name="langauge_german">German (Deutsch)</string>
- <string name="language_italian">Italian (Italiano)</string>
- <string name="language_spanish">Spanish (Español)</string>
- <string name="language_chinese">Chinese (简体中文)</string>
- <string name="language_korean">Korean (한국어)</string>
- <string name="language_dutch">Dutch (Nederlands)</string>
- <string name="language_portuguese">Portuguese (Português)</string>
- <string name="language_russian">Russian (Русский)</string>
- <string name="language_taiwanese">Taiwanese (台湾)</string>
- <string name="language_british_english">British English</string>
- <string name="language_canadian_french">Canadian French (Français canadien)</string>
- <string name="language_latin_american_spanish">Latin American Spanish (Español latinoamericano)</string>
- <string name="language_simplified_chinese">Simplified Chinese (简体中文)</string>
- <string name="language_traditional_chinese">Traditional Chinese (正體中文)</string>
- <string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string>
+ <string name="language_japanese" translatable="false">日本語</string>
+ <string name="language_english" translatable="false">English</string>
+ <string name="language_french" translatable="false">Français</string>
+ <string name="langauge_german" translatable="false">Deutsch</string>
+ <string name="language_italian" translatable="false">Italiano</string>
+ <string name="language_spanish" translatable="false">Español</string>
+ <string name="language_chinese" translatable="false">简体中文</string>
+ <string name="language_korean" translatable="false">한국어</string>
+ <string name="language_dutch" translatable="false">Nederlands</string>
+ <string name="language_portuguese" translatable="false">Português</string>
+ <string name="language_russian" translatable="false">Русский</string>
+ <string name="language_taiwanese" translatable="false">台湾</string>
+ <string name="language_british_english" translatable="false">British English</string>
+ <string name="language_canadian_french" translatable="false">Français canadien</string>
+ <string name="language_latin_american_spanish" translatable="false">Español latinoamericano</string>
+ <string name="language_simplified_chinese" translatable="false">简体中文</string>
+ <string name="language_traditional_chinese" translatable="false">正體中文</string>
+ <string name="language_brazilian_portuguese" translatable="false">Português do Brasil</string>
<!-- Memory Sizes -->
<string name="memory_byte">Byte</string>
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 @@
# SPDX-License-Identifier: GPL-2.0-or-later
add_library(audio_core STATIC
+ adsp/adsp.cpp
+ adsp/adsp.h
+ adsp/mailbox.h
+ adsp/apps/audio_renderer/audio_renderer.cpp
+ adsp/apps/audio_renderer/audio_renderer.h
+ adsp/apps/audio_renderer/command_buffer.h
+ adsp/apps/audio_renderer/command_list_processor.cpp
+ adsp/apps/audio_renderer/command_list_processor.h
+ adsp/apps/opus/opus_decoder.cpp
+ adsp/apps/opus/opus_decoder.h
+ adsp/apps/opus/opus_decode_object.cpp
+ adsp/apps/opus/opus_decode_object.h
+ adsp/apps/opus/opus_multistream_decode_object.cpp
+ adsp/apps/opus/opus_multistream_decode_object.h
+ adsp/apps/opus/shared_memory.h
audio_core.cpp
audio_core.h
audio_event.h
@@ -27,18 +42,18 @@ add_library(audio_core STATIC
in/audio_in.h
in/audio_in_system.cpp
in/audio_in_system.h
+ opus/hardware_opus.cpp
+ opus/hardware_opus.h
+ opus/decoder_manager.cpp
+ opus/decoder_manager.h
+ opus/decoder.cpp
+ opus/decoder.h
+ opus/parameters.h
out/audio_out.cpp
out/audio_out.h
out/audio_out_system.cpp
out/audio_out_system.h
precompiled_headers.h
- renderer/adsp/adsp.cpp
- renderer/adsp/adsp.h
- renderer/adsp/audio_renderer.cpp
- renderer/adsp/audio_renderer.h
- renderer/adsp/command_buffer.h
- renderer/adsp/command_list_processor.cpp
- renderer/adsp/command_list_processor.h
renderer/audio_device.cpp
renderer/audio_device.h
renderer/audio_renderer.h
@@ -213,7 +228,7 @@ else()
)
endif()
-target_link_libraries(audio_core PUBLIC common core)
+target_link_libraries(audio_core PUBLIC common core Opus::opus)
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_link_libraries(audio_core PRIVATE dynarmic::dynarmic)
endif()
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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/adsp/adsp.h"
+#include "core/core.h"
+
+namespace AudioCore::ADSP {
+
+ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
+ audio_renderer = std::make_unique<AudioRenderer::AudioRenderer>(system, sink);
+ opus_decoder = std::make_unique<OpusDecoder::OpusDecoder>(system);
+ opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start);
+ if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) {
+ LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize.");
+ return;
+ }
+}
+
+AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
+ return *audio_renderer.get();
+}
+
+OpusDecoder::OpusDecoder& ADSP::OpusDecoder() {
+ return *opus_decoder.get();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
+#include "audio_core/adsp/apps/opus/opus_decoder.h"
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace AudioCore {
+namespace Sink {
+class Sink;
+}
+
+namespace ADSP {
+
+/**
+ * Represents the ADSP embedded within the audio sysmodule.
+ * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
+ *
+ * The kernel will run the apps you write for it, Nintendo have the following:
+ *
+ * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
+ * audio samples end up, and we skip it entirely, since we have very different backends and
+ * mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
+ *
+ * AudioRenderer - Receives command lists generated by the audio render
+ * system on the host, processes them, and sends the samples to Gmix.
+ *
+ * OpusDecoder - Contains libopus, and decodes Opus audio packets into raw pcm data.
+ *
+ * Communication between the host and ADSP is done through mailboxes, and mapping of shared memory.
+ */
+class ADSP {
+public:
+ explicit ADSP(Core::System& system, Sink::Sink& sink);
+ ~ADSP() = default;
+
+ AudioRenderer::AudioRenderer& AudioRenderer();
+ OpusDecoder::OpusDecoder& OpusDecoder();
+
+private:
+ /// AudioRenderer app
+ std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{};
+ std::unique_ptr<OpusDecoder::OpusDecoder> opus_decoder{};
+};
+
+} // namespace ADSP
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <chrono>
+
+#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
+#include "audio_core/audio_core.h"
+#include "audio_core/common/common.h"
+#include "audio_core/sink/sink.h"
+#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+
+MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP_AudioRenderer", MP_RGB(60, 19, 97));
+
+namespace AudioCore::ADSP::AudioRenderer {
+
+AudioRenderer::AudioRenderer(Core::System& system_, Sink::Sink& sink_)
+ : system{system_}, sink{sink_} {}
+
+AudioRenderer::~AudioRenderer() {
+ Stop();
+}
+
+void AudioRenderer::Start() {
+ CreateSinkStreams();
+
+ mailbox.Initialize(AppMailboxId::AudioRenderer);
+
+ main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); });
+
+ mailbox.Send(Direction::DSP, Message::InitializeOK);
+ if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
+ LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
+ "message response from ADSP!");
+ return;
+ }
+ running = true;
+}
+
+void AudioRenderer::Stop() {
+ if (!running) {
+ return;
+ }
+
+ mailbox.Send(Direction::DSP, Message::Shutdown);
+ if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
+ LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
+ "message response from ADSP!");
+ }
+ main_thread.request_stop();
+ main_thread.join();
+
+ for (auto& stream : streams) {
+ if (stream) {
+ stream->Stop();
+ sink.CloseStream(stream);
+ stream = nullptr;
+ }
+ }
+ running = false;
+}
+
+void AudioRenderer::Signal() {
+ signalled_tick = system.CoreTiming().GetGlobalTimeNs().count();
+ Send(Direction::DSP, Message::Render);
+}
+
+void AudioRenderer::Wait() {
+ auto msg = Receive(Direction::Host);
+ if (msg != Message::RenderResponse) {
+ LOG_ERROR(Service_Audio,
+ "Did not receive the expected render response from the AudioRenderer! Expected "
+ "{}, got {}",
+ Message::RenderResponse, msg);
+ }
+}
+
+void AudioRenderer::Send(Direction dir, u32 message) {
+ mailbox.Send(dir, std::move(message));
+}
+
+u32 AudioRenderer::Receive(Direction dir) {
+ return mailbox.Receive(dir);
+}
+
+void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
+ u64 applet_resource_user_id, bool reset) noexcept {
+ command_buffers[session_id].buffer = buffer;
+ command_buffers[session_id].size = size;
+ command_buffers[session_id].time_limit = time_limit;
+ command_buffers[session_id].applet_resource_user_id = applet_resource_user_id;
+ command_buffers[session_id].reset_buffer = reset;
+}
+
+u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
+ return command_buffers[session_id].remaining_command_count;
+}
+
+void AudioRenderer::ClearRemainCommandCount(s32 session_id) noexcept {
+ command_buffers[session_id].remaining_command_count = 0;
+}
+
+u64 AudioRenderer::GetRenderingStartTick(s32 session_id) const noexcept {
+ return (1000 * command_buffers[session_id].render_time_taken_us) + signalled_tick;
+}
+
+void AudioRenderer::CreateSinkStreams() {
+ u32 channels{sink.GetDeviceChannels()};
+ for (u32 i = 0; i < MaxRendererSessions; i++) {
+ std::string name{fmt::format("ADSP_RenderStream-{}", i)};
+ streams[i] =
+ sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
+ streams[i]->SetRingSize(4);
+ }
+}
+
+void AudioRenderer::Main(std::stop_token stop_token) {
+ static constexpr char name[]{"DSP_AudioRenderer_Main"};
+ MicroProfileOnThreadCreate(name);
+ Common::SetCurrentThreadName(name);
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
+
+ // TODO: Create buffer map/unmap thread + mailbox
+ // TODO: Create gMix devices, initialize them here
+
+ if (mailbox.Receive(Direction::DSP) != Message::InitializeOK) {
+ LOG_ERROR(Service_Audio,
+ "ADSP Audio Renderer -- Failed to receive initialize message from host!");
+ return;
+ }
+
+ mailbox.Send(Direction::Host, Message::InitializeOK);
+
+ // 0.12 seconds (2,304,000 / 19,200,000)
+ constexpr u64 max_process_time{2'304'000ULL};
+
+ while (!stop_token.stop_requested()) {
+ auto msg{mailbox.Receive(Direction::DSP)};
+ switch (msg) {
+ case Message::Shutdown:
+ mailbox.Send(Direction::Host, Message::Shutdown);
+ return;
+
+ case Message::Render: {
+ if (system.IsShuttingDown()) [[unlikely]] {
+ std::this_thread::sleep_for(std::chrono::milliseconds(5));
+ mailbox.Send(Direction::Host, Message::RenderResponse);
+ continue;
+ }
+ std::array<bool, MaxRendererSessions> buffers_reset{};
+ std::array<u64, MaxRendererSessions> render_times_taken{};
+ const auto start_time{system.CoreTiming().GetGlobalTimeUs().count()};
+
+ for (u32 index = 0; index < MaxRendererSessions; index++) {
+ auto& command_buffer{command_buffers[index]};
+ auto& command_list_processor{command_list_processors[index]};
+
+ // Check this buffer is valid, as it may not be used.
+ if (command_buffer.buffer != 0) {
+ // If there are no remaining commands (from the previous list),
+ // this is a new command list, initialize it.
+ if (command_buffer.remaining_command_count == 0) {
+ command_list_processor.Initialize(system, command_buffer.buffer,
+ command_buffer.size, streams[index]);
+ }
+
+ if (command_buffer.reset_buffer && !buffers_reset[index]) {
+ streams[index]->ClearQueue();
+ buffers_reset[index] = true;
+ }
+
+ u64 max_time{max_process_time};
+ if (index == 1 && command_buffer.applet_resource_user_id ==
+ command_buffers[0].applet_resource_user_id) {
+ max_time = max_process_time - render_times_taken[0];
+ if (render_times_taken[0] > max_process_time) {
+ max_time = 0;
+ }
+ }
+
+ max_time = std::min(command_buffer.time_limit, max_time);
+ command_list_processor.SetProcessTimeMax(max_time);
+
+ if (index == 0) {
+ streams[index]->WaitFreeSpace(stop_token);
+ }
+
+ // Process the command list
+ {
+ MICROPROFILE_SCOPE(Audio_Renderer);
+ render_times_taken[index] =
+ command_list_processor.Process(index) - start_time;
+ }
+
+ const auto end_time{system.CoreTiming().GetGlobalTimeUs().count()};
+
+ command_buffer.remaining_command_count =
+ command_list_processor.GetRemainingCommandCount();
+ command_buffer.render_time_taken_us = end_time - start_time;
+ }
+ }
+
+ mailbox.Send(Direction::Host, Message::RenderResponse);
+ } break;
+
+ default:
+ LOG_WARNING(Service_Audio,
+ "ADSP AudioRenderer received an invalid message, msg={:02X}!", msg);
+ break;
+ }
+ }
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <thread>
+
+#include "audio_core/adsp/apps/audio_renderer/command_buffer.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
+#include "audio_core/adsp/mailbox.h"
+#include "common/common_types.h"
+#include "common/polyfill_thread.h"
+#include "common/reader_writer_queue.h"
+#include "common/thread.h"
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace AudioCore {
+namespace Sink {
+class Sink;
+}
+
+namespace ADSP::AudioRenderer {
+
+enum Message : u32 {
+ Invalid = 0,
+ MapUnmap_Map = 1,
+ MapUnmap_MapResponse = 2,
+ MapUnmap_Unmap = 3,
+ MapUnmap_UnmapResponse = 4,
+ MapUnmap_InvalidateCache = 5,
+ MapUnmap_InvalidateCacheResponse = 6,
+ MapUnmap_Shutdown = 7,
+ MapUnmap_ShutdownResponse = 8,
+ InitializeOK = 22,
+ RenderResponse = 32,
+ Render = 42,
+ Shutdown = 52,
+};
+
+/**
+ * The AudioRenderer application running on the ADSP.
+ */
+class AudioRenderer {
+public:
+ explicit AudioRenderer(Core::System& system, Sink::Sink& sink);
+ ~AudioRenderer();
+
+ /**
+ * Start the AudioRenderer.
+ *
+ * @param mailbox The mailbox to use for this session.
+ */
+ void Start();
+
+ /**
+ * Stop the AudioRenderer.
+ */
+ void Stop();
+
+ void Signal();
+ void Wait();
+
+ void Send(Direction dir, u32 message);
+ u32 Receive(Direction dir);
+
+ void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
+ u64 applet_resource_user_id, bool reset) noexcept;
+ u32 GetRemainCommandCount(s32 session_id) const noexcept;
+ void ClearRemainCommandCount(s32 session_id) noexcept;
+ u64 GetRenderingStartTick(s32 session_id) const noexcept;
+
+private:
+ /**
+ * Main AudioRenderer thread, responsible for processing the command lists.
+ */
+ void Main(std::stop_token stop_token);
+
+ /**
+ * Creates the streams which will receive the processed samples.
+ */
+ void CreateSinkStreams();
+
+ /// Core system
+ Core::System& system;
+ /// The output sink the AudioRenderer will send samples to
+ Sink::Sink& sink;
+ /// The active mailbox
+ Mailbox mailbox;
+ /// Main thread
+ std::jthread main_thread{};
+ /// The current state
+ std::atomic<bool> running{};
+ /// Shared memory of input command buffers, set by host, read by DSP
+ std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
+ /// The command lists to process
+ std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
+ /// The streams which will receive the processed samples
+ std::array<Sink::SinkStream*, MaxRendererSessions> streams{};
+ /// CPU Tick when the DSP was signalled to process, uses time rather than tick
+ u64 signalled_tick{0};
+};
+
+} // namespace ADSP::AudioRenderer
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::ADSP::AudioRenderer {
+
+struct CommandBuffer {
+ // Set by the host
+ CpuAddr buffer{};
+ u64 size{};
+ u64 time_limit{};
+ u64 applet_resource_user_id{};
+ bool reset_buffer{};
+ // Set by the DSP
+ u32 remaining_command_count{};
+ u64 render_time_taken_us{};
+};
+
+} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/command_list_header.h"
#include "audio_core/renderer/command/commands.h"
#include "common/settings.h"
@@ -11,15 +11,15 @@
#include "core/core_timing.h"
#include "core/memory.h"
-namespace AudioCore::AudioRenderer::ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
Sink::SinkStream* stream_) {
system = &system_;
memory = &system->ApplicationMemory();
stream = stream_;
- header = reinterpret_cast<CommandListHeader*>(buffer);
- commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
+ header = reinterpret_cast<Renderer::CommandListHeader*>(buffer);
+ commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader));
commands_buffer_size = size;
command_count = header->command_count;
sample_count = header->sample_count;
@@ -37,17 +37,12 @@ u32 CommandListProcessor::GetRemainingCommandCount() const {
return command_count - processed_command_count;
}
-void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
- commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
- commands_buffer_size = size;
-}
-
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
return stream;
}
u64 CommandListProcessor::Process(u32 session_id) {
- const auto start_time_{system->CoreTiming().GetClockTicks()};
+ const auto start_time_{system->CoreTiming().GetGlobalTimeUs().count()};
const auto command_base{CpuAddr(commands)};
if (processed_command_count > 0) {
@@ -60,12 +55,12 @@ u64 CommandListProcessor::Process(u32 session_id) {
std::string dump{fmt::format("\nSession {}\n", session_id)};
for (u32 index = 0; index < command_count; index++) {
- auto& command{*reinterpret_cast<ICommand*>(commands)};
+ auto& command{*reinterpret_cast<Renderer::ICommand*>(commands)};
if (command.magic != 0xCAFEBABE) {
LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
command.magic);
- return system->CoreTiming().GetClockTicks() - start_time_;
+ return system->CoreTiming().GetGlobalTimeUs().count() - start_time_;
}
auto current_offset{CpuAddr(commands) - command_base};
@@ -74,8 +69,8 @@ u64 CommandListProcessor::Process(u32 session_id) {
LOG_ERROR(Service_Audio,
"Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
commands_buffer_size,
- CpuAddr(commands) + command.size - sizeof(CommandListHeader));
- return system->CoreTiming().GetClockTicks() - start_time_;
+ CpuAddr(commands) + command.size - sizeof(Renderer::CommandListHeader));
+ return system->CoreTiming().GetGlobalTimeUs().count() - start_time_;
}
if (Settings::values.dump_audio_commands) {
@@ -101,8 +96,8 @@ u64 CommandListProcessor::Process(u32 session_id) {
last_dump = dump;
}
- end_time = system->CoreTiming().GetClockTicks();
+ end_time = system->CoreTiming().GetGlobalTimeUs().count();
return end_time - start_time_;
}
-} // namespace AudioCore::AudioRenderer::ADSP
+} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -6,6 +6,7 @@
#include <span>
#include "audio_core/common/common.h"
+#include "audio_core/renderer/command/command_list_header.h"
#include "common/common_types.h"
namespace Core {
@@ -20,10 +21,11 @@ namespace Sink {
class SinkStream;
}
-namespace AudioRenderer {
+namespace Renderer {
struct CommandListHeader;
+}
-namespace ADSP {
+namespace ADSP::AudioRenderer {
/**
* A processor for command lists given to the AudioRenderer.
@@ -55,14 +57,6 @@ public:
u32 GetRemainingCommandCount() const;
/**
- * Set the command buffer.
- *
- * @param buffer - The buffer to use.
- * @param size - The size of the buffer.
- */
- void SetBuffer(CpuAddr buffer, u64 size);
-
- /**
* Get the stream for this command list.
*
* @return The stream associated with this command list.
@@ -85,7 +79,7 @@ public:
/// Stream for the processed samples
Sink::SinkStream* stream{};
/// Header info for this command list
- CommandListHeader* header{};
+ Renderer::CommandListHeader* header{};
/// The command buffer
u8* commands{};
/// The command buffer size
@@ -114,6 +108,5 @@ public:
std::string last_dump{};
};
-} // namespace ADSP
-} // namespace AudioRenderer
+} // namespace ADSP::AudioRenderer
} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/adsp/apps/opus/opus_decode_object.h"
+#include "common/assert.h"
+
+namespace AudioCore::ADSP::OpusDecoder {
+namespace {
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+} // namespace
+
+u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
+ if (!IsValidChannelCount(channel_count)) {
+ return 0;
+ }
+ return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
+}
+
+OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
+ auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
+ auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
+
+ if (new_decoder->magic == DecodeObjectMagic) {
+ if (!new_decoder->initialized ||
+ (new_decoder->initialized && new_decoder->self == comparison)) {
+ new_decoder->state_valid = true;
+ }
+ } else {
+ new_decoder->initialized = false;
+ new_decoder->state_valid = true;
+ }
+ return *new_decoder;
+}
+
+s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ if (initialized) {
+ return OPUS_OK;
+ }
+
+ // Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
+ // it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
+ // provided.
+ // We could use _create and have libopus allocate it for us, but then we have to separately
+ // track which decoder is being used between this and multistream in order to call the correct
+ // destroy from the host side.
+ // This is a bit cringe, but is safe as these objects are only ever initialized inside the given
+ // workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
+ decoder = (LibOpusDecoder*)(this + 1);
+ s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
+ if (ret == OPUS_OK) {
+ magic = DecodeObjectMagic;
+ initialized = true;
+ state_valid = true;
+ self = this;
+ final_range = 0;
+ }
+ return ret;
+}
+
+s32 OpusDecodeObject::Shutdown() {
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ if (initialized) {
+ magic = 0x0;
+ initialized = false;
+ state_valid = false;
+ self = nullptr;
+ final_range = 0;
+ decoder = nullptr;
+ }
+ return OPUS_OK;
+}
+
+s32 OpusDecodeObject::ResetDecoder() {
+ return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
+}
+
+s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
+ u64 input_data, u64 input_data_size) {
+ ASSERT(initialized);
+ out_sample_count = 0;
+
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ auto ret_code_or_samples = opus_decode(
+ decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
+ reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
+
+ if (ret_code_or_samples < OPUS_OK) {
+ return ret_code_or_samples;
+ }
+
+ out_sample_count = ret_code_or_samples;
+ return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <opus.h>
+
+#include "common/common_types.h"
+
+namespace AudioCore::ADSP::OpusDecoder {
+using LibOpusDecoder = ::OpusDecoder;
+static constexpr u32 DecodeObjectMagic = 0xDEADBEEF;
+
+class OpusDecodeObject {
+public:
+ static u32 GetWorkBufferSize(u32 channel_count);
+ static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2);
+
+ s32 InitializeDecoder(u32 sample_rate, u32 channel_count);
+ s32 Shutdown();
+ s32 ResetDecoder();
+ s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
+ u64 input_data_size);
+ u32 GetFinalRange() const noexcept {
+ return final_range;
+ }
+
+private:
+ u32 magic;
+ bool initialized;
+ bool state_valid;
+ OpusDecodeObject* self;
+ u32 final_range;
+ LibOpusDecoder* decoder;
+};
+static_assert(std::is_trivially_constructible_v<OpusDecodeObject>);
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <chrono>
+
+#include "audio_core/adsp/apps/opus/opus_decode_object.h"
+#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
+#include "audio_core/adsp/apps/opus/shared_memory.h"
+#include "audio_core/audio_core.h"
+#include "audio_core/common/common.h"
+#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+
+MICROPROFILE_DEFINE(OpusDecoder, "Audio", "DSP_OpusDecoder", MP_RGB(60, 19, 97));
+
+namespace AudioCore::ADSP::OpusDecoder {
+
+namespace {
+constexpr size_t OpusStreamCountMax = 255;
+
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+
+bool IsValidMultiStreamChannelCount(u32 channel_count) {
+ return channel_count <= OpusStreamCountMax;
+}
+
+bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) {
+ return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
+ sterero_stream_count > 0 && sterero_stream_count <= total_stream_count;
+}
+} // namespace
+
+OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
+ init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
+}
+
+OpusDecoder::~OpusDecoder() {
+ if (!running) {
+ init_thread.request_stop();
+ return;
+ }
+
+ // Shutdown the thread
+ Send(Direction::DSP, Message::Shutdown);
+ auto msg = Receive(Direction::Host);
+ ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
+ Message::ShutdownOK, msg);
+ main_thread.request_stop();
+ main_thread.join();
+ running = false;
+}
+
+void OpusDecoder::Send(Direction dir, u32 message) {
+ mailbox.Send(dir, std::move(message));
+}
+
+u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
+ return mailbox.Receive(dir, stop_token);
+}
+
+void OpusDecoder::Init(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
+
+ if (Receive(Direction::DSP, stop_token) != Message::Start) {
+ LOG_ERROR(Service_Audio,
+ "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
+ return;
+ }
+ main_thread = std::jthread([this](std::stop_token st) { Main(st); });
+ running = true;
+ Send(Direction::Host, Message::StartOK);
+}
+
+void OpusDecoder::Main(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
+
+ while (!stop_token.stop_requested()) {
+ auto msg = Receive(Direction::DSP, stop_token);
+ switch (msg) {
+ case Shutdown:
+ Send(Direction::Host, Message::ShutdownOK);
+ return;
+
+ case GetWorkBufferSize: {
+ auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
+
+ ASSERT(IsValidChannelCount(channel_count));
+
+ shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
+ Send(Direction::Host, Message::GetWorkBufferSizeOK);
+ } break;
+
+ case InitializeDecodeObject: {
+ auto buffer = shared_memory->host_send_data[0];
+ auto buffer_size = shared_memory->host_send_data[1];
+ auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
+ auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
+
+ ASSERT(sample_rate >= 0);
+ ASSERT(IsValidChannelCount(channel_count));
+ ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
+
+ auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
+ shared_memory->dsp_return_data[0] =
+ decoder_object.InitializeDecoder(sample_rate, channel_count);
+
+ Send(Direction::Host, Message::InitializeDecodeObjectOK);
+ } break;
+
+ case ShutdownDecodeObject: {
+ auto buffer = shared_memory->host_send_data[0];
+ [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
+
+ auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
+ shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
+
+ Send(Direction::Host, Message::ShutdownDecodeObjectOK);
+ } break;
+
+ case DecodeInterleaved: {
+ auto start_time = system.CoreTiming().GetGlobalTimeUs();
+
+ auto buffer = shared_memory->host_send_data[0];
+ auto input_data = shared_memory->host_send_data[1];
+ auto input_data_size = shared_memory->host_send_data[2];
+ auto output_data = shared_memory->host_send_data[3];
+ auto output_data_size = shared_memory->host_send_data[4];
+ auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
+ auto reset_requested = shared_memory->host_send_data[6];
+
+ u32 decoded_samples{0};
+
+ auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
+ s32 error_code{OPUS_OK};
+ if (reset_requested) {
+ error_code = decoder_object.ResetDecoder();
+ }
+
+ if (error_code == OPUS_OK) {
+ error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
+ input_data, input_data_size);
+ }
+
+ if (error_code == OPUS_OK) {
+ if (final_range && decoder_object.GetFinalRange() != final_range) {
+ error_code = OPUS_INVALID_PACKET;
+ }
+ }
+
+ auto end_time = system.CoreTiming().GetGlobalTimeUs();
+ shared_memory->dsp_return_data[0] = error_code;
+ shared_memory->dsp_return_data[1] = decoded_samples;
+ shared_memory->dsp_return_data[2] = (end_time - start_time).count();
+
+ Send(Direction::Host, Message::DecodeInterleavedOK);
+ } break;
+
+ case MapMemory: {
+ [[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
+ [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
+ Send(Direction::Host, Message::MapMemoryOK);
+ } break;
+
+ case UnmapMemory: {
+ [[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
+ [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
+ Send(Direction::Host, Message::UnmapMemoryOK);
+ } break;
+
+ case GetWorkBufferSizeForMultiStream: {
+ auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
+ auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
+
+ ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
+
+ shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
+ total_stream_count, stereo_stream_count);
+ Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
+ } break;
+
+ case InitializeMultiStreamDecodeObject: {
+ auto buffer = shared_memory->host_send_data[0];
+ auto buffer_size = shared_memory->host_send_data[1];
+ auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
+ auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
+ auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
+ auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
+ // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
+ // mappings, but [6] is never set, and there is not enough room in the argument data for
+ // more than 40 channels, when 255 are possible.
+ // It also means the mapping values are undefined, though likely always 0,
+ // and the mappings given by the game are ignored. The mappings are copied to this
+ // dedicated buffer host side, so let's do as intended.
+ auto mappings = shared_memory->channel_mapping.data();
+
+ ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
+ ASSERT(sample_rate >= 0);
+ ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
+ total_stream_count, stereo_stream_count));
+
+ auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
+ shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder(
+ sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings);
+
+ Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
+ } break;
+
+ case ShutdownMultiStreamDecodeObject: {
+ auto buffer = shared_memory->host_send_data[0];
+ [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
+
+ auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
+ shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
+
+ Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
+ } break;
+
+ case DecodeInterleavedForMultiStream: {
+ auto start_time = system.CoreTiming().GetGlobalTimeUs();
+
+ auto buffer = shared_memory->host_send_data[0];
+ auto input_data = shared_memory->host_send_data[1];
+ auto input_data_size = shared_memory->host_send_data[2];
+ auto output_data = shared_memory->host_send_data[3];
+ auto output_data_size = shared_memory->host_send_data[4];
+ auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
+ auto reset_requested = shared_memory->host_send_data[6];
+
+ u32 decoded_samples{0};
+
+ auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
+ s32 error_code{OPUS_OK};
+ if (reset_requested) {
+ error_code = decoder_object.ResetDecoder();
+ }
+
+ if (error_code == OPUS_OK) {
+ error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
+ input_data, input_data_size);
+ }
+
+ if (error_code == OPUS_OK) {
+ if (final_range && decoder_object.GetFinalRange() != final_range) {
+ error_code = OPUS_INVALID_PACKET;
+ }
+ }
+
+ auto end_time = system.CoreTiming().GetGlobalTimeUs();
+ shared_memory->dsp_return_data[0] = error_code;
+ shared_memory->dsp_return_data[1] = decoded_samples;
+ shared_memory->dsp_return_data[2] = (end_time - start_time).count();
+
+ Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
+ } break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
+ continue;
+ }
+ }
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <thread>
+
+#include "audio_core/adsp/apps/opus/shared_memory.h"
+#include "audio_core/adsp/mailbox.h"
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace AudioCore::ADSP::OpusDecoder {
+
+enum Message : u32 {
+ Invalid = 0,
+ Start = 1,
+ Shutdown = 2,
+ StartOK = 11,
+ ShutdownOK = 12,
+ GetWorkBufferSize = 21,
+ InitializeDecodeObject = 22,
+ ShutdownDecodeObject = 23,
+ DecodeInterleaved = 24,
+ MapMemory = 25,
+ UnmapMemory = 26,
+ GetWorkBufferSizeForMultiStream = 27,
+ InitializeMultiStreamDecodeObject = 28,
+ ShutdownMultiStreamDecodeObject = 29,
+ DecodeInterleavedForMultiStream = 30,
+
+ GetWorkBufferSizeOK = 41,
+ InitializeDecodeObjectOK = 42,
+ ShutdownDecodeObjectOK = 43,
+ DecodeInterleavedOK = 44,
+ MapMemoryOK = 45,
+ UnmapMemoryOK = 46,
+ GetWorkBufferSizeForMultiStreamOK = 47,
+ InitializeMultiStreamDecodeObjectOK = 48,
+ ShutdownMultiStreamDecodeObjectOK = 49,
+ DecodeInterleavedForMultiStreamOK = 50,
+};
+
+/**
+ * The AudioRenderer application running on the ADSP.
+ */
+class OpusDecoder {
+public:
+ explicit OpusDecoder(Core::System& system);
+ ~OpusDecoder();
+
+ bool IsRunning() const noexcept {
+ return running;
+ }
+
+ void Send(Direction dir, u32 message);
+ u32 Receive(Direction dir, std::stop_token stop_token = {});
+
+ void SetSharedMemory(SharedMemory& shared_memory_) {
+ shared_memory = &shared_memory_;
+ }
+
+private:
+ /**
+ * Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
+ */
+ void Init(std::stop_token stop_token);
+ /**
+ * Main OpusDecoder thread, responsible for processing the incoming Opus packets.
+ */
+ void Main(std::stop_token stop_token);
+
+ /// Core system
+ Core::System& system;
+ /// Mailbox to communicate messages with the host, drives the main thread
+ Mailbox mailbox;
+ /// Init thread
+ std::jthread init_thread{};
+ /// Main thread
+ std::jthread main_thread{};
+ /// The current state
+ bool running{};
+ /// Structure shared with the host, input data set by the host before sending a mailbox message,
+ /// and the responses are written back by the OpusDecoder.
+ SharedMemory* shared_memory{};
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
+#include "common/assert.h"
+
+namespace AudioCore::ADSP::OpusDecoder {
+
+namespace {
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+
+bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
+ return total_stream_count > 0 && stereo_stream_count > 0 &&
+ stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count);
+}
+} // namespace
+
+u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
+ u32 stereo_stream_count) {
+ if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
+ return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
+ opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
+ }
+ return 0;
+}
+
+OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
+ auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
+ auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
+
+ if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
+ if (!new_decoder->initialized ||
+ (new_decoder->initialized && new_decoder->self == comparison)) {
+ new_decoder->state_valid = true;
+ }
+ } else {
+ new_decoder->initialized = false;
+ new_decoder->state_valid = true;
+ }
+ return *new_decoder;
+}
+
+s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
+ u32 channel_count, u32 stereo_stream_count,
+ u8* mappings) {
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ if (initialized) {
+ return OPUS_OK;
+ }
+
+ // See OpusDecodeObject::InitializeDecoder for an explanation of this
+ decoder = (LibOpusMSDecoder*)(this + 1);
+ s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
+ stereo_stream_count, mappings);
+ if (ret == OPUS_OK) {
+ magic = DecodeMultiStreamObjectMagic;
+ initialized = true;
+ state_valid = true;
+ self = this;
+ final_range = 0;
+ }
+ return ret;
+}
+
+s32 OpusMultiStreamDecodeObject::Shutdown() {
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ if (initialized) {
+ magic = 0x0;
+ initialized = false;
+ state_valid = false;
+ self = nullptr;
+ final_range = 0;
+ decoder = nullptr;
+ }
+ return OPUS_OK;
+}
+
+s32 OpusMultiStreamDecodeObject::ResetDecoder() {
+ return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
+}
+
+s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
+ u64 output_data_size, u64 input_data, u64 input_data_size) {
+ ASSERT(initialized);
+ out_sample_count = 0;
+
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ auto ret_code_or_samples = opus_multistream_decode(
+ decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
+ reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
+
+ if (ret_code_or_samples < OPUS_OK) {
+ return ret_code_or_samples;
+ }
+
+ out_sample_count = ret_code_or_samples;
+ return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <opus_multistream.h>
+
+#include "common/common_types.h"
+
+namespace AudioCore::ADSP::OpusDecoder {
+using LibOpusMSDecoder = ::OpusMSDecoder;
+static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF;
+
+class OpusMultiStreamDecodeObject {
+public:
+ static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count);
+ static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2);
+
+ s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count,
+ u32 stereo_stream_count, u8* mappings);
+ s32 Shutdown();
+ s32 ResetDecoder();
+ s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
+ u64 input_data_size);
+ u32 GetFinalRange() const noexcept {
+ return final_range;
+ }
+
+private:
+ u32 magic;
+ bool initialized;
+ bool state_valid;
+ OpusMultiStreamDecodeObject* self;
+ u32 final_range;
+ LibOpusMSDecoder* decoder;
+};
+static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>);
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace AudioCore::ADSP::OpusDecoder {
+
+struct SharedMemory {
+ std::array<u8, 0x100> channel_mapping{};
+ std::array<u64, 16> host_send_data{};
+ std::array<u64, 16> dsp_return_data{};
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/bounded_threadsafe_queue.h"
+#include "common/common_types.h"
+
+namespace AudioCore::ADSP {
+
+enum class AppMailboxId : u32 {
+ Invalid = 0,
+ AudioRenderer = 50,
+ AudioRendererMemoryMapUnmap = 51,
+};
+
+enum class Direction : u32 {
+ Host,
+ DSP,
+};
+
+class Mailbox {
+public:
+ void Initialize(AppMailboxId id_) {
+ Reset();
+ id = id_;
+ }
+
+ AppMailboxId Id() const noexcept {
+ return id;
+ }
+
+ void Send(Direction dir, u32 message) {
+ auto& queue = dir == Direction::Host ? host_queue : adsp_queue;
+ queue.EmplaceWait(message);
+ }
+
+ u32 Receive(Direction dir, std::stop_token stop_token = {}) {
+ auto& queue = dir == Direction::Host ? host_queue : adsp_queue;
+ return queue.PopWait(stop_token);
+ }
+
+ void Reset() {
+ id = AppMailboxId::Invalid;
+ u32 t{};
+ while (host_queue.TryPop(t)) {
+ }
+ while (adsp_queue.TryPop(t)) {
+ }
+ }
+
+private:
+ AppMailboxId id{0};
+ Common::SPSCQueue<u32> host_queue;
+ Common::SPSCQueue<u32> adsp_queue;
+};
+
+} // 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 {
AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} {
CreateSinks();
// Must be created after the sinks
- adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink);
+ adsp = std::make_unique<ADSP::ADSP>(system, *output_sink);
}
AudioCore ::~AudioCore() {
@@ -43,7 +43,7 @@ Sink::Sink& AudioCore::GetInputSink() {
return *input_sink;
}
-AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
+ADSP::ADSP& AudioCore::ADSP() {
return *adsp;
}
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 @@
#include <memory>
+#include "audio_core/adsp/adsp.h"
#include "audio_core/audio_manager.h"
-#include "audio_core/renderer/adsp/adsp.h"
#include "audio_core/sink/sink.h"
namespace Core {
@@ -55,7 +55,7 @@ public:
*
* @return Ref to the ADSP.
*/
- AudioRenderer::ADSP::ADSP& GetADSP();
+ ADSP::ADSP& ADSP();
private:
/**
@@ -70,7 +70,7 @@ private:
/// Sink used for audio input
std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule
- std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
+ std::unique_ptr<ADSP::ADSP> adsp;
};
} // 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 {
default:
UNREACHABLE();
}
- return 3;
}
void 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() {
}
}
-u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
+u32 Manager::GetDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names,
[[maybe_unused]] const u32 max_count,
[[maybe_unused]] const bool filter) {
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:
*
* @return Number of names written.
*/
- u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
- u32 max_count, bool filter);
+ u32 GetDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names, u32 max_count,
+ bool filter);
/// Core system
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() {
}
u32 Manager::GetAudioOutDeviceNames(
- std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
+ std::vector<Renderer::AudioDevice::AudioDeviceName>& names) const {
names.emplace_back("DeviceOut");
return 1;
}
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:
* @param names - Output container to write names to.
* @return Number of names written.
*/
- u32 GetAudioOutDeviceNames(
- std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const;
+ u32 GetAudioOutDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names) const;
/// Core system
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 @@
#include "audio_core/common/feature_support.h"
#include "core/core.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
Manager::Manager(Core::System& system_)
: system{system_}, system_manager{std::make_unique<SystemManager>(system)} {
@@ -67,4 +67,4 @@ bool Manager::RemoveSystem(System& system_) {
return system_manager->Remove(system_);
}
-} // namespace AudioCore::AudioRenderer
+} // 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;
namespace AudioCore {
struct AudioRendererParameterInternal;
-namespace AudioRenderer {
+namespace Renderer {
/**
* Wrapper for the audio system manager, handles service calls.
*/
@@ -101,5 +101,5 @@ private:
std::unique_ptr<SystemManager> system_manager{};
};
-} // namespace AudioRenderer
+} // namespace Renderer
} // namespace AudioCore
diff --git a/src/audio_core/common/audio_renderer_parameter.h b/src/audio_core/common/audio_renderer_parameter.h
index 2f62c383b..6c4e9fdc6 100644
--- a/src/audio_core/common/audio_renderer_parameter.h
+++ b/src/audio_core/common/audio_renderer_parameter.h
@@ -32,16 +32,16 @@ struct AudioRendererParameterInternal {
/* 0x14 */ u32 sinks;
/* 0x18 */ u32 effects;
/* 0x1C */ u32 perf_frames;
- /* 0x20 */ u16 voice_drop_enabled;
+ /* 0x20 */ u8 voice_drop_enabled;
+ /* 0x21 */ u8 unk_21;
/* 0x22 */ u8 rendering_device;
/* 0x23 */ ExecutionMode execution_mode;
/* 0x24 */ u32 splitter_infos;
/* 0x28 */ s32 splitter_destinations;
/* 0x2C */ u32 external_context_size;
/* 0x30 */ u32 revision;
- /* 0x34 */ char unk34[0x4];
};
-static_assert(sizeof(AudioRendererParameterInternal) == 0x38,
+static_assert(sizeof(AudioRendererParameterInternal) == 0x34,
"AudioRendererParameterInternal has the wrong size!");
/**
@@ -51,10 +51,10 @@ struct AudioRendererSystemContext {
s32 session_id;
s8 channels;
s16 mix_buffer_count;
- AudioRenderer::BehaviorInfo* behavior;
+ Renderer::BehaviorInfo* behavior;
std::span<s32> depop_buffer;
- AudioRenderer::UpsamplerManager* upsampler_manager;
- AudioRenderer::MemoryPoolInfo* memory_pool_info;
+ Renderer::UpsamplerManager* upsampler_manager;
+ Renderer::MemoryPoolInfo* memory_pool_info;
};
} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/opus/decoder.h"
+#include "audio_core/opus/hardware_opus.h"
+#include "audio_core/opus/parameters.h"
+#include "common/alignment.h"
+#include "common/swap.h"
+#include "core/core.h"
+
+namespace AudioCore::OpusDecoder {
+using namespace Service::Audio;
+namespace {
+OpusPacketHeader ReverseHeader(OpusPacketHeader header) {
+ OpusPacketHeader out;
+ out.size = Common::swap32(header.size);
+ out.final_range = Common::swap32(header.final_range);
+ return out;
+}
+} // namespace
+
+OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_)
+ : system{system_}, hardware_opus{hardware_opus_} {}
+
+OpusDecoder::~OpusDecoder() {
+ if (decode_object_initialized) {
+ hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size);
+ }
+}
+
+Result OpusDecoder::Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size) {
+ auto frame_size{params.use_large_frame_size ? 5760 : 1920};
+ shared_buffer_size = transfer_memory_size;
+ shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
+ shared_memory_mapped = true;
+
+ buffer_size =
+ Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
+
+ out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
+ size_t in_data_size{0x600u};
+ in_data = {out_data.data() - in_data_size, in_data_size};
+
+ ON_RESULT_FAILURE {
+ if (shared_memory_mapped) {
+ shared_memory_mapped = false;
+ ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
+ }
+ };
+
+ R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count,
+ shared_buffer.get(), shared_buffer_size));
+
+ sample_rate = params.sample_rate;
+ channel_count = params.channel_count;
+ use_large_frame_size = params.use_large_frame_size;
+ decode_object_initialized = true;
+ R_SUCCEED();
+}
+
+Result OpusDecoder::Initialize(OpusMultiStreamParametersEx& params,
+ Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
+ auto frame_size{params.use_large_frame_size ? 5760 : 1920};
+ shared_buffer_size = transfer_memory_size;
+ shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
+ shared_memory_mapped = true;
+
+ buffer_size =
+ Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
+
+ out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
+ size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)};
+ in_data = {out_data.data() - in_data_size, in_data_size};
+
+ ON_RESULT_FAILURE {
+ if (shared_memory_mapped) {
+ shared_memory_mapped = false;
+ ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
+ }
+ };
+
+ R_TRY(hardware_opus.InitializeMultiStreamDecodeObject(
+ params.sample_rate, params.channel_count, params.total_stream_count,
+ params.stereo_stream_count, params.mappings.data(), shared_buffer.get(),
+ shared_buffer_size));
+
+ sample_rate = params.sample_rate;
+ channel_count = params.channel_count;
+ total_stream_count = params.total_stream_count;
+ stereo_stream_count = params.stereo_stream_count;
+ use_large_frame_size = params.use_large_frame_size;
+ decode_object_initialized = true;
+ R_SUCCEED();
+}
+
+Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken,
+ u32* out_sample_count, std::span<const u8> input_data,
+ std::span<u8> output_data, bool reset) {
+ u32 out_samples;
+ u64 time_taken{};
+
+ R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
+
+ auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
+ OpusPacketHeader header{ReverseHeader(*header_p)};
+
+ R_UNLESS(in_data.size_bytes() >= header.size &&
+ header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
+ ResultBufferTooSmall);
+
+ if (!shared_memory_mapped) {
+ R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
+ shared_memory_mapped = true;
+ }
+
+ std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
+
+ R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(),
+ channel_count, in_data.data(), header.size,
+ shared_buffer.get(), time_taken, reset));
+
+ std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
+
+ *out_data_size = header.size + sizeof(OpusPacketHeader);
+ *out_sample_count = out_samples;
+ if (out_time_taken) {
+ *out_time_taken = time_taken / 1000;
+ }
+ R_SUCCEED();
+}
+
+Result OpusDecoder::SetContext([[maybe_unused]] std::span<const u8> context) {
+ R_SUCCEED_IF(shared_memory_mapped);
+ shared_memory_mapped = true;
+ R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
+}
+
+Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
+ u32* out_sample_count,
+ std::span<const u8> input_data,
+ std::span<u8> output_data, bool reset) {
+ u32 out_samples;
+ u64 time_taken{};
+
+ R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
+
+ auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
+ OpusPacketHeader header{ReverseHeader(*header_p)};
+
+ LOG_ERROR(Service_Audio, "header size 0x{:X} input data size 0x{:X} in_data size 0x{:X}",
+ header.size, input_data.size_bytes(), in_data.size_bytes());
+
+ R_UNLESS(in_data.size_bytes() >= header.size &&
+ header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
+ ResultBufferTooSmall);
+
+ if (!shared_memory_mapped) {
+ R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
+ shared_memory_mapped = true;
+ }
+
+ std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
+
+ R_TRY(hardware_opus.DecodeInterleavedForMultiStream(
+ out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(),
+ header.size, shared_buffer.get(), time_taken, reset));
+
+ std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
+
+ *out_data_size = header.size + sizeof(OpusPacketHeader);
+ *out_sample_count = out_samples;
+ if (out_time_taken) {
+ *out_time_taken = time_taken / 1000;
+ }
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/opus/parameters.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::OpusDecoder {
+class HardwareOpus;
+
+class OpusDecoder {
+public:
+ explicit OpusDecoder(Core::System& system, HardwareOpus& hardware_opus_);
+ ~OpusDecoder();
+
+ Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size);
+ Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size);
+ Result DecodeInterleaved(u32* out_data_size, u64* out_time_taken, u32* out_sample_count,
+ std::span<const u8> input_data, std::span<u8> output_data, bool reset);
+ Result SetContext([[maybe_unused]] std::span<const u8> context);
+ Result DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
+ u32* out_sample_count, std::span<const u8> input_data,
+ std::span<u8> output_data, bool reset);
+
+private:
+ Core::System& system;
+ HardwareOpus& hardware_opus;
+ std::unique_ptr<u8[]> shared_buffer{};
+ u64 shared_buffer_size;
+ std::span<u8> in_data{};
+ std::span<u8> out_data{};
+ u64 buffer_size{};
+ s32 sample_rate{};
+ s32 channel_count{};
+ bool use_large_frame_size{false};
+ s32 total_stream_count{};
+ s32 stereo_stream_count{};
+ bool shared_memory_mapped{false};
+ bool decode_object_initialized{false};
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/adsp/apps/opus/opus_decoder.h"
+#include "audio_core/opus/decoder_manager.h"
+#include "common/alignment.h"
+#include "core/core.h"
+
+namespace AudioCore::OpusDecoder {
+using namespace Service::Audio;
+
+namespace {
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+
+bool IsValidMultiStreamChannelCount(u32 channel_count) {
+ return channel_count > 0 && channel_count <= OpusStreamCountMax;
+}
+
+bool IsValidSampleRate(u32 sample_rate) {
+ return sample_rate == 8'000 || sample_rate == 12'000 || sample_rate == 16'000 ||
+ sample_rate == 24'000 || sample_rate == 48'000;
+}
+
+bool IsValidStreamCount(u32 channel_count, u32 total_stream_count, u32 stereo_stream_count) {
+ return total_stream_count > 0 && stereo_stream_count > 0 &&
+ stereo_stream_count <= total_stream_count &&
+ total_stream_count + stereo_stream_count <= channel_count;
+}
+
+} // namespace
+
+OpusDecoderManager::OpusDecoderManager(Core::System& system_)
+ : system{system_}, hardware_opus{system} {
+ for (u32 i = 0; i < MaxChannels; i++) {
+ required_workbuffer_sizes[i] = hardware_opus.GetWorkBufferSize(1 + i);
+ }
+}
+
+Result OpusDecoderManager::GetWorkBufferSize(OpusParameters& params, u64& out_size) {
+ OpusParametersEx ex{
+ .sample_rate = params.sample_rate,
+ .channel_count = params.channel_count,
+ .use_large_frame_size = false,
+ };
+ R_RETURN(GetWorkBufferSizeExEx(ex, out_size));
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size) {
+ R_RETURN(GetWorkBufferSizeExEx(params, out_size));
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size) {
+ R_UNLESS(IsValidChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
+ R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
+
+ auto work_buffer_size{required_workbuffer_sizes[params.channel_count - 1]};
+ auto frame_size{params.use_large_frame_size ? 5760 : 1920};
+ work_buffer_size +=
+ Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
+ out_size = work_buffer_size + 0x600;
+ R_SUCCEED();
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params,
+ u64& out_size) {
+ OpusMultiStreamParametersEx ex{
+ .sample_rate = params.sample_rate,
+ .channel_count = params.channel_count,
+ .total_stream_count = params.total_stream_count,
+ .stereo_stream_count = params.stereo_stream_count,
+ .use_large_frame_size = false,
+ .mappings = {},
+ };
+ R_RETURN(GetWorkBufferSizeForMultiStreamExEx(ex, out_size));
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params,
+ u64& out_size) {
+ R_RETURN(GetWorkBufferSizeForMultiStreamExEx(params, out_size));
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params,
+ u64& out_size) {
+ R_UNLESS(IsValidMultiStreamChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
+ R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
+ R_UNLESS(IsValidStreamCount(params.channel_count, params.total_stream_count,
+ params.stereo_stream_count),
+ ResultInvalidOpusSampleRate);
+
+ auto work_buffer_size{hardware_opus.GetWorkBufferSizeForMultiStream(
+ params.total_stream_count, params.stereo_stream_count)};
+ auto frame_size{params.use_large_frame_size ? 5760 : 1920};
+ work_buffer_size += Common::AlignUp(1500 * params.total_stream_count, 64);
+ work_buffer_size +=
+ Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
+ out_size = work_buffer_size;
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/opus/hardware_opus.h"
+#include "audio_core/opus/parameters.h"
+#include "common/common_types.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::OpusDecoder {
+
+class OpusDecoderManager {
+public:
+ OpusDecoderManager(Core::System& system);
+
+ HardwareOpus& GetHardwareOpus() {
+ return hardware_opus;
+ }
+
+ Result GetWorkBufferSize(OpusParameters& params, u64& out_size);
+ Result GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size);
+ Result GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size);
+ Result GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, u64& out_size);
+ Result GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, u64& out_size);
+ Result GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, u64& out_size);
+
+private:
+ Core::System& system;
+ HardwareOpus hardware_opus;
+ std::array<u64, MaxChannels> required_workbuffer_sizes{};
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/opus/hardware_opus.h"
+#include "core/core.h"
+
+namespace AudioCore::OpusDecoder {
+namespace {
+using namespace Service::Audio;
+
+static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) {
+ s32 error{static_cast<s32>(error_code)};
+ ASSERT(error <= OPUS_OK);
+ switch (error) {
+ case OPUS_ALLOC_FAIL:
+ R_THROW(ResultLibOpusAllocFail);
+ case OPUS_INVALID_STATE:
+ R_THROW(ResultLibOpusInvalidState);
+ case OPUS_UNIMPLEMENTED:
+ R_THROW(ResultLibOpusUnimplemented);
+ case OPUS_INVALID_PACKET:
+ R_THROW(ResultLibOpusInvalidPacket);
+ case OPUS_INTERNAL_ERROR:
+ R_THROW(ResultLibOpusInternalError);
+ case OPUS_BUFFER_TOO_SMALL:
+ R_THROW(ResultBufferTooSmall);
+ case OPUS_BAD_ARG:
+ R_THROW(ResultLibOpusBadArg);
+ case OPUS_OK:
+ R_RETURN(ResultSuccess);
+ }
+ UNREACHABLE();
+}
+
+} // namespace
+
+HardwareOpus::HardwareOpus(Core::System& system_)
+ : system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} {
+ opus_decoder.SetSharedMemory(shared_memory);
+}
+
+u64 HardwareOpus::GetWorkBufferSize(u32 channel) {
+ if (!opus_decoder.IsRunning()) {
+ return 0;
+ }
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = channel;
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::GetWorkBufferSize);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::GetWorkBufferSizeOK, msg);
+ return 0;
+ }
+ return shared_memory.dsp_return_data[0];
+}
+
+u64 HardwareOpus::GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = total_stream_count;
+ shared_memory.host_send_data[1] = stereo_stream_count;
+ opus_decoder.Send(ADSP::Direction::DSP,
+ ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStream);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK, msg);
+ return 0;
+ }
+ return shared_memory.dsp_return_data[0];
+}
+
+Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
+ u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+ shared_memory.host_send_data[2] = sample_rate;
+ shared_memory.host_send_data[3] = channel_count;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::InitializeDecodeObject);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::InitializeDecodeObjectOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::InitializeDecodeObjectOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+
+ R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
+}
+
+Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
+ u32 total_stream_count,
+ u32 stereo_stream_count, void* mappings,
+ void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+ shared_memory.host_send_data[2] = sample_rate;
+ shared_memory.host_send_data[3] = channel_count;
+ shared_memory.host_send_data[4] = total_stream_count;
+ shared_memory.host_send_data[5] = stereo_stream_count;
+
+ ASSERT(channel_count <= MaxChannels);
+ std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8));
+
+ opus_decoder.Send(ADSP::Direction::DSP,
+ ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObject);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+
+ R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
+}
+
+Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::ShutdownDecodeObject);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK,
+ "Expected Opus shutdown code {}, got {}",
+ ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
+
+ R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
+}
+
+Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+
+ opus_decoder.Send(ADSP::Direction::DSP,
+ ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObject);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK,
+ "Expected Opus shutdown code {}, got {}",
+ ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
+
+ R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
+}
+
+Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
+ u64 output_data_size, u32 channel_count, void* input_data,
+ u64 input_data_size, void* buffer, u64& out_time_taken,
+ bool reset) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = (u64)input_data;
+ shared_memory.host_send_data[2] = input_data_size;
+ shared_memory.host_send_data[3] = (u64)output_data;
+ shared_memory.host_send_data[4] = output_data_size;
+ shared_memory.host_send_data[5] = 0;
+ shared_memory.host_send_data[6] = reset;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleaved);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::DecodeInterleavedOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+
+ auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
+ if (error_code == OPUS_OK) {
+ out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
+ out_time_taken = 1000 * shared_memory.dsp_return_data[2];
+ }
+ R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
+}
+
+Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
+ u64 output_data_size, u32 channel_count,
+ void* input_data, u64 input_data_size,
+ void* buffer, u64& out_time_taken,
+ bool reset) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = (u64)input_data;
+ shared_memory.host_send_data[2] = input_data_size;
+ shared_memory.host_send_data[3] = (u64)output_data;
+ shared_memory.host_send_data[4] = output_data_size;
+ shared_memory.host_send_data[5] = 0;
+ shared_memory.host_send_data[6] = reset;
+
+ opus_decoder.Send(ADSP::Direction::DSP,
+ ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+
+ auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
+ if (error_code == OPUS_OK) {
+ out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
+ out_time_taken = 1000 * shared_memory.dsp_return_data[2];
+ }
+ R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
+}
+
+Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::MapMemoryOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+ R_SUCCEED();
+}
+
+Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+#include <opus.h>
+
+#include "audio_core/adsp/apps/opus/opus_decoder.h"
+#include "audio_core/adsp/apps/opus/shared_memory.h"
+#include "audio_core/adsp/mailbox.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::OpusDecoder {
+class HardwareOpus {
+public:
+ HardwareOpus(Core::System& system);
+
+ u64 GetWorkBufferSize(u32 channel);
+ u64 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
+
+ Result InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
+ u64 buffer_size);
+ Result InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
+ u32 totaL_stream_count, u32 stereo_stream_count,
+ void* mappings, void* buffer, u64 buffer_size);
+ Result ShutdownDecodeObject(void* buffer, u64 buffer_size);
+ Result ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size);
+ Result DecodeInterleaved(u32& out_sample_count, void* output_data, u64 output_data_size,
+ u32 channel_count, void* input_data, u64 input_data_size, void* buffer,
+ u64& out_time_taken, bool reset);
+ Result DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
+ u64 output_data_size, u32 channel_count,
+ void* input_data, u64 input_data_size, void* buffer,
+ u64& out_time_taken, bool reset);
+ Result MapMemory(void* buffer, u64 buffer_size);
+ Result UnmapMemory(void* buffer, u64 buffer_size);
+
+private:
+ Core::System& system;
+ std::mutex mutex;
+ ADSP::OpusDecoder::OpusDecoder& opus_decoder;
+ ADSP::OpusDecoder::SharedMemory shared_memory;
+};
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace AudioCore::OpusDecoder {
+constexpr size_t OpusStreamCountMax = 255;
+constexpr size_t MaxChannels = 2;
+
+struct OpusParameters {
+ /* 0x00 */ u32 sample_rate;
+ /* 0x04 */ u32 channel_count;
+}; // size = 0x8
+static_assert(sizeof(OpusParameters) == 0x8, "OpusParameters has the wrong size!");
+
+struct OpusParametersEx {
+ /* 0x00 */ u32 sample_rate;
+ /* 0x04 */ u32 channel_count;
+ /* 0x08 */ bool use_large_frame_size;
+ /* 0x09 */ INSERT_PADDING_BYTES(7);
+}; // size = 0x10
+static_assert(sizeof(OpusParametersEx) == 0x10, "OpusParametersEx has the wrong size!");
+
+struct OpusMultiStreamParameters {
+ /* 0x00 */ u32 sample_rate;
+ /* 0x04 */ u32 channel_count;
+ /* 0x08 */ u32 total_stream_count;
+ /* 0x0C */ u32 stereo_stream_count;
+ /* 0x10 */ std::array<u8, OpusStreamCountMax + 1> mappings;
+}; // size = 0x110
+static_assert(sizeof(OpusMultiStreamParameters) == 0x110,
+ "OpusMultiStreamParameters has the wrong size!");
+
+struct OpusMultiStreamParametersEx {
+ /* 0x00 */ u32 sample_rate;
+ /* 0x04 */ u32 channel_count;
+ /* 0x08 */ u32 total_stream_count;
+ /* 0x0C */ u32 stereo_stream_count;
+ /* 0x10 */ bool use_large_frame_size;
+ /* 0x11 */ INSERT_PADDING_BYTES(7);
+ /* 0x18 */ std::array<u8, OpusStreamCountMax + 1> mappings;
+}; // size = 0x118
+static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118,
+ "OpusMultiStreamParametersEx has the wrong size!");
+
+struct OpusPacketHeader {
+ /* 0x00 */ u32 size;
+ /* 0x04 */ u32 final_range;
+}; // size = 0x8
+static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusPacketHeader has the wrong size!");
+} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "audio_core/renderer/adsp/adsp.h"
-#include "audio_core/renderer/adsp/command_buffer.h"
-#include "audio_core/sink/sink.h"
-#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/memory.h"
-
-namespace AudioCore::AudioRenderer::ADSP {
-
-ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
- : system{system_}, memory{system.ApplicationMemory()}, sink{sink_} {}
-
-ADSP::~ADSP() {
- ClearCommandBuffers();
-}
-
-State ADSP::GetState() const {
- if (running) {
- return State::Started;
- }
- return State::Stopped;
-}
-
-AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
- return &render_mailbox;
-}
-
-void ADSP::ClearRemainCount(const u32 session_id) {
- render_mailbox.ClearRemainCount(session_id);
-}
-
-u64 ADSP::GetSignalledTick() const {
- return render_mailbox.GetSignalledTick();
-}
-
-u64 ADSP::GetTimeTaken() const {
- return render_mailbox.GetRenderTimeTaken();
-}
-
-u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
- return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
-}
-
-u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
- return render_mailbox.GetRemainCommandCount(session_id);
-}
-
-void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
- render_mailbox.SetCommandBuffer(session_id, command_buffer);
-}
-
-u64 ADSP::GetRenderingStartTick(const u32 session_id) {
- return render_mailbox.GetSignalledTick() +
- render_mailbox.GetCommandBuffer(session_id).render_time_taken;
-}
-
-bool ADSP::Start() {
- if (running) {
- return running;
- }
-
- running = true;
- systems_active++;
- audio_renderer = std::make_unique<AudioRenderer>(system);
- audio_renderer->Start(&render_mailbox);
- render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
- if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
- LOG_ERROR(
- Service_Audio,
- "Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
- }
- return running;
-}
-
-void ADSP::Stop() {
- systems_active--;
- if (running && systems_active == 0) {
- {
- std::scoped_lock l{mailbox_lock};
- render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
- if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
- LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
- "message response from ADSP!");
- }
- }
- audio_renderer->Stop();
- running = false;
- }
-}
-
-void ADSP::Signal() {
- const auto signalled_tick{system.CoreTiming().GetClockTicks()};
- render_mailbox.SetSignalledTick(signalled_tick);
- render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
-}
-
-void ADSP::Wait() {
- std::scoped_lock l{mailbox_lock};
- auto response{render_mailbox.HostWaitMessage()};
- if (response != RenderMessage::AudioRenderer_RenderResponse) {
- LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
- static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
- static_cast<u32>(response));
- }
-
- ClearCommandBuffers();
-}
-
-void ADSP::ClearCommandBuffers() {
- render_mailbox.ClearCommandBuffers();
-}
-
-} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <memory>
-#include <mutex>
-
-#include "audio_core/renderer/adsp/audio_renderer.h"
-#include "common/common_types.h"
-
-namespace Core {
-namespace Memory {
-class Memory;
-}
-class System;
-} // namespace Core
-
-namespace AudioCore {
-namespace Sink {
-class Sink;
-}
-
-namespace AudioRenderer::ADSP {
-struct CommandBuffer;
-
-enum class State {
- Started,
- Stopped,
-};
-
-/**
- * Represents the ADSP embedded within the audio sysmodule.
- * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
- *
- * The kernel will run apps you program for it, Nintendo have the following:
- *
- * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
- * audio samples end up, and we skip it entirely, since we have very different backends and
- * mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
- *
- * AudioRenderer - Receives command lists generated by the audio render
- * system, processes them, and sends the samples to Gmix.
- *
- * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
- * Not much research done here, TODO if needed.
- *
- * We only implement the AudioRenderer for now.
- *
- * Communication for the apps is done through mailboxes, and some shared memory.
- */
-class ADSP {
-public:
- explicit ADSP(Core::System& system, Sink::Sink& sink);
- ~ADSP();
-
- /**
- * Start the ADSP.
- *
- * @return True if started or already running, otherwise false.
- */
- bool Start();
-
- /**
- * Stop the ADSP.
- */
- void Stop();
-
- /**
- * Get the ADSP's state.
- *
- * @return Started or Stopped.
- */
- State GetState() const;
-
- /**
- * Get the AudioRenderer mailbox to communicate with it.
- *
- * @return The AudioRenderer mailbox.
- */
- AudioRenderer_Mailbox* GetRenderMailbox();
-
- /**
- * Get the tick the ADSP was signalled.
- *
- * @return The tick the ADSP was signalled.
- */
- u64 GetSignalledTick() const;
-
- /**
- * Get the total time it took for the ADSP to run the last command lists (both command lists).
- *
- * @return The tick the ADSP was signalled.
- */
- u64 GetTimeTaken() const;
-
- /**
- * Get the last time a given command list took to run.
- *
- * @param session_id - The session id to check (0 or 1).
- * @return The time it took.
- */
- u64 GetRenderTimeTaken(u32 session_id);
-
- /**
- * Clear the remaining command count for a given session.
- *
- * @param session_id - The session id to check (0 or 1).
- */
- void ClearRemainCount(u32 session_id);
-
- /**
- * Get the remaining number of commands left to process for a command list.
- *
- * @param session_id - The session id to check (0 or 1).
- * @return The number of commands remaining.
- */
- u32 GetRemainCommandCount(u32 session_id) const;
-
- /**
- * Get the last tick a command list started processing.
- *
- * @param session_id - The session id to check (0 or 1).
- * @return The last tick the given command list started.
- */
- u64 GetRenderingStartTick(u32 session_id);
-
- /**
- * Set a command buffer to be processed.
- *
- * @param session_id - The session id to check (0 or 1).
- * @param command_buffer - The command buffer to process.
- */
- void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
-
- /**
- * Clear the command buffers (does not clear the time taken or the remaining command count)
- */
- void ClearCommandBuffers();
-
- /**
- * Signal the AudioRenderer to begin processing.
- */
- void Signal();
-
- /**
- * Wait for the AudioRenderer to finish processing.
- */
- void Wait();
-
-private:
- /// Core system
- Core::System& system;
- /// Core memory
- Core::Memory::Memory& memory;
- /// Number of systems active, used to prevent accidental shutdowns
- u8 systems_active{0};
- /// ADSP running state
- std::atomic<bool> running{false};
- /// Output sink used by the ADSP
- Sink::Sink& sink;
- /// AudioRenderer app
- std::unique_ptr<AudioRenderer> audio_renderer{};
- /// Communication for the AudioRenderer
- AudioRenderer_Mailbox render_mailbox{};
- /// Mailbox lock ffor the render mailbox
- std::mutex mailbox_lock;
-};
-
-} // namespace AudioRenderer::ADSP
-} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <array>
-#include <chrono>
-
-#include "audio_core/audio_core.h"
-#include "audio_core/common/common.h"
-#include "audio_core/renderer/adsp/audio_renderer.h"
-#include "audio_core/sink/sink.h"
-#include "common/logging/log.h"
-#include "common/microprofile.h"
-#include "common/thread.h"
-#include "core/core.h"
-#include "core/core_timing.h"
-
-MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
-
-namespace AudioCore::AudioRenderer::ADSP {
-
-void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
- adsp_messages.enqueue(message_);
- adsp_event.Set();
-}
-
-RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
- host_event.Wait();
- RenderMessage msg{RenderMessage::Invalid};
- if (!host_messages.try_dequeue(msg)) {
- LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
- }
- return msg;
-}
-
-void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
- host_messages.enqueue(message_);
- host_event.Set();
-}
-
-RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
- adsp_event.Wait();
- RenderMessage msg{RenderMessage::Invalid};
- if (!adsp_messages.try_dequeue(msg)) {
- LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
- }
- return msg;
-}
-
-CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
- return command_buffers[session_id];
-}
-
-void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
- command_buffers[session_id] = buffer;
-}
-
-u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
- return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
-}
-
-u64 AudioRenderer_Mailbox::GetSignalledTick() const {
- return signalled_tick;
-}
-
-void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
- signalled_tick = tick;
-}
-
-void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
- command_buffers[session_id].remaining_command_count = 0;
-}
-
-u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
- return command_buffers[session_id].remaining_command_count;
-}
-
-void AudioRenderer_Mailbox::ClearCommandBuffers() {
- command_buffers[0].buffer = 0;
- command_buffers[0].size = 0;
- command_buffers[0].reset_buffers = false;
- command_buffers[1].buffer = 0;
- command_buffers[1].size = 0;
- command_buffers[1].reset_buffers = false;
-}
-
-AudioRenderer::AudioRenderer(Core::System& system_)
- : system{system_}, sink{system.AudioCore().GetOutputSink()} {
- CreateSinkStreams();
-}
-
-AudioRenderer::~AudioRenderer() {
- Stop();
- for (auto& stream : streams) {
- if (stream) {
- sink.CloseStream(stream);
- }
- stream = nullptr;
- }
-}
-
-void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
- if (running) {
- return;
- }
-
- mailbox = mailbox_;
- thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); });
- running = true;
-}
-
-void AudioRenderer::Stop() {
- if (!running) {
- return;
- }
-
- for (auto& stream : streams) {
- stream->Stop();
- }
- thread.join();
- running = false;
-}
-
-void AudioRenderer::CreateSinkStreams() {
- u32 channels{sink.GetDeviceChannels()};
- for (u32 i = 0; i < MaxRendererSessions; i++) {
- std::string name{fmt::format("ADSP_RenderStream-{}", i)};
- streams[i] =
- sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
- streams[i]->SetRingSize(4);
- }
-}
-
-void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
- static constexpr char name[]{"AudioRenderer"};
- MicroProfileOnThreadCreate(name);
- Common::SetCurrentThreadName(name);
- Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
- if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
- LOG_ERROR(Service_Audio,
- "ADSP Audio Renderer -- Failed to receive initialize message from host!");
- return;
- }
-
- mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
-
- // 0.12 seconds (2304000 / 19200000)
- constexpr u64 max_process_time{2'304'000ULL};
-
- while (!stop_token.stop_requested()) {
- auto message{mailbox->ADSPWaitMessage()};
- switch (message) {
- case RenderMessage::AudioRenderer_Shutdown:
- mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
- return;
-
- case RenderMessage::AudioRenderer_Render: {
- if (system.IsShuttingDown()) [[unlikely]] {
- std::this_thread::sleep_for(std::chrono::milliseconds(5));
- mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
- continue;
- }
- std::array<bool, MaxRendererSessions> buffers_reset{};
- std::array<u64, MaxRendererSessions> render_times_taken{};
- const auto start_time{system.CoreTiming().GetClockTicks()};
-
- for (u32 index = 0; index < 2; index++) {
- auto& command_buffer{mailbox->GetCommandBuffer(index)};
- auto& command_list_processor{command_list_processors[index]};
-
- // Check this buffer is valid, as it may not be used.
- if (command_buffer.buffer != 0) {
- // If there are no remaining commands (from the previous list),
- // this is a new command list, initialize it.
- if (command_buffer.remaining_command_count == 0) {
- command_list_processor.Initialize(system, command_buffer.buffer,
- command_buffer.size, streams[index]);
- }
-
- if (command_buffer.reset_buffers && !buffers_reset[index]) {
- streams[index]->ClearQueue();
- buffers_reset[index] = true;
- }
-
- u64 max_time{max_process_time};
- if (index == 1 && command_buffer.applet_resource_user_id ==
- mailbox->GetCommandBuffer(0).applet_resource_user_id) {
- max_time = max_process_time - render_times_taken[0];
- if (render_times_taken[0] > max_process_time) {
- max_time = 0;
- }
- }
-
- max_time = std::min(command_buffer.time_limit, max_time);
- command_list_processor.SetProcessTimeMax(max_time);
-
- streams[index]->WaitFreeSpace(stop_token);
-
- // Process the command list
- {
- MICROPROFILE_SCOPE(Audio_Renderer);
- render_times_taken[index] =
- command_list_processor.Process(index) - start_time;
- }
-
- const auto end_time{system.CoreTiming().GetClockTicks()};
-
- command_buffer.remaining_command_count =
- command_list_processor.GetRemainingCommandCount();
- command_buffer.render_time_taken = end_time - start_time;
- }
- }
-
- mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
- } break;
-
- default:
- LOG_WARNING(Service_Audio,
- "ADSP AudioRenderer received an invalid message, msg={:02X}!",
- static_cast<u32>(message));
- break;
- }
- }
-}
-
-} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <memory>
-#include <thread>
-
-#include "audio_core/renderer/adsp/command_buffer.h"
-#include "audio_core/renderer/adsp/command_list_processor.h"
-#include "common/common_types.h"
-#include "common/polyfill_thread.h"
-#include "common/reader_writer_queue.h"
-#include "common/thread.h"
-
-namespace Core {
-namespace Timing {
-struct EventType;
-}
-class System;
-} // namespace Core
-
-namespace AudioCore {
-namespace Sink {
-class Sink;
-}
-
-namespace AudioRenderer::ADSP {
-
-enum class RenderMessage {
- /* 0x00 */ Invalid,
- /* 0x01 */ AudioRenderer_MapUnmap_Map,
- /* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
- /* 0x03 */ AudioRenderer_MapUnmap_Unmap,
- /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
- /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
- /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
- /* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
- /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
- /* 0x16 */ AudioRenderer_InitializeOK = 0x16,
- /* 0x20 */ AudioRenderer_RenderResponse = 0x20,
- /* 0x2A */ AudioRenderer_Render = 0x2A,
- /* 0x34 */ AudioRenderer_Shutdown = 0x34,
-};
-
-/**
- * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
- * running on the ADSP.
- */
-class AudioRenderer_Mailbox {
-public:
- /**
- * Send a message from the host to the AudioRenderer.
- *
- * @param message - The message to send to the AudioRenderer.
- */
- void HostSendMessage(RenderMessage message);
-
- /**
- * Host wait for a message from the AudioRenderer.
- *
- * @return The message returned from the AudioRenderer.
- */
- RenderMessage HostWaitMessage();
-
- /**
- * Send a message from the AudioRenderer to the host.
- *
- * @param message - The message to send to the host.
- */
- void ADSPSendMessage(RenderMessage message);
-
- /**
- * AudioRenderer wait for a message from the host.
- *
- * @return The message returned from the AudioRenderer.
- */
- RenderMessage ADSPWaitMessage();
-
- /**
- * Get the command buffer with the given session id (0 or 1).
- *
- * @param session_id - The session id to get (0 or 1).
- * @return The command buffer.
- */
- CommandBuffer& GetCommandBuffer(u32 session_id);
-
- /**
- * Set the command buffer with the given session id (0 or 1).
- *
- * @param session_id - The session id to get (0 or 1).
- * @param buffer - The command buffer to set.
- */
- void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
-
- /**
- * Get the total render time taken for the last command lists sent.
- *
- * @return Total render time taken for the last command lists.
- */
- u64 GetRenderTimeTaken() const;
-
- /**
- * Get the tick the AudioRenderer was signalled.
- *
- * @return The tick the AudioRenderer was signalled.
- */
- u64 GetSignalledTick() const;
-
- /**
- * Set the tick the AudioRenderer was signalled.
- *
- * @param tick - The tick the AudioRenderer was signalled.
- */
- void SetSignalledTick(u64 tick);
-
- /**
- * Clear the remaining command count.
- *
- * @param session_id - Index for which command list to clear (0 or 1).
- */
- void ClearRemainCount(u32 session_id);
-
- /**
- * Get the remaining command count for a given command list.
- *
- * @param session_id - Index for which command list to clear (0 or 1).
- * @return The remaining command count.
- */
- u32 GetRemainCommandCount(u32 session_id) const;
-
- /**
- * Clear the command buffers (does not clear the time taken or the remaining command count).
- */
- void ClearCommandBuffers();
-
-private:
- /// Host signalling event
- Common::Event host_event{};
- /// AudioRenderer signalling event
- Common::Event adsp_event{};
- /// Host message queue
-
- Common::ReaderWriterQueue<RenderMessage> host_messages{};
- /// AudioRenderer message queue
-
- Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
- /// Command buffers
-
- std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
- /// Tick the AudioRnederer was signalled
- u64 signalled_tick{};
-};
-
-/**
- * The AudioRenderer application running on the ADSP.
- */
-class AudioRenderer {
-public:
- explicit AudioRenderer(Core::System& system);
- ~AudioRenderer();
-
- /**
- * Start the AudioRenderer.
- *
- * @param mailbox The mailbox to use for this session.
- */
- void Start(AudioRenderer_Mailbox* mailbox);
-
- /**
- * Stop the AudioRenderer.
- */
- void Stop();
-
-private:
- /**
- * Main AudioRenderer thread, responsible for processing the command lists.
- */
- void ThreadFunc(std::stop_token stop_token);
-
- /**
- * Creates the streams which will receive the processed samples.
- */
- void CreateSinkStreams();
-
- /// Core system
- Core::System& system;
- /// Main thread
- std::jthread thread{};
- /// The current state
- std::atomic<bool> running{};
- /// The active mailbox
- AudioRenderer_Mailbox* mailbox{};
- /// The command lists to process
- std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
- /// The output sink the AudioRenderer will use
- Sink::Sink& sink;
- /// The streams which will receive the processed samples
- std::array<Sink::SinkStream*, MaxRendererSessions> streams;
-};
-
-} // namespace AudioRenderer::ADSP
-} // 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 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include "audio_core/common/common.h"
-#include "common/common_types.h"
-
-namespace AudioCore::AudioRenderer::ADSP {
-
-struct CommandBuffer {
- CpuAddr buffer;
- u64 size;
- u64 time_limit;
- u32 remaining_command_count;
- bool reset_buffers;
- u64 applet_resource_user_id;
- u64 render_time_taken;
-};
-
-} // 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 @@
#include "audio_core/sink/sink.h"
#include "core/core.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
constexpr std::array usb_device_names{
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
@@ -71,4 +71,4 @@ f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
return output_sink.GetDeviceVolume();
}
-} // namespace AudioCore::AudioRenderer
+} // 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 {
class Sink;
}
-namespace AudioRenderer {
+namespace Renderer {
/**
* An interface to an output audio device available to the Switch.
*/
@@ -76,5 +76,5 @@ private:
const u32 user_revision;
};
-} // namespace AudioRenderer
+} // namespace Renderer
} // 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 @@
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/service/audio/errors.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
: core{system_}, manager{manager_}, system{system_, rendered_event} {}
@@ -64,4 +64,4 @@ Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performa
return system.Update(input, performance, output);
}
-} // namespace AudioCore::AudioRenderer
+} // 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;
namespace AudioCore {
struct AudioRendererParameterInternal;
-namespace AudioRenderer {
+namespace Renderer {
class Manager;
/**
@@ -31,7 +31,7 @@ public:
/**
* Initialize the renderer.
- * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes
+ * Registers the system with the Renderer::Manager, allocates workbuffers and initializes
* everything to a default state.
*
* @param params - Input parameters to initialize the system with.
@@ -93,5 +93,5 @@ private:
System system;
};
-} // namespace AudioRenderer
+} // namespace Renderer
} // 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 @@
#include "audio_core/common/feature_support.h"
#include "audio_core/renderer/behavior/behavior_info.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
@@ -190,4 +190,4 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "core/hle/service/audio/errors.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Holds host and user revisions, checks whether render features can be enabled, and reports errors.
*/
@@ -264,7 +264,7 @@ public:
/**
* Check if skipping voice pitch and sample rate conversion is supported.
* This speeds up the data source commands by skipping resampling if unwanted.
- * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
+ * See AudioCore::Renderer::DecodeFromWaveBuffers
*
* @return True if supported, otherwise false.
*/
@@ -273,7 +273,7 @@ public:
/**
* Check if resetting played sample count at loop points is supported.
* This resets the number of samples played in a voice state when a loop point is reached.
- * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
+ * See AudioCore::Renderer::DecodeFromWaveBuffers
*
* @return True if supported, otherwise false.
*/
@@ -373,4 +373,4 @@ public:
u32 error_count{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/splitter/splitter_context.h"
#include "audio_core/renderer/voice/voice_context.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_,
const u32 process_handle_, BehaviorInfo& behaviour_)
@@ -536,4 +536,4 @@ Result InfoUpdater::CheckConsumedSize() {
return ResultSuccess;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "core/hle/service/audio/errors.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class BehaviorInfo;
class VoiceContext;
class MixContext;
@@ -202,4 +202,4 @@ private:
BehaviorInfo& behaviour;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/voice/voice_info.h"
#include "audio_core/renderer/voice/voice_state.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
template <typename T, CommandId Id>
T& CommandBuffer::GenerateStart(const s32 node_id) {
@@ -713,4 +713,4 @@ void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase&
GenerateEnd<CompressorCommand>(cmd);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
struct UpsamplerInfo;
struct VoiceState;
class EffectInfoBase;
@@ -465,4 +465,4 @@ private:
void GenerateEnd(T& cmd);
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/voice/voice_context.h"
#include "common/alignment.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_,
const CommandListHeader& command_list_header_,
@@ -793,4 +793,4 @@ void CommandGenerator::GeneratePerformanceCommand(
command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
namespace AudioCore {
struct AudioRendererSystemContext;
-namespace AudioRenderer {
+namespace Renderer {
class CommandBuffer;
struct CommandListHeader;
class VoiceContext;
@@ -345,5 +345,5 @@ private:
PerformanceManager* performance_manager;
};
-} // namespace AudioRenderer
+} // namespace Renderer
} // 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 @@
#include "audio_core/common/common.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
struct CommandListHeader {
u64 buffer_size;
@@ -19,4 +19,4 @@ struct CommandListHeader {
u32 sample_rate;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/command_processing_time_estimator.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
u32 CommandProcessingTimeEstimatorVersion1::Estimate(
const PcmInt16DataSourceVersion1Command& command) const {
@@ -27,12 +27,12 @@ u32 CommandProcessingTimeEstimatorVersion1::Estimate(
u32 CommandProcessingTimeEstimatorVersion1::Estimate(
const AdpcmDataSourceVersion1Command& command) const {
- return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+ return static_cast<u32>(command.pitch * 0.46f * 1.2f);
}
u32 CommandProcessingTimeEstimatorVersion1::Estimate(
const AdpcmDataSourceVersion2Command& command) const {
- return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+ return static_cast<u32>(command.pitch * 0.46f * 1.2f);
}
u32 CommandProcessingTimeEstimatorVersion1::Estimate(
@@ -3617,4 +3617,4 @@ u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& co
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/commands.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Estimate the processing time required for all commands.
*/
@@ -251,4 +251,4 @@ private:
u32 buffer_count{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <span>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/data_source/adpcm.h"
#include "audio_core/renderer/command/data_source/decode.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+void AdpcmDataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
"rate {} target sample rate {} src quality {}\n",
output_index, sample_rate, processor.target_sample_rate, src_quality);
}
-void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListProcessor& processor) {
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
+ for (auto& wave_buffer : wave_buffers) {
+ wave_buffer.loop_start_offset = wave_buffer.start_offset;
+ wave_buffer.loop_end_offset = wave_buffer.end_offset;
+ wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
+ }
+
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::Adpcm},
.output{out_buffer},
@@ -41,18 +47,18 @@ void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& p
DecodeFromWaveBuffers(*processor.memory, args);
}
-bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+bool AdpcmDataSourceVersion1Command::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+void AdpcmDataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
"rate {} target sample rate {} src quality {}\n",
output_index, sample_rate, processor.target_sample_rate, src_quality);
}
-void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+void AdpcmDataSourceVersion2Command::Process(const AudioRenderer::CommandListProcessor& processor) {
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
@@ -77,8 +83,8 @@ void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& p
DecodeFromWaveBuffers(*processor.memory, args);
}
-bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+bool AdpcmDataSourceVersion2Command::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
* into the output_index mix buffer.
@@ -27,14 +28,14 @@ struct AdpcmDataSourceVersion1Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -42,13 +43,13 @@ struct AdpcmDataSourceVersion1Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
- /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
@@ -75,14 +76,14 @@ struct AdpcmDataSourceVersion2Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -90,13 +91,13 @@ struct AdpcmDataSourceVersion2Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
- /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
@@ -116,4 +117,4 @@ struct AdpcmDataSourceVersion2Command : ICommand {
u64 data_size;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/scratch_buffer.h"
#include "core/memory.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
constexpr u32 TempBufferSize = 0x3F00;
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
@@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
return 0;
}
- auto samples_to_process{
- std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
+ auto start_pos{req.start_offset + req.offset};
+ auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)};
+ if (samples_to_process == 0) {
+ return 0;
+ }
auto samples_to_read{samples_to_process};
- auto start_pos{req.start_offset + req.offset};
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
samples_remaining_in_frame};
@@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
* @param args - The wavebuffer data, and information for how to decode it.
*/
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
+ static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
+ auto& played_samples, auto& consumed) -> void {
+ voice_state.wave_buffer_valid[index] = false;
+ voice_state.loop_count = 0;
+
+ if (wavebuffer.stream_ended) {
+ played_samples = 0;
+ }
+
+ index = (index + 1) % MaxWaveBuffers;
+ consumed++;
+ };
auto& voice_state{*args.voice_state};
auto remaining_sample_count{args.sample_count};
auto fraction{voice_state.fraction};
- const auto sample_rate_ratio{
- (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
- args.pitch};
+ const auto sample_rate_ratio{Common::FixedPoint<49, 15>(
+ (f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)};
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
if (size_required < 0) {
@@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
auto end_offset{wavebuffer.end_offset};
if (wavebuffer.loop && voice_state.loop_count > 0 &&
- wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
start_offset = wavebuffer.loop_start_offset;
end_offset = wavebuffer.loop_end_offset;
}
- DecodeArg decode_arg{.buffer{wavebuffer.buffer},
- .buffer_size{wavebuffer.buffer_size},
- .start_offset{start_offset},
- .end_offset{end_offset},
- .channel_count{args.channel_count},
- .coefficients{},
- .adpcm_context{nullptr},
- .target_channel{args.channel},
- .offset{offset},
- .samples_to_read{samples_to_read - samples_read}};
+ DecodeArg decode_arg{
+ .buffer{wavebuffer.buffer},
+ .buffer_size{wavebuffer.buffer_size},
+ .start_offset{start_offset},
+ .end_offset{end_offset},
+ .channel_count{args.channel_count},
+ .coefficients{},
+ .adpcm_context{nullptr},
+ .target_channel{args.channel},
+ .offset{offset},
+ .samples_to_read{samples_to_read - samples_read},
+ };
s32 samples_decoded{0};
@@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
temp_buffer_pos += samples_decoded;
offset += samples_decoded;
- if (samples_decoded == 0 || offset >= end_offset - start_offset) {
- offset = 0;
- if (!wavebuffer.loop) {
- voice_state.wave_buffer_valid[wavebuffer_index] = false;
- voice_state.loop_count = 0;
-
- if (wavebuffer.stream_ended) {
- played_sample_count = 0;
- }
-
- wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
- wavebuffers_consumed++;
- } else {
- voice_state.loop_count++;
- if (wavebuffer.loop_count > 0 &&
- (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
- voice_state.wave_buffer_valid[wavebuffer_index] = false;
- voice_state.loop_count = 0;
-
- if (wavebuffer.stream_ended) {
- played_sample_count = 0;
- }
-
- wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
- wavebuffers_consumed++;
- }
-
- if (samples_decoded == 0) {
- is_buffer_starved = true;
- break;
- }
-
- if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
- played_sample_count = 0;
- }
+ if (samples_decoded && offset < end_offset - start_offset) {
+ continue;
+ }
+
+ offset = 0;
+ if (wavebuffer.loop) {
+ voice_state.loop_count++;
+ if (wavebuffer.loop_count >= 0 &&
+ (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
+ EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
+ wavebuffers_consumed);
+ }
+
+ if (samples_decoded == 0) {
+ is_buffer_starved = true;
+ break;
+ }
+
+ if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
+ played_sample_count = 0;
}
+ } else {
+ EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
+ wavebuffers_consumed);
}
}
@@ -423,4 +425,4 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
voice_state.fraction = fraction;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 {
class Memory;
}
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
struct DecodeFromWaveBuffersArgs {
SampleFormat sample_format;
@@ -56,4 +56,4 @@ struct DecodeArg {
*/
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/data_source/decode.h"
#include "audio_core/renderer/command/data_source/pcm_float.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+void PcmFloatDataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string +=
fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
@@ -16,10 +16,17 @@ void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& p
processor.target_sample_rate, src_quality);
}
-void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+void PcmFloatDataSourceVersion1Command::Process(
+ const AudioRenderer::CommandListProcessor& processor) {
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count);
+ for (auto& wave_buffer : wave_buffers) {
+ wave_buffer.loop_start_offset = wave_buffer.start_offset;
+ wave_buffer.loop_end_offset = wave_buffer.end_offset;
+ wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
+ }
+
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmFloat},
.output{out_buffer},
@@ -41,11 +48,12 @@ void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor
DecodeFromWaveBuffers(*processor.memory, args);
}
-bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+bool PcmFloatDataSourceVersion1Command::Verify(
+ const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+void PcmFloatDataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string +=
fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
@@ -54,7 +62,8 @@ void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& p
processor.target_sample_rate, src_quality);
}
-void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+void PcmFloatDataSourceVersion2Command::Process(
+ const AudioRenderer::CommandListProcessor& processor) {
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count);
@@ -79,8 +88,9 @@ void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor
DecodeFromWaveBuffers(*processor.memory, args);
}
-bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+bool PcmFloatDataSourceVersion2Command::Verify(
+ const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
* into the output_index mix buffer.
@@ -25,14 +26,14 @@ struct PcmFloatDataSourceVersion1Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -40,13 +41,13 @@ struct PcmFloatDataSourceVersion1Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
- /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
@@ -73,14 +74,14 @@ struct PcmFloatDataSourceVersion2Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -88,13 +89,13 @@ struct PcmFloatDataSourceVersion2Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
- /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
@@ -110,4 +111,4 @@ struct PcmFloatDataSourceVersion2Command : ICommand {
CpuAddr voice_state;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <span>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/data_source/decode.h"
#include "audio_core/renderer/command/data_source/pcm_int16.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+void PcmInt16DataSourceVersion1Command::Dump(const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string +=
fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
@@ -18,10 +18,17 @@ void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& p
processor.target_sample_rate, src_quality);
}
-void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+void PcmInt16DataSourceVersion1Command::Process(
+ const AudioRenderer::CommandListProcessor& processor) {
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count);
+ for (auto& wave_buffer : wave_buffers) {
+ wave_buffer.loop_start_offset = wave_buffer.start_offset;
+ wave_buffer.loop_end_offset = wave_buffer.end_offset;
+ wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
+ }
+
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmInt16},
.output{out_buffer},
@@ -43,11 +50,12 @@ void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor
DecodeFromWaveBuffers(*processor.memory, args);
}
-bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+bool PcmInt16DataSourceVersion1Command::Verify(
+ const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+void PcmInt16DataSourceVersion2Command::Dump(const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string +=
fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
@@ -56,7 +64,8 @@ void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& p
processor.target_sample_rate, src_quality);
}
-void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+void PcmInt16DataSourceVersion2Command::Process(
+ const AudioRenderer::CommandListProcessor& processor) {
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count);
DecodeFromWaveBuffersArgs args{
@@ -80,8 +89,9 @@ void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor
DecodeFromWaveBuffers(*processor.memory, args);
}
-bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+bool PcmInt16DataSourceVersion2Command::Verify(
+ const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
* into the output_index mix buffer.
@@ -25,14 +26,14 @@ struct PcmInt16DataSourceVersion1Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -40,13 +41,13 @@ struct PcmInt16DataSourceVersion1Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
- /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
@@ -72,26 +73,26 @@ struct PcmInt16DataSourceVersion2Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
- /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ /// Flags to control decoding (see AudioCore::Renderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
@@ -107,4 +108,4 @@ struct PcmInt16DataSourceVersion2Command : ICommand {
CpuAddr voice_state;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/aux_.h"
#include "audio_core/renderer/effect/aux_.h"
#include "core/core.h"
#include "core/memory.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Reset an AuxBuffer.
*
@@ -175,13 +175,13 @@ static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr return_info_,
return read_count_;
}
-void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void AuxCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
input, output);
}
-void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
+void AuxCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto input_buffer{
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
auto output_buffer{
@@ -208,8 +208,8 @@ void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool AuxCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
* memory, and reading into the output buffer from game memory.
@@ -24,14 +25,14 @@ struct AuxCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct AuxCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input;
@@ -63,4 +64,4 @@ struct AuxCommand : ICommand {
bool effect_enabled;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/biquad_filter.h"
#include "audio_core/renderer/voice/voice_state.h"
#include "common/bit_cast.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Biquad filter float implementation.
*
@@ -76,14 +76,14 @@ static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> inp
}
}
-void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void BiquadFilterCommand::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format(
"BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
input, output, needs_init, use_float_processing);
}
-void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
+void BiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
if (needs_init) {
*state_ = {};
@@ -103,8 +103,8 @@ void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool BiquadFilterCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/voice/voice_state.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
* the output mix buffer.
@@ -26,14 +27,14 @@ struct BiquadFilterCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct BiquadFilterCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input;
@@ -71,4 +72,4 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
std::array<s16, 3>& b, std::array<s16, 2>& a,
VoiceState::BiquadFilterState& state, const u32 sample_count);
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/capture.h"
#include "audio_core/renderer/effect/aux_.h"
#include "core/memory.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Reset an AuxBuffer.
*
@@ -118,13 +118,13 @@ static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_in
return write_count_;
}
-void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void CaptureCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
input, output);
}
-void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
+void CaptureCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
if (effect_enabled) {
auto input_buffer{
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
@@ -135,8 +135,8 @@ void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool CaptureCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
* address.
@@ -24,14 +25,14 @@ struct CaptureCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct CaptureCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input;
@@ -59,4 +60,4 @@ struct CaptureCommand : ICommand {
bool effect_enabled;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <span>
#include <vector>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/compressor.h"
#include "audio_core/renderer/effect/compressor.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state) {
@@ -110,7 +110,7 @@ static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& param
}
}
-void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void CompressorCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
for (s16 i = 0; i < parameter.channel_count; i++) {
@@ -123,7 +123,7 @@ void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
string += "\n";
}
-void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
+void CompressorCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::array<std::span<s32>, MaxChannels> output_buffers{};
@@ -148,8 +148,8 @@ void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
processor.sample_count);
}
-bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool CompressorCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/compressor.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for limiting volume between a high and low threshold.
* Version 1.
@@ -26,14 +27,14 @@ struct CompressorCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct CompressorCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
@@ -57,4 +58,4 @@ struct CompressorCommand : ICommand {
bool effect_enabled;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/delay.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Update the DelayInfo state according to the given parameters.
*
@@ -194,7 +194,7 @@ static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayIn
}
}
-void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void DelayCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
for (u32 i = 0; i < MaxChannels; i++) {
@@ -207,7 +207,7 @@ void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proce
string += "\n";
}
-void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
+void DelayCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::array<std::span<s32>, MaxChannels> output_buffers{};
@@ -231,8 +231,8 @@ void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
processor.sample_count);
}
-bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool DelayCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/delay.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
* and state, outputs receives the delayed samples.
@@ -26,14 +27,14 @@ struct DelayCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct DelayCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
@@ -57,4 +58,4 @@ struct DelayCommand : ICommand {
bool effect_enabled;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <numbers>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
#include "common/polyfill_ranges.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
5.0f,
@@ -394,7 +394,7 @@ static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& par
}
}
-void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void I3dl2ReverbCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
for (u32 i = 0; i < parameter.channel_count; i++) {
@@ -407,7 +407,7 @@ void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
string += "\n";
}
-void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
+void I3dl2ReverbCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::array<std::span<s32>, MaxChannels> output_buffers{};
@@ -431,8 +431,8 @@ void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
processor.sample_count);
}
-bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool I3dl2ReverbCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/i3dl2.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
* the I3DL2 spec, outputs receives the results.
@@ -26,14 +27,14 @@ struct I3dl2ReverbCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct I3dl2ReverbCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
@@ -57,4 +58,4 @@ struct I3dl2ReverbCommand : ICommand {
bool effect_enabled;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/light_limiter.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Update the LightLimiterInfo state according to the given parameters.
* A no-op.
@@ -133,8 +133,8 @@ static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& p
}
}
-void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void LightLimiterVersion1Command::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
@@ -146,7 +146,7 @@ void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListP
string += "\n";
}
-void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+void LightLimiterVersion1Command::Process(const AudioRenderer::CommandListProcessor& processor) {
std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::array<std::span<s32>, MaxChannels> output_buffers{};
@@ -172,12 +172,12 @@ void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& proc
processor.sample_count, statistics);
}
-bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+bool LightLimiterVersion1Command::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void LightLimiterVersion2Command::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
@@ -189,7 +189,7 @@ void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListP
string += "\n";
}
-void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+void LightLimiterVersion2Command::Process(const AudioRenderer::CommandListProcessor& processor) {
std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::array<std::span<s32>, MaxChannels> output_buffers{};
@@ -215,8 +215,8 @@ void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& proc
processor.sample_count, statistics);
}
-bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+bool LightLimiterVersion2Command::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/light_limiter.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for limiting volume between a high and low threshold.
* Version 1.
@@ -26,14 +27,14 @@ struct LightLimiterVersion1Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct LightLimiterVersion1Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
@@ -68,21 +69,21 @@ struct LightLimiterVersion2Command : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
@@ -100,4 +101,4 @@ struct LightLimiterVersion2Command : ICommand {
bool effect_enabled;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/biquad_filter.h"
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void MultiTapBiquadFilterCommand::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format(
"MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
input, output, needs_init[0], needs_init[1]);
}
-void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
+void MultiTapBiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
if (filter_tap_count > MaxBiquadFilters) {
LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
filter_tap_count = MaxBiquadFilters;
@@ -38,8 +38,8 @@ void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& proc
}
}
-bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool MultiTapBiquadFilterCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/voice/voice_info.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for applying multiple biquads at once.
*/
@@ -25,14 +26,14 @@ struct MultiTapBiquadFilterCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct MultiTapBiquadFilterCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input;
@@ -56,4 +57,4 @@ struct MultiTapBiquadFilterCommand : ICommand {
u8 filter_tap_count;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <numbers>
#include <ranges>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/effect/reverb.h"
#include "common/polyfill_ranges.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
53.9532470703125f,
@@ -396,7 +396,7 @@ static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, Rever
}
}
-void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void ReverbCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format(
"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
string += "\n";
}
-void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
+void ReverbCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::array<std::span<s32>, MaxChannels> output_buffers{};
@@ -435,8 +435,8 @@ void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
processor.sample_count);
}
-bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool ReverbCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/reverb.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
* the results.
@@ -26,14 +27,14 @@ struct ReverbCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -41,7 +42,7 @@ struct ReverbCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
@@ -59,4 +60,4 @@ struct ReverbCommand : ICommand {
bool long_size_pre_delay_supported;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#pragma once
+#include <string>
+
#include "audio_core/common/common.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+using namespace ::AudioCore::ADSP;
+
enum class CommandId : u8 {
/* 0x00 */ Invalid,
/* 0x01 */ DataSourcePcmInt16Version1,
@@ -59,14 +63,15 @@ struct ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0;
+ virtual void Dump(const AudioRenderer::CommandListProcessor& processor,
+ std::string& string) = 0;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- virtual void Process(const ADSP::CommandListProcessor& processor) = 0;
+ virtual void Process(const AudioRenderer::CommandListProcessor& processor) = 0;
/**
* Verify this command's data is valid.
@@ -74,7 +79,7 @@ struct ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0;
+ virtual bool Verify(const AudioRenderer::CommandListProcessor& processor) = 0;
/// Command magic 0xCAFEBABE
u32 magic{};
@@ -90,4 +95,4 @@ struct ICommand {
u32 node_id{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <string>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/mix/clear_mix.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void ClearMixBufferCommand::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format("ClearMixBufferCommand\n");
}
-void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
+void ClearMixBufferCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
}
-bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool ClearMixBufferCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for a clearing the mix buffers.
* Used at the start of each command list.
@@ -24,14 +25,14 @@ struct ClearMixBufferCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct ClearMixBufferCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/mix/copy_mix.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void CopyMixBufferCommand::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
output_index);
}
-void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
+void CopyMixBufferCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
@@ -20,8 +20,8 @@ void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor)
std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
}
-bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool CopyMixBufferCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for a copying a mix buffer from input to output.
*/
@@ -23,14 +24,14 @@ struct CopyMixBufferCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -38,7 +39,7 @@ struct CopyMixBufferCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input_index;
@@ -46,4 +47,4 @@ struct CopyMixBufferCommand : ICommand {
s16 output_index;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/common/common.h"
-#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
* according to decay.
@@ -36,13 +36,13 @@ static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
}
}
-void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void DepopForMixBuffersCommand::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
count, decay.to_float());
}
-void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) {
+void DepopForMixBuffersCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto end_index{std::min(processor.buffer_count, input + count)};
std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
@@ -57,8 +57,8 @@ void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& proces
}
}
-bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool DepopForMixBuffersCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for depopping a mix buffer.
* Adds a cumulation of previous samples to the current mix buffer with a decay.
@@ -25,14 +26,14 @@ struct DepopForMixBuffersCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct DepopForMixBuffersCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Starting input mix buffer index
u32 input;
@@ -52,4 +53,4 @@ struct DepopForMixBuffersCommand : ICommand {
CpuAddr depop_buffer;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/mix/depop_prepare.h"
#include "audio_core/renderer/voice/voice_state.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void DepopPrepareCommand::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format("DepopPrepareCommand\n\tinputs: ");
for (u32 i = 0; i < buffer_count; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
@@ -17,7 +17,7 @@ void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor
string += "\n";
}
-void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
+void DepopPrepareCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto samples{reinterpret_cast<s32*>(previous_samples)};
auto buffer{reinterpret_cast<s32*>(depop_buffer)};
@@ -29,8 +29,8 @@ void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool DepopPrepareCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for preparing depop.
* Adds the previusly output last samples to the depop buffer.
@@ -24,14 +25,14 @@ struct DepopPrepareCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct DepopPrepareCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Depop buffer offset for each mix buffer
std::array<s16, MaxMixBuffers> inputs;
@@ -51,4 +52,4 @@ struct DepopPrepareCommand : ICommand {
CpuAddr depop_buffer;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <limits>
#include <span>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/mix/mix.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Mix input mix buffer into output mix buffer, with volume applied to the input.
*
@@ -28,7 +28,7 @@ static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f3
}
}
-void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void MixCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("MixCommand");
string += fmt::format("\n\tinput {:02X}", input_index);
@@ -37,7 +37,7 @@ void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& process
string += "\n";
}
-void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
+void MixCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
@@ -63,8 +63,8 @@ void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool MixCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
* applied to the input.
@@ -24,14 +25,14 @@ struct MixCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct MixCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
@@ -51,4 +52,4 @@ struct MixCommand : ICommand {
f32 volume;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/mix/mix_ramp.h"
#include "common/fixed_point.h"
#include "common/logging/log.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
template <size_t Q>
s32 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
template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
-void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+void MixRampCommand::Dump(const AudioRenderer::CommandListProcessor& processor,
+ std::string& string) {
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
string += fmt::format("MixRampCommand");
string += fmt::format("\n\tinput {:02X}", input_index);
@@ -44,7 +45,7 @@ void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::stri
string += "\n";
}
-void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
+void MixRampCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
@@ -75,8 +76,8 @@ void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool MixRampCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
* applied to the input, and volume ramping to smooth out the transition.
@@ -25,14 +26,14 @@ struct MixRampCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct MixRampCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
@@ -70,4 +71,4 @@ template <size_t Q>
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
u32 sample_count);
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/mix/mix_ramp.h"
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+void MixRampGroupedCommand::Dump(const AudioRenderer::CommandListProcessor& processor,
+ std::string& string) {
string += "MixRampGroupedCommand";
for (u32 i = 0; i < buffer_count; i++) {
string += fmt::format("\n\t{}", i);
@@ -21,7 +22,7 @@ void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, st
}
}
-void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) {
+void MixRampGroupedCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
for (u32 i = 0; i < buffer_count; i++) {
@@ -58,8 +59,8 @@ void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor)
}
}
-bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool MixRampGroupedCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
* a volume applied to the input, and volume ramping to smooth out the transition.
@@ -25,14 +26,14 @@ struct MixRampGroupedCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct MixRampGroupedCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
@@ -58,4 +59,4 @@ struct MixRampGroupedCommand : ICommand {
CpuAddr previous_samples;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/mix/volume.h"
#include "common/fixed_point.h"
#include "common/logging/log.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Apply volume to the input mix buffer, saving to the output buffer.
*
@@ -29,7 +29,7 @@ static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input,
}
}
-void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void VolumeCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("VolumeCommand");
string += fmt::format("\n\tinput {:02X}", input_index);
@@ -38,7 +38,7 @@ void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc
string += "\n";
}
-void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
+void VolumeCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
// If input and output buffers are the same, and the volume is 1.0f, this won't do
// anything, so just skip.
if (input_index == output_index && volume == 1.0f) {
@@ -65,8 +65,8 @@ void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool VolumeCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for applying volume to a mix buffer.
*/
@@ -23,14 +24,14 @@ struct VolumeCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -38,7 +39,7 @@ struct VolumeCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
@@ -50,4 +51,4 @@ struct VolumeCommand : ICommand {
f32 volume;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/mix/volume_ramp.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Apply volume with ramping to the input mix buffer, saving to the output buffer.
*
@@ -38,7 +38,8 @@ static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32>
}
}
-void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+void VolumeRampCommand::Dump(const AudioRenderer::CommandListProcessor& processor,
+ std::string& string) {
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
string += fmt::format("VolumeRampCommand");
string += fmt::format("\n\tinput {:02X}", input_index);
@@ -49,7 +50,7 @@ void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::s
string += "\n";
}
-void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
+void VolumeRampCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
@@ -77,8 +78,8 @@ void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool VolumeRampCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
* out the transition.
@@ -24,14 +25,14 @@ struct VolumeRampCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct VolumeRampCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
@@ -53,4 +54,4 @@ struct VolumeRampCommand : ICommand {
f32 volume;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/performance/performance.h"
#include "core/core.h"
#include "core/core_timing.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void PerformanceCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
}
-void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
+void PerformanceCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto base{entry_address.translated_address};
if (state == PerformanceState::Start) {
auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
*start_time_ptr =
- static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
- processor.current_processing_time);
+ static_cast<u32>(processor.system->CoreTiming().GetGlobalTimeUs().count() -
+ processor.start_time - processor.current_processing_time);
} else if (state == PerformanceState::Stop) {
auto processed_time_ptr{
reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
@@ -27,14 +27,14 @@ void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
*processed_time_ptr =
- static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
- processor.current_processing_time);
+ static_cast<u32>(processor.system->CoreTiming().GetGlobalTimeUs().count() -
+ processor.start_time - processor.current_processing_time);
(*entry_count_ptr)++;
}
}
-bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool PerformanceCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
*/
@@ -25,14 +26,14 @@ struct PerformanceCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct PerformanceCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// State of the performance
PerformanceState state;
@@ -48,4 +49,4 @@ struct PerformanceCommand : ICommand {
PerformanceEntryAddresses entry_address;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void DownMix6chTo2chCommand::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
@@ -19,7 +19,7 @@ void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProces
string += "\n";
}
-void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) {
+void DownMix6chTo2chCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
auto in_front_left{
processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
auto in_front_right{
@@ -67,8 +67,8 @@ void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor
std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
}
-bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool DownMix6chTo2chCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for downmixing 6 channels to 2.
* Channel layout (SMPTE):
@@ -31,14 +32,14 @@ struct DownMix6chTo2chCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -46,7 +47,7 @@ struct DownMix6chTo2chCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
@@ -56,4 +57,4 @@ struct DownMix6chTo2chCommand : ICommand {
std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/resample/resample.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
const Common::FixedPoint<49, 15>& sample_rate_ratio,
@@ -880,4 +880,4 @@ void Resample(std::span<s32> output, std::span<const s16> input,
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Resample an input buffer into an output buffer, according to the sample_rate_ratio.
*
@@ -26,4 +26,4 @@ void Resample(std::span<s32> output, std::span<const s16> input,
const Common::FixedPoint<49, 15>& sample_rate_ratio,
Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <array>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/resample/upsample.h"
#include "audio_core/renderer/upsampler/upsampler_info.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
*
@@ -198,7 +198,7 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
}
}
-auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+auto UpsampleCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) -> void {
string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
source_sample_count, source_sample_rate);
@@ -213,7 +213,7 @@ auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& pr
string += "\n";
}
-void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
+void UpsampleCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
const auto input_count{std::min(info->input_count, buffer_count)};
const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
@@ -234,8 +234,8 @@ void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool UpsampleCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for upsampling a mix buffer to 48Khz.
* Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
@@ -24,14 +25,14 @@ struct UpsampleCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -39,7 +40,7 @@ struct UpsampleCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Pointer to the output samples buffer.
CpuAddr samples_buffer;
@@ -57,4 +58,4 @@ struct UpsampleCommand : ICommand {
CpuAddr upsampler_info;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <vector>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/sink/circular_buffer.h"
#include "core/memory.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
- std::string& string) {
+void CircularBufferSinkCommand::Dump(
+ [[maybe_unused]] const AudioRenderer::CommandListProcessor& processor, std::string& string) {
string += fmt::format(
"CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
input_count, size, pos);
@@ -20,7 +20,7 @@ void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListPro
string += "\n";
}
-void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
+void CircularBufferSinkCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
constexpr s32 min{std::numeric_limits<s16>::min()};
constexpr s32 max{std::numeric_limits<s16>::max()};
@@ -41,8 +41,8 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
}
}
-bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool CircularBufferSinkCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for sinking samples to a circular buffer.
*/
@@ -23,14 +24,14 @@ struct CircularBufferSinkCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -38,7 +39,7 @@ struct CircularBufferSinkCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Number of input mix buffers
u32 input_count;
@@ -52,4 +53,4 @@ struct CircularBufferSinkCommand : ICommand {
u32 pos;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <algorithm>
-#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
#include "audio_core/renderer/command/sink/device.h"
#include "audio_core/sink/sink.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
-void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+void DeviceSinkCommand::Dump([[maybe_unused]] const AudioRenderer::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
std::string_view(name), session_id, input_count);
@@ -19,7 +19,7 @@ void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
string += "\n";
}
-void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
+void DeviceSinkCommand::Process(const AudioRenderer::CommandListProcessor& processor) {
constexpr s32 min = std::numeric_limits<s16>::min();
constexpr s32 max = std::numeric_limits<s16>::max();
@@ -51,8 +51,8 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
}
}
-bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
+bool DeviceSinkCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {
return true;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP::AudioRenderer {
class CommandListProcessor;
}
+namespace AudioCore::Renderer {
+
/**
* AudioRenderer command for sinking samples to an output device.
*/
@@ -25,14 +26,14 @@ struct DeviceSinkCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
- void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+ void Dump(const AudioRenderer::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
- void Process(const ADSP::CommandListProcessor& processor) override;
+ void Process(const AudioRenderer::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
@@ -40,7 +41,7 @@ struct DeviceSinkCommand : ICommand {
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
- bool Verify(const ADSP::CommandListProcessor& processor) override;
+ bool Verify(const AudioRenderer::CommandListProcessor& processor) override;
/// Device name
char name[0x100];
@@ -54,4 +55,4 @@ struct DeviceSinkCommand : ICommand {
std::array<s16, MaxChannels> inputs;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/aux_.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
const PoolMapper& pool_mapper) {
@@ -90,4 +90,4 @@ CpuAddr AuxInfo::GetWorkbuffer(s32 index) {
return workbuffers[index].GetReference(true);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/effect_info_base.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Auxiliary Buffer used for Aux commands.
* Send and return buffers are available (names from the game's perspective).
@@ -120,4 +120,4 @@ public:
CpuAddr GetWorkbuffer(s32 index) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/biquad_filter.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
@@ -49,4 +49,4 @@ void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state,
EffectResultState& dsp_state) {}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/effect_info_base.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class BiquadFilterInfo : public EffectInfoBase {
public:
@@ -76,4 +76,4 @@ public:
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/buffer_mixer.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
@@ -46,4 +46,4 @@ void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {}
void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state,
EffectResultState& dsp_state) {}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/effect_info_base.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class BufferMixerInfo : public EffectInfoBase {
public:
@@ -72,4 +72,4 @@ public:
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/aux_.h"
#include "audio_core/renderer/effect/capture.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
const PoolMapper& pool_mapper) {
@@ -79,4 +79,4 @@ CpuAddr CaptureInfo::GetWorkbuffer(s32 index) {
return workbuffers[index].GetReference(true);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/effect_info_base.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class CaptureInfo : public EffectInfoBase {
public:
@@ -62,4 +62,4 @@ public:
CpuAddr GetWorkbuffer(s32 index) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/compressor.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {}
@@ -37,4 +37,4 @@ CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
return GetSingleBuffer(index);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class CompressorInfo : public EffectInfoBase {
public:
@@ -103,4 +103,4 @@ public:
CpuAddr GetWorkbuffer(s32 index) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/delay.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
const PoolMapper& pool_mapper) {
@@ -90,4 +90,4 @@ CpuAddr DelayInfo::GetWorkbuffer(s32 index) {
return GetSingleBuffer(index);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class DelayInfo : public EffectInfoBase {
public:
@@ -132,4 +132,4 @@ public:
CpuAddr GetWorkbuffer(s32 index) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/effect_context.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
std::span<EffectResultState> result_states_cpu_,
@@ -38,4 +38,4 @@ void EffectContext::UpdateStateByDspShared() {
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/effect_result_state.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class EffectContext {
public:
@@ -72,4 +72,4 @@ private:
size_t dsp_state_count{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/memory/pool_mapper.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Base of all effects. Holds various data and functions used for all derived effects.
* Should not be used directly.
@@ -432,4 +432,4 @@ protected:
std::array<u8, sizeof(State)> state{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/reverb.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Reset an effect, and create a new one of the given type.
*
@@ -68,4 +68,4 @@ static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type)
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
struct EffectResultState {
std::array<u8, 0x80> state;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/i3dl2.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
@@ -91,4 +91,4 @@ CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) {
return GetSingleBuffer(index);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class I3dl2ReverbInfo : public EffectInfoBase {
public:
@@ -198,4 +198,4 @@ public:
CpuAddr GetWorkbuffer(s32 index) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/light_limiter.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
@@ -78,4 +78,4 @@ CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) {
return GetSingleBuffer(index);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class LightLimiterInfo : public EffectInfoBase {
public:
@@ -135,4 +135,4 @@ public:
CpuAddr GetWorkbuffer(s32 index) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/effect/reverb.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
const PoolMapper& pool_mapper) {
@@ -90,4 +90,4 @@ CpuAddr ReverbInfo::GetWorkbuffer(s32 index) {
return GetSingleBuffer(index);
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class ReverbInfo : public EffectInfoBase {
public:
@@ -187,4 +187,4 @@ public:
CpuAddr GetWorkbuffer(s32 index) override;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/memory/memory_pool_info.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Represents a region of mapped or unmapped memory.
@@ -121,4 +121,4 @@ private:
CpuAddr dsp_address;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/memory/memory_pool_info.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
CpuAddr MemoryPoolInfo::GetCpuAddress() const {
return cpu_address;
@@ -58,4 +58,4 @@ bool MemoryPoolInfo::IsUsed() const {
return in_use;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/common/common.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
*/
@@ -167,4 +167,4 @@ private:
bool in_use{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/svc.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
PoolMapper::PoolMapper(u32 process_handle_, bool force_map_)
: process_handle{process_handle_}, force_map{force_map_} {}
@@ -240,4 +240,4 @@ bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory,
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "core/hle/service/audio/errors.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class AddressInfo;
/**
@@ -176,4 +176,4 @@ private:
bool force_map;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/splitter/splitter_context.h"
#include "common/polyfill_ranges.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_,
const u32 count_, std::span<s32> effect_process_order_buffer_,
@@ -139,4 +139,4 @@ EdgeMatrix& MixContext::GetEdgeMatrix() {
return edge_matrix;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/nodes/node_states.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class SplitterContext;
/*
@@ -121,4 +121,4 @@ private:
EdgeMatrix edge_matrix{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/nodes/edge_matrix.h"
#include "audio_core/renderer/splitter/splitter_context.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior)
: effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_},
@@ -117,4 +117,4 @@ bool MixInfo::HasAnyConnection() const {
return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/common/common.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class EdgeMatrix;
class SplitterContext;
class EffectContext;
@@ -121,4 +121,4 @@ public:
const bool long_size_pre_delay_supported;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Represents an array of bits used for nodes and edges for the mixing graph.
*/
@@ -22,4 +22,4 @@ struct BitArray {
u32 size{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/nodes/edge_matrix.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer,
[[maybe_unused]] const u64 node_buffer_size, const u32 count_) {
@@ -35,4 +35,4 @@ u32 EdgeMatrix::GetNodeCount() const {
return count;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/alignment.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* An edge matrix, holding the connections for each node to every other node in the graph.
*/
@@ -79,4 +79,4 @@ private:
u32 count;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/nodes/node_states.h"
#include "common/logging/log.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size,
const u32 count) {
@@ -138,4 +138,4 @@ std::pair<std::span<u32>::reverse_iterator, size_t> NodeStates::GetSortedResuls(
return {results.rbegin(), result_pos};
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/alignment.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Graph utility functions for sorting and getting results from the DAG.
*/
@@ -192,4 +192,4 @@ private:
Stack stack{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/command_generator.h"
#include "audio_core/renderer/performance/detail_aspect.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
DetailAspect::DetailAspect(CommandGenerator& command_generator_,
const PerformanceEntryType entry_type, const s32 node_id_,
@@ -22,4 +22,4 @@ DetailAspect::DetailAspect(CommandGenerator& command_generator_,
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class CommandGenerator;
/**
@@ -29,4 +29,4 @@ public:
s32 node_id;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/command/command_generator.h"
#include "audio_core/renderer/performance/entry_aspect.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type,
const s32 node_id_)
@@ -20,4 +20,4 @@ EntryAspect::EntryAspect(CommandGenerator& command_generator_, const Performance
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class CommandGenerator;
/**
@@ -28,4 +28,4 @@ public:
s32 node_id;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/performance/performance_entry.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
enum class PerformanceDetailType : u8 {
Invalid,
@@ -47,4 +47,4 @@ struct PerformanceDetailVersion2 {
static_assert(sizeof(PerformanceDetailVersion2) == 0x18,
"PerformanceDetailVersion2 has the wrong size!");
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
enum class PerformanceEntryType : u8 {
Invalid,
@@ -34,4 +34,4 @@ struct PerformanceEntryVersion2 {
static_assert(sizeof(PerformanceEntryVersion2) == 0x18,
"PerformanceEntryVersion2 has the wrong size!");
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/common/common.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
struct PerformanceEntryAddresses {
CpuAddr translated_address;
@@ -14,4 +14,4 @@ struct PerformanceEntryAddresses {
CpuAddr entry_processed_time_offset;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
struct PerformanceFrameHeaderVersion1 {
/* 0x00 */ u32 magic; // "PERF"
@@ -33,4 +33,4 @@ struct PerformanceFrameHeaderVersion2 {
static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30,
"PerformanceFrameHeaderVersion2 has the wrong size!");
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_funcs.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void PerformanceManager::CreateImpl(const size_t version) {
switch (version) {
@@ -643,4 +643,4 @@ void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeader
target_node_id = target_node_id_;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/performance/performance_frame_header.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class BehaviorInfo;
class MemoryPoolInfo;
@@ -272,4 +272,4 @@ private:
PerformanceVersion version{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
#include "audio_core/renderer/upsampler/upsampler_manager.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
CircularBufferSinkInfo::CircularBufferSinkInfo() {
state.fill(0);
@@ -73,4 +73,4 @@ void CircularBufferSinkInfo::UpdateForCommandGeneration() {
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/sink/sink_info_base.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Info for a circular buffer sink.
*/
@@ -38,4 +38,4 @@ public:
static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase),
"CircularBufferSinkInfo is too large!");
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/sink/device_sink_info.h"
#include "audio_core/renderer/upsampler/upsampler_manager.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
DeviceSinkInfo::DeviceSinkInfo() {
state.fill(0);
@@ -54,4 +54,4 @@ void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_
void DeviceSinkInfo::UpdateForCommandGeneration() {}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/sink/sink_info_base.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Info for a device sink.
*/
@@ -37,4 +37,4 @@ public:
};
static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!");
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/sink/sink_context.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) {
sink_infos = sink_infos_;
@@ -18,4 +18,4 @@ u32 SinkContext::GetCount() const {
return sink_count;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/sink/sink_info_base.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Manages output sinks.
*/
@@ -44,4 +44,4 @@ private:
u32 sink_count{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/memory/pool_mapper.h"
#include "audio_core/renderer/sink/sink_info_base.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
void SinkInfoBase::CleanUp() {
type = Type::Invalid;
@@ -48,4 +48,4 @@ u8* SinkInfoBase::GetParameter() {
return parameter.data();
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
struct UpsamplerInfo;
class PoolMapper;
@@ -174,4 +174,4 @@ protected:
parameter{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/splitter/splitter_context.h"
#include "common/alignment.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id,
const s32 destination_id) {
@@ -214,4 +214,4 @@ u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior,
return size;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 {
struct AudioRendererParameterInternal;
class WorkbufferAllocator;
-namespace AudioRenderer {
+namespace Renderer {
class BehaviorInfo;
/**
@@ -185,5 +185,5 @@ private:
bool splitter_bug_fixed{};
};
-} // namespace AudioRenderer
+} // namespace Renderer
} // 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 @@
#include "audio_core/renderer/splitter/splitter_destinations_data.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {}
@@ -84,4 +84,4 @@ void SplitterDestinationData::SetNext(SplitterDestinationData* next_) {
next = next_;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/common/common.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Represents a mixing node, can be connected to a previous and next destination forming a chain
* that a certain mix buffer will pass through to output.
@@ -132,4 +132,4 @@ private:
bool need_update{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/splitter/splitter_info.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {}
@@ -76,4 +76,4 @@ void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) {
destinations = destinations_;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/splitter/splitter_destinations_data.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Represents a splitter, wraps multiple output destinations to split an input mix into.
*/
@@ -104,4 +104,4 @@ private:
u32 channel_count{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include <chrono>
#include <span>
+#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
+#include "audio_core/adsp/apps/audio_renderer/command_buffer.h"
#include "audio_core/audio_core.h"
#include "audio_core/common/audio_renderer_parameter.h"
#include "audio_core/common/common.h"
#include "audio_core/common/feature_support.h"
#include "audio_core/common/workbuffer_allocator.h"
-#include "audio_core/renderer/adsp/adsp.h"
#include "audio_core/renderer/behavior/info_updater.h"
#include "audio_core/renderer/command/command_buffer.h"
#include "audio_core/renderer/command/command_generator.h"
@@ -34,7 +35,7 @@
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/memory.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
BehaviorInfo behavior;
@@ -95,7 +96,8 @@ u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
}
System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
- : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {}
+ : core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()},
+ adsp_rendered_event{adsp_rendered_event_} {}
Result System::Initialize(const AudioRendererParameterInternal& params,
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
@@ -443,7 +445,7 @@ void System::Stop() {
Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) {
std::scoped_lock l{lock};
- const auto start_time{core.CoreTiming().GetClockTicks()};
+ const auto start_time{core.CoreTiming().GetGlobalTimeNs().count()};
std::memset(output.data(), 0, output.size());
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:
adsp_rendered_event->Clear();
num_times_updated++;
- const auto end_time{core.CoreTiming().GetClockTicks()};
+ const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
ticks_spent_updating += end_time - start_time;
return ResultSuccess;
@@ -583,7 +585,7 @@ void System::SendCommandToDsp() {
if (initialized) {
if (active) {
terminate_event.Reset();
- const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)};
+ const auto remaining_command_count{audio_renderer.GetRemainCommandCount(session_id)};
u64 command_size{0};
if (remaining_command_count) {
@@ -607,26 +609,18 @@ void System::SendCommandToDsp() {
time_limit_percent = 70.0f;
}
- ADSP::CommandBuffer command_buffer{
- .buffer{translated_addr},
- .size{command_size},
- .time_limit{
- static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
- (static_cast<f32>(render_time_limit_percent) / 100.0f))},
- .remaining_command_count{remaining_command_count},
- .reset_buffers{reset_command_buffers},
- .applet_resource_user_id{applet_resource_user_id},
- .render_time_taken{adsp.GetRenderTimeTaken(session_id)},
- };
-
- adsp.SendCommandBuffer(session_id, command_buffer);
+ auto time_limit{
+ static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
+ (static_cast<f32>(render_time_limit_percent) / 100.0f))};
+ audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
+ applet_resource_user_id, reset_command_buffers);
reset_command_buffers = false;
command_buffer_size = command_size;
if (remaining_command_count == 0) {
adsp_rendered_event->Signal();
}
} else {
- adsp.ClearRemainCount(session_id);
+ audio_renderer.ClearRemainCommandCount(session_id);
terminate_event.Set();
}
}
@@ -635,7 +629,7 @@ void System::SendCommandToDsp() {
u64 System::GenerateCommand(std::span<u8> in_command_buffer,
[[maybe_unused]] u64 command_buffer_size_) {
PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count);
- const auto start_time{core.CoreTiming().GetClockTicks()};
+ const auto start_time{core.CoreTiming().GetGlobalTimeNs().count()};
auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())};
@@ -690,11 +684,11 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
sink_context, splitter_context, perf_manager};
voice_context.SortInfo();
+ command_generator.GenerateVoiceCommands();
const auto start_estimated_time{drop_voice_param *
static_cast<f32>(command_buffer.estimated_process_time)};
- command_generator.GenerateVoiceCommands();
command_generator.GenerateSubMixCommands();
command_generator.GenerateFinalMixCommands();
command_generator.GenerateSinkCommands();
@@ -714,11 +708,13 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
const auto end_estimated_time{drop_voice_param *
static_cast<f32>(command_buffer.estimated_process_time)};
+
+ const auto dsp_time_limit{((time_limit_percent / 100.0f) * 2'880'000.0f) *
+ (static_cast<f32>(render_time_limit_percent) / 100.0f)};
+
const auto estimated_time{start_estimated_time - end_estimated_time};
- const auto time_limit{static_cast<u32>(
- estimated_time + (((time_limit_percent / 100.0f) * 2'880'000.0) *
- (static_cast<f32>(render_time_limit_percent) / 100.0f)))};
+ const auto time_limit{static_cast<u32>(std::max(dsp_time_limit + estimated_time, 0.0f))};
num_voices_dropped =
DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit);
}
@@ -732,10 +728,10 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
effect_context.UpdateStateByDspShared();
}
- const auto end_time{core.CoreTiming().GetClockTicks()};
+ const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
total_ticks_elapsed += end_time - start_time;
num_command_lists_generated++;
- render_start_tick = adsp.GetRenderingStartTick(session_id);
+ render_start_tick = audio_renderer.GetRenderingStartTick(session_id);
frames_elapsed++;
return command_buffer.size;
@@ -778,7 +774,7 @@ u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time
while (i < command_buffer.count) {
const auto node_id{cmd->node_id};
const auto node_id_type{cmd->node_id >> 28};
- const auto node_id_base{cmd->node_id & 0xFFF};
+ const auto node_id_base{(cmd->node_id >> 16) & 0xFFF};
// If the new estimated process time falls below the limit, we're done dropping.
if (estimated_process_time <= time_limit) {
@@ -819,4 +815,4 @@ u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time
return voices_dropped;
}
-} // namespace AudioCore::AudioRenderer
+} // 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;
namespace AudioCore {
struct AudioRendererParameterInternal;
-
-namespace AudioRenderer {
-class CommandBuffer;
namespace ADSP {
class ADSP;
+namespace AudioRenderer {
+class AudioRenderer;
}
+} // namespace ADSP
+
+namespace Renderer {
+using namespace ::AudioCore::ADSP;
+class CommandBuffer;
/**
* Audio Renderer System, the main worker for audio rendering.
@@ -213,8 +217,8 @@ public:
private:
/// Core system
Core::System& core;
- /// Reference to the ADSP for communication
- ADSP::ADSP& adsp;
+ /// Reference to the ADSP's AudioRenderer for communication
+ ::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
/// Is this system initialized?
bool initialized{};
/// Is this system currently active?
@@ -319,5 +323,5 @@ private:
f32 drop_voice_param{1.0f};
};
-} // namespace AudioRenderer
+} // namespace Renderer
} // 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 @@
#include <chrono>
+#include "audio_core/adsp/adsp.h"
#include "audio_core/audio_core.h"
-#include "audio_core/renderer/adsp/adsp.h"
#include "audio_core/renderer/system_manager.h"
#include "common/microprofile.h"
#include "common/thread.h"
@@ -14,24 +14,21 @@
MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
MP_RGB(60, 19, 97));
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
SystemManager::SystemManager(Core::System& core_)
- : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()} {}
+ : core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()} {}
SystemManager::~SystemManager() {
Stop();
}
-bool SystemManager::InitializeUnsafe() {
+void SystemManager::InitializeUnsafe() {
if (!active) {
- if (adsp.Start()) {
- active = true;
- thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); });
- }
+ active = true;
+ audio_renderer.Start();
+ thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(stop_token); });
}
-
- return adsp.GetState() == ADSP::State::Started;
}
void SystemManager::Stop() {
@@ -41,7 +38,7 @@ void SystemManager::Stop() {
active = false;
thread.request_stop();
thread.join();
- adsp.Stop();
+ audio_renderer.Stop();
}
bool SystemManager::Add(System& system_) {
@@ -55,10 +52,7 @@ bool SystemManager::Add(System& system_) {
{
std::scoped_lock l{mutex1};
if (systems.empty()) {
- if (!InitializeUnsafe()) {
- LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager");
- return false;
- }
+ InitializeUnsafe();
}
}
@@ -100,9 +94,9 @@ void SystemManager::ThreadFunc(std::stop_token stop_token) {
}
}
- adsp.Signal();
- adsp.Wait();
+ audio_renderer.Signal();
+ audio_renderer.Wait();
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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;
class System;
} // namespace Core
-namespace AudioCore::AudioRenderer {
-namespace ADSP {
+namespace AudioCore::ADSP {
class ADSP;
-class AudioRenderer_Mailbox;
-} // namespace ADSP
+namespace AudioRenderer {
+class AudioRenderer;
+} // namespace AudioRenderer
+} // namespace AudioCore::ADSP
+
+namespace AudioCore::Renderer {
/**
* Manages all audio renderers, responsible for triggering command list generation and signalling
@@ -38,7 +41,7 @@ public:
*
* @return True if successfully initialized, otherwise false.
*/
- bool InitializeUnsafe();
+ void InitializeUnsafe();
/**
* Stop the system manager.
@@ -80,10 +83,8 @@ private:
std::mutex mutex2{};
/// Is the system manager thread active?
std::atomic<bool> active{};
- /// Reference to the ADSP for communication
- ADSP::ADSP& adsp;
- /// AudioRenderer mailbox for communication
- ADSP::AudioRenderer_Mailbox* mailbox{};
+ /// Reference to the ADSP's AudioRenderer for communication
+ ::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/upsampler/upsampler_state.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class UpsamplerManager;
/**
@@ -32,4 +32,4 @@ struct UpsamplerInfo {
std::array<s16, MaxChannels> inputs{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/upsampler/upsampler_manager.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_,
std::span<s32> workbuffer_)
@@ -41,4 +41,4 @@ void UpsamplerManager::Free(UpsamplerInfo* info) {
info->enabled = false;
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/upsampler/upsampler_info.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Manages and has utility functions for upsampler infos.
*/
@@ -42,4 +42,4 @@ private:
std::mutex lock{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Upsampling state used by the AudioRenderer across calls.
*/
@@ -37,4 +37,4 @@ struct UpsamplerState {
u8 sample_index;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/common/common.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Represents one channel for mixing a voice.
*/
@@ -35,4 +35,4 @@ public:
bool in_use{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/voice/voice_context.h"
#include "common/polyfill_ranges.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
VoiceState& VoiceContext::GetDspSharedState(const u32 index) {
if (index >= dsp_states.size()) {
@@ -84,4 +84,4 @@ void VoiceContext::UpdateStateByDspShared() {
std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState));
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/voice/voice_state.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Contains all voices, with utility functions for managing them.
*/
@@ -123,4 +123,4 @@ private:
u32 active_count{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/voice/voice_info.h"
#include "audio_core/renderer/voice/voice_state.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
VoiceInfo::VoiceInfo() {
Initialize();
@@ -405,4 +405,4 @@ void VoiceInfo::ResetResources(VoiceContext& voice_context) const {
}
}
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/renderer/memory/address_info.h"
#include "common/common_types.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
class PoolMapper;
class VoiceContext;
struct VoiceState;
@@ -377,4 +377,4 @@ public:
u8 flush_buffer_count{};
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "common/common_types.h"
#include "common/fixed_point.h"
-namespace AudioCore::AudioRenderer {
+namespace AudioCore::Renderer {
/**
* Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer,
* host-side is updated on the next iteration.
@@ -67,4 +67,4 @@ struct VoiceState {
s32 loop_count;
};
-} // namespace AudioCore::AudioRenderer
+} // 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 @@
#include "audio_core/sink/cubeb_sink.h"
#include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/core.h"
#ifdef _WIN32
@@ -332,25 +333,38 @@ std::vector<std::string> ListCubebSinkDevices(bool capture) {
return device_list;
}
-u32 GetCubebLatency() {
- cubeb* ctx;
+namespace {
+static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) {
+ return TargetSampleCount;
+}
+static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {}
+} // namespace
+
+bool IsCubebSuitable() {
+#if !defined(HAVE_CUBEB)
+ return false;
+#else
+ cubeb* ctx{nullptr};
#ifdef _WIN32
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
+ // Init cubeb
if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
- // Return a large latency so we choose SDL instead.
- return 10000u;
+ LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable.");
+ return false;
}
+ SCOPE_EXIT({ cubeb_destroy(ctx); });
+
#ifdef _WIN32
if (SUCCEEDED(com_init_result)) {
CoUninitialize();
}
#endif
+ // Get min latency
cubeb_stream_params params{};
params.rate = TargetSampleRate;
params.channels = 2;
@@ -361,12 +375,27 @@ u32 GetCubebLatency() {
u32 latency{0};
const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency);
if (latency_error != CUBEB_OK) {
- LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
- latency = TargetSampleCount * 2;
+ LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable.");
+ return false;
}
latency = std::max(latency, TargetSampleCount * 2);
- cubeb_destroy(ctx);
- return latency;
+
+ // Test opening a device with standard parameters
+ cubeb_devid output_device{0};
+ cubeb_devid input_device{0};
+ std::string name{"Yuzu test"};
+ cubeb_stream* stream{nullptr};
+
+ if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, &params,
+ latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) {
+ LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable.");
+ return false;
+ }
+
+ cubeb_stream_stop(stream);
+ cubeb_stream_destroy(stream);
+ return true;
+#endif
}
} // 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:
std::vector<std::string> ListCubebSinkDevices(bool capture);
/**
- * Get the reported latency for this sink.
+ * Check if this backend is suitable for use.
+ * Checks if enabled, its latency, whether it opens successfully, etc.
*
- * @return Minimum latency for this sink.
+ * @return True is this backend is suitable, false otherwise.
*/
-u32 GetCubebLatency();
+bool IsCubebSuitable();
} // 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 @@
#include "audio_core/sink/sdl2_sink.h"
#include "audio_core/sink/sink_stream.h"
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/core.h"
namespace AudioCore::Sink {
@@ -84,6 +85,7 @@ public:
}
Stop();
+ SDL_ClearQueuedAudio(device);
SDL_CloseAudioDevice(device);
}
@@ -227,8 +229,42 @@ std::vector<std::string> ListSDLSinkDevices(bool capture) {
return device_list;
}
-u32 GetSDLLatency() {
- return TargetSampleCount * 2;
+bool IsSDLSuitable() {
+#if !defined(HAVE_SDL2)
+ return false;
+#else
+ // Check SDL can init
+ if (!SDL_WasInit(SDL_INIT_AUDIO)) {
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
+ LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
+ SDL_GetError());
+ return false;
+ }
+ }
+
+ // We can set any latency frequency we want with SDL, so no need to check that.
+
+ // Check we can open a device with standard parameters
+ SDL_AudioSpec spec;
+ spec.freq = TargetSampleRate;
+ spec.channels = 2u;
+ spec.format = AUDIO_S16SYS;
+ spec.samples = TargetSampleCount * 2;
+ spec.callback = nullptr;
+ spec.userdata = nullptr;
+
+ SDL_AudioSpec obtained;
+ auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false);
+
+ if (device == 0) {
+ LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}",
+ SDL_GetError());
+ return false;
+ }
+
+ SDL_CloseAudioDevice(device);
+ return true;
+#endif
}
} // 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:
std::vector<std::string> ListSDLSinkDevices(bool capture);
/**
- * Get the reported latency for this sink.
+ * Check if this backend is suitable for use.
+ * Checks if enabled, its latency, whether it opens successfully, etc.
*
- * @return Minimum latency for this sink.
+ * @return True is this backend is suitable, false otherwise.
*/
-u32 GetSDLLatency();
+bool IsSDLSuitable();
} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 39ea6d91b..7c9a4e3ac 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -15,86 +15,95 @@
#endif
#include "audio_core/sink/null_sink.h"
#include "common/logging/log.h"
+#include "common/settings_enums.h"
namespace AudioCore::Sink {
namespace {
struct SinkDetails {
using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
using ListDevicesFn = std::vector<std::string> (*)(bool);
- using LatencyFn = u32 (*)();
+ using SuitableFn = bool (*)();
/// Name for this sink.
- std::string_view id;
+ Settings::AudioEngine id;
/// A method to call to construct an instance of this type of sink.
FactoryFn factory;
/// A method to call to list available devices.
ListDevicesFn list_devices;
- /// Method to get the latency of this backend.
- LatencyFn latency;
+ /// Check whether this backend is suitable to be used.
+ SuitableFn is_suitable;
};
// sink_details is ordered in terms of desirability, with the best choice at the top.
constexpr SinkDetails sink_details[] = {
#ifdef HAVE_CUBEB
SinkDetails{
- "cubeb",
+ Settings::AudioEngine::Cubeb,
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<CubebSink>(device_id);
},
&ListCubebSinkDevices,
- &GetCubebLatency,
+ &IsCubebSuitable,
},
#endif
#ifdef HAVE_SDL2
SinkDetails{
- "sdl2",
+ Settings::AudioEngine::Sdl2,
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<SDLSink>(device_id);
},
&ListSDLSinkDevices,
- &GetSDLLatency,
+ &IsSDLSuitable,
},
#endif
- SinkDetails{"null",
- [](std::string_view device_id) -> std::unique_ptr<Sink> {
- return std::make_unique<NullSink>(device_id);
- },
- [](bool capture) { return std::vector<std::string>{"null"}; }, []() { return 0u; }},
+ SinkDetails{
+ Settings::AudioEngine::Null,
+ [](std::string_view device_id) -> std::unique_ptr<Sink> {
+ return std::make_unique<NullSink>(device_id);
+ },
+ [](bool capture) { return std::vector<std::string>{"null"}; },
+ []() { return true; },
+ },
};
-const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
- const auto find_backend{[](std::string_view id) {
+const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
+ const auto find_backend{[](Settings::AudioEngine id) {
return std::find_if(std::begin(sink_details), std::end(sink_details),
[&id](const auto& sink_detail) { return sink_detail.id == id; });
}};
auto iter = find_backend(sink_id);
- if (sink_id == "auto") {
- // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
- // causes audio issues, in that case go with SDL.
-#if defined(HAVE_CUBEB) && defined(HAVE_SDL2)
- iter = find_backend("cubeb");
- if (iter->latency() > TargetSampleCount * 3) {
- iter = find_backend("sdl2");
+ if (sink_id == Settings::AudioEngine::Auto) {
+ // Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking
+ // that the backend is available and suitable to use.
+ for (auto& details : sink_details) {
+ if (details.is_suitable()) {
+ iter = &details;
+ break;
+ }
+ }
+ LOG_INFO(Service_Audio, "Auto-selecting the {} backend",
+ Settings::CanonicalizeEnum(iter->id));
+ } else {
+ if (iter != std::end(sink_details) && !iter->is_suitable()) {
+ LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null",
+ Settings::CanonicalizeEnum(iter->id));
+ iter = find_backend(Settings::AudioEngine::Null);
}
-#else
- iter = std::begin(sink_details);
-#endif
- LOG_INFO(Service_Audio, "Auto-selecting the {} backend", iter->id);
}
if (iter == std::end(sink_details)) {
- LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
- iter = find_backend("null");
+ LOG_ERROR(Audio, "Invalid sink_id {}", Settings::CanonicalizeEnum(sink_id));
+ iter = find_backend(Settings::AudioEngine::Null);
}
return *iter;
}
} // Anonymous namespace
-std::vector<std::string_view> GetSinkIDs() {
- std::vector<std::string_view> sink_ids(std::size(sink_details));
+std::vector<Settings::AudioEngine> GetSinkIDs() {
+ std::vector<Settings::AudioEngine> sink_ids(std::size(sink_details));
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
[](const auto& sink) { return sink.id; });
@@ -102,11 +111,11 @@ std::vector<std::string_view> GetSinkIDs() {
return sink_ids;
}
-std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture) {
+std::vector<std::string> GetDeviceListForSink(Settings::AudioEngine sink_id, bool capture) {
return GetOutputSinkDetails(sink_id).list_devices(capture);
}
-std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
+std::unique_ptr<Sink> CreateSinkFromID(Settings::AudioEngine sink_id, std::string_view device_id) {
return GetOutputSinkDetails(sink_id).factory(device_id);
}
diff --git a/src/audio_core/sink/sink_details.h b/src/audio_core/sink/sink_details.h
index e75932898..c8498842b 100644
--- a/src/audio_core/sink/sink_details.h
+++ b/src/audio_core/sink/sink_details.h
@@ -3,9 +3,11 @@
#pragma once
+#include <memory>
#include <string>
#include <string_view>
#include <vector>
+#include "common/settings_enums.h"
namespace AudioCore {
class AudioManager;
@@ -19,7 +21,7 @@ class Sink;
*
* @return Vector of available sink names.
*/
-std::vector<std::string_view> GetSinkIDs();
+std::vector<Settings::AudioEngine> GetSinkIDs();
/**
* Gets the list of devices for a particular sink identified by the given ID.
@@ -28,7 +30,7 @@ std::vector<std::string_view> GetSinkIDs();
* @param capture - Get capture (input) devices, or output devices?
* @return Vector of device names.
*/
-std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture);
+std::vector<std::string> GetDeviceListForSink(Settings::AudioEngine sink_id, bool capture);
/**
* Creates an audio sink identified by the given device ID.
@@ -37,7 +39,7 @@ std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool cap
* @param device_id - Name of the device to create.
* @return Pointer to the created sink.
*/
-std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
+std::unique_ptr<Sink> CreateSinkFromID(Settings::AudioEngine sink_id, std::string_view device_id);
} // namespace Sink
} // namespace AudioCore
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 3adf13a3f..416203c59 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -26,12 +26,11 @@ add_library(common STATIC
assert.h
atomic_helpers.h
atomic_ops.h
- detached_tasks.cpp
- detached_tasks.h
bit_cast.h
bit_field.h
bit_set.h
bit_util.h
+ bounded_threadsafe_queue.h
cityhash.cpp
cityhash.h
common_funcs.h
@@ -41,6 +40,8 @@ add_library(common STATIC
container_hash.h
demangle.cpp
demangle.h
+ detached_tasks.cpp
+ detached_tasks.h
div_ceil.h
dynamic_library.cpp
dynamic_library.h
@@ -110,8 +111,12 @@ add_library(common STATIC
scratch_buffer.h
settings.cpp
settings.h
+ settings_common.cpp
+ settings_common.h
+ settings_enums.h
settings_input.cpp
settings_input.h
+ settings_setting.h
socket_types.h
spin_lock.cpp
spin_lock.h
@@ -147,6 +152,10 @@ add_library(common STATIC
zstd_compression.h
)
+if (YUZU_ENABLE_PORTABLE)
+ add_compile_definitions(YUZU_ENABLE_PORTABLE)
+endif()
+
if (WIN32)
target_sources(common PRIVATE
windows/timer_resolution.cpp
@@ -187,15 +196,20 @@ if (MSVC)
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
)
target_compile_options(common PRIVATE
- /W4
-
/we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/we4800 # Implicit conversion from 'type' to bool. Possible information loss
)
-else()
+endif()
+
+if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(common PRIVATE
- $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
+ -fsized-deallocation
+ -Werror=unreachable-code-aggressive
+ )
+ target_compile_definitions(common PRIVATE
+ # Clang 14 and earlier have errors when explicitly instantiating Settings::Setting
+ $<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>
)
endif()
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 @@
#pragma once
+#include <bit>
#include <cstddef>
#include <new>
#include <type_traits>
@@ -10,8 +11,10 @@
namespace Common {
template <typename T>
- requires std::is_unsigned_v<T>
-[[nodiscard]] constexpr T AlignUp(T value, size_t size) {
+ requires std::is_integral_v<T>
+[[nodiscard]] constexpr T AlignUp(T value_, size_t size) {
+ using U = typename std::make_unsigned_t<T>;
+ auto value{static_cast<U>(value_)};
auto mod{static_cast<T>(value % size)};
value -= mod;
return static_cast<T>(mod == T{0} ? value : value + size);
@@ -24,8 +27,10 @@ template <typename T>
}
template <typename T>
- requires std::is_unsigned_v<T>
-[[nodiscard]] constexpr T AlignDown(T value, size_t size) {
+ requires std::is_integral_v<T>
+[[nodiscard]] constexpr T AlignDown(T value_, size_t size) {
+ using U = typename std::make_unsigned_t<T>;
+ const auto value{static_cast<U>(value_)};
return static_cast<T>(value - value % size);
}
@@ -55,6 +60,30 @@ template <typename T, typename U>
return (x + (y - 1)) / y;
}
+template <typename T>
+ requires std::is_integral_v<T>
+[[nodiscard]] constexpr T LeastSignificantOneBit(T x) {
+ return x & ~(x - 1);
+}
+
+template <typename T>
+ requires std::is_integral_v<T>
+[[nodiscard]] constexpr T ResetLeastSignificantOneBit(T x) {
+ return x & (x - 1);
+}
+
+template <typename T>
+ requires std::is_integral_v<T>
+[[nodiscard]] constexpr bool IsPowerOfTwo(T x) {
+ return x > 0 && ResetLeastSignificantOneBit(x) == 0;
+}
+
+template <typename T>
+ requires std::is_integral_v<T>
+[[nodiscard]] constexpr T FloorPowerOfTwo(T x) {
+ return T{1} << (sizeof(T) * 8 - std::countl_zero(x) - 1);
+}
+
template <typename T, size_t Align = 16>
class AlignmentAllocator {
public:
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:
}
T PopWait() {
- T t;
+ T t{};
Pop<PopMode::Wait>(t);
return t;
}
T PopWait(std::stop_token stop_token) {
- T t;
+ T t{};
Pop<PopMode::WaitWithStopToken>(t, stop_token);
return t;
}
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,
// Generic Filesystem Operations
bool Exists(const fs::path& path) {
+ std::error_code ec;
#ifdef ANDROID
if (Android::IsContentUri(path)) {
return Android::Exists(path);
} else {
- return fs::exists(path);
+ return fs::exists(path, ec);
}
#else
- return fs::exists(path);
+ return fs::exists(path, ec);
#endif
}
bool IsFile(const fs::path& path) {
+ std::error_code ec;
#ifdef ANDROID
if (Android::IsContentUri(path)) {
return !Android::IsDirectory(path);
} else {
- return fs::is_regular_file(path);
+ return fs::is_regular_file(path, ec);
}
#else
- return fs::is_regular_file(path);
+ return fs::is_regular_file(path, ec);
#endif
}
bool IsDir(const fs::path& path) {
+ std::error_code ec;
#ifdef ANDROID
if (Android::IsContentUri(path)) {
return Android::IsDirectory(path);
} else {
- return fs::is_directory(path);
+ return fs::is_directory(path, ec);
}
#else
- return fs::is_directory(path);
+ return fs::is_directory(path, ec);
#endif
}
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:
fs::path yuzu_path_config;
#ifdef _WIN32
+#ifdef YUZU_ENABLE_PORTABLE
yuzu_path = GetExeDirectory() / PORTABLE_DIR;
-
+#endif
if (!IsDir(yuzu_path)) {
yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
}
@@ -101,8 +102,9 @@ public:
yuzu_path_cache = yuzu_path / CACHE_DIR;
yuzu_path_config = yuzu_path / CONFIG_DIR;
#else
+#ifdef YUZU_ENABLE_PORTABLE
yuzu_path = GetCurrentDir() / PORTABLE_DIR;
-
+#endif
if (Exists(yuzu_path) && IsDir(yuzu_path)) {
yuzu_path_cache = yuzu_path / CACHE_DIR;
yuzu_path_config = yuzu_path / CONFIG_DIR;
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 6e8e8eb36..d4f27197c 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -108,7 +108,7 @@ public:
using namespace Common::Literals;
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
- const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB;
+ const auto write_limit = Settings::values.extended_logging.GetValue() ? 1_GiB : 100_MiB;
const bool write_limit_exceeded = bytes_written > write_limit;
if (entry.log_level >= Level::Error || write_limit_exceeded) {
if (write_limit_exceeded) {
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) {
SUB(Service, NCM) \
SUB(Service, NFC) \
SUB(Service, NFP) \
- SUB(Service, NGCT) \
+ SUB(Service, NGC) \
SUB(Service, NIFM) \
SUB(Service, NIM) \
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 {
Service_NCM, ///< The NCM service
Service_NFC, ///< The NFC (Near-field communication) service
Service_NFP, ///< The NFP service
- Service_NGCT, ///< The NGCT (No Good Content for Terra) service
+ Service_NGC, ///< The NGC (No Good Content) service
Service_NIFM, ///< The NIFM (Network interface) service
Service_NIM, ///< The NIM service
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
return uncompressed;
}
+int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size) {
+ // This is just a thin wrapper around LZ4.
+ return LZ4_decompress_safe(reinterpret_cast<const char*>(src), reinterpret_cast<char*>(dst),
+ static_cast<int>(src_size), static_cast<int>(dst_size));
+}
+
} // 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 {
[[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed,
std::size_t uncompressed_size);
+[[nodiscard]] int DecompressDataLZ4(void* dst, size_t dst_size, const void* src, size_t src_size);
+
} // 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 @@
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
-void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
- cv.wait(lock, token, std::move(pred));
+void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
+ cv.wait(lk, token, std::move(pred));
}
template <typename Rep, typename Period>
@@ -332,13 +332,17 @@ private:
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
-void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
+void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
if (token.stop_requested()) {
return;
}
- std::stop_callback callback(token, [&] { cv.notify_all(); });
- cv.wait(lock, [&] { return pred() || token.stop_requested(); });
+ std::stop_callback callback(token, [&] {
+ { std::scoped_lock lk2{*lk.mutex()}; }
+ cv.notify_all();
+ });
+
+ cv.wait(lk, [&] { return pred() || token.stop_requested(); });
}
template <typename Rep, typename Period>
@@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
std::stop_callback cb(token, [&] {
// Wake up the waiting thread.
- std::unique_lock lk{m};
- stop_requested = true;
+ {
+ std::scoped_lock lk{m};
+ stop_requested = true;
+ }
cv.notify_one();
});
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index d4e55f988..4ecaf550b 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -2,14 +2,22 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <version>
+#include "common/settings_enums.h"
#if __cpp_lib_chrono >= 201907L
#include <chrono>
#include <exception>
#include <stdexcept>
#endif
+#include <compare>
+#include <cstddef>
+#include <filesystem>
+#include <functional>
#include <string_view>
+#include <type_traits>
+#include <fmt/core.h>
#include "common/assert.h"
+#include "common/fs/fs_util.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
@@ -17,11 +25,50 @@
namespace Settings {
+// Clang 14 and earlier have errors when explicitly instantiating these classes
+#ifndef CANNOT_EXPLICITLY_INSTANTIATE
+#define SETTING(TYPE, RANGED) template class Setting<TYPE, RANGED>
+#define SWITCHABLE(TYPE, RANGED) template class SwitchableSetting<TYPE, RANGED>
+
+SETTING(AudioEngine, false);
+SETTING(bool, false);
+SETTING(int, false);
+SETTING(std::string, false);
+SETTING(u16, false);
+SWITCHABLE(AnisotropyMode, true);
+SWITCHABLE(AntiAliasing, false);
+SWITCHABLE(AspectRatio, true);
+SWITCHABLE(AstcDecodeMode, true);
+SWITCHABLE(AstcRecompression, true);
+SWITCHABLE(AudioMode, true);
+SWITCHABLE(CpuAccuracy, true);
+SWITCHABLE(FullscreenMode, true);
+SWITCHABLE(GpuAccuracy, true);
+SWITCHABLE(Language, true);
+SWITCHABLE(NvdecEmulation, false);
+SWITCHABLE(Region, true);
+SWITCHABLE(RendererBackend, true);
+SWITCHABLE(ScalingFilter, false);
+SWITCHABLE(ShaderBackend, true);
+SWITCHABLE(TimeZone, true);
+SETTING(VSyncMode, true);
+SWITCHABLE(bool, false);
+SWITCHABLE(int, false);
+SWITCHABLE(int, true);
+SWITCHABLE(s64, false);
+SWITCHABLE(u16, true);
+SWITCHABLE(u32, false);
+SWITCHABLE(u8, false);
+SWITCHABLE(u8, true);
+
+#undef SETTING
+#undef SWITCHABLE
+#endif
+
Values values;
-static bool configuring_global = true;
-std::string GetTimeZoneString() {
- const auto time_zone_index = static_cast<std::size_t>(values.time_zone_index.GetValue());
+std::string GetTimeZoneString(TimeZone time_zone) {
+ const auto time_zone_index = static_cast<std::size_t>(time_zone);
ASSERT(time_zone_index < Common::TimeZone::GetTimeZoneStrings().size());
std::string location_name;
@@ -61,73 +108,35 @@ void LogSettings() {
};
LOG_INFO(Config, "yuzu Configuration:");
- log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
- log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
- log_setting("System_DeviceName", values.device_name.GetValue());
- log_setting("System_CurrentUser", values.current_user.GetValue());
- log_setting("System_LanguageIndex", values.language_index.GetValue());
- log_setting("System_RegionIndex", values.region_index.GetValue());
- log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue());
- log_setting("System_UnsafeMemoryLayout", values.use_unsafe_extended_memory_layout.GetValue());
- log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());
- log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue());
- log_setting("Renderer_UseResolutionScaling", values.resolution_setup.GetValue());
- log_setting("Renderer_ScalingFilter", values.scaling_filter.GetValue());
- log_setting("Renderer_FSRSlider", values.fsr_sharpening_slider.GetValue());
- log_setting("Renderer_AntiAliasing", values.anti_aliasing.GetValue());
- log_setting("Renderer_UseSpeedLimit", values.use_speed_limit.GetValue());
- log_setting("Renderer_SpeedLimit", values.speed_limit.GetValue());
- log_setting("Renderer_UseDiskShaderCache", values.use_disk_shader_cache.GetValue());
- log_setting("Renderer_GPUAccuracyLevel", values.gpu_accuracy.GetValue());
- log_setting("Renderer_UseAsynchronousGpuEmulation",
- values.use_asynchronous_gpu_emulation.GetValue());
- log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue());
- log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue());
- log_setting("Renderer_AsyncASTC", values.async_astc.GetValue());
- log_setting("Renderer_AstcRecompression", values.astc_recompression.GetValue());
- log_setting("Renderer_UseVsync", values.vsync_mode.GetValue());
- log_setting("Renderer_UseReactiveFlushing", values.use_reactive_flushing.GetValue());
- log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue());
- log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
- log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
- log_setting("Audio_OutputEngine", values.sink_id.GetValue());
- log_setting("Audio_OutputDevice", values.audio_output_device_id.GetValue());
- log_setting("Audio_InputDevice", values.audio_input_device_id.GetValue());
- log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue());
+ for (auto& [category, settings] : values.linkage.by_category) {
+ for (const auto& setting : settings) {
+ if (setting->Id() == values.yuzu_token.Id()) {
+ // Hide the token secret, for security reasons.
+ continue;
+ }
+
+ const auto name = fmt::format(
+ "{:c}{:c} {}.{}", setting->ToString() == setting->DefaultToString() ? '-' : 'M',
+ setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category),
+ setting->GetLabel());
+
+ log_setting(name, setting->Canonicalize());
+ }
+ }
log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir));
log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir));
log_path("DataStorage_LoadDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir));
log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
- log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
- log_setting("Debugging_GDBStub", values.use_gdbstub.GetValue());
- log_setting("Input_EnableMotion", values.motion_enabled.GetValue());
- log_setting("Input_EnableVibration", values.vibration_enabled.GetValue());
- log_setting("Input_EnableTouch", values.touchscreen.enabled);
- log_setting("Input_EnableMouse", values.mouse_enabled.GetValue());
- log_setting("Input_EnableKeyboard", values.keyboard_enabled.GetValue());
- log_setting("Input_EnableRingController", values.enable_ring_controller.GetValue());
- log_setting("Input_EnableIrSensor", values.enable_ir_sensor.GetValue());
- log_setting("Input_EnableCustomJoycon", values.enable_joycon_driver.GetValue());
- log_setting("Input_EnableCustomProController", values.enable_procon_driver.GetValue());
- log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue());
-}
-
-bool IsConfiguringGlobal() {
- return configuring_global;
-}
-
-void SetConfiguringGlobal(bool is_global) {
- configuring_global = is_global;
}
bool IsGPULevelExtreme() {
- return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme;
+ return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme;
}
bool IsGPULevelHigh() {
- return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme ||
- values.gpu_accuracy.GetValue() == GPUAccuracy::High;
+ return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme ||
+ values.gpu_accuracy.GetValue() == GpuAccuracy::High;
}
bool IsFastmemEnabled() {
@@ -137,6 +146,10 @@ bool IsFastmemEnabled() {
return true;
}
+bool IsDockedMode() {
+ return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
+}
+
float Volume() {
if (values.audio_muted) {
return 0.0f;
@@ -144,9 +157,64 @@ float Volume() {
return values.volume.GetValue() / static_cast<f32>(values.volume.GetDefault());
}
-void UpdateRescalingInfo() {
- const auto setup = values.resolution_setup.GetValue();
- auto& info = values.resolution_info;
+const char* TranslateCategory(Category category) {
+ switch (category) {
+ case Category::Android:
+ return "Android";
+ case Category::Audio:
+ return "Audio";
+ case Category::Core:
+ return "Core";
+ case Category::Cpu:
+ case Category::CpuDebug:
+ case Category::CpuUnsafe:
+ return "Cpu";
+ case Category::Renderer:
+ case Category::RendererAdvanced:
+ case Category::RendererDebug:
+ return "Renderer";
+ case Category::System:
+ case Category::SystemAudio:
+ return "System";
+ case Category::DataStorage:
+ return "Data Storage";
+ case Category::Debugging:
+ case Category::DebuggingGraphics:
+ return "Debugging";
+ case Category::Miscellaneous:
+ return "Miscellaneous";
+ case Category::Network:
+ return "Network";
+ case Category::WebService:
+ return "WebService";
+ case Category::AddOns:
+ return "DisabledAddOns";
+ case Category::Controls:
+ return "Controls";
+ case Category::Ui:
+ case Category::UiGeneral:
+ return "UI";
+ case Category::UiLayout:
+ return "UiLayout";
+ case Category::UiGameList:
+ return "UiGameList";
+ case Category::Screenshots:
+ return "Screenshots";
+ case Category::Shortcuts:
+ return "Shortcuts";
+ case Category::Multiplayer:
+ return "Multiplayer";
+ case Category::Services:
+ return "Services";
+ case Category::Paths:
+ return "Paths";
+ case Category::MaxEnum:
+ break;
+ }
+ return "Miscellaneous";
+}
+
+void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info) {
info.downscale = false;
switch (setup) {
case ResolutionSetup::Res1_2X:
@@ -206,72 +274,31 @@ void UpdateRescalingInfo() {
info.active = info.up_scale != 1 || info.down_shift != 0;
}
+void UpdateRescalingInfo() {
+ const auto setup = values.resolution_setup.GetValue();
+ auto& info = values.resolution_info;
+ TranslateResolutionInfo(setup, info);
+}
+
void RestoreGlobalState(bool is_powered_on) {
// If a game is running, DO NOT restore the global settings state
if (is_powered_on) {
return;
}
- // Audio
- values.volume.SetGlobal(true);
-
- // Core
- values.use_multi_core.SetGlobal(true);
- values.use_unsafe_extended_memory_layout.SetGlobal(true);
-
- // CPU
- values.cpu_accuracy.SetGlobal(true);
- values.cpuopt_unsafe_unfuse_fma.SetGlobal(true);
- values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true);
- values.cpuopt_unsafe_ignore_standard_fpcr.SetGlobal(true);
- values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true);
- values.cpuopt_unsafe_fastmem_check.SetGlobal(true);
- values.cpuopt_unsafe_ignore_global_monitor.SetGlobal(true);
+ for (const auto& reset : values.linkage.restore_functions) {
+ reset();
+ }
+}
- // Renderer
- values.fsr_sharpening_slider.SetGlobal(true);
- values.renderer_backend.SetGlobal(true);
- values.async_presentation.SetGlobal(true);
- values.renderer_force_max_clock.SetGlobal(true);
- values.vulkan_device.SetGlobal(true);
- values.fullscreen_mode.SetGlobal(true);
- values.aspect_ratio.SetGlobal(true);
- values.resolution_setup.SetGlobal(true);
- values.scaling_filter.SetGlobal(true);
- values.anti_aliasing.SetGlobal(true);
- values.max_anisotropy.SetGlobal(true);
- values.use_speed_limit.SetGlobal(true);
- values.speed_limit.SetGlobal(true);
- values.use_disk_shader_cache.SetGlobal(true);
- values.gpu_accuracy.SetGlobal(true);
- values.use_asynchronous_gpu_emulation.SetGlobal(true);
- values.nvdec_emulation.SetGlobal(true);
- values.accelerate_astc.SetGlobal(true);
- values.async_astc.SetGlobal(true);
- values.astc_recompression.SetGlobal(true);
- values.use_reactive_flushing.SetGlobal(true);
- values.shader_backend.SetGlobal(true);
- values.use_asynchronous_shaders.SetGlobal(true);
- values.use_fast_gpu_time.SetGlobal(true);
- values.use_vulkan_driver_pipeline_cache.SetGlobal(true);
- values.bg_red.SetGlobal(true);
- values.bg_green.SetGlobal(true);
- values.bg_blue.SetGlobal(true);
- values.enable_compute_pipelines.SetGlobal(true);
- values.use_video_framerate.SetGlobal(true);
+static bool configuring_global = true;
- // System
- values.language_index.SetGlobal(true);
- values.region_index.SetGlobal(true);
- values.time_zone_index.SetGlobal(true);
- values.rng_seed.SetGlobal(true);
- values.sound_index.SetGlobal(true);
+bool IsConfiguringGlobal() {
+ return configuring_global;
+}
- // Controls
- values.players.SetGlobal(true);
- values.use_docked_mode.SetGlobal(true);
- values.vibration_enabled.SetGlobal(true);
- values.motion_enabled.SetGlobal(true);
+void SetConfiguringGlobal(bool is_global) {
+ configuring_global = is_global;
}
} // namespace Settings
diff --git a/src/common/settings.h b/src/common/settings.h
index 59e96e74f..82ec9077e 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -6,95 +6,21 @@
#include <algorithm>
#include <array>
#include <map>
-#include <optional>
+#include <memory>
+#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "common/common_types.h"
+#include "common/settings_common.h"
+#include "common/settings_enums.h"
#include "common/settings_input.h"
+#include "common/settings_setting.h"
namespace Settings {
-enum class VSyncMode : u32 {
- Immediate = 0,
- Mailbox = 1,
- FIFO = 2,
- FIFORelaxed = 3,
-};
-
-enum class RendererBackend : u32 {
- OpenGL = 0,
- Vulkan = 1,
- Null = 2,
-};
-
-enum class ShaderBackend : u32 {
- GLSL = 0,
- GLASM = 1,
- SPIRV = 2,
-};
-
-enum class GPUAccuracy : u32 {
- Normal = 0,
- High = 1,
- Extreme = 2,
-};
-
-enum class CPUAccuracy : u32 {
- Auto = 0,
- Accurate = 1,
- Unsafe = 2,
- Paranoid = 3,
-};
-
-enum class FullscreenMode : u32 {
- Borderless = 0,
- Exclusive = 1,
-};
-
-enum class NvdecEmulation : u32 {
- Off = 0,
- CPU = 1,
- GPU = 2,
-};
-
-enum class ResolutionSetup : u32 {
- Res1_2X = 0,
- Res3_4X = 1,
- Res1X = 2,
- Res3_2X = 3,
- Res2X = 4,
- Res3X = 5,
- Res4X = 6,
- Res5X = 7,
- Res6X = 8,
- Res7X = 9,
- Res8X = 10,
-};
-
-enum class ScalingFilter : u32 {
- NearestNeighbor = 0,
- Bilinear = 1,
- Bicubic = 2,
- Gaussian = 3,
- ScaleForce = 4,
- Fsr = 5,
- LastFilter = Fsr,
-};
-
-enum class AntiAliasing : u32 {
- None = 0,
- Fxaa = 1,
- Smaa = 2,
- LastAA = Smaa,
-};
-
-enum class AstcRecompression : u32 {
- Uncompressed = 0,
- Bc1 = 1,
- Bc3 = 2,
-};
+const char* TranslateCategory(Settings::Category category);
struct ResolutionScalingInfo {
u32 up_scale{1};
@@ -119,239 +45,47 @@ struct ResolutionScalingInfo {
}
};
-/** The Setting class is a simple resource manager. It defines a label and default value alongside
- * the actual value of the setting for simpler and less-error prone use with frontend
- * configurations. Specifying a default value and label is required. A minimum and maximum range can
- * be specified for sanitization.
- */
-template <typename Type, bool ranged = false>
-class Setting {
-protected:
- Setting() = default;
-
- /**
- * Only sets the setting to the given initializer, leaving the other members to their default
- * initializers.
- *
- * @param global_val Initial value of the setting
- */
- explicit Setting(const Type& val) : value{val} {}
-
-public:
- /**
- * Sets a default value, label, and setting value.
- *
- * @param default_val Initial value of the setting, and default value of the setting
- * @param name Label for the setting
- */
- explicit Setting(const Type& default_val, const std::string& name)
- requires(!ranged)
- : value{default_val}, default_value{default_val}, label{name} {}
- virtual ~Setting() = default;
-
- /**
- * Sets a default value, minimum value, maximum value, and label.
- *
- * @param default_val Initial value of the setting, and default value of the setting
- * @param min_val Sets the minimum allowed value of the setting
- * @param max_val Sets the maximum allowed value of the setting
- * @param name Label for the setting
- */
- explicit Setting(const Type& default_val, const Type& min_val, const Type& max_val,
- const std::string& name)
- requires(ranged)
- : value{default_val},
- default_value{default_val}, maximum{max_val}, minimum{min_val}, label{name} {}
-
- /**
- * Returns a reference to the setting's value.
- *
- * @returns A reference to the setting
- */
- [[nodiscard]] virtual const Type& GetValue() const {
- return value;
- }
-
- /**
- * Sets the setting to the given value.
- *
- * @param val The desired value
- */
- virtual void SetValue(const Type& val) {
- Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
- std::swap(value, temp);
- }
-
- /**
- * Returns the value that this setting was created with.
- *
- * @returns A reference to the default value
- */
- [[nodiscard]] const Type& GetDefault() const {
- return default_value;
- }
-
- /**
- * Returns the label this setting was created with.
- *
- * @returns A reference to the label
- */
- [[nodiscard]] const std::string& GetLabel() const {
- return label;
- }
-
- /**
- * Assigns a value to the setting.
- *
- * @param val The desired setting value
- *
- * @returns A reference to the setting
- */
- virtual const Type& operator=(const Type& val) {
- Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
- std::swap(value, temp);
- return value;
- }
-
- /**
- * Returns a reference to the setting.
- *
- * @returns A reference to the setting
- */
- explicit virtual operator const Type&() const {
- return value;
- }
-
-protected:
- Type value{}; ///< The setting
- const Type default_value{}; ///< The default value
- const Type maximum{}; ///< Maximum allowed value of the setting
- const Type minimum{}; ///< Minimum allowed value of the setting
- const std::string label{}; ///< The setting's label
-};
-
-/**
- * The SwitchableSetting class is a slightly more complex version of the Setting class. This adds a
- * custom setting to switch to when a guest application specifically requires it. The effect is that
- * other components of the emulator can access the setting's intended value without any need for the
- * component to ask whether the custom or global setting is needed at the moment.
- *
- * By default, the global setting is used.
- */
-template <typename Type, bool ranged = false>
-class SwitchableSetting : virtual public Setting<Type, ranged> {
-public:
- /**
- * Sets a default value, label, and setting value.
- *
- * @param default_val Initial value of the setting, and default value of the setting
- * @param name Label for the setting
- */
- explicit SwitchableSetting(const Type& default_val, const std::string& name)
- requires(!ranged)
- : Setting<Type>{default_val, name} {}
- virtual ~SwitchableSetting() = default;
-
- /**
- * Sets a default value, minimum value, maximum value, and label.
- *
- * @param default_val Initial value of the setting, and default value of the setting
- * @param min_val Sets the minimum allowed value of the setting
- * @param max_val Sets the maximum allowed value of the setting
- * @param name Label for the setting
- */
- explicit SwitchableSetting(const Type& default_val, const Type& min_val, const Type& max_val,
- const std::string& name)
- requires(ranged)
- : Setting<Type, true>{default_val, min_val, max_val, name} {}
-
- /**
- * Tells this setting to represent either the global or custom setting when other member
- * functions are used.
- *
- * @param to_global Whether to use the global or custom setting.
- */
- void SetGlobal(bool to_global) {
- use_global = to_global;
- }
-
- /**
- * Returns whether this setting is using the global setting or not.
- *
- * @returns The global state
- */
- [[nodiscard]] bool UsingGlobal() const {
- return use_global;
- }
-
- /**
- * Returns either the global or custom setting depending on the values of this setting's global
- * state or if the global value was specifically requested.
- *
- * @param need_global Request global value regardless of setting's state; defaults to false
- *
- * @returns The required value of the setting
- */
- [[nodiscard]] virtual const Type& GetValue() const override {
- if (use_global) {
- return this->value;
- }
- return custom;
- }
- [[nodiscard]] virtual const Type& GetValue(bool need_global) const {
- if (use_global || need_global) {
- return this->value;
- }
- return custom;
- }
-
- /**
- * Sets the current setting value depending on the global state.
- *
- * @param val The new value
- */
- void SetValue(const Type& val) override {
- Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
- if (use_global) {
- std::swap(this->value, temp);
- } else {
- std::swap(custom, temp);
- }
- }
-
- /**
- * Assigns the current setting value depending on the global state.
- *
- * @param val The new value
- *
- * @returns A reference to the current setting value
- */
- const Type& operator=(const Type& val) override {
- Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
- if (use_global) {
- std::swap(this->value, temp);
- return this->value;
- }
- std::swap(custom, temp);
- return custom;
- }
-
- /**
- * Returns the current setting value depending on the global state.
- *
- * @returns A reference to the current setting value
- */
- virtual explicit operator const Type&() const override {
- if (use_global) {
- return this->value;
- }
- return custom;
- }
-
-protected:
- bool use_global{true}; ///< The setting's global state
- Type custom{}; ///< The custom value of the setting
-};
+#ifndef CANNOT_EXPLICITLY_INSTANTIATE
+// Instantiate the classes elsewhere (settings.cpp) to reduce compiler/linker work
+#define SETTING(TYPE, RANGED) extern template class Setting<TYPE, RANGED>
+#define SWITCHABLE(TYPE, RANGED) extern template class SwitchableSetting<TYPE, RANGED>
+
+SETTING(AudioEngine, false);
+SETTING(bool, false);
+SETTING(int, false);
+SETTING(s32, false);
+SETTING(std::string, false);
+SETTING(std::string, false);
+SETTING(u16, false);
+SWITCHABLE(AnisotropyMode, true);
+SWITCHABLE(AntiAliasing, false);
+SWITCHABLE(AspectRatio, true);
+SWITCHABLE(AstcDecodeMode, true);
+SWITCHABLE(AstcRecompression, true);
+SWITCHABLE(AudioMode, true);
+SWITCHABLE(CpuAccuracy, true);
+SWITCHABLE(FullscreenMode, true);
+SWITCHABLE(GpuAccuracy, true);
+SWITCHABLE(Language, true);
+SWITCHABLE(NvdecEmulation, false);
+SWITCHABLE(Region, true);
+SWITCHABLE(RendererBackend, true);
+SWITCHABLE(ScalingFilter, false);
+SWITCHABLE(ShaderBackend, true);
+SWITCHABLE(TimeZone, true);
+SETTING(VSyncMode, true);
+SWITCHABLE(bool, false);
+SWITCHABLE(int, false);
+SWITCHABLE(int, true);
+SWITCHABLE(s64, false);
+SWITCHABLE(u16, true);
+SWITCHABLE(u32, false);
+SWITCHABLE(u8, false);
+SWITCHABLE(u8, true);
+
+#undef SETTING
+#undef SWITCHABLE
+#endif
/**
* The InputSetting class allows for getting a reference to either the global or custom members.
@@ -391,208 +125,396 @@ struct TouchFromButtonMap {
};
struct Values {
+ Linkage linkage{};
+
// Audio
- Setting<std::string> sink_id{"auto", "output_engine"};
- Setting<std::string> audio_output_device_id{"auto", "output_device"};
- Setting<std::string> audio_input_device_id{"auto", "input_device"};
- Setting<bool> audio_muted{false, "audio_muted"};
- SwitchableSetting<u8, true> volume{100, 0, 200, "volume"};
- Setting<bool> dump_audio_commands{false, "dump_audio_commands"};
+ Setting<AudioEngine> sink_id{linkage, AudioEngine::Auto, "output_engine", Category::Audio,
+ Specialization::RuntimeList};
+ Setting<std::string> audio_output_device_id{linkage, "auto", "output_device", Category::Audio,
+ Specialization::RuntimeList};
+ Setting<std::string> audio_input_device_id{linkage, "auto", "input_device", Category::Audio,
+ Specialization::RuntimeList};
+ SwitchableSetting<AudioMode, true> sound_index{
+ linkage, AudioMode::Stereo, AudioMode::Mono, AudioMode::Surround,
+ "sound_index", Category::SystemAudio, Specialization::Default, true,
+ true};
+ SwitchableSetting<u8, true> volume{linkage,
+ 100,
+ 0,
+ 200,
+ "volume",
+ Category::Audio,
+ Specialization::Scalar | Specialization::Percentage,
+ true,
+ true};
+ Setting<bool, false> audio_muted{
+ linkage, false, "audio_muted", Category::Audio, Specialization::Default, false, true};
+ Setting<bool, false> dump_audio_commands{
+ linkage, false, "dump_audio_commands", Category::Audio, Specialization::Default, false};
// Core
- SwitchableSetting<bool> use_multi_core{true, "use_multi_core"};
- SwitchableSetting<bool> use_unsafe_extended_memory_layout{false,
- "use_unsafe_extended_memory_layout"};
+ SwitchableSetting<bool> use_multi_core{linkage, true, "use_multi_core", Category::Core};
+ SwitchableSetting<MemoryLayout, true> memory_layout_mode{linkage,
+ MemoryLayout::Memory_4Gb,
+ MemoryLayout::Memory_4Gb,
+ MemoryLayout::Memory_8Gb,
+ "memory_layout_mode",
+ Category::Core};
+ SwitchableSetting<bool> use_speed_limit{
+ linkage, true, "use_speed_limit", Category::Core, Specialization::Paired, false, true};
+ SwitchableSetting<u16, true> speed_limit{linkage,
+ 100,
+ 0,
+ 9999,
+ "speed_limit",
+ Category::Core,
+ Specialization::Countable | Specialization::Percentage,
+ true,
+ true,
+ &use_speed_limit};
// Cpu
- SwitchableSetting<CPUAccuracy, true> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
- CPUAccuracy::Paranoid, "cpu_accuracy"};
- // TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021
- Setting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"};
- Setting<bool> cpu_debug_mode{false, "cpu_debug_mode"};
-
- Setting<bool> cpuopt_page_tables{true, "cpuopt_page_tables"};
- Setting<bool> cpuopt_block_linking{true, "cpuopt_block_linking"};
- Setting<bool> cpuopt_return_stack_buffer{true, "cpuopt_return_stack_buffer"};
- Setting<bool> cpuopt_fast_dispatcher{true, "cpuopt_fast_dispatcher"};
- Setting<bool> cpuopt_context_elimination{true, "cpuopt_context_elimination"};
- Setting<bool> cpuopt_const_prop{true, "cpuopt_const_prop"};
- Setting<bool> cpuopt_misc_ir{true, "cpuopt_misc_ir"};
- Setting<bool> cpuopt_reduce_misalign_checks{true, "cpuopt_reduce_misalign_checks"};
- Setting<bool> cpuopt_fastmem{true, "cpuopt_fastmem"};
- Setting<bool> cpuopt_fastmem_exclusives{true, "cpuopt_fastmem_exclusives"};
- Setting<bool> cpuopt_recompile_exclusives{true, "cpuopt_recompile_exclusives"};
- Setting<bool> cpuopt_ignore_memory_aborts{true, "cpuopt_ignore_memory_aborts"};
-
- SwitchableSetting<bool> cpuopt_unsafe_unfuse_fma{true, "cpuopt_unsafe_unfuse_fma"};
- SwitchableSetting<bool> cpuopt_unsafe_reduce_fp_error{true, "cpuopt_unsafe_reduce_fp_error"};
+ SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
+ CpuAccuracy::Auto, CpuAccuracy::Paranoid,
+ "cpu_accuracy", Category::Cpu};
+ Setting<bool> cpu_debug_mode{linkage, false, "cpu_debug_mode", Category::CpuDebug};
+
+ Setting<bool> cpuopt_page_tables{linkage, true, "cpuopt_page_tables", Category::CpuDebug};
+ Setting<bool> cpuopt_block_linking{linkage, true, "cpuopt_block_linking", Category::CpuDebug};
+ Setting<bool> cpuopt_return_stack_buffer{linkage, true, "cpuopt_return_stack_buffer",
+ Category::CpuDebug};
+ Setting<bool> cpuopt_fast_dispatcher{linkage, true, "cpuopt_fast_dispatcher",
+ Category::CpuDebug};
+ Setting<bool> cpuopt_context_elimination{linkage, true, "cpuopt_context_elimination",
+ Category::CpuDebug};
+ Setting<bool> cpuopt_const_prop{linkage, true, "cpuopt_const_prop", Category::CpuDebug};
+ Setting<bool> cpuopt_misc_ir{linkage, true, "cpuopt_misc_ir", Category::CpuDebug};
+ Setting<bool> cpuopt_reduce_misalign_checks{linkage, true, "cpuopt_reduce_misalign_checks",
+ Category::CpuDebug};
+ Setting<bool> cpuopt_fastmem{linkage, true, "cpuopt_fastmem", Category::CpuDebug};
+ Setting<bool> cpuopt_fastmem_exclusives{linkage, true, "cpuopt_fastmem_exclusives",
+ Category::CpuDebug};
+ Setting<bool> cpuopt_recompile_exclusives{linkage, true, "cpuopt_recompile_exclusives",
+ Category::CpuDebug};
+ Setting<bool> cpuopt_ignore_memory_aborts{linkage, true, "cpuopt_ignore_memory_aborts",
+ Category::CpuDebug};
+
+ SwitchableSetting<bool> cpuopt_unsafe_unfuse_fma{linkage, true, "cpuopt_unsafe_unfuse_fma",
+ Category::CpuUnsafe};
+ SwitchableSetting<bool> cpuopt_unsafe_reduce_fp_error{
+ linkage, true, "cpuopt_unsafe_reduce_fp_error", Category::CpuUnsafe};
SwitchableSetting<bool> cpuopt_unsafe_ignore_standard_fpcr{
- true, "cpuopt_unsafe_ignore_standard_fpcr"};
- SwitchableSetting<bool> cpuopt_unsafe_inaccurate_nan{true, "cpuopt_unsafe_inaccurate_nan"};
- SwitchableSetting<bool> cpuopt_unsafe_fastmem_check{true, "cpuopt_unsafe_fastmem_check"};
+ linkage, true, "cpuopt_unsafe_ignore_standard_fpcr", Category::CpuUnsafe};
+ SwitchableSetting<bool> cpuopt_unsafe_inaccurate_nan{
+ linkage, true, "cpuopt_unsafe_inaccurate_nan", Category::CpuUnsafe};
+ SwitchableSetting<bool> cpuopt_unsafe_fastmem_check{
+ linkage, true, "cpuopt_unsafe_fastmem_check", Category::CpuUnsafe};
SwitchableSetting<bool> cpuopt_unsafe_ignore_global_monitor{
- true, "cpuopt_unsafe_ignore_global_monitor"};
+ linkage, true, "cpuopt_unsafe_ignore_global_monitor", Category::CpuUnsafe};
// Renderer
SwitchableSetting<RendererBackend, true> renderer_backend{
- RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, "backend"};
- SwitchableSetting<bool> async_presentation{false, "async_presentation"};
- SwitchableSetting<bool> renderer_force_max_clock{false, "force_max_clock"};
- Setting<bool> renderer_debug{false, "debug"};
- Setting<bool> renderer_shader_feedback{false, "shader_feedback"};
- Setting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
- Setting<bool> disable_shader_loop_safety_checks{false, "disable_shader_loop_safety_checks"};
- SwitchableSetting<int> vulkan_device{0, "vulkan_device"};
-
- ResolutionScalingInfo resolution_info{};
- SwitchableSetting<ResolutionSetup> resolution_setup{ResolutionSetup::Res1X, "resolution_setup"};
- SwitchableSetting<ScalingFilter> scaling_filter{ScalingFilter::Bilinear, "scaling_filter"};
- SwitchableSetting<int, true> fsr_sharpening_slider{25, 0, 200, "fsr_sharpening_slider"};
- SwitchableSetting<AntiAliasing> anti_aliasing{AntiAliasing::None, "anti_aliasing"};
+ linkage, RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null,
+ "backend", Category::Renderer};
+ SwitchableSetting<ShaderBackend, true> shader_backend{
+ linkage, ShaderBackend::Glsl, ShaderBackend::Glsl, ShaderBackend::SpirV,
+ "shader_backend", Category::Renderer, Specialization::RuntimeList};
+ SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer,
+ Specialization::RuntimeList};
+
+ SwitchableSetting<bool> use_disk_shader_cache{linkage, true, "use_disk_shader_cache",
+ Category::Renderer};
+ SwitchableSetting<bool> use_asynchronous_gpu_emulation{
+ linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer};
+ SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage,
+ AstcDecodeMode::Gpu,
+ AstcDecodeMode::Cpu,
+ AstcDecodeMode::CpuAsynchronous,
+ "accelerate_astc",
+ Category::Renderer};
+ Setting<VSyncMode, true> vsync_mode{
+ linkage, VSyncMode::Fifo, VSyncMode::Immediate, VSyncMode::FifoRelaxed,
+ "use_vsync", Category::Renderer, Specialization::RuntimeList, true,
+ true};
+ SwitchableSetting<NvdecEmulation> nvdec_emulation{linkage, NvdecEmulation::Gpu,
+ "nvdec_emulation", Category::Renderer};
// *nix platforms may have issues with the borderless windowed fullscreen mode.
// Default to exclusive fullscreen on these platforms for now.
- SwitchableSetting<FullscreenMode, true> fullscreen_mode{
+ SwitchableSetting<FullscreenMode, true> fullscreen_mode{linkage,
#ifdef _WIN32
- FullscreenMode::Borderless,
+ FullscreenMode::Borderless,
#else
- FullscreenMode::Exclusive,
+ FullscreenMode::Exclusive,
#endif
- FullscreenMode::Borderless, FullscreenMode::Exclusive, "fullscreen_mode"};
- SwitchableSetting<int, true> aspect_ratio{0, 0, 4, "aspect_ratio"};
- SwitchableSetting<int, true> max_anisotropy{0, 0, 5, "max_anisotropy"};
- SwitchableSetting<bool> use_speed_limit{true, "use_speed_limit"};
- SwitchableSetting<u16, true> speed_limit{100, 0, 9999, "speed_limit"};
- SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
- SwitchableSetting<GPUAccuracy, true> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal,
- GPUAccuracy::Extreme, "gpu_accuracy"};
- SwitchableSetting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"};
- SwitchableSetting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
- SwitchableSetting<bool> accelerate_astc{true, "accelerate_astc"};
- SwitchableSetting<bool> async_astc{false, "async_astc"};
- Setting<VSyncMode, true> vsync_mode{VSyncMode::FIFO, VSyncMode::Immediate,
- VSyncMode::FIFORelaxed, "use_vsync"};
- SwitchableSetting<bool> use_reactive_flushing{true, "use_reactive_flushing"};
- SwitchableSetting<ShaderBackend, true> shader_backend{ShaderBackend::GLSL, ShaderBackend::GLSL,
- ShaderBackend::SPIRV, "shader_backend"};
- SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
- SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
- SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{true,
- "use_vulkan_driver_pipeline_cache"};
- SwitchableSetting<bool> enable_compute_pipelines{false, "enable_compute_pipelines"};
- SwitchableSetting<AstcRecompression, true> astc_recompression{
- AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
- "astc_recompression"};
- SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
- SwitchableSetting<bool> barrier_feedback_loops{true, "barrier_feedback_loops"};
-
- SwitchableSetting<u8> bg_red{0, "bg_red"};
- SwitchableSetting<u8> bg_green{0, "bg_green"};
- SwitchableSetting<u8> bg_blue{0, "bg_blue"};
+ FullscreenMode::Borderless,
+ FullscreenMode::Exclusive,
+ "fullscreen_mode",
+ Category::Renderer,
+ Specialization::Default,
+ true,
+ true};
+ SwitchableSetting<AspectRatio, true> aspect_ratio{linkage,
+ AspectRatio::R16_9,
+ AspectRatio::R16_9,
+ AspectRatio::Stretch,
+ "aspect_ratio",
+ Category::Renderer,
+ Specialization::Default,
+ true,
+ true};
+
+ ResolutionScalingInfo resolution_info{};
+ SwitchableSetting<ResolutionSetup> resolution_setup{linkage, ResolutionSetup::Res1X,
+ "resolution_setup", Category::Renderer};
+ SwitchableSetting<ScalingFilter> scaling_filter{linkage,
+ ScalingFilter::Bilinear,
+ "scaling_filter",
+ Category::Renderer,
+ Specialization::Default,
+ true,
+ true};
+ SwitchableSetting<AntiAliasing> anti_aliasing{linkage,
+ AntiAliasing::None,
+ "anti_aliasing",
+ Category::Renderer,
+ Specialization::Default,
+ true,
+ true};
+ SwitchableSetting<int, true> fsr_sharpening_slider{linkage,
+ 25,
+ 0,
+ 200,
+ "fsr_sharpening_slider",
+ Category::Renderer,
+ Specialization::Scalar |
+ Specialization::Percentage,
+ true,
+ true};
+
+ SwitchableSetting<u8, false> bg_red{
+ linkage, 0, "bg_red", Category::Renderer, Specialization::Default, true, true};
+ SwitchableSetting<u8, false> bg_green{
+ linkage, 0, "bg_green", Category::Renderer, Specialization::Default, true, true};
+ SwitchableSetting<u8, false> bg_blue{
+ linkage, 0, "bg_blue", Category::Renderer, Specialization::Default, true, true};
+
+ SwitchableSetting<GpuAccuracy, true> gpu_accuracy{linkage,
+ GpuAccuracy::High,
+ GpuAccuracy::Normal,
+ GpuAccuracy::Extreme,
+ "gpu_accuracy",
+ Category::RendererAdvanced,
+ Specialization::Default,
+ true,
+ true};
+ SwitchableSetting<AnisotropyMode, true> max_anisotropy{
+ linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16,
+ "max_anisotropy", Category::RendererAdvanced};
+ SwitchableSetting<AstcRecompression, true> astc_recompression{linkage,
+ AstcRecompression::Uncompressed,
+ AstcRecompression::Uncompressed,
+ AstcRecompression::Bc3,
+ "astc_recompression",
+ Category::RendererAdvanced};
+ SwitchableSetting<bool> async_presentation{linkage, false, "async_presentation",
+ Category::RendererAdvanced};
+ SwitchableSetting<bool> renderer_force_max_clock{linkage, false, "force_max_clock",
+ Category::RendererAdvanced};
+ SwitchableSetting<bool> use_reactive_flushing{linkage, true, "use_reactive_flushing",
+ Category::RendererAdvanced};
+ SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
+ Category::RendererAdvanced};
+ SwitchableSetting<bool> use_fast_gpu_time{
+ linkage, true, "use_fast_gpu_time", Category::RendererAdvanced, Specialization::Default,
+ true, true};
+ SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{linkage,
+ true,
+ "use_vulkan_driver_pipeline_cache",
+ Category::RendererAdvanced,
+ Specialization::Default,
+ true,
+ true};
+ SwitchableSetting<bool> enable_compute_pipelines{linkage, false, "enable_compute_pipelines",
+ Category::RendererAdvanced};
+ SwitchableSetting<bool> use_video_framerate{linkage, false, "use_video_framerate",
+ Category::RendererAdvanced};
+ SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
+ Category::RendererAdvanced};
+
+ Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
+ Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
+ Category::RendererDebug};
+ Setting<bool> enable_nsight_aftermath{linkage, false, "nsight_aftermath",
+ Category::RendererDebug};
+ Setting<bool> disable_shader_loop_safety_checks{
+ linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
+ Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
+ Category::RendererDebug};
// System
- SwitchableSetting<std::optional<u32>> rng_seed{std::optional<u32>(), "rng_seed"};
- Setting<std::string> device_name{"Yuzu", "device_name"};
+ SwitchableSetting<Language, true> language_index{linkage,
+ Language::EnglishAmerican,
+ Language::Japanese,
+ Language::PortugueseBrazilian,
+ "language_index",
+ Category::System};
+ SwitchableSetting<Region, true> region_index{linkage, Region::Usa, Region::Japan,
+ Region::Taiwan, "region_index", Category::System};
+ SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto,
+ TimeZone::Auto, TimeZone::Zulu,
+ "time_zone_index", Category::System};
// Measured in seconds since epoch
- std::optional<s64> custom_rtc;
+ SwitchableSetting<bool> custom_rtc_enabled{
+ linkage, false, "custom_rtc_enabled", Category::System, Specialization::Paired, true, true};
+ SwitchableSetting<s64> custom_rtc{
+ linkage, 0, "custom_rtc", Category::System, Specialization::Time,
+ true, true, &custom_rtc_enabled};
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
s64 custom_rtc_differential;
-
- Setting<s32> current_user{0, "current_user"};
- SwitchableSetting<s32, true> language_index{1, 0, 17, "language_index"};
- SwitchableSetting<s32, true> region_index{1, 0, 6, "region_index"};
- SwitchableSetting<s32, true> time_zone_index{0, 0, 45, "time_zone_index"};
- SwitchableSetting<s32, true> sound_index{1, 0, 2, "sound_index"};
+ SwitchableSetting<bool> rng_seed_enabled{
+ linkage, false, "rng_seed_enabled", Category::System, Specialization::Paired, true, true};
+ SwitchableSetting<u32> rng_seed{
+ linkage, 0, "rng_seed", Category::System, Specialization::Hex,
+ true, true, &rng_seed_enabled};
+ Setting<std::string> device_name{
+ linkage, "yuzu", "device_name", Category::System, Specialization::Default, true, true};
+
+ Setting<s32> current_user{linkage, 0, "current_user", Category::System};
+
+ SwitchableSetting<ConsoleMode> use_docked_mode{linkage,
+ ConsoleMode::Docked,
+ "use_docked_mode",
+ Category::System,
+ Specialization::Radio,
+ true,
+ true};
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
- SwitchableSetting<bool> use_docked_mode{true, "use_docked_mode"};
-
- Setting<bool> enable_raw_input{false, "enable_raw_input"};
- Setting<bool> controller_navigation{true, "controller_navigation"};
- Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"};
- Setting<bool> enable_procon_driver{false, "enable_procon_driver"};
-
- SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};
- SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
-
- SwitchableSetting<bool> motion_enabled{true, "motion_enabled"};
- Setting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
- Setting<bool> enable_udp_controller{false, "enable_udp_controller"};
-
- Setting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
- Setting<bool> tas_enable{false, "tas_enable"};
- Setting<bool> tas_loop{false, "tas_loop"};
-
- Setting<bool> mouse_panning{false, "mouse_panning"};
- Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
- Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
- Setting<u8, true> mouse_panning_deadzone_counterweight{20, 0, 100,
- "mouse_panning_deadzone_counterweight"};
- Setting<u8, true> mouse_panning_decay_strength{18, 0, 100, "mouse_panning_decay_strength"};
- Setting<u8, true> mouse_panning_min_decay{6, 0, 100, "mouse_panning_min_decay"};
-
- Setting<bool> mouse_enabled{false, "mouse_enabled"};
- Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
- Setting<bool> keyboard_enabled{false, "keyboard_enabled"};
-
- Setting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
+ Setting<bool> enable_raw_input{
+ linkage, false, "enable_raw_input", Category::Controls, Specialization::Default,
+// Only read/write enable_raw_input on Windows platforms
+#ifdef _WIN32
+ true
+#else
+ false
+#endif
+ };
+ Setting<bool> controller_navigation{linkage, true, "controller_navigation", Category::Controls};
+ Setting<bool> enable_joycon_driver{linkage, true, "enable_joycon_driver", Category::Controls};
+ Setting<bool> enable_procon_driver{linkage, false, "enable_procon_driver", Category::Controls};
+
+ SwitchableSetting<bool> vibration_enabled{linkage, true, "vibration_enabled",
+ Category::Controls};
+ SwitchableSetting<bool> enable_accurate_vibrations{linkage, false, "enable_accurate_vibrations",
+ Category::Controls};
+
+ SwitchableSetting<bool> motion_enabled{linkage, true, "motion_enabled", Category::Controls};
+ Setting<std::string> udp_input_servers{linkage, "127.0.0.1:26760", "udp_input_servers",
+ Category::Controls};
+ Setting<bool> enable_udp_controller{linkage, false, "enable_udp_controller",
+ Category::Controls};
+
+ Setting<bool> pause_tas_on_load{linkage, true, "pause_tas_on_load", Category::Controls};
+ Setting<bool> tas_enable{linkage, false, "tas_enable", Category::Controls};
+ Setting<bool> tas_loop{linkage, false, "tas_loop", Category::Controls};
+
+ Setting<bool> mouse_panning{
+ linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false};
+ Setting<u8, true> mouse_panning_sensitivity{
+ linkage, 50, 1, 100, "mouse_panning_sensitivity", Category::Controls};
+ Setting<bool> mouse_enabled{linkage, false, "mouse_enabled", Category::Controls};
+
+ Setting<u8, true> mouse_panning_x_sensitivity{
+ linkage, 50, 1, 100, "mouse_panning_x_sensitivity", Category::Controls};
+ Setting<u8, true> mouse_panning_y_sensitivity{
+ linkage, 50, 1, 100, "mouse_panning_y_sensitivity", Category::Controls};
+ Setting<u8, true> mouse_panning_deadzone_counterweight{
+ linkage, 20, 0, 100, "mouse_panning_deadzone_counterweight", Category::Controls};
+ Setting<u8, true> mouse_panning_decay_strength{
+ linkage, 18, 0, 100, "mouse_panning_decay_strength", Category::Controls};
+ Setting<u8, true> mouse_panning_min_decay{
+ linkage, 6, 0, 100, "mouse_panning_min_decay", Category::Controls};
+
+ Setting<bool> emulate_analog_keyboard{linkage, false, "emulate_analog_keyboard",
+ Category::Controls};
+ Setting<bool> keyboard_enabled{linkage, false, "keyboard_enabled", Category::Controls};
+
+ Setting<bool> debug_pad_enabled{linkage, false, "debug_pad_enabled", Category::Controls};
ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs;
TouchscreenInput touchscreen;
- Setting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850", "touch_device"};
- Setting<int> touch_from_button_map_index{0, "touch_from_button_map"};
+ Setting<std::string> touch_device{linkage, "min_x:100,min_y:50,max_x:1800,max_y:850",
+ "touch_device", Category::Controls};
+ Setting<int> touch_from_button_map_index{linkage, 0, "touch_from_button_map",
+ Category::Controls};
std::vector<TouchFromButtonMap> touch_from_button_maps;
- Setting<bool> enable_ring_controller{true, "enable_ring_controller"};
+ Setting<bool> enable_ring_controller{linkage, true, "enable_ring_controller",
+ Category::Controls};
RingconRaw ringcon_analogs;
- Setting<bool> enable_ir_sensor{false, "enable_ir_sensor"};
- Setting<std::string> ir_sensor_device{"auto", "ir_sensor_device"};
+ Setting<bool> enable_ir_sensor{linkage, false, "enable_ir_sensor", Category::Controls};
+ Setting<std::string> ir_sensor_device{linkage, "auto", "ir_sensor_device", Category::Controls};
- Setting<bool> random_amiibo_id{false, "random_amiibo_id"};
+ Setting<bool> random_amiibo_id{linkage, false, "random_amiibo_id", Category::Controls};
// Data Storage
- Setting<bool> use_virtual_sd{true, "use_virtual_sd"};
- Setting<bool> gamecard_inserted{false, "gamecard_inserted"};
- Setting<bool> gamecard_current_game{false, "gamecard_current_game"};
- Setting<std::string> gamecard_path{std::string(), "gamecard_path"};
+ Setting<bool> use_virtual_sd{linkage, true, "use_virtual_sd", Category::DataStorage};
+ Setting<bool> gamecard_inserted{linkage, false, "gamecard_inserted", Category::DataStorage};
+ Setting<bool> gamecard_current_game{linkage, false, "gamecard_current_game",
+ Category::DataStorage};
+ Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
+ Category::DataStorage};
// Debugging
bool record_frame_times;
- Setting<bool> use_gdbstub{false, "use_gdbstub"};
- Setting<u16> gdbstub_port{6543, "gdbstub_port"};
- Setting<std::string> program_args{std::string(), "program_args"};
- Setting<bool> dump_exefs{false, "dump_exefs"};
- Setting<bool> dump_nso{false, "dump_nso"};
- Setting<bool> dump_shaders{false, "dump_shaders"};
- Setting<bool> dump_macros{false, "dump_macros"};
- Setting<bool> enable_fs_access_log{false, "enable_fs_access_log"};
- Setting<bool> reporting_services{false, "reporting_services"};
- Setting<bool> quest_flag{false, "quest_flag"};
- Setting<bool> disable_macro_jit{false, "disable_macro_jit"};
- Setting<bool> disable_macro_hle{false, "disable_macro_hle"};
- Setting<bool> extended_logging{false, "extended_logging"};
- Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
- Setting<bool> use_auto_stub{false, "use_auto_stub"};
- Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
- Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
- Setting<bool> perform_vulkan_check{true, "perform_vulkan_check"};
+ Setting<bool> use_gdbstub{linkage, false, "use_gdbstub", Category::Debugging};
+ Setting<u16> gdbstub_port{linkage, 6543, "gdbstub_port", Category::Debugging};
+ Setting<std::string> program_args{linkage, std::string(), "program_args", Category::Debugging};
+ Setting<bool> dump_exefs{linkage, false, "dump_exefs", Category::Debugging};
+ Setting<bool> dump_nso{linkage, false, "dump_nso", Category::Debugging};
+ Setting<bool> dump_shaders{
+ linkage, false, "dump_shaders", Category::DebuggingGraphics, Specialization::Default,
+ false};
+ Setting<bool> dump_macros{
+ linkage, false, "dump_macros", Category::DebuggingGraphics, Specialization::Default, false};
+ Setting<bool> enable_fs_access_log{linkage, false, "enable_fs_access_log", Category::Debugging};
+ Setting<bool> reporting_services{
+ linkage, false, "reporting_services", Category::Debugging, Specialization::Default, false};
+ Setting<bool> quest_flag{linkage, false, "quest_flag", Category::Debugging};
+ Setting<bool> disable_macro_jit{linkage, false, "disable_macro_jit",
+ Category::DebuggingGraphics};
+ Setting<bool> disable_macro_hle{linkage, false, "disable_macro_hle",
+ Category::DebuggingGraphics};
+ Setting<bool> extended_logging{
+ linkage, false, "extended_logging", Category::Debugging, Specialization::Default, false};
+ Setting<bool> use_debug_asserts{linkage, false, "use_debug_asserts", Category::Debugging};
+ Setting<bool> use_auto_stub{
+ linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false};
+ Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
+ Category::Debugging};
+ Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging};
+ Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
// Miscellaneous
- Setting<std::string> log_filter{"*:Info", "log_filter"};
- Setting<bool> use_dev_keys{false, "use_dev_keys"};
+ Setting<std::string> log_filter{linkage, "*:Info", "log_filter", Category::Miscellaneous};
+ Setting<bool> use_dev_keys{linkage, false, "use_dev_keys", Category::Miscellaneous};
// Network
- Setting<std::string> network_interface{std::string(), "network_interface"};
+ Setting<std::string> network_interface{linkage, std::string(), "network_interface",
+ Category::Network};
// WebService
- Setting<bool> enable_telemetry{true, "enable_telemetry"};
- Setting<std::string> web_api_url{"https://api.yuzu-emu.org", "web_api_url"};
- Setting<std::string> yuzu_username{std::string(), "yuzu_username"};
- Setting<std::string> yuzu_token{std::string(), "yuzu_token"};
+ Setting<bool> enable_telemetry{linkage, true, "enable_telemetry", Category::WebService};
+ Setting<std::string> web_api_url{linkage, "https://api.yuzu-emu.org", "web_api_url",
+ Category::WebService};
+ Setting<std::string> yuzu_username{linkage, std::string(), "yuzu_username",
+ Category::WebService};
+ Setting<std::string> yuzu_token{linkage, std::string(), "yuzu_token", Category::WebService};
// Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons;
@@ -600,23 +522,26 @@ struct Values {
extern Values values;
-bool IsConfiguringGlobal();
-void SetConfiguringGlobal(bool is_global);
-
bool IsGPULevelExtreme();
bool IsGPULevelHigh();
bool IsFastmemEnabled();
+bool IsDockedMode();
+
float Volume();
-std::string GetTimeZoneString();
+std::string GetTimeZoneString(TimeZone time_zone);
void LogSettings();
+void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info);
void UpdateRescalingInfo();
// Restore the global state of all applicable settings in the Values struct
void RestoreGlobalState(bool is_powered_on);
+bool IsConfiguringGlobal();
+void SetConfiguringGlobal(bool is_global);
+
} // namespace Settings
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp
new file mode 100644
index 000000000..5960b78aa
--- /dev/null
+++ b/src/common/settings_common.cpp
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <functional>
+#include <string>
+#include <vector>
+#include "common/settings_common.h"
+
+namespace Settings {
+
+BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Category category_,
+ bool save_, bool runtime_modifiable_, u32 specialization_,
+ BasicSetting* other_setting_)
+ : label{name}, category{category_}, id{linkage.count}, save{save_},
+ runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
+ other_setting{other_setting_} {
+ linkage.by_key.insert({name, this});
+ linkage.by_category[category].push_back(this);
+ linkage.count++;
+}
+
+BasicSetting::~BasicSetting() = default;
+
+std::string BasicSetting::ToStringGlobal() const {
+ return this->ToString();
+}
+
+bool BasicSetting::UsingGlobal() const {
+ return true;
+}
+
+void BasicSetting::SetGlobal(bool global) {}
+
+bool BasicSetting::Save() const {
+ return save;
+}
+
+bool BasicSetting::RuntimeModfiable() const {
+ return runtime_modifiable;
+}
+
+Category BasicSetting::GetCategory() const {
+ return category;
+}
+
+u32 BasicSetting::Specialization() const {
+ return specialization;
+}
+
+BasicSetting* BasicSetting::PairedSetting() const {
+ return other_setting;
+}
+
+const std::string& BasicSetting::GetLabel() const {
+ return label;
+}
+
+Linkage::Linkage(u32 initial_count) : count{initial_count} {}
+Linkage::~Linkage() = default;
+
+} // namespace Settings
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
new file mode 100644
index 000000000..1800ab10d
--- /dev/null
+++ b/src/common/settings_common.h
@@ -0,0 +1,269 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <map>
+#include <string>
+#include <typeindex>
+#include "common/common_types.h"
+
+namespace Settings {
+
+enum class Category : u32 {
+ Android,
+ Audio,
+ Core,
+ Cpu,
+ CpuDebug,
+ CpuUnsafe,
+ Renderer,
+ RendererAdvanced,
+ RendererDebug,
+ System,
+ SystemAudio,
+ DataStorage,
+ Debugging,
+ DebuggingGraphics,
+ Miscellaneous,
+ Network,
+ WebService,
+ AddOns,
+ Controls,
+ Ui,
+ UiGeneral,
+ UiLayout,
+ UiGameList,
+ Screenshots,
+ Shortcuts,
+ Multiplayer,
+ Services,
+ Paths,
+ MaxEnum,
+};
+
+constexpr u8 SpecializationTypeMask = 0xf;
+constexpr u8 SpecializationAttributeMask = 0xf0;
+constexpr u8 SpecializationAttributeOffset = 4;
+
+// Scalar and countable could have better names
+enum Specialization : u8 {
+ Default = 0,
+ Time = 1, // Duration or specific moment in time
+ Hex = 2, // Hexadecimal number
+ List = 3, // Setting has specific members
+ RuntimeList = 4, // Members of the list are determined during runtime
+ Scalar = 5, // Values are continuous
+ Countable = 6, // Can be stepped through
+ Paired = 7, // Another setting is associated with this setting
+ Radio = 8, // Setting should be presented in a radio group
+
+ Percentage = (1 << SpecializationAttributeOffset), // Should be represented as a percentage
+};
+
+class BasicSetting;
+
+class Linkage {
+public:
+ explicit Linkage(u32 initial_count = 0);
+ ~Linkage();
+ std::map<Category, std::vector<BasicSetting*>> by_category{};
+ std::map<std::string, Settings::BasicSetting*> by_key{};
+ std::vector<std::function<void()>> restore_functions{};
+ u32 count;
+};
+
+/**
+ * BasicSetting is an abstract class that only keeps track of metadata. The string methods are
+ * available to get data values out.
+ */
+class BasicSetting {
+protected:
+ explicit BasicSetting(Linkage& linkage, const std::string& name, Category category_, bool save_,
+ bool runtime_modifiable_, u32 specialization,
+ BasicSetting* other_setting);
+
+public:
+ virtual ~BasicSetting();
+
+ /*
+ * Data retrieval
+ */
+
+ /**
+ * Returns a string representation of the internal data. If the Setting is Switchable, it
+ * respects the internal global state: it is based on GetValue().
+ *
+ * @returns A string representation of the internal data.
+ */
+ [[nodiscard]] virtual std::string ToString() const = 0;
+
+ /**
+ * Returns a string representation of the global version of internal data. If the Setting is
+ * not Switchable, it behaves like ToString.
+ *
+ * @returns A string representation of the global version of internal data.
+ */
+ [[nodiscard]] virtual std::string ToStringGlobal() const;
+
+ /**
+ * @returns A string representation of the Setting's default value.
+ */
+ [[nodiscard]] virtual std::string DefaultToString() const = 0;
+
+ /**
+ * Returns a string representation of the minimum value of the setting. If the Setting is not
+ * ranged, the string represents the default initialization of the data type.
+ *
+ * @returns A string representation of the minimum value of the setting.
+ */
+ [[nodiscard]] virtual std::string MinVal() const = 0;
+
+ /**
+ * Returns a string representation of the maximum value of the setting. If the Setting is not
+ * ranged, the string represents the default initialization of the data type.
+ *
+ * @returns A string representation of the maximum value of the setting.
+ */
+ [[nodiscard]] virtual std::string MaxVal() const = 0;
+
+ /**
+ * Takes a string input, converts it to the internal data type if necessary, and then runs
+ * SetValue with it.
+ *
+ * @param load String of the input data.
+ */
+ virtual void LoadString(const std::string& load) = 0;
+
+ /**
+ * Returns a string representation of the data. If the data is an enum, it returns a string of
+ * the enum value. If the internal data type is not an enum, this is equivalent to ToString.
+ *
+ * e.g. renderer_backend.Canonicalize() == "OpenGL"
+ *
+ * @returns Canonicalized string representation of the internal data
+ */
+ [[nodiscard]] virtual std::string Canonicalize() const = 0;
+
+ /*
+ * Metadata
+ */
+
+ /**
+ * @returns A unique identifier for the Setting's internal data type.
+ */
+ [[nodiscard]] virtual std::type_index TypeId() const = 0;
+
+ /**
+ * Returns true if the Setting's internal data type is an enum.
+ *
+ * @returns True if the Setting's internal data type is an enum
+ */
+ [[nodiscard]] virtual constexpr bool IsEnum() const = 0;
+
+ /**
+ * Returns true if the current setting is Switchable.
+ *
+ * @returns If the setting is a SwitchableSetting
+ */
+ [[nodiscard]] virtual constexpr bool Switchable() const {
+ return false;
+ }
+
+ /**
+ * Returns true to suggest that a frontend can read or write the setting to a configuration
+ * file.
+ *
+ * @returns The save preference
+ */
+ [[nodiscard]] bool Save() const;
+
+ /**
+ * @returns true if the current setting can be changed while the guest is running.
+ */
+ [[nodiscard]] bool RuntimeModfiable() const;
+
+ /**
+ * @returns A unique number corresponding to the setting.
+ */
+ [[nodiscard]] constexpr u32 Id() const {
+ return id;
+ }
+
+ /**
+ * Returns the setting's category AKA INI group.
+ *
+ * @returns The setting's category
+ */
+ [[nodiscard]] Category GetCategory() const;
+
+ /**
+ * @returns Extra metadata for data representation in frontend implementations.
+ */
+ [[nodiscard]] u32 Specialization() const;
+
+ /**
+ * @returns Another BasicSetting if one is paired, or nullptr otherwise.
+ */
+ [[nodiscard]] BasicSetting* PairedSetting() const;
+
+ /**
+ * Returns the label this setting was created with.
+ *
+ * @returns A reference to the label
+ */
+ [[nodiscard]] const std::string& GetLabel() const;
+
+ /**
+ * @returns If the Setting checks input values for valid ranges.
+ */
+ [[nodiscard]] virtual constexpr bool Ranged() const = 0;
+
+ /**
+ * @returns The index of the enum if the underlying setting type is an enum, else max of u32.
+ */
+ [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
+
+ /**
+ * @returns True if the underlying type is a floating point storage
+ */
+ [[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0;
+
+ /**
+ * @returns True if the underlying type is an integer storage
+ */
+ [[nodiscard]] virtual constexpr bool IsIntegral() const = 0;
+
+ /*
+ * Switchable settings
+ */
+
+ /**
+ * Sets a setting's global state. True means use the normal setting, false to use a custom
+ * value. Has no effect if the Setting is not Switchable.
+ *
+ * @param global The desired state
+ */
+ virtual void SetGlobal(bool global);
+
+ /**
+ * Returns true if the setting is using the normal setting value. Always true if the setting is
+ * not Switchable.
+ *
+ * @returns The Setting's global state
+ */
+ [[nodiscard]] virtual bool UsingGlobal() const;
+
+private:
+ const std::string label; ///< The setting's label
+ const Category category; ///< The setting's category AKA INI group
+ const u32 id; ///< Unique integer for the setting
+ const bool save; ///< Suggests if the setting should be saved and read to a frontend config
+ const bool
+ runtime_modifiable; ///< Suggests if the setting can be modified while a guest is running
+ const u32 specialization; ///< Extra data to identify representation of a setting
+ BasicSetting* const other_setting; ///< A paired setting
+};
+
+} // namespace Settings
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
new file mode 100644
index 000000000..815cafe15
--- /dev/null
+++ b/src/common/settings_enums.h
@@ -0,0 +1,216 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+#include <utility>
+#include <vector>
+#include "common/common_types.h"
+
+namespace Settings {
+
+template <typename T>
+struct EnumMetadata {
+ static std::vector<std::pair<std::string, T>> Canonicalizations();
+ static u32 Index();
+};
+
+#define PAIR_45(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_46(N, __VA_ARGS__))
+#define PAIR_44(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_45(N, __VA_ARGS__))
+#define PAIR_43(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_44(N, __VA_ARGS__))
+#define PAIR_42(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_43(N, __VA_ARGS__))
+#define PAIR_41(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_42(N, __VA_ARGS__))
+#define PAIR_40(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_41(N, __VA_ARGS__))
+#define PAIR_39(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_40(N, __VA_ARGS__))
+#define PAIR_38(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_39(N, __VA_ARGS__))
+#define PAIR_37(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_38(N, __VA_ARGS__))
+#define PAIR_36(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_37(N, __VA_ARGS__))
+#define PAIR_35(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_36(N, __VA_ARGS__))
+#define PAIR_34(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_35(N, __VA_ARGS__))
+#define PAIR_33(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_34(N, __VA_ARGS__))
+#define PAIR_32(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_33(N, __VA_ARGS__))
+#define PAIR_31(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_32(N, __VA_ARGS__))
+#define PAIR_30(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_31(N, __VA_ARGS__))
+#define PAIR_29(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_30(N, __VA_ARGS__))
+#define PAIR_28(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_29(N, __VA_ARGS__))
+#define PAIR_27(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_28(N, __VA_ARGS__))
+#define PAIR_26(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_27(N, __VA_ARGS__))
+#define PAIR_25(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_26(N, __VA_ARGS__))
+#define PAIR_24(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_25(N, __VA_ARGS__))
+#define PAIR_23(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_24(N, __VA_ARGS__))
+#define PAIR_22(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_23(N, __VA_ARGS__))
+#define PAIR_21(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_22(N, __VA_ARGS__))
+#define PAIR_20(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_21(N, __VA_ARGS__))
+#define PAIR_19(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_20(N, __VA_ARGS__))
+#define PAIR_18(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_19(N, __VA_ARGS__))
+#define PAIR_17(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_18(N, __VA_ARGS__))
+#define PAIR_16(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_17(N, __VA_ARGS__))
+#define PAIR_15(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_16(N, __VA_ARGS__))
+#define PAIR_14(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_15(N, __VA_ARGS__))
+#define PAIR_13(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_14(N, __VA_ARGS__))
+#define PAIR_12(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_13(N, __VA_ARGS__))
+#define PAIR_11(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_12(N, __VA_ARGS__))
+#define PAIR_10(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_11(N, __VA_ARGS__))
+#define PAIR_9(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_10(N, __VA_ARGS__))
+#define PAIR_8(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_9(N, __VA_ARGS__))
+#define PAIR_7(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_8(N, __VA_ARGS__))
+#define PAIR_6(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_7(N, __VA_ARGS__))
+#define PAIR_5(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_6(N, __VA_ARGS__))
+#define PAIR_4(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_5(N, __VA_ARGS__))
+#define PAIR_3(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_4(N, __VA_ARGS__))
+#define PAIR_2(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_3(N, __VA_ARGS__))
+#define PAIR_1(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_2(N, __VA_ARGS__))
+#define PAIR(N, X, ...) {#X, N::X} __VA_OPT__(, PAIR_1(N, __VA_ARGS__))
+
+#define ENUM(NAME, ...) \
+ enum class NAME : u32 { __VA_ARGS__ }; \
+ template <> \
+ inline std::vector<std::pair<std::string, NAME>> EnumMetadata<NAME>::Canonicalizations() { \
+ return {PAIR(NAME, __VA_ARGS__)}; \
+ } \
+ template <> \
+ inline u32 EnumMetadata<NAME>::Index() { \
+ return __COUNTER__; \
+ }
+
+// AudioEngine must be specified discretely due to having existing but slightly different
+// canonicalizations
+// TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id
+enum class AudioEngine : u32 {
+ Auto,
+ Cubeb,
+ Sdl2,
+ Null,
+};
+
+template <>
+inline std::vector<std::pair<std::string, AudioEngine>>
+EnumMetadata<AudioEngine>::Canonicalizations() {
+ return {
+ {"auto", AudioEngine::Auto},
+ {"cubeb", AudioEngine::Cubeb},
+ {"sdl2", AudioEngine::Sdl2},
+ {"null", AudioEngine::Null},
+ };
+}
+
+template <>
+inline u32 EnumMetadata<AudioEngine>::Index() {
+ // This is just a sufficiently large number that is more than the number of other enums declared
+ // here
+ return 100;
+}
+
+ENUM(AudioMode, Mono, Stereo, Surround);
+
+ENUM(Language, Japanese, EnglishAmerican, French, German, Italian, Spanish, Chinese, Korean, Dutch,
+ Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin,
+ ChineseSimplified, ChineseTraditional, PortugueseBrazilian);
+
+ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan);
+
+ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt, Gb, GbEire, Gmt,
+ GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
+ Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
+ Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
+
+ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16);
+
+ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
+
+ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
+
+ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed);
+
+ENUM(RendererBackend, OpenGL, Vulkan, Null);
+
+ENUM(ShaderBackend, Glsl, Glasm, SpirV);
+
+ENUM(GpuAccuracy, Normal, High, Extreme);
+
+ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
+
+ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
+
+ENUM(FullscreenMode, Borderless, Exclusive);
+
+ENUM(NvdecEmulation, Off, Cpu, Gpu);
+
+ENUM(ResolutionSetup, Res1_2X, Res3_4X, Res1X, Res3_2X, Res2X, Res3X, Res4X, Res5X, Res6X, Res7X,
+ Res8X);
+
+ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, ScaleForce, Fsr, MaxEnum);
+
+ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);
+
+ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
+
+ENUM(ConsoleMode, Handheld, Docked);
+
+template <typename Type>
+inline std::string CanonicalizeEnum(Type id) {
+ const auto group = EnumMetadata<Type>::Canonicalizations();
+ for (auto& [name, value] : group) {
+ if (value == id) {
+ return name;
+ }
+ }
+ return "unknown";
+}
+
+template <typename Type>
+inline Type ToEnum(const std::string& canonicalization) {
+ const auto group = EnumMetadata<Type>::Canonicalizations();
+ for (auto& [name, value] : group) {
+ if (name == canonicalization) {
+ return value;
+ }
+ }
+ return {};
+}
+} // namespace Settings
+
+#undef ENUM
+#undef PAIR
+#undef PAIR_1
+#undef PAIR_2
+#undef PAIR_3
+#undef PAIR_4
+#undef PAIR_5
+#undef PAIR_6
+#undef PAIR_7
+#undef PAIR_8
+#undef PAIR_9
+#undef PAIR_10
+#undef PAIR_12
+#undef PAIR_13
+#undef PAIR_14
+#undef PAIR_15
+#undef PAIR_16
+#undef PAIR_17
+#undef PAIR_18
+#undef PAIR_19
+#undef PAIR_20
+#undef PAIR_22
+#undef PAIR_23
+#undef PAIR_24
+#undef PAIR_25
+#undef PAIR_26
+#undef PAIR_27
+#undef PAIR_28
+#undef PAIR_29
+#undef PAIR_30
+#undef PAIR_32
+#undef PAIR_33
+#undef PAIR_34
+#undef PAIR_35
+#undef PAIR_36
+#undef PAIR_37
+#undef PAIR_38
+#undef PAIR_39
+#undef PAIR_40
+#undef PAIR_42
+#undef PAIR_43
+#undef PAIR_44
+#undef PAIR_45
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
new file mode 100644
index 000000000..7be6f26f7
--- /dev/null
+++ b/src/common/settings_setting.h
@@ -0,0 +1,417 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <limits>
+#include <map>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <typeindex>
+#include <typeinfo>
+#include <fmt/core.h>
+#include "common/common_types.h"
+#include "common/settings_common.h"
+#include "common/settings_enums.h"
+
+namespace Settings {
+
+/** The Setting class is a simple resource manager. It defines a label and default value
+ * alongside the actual value of the setting for simpler and less-error prone use with frontend
+ * configurations. Specifying a default value and label is required. A minimum and maximum range
+ * can be specified for sanitization.
+ */
+template <typename Type, bool ranged = false>
+class Setting : public BasicSetting {
+protected:
+ Setting() = default;
+
+public:
+ /**
+ * Sets a default value, label, and setting value.
+ *
+ * @param linkage Setting registry
+ * @param default_val Initial value of the setting, and default value of the setting
+ * @param name Label for the setting
+ * @param category_ Category of the setting AKA INI group
+ * @param specialization_ Suggestion for how frontend implementations represent this in a config
+ * @param save_ Suggests that this should or should not be saved to a frontend config file
+ * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
+ * @param other_setting_ A second Setting to associate to this one in metadata
+ */
+ explicit Setting(Linkage& linkage, const Type& default_val, const std::string& name,
+ Category category_, u32 specialization_ = Specialization::Default,
+ bool save_ = true, bool runtime_modifiable_ = false,
+ BasicSetting* other_setting_ = nullptr)
+ requires(!ranged)
+ : BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_,
+ other_setting_),
+ value{default_val}, default_value{default_val} {}
+ virtual ~Setting() = default;
+
+ /**
+ * Sets a default value, minimum value, maximum value, and label.
+ *
+ * @param linkage Setting registry
+ * @param default_val Initial value of the setting, and default value of the setting
+ * @param min_val Sets the minimum allowed value of the setting
+ * @param max_val Sets the maximum allowed value of the setting
+ * @param name Label for the setting
+ * @param category_ Category of the setting AKA INI group
+ * @param specialization_ Suggestion for how frontend implementations represent this in a config
+ * @param save_ Suggests that this should or should not be saved to a frontend config file
+ * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
+ * @param other_setting_ A second Setting to associate to this one in metadata
+ */
+ explicit Setting(Linkage& linkage, const Type& default_val, const Type& min_val,
+ const Type& max_val, const std::string& name, Category category_,
+ u32 specialization_ = Specialization::Default, bool save_ = true,
+ bool runtime_modifiable_ = false, BasicSetting* other_setting_ = nullptr)
+ requires(ranged)
+ : BasicSetting(linkage, name, category_, save_, runtime_modifiable_, specialization_,
+ other_setting_),
+ value{default_val}, default_value{default_val}, maximum{max_val}, minimum{min_val} {}
+
+ /**
+ * Returns a reference to the setting's value.
+ *
+ * @returns A reference to the setting
+ */
+ [[nodiscard]] virtual const Type& GetValue() const {
+ return value;
+ }
+
+ /**
+ * Sets the setting to the given value.
+ *
+ * @param val The desired value
+ */
+ virtual void SetValue(const Type& val) {
+ Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
+ std::swap(value, temp);
+ }
+
+ /**
+ * Returns the value that this setting was created with.
+ *
+ * @returns A reference to the default value
+ */
+ [[nodiscard]] const Type& GetDefault() const {
+ return default_value;
+ }
+
+ [[nodiscard]] constexpr bool IsEnum() const override {
+ return std::is_enum_v<Type>;
+ }
+
+protected:
+ [[nodiscard]] std::string ToString(const Type& value_) const {
+ if constexpr (std::is_same_v<Type, std::string>) {
+ return value_;
+ } else if constexpr (std::is_same_v<Type, std::optional<u32>>) {
+ return value_.has_value() ? std::to_string(*value_) : "none";
+ } else if constexpr (std::is_same_v<Type, bool>) {
+ return value_ ? "true" : "false";
+ } else if constexpr (std::is_same_v<Type, AudioEngine>) {
+ // Compatibility with old AudioEngine setting being a string
+ return CanonicalizeEnum(value_);
+ } else if constexpr (std::is_floating_point_v<Type>) {
+ return fmt::format("{:f}", value_);
+ } else if constexpr (std::is_enum_v<Type>) {
+ return std::to_string(static_cast<u32>(value_));
+ } else {
+ return std::to_string(value_);
+ }
+ }
+
+public:
+ /**
+ * Converts the value of the setting to a std::string. Respects the global state if the setting
+ * has one.
+ *
+ * @returns The current setting as a std::string
+ */
+ [[nodiscard]] std::string ToString() const override {
+ return ToString(this->GetValue());
+ }
+
+ /**
+ * Returns the default value of the setting as a std::string.
+ *
+ * @returns The default value as a string.
+ */
+ [[nodiscard]] std::string DefaultToString() const override {
+ return ToString(default_value);
+ }
+
+ /**
+ * Assigns a value to the setting.
+ *
+ * @param val The desired setting value
+ *
+ * @returns A reference to the setting
+ */
+ virtual const Type& operator=(const Type& val) {
+ Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
+ std::swap(value, temp);
+ return value;
+ }
+
+ /**
+ * Returns a reference to the setting.
+ *
+ * @returns A reference to the setting
+ */
+ explicit virtual operator const Type&() const {
+ return value;
+ }
+
+ /**
+ * Converts the given value to the Setting's type of value. Uses SetValue to enter the setting,
+ * thus respecting its constraints.
+ *
+ * @param input The desired value
+ */
+ void LoadString(const std::string& input) override final {
+ if (input.empty()) {
+ this->SetValue(this->GetDefault());
+ return;
+ }
+ try {
+ if constexpr (std::is_same_v<Type, std::string>) {
+ this->SetValue(input);
+ } else if constexpr (std::is_same_v<Type, std::optional<u32>>) {
+ this->SetValue(static_cast<u32>(std::stoul(input)));
+ } else if constexpr (std::is_same_v<Type, bool>) {
+ this->SetValue(input == "true");
+ } else if constexpr (std::is_same_v<Type, float>) {
+ this->SetValue(std::stof(input));
+ } else {
+ this->SetValue(static_cast<Type>(std::stoll(input)));
+ }
+ } catch (std::invalid_argument&) {
+ this->SetValue(this->GetDefault());
+ } catch (std::out_of_range&) {
+ this->SetValue(this->GetDefault());
+ }
+ }
+
+ [[nodiscard]] std::string Canonicalize() const override final {
+ if constexpr (std::is_enum_v<Type>) {
+ return CanonicalizeEnum(this->GetValue());
+ } else {
+ return ToString(this->GetValue());
+ }
+ }
+
+ /**
+ * Gives us another way to identify the setting without having to go through a string.
+ *
+ * @returns the type_index of the setting's type
+ */
+ [[nodiscard]] std::type_index TypeId() const override final {
+ return std::type_index(typeid(Type));
+ }
+
+ [[nodiscard]] constexpr u32 EnumIndex() const override final {
+ if constexpr (std::is_enum_v<Type>) {
+ return EnumMetadata<Type>::Index();
+ } else {
+ return std::numeric_limits<u32>::max();
+ }
+ }
+
+ [[nodiscard]] constexpr bool IsFloatingPoint() const final {
+ return std::is_floating_point_v<Type>;
+ }
+
+ [[nodiscard]] constexpr bool IsIntegral() const final {
+ return std::is_integral_v<Type>;
+ }
+
+ [[nodiscard]] std::string MinVal() const override final {
+ if constexpr (std::is_arithmetic_v<Type> && !ranged) {
+ return this->ToString(std::numeric_limits<Type>::min());
+ } else {
+ return this->ToString(minimum);
+ }
+ }
+ [[nodiscard]] std::string MaxVal() const override final {
+ if constexpr (std::is_arithmetic_v<Type> && !ranged) {
+ return this->ToString(std::numeric_limits<Type>::max());
+ } else {
+ return this->ToString(maximum);
+ }
+ }
+
+ [[nodiscard]] constexpr bool Ranged() const override {
+ return ranged;
+ }
+
+protected:
+ Type value{}; ///< The setting
+ const Type default_value{}; ///< The default value
+ const Type maximum{}; ///< Maximum allowed value of the setting
+ const Type minimum{}; ///< Minimum allowed value of the setting
+};
+
+/**
+ * The SwitchableSetting class is a slightly more complex version of the Setting class. This adds a
+ * custom setting to switch to when a guest application specifically requires it. The effect is that
+ * other components of the emulator can access the setting's intended value without any need for the
+ * component to ask whether the custom or global setting is needed at the moment.
+ *
+ * By default, the global setting is used.
+ */
+template <typename Type, bool ranged = false>
+class SwitchableSetting : virtual public Setting<Type, ranged> {
+public:
+ /**
+ * Sets a default value, label, and setting value.
+ *
+ * @param linkage Setting registry
+ * @param default_val Initial value of the setting, and default value of the setting
+ * @param name Label for the setting
+ * @param category_ Category of the setting AKA INI group
+ * @param specialization_ Suggestion for how frontend implementations represent this in a config
+ * @param save_ Suggests that this should or should not be saved to a frontend config file
+ * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
+ * @param other_setting_ A second Setting to associate to this one in metadata
+ */
+ template <typename T = BasicSetting>
+ explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const std::string& name,
+ Category category_, u32 specialization_ = Specialization::Default,
+ bool save_ = true, bool runtime_modifiable_ = false,
+ typename std::enable_if<!ranged, T*>::type other_setting_ = nullptr)
+ : Setting<Type, false>{
+ linkage, default_val, name, category_, specialization_,
+ save_, runtime_modifiable_, other_setting_} {
+ linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
+ }
+ virtual ~SwitchableSetting() = default;
+
+ /**
+ * Sets a default value, minimum value, maximum value, and label.
+ *
+ * @param linkage Setting registry
+ * @param default_val Initial value of the setting, and default value of the setting
+ * @param min_val Sets the minimum allowed value of the setting
+ * @param max_val Sets the maximum allowed value of the setting
+ * @param name Label for the setting
+ * @param category_ Category of the setting AKA INI group
+ * @param specialization_ Suggestion for how frontend implementations represent this in a config
+ * @param save_ Suggests that this should or should not be saved to a frontend config file
+ * @param runtime_modifiable_ Suggests whether this is modifiable while a guest is loaded
+ * @param other_setting_ A second Setting to associate to this one in metadata
+ */
+ template <typename T = BasicSetting>
+ explicit SwitchableSetting(Linkage& linkage, const Type& default_val, const Type& min_val,
+ const Type& max_val, const std::string& name, Category category_,
+ u32 specialization_ = Specialization::Default, bool save_ = true,
+ bool runtime_modifiable_ = false,
+ typename std::enable_if<ranged, T*>::type other_setting_ = nullptr)
+ : Setting<Type, true>{linkage, default_val, min_val,
+ max_val, name, category_,
+ specialization_, save_, runtime_modifiable_,
+ other_setting_} {
+ linkage.restore_functions.emplace_back([this]() { this->SetGlobal(true); });
+ }
+
+ /**
+ * Tells this setting to represent either the global or custom setting when other member
+ * functions are used.
+ *
+ * @param to_global Whether to use the global or custom setting.
+ */
+ void SetGlobal(bool to_global) override final {
+ use_global = to_global;
+ }
+
+ /**
+ * Returns whether this setting is using the global setting or not.
+ *
+ * @returns The global state
+ */
+ [[nodiscard]] bool UsingGlobal() const override final {
+ return use_global;
+ }
+
+ /**
+ * Returns either the global or custom setting depending on the values of this setting's global
+ * state or if the global value was specifically requested.
+ *
+ * @param need_global Request global value regardless of setting's state; defaults to false
+ *
+ * @returns The required value of the setting
+ */
+ [[nodiscard]] const Type& GetValue() const override final {
+ if (use_global) {
+ return this->value;
+ }
+ return custom;
+ }
+ [[nodiscard]] const Type& GetValue(bool need_global) const {
+ if (use_global || need_global) {
+ return this->value;
+ }
+ return custom;
+ }
+
+ /**
+ * Sets the current setting value depending on the global state.
+ *
+ * @param val The new value
+ */
+ void SetValue(const Type& val) override final {
+ Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
+ if (use_global) {
+ std::swap(this->value, temp);
+ } else {
+ std::swap(custom, temp);
+ }
+ }
+
+ [[nodiscard]] constexpr bool Switchable() const override final {
+ return true;
+ }
+
+ [[nodiscard]] std::string ToStringGlobal() const override final {
+ return this->ToString(this->value);
+ }
+
+ /**
+ * Assigns the current setting value depending on the global state.
+ *
+ * @param val The new value
+ *
+ * @returns A reference to the current setting value
+ */
+ const Type& operator=(const Type& val) override final {
+ Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
+ if (use_global) {
+ std::swap(this->value, temp);
+ return this->value;
+ }
+ std::swap(custom, temp);
+ return custom;
+ }
+
+ /**
+ * Returns the current setting value depending on the global state.
+ *
+ * @returns A reference to the current setting value
+ */
+ explicit operator const Type&() const override final {
+ if (use_global) {
+ return this->value;
+ }
+ return custom;
+ }
+
+protected:
+ bool use_global{true}; ///< The setting's global state
+ Type custom{}; ///< The custom value of the setting
+};
+
+} // namespace Settings
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) {
return i & v.swap();
}
-template <typename S, typename T, typename F>
-S operator&(const swap_struct_t<T, F> v, const S& i) {
- return static_cast<S>(v.swap() & i);
-}
-
// Comparison
template <typename S, typename T, typename F>
bool operator<(const S& p, const swap_struct_t<T, F> v) {
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index dc0dcbd68..71e15ab4c 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -56,12 +56,12 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
#ifdef ARCHITECTURE_x86_64
const auto& caps = GetCPUCaps();
- if (caps.invariant_tsc && caps.tsc_frequency >= WallClock::GPUTickFreq) {
+ if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
} else {
// Fallback to StandardWallClock if the hardware TSC
// - Is not invariant
- // - Is not more precise than GPUTickFreq
+ // - Is not more precise than 1 GHz (1ns resolution)
return std::make_unique<StandardWallClock>();
}
#else
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4b7395be8..d0f76e57e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -37,6 +37,49 @@ add_library(core STATIC
debugger/gdbstub.h
device_memory.cpp
device_memory.h
+ file_sys/fssystem/fs_i_storage.h
+ file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
+ file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
+ file_sys/fssystem/fssystem_aes_ctr_storage.cpp
+ file_sys/fssystem/fssystem_aes_ctr_storage.h
+ file_sys/fssystem/fssystem_aes_xts_storage.cpp
+ file_sys/fssystem/fssystem_aes_xts_storage.h
+ file_sys/fssystem/fssystem_alignment_matching_storage.h
+ file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp
+ file_sys/fssystem/fssystem_alignment_matching_storage_impl.h
+ file_sys/fssystem/fssystem_bucket_tree.cpp
+ file_sys/fssystem/fssystem_bucket_tree.h
+ file_sys/fssystem/fssystem_bucket_tree_utils.h
+ file_sys/fssystem/fssystem_compressed_storage.h
+ file_sys/fssystem/fssystem_compression_common.h
+ file_sys/fssystem/fssystem_compression_configuration.cpp
+ file_sys/fssystem/fssystem_compression_configuration.h
+ file_sys/fssystem/fssystem_crypto_configuration.cpp
+ file_sys/fssystem/fssystem_crypto_configuration.h
+ file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
+ file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
+ file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
+ file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
+ file_sys/fssystem/fssystem_indirect_storage.cpp
+ file_sys/fssystem/fssystem_indirect_storage.h
+ file_sys/fssystem/fssystem_integrity_romfs_storage.cpp
+ file_sys/fssystem/fssystem_integrity_romfs_storage.h
+ file_sys/fssystem/fssystem_integrity_verification_storage.cpp
+ file_sys/fssystem/fssystem_integrity_verification_storage.h
+ file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h
+ file_sys/fssystem/fssystem_nca_file_system_driver.cpp
+ file_sys/fssystem/fssystem_nca_file_system_driver.h
+ file_sys/fssystem/fssystem_nca_header.cpp
+ file_sys/fssystem/fssystem_nca_header.h
+ file_sys/fssystem/fssystem_nca_reader.cpp
+ file_sys/fssystem/fssystem_pooled_buffer.cpp
+ file_sys/fssystem/fssystem_pooled_buffer.h
+ file_sys/fssystem/fssystem_sparse_storage.cpp
+ file_sys/fssystem/fssystem_sparse_storage.h
+ file_sys/fssystem/fssystem_switch_storage.h
+ file_sys/fssystem/fssystem_utility.cpp
+ file_sys/fssystem/fssystem_utility.h
+ file_sys/fssystem/fs_types.h
file_sys/bis_factory.cpp
file_sys/bis_factory.h
file_sys/card_image.cpp
@@ -57,8 +100,6 @@ add_library(core STATIC
file_sys/mode.h
file_sys/nca_metadata.cpp
file_sys/nca_metadata.h
- file_sys/nca_patch.cpp
- file_sys/nca_patch.h
file_sys/partition_filesystem.cpp
file_sys/partition_filesystem.h
file_sys/patch_manager.cpp
@@ -543,13 +584,27 @@ add_library(core STATIC
hle/service/lm/lm.h
hle/service/mig/mig.cpp
hle/service/mig/mig.h
+ hle/service/mii/types/char_info.cpp
+ hle/service/mii/types/char_info.h
+ hle/service/mii/types/core_data.cpp
+ hle/service/mii/types/core_data.h
+ hle/service/mii/types/raw_data.cpp
+ hle/service/mii/types/raw_data.h
+ hle/service/mii/types/store_data.cpp
+ hle/service/mii/types/store_data.h
+ hle/service/mii/types/ver3_store_data.cpp
+ hle/service/mii/types/ver3_store_data.h
hle/service/mii/mii.cpp
hle/service/mii/mii.h
+ hle/service/mii/mii_database.cpp
+ hle/service/mii/mii_database.h
+ hle/service/mii/mii_database_manager.cpp
+ hle/service/mii/mii_database_manager.h
hle/service/mii/mii_manager.cpp
hle/service/mii/mii_manager.h
- hle/service/mii/raw_data.cpp
- hle/service/mii/raw_data.h
- hle/service/mii/types.h
+ hle/service/mii/mii_result.h
+ hle/service/mii/mii_types.h
+ hle/service/mii/mii_util.h
hle/service/mm/mm_u.cpp
hle/service/mm/mm_u.h
hle/service/mnpp/mnpp_app.cpp
@@ -576,8 +631,8 @@ add_library(core STATIC
hle/service/nfp/nfp_interface.h
hle/service/nfp/nfp_result.h
hle/service/nfp/nfp_types.h
- hle/service/ngct/ngct.cpp
- hle/service/ngct/ngct.h
+ hle/service/ngc/ngc.cpp
+ hle/service/ngc/ngc.h
hle/service/nifm/nifm.cpp
hle/service/nifm/nifm.h
hle/service/nim/nim.cpp
@@ -813,6 +868,8 @@ add_library(core STATIC
telemetry_session.h
tools/freezer.cpp
tools/freezer.h
+ tools/renderdoc.cpp
+ tools/renderdoc.h
)
if (MSVC)
@@ -828,6 +885,7 @@ else()
-Werror=conversion
-Wno-sign-conversion
+ -Wno-cast-function-type
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
)
@@ -836,7 +894,7 @@ endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
-target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus)
+target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API)
if (MINGW)
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
endif()
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index c97158a71..44a297cdc 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -287,7 +287,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
} else {
// Unsafe optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) {
config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
@@ -307,7 +307,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
// Curated optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Auto) {
config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
@@ -316,7 +316,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
// Paranoia mode for debugging optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) {
config.unsafe_optimizations = false;
config.optimizations = Dynarmic::no_optimizations;
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 791d466ca..2e3674b6d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -347,7 +347,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
}
} else {
// Unsafe optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) {
config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
@@ -367,7 +367,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
}
// Curated optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Auto) {
config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.fastmem_address_space_bits = 64;
@@ -375,7 +375,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
}
// Paranoia mode for debugging optimizations
- if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) {
+ if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) {
config.unsafe_optimizations = false;
config.optimizations = Dynarmic::no_optimizations;
}
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 48233d7c8..e8300cd05 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -12,6 +12,7 @@
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
@@ -50,6 +51,7 @@
#include "core/reporter.h"
#include "core/telemetry_session.h"
#include "core/tools/freezer.h"
+#include "core/tools/renderdoc.h"
#include "network/network.h"
#include "video_core/host1x/host1x.h"
#include "video_core/renderer_base.h"
@@ -140,16 +142,13 @@ struct System::Impl {
device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue();
- extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
+ extended_memory_layout =
+ Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
- const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
- const auto current_time =
- std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
- Settings::values.custom_rtc_differential =
- Settings::values.custom_rtc.value_or(current_time) - current_time;
+ RefreshTime();
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr) {
@@ -172,7 +171,8 @@ struct System::Impl {
void ReinitializeIfNecessary(System& system) {
const bool must_reinitialize =
is_multicore != Settings::values.use_multi_core.GetValue() ||
- extended_memory_layout != Settings::values.use_unsafe_extended_memory_layout.GetValue();
+ extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
+ Settings::MemoryLayout::Memory_4Gb);
if (!must_reinitialize) {
return;
@@ -181,11 +181,22 @@ struct System::Impl {
LOG_DEBUG(Kernel, "Re-initializing");
is_multicore = Settings::values.use_multi_core.GetValue();
- extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
+ extended_memory_layout =
+ Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
Initialize(system);
}
+ void RefreshTime() {
+ const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
+ const auto current_time =
+ std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
+ Settings::values.custom_rtc_differential =
+ (Settings::values.custom_rtc_enabled ? Settings::values.custom_rtc.GetValue()
+ : current_time) -
+ current_time;
+ }
+
void Run() {
std::unique_lock<std::mutex> lk(suspend_guard);
@@ -263,13 +274,18 @@ struct System::Impl {
time_manager.Initialize();
is_powered_on = true;
- exit_lock = false;
+ exit_locked = false;
+ exit_requested = false;
microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0);
microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1);
microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
+ if (Settings::values.enable_renderdoc_hotkey) {
+ renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
+ }
+
LOG_DEBUG(Core, "Initialized OK");
return SystemResultStatus::Success;
@@ -388,12 +404,14 @@ struct System::Impl {
}
is_powered_on = false;
- exit_lock = false;
+ exit_locked = false;
+ exit_requested = false;
if (gpu_core != nullptr) {
gpu_core->NotifyShutdown();
}
+ Network::CancelPendingSocketOperations();
kernel.SuspendApplication(true);
if (services) {
services->KillNVNFlinger();
@@ -415,6 +433,7 @@ struct System::Impl {
debugger.reset();
kernel.Shutdown();
memory.Reset();
+ Network::RestartSocketOperations();
if (auto room_member = room_network.GetRoomMember().lock()) {
Network::GameInfo game_info{};
@@ -497,7 +516,8 @@ struct System::Impl {
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};
- bool exit_lock = false;
+ bool exit_locked = false;
+ bool exit_requested = false;
bool nvdec_active{};
@@ -506,6 +526,8 @@ struct System::Impl {
std::unique_ptr<Tools::Freezer> memory_freezer;
std::array<u8, 0x20> build_id{};
+ std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
+
/// Frontend applets
Service::AM::Applets::AppletManager applet_manager;
@@ -549,6 +571,8 @@ struct System::Impl {
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
gpu_dirty_memory_write_manager{};
+
+ std::deque<std::vector<u8>> user_channel;
};
System::System() : impl{std::make_unique<Impl>(*this)} {}
@@ -933,12 +957,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const {
return impl->time_manager;
}
-void System::SetExitLock(bool locked) {
- impl->exit_lock = locked;
+void System::SetExitLocked(bool locked) {
+ impl->exit_locked = locked;
+}
+
+bool System::GetExitLocked() const {
+ return impl->exit_locked;
+}
+
+void System::SetExitRequested(bool requested) {
+ impl->exit_requested = requested;
}
-bool System::GetExitLock() const {
- return impl->exit_lock;
+bool System::GetExitRequested() const {
+ return impl->exit_requested;
}
void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) {
@@ -999,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
return impl->room_network;
}
+Tools::RenderdocAPI& System::GetRenderdocAPI() {
+ return *impl->renderdoc_api;
+}
+
void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
return impl->kernel.RunServer(std::move(server_manager));
}
@@ -1015,6 +1051,10 @@ void System::ExecuteProgram(std::size_t program_index) {
}
}
+std::deque<std::vector<u8>>& System::GetUserChannel() {
+ return impl->user_channel;
+}
+
void System::RegisterExitCallback(ExitCallback&& callback) {
impl->exit_callback = std::move(callback);
}
@@ -1028,6 +1068,8 @@ void System::Exit() {
}
void System::ApplySettings() {
+ impl->RefreshTime();
+
if (IsPoweredOn()) {
Renderer().RefreshBaseSettings();
}
diff --git a/src/core/core.h b/src/core/core.h
index c70ea1965..df20f26f3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -4,6 +4,7 @@
#pragma once
#include <cstddef>
+#include <deque>
#include <functional>
#include <memory>
#include <mutex>
@@ -101,6 +102,10 @@ namespace Network {
class RoomNetwork;
}
+namespace Tools {
+class RenderdocAPI;
+}
+
namespace Core {
class ARM_Interface;
@@ -412,8 +417,13 @@ public:
/// Gets an immutable reference to the Room Network.
[[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
- void SetExitLock(bool locked);
- [[nodiscard]] bool GetExitLock() const;
+ [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
+
+ void SetExitLocked(bool locked);
+ bool GetExitLocked() const;
+
+ void SetExitRequested(bool requested);
+ bool GetExitRequested() const;
void SetApplicationProcessBuildID(const CurrentBuildProcessID& id);
[[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const;
@@ -456,6 +466,12 @@ public:
*/
void ExecuteProgram(std::size_t program_index);
+ /**
+ * Gets a reference to the user channel stack.
+ * It is used to transfer data between programs.
+ */
+ [[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel();
+
/// Type used for the frontend to designate a callback for System to exit the application.
using ExitCallback = std::function<void()>;
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 4ff2c50e5..43a3c5ffd 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -35,7 +35,6 @@ namespace Core::Crypto {
namespace {
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
-constexpr u64 FULL_TICKET_SIZE = 0x400;
using Common::AsArray;
@@ -156,6 +155,10 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
UNREACHABLE();
}
+bool Ticket::IsValid() const {
+ return !std::holds_alternative<std::monostate>(data);
+}
+
SignatureType Ticket::GetSignatureType() const {
if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
return ticket->sig_type;
@@ -210,6 +213,54 @@ Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& righ
return Ticket{out};
}
+Ticket Ticket::Read(const FileSys::VirtualFile& file) {
+ // Attempt to read up to the largest ticket size, and make sure we read at least a signature
+ // type.
+ std::array<u8, sizeof(RSA4096Ticket)> raw_data{};
+ auto read_size = file->Read(raw_data.data(), raw_data.size(), 0);
+ if (read_size < sizeof(SignatureType)) {
+ LOG_WARNING(Crypto, "Attempted to read ticket file with invalid size {}.", read_size);
+ return Ticket{std::monostate()};
+ }
+ return Read(std::span{raw_data});
+}
+
+Ticket Ticket::Read(std::span<const u8> raw_data) {
+ // Some tools read only 0x180 bytes of ticket data instead of 0x2C0, so
+ // just make sure we have at least the bare minimum of data to work with.
+ SignatureType sig_type;
+ if (raw_data.size() < sizeof(SignatureType)) {
+ LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.",
+ raw_data.size());
+ return Ticket{std::monostate()};
+ }
+ std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type));
+
+ switch (sig_type) {
+ case SignatureType::RSA_4096_SHA1:
+ case SignatureType::RSA_4096_SHA256: {
+ RSA4096Ticket ticket{};
+ std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
+ return Ticket{ticket};
+ }
+ case SignatureType::RSA_2048_SHA1:
+ case SignatureType::RSA_2048_SHA256: {
+ RSA2048Ticket ticket{};
+ std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
+ return Ticket{ticket};
+ }
+ case SignatureType::ECDSA_SHA1:
+ case SignatureType::ECDSA_SHA256: {
+ ECDSATicket ticket{};
+ std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
+ return Ticket{ticket};
+ }
+ default:
+ LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid type {}.", sig_type);
+ return Ticket{std::monostate()};
+ }
+}
+
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
Key128 out{};
@@ -290,9 +341,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
}
}
-RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
+void KeyManager::DeriveETicketRSAKey() {
if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) {
- return {};
+ return;
}
const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
@@ -304,12 +355,12 @@ RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
extended_dec.data(), Op::Decrypt);
- RSAKeyPair<2048> rsa_key{};
- std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
- std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
- std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
-
- return rsa_key;
+ std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(),
+ eticket_rsa_keypair.decryption_key.size());
+ std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100,
+ eticket_rsa_keypair.modulus.size());
+ std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200,
+ eticket_rsa_keypair.exponent.size());
}
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
@@ -447,10 +498,12 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
buffer[offset + 3] == 0x0) {
- out.emplace_back();
- auto& next = out.back();
- std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
- offset += FULL_TICKET_SIZE;
+ // NOTE: Assumes ticket blob will only contain RSA-2048 tickets.
+ auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)});
+ offset += sizeof(RSA2048Ticket);
+ if (ticket.IsValid()) {
+ out.push_back(ticket);
+ }
}
}
@@ -503,25 +556,36 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
return offset;
}
-std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
- const RSAKeyPair<2048>& key) {
+std::optional<Key128> KeyManager::ParseTicketTitleKey(const Ticket& ticket) {
+ if (!ticket.IsValid()) {
+ LOG_WARNING(Crypto, "Attempted to parse title key of invalid ticket.");
+ return std::nullopt;
+ }
+
+ if (ticket.GetData().rights_id == Key128{}) {
+ LOG_WARNING(Crypto, "Attempted to parse title key of ticket with no rights ID.");
+ return std::nullopt;
+ }
+
const auto issuer = ticket.GetData().issuer;
if (IsAllZeroArray(issuer)) {
+ LOG_WARNING(Crypto, "Attempted to parse title key of ticket with invalid issuer.");
return std::nullopt;
}
+
if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
- LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
+ LOG_WARNING(Crypto, "Parsing ticket with non-standard certificate authority.");
}
- Key128 rights_id = ticket.GetData().rights_id;
-
- if (rights_id == Key128{}) {
- return std::nullopt;
+ if (ticket.GetData().type == TitleKeyType::Common) {
+ return ticket.GetData().title_key_common;
}
- if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
- ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
- return std::make_pair(rights_id, ticket.GetData().title_key_common);
+ if (eticket_rsa_keypair == RSAKeyPair<2048>{}) {
+ LOG_WARNING(
+ Crypto,
+ "Skipping personalized ticket title key parsing due to missing ETicket RSA key-pair.");
+ return std::nullopt;
}
mbedtls_mpi D; // RSA Private Exponent
@@ -534,9 +598,12 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
mbedtls_mpi_init(&S);
mbedtls_mpi_init(&M);
- mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
- mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
- mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100);
+ const auto& title_key_block = ticket.GetData().title_key_block;
+ mbedtls_mpi_read_binary(&D, eticket_rsa_keypair.decryption_key.data(),
+ eticket_rsa_keypair.decryption_key.size());
+ mbedtls_mpi_read_binary(&N, eticket_rsa_keypair.modulus.data(),
+ eticket_rsa_keypair.modulus.size());
+ mbedtls_mpi_read_binary(&S, title_key_block.data(), title_key_block.size());
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
@@ -564,8 +631,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
Key128 key_temp{};
std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
-
- return std::make_pair(rights_id, key_temp);
+ return key_temp;
}
KeyManager::KeyManager() {
@@ -658,17 +724,25 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
continue;
}
- const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
+ const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
if (!ValidCryptoRevisionString(out[0], 18, 2)) {
continue;
}
- const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
+ const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
} else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
+ } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) {
+ const auto key_data = Common::HexStringToArray<528>(out[1]);
+ std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(),
+ eticket_rsa_keypair.decryption_key.size());
+ std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100,
+ eticket_rsa_keypair.modulus.size());
+ std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200,
+ eticket_rsa_keypair.exponent.size());
} else {
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) {
@@ -676,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
}
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
const auto index =
- std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
+ std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
const auto sub = kv.first.second;
if (sub == 0) {
s128_keys[{kv.first.first, index, 0}] =
@@ -696,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
const auto& match = kak_names[j];
if (out[0].compare(0, std::strlen(match), match) == 0) {
const auto index =
- std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
+ std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
s128_keys[{S128KeyType::KeyArea, index, j}] =
Common::HexStringToArray<16>(out[1]);
}
@@ -1110,56 +1184,38 @@ void KeyManager::DeriveETicket(PartitionDataManager& data,
eticket_extended_kek = data.GetETicketExtendedKek();
WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
+ DeriveETicketRSAKey();
PopulateTickets();
}
void KeyManager::PopulateTickets() {
- const auto rsa_key = GetETicketRSAKey();
-
- if (rsa_key == RSAKeyPair<2048>{}) {
+ if (ticket_databases_loaded) {
return;
}
+ ticket_databases_loaded = true;
- if (!common_tickets.empty() && !personal_tickets.empty()) {
- return;
- }
+ std::vector<Ticket> tickets;
const auto system_save_e1_path =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1";
-
- const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read,
- Common::FS::FileType::BinaryFile};
+ if (Common::FS::Exists(system_save_e1_path)) {
+ const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+ const auto blob1 = GetTicketblob(save_e1);
+ tickets.insert(tickets.end(), blob1.begin(), blob1.end());
+ }
const auto system_save_e2_path =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2";
+ if (Common::FS::Exists(system_save_e2_path)) {
+ const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+ const auto blob2 = GetTicketblob(save_e2);
+ tickets.insert(tickets.end(), blob2.begin(), blob2.end());
+ }
- const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read,
- Common::FS::FileType::BinaryFile};
-
- const auto blob2 = GetTicketblob(save_e2);
- auto res = GetTicketblob(save_e1);
-
- const auto idx = res.size();
- res.insert(res.end(), blob2.begin(), blob2.end());
-
- for (std::size_t i = 0; i < res.size(); ++i) {
- const auto common = i < idx;
- const auto pair = ParseTicket(res[i], rsa_key);
- if (!pair) {
- continue;
- }
-
- const auto& [rid, key] = *pair;
- u128 rights_id;
- std::memcpy(rights_id.data(), rid.data(), rid.size());
-
- if (common) {
- common_tickets[rights_id] = res[i];
- } else {
- personal_tickets[rights_id] = res[i];
- }
-
- SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ for (const auto& ticket : tickets) {
+ AddTicket(ticket);
}
}
@@ -1291,41 +1347,33 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
return personal_tickets;
}
-bool KeyManager::AddTicketCommon(Ticket raw) {
- const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{}) {
- return false;
- }
-
- const auto pair = ParseTicket(raw, rsa_key);
- if (!pair) {
+bool KeyManager::AddTicket(const Ticket& ticket) {
+ if (!ticket.IsValid()) {
+ LOG_WARNING(Crypto, "Attempted to add invalid ticket.");
return false;
}
- const auto& [rid, key] = *pair;
+ const auto& rid = ticket.GetData().rights_id;
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
- common_tickets[rights_id] = raw;
- SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
- return true;
-}
+ if (ticket.GetData().type == Core::Crypto::TitleKeyType::Common) {
+ common_tickets[rights_id] = ticket;
+ } else {
+ personal_tickets[rights_id] = ticket;
+ }
-bool KeyManager::AddTicketPersonalized(Ticket raw) {
- const auto rsa_key = GetETicketRSAKey();
- if (rsa_key == RSAKeyPair<2048>{}) {
- return false;
+ if (HasKey(S128KeyType::Titlekey, rights_id[1], rights_id[0])) {
+ LOG_DEBUG(Crypto,
+ "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.",
+ rights_id[1], rights_id[0]);
+ return true;
}
- const auto pair = ParseTicket(raw, rsa_key);
- if (!pair) {
+ const auto key = ParseTicketTitleKey(ticket);
+ if (!key) {
return false;
}
-
- const auto& [rid, key] = *pair;
- u128 rights_id;
- std::memcpy(rights_id.data(), rid.data(), rid.size());
- common_tickets[rights_id] = raw;
- SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]);
return true;
}
} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 8c864503b..2250eccec 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -7,6 +7,7 @@
#include <filesystem>
#include <map>
#include <optional>
+#include <span>
#include <string>
#include <variant>
@@ -29,8 +30,6 @@ enum class ResultStatus : u16;
namespace Core::Crypto {
-constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
-
using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>;
@@ -82,6 +81,7 @@ struct RSA4096Ticket {
INSERT_PADDING_BYTES(0x3C);
TicketData data;
};
+static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size.");
struct RSA2048Ticket {
SignatureType sig_type;
@@ -89,6 +89,7 @@ struct RSA2048Ticket {
INSERT_PADDING_BYTES(0x3C);
TicketData data;
};
+static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size.");
struct ECDSATicket {
SignatureType sig_type;
@@ -96,16 +97,41 @@ struct ECDSATicket {
INSERT_PADDING_BYTES(0x40);
TicketData data;
};
+static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size.");
struct Ticket {
- std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
-
- SignatureType GetSignatureType() const;
- TicketData& GetData();
- const TicketData& GetData() const;
- u64 GetSize() const;
-
+ std::variant<std::monostate, RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
+
+ [[nodiscard]] bool IsValid() const;
+ [[nodiscard]] SignatureType GetSignatureType() const;
+ [[nodiscard]] TicketData& GetData();
+ [[nodiscard]] const TicketData& GetData() const;
+ [[nodiscard]] u64 GetSize() const;
+
+ /**
+ * Synthesizes a common ticket given a title key and rights ID.
+ *
+ * @param title_key Title key to store in the ticket.
+ * @param rights_id Rights ID the ticket is for.
+ * @return The synthesized common ticket.
+ */
static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
+
+ /**
+ * Reads a ticket from a file.
+ *
+ * @param file File to read the ticket from.
+ * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false.
+ */
+ static Ticket Read(const FileSys::VirtualFile& file);
+
+ /**
+ * Reads a ticket from a memory buffer.
+ *
+ * @param raw_data Buffer to read the ticket from.
+ * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false.
+ */
+ static Ticket Read(std::span<const u8> raw_data);
};
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
@@ -264,8 +290,7 @@ public:
const std::map<u128, Ticket>& GetCommonTickets() const;
const std::map<u128, Ticket>& GetPersonalizedTickets() const;
- bool AddTicketCommon(Ticket raw);
- bool AddTicketPersonalized(Ticket raw);
+ bool AddTicket(const Ticket& ticket);
void ReloadKeys();
bool AreKeysLoaded() const;
@@ -279,10 +304,12 @@ private:
// Map from rights ID to ticket
std::map<u128, Ticket> common_tickets;
std::map<u128, Ticket> personal_tickets;
+ bool ticket_databases_loaded = false;
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
std::array<u8, 576> eticket_extended_kek{};
+ RSAKeyPair<2048> eticket_rsa_keypair{};
bool dev_mode;
void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys);
@@ -293,10 +320,13 @@ private:
void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
- RSAKeyPair<2048> GetETicketRSAKey() const;
+ void DeriveETicketRSAKey();
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
+
+ /// Parses the title key section of a ticket.
+ std::optional<Key128> ParseTicketTitleKey(const Ticket& ticket);
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
@@ -311,9 +341,4 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save);
-// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
-// (offset 0x140-0x144 is zero)
-std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
- const RSAKeyPair<2048>& eticket_extended_key);
-
} // namespace Core::Crypto
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 0f839d5b4..e55831f27 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -263,6 +263,23 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
std::vector<u8> mem(size);
if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) {
+ // Restore any bytes belonging to replaced instructions.
+ auto it = replaced_instructions.lower_bound(addr);
+ for (; it != replaced_instructions.end() && it->first < addr + size; it++) {
+ // Get the bytes of the instruction we previously replaced.
+ const u32 original_bytes = it->second;
+
+ // Calculate where to start writing to the output buffer.
+ const size_t output_offset = it->first - addr;
+
+ // Calculate how many bytes to write.
+ // The loop condition ensures output_offset < size.
+ const size_t n = std::min<size_t>(size - output_offset, sizeof(u32));
+
+ // Write the bytes to the output buffer.
+ std::memcpy(mem.data() + output_offset, &original_bytes, n);
+ }
+
SendReply(Common::HexToString(mem));
} else {
SendReply(GDB_STUB_REPLY_ERR);
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 5d02865f4..8b9a4fc5a 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -31,13 +31,9 @@ XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
partitions(partition_names.size()),
partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
- if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
- status = Loader::ResultStatus::ErrorBadXCIHeader;
- return;
- }
-
- if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
- status = Loader::ResultStatus::ErrorBadXCIHeader;
+ const auto header_status = TryReadHeader();
+ if (header_status != Loader::ResultStatus::Success) {
+ status = header_status;
return;
}
@@ -183,9 +179,9 @@ u32 XCI::GetSystemUpdateVersion() {
}
for (const auto& update_file : update->GetFiles()) {
- NCA nca{update_file, nullptr, 0};
+ NCA nca{update_file};
- if (nca.GetStatus() != Loader::ResultStatus::Success) {
+ if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) {
continue;
}
@@ -296,7 +292,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
continue;
}
- auto nca = std::make_shared<NCA>(partition_file, nullptr, 0);
+ auto nca = std::make_shared<NCA>(partition_file);
if (nca->IsUpdate()) {
continue;
}
@@ -316,6 +312,44 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
return Loader::ResultStatus::Success;
}
+Loader::ResultStatus XCI::TryReadHeader() {
+ constexpr size_t CardInitialDataRegionSize = 0x1000;
+
+ // Define the function we'll use to determine if we read a valid header.
+ const auto ReadCardHeader = [&]() {
+ // Ensure we can read the entire header. If we can't, we can't read the card image.
+ if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
+ return Loader::ResultStatus::ErrorBadXCIHeader;
+ }
+
+ // Ensure the header magic matches. If it doesn't, this isn't a card image header.
+ if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
+ return Loader::ResultStatus::ErrorBadXCIHeader;
+ }
+
+ // We read a card image header.
+ return Loader::ResultStatus::Success;
+ };
+
+ // Try to read the header directly.
+ if (ReadCardHeader() == Loader::ResultStatus::Success) {
+ return Loader::ResultStatus::Success;
+ }
+
+ // Get the size of the file.
+ const size_t card_image_size = file->GetSize();
+
+ // If we are large enough to have a key area, offset past the key area and retry.
+ if (card_image_size >= CardInitialDataRegionSize) {
+ file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize,
+ CardInitialDataRegionSize);
+ return ReadCardHeader();
+ }
+
+ // We had no header and aren't large enough to have a key area, so this can't be parsed.
+ return Loader::ResultStatus::ErrorBadXCIHeader;
+}
+
u8 XCI::GetFormatVersion() {
return GetLogoPartition() == nullptr ? 0x1 : 0x2;
}
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 1283f8216..9886123e7 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -128,6 +128,7 @@ public:
private:
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
+ Loader::ResultStatus TryReadHeader();
VirtualFile file;
GamecardHeader header{};
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 06efab46d..7d2f0abb8 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -12,545 +12,118 @@
#include "core/crypto/ctr_encryption_layer.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/content_archive.h"
-#include "core/file_sys/nca_patch.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
+#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
+#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+
namespace FileSys {
-// Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
-constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
-
-constexpr u64 SECTION_HEADER_SIZE = 0x200;
-constexpr u64 SECTION_HEADER_OFFSET = 0x400;
-
-constexpr u32 IVFC_MAX_LEVEL = 6;
-
-enum class NCASectionFilesystemType : u8 {
- PFS0 = 0x2,
- ROMFS = 0x3,
-};
-
-struct IVFCLevel {
- u64_le offset;
- u64_le size;
- u32_le block_size;
- u32_le reserved;
-};
-static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
-
-struct IVFCHeader {
- u32_le magic;
- u32_le magic_number;
- INSERT_PADDING_BYTES_NOINIT(8);
- std::array<IVFCLevel, 6> levels;
- INSERT_PADDING_BYTES_NOINIT(64);
-};
-static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
-
-struct NCASectionHeaderBlock {
- INSERT_PADDING_BYTES_NOINIT(3);
- NCASectionFilesystemType filesystem_type;
- NCASectionCryptoType crypto_type;
- INSERT_PADDING_BYTES_NOINIT(3);
-};
-static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
-
-struct NCABucketInfo {
- u64 table_offset;
- u64 table_size;
- std::array<u8, 0x10> table_header;
-};
-static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size.");
-
-struct NCASparseInfo {
- NCABucketInfo bucket;
- u64 physical_offset;
- u16 generation;
- INSERT_PADDING_BYTES_NOINIT(0x6);
-};
-static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size.");
-
-struct NCACompressionInfo {
- NCABucketInfo bucket;
- INSERT_PADDING_BYTES_NOINIT(0x8);
-};
-static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size.");
-
-struct NCASectionRaw {
- NCASectionHeaderBlock header;
- std::array<u8, 0x138> block_data;
- std::array<u8, 0x8> section_ctr;
- NCASparseInfo sparse_info;
- NCACompressionInfo compression_info;
- INSERT_PADDING_BYTES_NOINIT(0x60);
-};
-static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
-
-struct PFS0Superblock {
- NCASectionHeaderBlock header_block;
- std::array<u8, 0x20> hash;
- u32_le size;
- INSERT_PADDING_BYTES_NOINIT(4);
- u64_le hash_table_offset;
- u64_le hash_table_size;
- u64_le pfs0_header_offset;
- u64_le pfs0_size;
- INSERT_PADDING_BYTES_NOINIT(0x1B0);
-};
-static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
-
-struct RomFSSuperblock {
- NCASectionHeaderBlock header_block;
- IVFCHeader ivfc;
- INSERT_PADDING_BYTES_NOINIT(0x118);
-};
-static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
-
-struct BKTRHeader {
- u64_le offset;
- u64_le size;
- u32_le magic;
- INSERT_PADDING_BYTES_NOINIT(0x4);
- u32_le number_entries;
- INSERT_PADDING_BYTES_NOINIT(0x4);
-};
-static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
-
-struct BKTRSuperblock {
- NCASectionHeaderBlock header_block;
- IVFCHeader ivfc;
- INSERT_PADDING_BYTES_NOINIT(0x18);
- BKTRHeader relocation;
- BKTRHeader subsection;
- INSERT_PADDING_BYTES_NOINIT(0xC0);
-};
-static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
-
-union NCASectionHeader {
- NCASectionRaw raw{};
- PFS0Superblock pfs0;
- RomFSSuperblock romfs;
- BKTRSuperblock bktr;
-};
-static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
-
-static bool IsValidNCA(const NCAHeader& header) {
- // TODO(DarkLordZach): Add NCA2/NCA0 support.
- return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
+static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
+ return std::max<u8>(key_generation, 1) - 1;
}
-NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
- : file(std::move(file_)),
- bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} {
+NCA::NCA(VirtualFile file_, const NCA* base_nca)
+ : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
return;
}
- if (sizeof(NCAHeader) != file->ReadObject(&header)) {
- LOG_ERROR(Loader, "File reader errored out during header read.");
+ reader = std::make_shared<NcaReader>();
+ if (Result rc =
+ reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration());
+ R_FAILED(rc)) {
+ if (rc != ResultInvalidNcaSignature) {
+ LOG_ERROR(Loader, "File reader errored out during header read: {:#x}",
+ rc.GetInnerValue());
+ }
status = Loader::ResultStatus::ErrorBadNCAHeader;
return;
}
- if (!HandlePotentialHeaderDecryption()) {
- return;
- }
-
- has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; });
-
- const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
- is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) {
- return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
- });
-
- if (!ReadSections(sections, bktr_base_ivfc_offset)) {
+ // Ensure we have the proper key area keys to continue.
+ const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration());
+ if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) {
+ status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return;
}
- status = Loader::ResultStatus::Success;
-}
-
-NCA::~NCA() = default;
-
-bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
- if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
- status = Loader::ResultStatus::ErrorNCA2;
- return false;
- }
+ RightsId rights_id{};
+ reader->GetRightsId(rights_id.data(), rights_id.size());
+ if (rights_id != RightsId{}) {
+ // External decryption key required; provide it here.
+ u128 rights_id_u128;
+ std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id));
- if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
- status = Loader::ResultStatus::ErrorNCA0;
- return false;
- }
-
- return true;
-}
-
-bool NCA::HandlePotentialHeaderDecryption() {
- if (IsValidNCA(header)) {
- return true;
- }
-
- if (!CheckSupportedNCA(header)) {
- return false;
- }
-
- NCAHeader dec_header{};
- Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
- keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
- cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
- Core::Crypto::Op::Decrypt);
- if (IsValidNCA(dec_header)) {
- header = dec_header;
- encrypted = true;
- } else {
- if (!CheckSupportedNCA(dec_header)) {
- return false;
+ auto titlekey =
+ keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]);
+ if (titlekey == Core::Crypto::Key128{}) {
+ status = Loader::ResultStatus::ErrorMissingTitlekey;
+ return;
}
- if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
- status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
- } else {
- status = Loader::ResultStatus::ErrorMissingHeaderKey;
+ if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
+ status = Loader::ResultStatus::ErrorMissingTitlekek;
+ return;
}
- return false;
- }
- return true;
-}
+ auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id);
+ Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB);
+ cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(),
+ Core::Crypto::Op::Decrypt);
-std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
- const std::ptrdiff_t number_sections =
- std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) {
- return entry.media_offset > 0;
- });
-
- std::vector<NCASectionHeader> sections(number_sections);
- const auto length_sections = SECTION_HEADER_SIZE * number_sections;
-
- if (encrypted) {
- auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
- Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
- keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
- cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
- Core::Crypto::Op::Decrypt);
- } else {
- file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
- }
-
- return sections;
-}
-
-bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
- for (std::size_t i = 0; i < sections.size(); ++i) {
- const auto& section = sections[i];
-
- if (section.raw.sparse_info.bucket.table_offset != 0 &&
- section.raw.sparse_info.bucket.table_size != 0) {
- LOG_ERROR(Loader, "Sparse NCAs are not supported.");
- status = Loader::ResultStatus::ErrorSparseNCA;
- return false;
- }
-
- if (section.raw.compression_info.bucket.table_offset != 0 &&
- section.raw.compression_info.bucket.table_size != 0) {
- LOG_ERROR(Loader, "Compressed NCAs are not supported.");
- status = Loader::ResultStatus::ErrorCompressedNCA;
- return false;
- }
-
- if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
- if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
- return false;
- }
- } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
- if (!ReadPFS0Section(section, header.section_tables[i])) {
- return false;
- }
- }
- }
-
- return true;
-}
-
-bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
- u64 bktr_base_ivfc_offset) {
- const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
- ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
- const std::size_t romfs_offset = base_offset + ivfc_offset;
- const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
- auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
- auto dec = Decrypt(section, raw, romfs_offset);
-
- if (dec == nullptr) {
- if (status != Loader::ResultStatus::Success)
- return false;
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return false;
+ reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size());
}
- if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
- if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
- section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
- status = Loader::ResultStatus::ErrorBadBKTRHeader;
- return false;
- }
-
- if (section.bktr.relocation.offset + section.bktr.relocation.size !=
- section.bktr.subsection.offset) {
- status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
- return false;
- }
-
- const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
- if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
- status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
- return false;
- }
-
- const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
- RelocationBlock relocation_block{};
- if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
- sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadRelocationBlock;
- return false;
- }
- SubsectionBlock subsection_block{};
- if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
- sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadSubsectionBlock;
- return false;
- }
-
- std::vector<RelocationBucketRaw> relocation_buckets_raw(
- (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
- if (dec->ReadBytes(relocation_buckets_raw.data(),
- section.bktr.relocation.size - sizeof(RelocationBlock),
- section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
- section.bktr.relocation.size - sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadRelocationBuckets;
- return false;
+ const s32 fs_count = reader->GetFsCount();
+ NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader);
+ std::vector<VirtualFile> filesystems(fs_count);
+ for (s32 i = 0; i < fs_count; i++) {
+ NcaFsHeaderReader header_reader;
+ const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i);
+ if (R_FAILED(rc)) {
+ LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i,
+ rc.GetInnerValue());
+ status = Loader::ResultStatus::ErrorBadNCAHeader;
+ return;
}
- std::vector<SubsectionBucketRaw> subsection_buckets_raw(
- (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
- if (dec->ReadBytes(subsection_buckets_raw.data(),
- section.bktr.subsection.size - sizeof(SubsectionBlock),
- section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
- section.bktr.subsection.size - sizeof(SubsectionBlock)) {
- status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
- return false;
+ if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) {
+ files.push_back(filesystems[i]);
+ romfs = files.back();
}
- std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
- std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(),
- &ConvertRelocationBucketRaw);
- std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
- std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(),
- &ConvertSubsectionBucketRaw);
-
- u32 ctr_low;
- std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
- subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
- subsection_buckets.back().entries.push_back({size, {0}, 0});
-
- std::optional<Core::Crypto::Key128> key;
- if (encrypted) {
- if (has_rights_id) {
- status = Loader::ResultStatus::Success;
- key = GetTitlekey();
- if (!key) {
- status = Loader::ResultStatus::ErrorMissingTitlekey;
- return false;
- }
- } else {
- key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
- if (!key) {
- status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
- return false;
+ if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) {
+ auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]);
+ if (npfs->GetStatus() == Loader::ResultStatus::Success) {
+ dirs.push_back(npfs);
+ if (IsDirectoryExeFS(npfs)) {
+ exefs = dirs.back();
+ } else if (IsDirectoryLogoPartition(npfs)) {
+ logo = dirs.back();
+ } else {
+ continue;
}
}
}
- if (bktr_base_romfs == nullptr) {
- status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
- return false;
+ if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) {
+ is_update = true;
}
-
- auto bktr = std::make_shared<BKTR>(
- bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
- relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
- encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
- section.raw.section_ctr);
-
- // BKTR applies to entire IVFC, so make an offset version to level 6
- files.push_back(std::make_shared<OffsetVfsFile>(
- bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
- } else {
- files.push_back(std::move(dec));
}
- romfs = files.back();
- return true;
-}
-
-bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
- const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
- section.pfs0.pfs0_header_offset;
- const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
-
- auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
- if (dec != nullptr) {
- auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
-
- if (npfs->GetStatus() == Loader::ResultStatus::Success) {
- dirs.push_back(std::move(npfs));
- if (IsDirectoryExeFS(dirs.back()))
- exefs = dirs.back();
- else if (IsDirectoryLogoPartition(dirs.back()))
- logo = dirs.back();
- } else {
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return false;
- }
+ if (is_update && base_nca == nullptr) {
+ status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
} else {
- if (status != Loader::ResultStatus::Success)
- return false;
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return false;
+ status = Loader::ResultStatus::Success;
}
-
- return true;
-}
-
-u8 NCA::GetCryptoRevision() const {
- u8 master_key_id = header.crypto_type;
- if (header.crypto_type_2 > master_key_id)
- master_key_id = header.crypto_type_2;
- if (master_key_id > 0)
- --master_key_id;
- return master_key_id;
-}
-
-std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
- const auto master_key_id = GetCryptoRevision();
-
- if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) {
- return std::nullopt;
- }
-
- std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
- Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
- keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
- Core::Crypto::Mode::ECB);
- cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
-
- Core::Crypto::Key128 out{};
- if (type == NCASectionCryptoType::XTS) {
- std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
- } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) {
- std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
- } else {
- LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
- type);
- }
-
- u128 out_128{};
- std::memcpy(out_128.data(), out.data(), sizeof(u128));
- LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
- master_key_id, header.key_index, out_128[1], out_128[0]);
-
- return out;
}
-std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
- const auto master_key_id = GetCryptoRevision();
-
- u128 rights_id{};
- memcpy(rights_id.data(), header.rights_id.data(), 16);
- if (rights_id == u128{}) {
- status = Loader::ResultStatus::ErrorInvalidRightsID;
- return std::nullopt;
- }
-
- auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
- if (titlekey == Core::Crypto::Key128{}) {
- status = Loader::ResultStatus::ErrorMissingTitlekey;
- return std::nullopt;
- }
-
- if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
- status = Loader::ResultStatus::ErrorMissingTitlekek;
- return std::nullopt;
- }
-
- Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
- keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB);
- cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt);
-
- return titlekey;
-}
-
-VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
- if (!encrypted)
- return in;
-
- switch (s_header.raw.header.crypto_type) {
- case NCASectionCryptoType::NONE:
- LOG_TRACE(Crypto, "called with mode=NONE");
- return in;
- case NCASectionCryptoType::CTR:
- // During normal BKTR decryption, this entire function is skipped. This is for the metadata,
- // which uses the same CTR as usual.
- case NCASectionCryptoType::BKTR:
- LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
- {
- std::optional<Core::Crypto::Key128> key;
- if (has_rights_id) {
- status = Loader::ResultStatus::Success;
- key = GetTitlekey();
- if (!key) {
- if (status == Loader::ResultStatus::Success)
- status = Loader::ResultStatus::ErrorMissingTitlekey;
- return nullptr;
- }
- } else {
- key = GetKeyAreaKey(NCASectionCryptoType::CTR);
- if (!key) {
- status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
- return nullptr;
- }
- }
-
- auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
- starting_offset);
- Core::Crypto::CTREncryptionLayer::IVData iv{};
- for (std::size_t i = 0; i < 8; ++i) {
- iv[i] = s_header.raw.section_ctr[8 - i - 1];
- }
- out->SetIV(iv);
- return std::static_pointer_cast<VfsFile>(out);
- }
- case NCASectionCryptoType::XTS:
- // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
- default:
- LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
- s_header.raw.header.crypto_type);
- return nullptr;
- }
-}
+NCA::~NCA() = default;
Loader::ResultStatus NCA::GetStatus() const {
return status;
@@ -579,21 +152,24 @@ VirtualDir NCA::GetParentDirectory() const {
}
NCAContentType NCA::GetType() const {
- return header.content_type;
+ return static_cast<NCAContentType>(reader->GetContentType());
}
u64 NCA::GetTitleId() const {
- if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS)
- return header.title_id | 0x800;
- return header.title_id;
+ if (is_update) {
+ return reader->GetProgramId() | 0x800;
+ }
+ return reader->GetProgramId();
}
-std::array<u8, 16> NCA::GetRightsId() const {
- return header.rights_id;
+RightsId NCA::GetRightsId() const {
+ RightsId result;
+ reader->GetRightsId(result.data(), result.size());
+ return result;
}
u32 NCA::GetSDKVersion() const {
- return header.sdk_version;
+ return reader->GetSdkAddonVersion();
}
bool NCA::IsUpdate() const {
@@ -612,10 +188,6 @@ VirtualFile NCA::GetBaseFile() const {
return file;
}
-u64 NCA::GetBaseIVFCOffset() const {
- return ivfc_offset;
-}
-
VirtualDir NCA::GetLogoPartition() const {
return logo;
}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 20f524f80..af521d453 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -21,7 +21,7 @@ enum class ResultStatus : u16;
namespace FileSys {
-union NCASectionHeader;
+class NcaReader;
/// Describes the type of content within an NCA archive.
enum class NCAContentType : u8 {
@@ -45,41 +45,7 @@ enum class NCAContentType : u8 {
PublicData = 5,
};
-enum class NCASectionCryptoType : u8 {
- NONE = 1,
- XTS = 2,
- CTR = 3,
- BKTR = 4,
-};
-
-struct NCASectionTableEntry {
- u32_le media_offset;
- u32_le media_end_offset;
- INSERT_PADDING_BYTES(0x8);
-};
-static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
-
-struct NCAHeader {
- std::array<u8, 0x100> rsa_signature_1;
- std::array<u8, 0x100> rsa_signature_2;
- u32_le magic;
- u8 is_system;
- NCAContentType content_type;
- u8 crypto_type;
- u8 key_index;
- u64_le size;
- u64_le title_id;
- INSERT_PADDING_BYTES(0x4);
- u32_le sdk_version;
- u8 crypto_type_2;
- INSERT_PADDING_BYTES(15);
- std::array<u8, 0x10> rights_id;
- std::array<NCASectionTableEntry, 0x4> section_tables;
- std::array<std::array<u8, 0x20>, 0x4> hash_tables;
- std::array<u8, 0x40> key_area;
- INSERT_PADDING_BYTES(0xC0);
-};
-static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
+using RightsId = std::array<u8, 0x10>;
inline bool IsDirectoryExeFS(const VirtualDir& pfs) {
// According to switchbrew, an exefs must only contain these two files:
@@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
public:
- explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
- u64 bktr_base_ivfc_offset = 0);
+ explicit NCA(VirtualFile file, const NCA* base_nca = nullptr);
~NCA() override;
Loader::ResultStatus GetStatus() const;
@@ -110,7 +75,7 @@ public:
NCAContentType GetType() const;
u64 GetTitleId() const;
- std::array<u8, 0x10> GetRightsId() const;
+ RightsId GetRightsId() const;
u32 GetSDKVersion() const;
bool IsUpdate() const;
@@ -119,26 +84,9 @@ public:
VirtualFile GetBaseFile() const;
- // Returns the base ivfc offset used in BKTR patching.
- u64 GetBaseIVFCOffset() const;
-
VirtualDir GetLogoPartition() const;
private:
- bool CheckSupportedNCA(const NCAHeader& header);
- bool HandlePotentialHeaderDecryption();
-
- std::vector<NCASectionHeader> ReadSectionHeaders() const;
- bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
- bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
- u64 bktr_base_ivfc_offset);
- bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
-
- u8 GetCryptoRevision() const;
- std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
- std::optional<Core::Crypto::Key128> GetTitlekey();
- VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
-
std::vector<VirtualDir> dirs;
std::vector<VirtualFile> files;
@@ -146,11 +94,6 @@ private:
VirtualDir exefs = nullptr;
VirtualDir logo = nullptr;
VirtualFile file;
- VirtualFile bktr_base_romfs;
- u64 ivfc_offset = 0;
-
- NCAHeader header{};
- bool has_rights_id{};
Loader::ResultStatus status{};
@@ -158,6 +101,7 @@ private:
bool is_update = false;
Core::Crypto::KeyManager& keys;
+ std::shared_ptr<NcaReader> reader;
};
} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index cd9ac2e75..0697c29ae 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -68,7 +68,8 @@ NACP::NACP(VirtualFile file) {
NACP::~NACP() = default;
const LanguageEntry& NACP::GetLanguageEntry() const {
- Language language = language_to_codes[Settings::values.language_index.GetValue()];
+ Language language =
+ language_to_codes[static_cast<s32>(Settings::values.language_index.GetValue())];
{
const auto& language_entry = raw.language_entries.at(static_cast<u8>(language));
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index 7cee0c7df..2f5045a67 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -17,4 +17,74 @@ constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
+constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50};
+constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001};
+constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002};
+constexpr Result ResultOutOfRange{ErrorModule::FS, 3005};
+constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294};
+constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341};
+constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363};
+constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399};
+constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412};
+constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422};
+constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423};
+constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012};
+constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021};
+constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022};
+constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023};
+constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024};
+constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032};
+constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033};
+constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034};
+constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035};
+constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036};
+constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037};
+constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038};
+constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039};
+constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084};
+constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085};
+constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086};
+constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087};
+constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088};
+constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089};
+constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090};
+constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091};
+constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091};
+constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509};
+constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510};
+constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511};
+constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517};
+constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520};
+constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521};
+constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522};
+constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523};
+constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524};
+constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525};
+constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526};
+constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528};
+constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529};
+constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530};
+constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532};
+constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533};
+constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534};
+constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535};
+constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541};
+constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542};
+constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543};
+constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547};
+constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548};
+constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549};
+constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
+constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
+constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
+constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
+constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
+constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
+constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
+constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
+constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
+constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
+constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
+constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705};
+
} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h
new file mode 100644
index 000000000..416dd57b8
--- /dev/null
+++ b/src/core/file_sys/fssystem/fs_i_storage.h
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/overflow.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+class IStorage : public VfsFile {
+public:
+ virtual std::string GetName() const override {
+ return {};
+ }
+
+ virtual VirtualDir GetContainingDirectory() const override {
+ return {};
+ }
+
+ virtual bool IsWritable() const override {
+ return true;
+ }
+
+ virtual bool IsReadable() const override {
+ return true;
+ }
+
+ virtual bool Resize(size_t size) override {
+ return false;
+ }
+
+ virtual bool Rename(std::string_view name) override {
+ return false;
+ }
+
+ static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) {
+ R_UNLESS(offset >= 0, ResultInvalidOffset);
+ R_UNLESS(size >= 0, ResultInvalidSize);
+ R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange);
+ R_UNLESS(offset + size <= total_size, ResultOutOfRange);
+ R_SUCCEED();
+ }
+};
+
+class IReadOnlyStorage : public IStorage {
+public:
+ virtual bool IsWritable() const override {
+ return false;
+ }
+
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
+ return 0;
+ }
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fs_types.h b/src/core/file_sys/fssystem/fs_types.h
new file mode 100644
index 000000000..43aeaf447
--- /dev/null
+++ b/src/core/file_sys/fssystem/fs_types.h
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+
+namespace FileSys {
+
+struct Int64 {
+ u32 low;
+ u32 high;
+
+ constexpr void Set(s64 v) {
+ this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0);
+ this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32);
+ }
+
+ constexpr s64 Get() const {
+ return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low));
+ }
+
+ constexpr Int64& operator=(s64 v) {
+ this->Set(v);
+ return *this;
+ }
+
+ constexpr operator s64() const {
+ return this->Get();
+ }
+};
+
+struct HashSalt {
+ static constexpr size_t Size = 32;
+
+ std::array<u8, Size> value;
+};
+static_assert(std::is_trivial_v<HashSalt>);
+static_assert(sizeof(HashSalt) == HashSalt::Size);
+
+constexpr inline size_t IntegrityMinLayerCount = 2;
+constexpr inline size_t IntegrityMaxLayerCount = 7;
+constexpr inline size_t IntegrityLayerCountSave = 5;
+constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4;
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
new file mode 100644
index 000000000..f25c95472
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
@@ -0,0 +1,251 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
+#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
+#include "core/file_sys/fssystem/fssystem_nca_header.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+namespace {
+
+class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
+public:
+ virtual void Decrypt(
+ u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
+ const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final;
+};
+
+} // namespace
+
+Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) {
+ std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>();
+ R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA);
+ *out = std::move(decryptor);
+ R_SUCCEED();
+}
+
+Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
+ VirtualFile data_storage,
+ VirtualFile table_storage) {
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ table_storage->ReadObject(std::addressof(header), 0);
+ R_TRY(header.Verify());
+
+ // Determine extents.
+ const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
+ const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
+ const auto node_storage_offset = QueryHeaderStorageSize();
+ const auto entry_storage_offset = node_storage_offset + node_storage_size;
+
+ // Create a software decryptor.
+ std::unique_ptr<IDecryptor> sw_decryptor;
+ R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
+
+ // Initialize.
+ R_RETURN(this->Initialize(
+ key, key_size, secure_value, 0, data_storage,
+ std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
+ std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
+ header.entry_count, std::move(sw_decryptor)));
+}
+
+Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value,
+ s64 counter_offset, VirtualFile data_storage,
+ VirtualFile node_storage, VirtualFile entry_storage,
+ s32 entry_count,
+ std::unique_ptr<IDecryptor>&& decryptor) {
+ // Validate preconditions.
+ ASSERT(key != nullptr);
+ ASSERT(key_size == KeySize);
+ ASSERT(counter_offset >= 0);
+ ASSERT(decryptor != nullptr);
+
+ // Initialize the bucket tree table.
+ if (entry_count > 0) {
+ R_TRY(
+ m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
+ } else {
+ m_table.Initialize(NodeSize, 0);
+ }
+
+ // Set members.
+ m_data_storage = data_storage;
+ std::memcpy(m_key.data(), key, key_size);
+ m_secure_value = secure_value;
+ m_counter_offset = counter_offset;
+ m_decryptor = std::move(decryptor);
+
+ R_SUCCEED();
+}
+
+void AesCtrCounterExtendedStorage::Finalize() {
+ if (this->IsInitialized()) {
+ m_table.Finalize();
+ m_data_storage = VirtualFile();
+ }
+}
+
+Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count,
+ s32 entry_count, s64 offset, s64 size) {
+ // Validate pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Clear the out count.
+ R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
+ *out_entry_count = 0;
+
+ // Succeed if there's no range.
+ R_SUCCEED_IF(size == 0);
+
+ // If we have an output array, we need it to be non-null.
+ R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
+
+ // Check that our range is valid.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->GetOffset();
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultInvalidAesCtrCounterExtendedEntryOffset);
+ }
+
+ // Prepare to loop over entries.
+ const auto end_offset = offset + static_cast<s64>(size);
+ s32 count = 0;
+
+ auto cur_entry = *visitor.Get<Entry>();
+ while (cur_entry.GetOffset() < end_offset) {
+ // Try to write the entry to the out list.
+ if (entry_count != 0) {
+ if (count >= entry_count) {
+ break;
+ }
+ std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
+ }
+
+ count++;
+
+ // Advance.
+ if (visitor.CanMoveNext()) {
+ R_TRY(visitor.MoveNext());
+ cur_entry = *visitor.Get<Entry>();
+ } else {
+ break;
+ }
+ }
+
+ // Write the output count.
+ *out_entry_count = count;
+ R_SUCCEED();
+}
+
+size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Validate preconditions.
+ ASSERT(this->IsInitialized());
+
+ // Allow zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+ ASSERT(Common::IsAligned(offset, BlockSize));
+ ASSERT(Common::IsAligned(size, BlockSize));
+
+ BucketTree::Offsets table_offsets;
+ ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets))));
+
+ ASSERT(table_offsets.IsInclude(offset, size));
+
+ // Read the data.
+ m_data_storage->Read(buffer, size, offset);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset)));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->GetOffset();
+ ASSERT(Common::IsAligned(entry_offset, BlockSize));
+ ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset));
+ }
+
+ // Prepare to read in chunks.
+ u8* cur_data = static_cast<u8*>(buffer);
+ auto cur_offset = offset;
+ const auto end_offset = offset + static_cast<s64>(size);
+
+ while (cur_offset < end_offset) {
+ // Get the current entry.
+ const auto cur_entry = *visitor.Get<Entry>();
+
+ // Get and validate the entry's offset.
+ const auto cur_entry_offset = cur_entry.GetOffset();
+ ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset);
+
+ // Get and validate the next entry offset.
+ s64 next_entry_offset;
+ if (visitor.CanMoveNext()) {
+ ASSERT(R_SUCCEEDED(visitor.MoveNext()));
+ next_entry_offset = visitor.Get<Entry>()->GetOffset();
+ ASSERT(table_offsets.IsInclude(next_entry_offset));
+ } else {
+ next_entry_offset = table_offsets.end_offset;
+ }
+ ASSERT(Common::IsAligned(next_entry_offset, BlockSize));
+ ASSERT(cur_offset < static_cast<size_t>(next_entry_offset));
+
+ // Get the offset of the entry in the data we read.
+ const auto data_offset = cur_offset - cur_entry_offset;
+ const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset;
+ ASSERT(data_size > 0);
+
+ // Determine how much is left.
+ const auto remaining_size = end_offset - cur_offset;
+ const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size));
+ ASSERT(cur_size <= size);
+
+ // If necessary, perform decryption.
+ if (cur_entry.encryption_value == Entry::Encryption::Encrypted) {
+ // Make the CTR for the data we're decrypting.
+ const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset;
+ NcaAesCtrUpperIv upper_iv = {
+ .part = {.generation = static_cast<u32>(cur_entry.generation),
+ .secure_value = m_secure_value}};
+
+ std::array<u8, IvSize> iv;
+ AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset);
+
+ // Decrypt.
+ m_decryptor->Decrypt(cur_data, cur_size, m_key, iv);
+ }
+
+ // Advance.
+ cur_data += cur_size;
+ cur_offset += cur_size;
+ }
+
+ return size;
+}
+
+void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size,
+ const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key,
+ const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) {
+ Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher(
+ key, Core::Crypto::Mode::CTR);
+ cipher.SetIV(iv);
+ cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
new file mode 100644
index 000000000..d0e9ceed0
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
@@ -0,0 +1,114 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+
+#include "common/literals.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+class AesCtrCounterExtendedStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage);
+ YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage);
+
+public:
+ static constexpr size_t BlockSize = 0x10;
+ static constexpr size_t KeySize = 0x10;
+ static constexpr size_t IvSize = 0x10;
+ static constexpr size_t NodeSize = 16_KiB;
+
+ class IDecryptor {
+ public:
+ virtual ~IDecryptor() {}
+ virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key,
+ const std::array<u8, IvSize>& iv) = 0;
+ };
+
+ struct Entry {
+ enum class Encryption : u8 {
+ Encrypted = 0,
+ NotEncrypted = 1,
+ };
+
+ std::array<u8, sizeof(s64)> offset;
+ Encryption encryption_value;
+ std::array<u8, 3> reserved;
+ s32 generation;
+
+ void SetOffset(s64 value) {
+ std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64));
+ }
+
+ s64 GetOffset() const {
+ s64 value;
+ std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64));
+ return value;
+ }
+ };
+ static_assert(sizeof(Entry) == 0x10);
+ static_assert(alignof(Entry) == 4);
+ static_assert(std::is_trivial_v<Entry>);
+
+public:
+ static constexpr s64 QueryHeaderStorageSize() {
+ return BucketTree::QueryHeaderStorageSize();
+ }
+
+ static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
+ return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+ static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
+ return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+ static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out);
+
+public:
+ AesCtrCounterExtendedStorage()
+ : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {}
+ virtual ~AesCtrCounterExtendedStorage() {
+ this->Finalize();
+ }
+
+ Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset,
+ VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
+ s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor);
+ void Finalize();
+
+ bool IsInitialized() const {
+ return m_table.IsInitialized();
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+
+ virtual size_t GetSize() const override {
+ BucketTree::Offsets offsets;
+ ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets))));
+
+ return offsets.end_offset;
+ }
+
+ Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
+ s64 size);
+
+private:
+ Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage,
+ VirtualFile table_storage);
+
+private:
+ mutable BucketTree m_table;
+ VirtualFile m_data_storage;
+ std::array<u8, KeySize> m_key;
+ u32 m_secure_value;
+ s64 m_counter_offset;
+ std::unique_ptr<IDecryptor> m_decryptor;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp
new file mode 100644
index 000000000..b65aca18d
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp
@@ -0,0 +1,129 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/swap.h"
+#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+#include "core/file_sys/fssystem/fssystem_utility.h"
+
+namespace FileSys {
+
+void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) {
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size == IvSize);
+ ASSERT(offset >= 0);
+
+ const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
+
+ *reinterpret_cast<u64_be*>(out_addr + 0) = upper;
+ *reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize);
+}
+
+AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
+ size_t iv_size)
+ : m_base_storage(std::move(base)) {
+ ASSERT(m_base_storage != nullptr);
+ ASSERT(key != nullptr);
+ ASSERT(iv != nullptr);
+ ASSERT(key_size == KeySize);
+ ASSERT(iv_size == IvSize);
+
+ std::memcpy(m_key.data(), key, KeySize);
+ std::memcpy(m_iv.data(), iv, IvSize);
+
+ m_cipher.emplace(m_key, Core::Crypto::Mode::CTR);
+}
+
+size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Allow zero-size reads.
+ if (size == 0) {
+ return size;
+ }
+
+ // Ensure buffer is valid.
+ ASSERT(buffer != nullptr);
+
+ // We can only read at block aligned offsets.
+ ASSERT(Common::IsAligned(offset, BlockSize));
+ ASSERT(Common::IsAligned(size, BlockSize));
+
+ // Read the data.
+ m_base_storage->Read(buffer, size, offset);
+
+ // Setup the counter.
+ std::array<u8, IvSize> ctr;
+ std::memcpy(ctr.data(), m_iv.data(), IvSize);
+ AddCounter(ctr.data(), IvSize, offset / BlockSize);
+
+ // Decrypt.
+ m_cipher->SetIV(ctr);
+ m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt);
+
+ return size;
+}
+
+size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) {
+ // Allow zero-size writes.
+ if (size == 0) {
+ return size;
+ }
+
+ // Ensure buffer is valid.
+ ASSERT(buffer != nullptr);
+
+ // We can only write at block aligned offsets.
+ ASSERT(Common::IsAligned(offset, BlockSize));
+ ASSERT(Common::IsAligned(size, BlockSize));
+
+ // Get a pooled buffer.
+ PooledBuffer pooled_buffer;
+ const bool use_work_buffer = true;
+ if (use_work_buffer) {
+ pooled_buffer.Allocate(size, BlockSize);
+ }
+
+ // Setup the counter.
+ std::array<u8, IvSize> ctr;
+ std::memcpy(ctr.data(), m_iv.data(), IvSize);
+ AddCounter(ctr.data(), IvSize, offset / BlockSize);
+
+ // Loop until all data is written.
+ size_t remaining = size;
+ s64 cur_offset = 0;
+ while (remaining > 0) {
+ // Determine data we're writing and where.
+ const size_t write_size =
+ use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
+
+ void* write_buf;
+ if (use_work_buffer) {
+ write_buf = pooled_buffer.GetBuffer();
+ } else {
+ write_buf = const_cast<u8*>(buffer);
+ }
+
+ // Encrypt the data.
+ m_cipher->SetIV(ctr);
+ m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf),
+ Core::Crypto::Op::Encrypt);
+
+ // Write the encrypted data.
+ m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset);
+
+ // Advance.
+ cur_offset += write_size;
+ remaining -= write_size;
+ if (remaining > 0) {
+ AddCounter(ctr.data(), IvSize, write_size / BlockSize);
+ }
+ }
+
+ return size;
+}
+
+size_t AesCtrStorage::GetSize() const {
+ return m_base_storage->GetSize();
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h
new file mode 100644
index 000000000..339e49697
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+
+#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+class AesCtrStorage : public IStorage {
+ YUZU_NON_COPYABLE(AesCtrStorage);
+ YUZU_NON_MOVEABLE(AesCtrStorage);
+
+public:
+ static constexpr size_t BlockSize = 0x10;
+ static constexpr size_t KeySize = 0x10;
+ static constexpr size_t IvSize = 0x10;
+
+public:
+ static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset);
+
+public:
+ AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv,
+ size_t iv_size);
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override;
+ virtual size_t GetSize() const override;
+
+private:
+ VirtualFile m_base_storage;
+ std::array<u8, KeySize> m_key;
+ std::array<u8, IvSize> m_iv;
+ mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp
new file mode 100644
index 000000000..022424229
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp
@@ -0,0 +1,112 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/swap.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+#include "core/file_sys/fssystem/fssystem_utility.h"
+
+namespace FileSys {
+
+void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) {
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size == IvSize);
+ ASSERT(offset >= 0);
+
+ const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
+
+ *reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size;
+}
+
+AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
+ const void* iv, size_t iv_size, size_t block_size)
+ : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() {
+ ASSERT(m_base_storage != nullptr);
+ ASSERT(key1 != nullptr);
+ ASSERT(key2 != nullptr);
+ ASSERT(iv != nullptr);
+ ASSERT(key_size == KeySize);
+ ASSERT(iv_size == IvSize);
+ ASSERT(Common::IsAligned(m_block_size, AesBlockSize));
+
+ std::memcpy(m_key.data() + 0, key1, KeySize);
+ std::memcpy(m_key.data() + 0x10, key2, KeySize);
+ std::memcpy(m_iv.data(), iv, IvSize);
+
+ m_cipher.emplace(m_key, Core::Crypto::Mode::XTS);
+}
+
+size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Allow zero-size reads.
+ if (size == 0) {
+ return size;
+ }
+
+ // Ensure buffer is valid.
+ ASSERT(buffer != nullptr);
+
+ // We can only read at block aligned offsets.
+ ASSERT(Common::IsAligned(offset, AesBlockSize));
+ ASSERT(Common::IsAligned(size, AesBlockSize));
+
+ // Read the data.
+ m_base_storage->Read(buffer, size, offset);
+
+ // Setup the counter.
+ std::array<u8, IvSize> ctr;
+ std::memcpy(ctr.data(), m_iv.data(), IvSize);
+ AddCounter(ctr.data(), IvSize, offset / m_block_size);
+
+ // Handle any unaligned data before the start.
+ size_t processed_size = 0;
+ if ((offset % m_block_size) != 0) {
+ // Determine the size of the pre-data read.
+ const size_t skip_size =
+ static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size));
+ const size_t data_size = std::min(size, m_block_size - skip_size);
+
+ // Decrypt into a pooled buffer.
+ {
+ PooledBuffer tmp_buf(m_block_size, m_block_size);
+ ASSERT(tmp_buf.GetSize() >= m_block_size);
+
+ std::memset(tmp_buf.GetBuffer(), 0, skip_size);
+ std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
+
+ m_cipher->SetIV(ctr);
+ m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(),
+ Core::Crypto::Op::Decrypt);
+
+ std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size);
+ }
+
+ AddCounter(ctr.data(), IvSize, 1);
+ processed_size += data_size;
+ ASSERT(processed_size == std::min(size, m_block_size - skip_size));
+ }
+
+ // Decrypt aligned chunks.
+ char* cur = reinterpret_cast<char*>(buffer) + processed_size;
+ size_t remaining = size - processed_size;
+ while (remaining > 0) {
+ const size_t cur_size = std::min(m_block_size, remaining);
+
+ m_cipher->SetIV(ctr);
+ m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt);
+
+ remaining -= cur_size;
+ cur += cur_size;
+
+ AddCounter(ctr.data(), IvSize, 1);
+ }
+
+ return size;
+}
+
+size_t AesXtsStorage::GetSize() const {
+ return m_base_storage->GetSize();
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h
new file mode 100644
index 000000000..f342efb57
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+
+#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+
+namespace FileSys {
+
+class AesXtsStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(AesXtsStorage);
+ YUZU_NON_MOVEABLE(AesXtsStorage);
+
+public:
+ static constexpr size_t AesBlockSize = 0x10;
+ static constexpr size_t KeySize = 0x20;
+ static constexpr size_t IvSize = 0x10;
+
+public:
+ static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size);
+
+public:
+ AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size,
+ const void* iv, size_t iv_size, size_t block_size);
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+ virtual size_t GetSize() const override;
+
+private:
+ VirtualFile m_base_storage;
+ std::array<u8, KeySize> m_key;
+ std::array<u8, IvSize> m_iv;
+ const size_t m_block_size;
+ std::mutex m_mutex;
+ mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h
new file mode 100644
index 000000000..f96691d03
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h
@@ -0,0 +1,146 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/alignment.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+
+namespace FileSys {
+
+template <size_t DataAlign_, size_t BufferAlign_>
+class AlignmentMatchingStorage : public IStorage {
+ YUZU_NON_COPYABLE(AlignmentMatchingStorage);
+ YUZU_NON_MOVEABLE(AlignmentMatchingStorage);
+
+public:
+ static constexpr size_t DataAlign = DataAlign_;
+ static constexpr size_t BufferAlign = BufferAlign_;
+
+ static constexpr size_t DataAlignMax = 0x200;
+ static_assert(DataAlign <= DataAlignMax);
+ static_assert(Common::IsPowerOfTwo(DataAlign));
+ static_assert(Common::IsPowerOfTwo(BufferAlign));
+
+private:
+ VirtualFile m_base_storage;
+ s64 m_base_storage_size;
+
+public:
+ explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {}
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Allocate a work buffer on stack.
+ alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
+
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ s64 bs_size = this->GetSize();
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
+
+ return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(),
+ DataAlign, BufferAlign, offset, buffer, size);
+ }
+
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
+ // Allocate a work buffer on stack.
+ alignas(DataAlignMax) std::array<char, DataAlign> work_buf;
+
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ s64 bs_size = this->GetSize();
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
+
+ return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(),
+ DataAlign, BufferAlign, offset, buffer, size);
+ }
+
+ virtual size_t GetSize() const override {
+ return m_base_storage->GetSize();
+ }
+};
+
+template <size_t BufferAlign_>
+class AlignmentMatchingStoragePooledBuffer : public IStorage {
+ YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer);
+ YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer);
+
+public:
+ static constexpr size_t BufferAlign = BufferAlign_;
+
+ static_assert(Common::IsPowerOfTwo(BufferAlign));
+
+private:
+ VirtualFile m_base_storage;
+ s64 m_base_storage_size;
+ size_t m_data_align;
+
+public:
+ explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da)
+ : m_base_storage(std::move(bs)), m_data_align(da) {
+ ASSERT(Common::IsPowerOfTwo(da));
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ s64 bs_size = this->GetSize();
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
+
+ // Allocate a pooled buffer.
+ PooledBuffer pooled_buffer;
+ pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
+
+ return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(),
+ pooled_buffer.GetSize(), m_data_align,
+ BufferAlign, offset, buffer, size);
+ }
+
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ s64 bs_size = this->GetSize();
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size)));
+
+ // Allocate a pooled buffer.
+ PooledBuffer pooled_buffer;
+ pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align);
+
+ return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(),
+ pooled_buffer.GetSize(), m_data_align,
+ BufferAlign, offset, buffer, size);
+ }
+
+ virtual size_t GetSize() const override {
+ return m_base_storage->GetSize();
+ }
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp
new file mode 100644
index 000000000..641c888ae
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp
@@ -0,0 +1,204 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h"
+
+namespace FileSys {
+
+namespace {
+
+template <typename T>
+constexpr size_t GetRoundDownDifference(T x, size_t align) {
+ return static_cast<size_t>(x - Common::AlignDown(x, align));
+}
+
+template <typename T>
+constexpr size_t GetRoundUpDifference(T x, size_t align) {
+ return static_cast<size_t>(Common::AlignUp(x, align) - x);
+}
+
+template <typename T>
+size_t GetRoundUpDifference(T* x, size_t align) {
+ return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align);
+}
+
+} // namespace
+
+size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf,
+ size_t work_buf_size, size_t data_alignment,
+ size_t buffer_alignment, s64 offset, u8* buffer,
+ size_t size) {
+ // Check preconditions.
+ ASSERT(work_buf_size >= data_alignment);
+
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ // Determine extents.
+ u8* aligned_core_buffer;
+ s64 core_offset;
+ size_t core_size;
+ size_t buffer_gap;
+ size_t offset_gap;
+ s64 covered_offset;
+
+ const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
+ if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
+ buffer_alignment)) {
+ aligned_core_buffer = buffer + offset_round_up_difference;
+
+ core_offset = Common::AlignUp(offset, data_alignment);
+ core_size = (size < offset_round_up_difference)
+ ? 0
+ : Common::AlignDown(size - offset_round_up_difference, data_alignment);
+ buffer_gap = 0;
+ offset_gap = 0;
+
+ covered_offset = core_size > 0 ? core_offset : offset;
+ } else {
+ const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment);
+
+ aligned_core_buffer = buffer + buffer_round_up_difference;
+
+ core_offset = Common::AlignDown(offset, data_alignment);
+ core_size = (size < buffer_round_up_difference)
+ ? 0
+ : Common::AlignDown(size - buffer_round_up_difference, data_alignment);
+ buffer_gap = buffer_round_up_difference;
+ offset_gap = GetRoundDownDifference(offset, data_alignment);
+
+ covered_offset = offset;
+ }
+
+ // Read the core portion.
+ if (core_size > 0) {
+ base_storage->Read(aligned_core_buffer, core_size, core_offset);
+
+ if (offset_gap != 0 || buffer_gap != 0) {
+ std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap,
+ core_size - offset_gap);
+ core_size -= offset_gap;
+ }
+ }
+
+ // Handle the head portion.
+ if (offset < covered_offset) {
+ const s64 head_offset = Common::AlignDown(offset, data_alignment);
+ const size_t head_size = static_cast<size_t>(covered_offset - offset);
+
+ ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size);
+
+ base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
+ std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size);
+ }
+
+ // Handle the tail portion.
+ s64 tail_offset = covered_offset + core_size;
+ size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
+ while (remaining_tail_size > 0) {
+ const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
+ const auto cur_size =
+ std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
+ remaining_tail_size);
+ base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
+
+ ASSERT((tail_offset - offset) + cur_size <= size);
+ ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment);
+ std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset),
+ work_buf + (tail_offset - aligned_tail_offset), cur_size);
+
+ remaining_tail_size -= cur_size;
+ tail_offset += cur_size;
+ }
+
+ return size;
+}
+
+size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf,
+ size_t work_buf_size, size_t data_alignment,
+ size_t buffer_alignment, s64 offset, const u8* buffer,
+ size_t size) {
+ // Check preconditions.
+ ASSERT(work_buf_size >= data_alignment);
+
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ // Determine extents.
+ const u8* aligned_core_buffer;
+ s64 core_offset;
+ size_t core_size;
+ s64 covered_offset;
+
+ const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
+ if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference,
+ buffer_alignment)) {
+ aligned_core_buffer = buffer + offset_round_up_difference;
+
+ core_offset = Common::AlignUp(offset, data_alignment);
+ core_size = (size < offset_round_up_difference)
+ ? 0
+ : Common::AlignDown(size - offset_round_up_difference, data_alignment);
+
+ covered_offset = core_size > 0 ? core_offset : offset;
+ } else {
+ aligned_core_buffer = nullptr;
+
+ core_offset = Common::AlignDown(offset, data_alignment);
+ core_size = 0;
+
+ covered_offset = offset;
+ }
+
+ // Write the core portion.
+ if (core_size > 0) {
+ base_storage->Write(aligned_core_buffer, core_size, core_offset);
+ }
+
+ // Handle the head portion.
+ if (offset < covered_offset) {
+ const s64 head_offset = Common::AlignDown(offset, data_alignment);
+ const size_t head_size = static_cast<size_t>(covered_offset - offset);
+
+ ASSERT((offset - head_offset) + head_size <= data_alignment);
+
+ base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
+ std::memcpy(work_buf + (offset - head_offset), buffer, head_size);
+ base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset);
+ }
+
+ // Handle the tail portion.
+ s64 tail_offset = covered_offset + core_size;
+ size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
+ while (remaining_tail_size > 0) {
+ ASSERT(static_cast<size_t>(tail_offset - offset) < size);
+
+ const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment);
+ const auto cur_size =
+ std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset),
+ remaining_tail_size);
+
+ base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
+ std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment),
+ buffer + (tail_offset - offset), cur_size);
+ base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset);
+
+ remaining_tail_size -= cur_size;
+ tail_offset += cur_size;
+ }
+
+ return size;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h
new file mode 100644
index 000000000..4a05b0e88
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+
+namespace FileSys {
+
+class AlignmentMatchingStorageImpl {
+public:
+ static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
+ size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer,
+ size_t size);
+ static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size,
+ size_t data_alignment, size_t buffer_alignment, s64 offset,
+ const u8* buffer, size_t size);
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
new file mode 100644
index 000000000..af8541009
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
@@ -0,0 +1,598 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+
+namespace FileSys {
+
+namespace {
+
+using Node = impl::BucketTreeNode<const s64*>;
+static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader));
+static_assert(std::is_trivial_v<Node>);
+
+constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader);
+
+class StorageNode {
+private:
+ class Offset {
+ public:
+ using difference_type = s64;
+
+ private:
+ s64 m_offset;
+ s32 m_stride;
+
+ public:
+ constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {}
+
+ constexpr Offset& operator++() {
+ m_offset += m_stride;
+ return *this;
+ }
+ constexpr Offset operator++(int) {
+ Offset ret(*this);
+ m_offset += m_stride;
+ return ret;
+ }
+
+ constexpr Offset& operator--() {
+ m_offset -= m_stride;
+ return *this;
+ }
+ constexpr Offset operator--(int) {
+ Offset ret(*this);
+ m_offset -= m_stride;
+ return ret;
+ }
+
+ constexpr difference_type operator-(const Offset& rhs) const {
+ return (m_offset - rhs.m_offset) / m_stride;
+ }
+
+ constexpr Offset operator+(difference_type ofs) const {
+ return Offset(m_offset + ofs * m_stride, m_stride);
+ }
+ constexpr Offset operator-(difference_type ofs) const {
+ return Offset(m_offset - ofs * m_stride, m_stride);
+ }
+
+ constexpr Offset& operator+=(difference_type ofs) {
+ m_offset += ofs * m_stride;
+ return *this;
+ }
+ constexpr Offset& operator-=(difference_type ofs) {
+ m_offset -= ofs * m_stride;
+ return *this;
+ }
+
+ constexpr bool operator==(const Offset& rhs) const {
+ return m_offset == rhs.m_offset;
+ }
+ constexpr bool operator!=(const Offset& rhs) const {
+ return m_offset != rhs.m_offset;
+ }
+
+ constexpr s64 Get() const {
+ return m_offset;
+ }
+ };
+
+private:
+ const Offset m_start;
+ const s32 m_count;
+ s32 m_index;
+
+public:
+ StorageNode(size_t size, s32 count)
+ : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {}
+ StorageNode(s64 ofs, size_t size, s32 count)
+ : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {}
+
+ s32 GetIndex() const {
+ return m_index;
+ }
+
+ void Find(const char* buffer, s64 virtual_address) {
+ s32 end = m_count;
+ auto pos = m_start;
+
+ while (end > 0) {
+ auto half = end / 2;
+ auto mid = pos + half;
+
+ s64 offset = 0;
+ std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64));
+
+ if (offset <= virtual_address) {
+ pos = mid + 1;
+ end -= half + 1;
+ } else {
+ end = half;
+ }
+ }
+
+ m_index = static_cast<s32>(pos - m_start) - 1;
+ }
+
+ Result Find(VirtualFile storage, s64 virtual_address) {
+ s32 end = m_count;
+ auto pos = m_start;
+
+ while (end > 0) {
+ auto half = end / 2;
+ auto mid = pos + half;
+
+ s64 offset = 0;
+ storage->ReadObject(std::addressof(offset), mid.Get());
+
+ if (offset <= virtual_address) {
+ pos = mid + 1;
+ end -= half + 1;
+ } else {
+ end = half;
+ }
+ }
+
+ m_index = static_cast<s32>(pos - m_start) - 1;
+ R_SUCCEED();
+ }
+};
+
+} // namespace
+
+void BucketTree::Header::Format(s32 entry_count_) {
+ ASSERT(entry_count_ >= 0);
+
+ this->magic = Magic;
+ this->version = Version;
+ this->entry_count = entry_count_;
+ this->reserved = 0;
+}
+
+Result BucketTree::Header::Verify() const {
+ R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature);
+ R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount);
+ R_UNLESS(this->version <= Version, ResultUnsupportedVersion);
+ R_SUCCEED();
+}
+
+Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const {
+ R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex);
+ R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize);
+
+ const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size;
+ R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count,
+ ResultInvalidBucketTreeNodeEntryCount);
+ R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset);
+
+ R_SUCCEED();
+}
+
+Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
+ size_t entry_size, s32 entry_count) {
+ // Validate preconditions.
+ ASSERT(entry_size >= sizeof(s64));
+ ASSERT(node_size >= entry_size + sizeof(NodeHeader));
+ ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
+ ASSERT(Common::IsPowerOfTwo(node_size));
+ ASSERT(!this->IsInitialized());
+
+ // Ensure valid entry count.
+ R_UNLESS(entry_count > 0, ResultInvalidArgument);
+
+ // Allocate node.
+ R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed);
+ ON_RESULT_FAILURE {
+ m_node_l1.Free(node_size);
+ };
+
+ // Read node.
+ node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size);
+
+ // Verify node.
+ R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64)));
+
+ // Validate offsets.
+ const auto offset_count = GetOffsetCount(node_size);
+ const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
+ const auto* const node = m_node_l1.Get<Node>();
+
+ s64 start_offset;
+ if (offset_count < entry_set_count && node->GetCount() < offset_count) {
+ start_offset = *node->GetEnd();
+ } else {
+ start_offset = *node->GetBegin();
+ }
+ const auto end_offset = node->GetEndOffset();
+
+ R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
+ ResultInvalidBucketTreeEntryOffset);
+ R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
+
+ // Set member variables.
+ m_node_storage = node_storage;
+ m_entry_storage = entry_storage;
+ m_node_size = node_size;
+ m_entry_size = entry_size;
+ m_entry_count = entry_count;
+ m_offset_count = offset_count;
+ m_entry_set_count = entry_set_count;
+
+ m_offset_cache.offsets.start_offset = start_offset;
+ m_offset_cache.offsets.end_offset = end_offset;
+ m_offset_cache.is_initialized = true;
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+void BucketTree::Initialize(size_t node_size, s64 end_offset) {
+ ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
+ ASSERT(Common::IsPowerOfTwo(node_size));
+ ASSERT(end_offset > 0);
+ ASSERT(!this->IsInitialized());
+
+ m_node_size = node_size;
+
+ m_offset_cache.offsets.start_offset = 0;
+ m_offset_cache.offsets.end_offset = end_offset;
+ m_offset_cache.is_initialized = true;
+}
+
+void BucketTree::Finalize() {
+ if (this->IsInitialized()) {
+ m_node_storage = VirtualFile();
+ m_entry_storage = VirtualFile();
+ m_node_l1.Free(m_node_size);
+ m_node_size = 0;
+ m_entry_size = 0;
+ m_entry_count = 0;
+ m_offset_count = 0;
+ m_entry_set_count = 0;
+
+ m_offset_cache.offsets.start_offset = 0;
+ m_offset_cache.offsets.end_offset = 0;
+ m_offset_cache.is_initialized = false;
+ }
+}
+
+Result BucketTree::Find(Visitor* visitor, s64 virtual_address) {
+ ASSERT(visitor != nullptr);
+ ASSERT(this->IsInitialized());
+
+ R_UNLESS(virtual_address >= 0, ResultInvalidOffset);
+ R_UNLESS(!this->IsEmpty(), ResultOutOfRange);
+
+ BucketTree::Offsets offsets;
+ R_TRY(this->GetOffsets(std::addressof(offsets)));
+
+ R_TRY(visitor->Initialize(this, offsets));
+
+ R_RETURN(visitor->Find(virtual_address));
+}
+
+Result BucketTree::InvalidateCache() {
+ // Reset our offsets.
+ m_offset_cache.is_initialized = false;
+
+ R_SUCCEED();
+}
+
+Result BucketTree::EnsureOffsetCache() {
+ // If we already have an offset cache, we're good.
+ R_SUCCEED_IF(m_offset_cache.is_initialized);
+
+ // Acquire exclusive right to edit the offset cache.
+ std::scoped_lock lk(m_offset_cache.mutex);
+
+ // Check again, to be sure.
+ R_SUCCEED_IF(m_offset_cache.is_initialized);
+
+ // Read/verify L1.
+ m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size);
+ R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64)));
+
+ // Get the node.
+ auto* const node = m_node_l1.Get<Node>();
+
+ s64 start_offset;
+ if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) {
+ start_offset = *node->GetEnd();
+ } else {
+ start_offset = *node->GetBegin();
+ }
+ const auto end_offset = node->GetEndOffset();
+
+ R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(),
+ ResultInvalidBucketTreeEntryOffset);
+ R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset);
+
+ m_offset_cache.offsets.start_offset = start_offset;
+ m_offset_cache.offsets.end_offset = end_offset;
+ m_offset_cache.is_initialized = true;
+
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) {
+ ASSERT(tree != nullptr);
+ ASSERT(m_tree == nullptr || m_tree == tree);
+
+ if (m_entry == nullptr) {
+ m_entry = ::operator new(tree->m_entry_size);
+ R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed);
+
+ m_tree = tree;
+ m_offsets = offsets;
+ }
+
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::MoveNext() {
+ R_UNLESS(this->IsValid(), ResultOutOfRange);
+
+ // Invalidate our index, and read the header for the next index.
+ auto entry_index = m_entry_index + 1;
+ if (entry_index == m_entry_set.info.count) {
+ const auto entry_set_index = m_entry_set.info.index + 1;
+ R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange);
+
+ m_entry_index = -1;
+
+ const auto end = m_entry_set.info.end;
+
+ const auto entry_set_size = m_tree->m_node_size;
+ const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
+
+ m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
+ R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
+
+ R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end,
+ ResultInvalidBucketTreeEntrySetOffset);
+
+ entry_index = 0;
+ } else {
+ m_entry_index = -1;
+ }
+
+ // Read the new entry.
+ const auto entry_size = m_tree->m_entry_size;
+ const auto entry_offset = impl::GetBucketTreeEntryOffset(
+ m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
+ m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
+
+ // Note that we changed index.
+ m_entry_index = entry_index;
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::MovePrevious() {
+ R_UNLESS(this->IsValid(), ResultOutOfRange);
+
+ // Invalidate our index, and read the header for the previous index.
+ auto entry_index = m_entry_index;
+ if (entry_index == 0) {
+ R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange);
+
+ m_entry_index = -1;
+
+ const auto start = m_entry_set.info.start;
+
+ const auto entry_set_size = m_tree->m_node_size;
+ const auto entry_set_index = m_entry_set.info.index - 1;
+ const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
+
+ m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset);
+ R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
+
+ R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end,
+ ResultInvalidBucketTreeEntrySetOffset);
+
+ entry_index = m_entry_set.info.count;
+ } else {
+ m_entry_index = -1;
+ }
+
+ --entry_index;
+
+ // Read the new entry.
+ const auto entry_size = m_tree->m_entry_size;
+ const auto entry_offset = impl::GetBucketTreeEntryOffset(
+ m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
+ m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
+
+ // Note that we changed index.
+ m_entry_index = entry_index;
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::Find(s64 virtual_address) {
+ ASSERT(m_tree != nullptr);
+
+ // Get the node.
+ const auto* const node = m_tree->m_node_l1.Get<Node>();
+ R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange);
+
+ // Get the entry set index.
+ s32 entry_set_index = -1;
+ if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) {
+ const auto start = node->GetEnd();
+ const auto end = node->GetBegin() + m_tree->m_offset_count;
+
+ auto pos = std::upper_bound(start, end, virtual_address);
+ R_UNLESS(start < pos, ResultOutOfRange);
+ --pos;
+
+ entry_set_index = static_cast<s32>(pos - start);
+ } else {
+ const auto start = node->GetBegin();
+ const auto end = node->GetEnd();
+
+ auto pos = std::upper_bound(start, end, virtual_address);
+ R_UNLESS(start < pos, ResultOutOfRange);
+ --pos;
+
+ if (m_tree->IsExistL2()) {
+ const auto node_index = static_cast<s32>(pos - start);
+ R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count,
+ ResultInvalidBucketTreeNodeOffset);
+
+ R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index));
+ } else {
+ entry_set_index = static_cast<s32>(pos - start);
+ }
+ }
+
+ // Validate the entry set index.
+ R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count,
+ ResultInvalidBucketTreeNodeOffset);
+
+ // Find the entry.
+ R_TRY(this->FindEntry(virtual_address, entry_set_index));
+
+ // Set count.
+ m_entry_set_count = m_tree->m_entry_set_count;
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) {
+ const auto node_size = m_tree->m_node_size;
+
+ PooledBuffer pool(node_size, 1);
+ if (node_size <= pool.GetSize()) {
+ R_RETURN(
+ this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer()));
+ } else {
+ pool.Deallocate();
+ R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index));
+ }
+}
+
+Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address,
+ s32 node_index, char* buffer) {
+ // Calculate node extents.
+ const auto node_size = m_tree->m_node_size;
+ const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
+ VirtualFile storage = m_tree->m_node_storage;
+
+ // Read the node.
+ storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset);
+
+ // Validate the header.
+ NodeHeader header;
+ std::memcpy(std::addressof(header), buffer, NodeHeaderSize);
+ R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
+
+ // Create the node, and find.
+ StorageNode node(sizeof(s64), header.count);
+ node.Find(buffer, virtual_address);
+ R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset);
+
+ // Return the index.
+ *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address,
+ s32 node_index) {
+ // Calculate node extents.
+ const auto node_size = m_tree->m_node_size;
+ const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
+ VirtualFile storage = m_tree->m_node_storage;
+
+ // Read and validate the header.
+ NodeHeader header;
+ storage->ReadObject(std::addressof(header), node_offset);
+ R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
+
+ // Create the node, and find.
+ StorageNode node(node_offset, sizeof(s64), header.count);
+ R_TRY(node.Find(storage, virtual_address));
+ R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
+
+ // Return the index.
+ *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex()));
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) {
+ const auto entry_set_size = m_tree->m_node_size;
+
+ PooledBuffer pool(entry_set_size, 1);
+ if (entry_set_size <= pool.GetSize()) {
+ R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer()));
+ } else {
+ pool.Deallocate();
+ R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index));
+ }
+}
+
+Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index,
+ char* buffer) {
+ // Calculate entry set extents.
+ const auto entry_size = m_tree->m_entry_size;
+ const auto entry_set_size = m_tree->m_node_size;
+ const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
+ VirtualFile storage = m_tree->m_entry_storage;
+
+ // Read the entry set.
+ storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset);
+
+ // Validate the entry_set.
+ EntrySetHeader entry_set;
+ std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader));
+ R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
+
+ // Create the node, and find.
+ StorageNode node(entry_size, entry_set.info.count);
+ node.Find(buffer, virtual_address);
+ R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
+
+ // Copy the data into entry.
+ const auto entry_index = node.GetIndex();
+ const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index);
+ std::memcpy(m_entry, buffer + entry_offset, entry_size);
+
+ // Set our entry set/index.
+ m_entry_set = entry_set;
+ m_entry_index = entry_index;
+
+ R_SUCCEED();
+}
+
+Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) {
+ // Calculate entry set extents.
+ const auto entry_size = m_tree->m_entry_size;
+ const auto entry_set_size = m_tree->m_node_size;
+ const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
+ VirtualFile storage = m_tree->m_entry_storage;
+
+ // Read and validate the entry_set.
+ EntrySetHeader entry_set;
+ storage->ReadObject(std::addressof(entry_set), entry_set_offset);
+ R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
+
+ // Create the node, and find.
+ StorageNode node(entry_set_offset, entry_size, entry_set.info.count);
+ R_TRY(node.Find(storage, virtual_address));
+ R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange);
+
+ // Copy the data into entry.
+ const auto entry_index = node.GetIndex();
+ const auto entry_offset =
+ impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index);
+ storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset);
+
+ // Set our entry set/index.
+ m_entry_set = entry_set;
+ m_entry_index = entry_index;
+
+ R_SUCCEED();
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h
new file mode 100644
index 000000000..46850cd48
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h
@@ -0,0 +1,416 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "common/alignment.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/literals.h"
+
+#include "core/file_sys/vfs.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+class BucketTree {
+ YUZU_NON_COPYABLE(BucketTree);
+ YUZU_NON_MOVEABLE(BucketTree);
+
+public:
+ static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R');
+ static constexpr u32 Version = 1;
+
+ static constexpr size_t NodeSizeMin = 1_KiB;
+ static constexpr size_t NodeSizeMax = 512_KiB;
+
+public:
+ class Visitor;
+
+ struct Header {
+ u32 magic;
+ u32 version;
+ s32 entry_count;
+ s32 reserved;
+
+ void Format(s32 entry_count);
+ Result Verify() const;
+ };
+ static_assert(std::is_trivial_v<Header>);
+ static_assert(sizeof(Header) == 0x10);
+
+ struct NodeHeader {
+ s32 index;
+ s32 count;
+ s64 offset;
+
+ Result Verify(s32 node_index, size_t node_size, size_t entry_size) const;
+ };
+ static_assert(std::is_trivial_v<NodeHeader>);
+ static_assert(sizeof(NodeHeader) == 0x10);
+
+ struct Offsets {
+ s64 start_offset;
+ s64 end_offset;
+
+ constexpr bool IsInclude(s64 offset) const {
+ return this->start_offset <= offset && offset < this->end_offset;
+ }
+
+ constexpr bool IsInclude(s64 offset, s64 size) const {
+ return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset);
+ }
+ };
+ static_assert(std::is_trivial_v<Offsets>);
+ static_assert(sizeof(Offsets) == 0x10);
+
+ struct OffsetCache {
+ Offsets offsets;
+ std::mutex mutex;
+ bool is_initialized;
+
+ OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {}
+ };
+
+ class ContinuousReadingInfo {
+ public:
+ constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {}
+
+ constexpr void Reset() {
+ m_read_size = 0;
+ m_skip_count = 0;
+ m_done = false;
+ }
+
+ constexpr void SetSkipCount(s32 count) {
+ ASSERT(count >= 0);
+ m_skip_count = count;
+ }
+ constexpr s32 GetSkipCount() const {
+ return m_skip_count;
+ }
+ constexpr bool CheckNeedScan() {
+ return (--m_skip_count) <= 0;
+ }
+
+ constexpr void Done() {
+ m_read_size = 0;
+ m_done = true;
+ }
+ constexpr bool IsDone() const {
+ return m_done;
+ }
+
+ constexpr void SetReadSize(size_t size) {
+ m_read_size = size;
+ }
+ constexpr size_t GetReadSize() const {
+ return m_read_size;
+ }
+ constexpr bool CanDo() const {
+ return m_read_size > 0;
+ }
+
+ private:
+ size_t m_read_size;
+ s32 m_skip_count;
+ bool m_done;
+ };
+
+private:
+ class NodeBuffer {
+ YUZU_NON_COPYABLE(NodeBuffer);
+
+ public:
+ NodeBuffer() : m_header() {}
+
+ ~NodeBuffer() {
+ ASSERT(m_header == nullptr);
+ }
+
+ NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) {
+ rhs.m_header = nullptr;
+ }
+
+ NodeBuffer& operator=(NodeBuffer&& rhs) {
+ if (this != std::addressof(rhs)) {
+ ASSERT(m_header == nullptr);
+
+ m_header = rhs.m_header;
+
+ rhs.m_header = nullptr;
+ }
+ return *this;
+ }
+
+ bool Allocate(size_t node_size) {
+ ASSERT(m_header == nullptr);
+
+ m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)});
+
+ // ASSERT(Common::IsAligned(m_header, sizeof(s64)));
+
+ return m_header != nullptr;
+ }
+
+ void Free(size_t node_size) {
+ if (m_header) {
+ ::operator delete(m_header, std::align_val_t{sizeof(s64)});
+ m_header = nullptr;
+ }
+ }
+
+ void FillZero(size_t node_size) const {
+ if (m_header) {
+ std::memset(m_header, 0, node_size);
+ }
+ }
+
+ NodeHeader* Get() const {
+ return reinterpret_cast<NodeHeader*>(m_header);
+ }
+
+ NodeHeader* operator->() const {
+ return this->Get();
+ }
+
+ template <typename T>
+ T* Get() const {
+ static_assert(std::is_trivial_v<T>);
+ static_assert(sizeof(T) == sizeof(NodeHeader));
+ return reinterpret_cast<T*>(m_header);
+ }
+
+ private:
+ void* m_header;
+ };
+
+private:
+ static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) {
+ return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size);
+ }
+
+ static constexpr s32 GetOffsetCount(size_t node_size) {
+ return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64));
+ }
+
+ static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) {
+ const s32 entry_count_per_node = GetEntryCount(node_size, entry_size);
+ return Common::DivideUp(entry_count, entry_count_per_node);
+ }
+
+ static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) {
+ const s32 offset_count_per_node = GetOffsetCount(node_size);
+ const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
+
+ if (entry_set_count <= offset_count_per_node) {
+ return 0;
+ }
+
+ const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node);
+ ASSERT(node_l2_count <= offset_count_per_node);
+
+ return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)),
+ offset_count_per_node);
+ }
+
+public:
+ BucketTree()
+ : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(),
+ m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {}
+ ~BucketTree() {
+ this->Finalize();
+ }
+
+ Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size,
+ size_t entry_size, s32 entry_count);
+ void Initialize(size_t node_size, s64 end_offset);
+ void Finalize();
+
+ bool IsInitialized() const {
+ return m_node_size > 0;
+ }
+ bool IsEmpty() const {
+ return m_entry_size == 0;
+ }
+
+ Result Find(Visitor* visitor, s64 virtual_address);
+ Result InvalidateCache();
+
+ s32 GetEntryCount() const {
+ return m_entry_count;
+ }
+
+ Result GetOffsets(Offsets* out) {
+ // Ensure we have an offset cache.
+ R_TRY(this->EnsureOffsetCache());
+
+ // Set the output.
+ *out = m_offset_cache.offsets;
+ R_SUCCEED();
+ }
+
+public:
+ static constexpr s64 QueryHeaderStorageSize() {
+ return sizeof(Header);
+ }
+
+ static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size,
+ s32 entry_count) {
+ ASSERT(entry_size >= sizeof(s64));
+ ASSERT(node_size >= entry_size + sizeof(NodeHeader));
+ ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
+ ASSERT(Common::IsPowerOfTwo(node_size));
+ ASSERT(entry_count >= 0);
+
+ if (entry_count <= 0) {
+ return 0;
+ }
+ return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) *
+ static_cast<s64>(node_size);
+ }
+
+ static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size,
+ s32 entry_count) {
+ ASSERT(entry_size >= sizeof(s64));
+ ASSERT(node_size >= entry_size + sizeof(NodeHeader));
+ ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
+ ASSERT(Common::IsPowerOfTwo(node_size));
+ ASSERT(entry_count >= 0);
+
+ if (entry_count <= 0) {
+ return 0;
+ }
+ return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size);
+ }
+
+private:
+ template <typename EntryType>
+ struct ContinuousReadingParam {
+ s64 offset;
+ size_t size;
+ NodeHeader entry_set;
+ s32 entry_index;
+ Offsets offsets;
+ EntryType entry;
+ };
+
+private:
+ template <typename EntryType>
+ Result ScanContinuousReading(ContinuousReadingInfo* out_info,
+ const ContinuousReadingParam<EntryType>& param) const;
+
+ bool IsExistL2() const {
+ return m_offset_count < m_entry_set_count;
+ }
+ bool IsExistOffsetL2OnL1() const {
+ return this->IsExistL2() && m_node_l1->count < m_offset_count;
+ }
+
+ s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const {
+ return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index;
+ }
+
+ Result EnsureOffsetCache();
+
+private:
+ mutable VirtualFile m_node_storage;
+ mutable VirtualFile m_entry_storage;
+ NodeBuffer m_node_l1;
+ size_t m_node_size;
+ size_t m_entry_size;
+ s32 m_entry_count;
+ s32 m_offset_count;
+ s32 m_entry_set_count;
+ OffsetCache m_offset_cache;
+};
+
+class BucketTree::Visitor {
+ YUZU_NON_COPYABLE(Visitor);
+ YUZU_NON_MOVEABLE(Visitor);
+
+public:
+ constexpr Visitor()
+ : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {}
+ ~Visitor() {
+ if (m_entry != nullptr) {
+ ::operator delete(m_entry, m_tree->m_entry_size);
+ m_tree = nullptr;
+ m_entry = nullptr;
+ }
+ }
+
+ bool IsValid() const {
+ return m_entry_index >= 0;
+ }
+ bool CanMoveNext() const {
+ return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count ||
+ m_entry_set.info.index + 1 < m_entry_set_count);
+ }
+ bool CanMovePrevious() const {
+ return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0);
+ }
+
+ Result MoveNext();
+ Result MovePrevious();
+
+ template <typename EntryType>
+ Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const;
+
+ const void* Get() const {
+ ASSERT(this->IsValid());
+ return m_entry;
+ }
+
+ template <typename T>
+ const T* Get() const {
+ ASSERT(this->IsValid());
+ return reinterpret_cast<const T*>(m_entry);
+ }
+
+ const BucketTree* GetTree() const {
+ return m_tree;
+ }
+
+private:
+ Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets);
+
+ Result Find(s64 virtual_address);
+
+ Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index);
+ Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index,
+ char* buffer);
+ Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index);
+
+ Result FindEntry(s64 virtual_address, s32 entry_set_index);
+ Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer);
+ Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index);
+
+private:
+ friend class BucketTree;
+
+ union EntrySetHeader {
+ NodeHeader header;
+ struct Info {
+ s32 index;
+ s32 count;
+ s64 end;
+ s64 start;
+ } info;
+ static_assert(std::is_trivial_v<Info>);
+ };
+ static_assert(std::is_trivial_v<EntrySetHeader>);
+
+ const BucketTree* m_tree;
+ BucketTree::Offsets m_offsets;
+ void* m_entry;
+ s32 m_entry_index;
+ s32 m_entry_set_count;
+ EntrySetHeader m_entry_set;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h
new file mode 100644
index 000000000..030b2916b
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h
@@ -0,0 +1,170 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+
+namespace FileSys {
+
+template <typename EntryType>
+Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info,
+ const ContinuousReadingParam<EntryType>& param) const {
+ static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>);
+
+ // Validate our preconditions.
+ ASSERT(this->IsInitialized());
+ ASSERT(out_info != nullptr);
+ ASSERT(m_entry_size == sizeof(EntryType));
+
+ // Reset the output.
+ out_info->Reset();
+
+ // If there's nothing to read, we're done.
+ R_SUCCEED_IF(param.size == 0);
+
+ // If we're reading a fragment, we're done.
+ R_SUCCEED_IF(param.entry.IsFragment());
+
+ // Validate the first entry.
+ auto entry = param.entry;
+ auto cur_offset = param.offset;
+ R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange);
+
+ // Create a pooled buffer for our scan.
+ PooledBuffer pool(m_node_size, 1);
+ char* buffer = nullptr;
+
+ s64 entry_storage_size = m_entry_storage->GetSize();
+
+ // Read the node.
+ if (m_node_size <= pool.GetSize()) {
+ buffer = pool.GetBuffer();
+ const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size);
+ R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size),
+ ResultInvalidBucketTreeNodeEntryCount);
+
+ m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs);
+ }
+
+ // Calculate extents.
+ const auto end_offset = cur_offset + static_cast<s64>(param.size);
+ s64 phys_offset = entry.GetPhysicalOffset();
+
+ // Start merge tracking.
+ s64 merge_size = 0;
+ s64 readable_size = 0;
+ bool merged = false;
+
+ // Iterate.
+ auto entry_index = param.entry_index;
+ for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) {
+ // If we're past the end, we're done.
+ if (end_offset <= cur_offset) {
+ break;
+ }
+
+ // Validate the entry offset.
+ const auto entry_offset = entry.GetVirtualOffset();
+ R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
+
+ // Get the next entry.
+ EntryType next_entry = {};
+ s64 next_entry_offset;
+
+ if (entry_index + 1 < entry_count) {
+ if (buffer != nullptr) {
+ const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1);
+ std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size);
+ } else {
+ const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size,
+ m_entry_size, entry_index + 1);
+ m_entry_storage->ReadObject(std::addressof(next_entry), ofs);
+ }
+
+ next_entry_offset = next_entry.GetVirtualOffset();
+ R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
+ } else {
+ next_entry_offset = param.entry_set.offset;
+ }
+
+ // Validate the next entry offset.
+ R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
+
+ // Determine the much data there is.
+ const auto data_size = next_entry_offset - cur_offset;
+ ASSERT(data_size > 0);
+
+ // Determine how much data we should read.
+ const auto remaining_size = end_offset - cur_offset;
+ const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size));
+ ASSERT(read_size <= param.size);
+
+ // Update our merge tracking.
+ if (entry.IsFragment()) {
+ // If we can't merge, stop looping.
+ if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) {
+ break;
+ }
+
+ // Otherwise, add the current size to the merge size.
+ merge_size += read_size;
+ } else {
+ // If we can't merge, stop looping.
+ if (phys_offset != entry.GetPhysicalOffset()) {
+ break;
+ }
+
+ // Add the size to the readable amount.
+ readable_size += merge_size + read_size;
+ ASSERT(readable_size <= static_cast<s64>(param.size));
+
+ // Update whether we've merged.
+ merged |= merge_size > 0;
+ merge_size = 0;
+ }
+
+ // Advance.
+ cur_offset += read_size;
+ ASSERT(cur_offset <= end_offset);
+
+ phys_offset += next_entry_offset - entry_offset;
+ entry = next_entry;
+ }
+
+ // If we merged, set our readable size.
+ if (merged) {
+ out_info->SetReadSize(static_cast<size_t>(readable_size));
+ }
+ out_info->SetSkipCount(entry_index - param.entry_index);
+
+ R_SUCCEED();
+}
+
+template <typename EntryType>
+Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset,
+ size_t size) const {
+ static_assert(std::is_trivial_v<EntryType>);
+ ASSERT(this->IsValid());
+
+ // Create our parameters.
+ ContinuousReadingParam<EntryType> param = {
+ .offset = offset,
+ .size = size,
+ .entry_set = m_entry_set.header,
+ .entry_index = m_entry_index,
+ .offsets{},
+ .entry{},
+ };
+ std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets),
+ sizeof(BucketTree::Offsets));
+ std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType));
+
+ // Scan.
+ R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param));
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h
new file mode 100644
index 000000000..5503613fc
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+
+namespace FileSys::impl {
+
+class SafeValue {
+public:
+ static s64 GetInt64(const void* ptr) {
+ s64 value;
+ std::memcpy(std::addressof(value), ptr, sizeof(s64));
+ return value;
+ }
+
+ static s64 GetInt64(const s64* ptr) {
+ return GetInt64(static_cast<const void*>(ptr));
+ }
+
+ static s64 GetInt64(const s64& v) {
+ return GetInt64(std::addressof(v));
+ }
+
+ static void SetInt64(void* dst, const void* src) {
+ std::memcpy(dst, src, sizeof(s64));
+ }
+
+ static void SetInt64(void* dst, const s64* src) {
+ return SetInt64(dst, static_cast<const void*>(src));
+ }
+
+ static void SetInt64(void* dst, const s64& v) {
+ return SetInt64(dst, std::addressof(v));
+ }
+};
+
+template <typename IteratorType>
+struct BucketTreeNode {
+ using Header = BucketTree::NodeHeader;
+
+ Header header;
+
+ s32 GetCount() const {
+ return this->header.count;
+ }
+
+ void* GetArray() {
+ return std::addressof(this->header) + 1;
+ }
+ template <typename T>
+ T* GetArray() {
+ return reinterpret_cast<T*>(this->GetArray());
+ }
+ const void* GetArray() const {
+ return std::addressof(this->header) + 1;
+ }
+ template <typename T>
+ const T* GetArray() const {
+ return reinterpret_cast<const T*>(this->GetArray());
+ }
+
+ s64 GetBeginOffset() const {
+ return *this->GetArray<s64>();
+ }
+ s64 GetEndOffset() const {
+ return this->header.offset;
+ }
+
+ IteratorType GetBegin() {
+ return IteratorType(this->GetArray<s64>());
+ }
+ IteratorType GetEnd() {
+ return IteratorType(this->GetArray<s64>()) + this->header.count;
+ }
+ IteratorType GetBegin() const {
+ return IteratorType(this->GetArray<s64>());
+ }
+ IteratorType GetEnd() const {
+ return IteratorType(this->GetArray<s64>()) + this->header.count;
+ }
+
+ IteratorType GetBegin(size_t entry_size) {
+ return IteratorType(this->GetArray(), entry_size);
+ }
+ IteratorType GetEnd(size_t entry_size) {
+ return IteratorType(this->GetArray(), entry_size) + this->header.count;
+ }
+ IteratorType GetBegin(size_t entry_size) const {
+ return IteratorType(this->GetArray(), entry_size);
+ }
+ IteratorType GetEnd(size_t entry_size) const {
+ return IteratorType(this->GetArray(), entry_size) + this->header.count;
+ }
+};
+
+constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size,
+ s32 entry_index) {
+ return entry_set_offset + sizeof(BucketTree::NodeHeader) +
+ entry_index * static_cast<s64>(entry_size);
+}
+
+constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size,
+ size_t entry_size, s32 entry_index) {
+ return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size,
+ entry_index);
+}
+
+} // namespace FileSys::impl
diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h
new file mode 100644
index 000000000..33d93938e
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h
@@ -0,0 +1,963 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/literals.h"
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+#include "core/file_sys/fssystem/fssystem_compression_common.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+class CompressedStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(CompressedStorage);
+ YUZU_NON_MOVEABLE(CompressedStorage);
+
+public:
+ static constexpr size_t NodeSize = 16_KiB;
+
+ struct Entry {
+ s64 virt_offset;
+ s64 phys_offset;
+ CompressionType compression_type;
+ s32 phys_size;
+
+ s64 GetPhysicalSize() const {
+ return this->phys_size;
+ }
+ };
+ static_assert(std::is_trivial_v<Entry>);
+ static_assert(sizeof(Entry) == 0x18);
+
+public:
+ static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
+ return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+ static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
+ return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+private:
+ class CompressedStorageCore {
+ YUZU_NON_COPYABLE(CompressedStorageCore);
+ YUZU_NON_MOVEABLE(CompressedStorageCore);
+
+ public:
+ CompressedStorageCore() : m_table(), m_data_storage() {}
+
+ ~CompressedStorageCore() {
+ this->Finalize();
+ }
+
+ public:
+ Result Initialize(VirtualFile data_storage, VirtualFile node_storage,
+ VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max,
+ size_t continuous_reading_size_max,
+ GetDecompressorFunction get_decompressor) {
+ // Check pre-conditions.
+ ASSERT(0 < block_size_max);
+ ASSERT(block_size_max <= continuous_reading_size_max);
+ ASSERT(get_decompressor != nullptr);
+
+ // Initialize our entry table.
+ R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry),
+ bktr_entry_count));
+
+ // Set our other fields.
+ m_block_size_max = block_size_max;
+ m_continuous_reading_size_max = continuous_reading_size_max;
+ m_data_storage = data_storage;
+ m_get_decompressor_function = get_decompressor;
+
+ R_SUCCEED();
+ }
+
+ void Finalize() {
+ if (this->IsInitialized()) {
+ m_table.Finalize();
+ m_data_storage = VirtualFile();
+ }
+ }
+
+ VirtualFile GetDataStorage() {
+ return m_data_storage;
+ }
+
+ Result GetDataStorageSize(s64* out) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+
+ // Get size.
+ *out = m_data_storage->GetSize();
+
+ R_SUCCEED();
+ }
+
+ BucketTree& GetEntryTable() {
+ return m_table;
+ }
+
+ Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count,
+ s64 offset, s64 size) {
+ // Check pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Check that we can output the count.
+ R_UNLESS(out_read_count != nullptr, ResultNullptrArgument);
+
+ // Check that we have anything to read at all.
+ R_SUCCEED_IF(size == 0);
+
+ // Check that either we have a buffer, or this is to determine how many we need.
+ if (max_entry_count != 0) {
+ R_UNLESS(out_entries != nullptr, ResultNullptrArgument);
+ }
+
+ // Get the table offsets.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ // Validate arguments.
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->virt_offset;
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultUnexpectedInCompressedStorageA);
+ }
+
+ // Get the entries.
+ const auto end_offset = offset + size;
+ s32 read_count = 0;
+ while (visitor.Get<Entry>()->virt_offset < end_offset) {
+ // If we should be setting the output, do so.
+ if (max_entry_count != 0) {
+ // Ensure we only read as many entries as we can.
+ if (read_count >= max_entry_count) {
+ break;
+ }
+
+ // Set the current output entry.
+ out_entries[read_count] = *visitor.Get<Entry>();
+ }
+
+ // Increase the read count.
+ ++read_count;
+
+ // If we're at the end, we're done.
+ if (!visitor.CanMoveNext()) {
+ break;
+ }
+
+ // Move to the next entry.
+ R_TRY(visitor.MoveNext());
+ }
+
+ // Set the output read count.
+ *out_read_count = read_count;
+ R_SUCCEED();
+ }
+
+ Result GetSize(s64* out) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+
+ // Get our table offsets.
+ BucketTree::Offsets offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(offsets)));
+
+ // Set the output.
+ *out = offsets.end_offset;
+ R_SUCCEED();
+ }
+
+ Result OperatePerEntry(s64 offset, s64 size, auto f) {
+ // Check pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Succeed if there's nothing to operate on.
+ R_SUCCEED_IF(size == 0);
+
+ // Get the table offsets.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ // Validate arguments.
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->virt_offset;
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultUnexpectedInCompressedStorageA);
+ }
+
+ // Prepare to operate in chunks.
+ auto cur_offset = offset;
+ const auto end_offset = offset + static_cast<s64>(size);
+
+ while (cur_offset < end_offset) {
+ // Get the current entry.
+ const auto cur_entry = *visitor.Get<Entry>();
+
+ // Get and validate the entry's offset.
+ const auto cur_entry_offset = cur_entry.virt_offset;
+ R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA);
+
+ // Get and validate the next entry offset.
+ s64 next_entry_offset;
+ if (visitor.CanMoveNext()) {
+ R_TRY(visitor.MoveNext());
+ next_entry_offset = visitor.Get<Entry>()->virt_offset;
+ R_UNLESS(table_offsets.IsInclude(next_entry_offset),
+ ResultUnexpectedInCompressedStorageA);
+ } else {
+ next_entry_offset = table_offsets.end_offset;
+ }
+ R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA);
+
+ // Get the offset of the entry in the data we read.
+ const auto data_offset = cur_offset - cur_entry_offset;
+ const auto data_size = (next_entry_offset - cur_entry_offset);
+ ASSERT(data_size > 0);
+
+ // Determine how much is left.
+ const auto remaining_size = end_offset - cur_offset;
+ const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
+ ASSERT(cur_size <= size);
+
+ // Get the data storage size.
+ s64 storage_size = m_data_storage->GetSize();
+
+ // Check that our read remains naively physically in bounds.
+ R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size,
+ ResultUnexpectedInCompressedStorageC);
+
+ // If we have any compression, verify that we remain physically in bounds.
+ if (cur_entry.compression_type != CompressionType::None) {
+ R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size,
+ ResultUnexpectedInCompressedStorageC);
+ }
+
+ // Check that block alignment requirements are met.
+ if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) {
+ R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment),
+ ResultUnexpectedInCompressedStorageA);
+ }
+
+ // Invoke the operator.
+ bool is_continuous = true;
+ R_TRY(
+ f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size));
+
+ // If not continuous, we're done.
+ if (!is_continuous) {
+ break;
+ }
+
+ // Advance.
+ cur_offset += cur_size;
+ }
+
+ R_SUCCEED();
+ }
+
+ public:
+ using ReadImplFunction = std::function<Result(void*, size_t)>;
+ using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>;
+
+ public:
+ Result Read(s64 offset, s64 size, const ReadFunction& read_func) {
+ // Check pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Succeed immediately, if we have nothing to read.
+ R_SUCCEED_IF(size == 0);
+
+ // Declare read lambda.
+ constexpr int EntriesCountMax = 0x80;
+ struct Entries {
+ CompressionType compression_type;
+ u32 gap_from_prev;
+ u32 physical_size;
+ u32 virtual_size;
+ };
+ std::array<Entries, EntriesCountMax> entries;
+ s32 entry_count = 0;
+ Entry prev_entry = {
+ .virt_offset = -1,
+ .phys_offset{},
+ .compression_type{},
+ .phys_size{},
+ };
+ bool will_allocate_pooled_buffer = false;
+ s64 required_access_physical_offset = 0;
+ s64 required_access_physical_size = 0;
+
+ auto PerformRequiredRead = [&]() -> Result {
+ // If there are no entries, we have nothing to do.
+ R_SUCCEED_IF(entry_count == 0);
+
+ // Get the remaining size in a convenient form.
+ const size_t total_required_size =
+ static_cast<size_t>(required_access_physical_size);
+
+ // Perform the read based on whether we need to allocate a buffer.
+ if (will_allocate_pooled_buffer) {
+ // Allocate a pooled buffer.
+ PooledBuffer pooled_buffer;
+ if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) {
+ pooled_buffer.Allocate(total_required_size, m_block_size_max);
+ } else {
+ pooled_buffer.AllocateParticularlyLarge(
+ std::min<size_t>(
+ total_required_size,
+ PooledBuffer::GetAllocatableParticularlyLargeSizeMax()),
+ m_block_size_max);
+ }
+
+ // Read each of the entries.
+ for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) {
+ // Determine the current read size.
+ bool will_use_pooled_buffer = false;
+ const size_t cur_read_size = [&]() -> size_t {
+ if (const size_t target_entry_size =
+ static_cast<size_t>(entries[entry_idx].physical_size) +
+ static_cast<size_t>(entries[entry_idx].gap_from_prev);
+ target_entry_size <= pooled_buffer.GetSize()) {
+ // We'll be using the pooled buffer.
+ will_use_pooled_buffer = true;
+
+ // Determine how much we can read.
+ const size_t max_size = std::min<size_t>(
+ required_access_physical_size, pooled_buffer.GetSize());
+
+ size_t read_size = 0;
+ for (auto n = entry_idx; n < entry_count; ++n) {
+ const size_t cur_entry_size =
+ static_cast<size_t>(entries[n].physical_size) +
+ static_cast<size_t>(entries[n].gap_from_prev);
+ if (read_size + cur_entry_size > max_size) {
+ break;
+ }
+
+ read_size += cur_entry_size;
+ }
+
+ return read_size;
+ } else {
+ // If we don't fit, we must be uncompressed.
+ ASSERT(entries[entry_idx].compression_type ==
+ CompressionType::None);
+
+ // We can perform the whole of an uncompressed read directly.
+ return entries[entry_idx].virtual_size;
+ }
+ }();
+
+ // Perform the read based on whether or not we'll use the pooled buffer.
+ if (will_use_pooled_buffer) {
+ // Read the compressed data into the pooled buffer.
+ auto* const buffer = pooled_buffer.GetBuffer();
+ m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size,
+ required_access_physical_offset);
+
+ // Decompress the data.
+ size_t buffer_offset;
+ for (buffer_offset = 0;
+ entry_idx < entry_count &&
+ ((static_cast<size_t>(entries[entry_idx].physical_size) +
+ static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 ||
+ buffer_offset < cur_read_size);
+ buffer_offset += entries[entry_idx++].physical_size) {
+ // Advance by the relevant gap.
+ buffer_offset += entries[entry_idx].gap_from_prev;
+
+ const auto compression_type = entries[entry_idx].compression_type;
+ switch (compression_type) {
+ case CompressionType::None: {
+ // Check that we can remain within bounds.
+ ASSERT(buffer_offset + entries[entry_idx].virtual_size <=
+ cur_read_size);
+
+ // Perform no decompression.
+ R_TRY(read_func(
+ entries[entry_idx].virtual_size,
+ [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size == entries[entry_idx].virtual_size);
+
+ // We have no compression, so just copy the data
+ // out.
+ std::memcpy(dst, buffer + buffer_offset,
+ entries[entry_idx].virtual_size);
+ R_SUCCEED();
+ }));
+
+ break;
+ }
+ case CompressionType::Zeros: {
+ // Check that we can remain within bounds.
+ ASSERT(buffer_offset <= cur_read_size);
+
+ // Zero the memory.
+ R_TRY(read_func(
+ entries[entry_idx].virtual_size,
+ [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size == entries[entry_idx].virtual_size);
+
+ // The data is zeroes, so zero the buffer.
+ std::memset(dst, 0, entries[entry_idx].virtual_size);
+ R_SUCCEED();
+ }));
+
+ break;
+ }
+ default: {
+ // Check that we can remain within bounds.
+ ASSERT(buffer_offset + entries[entry_idx].physical_size <=
+ cur_read_size);
+
+ // Get the decompressor.
+ const auto decompressor =
+ this->GetDecompressor(compression_type);
+ R_UNLESS(decompressor != nullptr,
+ ResultUnexpectedInCompressedStorageB);
+
+ // Decompress the data.
+ R_TRY(read_func(entries[entry_idx].virtual_size,
+ [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size ==
+ entries[entry_idx].virtual_size);
+
+ // Perform the decompression.
+ R_RETURN(decompressor(
+ dst, entries[entry_idx].virtual_size,
+ buffer + buffer_offset,
+ entries[entry_idx].physical_size));
+ }));
+
+ break;
+ }
+ }
+ }
+
+ // Check that we processed the correct amount of data.
+ ASSERT(buffer_offset == cur_read_size);
+ } else {
+ // Account for the gap from the previous entry.
+ required_access_physical_offset += entries[entry_idx].gap_from_prev;
+ required_access_physical_size -= entries[entry_idx].gap_from_prev;
+
+ // We don't need the buffer (as the data is uncompressed), so just
+ // execute the read.
+ R_TRY(
+ read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size == cur_read_size);
+
+ // Perform the read.
+ m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size,
+ required_access_physical_offset);
+
+ R_SUCCEED();
+ }));
+ }
+
+ // Advance on.
+ required_access_physical_offset += cur_read_size;
+ required_access_physical_size -= cur_read_size;
+ }
+
+ // Verify that we have nothing remaining to read.
+ ASSERT(required_access_physical_size == 0);
+
+ R_SUCCEED();
+ } else {
+ // We don't need a buffer, so just execute the read.
+ R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result {
+ // Check that the size is valid.
+ ASSERT(dst_size == total_required_size);
+
+ // Perform the read.
+ m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size,
+ required_access_physical_offset);
+
+ R_SUCCEED();
+ }));
+ }
+
+ R_SUCCEED();
+ };
+
+ R_TRY(this->OperatePerEntry(
+ offset, size,
+ [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
+ s64 data_offset, s64 read_size) -> Result {
+ // Determine the physical extents.
+ s64 physical_offset, physical_size;
+ if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) {
+ physical_offset = entry.phys_offset + data_offset;
+ physical_size = read_size;
+ } else {
+ physical_offset = entry.phys_offset;
+ physical_size = entry.GetPhysicalSize();
+ }
+
+ // If we have a pending data storage operation, perform it if we have to.
+ const s64 required_access_physical_end =
+ required_access_physical_offset + required_access_physical_size;
+ if (required_access_physical_size > 0) {
+ const bool required_by_gap =
+ !(required_access_physical_end <= physical_offset &&
+ physical_offset <= Common::AlignUp(required_access_physical_end,
+ CompressionBlockAlignment));
+ const bool required_by_continuous_size =
+ ((physical_size + physical_offset) - required_access_physical_end) +
+ required_access_physical_size >
+ static_cast<s64>(m_continuous_reading_size_max);
+ const bool required_by_entry_count = entry_count == EntriesCountMax;
+ if (required_by_gap || required_by_continuous_size ||
+ required_by_entry_count) {
+ // Check that our planned access is sane.
+ ASSERT(!will_allocate_pooled_buffer ||
+ required_access_physical_size <=
+ static_cast<s64>(m_continuous_reading_size_max));
+
+ // Perform the required read.
+ const Result rc = PerformRequiredRead();
+ if (R_FAILED(rc)) {
+ R_THROW(rc);
+ }
+
+ // Reset our requirements.
+ prev_entry.virt_offset = -1;
+ required_access_physical_size = 0;
+ entry_count = 0;
+ will_allocate_pooled_buffer = false;
+ }
+ }
+
+ // Sanity check that we're within bounds on entries.
+ ASSERT(entry_count < EntriesCountMax);
+
+ // Determine if a buffer allocation is needed.
+ if (entry.compression_type != CompressionType::None ||
+ (prev_entry.virt_offset >= 0 &&
+ entry.virt_offset - prev_entry.virt_offset !=
+ entry.phys_offset - prev_entry.phys_offset)) {
+ will_allocate_pooled_buffer = true;
+ }
+
+ // If we need to access the data storage, update our required access parameters.
+ if (CompressionTypeUtility::IsDataStorageAccessRequired(
+ entry.compression_type)) {
+ // If the data is compressed, ensure the access is sane.
+ if (entry.compression_type != CompressionType::None) {
+ R_UNLESS(data_offset == 0, ResultInvalidOffset);
+ R_UNLESS(virtual_data_size == read_size, ResultInvalidSize);
+ R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max),
+ ResultUnexpectedInCompressedStorageD);
+ }
+
+ // Update the required access parameters.
+ s64 gap_from_prev;
+ if (required_access_physical_size > 0) {
+ gap_from_prev = physical_offset - required_access_physical_end;
+ } else {
+ gap_from_prev = 0;
+ required_access_physical_offset = physical_offset;
+ }
+ required_access_physical_size += physical_size + gap_from_prev;
+
+ // Create an entry to access the data storage.
+ entries[entry_count++] = {
+ .compression_type = entry.compression_type,
+ .gap_from_prev = static_cast<u32>(gap_from_prev),
+ .physical_size = static_cast<u32>(physical_size),
+ .virtual_size = static_cast<u32>(read_size),
+ };
+ } else {
+ // Verify that we're allowed to be operating on the non-data-storage-access
+ // type.
+ R_UNLESS(entry.compression_type == CompressionType::Zeros,
+ ResultUnexpectedInCompressedStorageB);
+
+ // If we have entries, create a fake entry for the zero region.
+ if (entry_count != 0) {
+ // We need to have a physical size.
+ R_UNLESS(entry.GetPhysicalSize() != 0,
+ ResultUnexpectedInCompressedStorageD);
+
+ // Create a fake entry.
+ entries[entry_count++] = {
+ .compression_type = CompressionType::Zeros,
+ .gap_from_prev = 0,
+ .physical_size = 0,
+ .virtual_size = static_cast<u32>(read_size),
+ };
+ } else {
+ // We have no entries, so we can just perform the read.
+ const Result rc =
+ read_func(static_cast<size_t>(read_size),
+ [&](void* dst, size_t dst_size) -> Result {
+ // Check the space we should zero is correct.
+ ASSERT(dst_size == static_cast<size_t>(read_size));
+
+ // Zero the memory.
+ std::memset(dst, 0, read_size);
+ R_SUCCEED();
+ });
+ if (R_FAILED(rc)) {
+ R_THROW(rc);
+ }
+ }
+ }
+
+ // Set the previous entry.
+ prev_entry = entry;
+
+ // We're continuous.
+ *out_continuous = true;
+ R_SUCCEED();
+ }));
+
+ // If we still have a pending access, perform it.
+ if (required_access_physical_size != 0) {
+ R_TRY(PerformRequiredRead());
+ }
+
+ R_SUCCEED();
+ }
+
+ private:
+ DecompressorFunction GetDecompressor(CompressionType type) const {
+ // Check that we can get a decompressor for the type.
+ if (CompressionTypeUtility::IsUnknownType(type)) {
+ return nullptr;
+ }
+
+ // Get the decompressor.
+ return m_get_decompressor_function(type);
+ }
+
+ bool IsInitialized() const {
+ return m_table.IsInitialized();
+ }
+
+ private:
+ size_t m_block_size_max;
+ size_t m_continuous_reading_size_max;
+ BucketTree m_table;
+ VirtualFile m_data_storage;
+ GetDecompressorFunction m_get_decompressor_function;
+ };
+
+ class CacheManager {
+ YUZU_NON_COPYABLE(CacheManager);
+ YUZU_NON_MOVEABLE(CacheManager);
+
+ private:
+ struct AccessRange {
+ s64 virtual_offset;
+ s64 virtual_size;
+ u32 physical_size;
+ bool is_block_alignment_required;
+
+ s64 GetEndVirtualOffset() const {
+ return this->virtual_offset + this->virtual_size;
+ }
+ };
+ static_assert(std::is_trivial_v<AccessRange>);
+
+ public:
+ CacheManager() = default;
+
+ public:
+ Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1,
+ size_t max_cache_entries) {
+ // Set our fields.
+ m_storage_size = storage_size;
+
+ R_SUCCEED();
+ }
+
+ Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) {
+ // If we have nothing to read, succeed.
+ R_SUCCEED_IF(size == 0);
+
+ // Check that we have a buffer to read into.
+ R_UNLESS(buffer != nullptr, ResultNullptrArgument);
+
+ // Check that the read is in bounds.
+ R_UNLESS(offset <= m_storage_size, ResultInvalidOffset);
+
+ // Determine how much we can read.
+ const size_t read_size = std::min<size_t>(size, m_storage_size - offset);
+
+ // Create head/tail ranges.
+ AccessRange head_range = {};
+ AccessRange tail_range = {};
+ bool is_tail_set = false;
+
+ // Operate to determine the head range.
+ R_TRY(core.OperatePerEntry(
+ offset, 1,
+ [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
+ s64 data_offset, s64 data_read_size) -> Result {
+ // Set the head range.
+ head_range = {
+ .virtual_offset = entry.virt_offset,
+ .virtual_size = virtual_data_size,
+ .physical_size = static_cast<u32>(entry.phys_size),
+ .is_block_alignment_required =
+ CompressionTypeUtility::IsBlockAlignmentRequired(
+ entry.compression_type),
+ };
+
+ // If required, set the tail range.
+ if (static_cast<s64>(offset + read_size) <=
+ entry.virt_offset + virtual_data_size) {
+ tail_range = {
+ .virtual_offset = entry.virt_offset,
+ .virtual_size = virtual_data_size,
+ .physical_size = static_cast<u32>(entry.phys_size),
+ .is_block_alignment_required =
+ CompressionTypeUtility::IsBlockAlignmentRequired(
+ entry.compression_type),
+ };
+ is_tail_set = true;
+ }
+
+ // We only want to determine the head range, so we're not continuous.
+ *out_continuous = false;
+ R_SUCCEED();
+ }));
+
+ // If necessary, determine the tail range.
+ if (!is_tail_set) {
+ R_TRY(core.OperatePerEntry(
+ offset + read_size - 1, 1,
+ [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size,
+ s64 data_offset, s64 data_read_size) -> Result {
+ // Set the tail range.
+ tail_range = {
+ .virtual_offset = entry.virt_offset,
+ .virtual_size = virtual_data_size,
+ .physical_size = static_cast<u32>(entry.phys_size),
+ .is_block_alignment_required =
+ CompressionTypeUtility::IsBlockAlignmentRequired(
+ entry.compression_type),
+ };
+
+ // We only want to determine the tail range, so we're not continuous.
+ *out_continuous = false;
+ R_SUCCEED();
+ }));
+ }
+
+ // Begin performing the accesses.
+ s64 cur_offset = offset;
+ size_t cur_size = read_size;
+ char* cur_dst = static_cast<char*>(buffer);
+
+ // Determine our alignment.
+ const bool head_unaligned = head_range.is_block_alignment_required &&
+ (cur_offset != head_range.virtual_offset ||
+ static_cast<s64>(cur_size) < head_range.virtual_size);
+ const bool tail_unaligned = [&]() -> bool {
+ if (tail_range.is_block_alignment_required) {
+ if (static_cast<s64>(cur_size + cur_offset) ==
+ tail_range.GetEndVirtualOffset()) {
+ return false;
+ } else if (!head_unaligned) {
+ return true;
+ } else {
+ return head_range.GetEndVirtualOffset() <
+ static_cast<s64>(cur_size + cur_offset);
+ }
+ } else {
+ return false;
+ }
+ }();
+
+ // Determine start/end offsets.
+ const s64 start_offset =
+ head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset;
+ const s64 end_offset = tail_range.is_block_alignment_required
+ ? tail_range.GetEndVirtualOffset()
+ : cur_offset + cur_size;
+
+ // Perform the read.
+ bool is_burst_reading = false;
+ R_TRY(core.Read(
+ start_offset, end_offset - start_offset,
+ [&](size_t size_buffer_required,
+ const CompressedStorageCore::ReadImplFunction& read_impl) -> Result {
+ // Determine whether we're burst reading.
+ const AccessRange* unaligned_range = nullptr;
+ if (!is_burst_reading) {
+ // Check whether we're using head, tail, or none as unaligned.
+ if (head_unaligned && head_range.virtual_offset <= cur_offset &&
+ cur_offset < head_range.GetEndVirtualOffset()) {
+ unaligned_range = std::addressof(head_range);
+ } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset &&
+ cur_offset < tail_range.GetEndVirtualOffset()) {
+ unaligned_range = std::addressof(tail_range);
+ } else {
+ is_burst_reading = true;
+ }
+ }
+ ASSERT((is_burst_reading ^ (unaligned_range != nullptr)));
+
+ // Perform reading by burst, or not.
+ if (is_burst_reading) {
+ // Check that the access is valid for burst reading.
+ ASSERT(size_buffer_required <= cur_size);
+
+ // Perform the read.
+ Result rc = read_impl(cur_dst, size_buffer_required);
+ if (R_FAILED(rc)) {
+ R_THROW(rc);
+ }
+
+ // Advance.
+ cur_dst += size_buffer_required;
+ cur_offset += size_buffer_required;
+ cur_size -= size_buffer_required;
+
+ // Determine whether we're going to continue burst reading.
+ const s64 offset_aligned =
+ tail_unaligned ? tail_range.virtual_offset : end_offset;
+ ASSERT(cur_offset <= offset_aligned);
+
+ if (offset_aligned <= cur_offset) {
+ is_burst_reading = false;
+ }
+ } else {
+ // We're not burst reading, so we have some unaligned range.
+ ASSERT(unaligned_range != nullptr);
+
+ // Check that the size is correct.
+ ASSERT(size_buffer_required ==
+ static_cast<size_t>(unaligned_range->virtual_size));
+
+ // Get a pooled buffer for our read.
+ PooledBuffer pooled_buffer;
+ pooled_buffer.Allocate(size_buffer_required, size_buffer_required);
+
+ // Perform read.
+ Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required);
+ if (R_FAILED(rc)) {
+ R_THROW(rc);
+ }
+
+ // Copy the data we read to the destination.
+ const size_t skip_size = cur_offset - unaligned_range->virtual_offset;
+ const size_t copy_size = std::min<size_t>(
+ cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset);
+
+ std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size);
+
+ // Advance.
+ cur_dst += copy_size;
+ cur_offset += copy_size;
+ cur_size -= copy_size;
+ }
+
+ R_SUCCEED();
+ }));
+
+ R_SUCCEED();
+ }
+
+ private:
+ s64 m_storage_size = 0;
+ };
+
+public:
+ CompressedStorage() = default;
+ virtual ~CompressedStorage() {
+ this->Finalize();
+ }
+
+ Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage,
+ s32 bktr_entry_count, size_t block_size_max,
+ size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor,
+ size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) {
+ // Initialize our core.
+ R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count,
+ block_size_max, continuous_reading_size_max, get_decompressor));
+
+ // Get our core size.
+ s64 core_size = 0;
+ R_TRY(m_core.GetSize(std::addressof(core_size)));
+
+ // Initialize our cache manager.
+ R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries));
+
+ R_SUCCEED();
+ }
+
+ void Finalize() {
+ m_core.Finalize();
+ }
+
+ VirtualFile GetDataStorage() {
+ return m_core.GetDataStorage();
+ }
+
+ Result GetDataStorageSize(s64* out) {
+ R_RETURN(m_core.GetDataStorageSize(out));
+ }
+
+ Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset,
+ s64 size) {
+ R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size));
+ }
+
+ BucketTree& GetEntryTable() {
+ return m_core.GetEntryTable();
+ }
+
+public:
+ virtual size_t GetSize() const override {
+ s64 ret{};
+ m_core.GetSize(&ret);
+ return ret;
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) {
+ return size;
+ } else {
+ return 0;
+ }
+ }
+
+private:
+ mutable CompressedStorageCore m_core;
+ mutable CacheManager m_cache_manager;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_compression_common.h b/src/core/file_sys/fssystem/fssystem_compression_common.h
new file mode 100644
index 000000000..266e0a7e5
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_compression_common.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+enum class CompressionType : u8 {
+ None = 0,
+ Zeros = 1,
+ Two = 2,
+ Lz4 = 3,
+ Unknown = 4,
+};
+
+using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t);
+using GetDecompressorFunction = DecompressorFunction (*)(CompressionType);
+
+constexpr s64 CompressionBlockAlignment = 0x10;
+
+namespace CompressionTypeUtility {
+
+constexpr bool IsBlockAlignmentRequired(CompressionType type) {
+ return type != CompressionType::None && type != CompressionType::Zeros;
+}
+
+constexpr bool IsDataStorageAccessRequired(CompressionType type) {
+ return type != CompressionType::Zeros;
+}
+
+constexpr bool IsRandomAccessible(CompressionType type) {
+ return type == CompressionType::None;
+}
+
+constexpr bool IsUnknownType(CompressionType type) {
+ return type >= CompressionType::Unknown;
+}
+
+} // namespace CompressionTypeUtility
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp
new file mode 100644
index 000000000..ef552cefe
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/lz4_compression.h"
+#include "core/file_sys/fssystem/fssystem_compression_configuration.h"
+
+namespace FileSys {
+
+namespace {
+
+Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) {
+ auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size);
+ R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC);
+ R_SUCCEED();
+}
+
+constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) {
+ switch (type) {
+ case CompressionType::Lz4:
+ return DecompressLz4;
+ default:
+ return nullptr;
+ }
+}
+
+} // namespace
+
+const NcaCompressionConfiguration& GetNcaCompressionConfiguration() {
+ static const NcaCompressionConfiguration configuration = {
+ .get_decompressor = GetNcaDecompressorFunction,
+ };
+
+ return configuration;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.h b/src/core/file_sys/fssystem/fssystem_compression_configuration.h
new file mode 100644
index 000000000..ec9b48e9a
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.h
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+
+namespace FileSys {
+
+const NcaCompressionConfiguration& GetNcaCompressionConfiguration();
+
+}
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp
new file mode 100644
index 000000000..a4f0cde28
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
+#include "core/file_sys/fssystem/fssystem_crypto_configuration.h"
+
+namespace FileSys {
+
+namespace {
+
+void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size,
+ s32 key_type) {
+ if (key_type == static_cast<s32>(KeyType::ZeroKey)) {
+ std::memset(dst_key, 0, dst_key_size);
+ return;
+ }
+
+ if (key_type == static_cast<s32>(KeyType::InvalidKey) ||
+ key_type < static_cast<s32>(KeyType::ZeroKey) ||
+ key_type >= static_cast<s32>(KeyType::NcaExternalKey)) {
+ std::memset(dst_key, 0xFF, dst_key_size);
+ return;
+ }
+
+ const auto& instance = Core::Crypto::KeyManager::Instance();
+
+ if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) ||
+ key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) {
+ const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type;
+ const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header);
+ std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2));
+ return;
+ }
+
+ const s32 key_generation =
+ std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1;
+ const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount;
+
+ Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
+ instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index),
+ Core::Crypto::Mode::ECB);
+ cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size,
+ reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt);
+}
+
+} // namespace
+
+const NcaCryptoConfiguration& GetCryptoConfiguration() {
+ static const NcaCryptoConfiguration configuration = {
+ .header_1_sign_key_moduli{},
+ .header_1_sign_key_public_exponent{},
+ .key_area_encryption_key_source{},
+ .header_encryption_key_source{},
+ .header_encrypted_encryption_keys{},
+ .generate_key = GenerateKey,
+ .verify_sign1{},
+ .is_plaintext_header_available{},
+ .is_available_sw_key{},
+ };
+
+ return configuration;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.h b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h
new file mode 100644
index 000000000..7fd9c5a8d
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+
+namespace FileSys {
+
+const NcaCryptoConfiguration& GetCryptoConfiguration();
+
+}
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
new file mode 100644
index 000000000..4a75b5308
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
@@ -0,0 +1,127 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage()
+ : m_data_size(-1) {
+ for (size_t i = 0; i < MaxLayers - 1; i++) {
+ m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>();
+ }
+}
+
+Result HierarchicalIntegrityVerificationStorage::Initialize(
+ const HierarchicalIntegrityVerificationInformation& info,
+ HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries,
+ s8 buffer_level) {
+ // Validate preconditions.
+ ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount);
+
+ // Set member variables.
+ m_max_layers = info.max_layers;
+
+ // Initialize the top level verification storage.
+ m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage],
+ storage[HierarchicalStorageInformation::Layer1Storage],
+ static_cast<s64>(1) << info.info[0].block_order, HashSize,
+ false);
+
+ // Ensure we don't leak state if further initialization goes wrong.
+ ON_RESULT_FAILURE {
+ m_verify_storages[0]->Finalize();
+ m_data_size = -1;
+ };
+
+ // Initialize the top level buffer storage.
+ m_buffer_storages[0] = m_verify_storages[0];
+ R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Prepare to initialize the level storages.
+ s32 level = 0;
+
+ // Ensure we don't leak state if further initialization goes wrong.
+ ON_RESULT_FAILURE_2 {
+ m_verify_storages[level + 1]->Finalize();
+ for (; level > 0; --level) {
+ m_buffer_storages[level].reset();
+ m_verify_storages[level]->Finalize();
+ }
+ };
+
+ // Initialize the level storages.
+ for (; level < m_max_layers - 3; ++level) {
+ // Initialize the verification storage.
+ auto buffer_storage =
+ std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
+ m_verify_storages[level + 1]->Initialize(
+ std::move(buffer_storage), storage[level + 2],
+ static_cast<s64>(1) << info.info[level + 1].block_order,
+ static_cast<s64>(1) << info.info[level].block_order, false);
+
+ // Initialize the buffer storage.
+ m_buffer_storages[level + 1] = m_verify_storages[level + 1];
+ R_UNLESS(m_buffer_storages[level + 1] != nullptr,
+ ResultAllocationMemoryFailedAllocateShared);
+ }
+
+ // Initialize the final level storage.
+ {
+ // Initialize the verification storage.
+ auto buffer_storage =
+ std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0);
+ m_verify_storages[level + 1]->Initialize(
+ std::move(buffer_storage), storage[level + 2],
+ static_cast<s64>(1) << info.info[level + 1].block_order,
+ static_cast<s64>(1) << info.info[level].block_order, true);
+
+ // Initialize the buffer storage.
+ m_buffer_storages[level + 1] = m_verify_storages[level + 1];
+ R_UNLESS(m_buffer_storages[level + 1] != nullptr,
+ ResultAllocationMemoryFailedAllocateShared);
+ }
+
+ // Set the data size.
+ m_data_size = info.info[level + 1].size;
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+void HierarchicalIntegrityVerificationStorage::Finalize() {
+ if (m_data_size >= 0) {
+ m_data_size = 0;
+
+ for (s32 level = m_max_layers - 2; level >= 0; --level) {
+ m_buffer_storages[level].reset();
+ m_verify_storages[level]->Finalize();
+ }
+
+ m_data_size = -1;
+ }
+}
+
+size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size,
+ size_t offset) const {
+ // Validate preconditions.
+ ASSERT(m_data_size >= 0);
+
+ // Succeed if zero-size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ // Read the data.
+ return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset);
+}
+
+size_t HierarchicalIntegrityVerificationStorage::GetSize() const {
+ return m_data_size;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
new file mode 100644
index 000000000..5cf697efe
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
@@ -0,0 +1,164 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/alignment.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fs_types.h"
+#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
+#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+struct HierarchicalIntegrityVerificationLevelInformation {
+ Int64 offset;
+ Int64 size;
+ s32 block_order;
+ std::array<u8, 4> reserved;
+};
+static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>);
+static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18);
+static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4);
+
+struct HierarchicalIntegrityVerificationInformation {
+ u32 max_layers;
+ std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info;
+ HashSalt seed;
+
+ s64 GetLayeredHashSize() const {
+ return this->info[this->max_layers - 2].offset;
+ }
+
+ s64 GetDataOffset() const {
+ return this->info[this->max_layers - 2].offset;
+ }
+
+ s64 GetDataSize() const {
+ return this->info[this->max_layers - 2].size;
+ }
+};
+static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>);
+
+struct HierarchicalIntegrityVerificationMetaInformation {
+ u32 magic;
+ u32 version;
+ u32 master_hash_size;
+ HierarchicalIntegrityVerificationInformation level_hash_info;
+};
+static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>);
+
+struct HierarchicalIntegrityVerificationSizeSet {
+ s64 control_size;
+ s64 master_hash_size;
+ std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes;
+};
+static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>);
+
+class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage);
+ YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage);
+
+public:
+ using GenerateRandomFunction = void (*)(void* dst, size_t size);
+
+ class HierarchicalStorageInformation {
+ public:
+ enum {
+ MasterStorage = 0,
+ Layer1Storage = 1,
+ Layer2Storage = 2,
+ Layer3Storage = 3,
+ Layer4Storage = 4,
+ Layer5Storage = 5,
+ DataStorage = 6,
+ };
+
+ private:
+ std::array<VirtualFile, DataStorage + 1> m_storages;
+
+ public:
+ void SetMasterHashStorage(VirtualFile s) {
+ m_storages[MasterStorage] = s;
+ }
+ void SetLayer1HashStorage(VirtualFile s) {
+ m_storages[Layer1Storage] = s;
+ }
+ void SetLayer2HashStorage(VirtualFile s) {
+ m_storages[Layer2Storage] = s;
+ }
+ void SetLayer3HashStorage(VirtualFile s) {
+ m_storages[Layer3Storage] = s;
+ }
+ void SetLayer4HashStorage(VirtualFile s) {
+ m_storages[Layer4Storage] = s;
+ }
+ void SetLayer5HashStorage(VirtualFile s) {
+ m_storages[Layer5Storage] = s;
+ }
+ void SetDataStorage(VirtualFile s) {
+ m_storages[DataStorage] = s;
+ }
+
+ VirtualFile& operator[](s32 index) {
+ ASSERT(MasterStorage <= index && index <= DataStorage);
+ return m_storages[index];
+ }
+ };
+
+public:
+ HierarchicalIntegrityVerificationStorage();
+ virtual ~HierarchicalIntegrityVerificationStorage() override {
+ this->Finalize();
+ }
+
+ Result Initialize(const HierarchicalIntegrityVerificationInformation& info,
+ HierarchicalStorageInformation storage, int max_data_cache_entries,
+ int max_hash_cache_entries, s8 buffer_level);
+ void Finalize();
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+ virtual size_t GetSize() const override;
+
+ bool IsInitialized() const {
+ return m_data_size >= 0;
+ }
+
+ s64 GetL1HashVerificationBlockSize() const {
+ return m_verify_storages[m_max_layers - 2]->GetBlockSize();
+ }
+
+ VirtualFile GetL1HashStorage() {
+ return std::make_shared<OffsetVfsFile>(
+ m_buffer_storages[m_max_layers - 3],
+ Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0);
+ }
+
+public:
+ static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) {
+ return static_cast<s8>(16 + max_layers - 2);
+ }
+
+protected:
+ static constexpr s64 HashSize = 256 / 8;
+ static constexpr size_t MaxLayers = IntegrityMaxLayerCount;
+
+private:
+ static GenerateRandomFunction s_generate_random;
+
+ static void SetGenerateRandomFunction(GenerateRandomFunction func) {
+ s_generate_random = func;
+ }
+
+private:
+ friend struct HierarchicalIntegrityVerificationMetaInformation;
+
+private:
+ std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages;
+ std::array<VirtualFile, MaxLayers - 1> m_buffer_storages;
+ s64 m_data_size;
+ s32 m_max_layers;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
new file mode 100644
index 000000000..caea0b8f8
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/scope_exit.h"
+#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h"
+
+namespace FileSys {
+
+namespace {
+
+s32 Log2(s32 value) {
+ ASSERT(value > 0);
+ ASSERT(Common::IsPowerOfTwo(value));
+
+ s32 log = 0;
+ while ((value >>= 1) > 0) {
+ ++log;
+ }
+ return log;
+}
+
+} // namespace
+
+Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count,
+ size_t htbs, void* hash_buf, size_t hash_buf_size) {
+ // Validate preconditions.
+ ASSERT(layer_count == LayerCount);
+ ASSERT(Common::IsPowerOfTwo(htbs));
+ ASSERT(hash_buf != nullptr);
+
+ // Set size tracking members.
+ m_hash_target_block_size = static_cast<s32>(htbs);
+ m_log_size_ratio = Log2(m_hash_target_block_size / HashSize);
+
+ // Get the base storage size.
+ m_base_storage_size = base_storages[2]->GetSize();
+ {
+ auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; });
+ R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize)
+ << m_log_size_ratio << m_log_size_ratio,
+ ResultHierarchicalSha256BaseStorageTooLarge);
+ size_guard.Cancel();
+ }
+
+ // Set hash buffer tracking members.
+ m_base_storage = base_storages[2];
+ m_hash_buffer = static_cast<char*>(hash_buf);
+ m_hash_buffer_size = hash_buf_size;
+
+ // Read the master hash.
+ std::array<u8, HashSize> master_hash{};
+ base_storages[0]->ReadObject(std::addressof(master_hash));
+
+ // Read and validate the data being hashed.
+ s64 hash_storage_size = base_storages[1]->GetSize();
+ ASSERT(Common::IsAligned(hash_storage_size, HashSize));
+ ASSERT(hash_storage_size <= m_hash_target_block_size);
+ ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size));
+
+ base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer),
+ static_cast<size_t>(hash_storage_size), 0);
+
+ R_SUCCEED();
+}
+
+size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Succeed if zero-size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate that we have a buffer to read into.
+ ASSERT(buffer != nullptr);
+
+ // Read the data.
+ return m_base_storage->Read(buffer, size, offset);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
new file mode 100644
index 000000000..18df400af
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+class HierarchicalSha256Storage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(HierarchicalSha256Storage);
+ YUZU_NON_MOVEABLE(HierarchicalSha256Storage);
+
+public:
+ static constexpr s32 LayerCount = 3;
+ static constexpr size_t HashSize = 256 / 8;
+
+public:
+ HierarchicalSha256Storage() : m_mutex() {}
+
+ Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf,
+ size_t hash_buf_size);
+
+ virtual size_t GetSize() const override {
+ return m_base_storage->GetSize();
+ }
+
+ virtual size_t Read(u8* buffer, size_t length, size_t offset) const override;
+
+private:
+ VirtualFile m_base_storage;
+ s64 m_base_storage_size;
+ char* m_hash_buffer;
+ size_t m_hash_buffer_size;
+ s32 m_hash_target_block_size;
+ s32 m_log_size_ratio;
+ std::mutex m_mutex;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
new file mode 100644
index 000000000..7544e70b2
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
+
+namespace FileSys {
+
+Result IndirectStorage::Initialize(VirtualFile table_storage) {
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ table_storage->ReadObject(std::addressof(header));
+ R_TRY(header.Verify());
+
+ // Determine extents.
+ const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
+ const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
+ const auto node_storage_offset = QueryHeaderStorageSize();
+ const auto entry_storage_offset = node_storage_offset + node_storage_size;
+
+ // Initialize.
+ R_RETURN(this->Initialize(
+ std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
+ std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
+ header.entry_count));
+}
+
+void IndirectStorage::Finalize() {
+ if (this->IsInitialized()) {
+ m_table.Finalize();
+ for (auto i = 0; i < StorageCount; i++) {
+ m_data_storage[i] = VirtualFile();
+ }
+ }
+}
+
+Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count,
+ s64 offset, s64 size) {
+ // Validate pre-conditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Clear the out count.
+ R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
+ *out_entry_count = 0;
+
+ // Succeed if there's no range.
+ R_SUCCEED_IF(size == 0);
+
+ // If we have an output array, we need it to be non-null.
+ R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);
+
+ // Check that our range is valid.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultInvalidIndirectEntryOffset);
+ }
+
+ // Prepare to loop over entries.
+ const auto end_offset = offset + static_cast<s64>(size);
+ s32 count = 0;
+
+ auto cur_entry = *visitor.Get<Entry>();
+ while (cur_entry.GetVirtualOffset() < end_offset) {
+ // Try to write the entry to the out list.
+ if (entry_count != 0) {
+ if (count >= entry_count) {
+ break;
+ }
+ std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
+ }
+
+ count++;
+
+ // Advance.
+ if (visitor.CanMoveNext()) {
+ R_TRY(visitor.MoveNext());
+ cur_entry = *visitor.Get<Entry>();
+ } else {
+ break;
+ }
+ }
+
+ // Write the output count.
+ *out_entry_count = count;
+ R_SUCCEED();
+}
+
+size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Validate pre-conditions.
+ ASSERT(this->IsInitialized());
+ ASSERT(buffer != nullptr);
+
+ // Succeed if there's nothing to read.
+ if (size == 0) {
+ return 0;
+ }
+
+ const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>(
+ offset, size,
+ [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
+ storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
+ static_cast<size_t>(cur_size), data_offset);
+ R_SUCCEED();
+ });
+
+ return size;
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
new file mode 100644
index 000000000..7854335bf
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
@@ -0,0 +1,294 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
+#include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h"
+#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+class IndirectStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(IndirectStorage);
+ YUZU_NON_MOVEABLE(IndirectStorage);
+
+public:
+ static constexpr s32 StorageCount = 2;
+ static constexpr size_t NodeSize = 16_KiB;
+
+ struct Entry {
+ std::array<u8, sizeof(s64)> virt_offset;
+ std::array<u8, sizeof(s64)> phys_offset;
+ s32 storage_index;
+
+ void SetVirtualOffset(const s64& ofs) {
+ std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64));
+ }
+
+ s64 GetVirtualOffset() const {
+ s64 offset;
+ std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64));
+ return offset;
+ }
+
+ void SetPhysicalOffset(const s64& ofs) {
+ std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64));
+ }
+
+ s64 GetPhysicalOffset() const {
+ s64 offset;
+ std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64));
+ return offset;
+ }
+ };
+ static_assert(std::is_trivial_v<Entry>);
+ static_assert(sizeof(Entry) == 0x14);
+
+ struct EntryData {
+ s64 virt_offset;
+ s64 phys_offset;
+ s32 storage_index;
+
+ void Set(const Entry& entry) {
+ this->virt_offset = entry.GetVirtualOffset();
+ this->phys_offset = entry.GetPhysicalOffset();
+ this->storage_index = entry.storage_index;
+ }
+ };
+ static_assert(std::is_trivial_v<EntryData>);
+
+public:
+ IndirectStorage() : m_table(), m_data_storage() {}
+ virtual ~IndirectStorage() {
+ this->Finalize();
+ }
+
+ Result Initialize(VirtualFile table_storage);
+ void Finalize();
+
+ bool IsInitialized() const {
+ return m_table.IsInitialized();
+ }
+
+ Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) {
+ R_RETURN(
+ m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
+ }
+
+ void SetStorage(s32 idx, VirtualFile storage) {
+ ASSERT(0 <= idx && idx < StorageCount);
+ m_data_storage[idx] = storage;
+ }
+
+ template <typename T>
+ void SetStorage(s32 idx, T storage, s64 offset, s64 size) {
+ ASSERT(0 <= idx && idx < StorageCount);
+ m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset);
+ }
+
+ Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset,
+ s64 size);
+
+ virtual size_t GetSize() const override {
+ BucketTree::Offsets offsets{};
+ m_table.GetOffsets(std::addressof(offsets));
+
+ return offsets.end_offset;
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+
+public:
+ static constexpr s64 QueryHeaderStorageSize() {
+ return BucketTree::QueryHeaderStorageSize();
+ }
+
+ static constexpr s64 QueryNodeStorageSize(s32 entry_count) {
+ return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+ static constexpr s64 QueryEntryStorageSize(s32 entry_count) {
+ return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
+ }
+
+protected:
+ BucketTree& GetEntryTable() {
+ return m_table;
+ }
+
+ VirtualFile& GetDataStorage(s32 index) {
+ ASSERT(0 <= index && index < StorageCount);
+ return m_data_storage[index];
+ }
+
+ template <bool ContinuousCheck, bool RangeCheck, typename F>
+ Result OperatePerEntry(s64 offset, s64 size, F func);
+
+private:
+ struct ContinuousReadingEntry {
+ static constexpr size_t FragmentSizeMax = 4_KiB;
+
+ IndirectStorage::Entry entry;
+
+ s64 GetVirtualOffset() const {
+ return this->entry.GetVirtualOffset();
+ }
+
+ s64 GetPhysicalOffset() const {
+ return this->entry.GetPhysicalOffset();
+ }
+
+ bool IsFragment() const {
+ return this->entry.storage_index != 0;
+ }
+ };
+ static_assert(std::is_trivial_v<ContinuousReadingEntry>);
+
+private:
+ mutable BucketTree m_table;
+ std::array<VirtualFile, StorageCount> m_data_storage;
+};
+
+template <bool ContinuousCheck, bool RangeCheck, typename F>
+Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
+ // Validate preconditions.
+ ASSERT(offset >= 0);
+ ASSERT(size >= 0);
+ ASSERT(this->IsInitialized());
+
+ // Succeed if there's nothing to operate on.
+ R_SUCCEED_IF(size == 0);
+
+ // Get the table offsets.
+ BucketTree::Offsets table_offsets;
+ R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
+
+ // Validate arguments.
+ R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);
+
+ // Find the offset in our tree.
+ BucketTree::Visitor visitor;
+ R_TRY(m_table.Find(std::addressof(visitor), offset));
+ {
+ const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
+ R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
+ ResultInvalidIndirectEntryOffset);
+ }
+
+ // Prepare to operate in chunks.
+ auto cur_offset = offset;
+ const auto end_offset = offset + static_cast<s64>(size);
+ BucketTree::ContinuousReadingInfo cr_info;
+
+ while (cur_offset < end_offset) {
+ // Get the current entry.
+ const auto cur_entry = *visitor.Get<Entry>();
+
+ // Get and validate the entry's offset.
+ const auto cur_entry_offset = cur_entry.GetVirtualOffset();
+ R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset);
+
+ // Validate the storage index.
+ R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount,
+ ResultInvalidIndirectEntryStorageIndex);
+
+ // If we need to check the continuous info, do so.
+ if constexpr (ContinuousCheck) {
+ // Scan, if we need to.
+ if (cr_info.CheckNeedScan()) {
+ R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>(
+ std::addressof(cr_info), cur_offset,
+ static_cast<size_t>(end_offset - cur_offset)));
+ }
+
+ // Process a base storage entry.
+ if (cr_info.CanDo()) {
+ // Ensure that we can process.
+ R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex);
+
+ // Ensure that we remain within range.
+ const auto data_offset = cur_offset - cur_entry_offset;
+ const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
+ const auto cur_size = static_cast<s64>(cr_info.GetReadSize());
+
+ // If we should, verify the range.
+ if constexpr (RangeCheck) {
+ // Get the current data storage's size.
+ s64 cur_data_storage_size = m_data_storage[0]->GetSize();
+
+ R_UNLESS(0 <= cur_entry_phys_offset &&
+ cur_entry_phys_offset <= cur_data_storage_size,
+ ResultInvalidIndirectEntryOffset);
+ R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <=
+ cur_data_storage_size,
+ ResultInvalidIndirectStorageSize);
+ }
+
+ // Operate.
+ R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset,
+ cur_size));
+
+ // Mark as done.
+ cr_info.Done();
+ }
+ }
+
+ // Get and validate the next entry offset.
+ s64 next_entry_offset;
+ if (visitor.CanMoveNext()) {
+ R_TRY(visitor.MoveNext());
+ next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
+ R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset);
+ } else {
+ next_entry_offset = table_offsets.end_offset;
+ }
+ R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset);
+
+ // Get the offset of the entry in the data we read.
+ const auto data_offset = cur_offset - cur_entry_offset;
+ const auto data_size = (next_entry_offset - cur_entry_offset);
+ ASSERT(data_size > 0);
+
+ // Determine how much is left.
+ const auto remaining_size = end_offset - cur_offset;
+ const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset);
+ ASSERT(cur_size <= size);
+
+ // Operate, if we need to.
+ bool needs_operate;
+ if constexpr (!ContinuousCheck) {
+ needs_operate = true;
+ } else {
+ needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0;
+ }
+
+ if (needs_operate) {
+ const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
+
+ if constexpr (RangeCheck) {
+ // Get the current data storage's size.
+ s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
+
+ // Ensure that we remain within range.
+ R_UNLESS(0 <= cur_entry_phys_offset &&
+ cur_entry_phys_offset <= cur_data_storage_size,
+ ResultIndirectStorageCorrupted);
+ R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size,
+ ResultIndirectStorageCorrupted);
+ }
+
+ R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset,
+ cur_offset, cur_size));
+ }
+
+ cur_offset += cur_size;
+ }
+
+ R_SUCCEED();
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp
new file mode 100644
index 000000000..2c3da230c
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h"
+
+namespace FileSys {
+
+Result IntegrityRomFsStorage::Initialize(
+ HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
+ HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
+ int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) {
+ // Set master hash.
+ m_master_hash = master_hash;
+ m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value);
+ R_UNLESS(m_master_hash_storage != nullptr,
+ ResultAllocationMemoryFailedInIntegrityRomFsStorageA);
+
+ // Set the master hash storage.
+ storage_info[0] = m_master_hash_storage;
+
+ // Initialize our integrity storage.
+ R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries,
+ max_hash_cache_entries, buffer_level));
+}
+
+void IntegrityRomFsStorage::Finalize() {
+ m_integrity_storage.Finalize();
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h
new file mode 100644
index 000000000..5f8512b2a
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
+#include "core/file_sys/fssystem/fssystem_nca_header.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys {
+
+constexpr inline size_t IntegrityLayerCountRomFs = 7;
+constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB;
+
+class IntegrityRomFsStorage : public IReadOnlyStorage {
+public:
+ IntegrityRomFsStorage() {}
+ virtual ~IntegrityRomFsStorage() override {
+ this->Finalize();
+ }
+
+ Result Initialize(
+ HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash,
+ HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info,
+ int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
+ void Finalize();
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ return m_integrity_storage.Read(buffer, size, offset);
+ }
+
+ virtual size_t GetSize() const override {
+ return m_integrity_storage.GetSize();
+ }
+
+private:
+ HierarchicalIntegrityVerificationStorage m_integrity_storage;
+ Hash m_master_hash;
+ std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp
new file mode 100644
index 000000000..2f73abf86
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp
@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
+
+namespace FileSys {
+
+constexpr inline u32 ILog2(u32 val) {
+ ASSERT(val > 0);
+ return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val));
+}
+
+void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
+ s64 upper_layer_verif_block_size, bool is_real_data) {
+ // Validate preconditions.
+ ASSERT(verif_block_size >= HashSize);
+
+ // Set storages.
+ m_hash_storage = hs;
+ m_data_storage = ds;
+
+ // Set verification block sizes.
+ m_verification_block_size = verif_block_size;
+ m_verification_block_order = ILog2(static_cast<u32>(verif_block_size));
+ ASSERT(m_verification_block_size == 1ll << m_verification_block_order);
+
+ // Set upper layer block sizes.
+ upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize);
+ m_upper_layer_verification_block_size = upper_layer_verif_block_size;
+ m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size));
+ ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order);
+
+ // Validate sizes.
+ {
+ s64 hash_size = m_hash_storage->GetSize();
+ s64 data_size = m_data_storage->GetSize();
+ ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size);
+ }
+
+ // Set data.
+ m_is_real_data = is_real_data;
+}
+
+void IntegrityVerificationStorage::Finalize() {
+ m_hash_storage = VirtualFile();
+ m_data_storage = VirtualFile();
+}
+
+size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Succeed if zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ // Validate arguments.
+ ASSERT(buffer != nullptr);
+
+ // Validate the offset.
+ s64 data_size = m_data_storage->GetSize();
+ ASSERT(offset <= static_cast<size_t>(data_size));
+
+ // Validate the access range.
+ ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(
+ offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size)))));
+
+ // Determine the read extents.
+ size_t read_size = size;
+ if (static_cast<s64>(offset + read_size) > data_size) {
+ // Determine the padding sizes.
+ s64 padding_offset = data_size - offset;
+ size_t padding_size = static_cast<size_t>(
+ m_verification_block_size - (padding_offset & (m_verification_block_size - 1)));
+ ASSERT(static_cast<s64>(padding_size) < m_verification_block_size);
+
+ // Clear the padding.
+ std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size);
+
+ // Set the new in-bounds size.
+ read_size = static_cast<size_t>(data_size - offset);
+ }
+
+ // Perform the read.
+ return m_data_storage->Read(buffer, read_size, offset);
+}
+
+size_t IntegrityVerificationStorage::GetSize() const {
+ return m_data_storage->GetSize();
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h
new file mode 100644
index 000000000..09f76799d
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <optional>
+
+#include "core/file_sys/fssystem/fs_i_storage.h"
+#include "core/file_sys/fssystem/fs_types.h"
+
+namespace FileSys {
+
+class IntegrityVerificationStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(IntegrityVerificationStorage);
+ YUZU_NON_MOVEABLE(IntegrityVerificationStorage);
+
+public:
+ static constexpr s64 HashSize = 256 / 8;
+
+ struct BlockHash {
+ std::array<u8, HashSize> hash;
+ };
+ static_assert(std::is_trivial_v<BlockHash>);
+
+public:
+ IntegrityVerificationStorage()
+ : m_verification_block_size(0), m_verification_block_order(0),
+ m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {}
+ virtual ~IntegrityVerificationStorage() override {
+ this->Finalize();
+ }
+
+ void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size,
+ s64 upper_layer_verif_block_size, bool is_real_data);
+ void Finalize();
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+ virtual size_t GetSize() const override;
+
+ s64 GetBlockSize() const {
+ return m_verification_block_size;
+ }
+
+private:
+ static void SetValidationBit(BlockHash* hash) {
+ ASSERT(hash != nullptr);
+ hash->hash[HashSize - 1] |= 0x80;
+ }
+
+ static bool IsValidationBit(const BlockHash* hash) {
+ ASSERT(hash != nullptr);
+ return (hash->hash[HashSize - 1] & 0x80) != 0;
+ }
+
+private:
+ VirtualFile m_hash_storage;
+ VirtualFile m_data_storage;
+ s64 m_verification_block_size;
+ s64 m_verification_block_order;
+ s64 m_upper_layer_verification_block_size;
+ s64 m_upper_layer_verification_block_order;
+ bool m_is_real_data;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h
new file mode 100644
index 000000000..c07a127fb
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fs_i_storage.h"
+
+namespace FileSys {
+
+class MemoryResourceBufferHoldStorage : public IStorage {
+ YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage);
+ YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage);
+
+public:
+ MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size)
+ : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)),
+ m_buffer_size(buffer_size) {}
+
+ virtual ~MemoryResourceBufferHoldStorage() {
+ // If we have a buffer, deallocate it.
+ if (m_buffer != nullptr) {
+ ::operator delete(m_buffer);
+ }
+ }
+
+ bool IsValid() const {
+ return m_buffer != nullptr;
+ }
+ void* GetBuffer() const {
+ return m_buffer;
+ }
+
+public:
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Check pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ return m_storage->Read(buffer, size, offset);
+ }
+
+ virtual size_t GetSize() const override {
+ // Check pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ return m_storage->GetSize();
+ }
+
+ virtual size_t Write(const u8* buffer, size_t size, size_t offset) override {
+ // Check pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ return m_storage->Write(buffer, size, offset);
+ }
+
+private:
+ VirtualFile m_storage;
+ void* m_buffer;
+ size_t m_buffer_size;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
new file mode 100644
index 000000000..0f5432203
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
@@ -0,0 +1,1351 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
+#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
+#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
+#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
+#include "core/file_sys/fssystem/fssystem_compressed_storage.h"
+#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
+#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h"
+#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
+#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h"
+#include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h"
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+#include "core/file_sys/fssystem/fssystem_sparse_storage.h"
+#include "core/file_sys/fssystem/fssystem_switch_storage.h"
+#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys {
+
+namespace {
+
+constexpr inline s32 IntegrityDataCacheCount = 24;
+constexpr inline s32 IntegrityHashCacheCount = 8;
+
+constexpr inline s32 IntegrityDataCacheCountForMeta = 16;
+constexpr inline s32 IntegrityHashCacheCountForMeta = 2;
+
+class SharedNcaBodyStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(SharedNcaBodyStorage);
+ YUZU_NON_MOVEABLE(SharedNcaBodyStorage);
+
+private:
+ VirtualFile m_storage;
+ std::shared_ptr<NcaReader> m_nca_reader;
+
+public:
+ SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r)
+ : m_storage(std::move(s)), m_nca_reader(std::move(r)) {}
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Validate pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ // Read from the base storage.
+ return m_storage->Read(buffer, size, offset);
+ }
+
+ virtual size_t GetSize() const override {
+ // Validate pre-conditions.
+ ASSERT(m_storage != nullptr);
+
+ return m_storage->GetSize();
+ }
+};
+
+inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) {
+ return static_cast<s64>(reader.GetFsOffset(fs_index));
+}
+
+inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) {
+ return static_cast<s64>(reader.GetFsEndOffset(fs_index));
+}
+
+using Sha256DataRegion = NcaFsHeader::Region;
+using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo;
+using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation;
+
+} // namespace
+
+Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out,
+ NcaFsHeaderReader* out_header_reader,
+ s32 fs_index, StorageContext* ctx) {
+ // Open storage.
+ R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx));
+}
+
+Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader,
+ s32 fs_index, StorageContext* ctx) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(out_header_reader != nullptr);
+ ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax);
+
+ // Validate the fs index.
+ R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound);
+
+ // Initialize our header reader for the fs index.
+ R_TRY(out_header_reader->Initialize(*m_reader, fs_index));
+
+ // Declare the storage we're opening.
+ VirtualFile storage;
+
+ // Process sparse layer.
+ s64 fs_data_offset = 0;
+ if (out_header_reader->ExistsSparseLayer()) {
+ // Get the sparse info.
+ const auto& sparse_info = out_header_reader->GetSparseInfo();
+
+ // Create based on whether we have a meta hash layer.
+ if (out_header_reader->ExistsSparseMetaHashLayer()) {
+ // Create the sparse storage with verification.
+ R_TRY(this->CreateSparseStorageWithVerification(
+ std::addressof(storage), std::addressof(fs_data_offset),
+ ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index,
+ out_header_reader->GetAesCtrUpperIv(), sparse_info,
+ out_header_reader->GetSparseMetaDataHashDataInfo(),
+ out_header_reader->GetSparseMetaHashType()));
+ } else {
+ // Create the sparse storage.
+ R_TRY(this->CreateSparseStorage(
+ std::addressof(storage), std::addressof(fs_data_offset),
+ ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
+ fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info));
+ }
+ } else {
+ // Get the data offsets.
+ fs_data_offset = GetFsOffset(*m_reader, fs_index);
+ const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index);
+
+ // Validate that we're within range.
+ const auto data_size = fs_end_offset - fs_data_offset;
+ R_UNLESS(data_size > 0, ResultInvalidNcaHeader);
+
+ // Create the body substorage.
+ R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size));
+
+ // Potentially save the body substorage to our context.
+ if (ctx != nullptr) {
+ ctx->body_substorage = storage;
+ }
+ }
+
+ // Process patch layer.
+ const auto& patch_info = out_header_reader->GetPatchInfo();
+ VirtualFile patch_meta_aes_ctr_ex_meta_storage;
+ VirtualFile patch_meta_indirect_meta_storage;
+ if (out_header_reader->ExistsPatchMetaHashLayer()) {
+ // Check the meta hash type.
+ R_UNLESS(out_header_reader->GetPatchMetaHashType() ==
+ NcaFsHeader::MetaDataHashType::HierarchicalIntegrity,
+ ResultRomNcaInvalidPatchMetaDataHashType);
+
+ // Create the patch meta storage.
+ R_TRY(this->CreatePatchMetaStorage(
+ std::addressof(patch_meta_aes_ctr_ex_meta_storage),
+ std::addressof(patch_meta_indirect_meta_storage),
+ ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage,
+ fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info,
+ out_header_reader->GetPatchMetaDataHashDataInfo()));
+ }
+
+ if (patch_info.HasAesCtrExTable()) {
+ // Check the encryption type.
+ ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None ||
+ out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx ||
+ out_header_reader->GetEncryptionType() ==
+ NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
+
+ // Create the ex meta storage.
+ VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage;
+ if (aes_ctr_ex_storage_meta_storage == nullptr) {
+ // If we don't have a meta storage, we must not have a patch meta hash layer.
+ ASSERT(!out_header_reader->ExistsPatchMetaHashLayer());
+
+ R_TRY(this->CreateAesCtrExStorageMetaStorage(
+ std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset,
+ out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(),
+ patch_info));
+ }
+
+ // Create the ex storage.
+ VirtualFile aes_ctr_ex_storage;
+ R_TRY(this->CreateAesCtrExStorage(
+ std::addressof(aes_ctr_ex_storage),
+ ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage),
+ aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
+ patch_info));
+
+ // Set the base storage as the ex storage.
+ storage = std::move(aes_ctr_ex_storage);
+
+ // Potentially save storages to our context.
+ if (ctx != nullptr) {
+ ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage;
+ ctx->aes_ctr_ex_storage_data_storage = storage;
+ ctx->fs_data_storage = storage;
+ }
+ } else {
+ // Create the appropriate storage for the encryption type.
+ switch (out_header_reader->GetEncryptionType()) {
+ case NcaFsHeader::EncryptionType::None:
+ // If there's no encryption, use the base storage we made previously.
+ break;
+ case NcaFsHeader::EncryptionType::AesXts:
+ R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
+ fs_data_offset));
+ break;
+ case NcaFsHeader::EncryptionType::AesCtr:
+ R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
+ fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
+ AlignmentStorageRequirement::None));
+ break;
+ case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: {
+ // Create the aes ctr storage.
+ VirtualFile aes_ctr_storage;
+ R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage,
+ fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
+ AlignmentStorageRequirement::None));
+
+ // Create region switch storage.
+ R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
+ std::move(storage), std::move(aes_ctr_storage)));
+ } break;
+ default:
+ R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
+ }
+
+ // Potentially save storages to our context.
+ if (ctx != nullptr) {
+ ctx->fs_data_storage = storage;
+ }
+ }
+
+ // Process indirect layer.
+ if (patch_info.HasIndirectTable()) {
+ // Create the indirect meta storage.
+ VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage;
+ if (indirect_storage_meta_storage == nullptr) {
+ // If we don't have a meta storage, we must not have a patch meta hash layer.
+ ASSERT(!out_header_reader->ExistsPatchMetaHashLayer());
+
+ R_TRY(this->CreateIndirectStorageMetaStorage(
+ std::addressof(indirect_storage_meta_storage), storage, patch_info));
+ }
+
+ // Potentially save the indirect meta storage to our context.
+ if (ctx != nullptr) {
+ ctx->indirect_storage_meta_storage = indirect_storage_meta_storage;
+ }
+
+ // Get the original indirectable storage.
+ VirtualFile original_indirectable_storage;
+ if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) {
+ // Create a driver for the original.
+ NcaFileSystemDriver original_driver(m_original_reader);
+
+ // Create a header reader for the original.
+ NcaFsHeaderReader original_header_reader;
+ R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index));
+
+ // Open original indirectable storage.
+ R_TRY(original_driver.OpenIndirectableStorageAsOriginal(
+ std::addressof(original_indirectable_storage),
+ std::addressof(original_header_reader), ctx));
+ } else if (ctx != nullptr && ctx->external_original_storage != nullptr) {
+ // Use the external original storage.
+ original_indirectable_storage = ctx->external_original_storage;
+ } else {
+ // Allocate a dummy memory storage as original storage.
+ original_indirectable_storage = std::make_shared<VectorVfsFile>();
+ R_UNLESS(original_indirectable_storage != nullptr,
+ ResultAllocationMemoryFailedAllocateShared);
+ }
+
+ // Create the indirect storage.
+ VirtualFile indirect_storage;
+ R_TRY(this->CreateIndirectStorage(
+ std::addressof(indirect_storage),
+ ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage),
+ std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage),
+ patch_info));
+
+ // Set storage as the indirect storage.
+ storage = std::move(indirect_storage);
+ }
+
+ // Check if we're sparse or requested to skip the integrity layer.
+ if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) {
+ *out = std::move(storage);
+ R_SUCCEED();
+ }
+
+ // Create the non-raw storage.
+ R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx));
+}
+
+Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out,
+ const NcaFsHeaderReader* header_reader,
+ VirtualFile raw_storage,
+ StorageContext* ctx) {
+ // Initialize storage as raw storage.
+ VirtualFile storage = std::move(raw_storage);
+
+ // Process hash/integrity layer.
+ switch (header_reader->GetHashType()) {
+ case NcaFsHeader::HashType::HierarchicalSha256Hash:
+ R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage),
+ header_reader->GetHashData().hierarchical_sha256_data));
+ break;
+ case NcaFsHeader::HashType::HierarchicalIntegrityHash:
+ R_TRY(this->CreateIntegrityVerificationStorage(
+ std::addressof(storage), std::move(storage),
+ header_reader->GetHashData().integrity_meta_info));
+ break;
+ default:
+ R_THROW(ResultInvalidNcaFsHeaderHashType);
+ }
+
+ // Process compression layer.
+ if (header_reader->ExistsCompressionLayer()) {
+ R_TRY(this->CreateCompressedStorage(
+ std::addressof(storage),
+ ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr,
+ std::move(storage), header_reader->GetCompressionInfo()));
+ }
+
+ // Set output storage.
+ *out = std::move(storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(
+ VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) {
+ // Get the fs index.
+ const auto fs_index = header_reader->GetFsIndex();
+
+ // Declare the storage we're opening.
+ VirtualFile storage;
+
+ // Process sparse layer.
+ s64 fs_data_offset = 0;
+ if (header_reader->ExistsSparseLayer()) {
+ // Get the sparse info.
+ const auto& sparse_info = header_reader->GetSparseInfo();
+
+ // Create based on whether we have a meta hash layer.
+ if (header_reader->ExistsSparseMetaHashLayer()) {
+ // Create the sparse storage with verification.
+ R_TRY(this->CreateSparseStorageWithVerification(
+ std::addressof(storage), std::addressof(fs_data_offset),
+ ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index,
+ header_reader->GetAesCtrUpperIv(), sparse_info,
+ header_reader->GetSparseMetaDataHashDataInfo(),
+ header_reader->GetSparseMetaHashType()));
+ } else {
+ // Create the sparse storage.
+ R_TRY(this->CreateSparseStorage(
+ std::addressof(storage), std::addressof(fs_data_offset),
+ ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr,
+ ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr,
+ fs_index, header_reader->GetAesCtrUpperIv(), sparse_info));
+ }
+ } else {
+ // Get the data offsets.
+ fs_data_offset = GetFsOffset(*m_reader, fs_index);
+ const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index);
+
+ // Validate that we're within range.
+ const auto data_size = fs_end_offset - fs_data_offset;
+ R_UNLESS(data_size > 0, ResultInvalidNcaHeader);
+
+ // Create the body substorage.
+ R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size));
+ }
+
+ // Create the appropriate storage for the encryption type.
+ switch (header_reader->GetEncryptionType()) {
+ case NcaFsHeader::EncryptionType::None:
+ // If there's no encryption, use the base storage we made previously.
+ break;
+ case NcaFsHeader::EncryptionType::AesXts:
+ R_TRY(
+ this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset));
+ break;
+ case NcaFsHeader::EncryptionType::AesCtr:
+ R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset,
+ header_reader->GetAesCtrUpperIv(),
+ AlignmentStorageRequirement::CacheBlockSize));
+ break;
+ default:
+ R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
+ }
+
+ // Set output storage.
+ *out = std::move(storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) {
+ // Create the body storage.
+ auto body_storage =
+ std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader);
+ R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Get the body storage size.
+ s64 body_size = body_storage->GetSize();
+
+ // Check that we're within range.
+ R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB);
+
+ // Create substorage.
+ auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset);
+ R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output storage.
+ *out = std::move(body_substorage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateAesCtrStorage(
+ VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv,
+ AlignmentStorageRequirement alignment_storage_requirement) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Create the iv.
+ std::array<u8, AesCtrStorage::IvSize> iv{};
+ AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset);
+
+ // Create the ctr storage.
+ VirtualFile aes_ctr_storage;
+ if (m_reader->HasExternalDecryptionKey()) {
+ aes_ctr_storage = std::make_shared<AesCtrStorage>(
+ std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize,
+ iv.data(), AesCtrStorage::IvSize);
+ R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+ } else {
+ // Create software decryption storage.
+ auto sw_storage = std::make_shared<AesCtrStorage>(
+ base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr),
+ AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize);
+ R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ aes_ctr_storage = std::move(sw_storage);
+ }
+
+ // Create alignment matching storage.
+ auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>(
+ std::move(aes_ctr_storage));
+ R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the out storage.
+ *out = std::move(aligned_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage,
+ s64 offset) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Create the iv.
+ std::array<u8, AesXtsStorage::IvSize> iv{};
+ AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize);
+
+ // Make the aes xts storage.
+ const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1);
+ const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2);
+ auto xts_storage =
+ std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize,
+ iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize);
+ R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create alignment matching storage.
+ auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>(
+ std::move(xts_storage));
+ R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the out storage.
+ *out = std::move(xts_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out,
+ VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv,
+ const NcaSparseInfo& sparse_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Get the meta extents.
+ const auto meta_offset = sparse_info.bucket.offset;
+ const auto meta_size = sparse_info.bucket.size;
+ R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB);
+
+ // Create the encrypted storage.
+ auto enc_storage =
+ std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset);
+ R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the decrypted storage.
+ VirtualFile decrypted_storage;
+ R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
+ offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv),
+ AlignmentStorageRequirement::None));
+
+ // Create buffered storage.
+ std::vector<u8> meta_data(meta_size);
+ decrypted_storage->Read(meta_data.data(), meta_size, 0);
+
+ auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
+ R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(buffered_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out,
+ VirtualFile base_storage, s64 base_size,
+ VirtualFile meta_storage,
+ const NcaSparseInfo& sparse_info,
+ bool external_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(meta_storage != nullptr);
+
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine storage extents.
+ const auto node_offset = 0;
+ const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count);
+ const auto entry_offset = node_offset + node_size;
+ const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count);
+
+ // Create the sparse storage.
+ auto sparse_storage = std::make_shared<SparseStorage>();
+ R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Sanity check that we can be doing this.
+ ASSERT(header.entry_count != 0);
+
+ // Initialize the sparse storage.
+ R_TRY(sparse_storage->Initialize(
+ std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset),
+ std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset),
+ header.entry_count));
+
+ // If not external, set the data storage.
+ if (!external_info) {
+ sparse_storage->SetDataStorage(
+ std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0));
+ }
+
+ // Set the output.
+ *out = std::move(sparse_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset,
+ std::shared_ptr<SparseStorage>* out_sparse_storage,
+ VirtualFile* out_meta_storage, s32 index,
+ const NcaAesCtrUpperIv& upper_iv,
+ const NcaSparseInfo& sparse_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(out_fs_data_offset != nullptr);
+
+ // Check the sparse info generation.
+ R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader);
+
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the storage extents.
+ const auto fs_offset = GetFsOffset(*m_reader, index);
+ const auto fs_end_offset = GetFsEndOffset(*m_reader, index);
+ const auto fs_size = fs_end_offset - fs_offset;
+
+ // Create the sparse storage.
+ std::shared_ptr<SparseStorage> sparse_storage;
+ if (header.entry_count != 0) {
+ // Create the body substorage.
+ VirtualFile body_substorage;
+ R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage),
+ sparse_info.physical_offset,
+ sparse_info.GetPhysicalSize()));
+
+ // Create the meta storage.
+ VirtualFile meta_storage;
+ R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage,
+ sparse_info.physical_offset, upper_iv,
+ sparse_info));
+
+ // Potentially set the output meta storage.
+ if (out_meta_storage != nullptr) {
+ *out_meta_storage = meta_storage;
+ }
+
+ // Create the sparse storage.
+ R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage,
+ sparse_info.GetPhysicalSize(), std::move(meta_storage),
+ sparse_info, false));
+ } else {
+ // If there are no entries, there's nothing to actually do.
+ sparse_storage = std::make_shared<SparseStorage>();
+ R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ sparse_storage->Initialize(fs_size);
+ }
+
+ // Potentially set the output sparse storage.
+ if (out_sparse_storage != nullptr) {
+ *out_sparse_storage = sparse_storage;
+ }
+
+ // Set the output fs data offset.
+ *out_fs_data_offset = fs_offset;
+
+ // Set the output storage.
+ *out = std::move(sparse_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification(
+ VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Get the meta extents.
+ const auto meta_offset = sparse_info.bucket.offset;
+ const auto meta_size = sparse_info.bucket.size;
+ R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB);
+
+ // Get the meta data hash data extents.
+ const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset;
+ const s64 meta_data_hash_data_size =
+ Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize);
+ R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeB);
+
+ // Check that the meta is before the hash data.
+ R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset,
+ ResultRomNcaInvalidSparseMetaDataHashDataOffset);
+
+ // Check that offsets are appropriately aligned.
+ R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize),
+ ResultRomNcaInvalidSparseMetaDataHashDataOffset);
+ R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize),
+ ResultInvalidNcaFsHeader);
+
+ // Create the meta storage.
+ auto enc_storage = std::make_shared<OffsetVfsFile>(
+ std::move(base_storage),
+ meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset);
+ R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the decrypted storage.
+ VirtualFile decrypted_storage;
+ R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
+ offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv),
+ AlignmentStorageRequirement::None));
+
+ // Create the verification storage.
+ VirtualFile integrity_storage;
+ Result rc = this->CreateIntegrityVerificationStorageForMeta(
+ std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage),
+ meta_offset, meta_data_hash_data_info);
+ if (rc == ResultInvalidNcaMetaDataHashDataSize) {
+ R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize);
+ }
+ if (rc == ResultInvalidNcaMetaDataHashDataHash) {
+ R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash);
+ }
+ R_TRY(rc);
+
+ // Create the meta storage.
+ auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(meta_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSparseStorageWithVerification(
+ VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage,
+ VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index,
+ const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info,
+ NcaFsHeader::MetaDataHashType meta_data_hash_type) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(out_fs_data_offset != nullptr);
+
+ // Check the sparse info generation.
+ R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader);
+
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the storage extents.
+ const auto fs_offset = GetFsOffset(*m_reader, index);
+ const auto fs_end_offset = GetFsEndOffset(*m_reader, index);
+ const auto fs_size = fs_end_offset - fs_offset;
+
+ // Create the sparse storage.
+ std::shared_ptr<SparseStorage> sparse_storage;
+ if (header.entry_count != 0) {
+ // Create the body substorage.
+ VirtualFile body_substorage;
+ R_TRY(this->CreateBodySubStorage(
+ std::addressof(body_substorage), sparse_info.physical_offset,
+ Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) +
+ static_cast<s64>(meta_data_hash_data_info.size),
+ NcaHeader::CtrBlockSize)));
+
+ // Check the meta data hash type.
+ R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity,
+ ResultRomNcaInvalidSparseMetaDataHashType);
+
+ // Create the meta storage.
+ VirtualFile meta_storage;
+ R_TRY(this->CreateSparseStorageMetaStorageWithVerification(
+ std::addressof(meta_storage), out_layer_info_storage, body_substorage,
+ sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info));
+
+ // Potentially set the output meta storage.
+ if (out_meta_storage != nullptr) {
+ *out_meta_storage = meta_storage;
+ }
+
+ // Create the sparse storage.
+ R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage,
+ sparse_info.GetPhysicalSize(), std::move(meta_storage),
+ sparse_info, false));
+ } else {
+ // If there are no entries, there's nothing to actually do.
+ sparse_storage = std::make_shared<SparseStorage>();
+ R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ sparse_storage->Initialize(fs_size);
+ }
+
+ // Potentially set the output sparse storage.
+ if (out_sparse_storage != nullptr) {
+ *out_sparse_storage = sparse_storage;
+ }
+
+ // Set the output fs data offset.
+ *out_fs_data_offset = fs_offset;
+
+ // Set the output storage.
+ *out = std::move(sparse_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(
+ VirtualFile* out, VirtualFile base_storage, s64 offset,
+ NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv,
+ const NcaPatchInfo& patch_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(encryption_type == NcaFsHeader::EncryptionType::None ||
+ encryption_type == NcaFsHeader::EncryptionType::AesCtrEx ||
+ encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
+ ASSERT(patch_info.HasAesCtrExTable());
+
+ // Validate patch info extents.
+ R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize);
+ R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize);
+ R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset,
+ ResultInvalidNcaPatchInfoAesCtrExOffset);
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Get and validate the meta extents.
+ const s64 meta_offset = patch_info.aes_ctr_ex_offset;
+ const s64 meta_size =
+ Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize);
+ R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB);
+
+ // Create the encrypted storage.
+ auto enc_storage =
+ std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset);
+ R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the decrypted storage.
+ VirtualFile decrypted_storage;
+ if (encryption_type != NcaFsHeader::EncryptionType::None) {
+ R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
+ offset + meta_offset, upper_iv,
+ AlignmentStorageRequirement::None));
+ } else {
+ // If encryption type is none, don't do any decryption.
+ decrypted_storage = std::move(enc_storage);
+ }
+
+ // Create meta storage.
+ auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create buffered storage.
+ std::vector<u8> meta_data(meta_size);
+ meta_storage->Read(meta_data.data(), meta_size, 0);
+
+ auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
+ R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(buffered_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateAesCtrExStorage(
+ VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext,
+ VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset,
+ const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) {
+ // Validate pre-conditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(meta_storage != nullptr);
+ ASSERT(patch_info.HasAesCtrExTable());
+
+ // Read the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the bucket extents.
+ const auto entry_count = header.entry_count;
+ const s64 data_offset = 0;
+ const s64 data_size = patch_info.aes_ctr_ex_offset;
+ const s64 node_offset = 0;
+ const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count);
+ const s64 entry_offset = node_offset + node_size;
+ const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count);
+
+ // Create bucket storages.
+ auto data_storage =
+ std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset);
+ auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset);
+ auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset);
+
+ // Get the secure value.
+ const auto secure_value = upper_iv.part.secure_value;
+
+ // Create the aes ctr ex storage.
+ VirtualFile aes_ctr_ex_storage;
+ if (m_reader->HasExternalDecryptionKey()) {
+ // Create the decryptor.
+ std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor;
+ R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor)));
+
+ // Create the aes ctr ex storage.
+ auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>();
+ R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the aes ctr ex storage.
+ R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize,
+ secure_value, counter_offset, data_storage, node_storage,
+ entry_storage, entry_count, std::move(decryptor)));
+
+ // Potentially set the output implementation storage.
+ if (out_ext != nullptr) {
+ *out_ext = impl_storage;
+ }
+
+ // Set the implementation storage.
+ aes_ctr_ex_storage = std::move(impl_storage);
+ } else {
+ // Create the software decryptor.
+ std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor;
+ R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
+
+ // Make the software storage.
+ auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>();
+ R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the software storage.
+ R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr),
+ AesCtrStorage::KeySize, secure_value, counter_offset,
+ data_storage, node_storage, entry_storage, entry_count,
+ std::move(sw_decryptor)));
+
+ // Potentially set the output implementation storage.
+ if (out_ext != nullptr) {
+ *out_ext = sw_storage;
+ }
+
+ // Set the implementation storage.
+ aes_ctr_ex_storage = std::move(sw_storage);
+ }
+
+ // Create an alignment-matching storage.
+ using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>;
+ auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage));
+ R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(aligned_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out,
+ VirtualFile base_storage,
+ const NcaPatchInfo& patch_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(patch_info.HasIndirectTable());
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Check that we're within range.
+ R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeE);
+
+ // Create the meta storage.
+ auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size,
+ patch_info.indirect_offset);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create buffered storage.
+ std::vector<u8> meta_data(patch_info.indirect_size);
+ meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0);
+
+ auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data));
+ R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(buffered_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateIndirectStorage(
+ VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage,
+ VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(meta_storage != nullptr);
+ ASSERT(patch_info.HasIndirectTable());
+
+ // Read the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the storage sizes.
+ const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count);
+ const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count);
+ R_UNLESS(node_size + entry_size <= patch_info.indirect_size,
+ ResultInvalidNcaIndirectStorageOutOfRange);
+
+ // Get the indirect data size.
+ const s64 indirect_data_size = patch_info.indirect_offset;
+ ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize));
+
+ // Create the indirect data storage.
+ auto indirect_data_storage =
+ std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0);
+ R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the indirect storage.
+ auto indirect_storage = std::make_shared<IndirectStorage>();
+ R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the indirect storage.
+ R_TRY(indirect_storage->Initialize(
+ std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0),
+ std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count));
+
+ // Get the original data size.
+ s64 original_data_size = original_data_storage->GetSize();
+
+ // Set the indirect storages.
+ indirect_storage->SetStorage(
+ 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0));
+ indirect_storage->SetStorage(
+ 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0));
+
+ // If necessary, set the output indirect storage.
+ if (out_ind != nullptr) {
+ *out_ind = indirect_storage;
+ }
+
+ // Set the output.
+ *out = std::move(indirect_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreatePatchMetaStorage(
+ VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta,
+ VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
+ // Validate preconditions.
+ ASSERT(out_aes_ctr_ex_meta != nullptr);
+ ASSERT(out_indirect_meta != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(patch_info.HasAesCtrExTable());
+ ASSERT(patch_info.HasIndirectTable());
+ ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize));
+
+ // Validate patch info extents.
+ R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize);
+ R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize);
+ R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset,
+ ResultInvalidNcaPatchInfoAesCtrExOffset);
+ R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <=
+ meta_data_hash_data_info.offset,
+ ResultRomNcaInvalidPatchMetaDataHashDataOffset);
+
+ // Get the base storage size.
+ s64 base_size = base_storage->GetSize();
+
+ // Check that extents remain within range.
+ R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeE);
+ R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeB);
+
+ // Check that metadata hash data extents remain within range.
+ const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset;
+ const s64 meta_data_hash_data_size =
+ Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize);
+ R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size,
+ ResultNcaBaseStorageOutOfRangeB);
+
+ // Create the encrypted storage.
+ auto enc_storage = std::make_shared<OffsetVfsFile>(
+ std::move(base_storage),
+ meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset,
+ patch_info.indirect_offset);
+ R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the decrypted storage.
+ VirtualFile decrypted_storage;
+ R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage),
+ offset + patch_info.indirect_offset, upper_iv,
+ AlignmentStorageRequirement::None));
+
+ // Create the verification storage.
+ VirtualFile integrity_storage;
+ Result rc = this->CreateIntegrityVerificationStorageForMeta(
+ std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage),
+ patch_info.indirect_offset, meta_data_hash_data_info);
+ if (rc == ResultInvalidNcaMetaDataHashDataSize) {
+ R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize);
+ }
+ if (rc == ResultInvalidNcaMetaDataHashDataHash) {
+ R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash);
+ }
+ R_TRY(rc);
+
+ // Create the indirect meta storage.
+ auto indirect_meta_storage =
+ std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size,
+ patch_info.indirect_offset - patch_info.indirect_offset);
+ R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the aes ctr ex meta storage.
+ auto aes_ctr_ex_meta_storage =
+ std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size,
+ patch_info.aes_ctr_ex_offset - patch_info.indirect_offset);
+ R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage);
+ *out_indirect_meta = std::move(indirect_meta_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateSha256Storage(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+
+ // Define storage types.
+ using VerificationStorage = HierarchicalSha256Storage;
+
+ // Validate the hash data.
+ R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size),
+ ResultInvalidHierarchicalSha256BlockSize);
+ R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1,
+ ResultInvalidHierarchicalSha256LayerCount);
+
+ // Get the regions.
+ const auto& hash_region = hash_data.hash_layer_region[0];
+ const auto& data_region = hash_data.hash_layer_region[1];
+
+ // Determine buffer sizes.
+ constexpr s32 CacheBlockCount = 2;
+ const auto hash_buffer_size = static_cast<size_t>(hash_region.size);
+ const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size;
+ const auto total_buffer_size = hash_buffer_size + cache_buffer_size;
+
+ // Make a buffer holder storage.
+ auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>(
+ std::move(base_storage), total_buffer_size);
+ R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+ R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI);
+
+ // Get storage size.
+ s64 base_size = buffer_hold_storage->GetSize();
+
+ // Check that we're within range.
+ R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC);
+ R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC);
+
+ // Create the master hash storage.
+ auto master_hash_storage =
+ std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value);
+
+ // Make the verification storage.
+ auto verification_storage = std::make_shared<VerificationStorage>();
+ R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Make layer storages.
+ std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{
+ std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0),
+ std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset),
+ std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset),
+ };
+
+ // Initialize the verification storage.
+ R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount,
+ hash_data.hash_block_size,
+ buffer_hold_storage->GetBuffer(), hash_buffer_size));
+
+ // Set the output.
+ *out = std::move(verification_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateIntegrityVerificationStorage(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) {
+ R_RETURN(this->CreateIntegrityVerificationStorageImpl(
+ out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount,
+ HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel(
+ meta_info.level_hash_info.max_layers)));
+}
+
+Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta(
+ VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+
+ // Check the meta data hash data size.
+ R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData),
+ ResultInvalidNcaMetaDataHashDataSize);
+
+ // Read the meta data hash data.
+ NcaMetaDataHashData meta_data_hash_data;
+ base_storage->ReadObject(std::addressof(meta_data_hash_data),
+ meta_data_hash_data_info.offset - offset);
+
+ // Set the out layer info storage, if necessary.
+ if (out_layer_info_storage != nullptr) {
+ auto layer_info_storage = std::make_shared<OffsetVfsFile>(
+ base_storage,
+ meta_data_hash_data_info.offset + meta_data_hash_data_info.size -
+ meta_data_hash_data.layer_info_offset,
+ meta_data_hash_data.layer_info_offset - offset);
+ R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ *out_layer_info_storage = std::move(layer_info_storage);
+ }
+
+ // Create the meta storage.
+ auto meta_storage = std::make_shared<OffsetVfsFile>(
+ std::move(base_storage), meta_data_hash_data_info.offset - offset, 0);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Create the integrity verification storage.
+ R_RETURN(this->CreateIntegrityVerificationStorageImpl(
+ out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info,
+ meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta,
+ IntegrityHashCacheCountForMeta, 0));
+}
+
+Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset,
+ int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) {
+ // Validate preconditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(layer_info_offset >= 0);
+
+ // Define storage types.
+ using VerificationStorage = HierarchicalIntegrityVerificationStorage;
+ using StorageInfo = VerificationStorage::HierarchicalStorageInformation;
+
+ // Validate the meta info.
+ HierarchicalIntegrityVerificationInformation level_hash_info;
+ std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info),
+ sizeof(level_hash_info));
+
+ R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers,
+ ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
+ R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount,
+ ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
+
+ // Get the base storage size.
+ s64 base_storage_size = base_storage->GetSize();
+
+ // Create storage info.
+ StorageInfo storage_info;
+ for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) {
+ const auto& layer_info = level_hash_info.info[i];
+ R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size,
+ ResultNcaBaseStorageOutOfRangeD);
+
+ storage_info[i + 1] = std::make_shared<OffsetVfsFile>(
+ base_storage, layer_info.size, layer_info_offset + layer_info.offset);
+ }
+
+ // Set the last layer info.
+ const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2];
+ const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get();
+ R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size,
+ ResultNcaBaseStorageOutOfRangeD);
+ if (layer_info_offset > 0) {
+ R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset,
+ ResultRomNcaInvalidIntegrityLayerInfoOffset);
+ }
+ storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>(
+ std::move(base_storage), layer_info.size, last_layer_info_offset));
+
+ // Make the integrity romfs storage.
+ auto integrity_storage = std::make_shared<IntegrityRomFsStorage>();
+ R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the integrity storage.
+ R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info,
+ max_data_cache_entries, max_hash_cache_entries,
+ buffer_level));
+
+ // Set the output.
+ *out = std::move(integrity_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out,
+ const NcaFsHeaderReader* header_reader,
+ VirtualFile inside_storage,
+ VirtualFile outside_storage) {
+ // Check pre-conditions.
+ ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash);
+
+ // Create the region.
+ RegionSwitchStorage::Region region = {};
+ R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size)));
+
+ // Create the region switch storage.
+ auto region_switch_storage = std::make_shared<RegionSwitchStorage>(
+ std::move(inside_storage), std::move(outside_storage), region);
+ R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Set the output.
+ *out = std::move(region_switch_storage);
+ R_SUCCEED();
+}
+
+Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out,
+ std::shared_ptr<CompressedStorage>* out_cmp,
+ VirtualFile* out_meta, VirtualFile base_storage,
+ const NcaCompressionInfo& compression_info) {
+ R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage),
+ compression_info, m_reader->GetDecompressor()));
+}
+
+Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out,
+ std::shared_ptr<CompressedStorage>* out_cmp,
+ VirtualFile* out_meta, VirtualFile base_storage,
+ const NcaCompressionInfo& compression_info,
+ GetDecompressorFunction get_decompressor) {
+ // Check pre-conditions.
+ ASSERT(out != nullptr);
+ ASSERT(base_storage != nullptr);
+ ASSERT(get_decompressor != nullptr);
+
+ // Read and verify the bucket tree header.
+ BucketTree::Header header;
+ std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header));
+ R_TRY(header.Verify());
+
+ // Determine the storage extents.
+ const auto table_offset = compression_info.bucket.offset;
+ const auto table_size = compression_info.bucket.size;
+ const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count);
+ const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count);
+ R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize);
+
+ // If we should, set the output meta storage.
+ if (out_meta != nullptr) {
+ auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset);
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ *out_meta = std::move(meta_storage);
+ }
+
+ // Allocate the compressed storage.
+ auto compressed_storage = std::make_shared<CompressedStorage>();
+ R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the compressed storage.
+ R_TRY(compressed_storage->Initialize(
+ std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0),
+ std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset),
+ std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size),
+ header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32));
+
+ // Potentially set the output compressed storage.
+ if (out_cmp) {
+ *out_cmp = compressed_storage;
+ }
+
+ // Set the output.
+ *out = std::move(compressed_storage);
+ R_SUCCEED();
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h
new file mode 100644
index 000000000..5771a21fc
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h
@@ -0,0 +1,364 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_compression_common.h"
+#include "core/file_sys/fssystem/fssystem_nca_header.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+class CompressedStorage;
+class AesCtrCounterExtendedStorage;
+class IndirectStorage;
+class SparseStorage;
+
+struct NcaCryptoConfiguration;
+
+using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key,
+ size_t src_key_size, s32 key_type);
+using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data,
+ size_t data_size, u8 generation);
+
+struct NcaCryptoConfiguration {
+ static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8;
+ static constexpr size_t Rsa2048KeyPublicExponentSize = 3;
+ static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize;
+
+ static constexpr size_t Aes128KeySize = 128 / 8;
+
+ static constexpr size_t Header1SignatureKeyGenerationMax = 1;
+
+ static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3;
+ static constexpr s32 HeaderEncryptionKeyCount = 2;
+
+ static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF;
+
+ static constexpr size_t KeyGenerationMax = 32;
+
+ std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli;
+ std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent;
+ std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount>
+ key_area_encryption_key_source;
+ std::array<u8, Aes128KeySize> header_encryption_key_source;
+ std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount>
+ header_encrypted_encryption_keys;
+ KeyGenerationFunction generate_key;
+ VerifySign1Function verify_sign1;
+ bool is_plaintext_header_available;
+ bool is_available_sw_key;
+};
+static_assert(std::is_trivial_v<NcaCryptoConfiguration>);
+
+struct NcaCompressionConfiguration {
+ GetDecompressorFunction get_decompressor;
+};
+static_assert(std::is_trivial_v<NcaCompressionConfiguration>);
+
+constexpr inline s32 KeyAreaEncryptionKeyCount =
+ NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount *
+ NcaCryptoConfiguration::KeyGenerationMax;
+
+enum class KeyType : s32 {
+ ZeroKey = -2,
+ InvalidKey = -1,
+ NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0,
+ NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1,
+ NcaExternalKey = KeyAreaEncryptionKeyCount + 2,
+ SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3,
+ SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4,
+ SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5,
+};
+
+constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) {
+ return key_type < 0;
+}
+
+constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) {
+ if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) {
+ return static_cast<s32>(KeyType::ZeroKey);
+ }
+
+ if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) {
+ return static_cast<s32>(KeyType::InvalidKey);
+ }
+
+ return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index;
+}
+
+class NcaReader {
+ YUZU_NON_COPYABLE(NcaReader);
+ YUZU_NON_MOVEABLE(NcaReader);
+
+public:
+ NcaReader();
+ ~NcaReader();
+
+ Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg,
+ const NcaCompressionConfiguration& compression_cfg);
+
+ VirtualFile GetSharedBodyStorage();
+ u32 GetMagic() const;
+ NcaHeader::DistributionType GetDistributionType() const;
+ NcaHeader::ContentType GetContentType() const;
+ u8 GetHeaderSign1KeyGeneration() const;
+ u8 GetKeyGeneration() const;
+ u8 GetKeyIndex() const;
+ u64 GetContentSize() const;
+ u64 GetProgramId() const;
+ u32 GetContentIndex() const;
+ u32 GetSdkAddonVersion() const;
+ void GetRightsId(u8* dst, size_t dst_size) const;
+ bool HasFsInfo(s32 index) const;
+ s32 GetFsCount() const;
+ const Hash& GetFsHeaderHash(s32 index) const;
+ void GetFsHeaderHash(Hash* dst, s32 index) const;
+ void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const;
+ u64 GetFsOffset(s32 index) const;
+ u64 GetFsEndOffset(s32 index) const;
+ u64 GetFsSize(s32 index) const;
+ void GetEncryptedKey(void* dst, size_t size) const;
+ const void* GetDecryptionKey(s32 index) const;
+ bool HasValidInternalKey() const;
+ bool HasInternalDecryptionKeyForAesHw() const;
+ bool IsSoftwareAesPrioritized() const;
+ void PrioritizeSoftwareAes();
+ bool IsAvailableSwKey() const;
+ bool HasExternalDecryptionKey() const;
+ const void* GetExternalDecryptionKey() const;
+ void SetExternalDecryptionKey(const void* src, size_t size);
+ void GetRawData(void* dst, size_t dst_size) const;
+ NcaHeader::EncryptionType GetEncryptionType() const;
+ Result ReadHeader(NcaFsHeader* dst, s32 index) const;
+
+ GetDecompressorFunction GetDecompressor() const;
+
+ bool GetHeaderSign1Valid() const;
+
+ void GetHeaderSign2(void* dst, size_t size) const;
+
+private:
+ NcaHeader m_header;
+ std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>,
+ NcaHeader::DecryptionKey_Count>
+ m_decryption_keys;
+ VirtualFile m_body_storage;
+ VirtualFile m_header_storage;
+ std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key;
+ bool m_is_software_aes_prioritized;
+ bool m_is_available_sw_key;
+ NcaHeader::EncryptionType m_header_encryption_type;
+ bool m_is_header_sign1_signature_valid;
+ GetDecompressorFunction m_get_decompressor;
+};
+
+class NcaFsHeaderReader {
+ YUZU_NON_COPYABLE(NcaFsHeaderReader);
+ YUZU_NON_MOVEABLE(NcaFsHeaderReader);
+
+public:
+ NcaFsHeaderReader() : m_fs_index(-1) {
+ std::memset(std::addressof(m_data), 0, sizeof(m_data));
+ }
+
+ Result Initialize(const NcaReader& reader, s32 index);
+ bool IsInitialized() const {
+ return m_fs_index >= 0;
+ }
+
+ void GetRawData(void* dst, size_t dst_size) const;
+
+ NcaFsHeader::HashData& GetHashData();
+ const NcaFsHeader::HashData& GetHashData() const;
+ u16 GetVersion() const;
+ s32 GetFsIndex() const;
+ NcaFsHeader::FsType GetFsType() const;
+ NcaFsHeader::HashType GetHashType() const;
+ NcaFsHeader::EncryptionType GetEncryptionType() const;
+ NcaPatchInfo& GetPatchInfo();
+ const NcaPatchInfo& GetPatchInfo() const;
+ const NcaAesCtrUpperIv GetAesCtrUpperIv() const;
+
+ bool IsSkipLayerHashEncryption() const;
+ Result GetHashTargetOffset(s64* out) const;
+
+ bool ExistsSparseLayer() const;
+ NcaSparseInfo& GetSparseInfo();
+ const NcaSparseInfo& GetSparseInfo() const;
+
+ bool ExistsCompressionLayer() const;
+ NcaCompressionInfo& GetCompressionInfo();
+ const NcaCompressionInfo& GetCompressionInfo() const;
+
+ bool ExistsPatchMetaHashLayer() const;
+ NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo();
+ const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const;
+ NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const;
+
+ bool ExistsSparseMetaHashLayer() const;
+ NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo();
+ const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const;
+ NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const;
+
+private:
+ NcaFsHeader m_data;
+ s32 m_fs_index;
+};
+
+class NcaFileSystemDriver {
+ YUZU_NON_COPYABLE(NcaFileSystemDriver);
+ YUZU_NON_MOVEABLE(NcaFileSystemDriver);
+
+public:
+ struct StorageContext {
+ bool open_raw_storage;
+ VirtualFile body_substorage;
+ std::shared_ptr<SparseStorage> current_sparse_storage;
+ VirtualFile sparse_storage_meta_storage;
+ std::shared_ptr<SparseStorage> original_sparse_storage;
+ void* external_current_sparse_storage;
+ void* external_original_sparse_storage;
+ VirtualFile aes_ctr_ex_storage_meta_storage;
+ VirtualFile aes_ctr_ex_storage_data_storage;
+ std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage;
+ VirtualFile indirect_storage_meta_storage;
+ std::shared_ptr<IndirectStorage> indirect_storage;
+ VirtualFile fs_data_storage;
+ VirtualFile compressed_storage_meta_storage;
+ std::shared_ptr<CompressedStorage> compressed_storage;
+
+ VirtualFile patch_layer_info_storage;
+ VirtualFile sparse_layer_info_storage;
+
+ VirtualFile external_original_storage;
+ };
+
+private:
+ enum class AlignmentStorageRequirement {
+ CacheBlockSize = 0,
+ None = 1,
+ };
+
+public:
+ static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader,
+ s32 fs_index);
+
+public:
+ NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) {
+ ASSERT(m_reader != nullptr);
+ }
+
+ NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader,
+ std::shared_ptr<NcaReader> reader)
+ : m_original_reader(original_reader), m_reader(reader) {
+ ASSERT(m_reader != nullptr);
+ }
+
+ Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader,
+ s32 fs_index, StorageContext* ctx);
+
+ Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) {
+ // Create a storage context.
+ StorageContext ctx{};
+
+ // Open the storage.
+ R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx)));
+ }
+
+public:
+ Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
+ VirtualFile raw_storage, StorageContext* ctx);
+
+private:
+ Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index,
+ StorageContext* ctx);
+
+ Result OpenIndirectableStorageAsOriginal(VirtualFile* out,
+ const NcaFsHeaderReader* header_reader,
+ StorageContext* ctx);
+
+ Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size);
+
+ Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv,
+ AlignmentStorageRequirement alignment_storage_requirement);
+ Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset);
+
+ Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv,
+ const NcaSparseInfo& sparse_info);
+ Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage,
+ s64 base_size, VirtualFile meta_storage,
+ const NcaSparseInfo& sparse_info, bool external_info);
+ Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset,
+ std::shared_ptr<SparseStorage>* out_sparse_storage,
+ VirtualFile* out_meta_storage, s32 index,
+ const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info);
+
+ Result CreateSparseStorageMetaStorageWithVerification(
+ VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset,
+ const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
+ Result CreateSparseStorageWithVerification(
+ VirtualFile* out, s64* out_fs_data_offset,
+ std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage,
+ VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv,
+ const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info,
+ NcaFsHeader::MetaDataHashType meta_data_hash_type);
+
+ Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset,
+ NcaFsHeader::EncryptionType encryption_type,
+ const NcaAesCtrUpperIv& upper_iv,
+ const NcaPatchInfo& patch_info);
+ Result CreateAesCtrExStorage(VirtualFile* out,
+ std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext,
+ VirtualFile base_storage, VirtualFile meta_storage,
+ s64 counter_offset, const NcaAesCtrUpperIv& upper_iv,
+ const NcaPatchInfo& patch_info);
+
+ Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage,
+ const NcaPatchInfo& patch_info);
+ Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind,
+ VirtualFile base_storage, VirtualFile original_data_storage,
+ VirtualFile meta_storage, const NcaPatchInfo& patch_info);
+
+ Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta,
+ VirtualFile* out_verification, VirtualFile base_storage,
+ s64 offset, const NcaAesCtrUpperIv& upper_iv,
+ const NcaPatchInfo& patch_info,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
+
+ Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data);
+
+ Result CreateIntegrityVerificationStorage(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info);
+ Result CreateIntegrityVerificationStorageForMeta(
+ VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset,
+ const NcaMetaDataHashDataInfo& meta_data_hash_data_info);
+ Result CreateIntegrityVerificationStorageImpl(
+ VirtualFile* out, VirtualFile base_storage,
+ const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset,
+ int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level);
+
+ Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader,
+ VirtualFile inside_storage, VirtualFile outside_storage);
+
+ Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp,
+ VirtualFile* out_meta, VirtualFile base_storage,
+ const NcaCompressionInfo& compression_info);
+
+public:
+ Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp,
+ VirtualFile* out_meta, VirtualFile base_storage,
+ const NcaCompressionInfo& compression_info,
+ GetDecompressorFunction get_decompressor);
+
+private:
+ std::shared_ptr<NcaReader> m_original_reader;
+ std::shared_ptr<NcaReader> m_reader;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp
new file mode 100644
index 000000000..bf5742d39
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_nca_header.h"
+
+namespace FileSys {
+
+u8 NcaHeader::GetProperKeyGeneration() const {
+ return std::max(this->key_generation, this->key_generation_2);
+}
+
+bool NcaPatchInfo::HasIndirectTable() const {
+ return this->indirect_size != 0;
+}
+
+bool NcaPatchInfo::HasAesCtrExTable() const {
+ return this->aes_ctr_ex_size != 0;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h
new file mode 100644
index 000000000..a02c5d881
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_header.h
@@ -0,0 +1,338 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/literals.h"
+
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fssystem/fs_types.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+struct Hash {
+ static constexpr std::size_t Size = 256 / 8;
+ std::array<u8, Size> value;
+};
+static_assert(sizeof(Hash) == Hash::Size);
+static_assert(std::is_trivial_v<Hash>);
+
+using NcaDigest = Hash;
+
+struct NcaHeader {
+ enum class ContentType : u8 {
+ Program = 0,
+ Meta = 1,
+ Control = 2,
+ Manual = 3,
+ Data = 4,
+ PublicData = 5,
+
+ Start = Program,
+ End = PublicData,
+ };
+
+ enum class DistributionType : u8 {
+ Download = 0,
+ GameCard = 1,
+
+ Start = Download,
+ End = GameCard,
+ };
+
+ enum class EncryptionType : u8 {
+ Auto = 0,
+ None = 1,
+ };
+
+ enum DecryptionKey {
+ DecryptionKey_AesXts = 0,
+ DecryptionKey_AesXts1 = DecryptionKey_AesXts,
+ DecryptionKey_AesXts2 = 1,
+ DecryptionKey_AesCtr = 2,
+ DecryptionKey_AesCtrEx = 3,
+ DecryptionKey_AesCtrHw = 4,
+ DecryptionKey_Count,
+ };
+
+ struct FsInfo {
+ u32 start_sector;
+ u32 end_sector;
+ u32 hash_sectors;
+ u32 reserved;
+ };
+ static_assert(sizeof(FsInfo) == 0x10);
+ static_assert(std::is_trivial_v<FsInfo>);
+
+ static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0');
+ static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1');
+ static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2');
+ static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3');
+
+ static constexpr u32 Magic = Magic3;
+
+ static constexpr std::size_t Size = 1_KiB;
+ static constexpr s32 FsCountMax = 4;
+ static constexpr std::size_t HeaderSignCount = 2;
+ static constexpr std::size_t HeaderSignSize = 0x100;
+ static constexpr std::size_t EncryptedKeyAreaSize = 0x100;
+ static constexpr std::size_t SectorSize = 0x200;
+ static constexpr std::size_t SectorShift = 9;
+ static constexpr std::size_t RightsIdSize = 0x10;
+ static constexpr std::size_t XtsBlockSize = 0x200;
+ static constexpr std::size_t CtrBlockSize = 0x10;
+
+ static_assert(SectorSize == (1 << SectorShift));
+
+ // Data members.
+ std::array<u8, HeaderSignSize> header_sign_1;
+ std::array<u8, HeaderSignSize> header_sign_2;
+ u32 magic;
+ DistributionType distribution_type;
+ ContentType content_type;
+ u8 key_generation;
+ u8 key_index;
+ u64 content_size;
+ u64 program_id;
+ u32 content_index;
+ u32 sdk_addon_version;
+ u8 key_generation_2;
+ u8 header1_signature_key_generation;
+ std::array<u8, 2> reserved_222;
+ std::array<u32, 3> reserved_224;
+ std::array<u8, RightsIdSize> rights_id;
+ std::array<FsInfo, FsCountMax> fs_info;
+ std::array<Hash, FsCountMax> fs_header_hash;
+ std::array<u8, EncryptedKeyAreaSize> encrypted_key_area;
+
+ static constexpr u64 SectorToByte(u32 sector) {
+ return static_cast<u64>(sector) << SectorShift;
+ }
+
+ static constexpr u32 ByteToSector(u64 byte) {
+ return static_cast<u32>(byte >> SectorShift);
+ }
+
+ u8 GetProperKeyGeneration() const;
+};
+static_assert(sizeof(NcaHeader) == NcaHeader::Size);
+static_assert(std::is_trivial_v<NcaHeader>);
+
+struct NcaBucketInfo {
+ static constexpr size_t HeaderSize = 0x10;
+ Int64 offset;
+ Int64 size;
+ std::array<u8, HeaderSize> header;
+};
+static_assert(std::is_trivial_v<NcaBucketInfo>);
+
+struct NcaPatchInfo {
+ static constexpr size_t Size = 0x40;
+ static constexpr size_t Offset = 0x100;
+
+ Int64 indirect_offset;
+ Int64 indirect_size;
+ std::array<u8, NcaBucketInfo::HeaderSize> indirect_header;
+ Int64 aes_ctr_ex_offset;
+ Int64 aes_ctr_ex_size;
+ std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header;
+
+ bool HasIndirectTable() const;
+ bool HasAesCtrExTable() const;
+};
+static_assert(std::is_trivial_v<NcaPatchInfo>);
+
+union NcaAesCtrUpperIv {
+ u64 value;
+ struct {
+ u32 generation;
+ u32 secure_value;
+ } part;
+};
+static_assert(std::is_trivial_v<NcaAesCtrUpperIv>);
+
+struct NcaSparseInfo {
+ NcaBucketInfo bucket;
+ Int64 physical_offset;
+ u16 generation;
+ std::array<u8, 6> reserved;
+
+ s64 GetPhysicalSize() const {
+ return this->bucket.offset + this->bucket.size;
+ }
+
+ u32 GetGeneration() const {
+ return static_cast<u32>(this->generation) << 16;
+ }
+
+ const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const {
+ NcaAesCtrUpperIv sparse_upper_iv = upper_iv;
+ sparse_upper_iv.part.generation = this->GetGeneration();
+ return sparse_upper_iv;
+ }
+};
+static_assert(std::is_trivial_v<NcaSparseInfo>);
+
+struct NcaCompressionInfo {
+ NcaBucketInfo bucket;
+ std::array<u8, 8> resreved;
+};
+static_assert(std::is_trivial_v<NcaCompressionInfo>);
+
+struct NcaMetaDataHashDataInfo {
+ Int64 offset;
+ Int64 size;
+ Hash hash;
+};
+static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>);
+
+struct NcaFsHeader {
+ static constexpr size_t Size = 0x200;
+ static constexpr size_t HashDataOffset = 0x8;
+
+ struct Region {
+ Int64 offset;
+ Int64 size;
+ };
+ static_assert(std::is_trivial_v<Region>);
+
+ enum class FsType : u8 {
+ RomFs = 0,
+ PartitionFs = 1,
+ };
+
+ enum class EncryptionType : u8 {
+ Auto = 0,
+ None = 1,
+ AesXts = 2,
+ AesCtr = 3,
+ AesCtrEx = 4,
+ AesCtrSkipLayerHash = 5,
+ AesCtrExSkipLayerHash = 6,
+ };
+
+ enum class HashType : u8 {
+ Auto = 0,
+ None = 1,
+ HierarchicalSha256Hash = 2,
+ HierarchicalIntegrityHash = 3,
+ AutoSha3 = 4,
+ HierarchicalSha3256Hash = 5,
+ HierarchicalIntegritySha3Hash = 6,
+ };
+
+ enum class MetaDataHashType : u8 {
+ None = 0,
+ HierarchicalIntegrity = 1,
+ };
+
+ union HashData {
+ struct HierarchicalSha256Data {
+ static constexpr size_t HashLayerCountMax = 5;
+ static const size_t MasterHashOffset;
+
+ Hash fs_data_master_hash;
+ s32 hash_block_size;
+ s32 hash_layer_count;
+ std::array<Region, HashLayerCountMax> hash_layer_region;
+ } hierarchical_sha256_data;
+ static_assert(std::is_trivial_v<HierarchicalSha256Data>);
+
+ struct IntegrityMetaInfo {
+ static const size_t MasterHashOffset;
+
+ u32 magic;
+ u32 version;
+ u32 master_hash_size;
+
+ struct LevelHashInfo {
+ u32 max_layers;
+
+ struct HierarchicalIntegrityVerificationLevelInformation {
+ static constexpr size_t IntegrityMaxLayerCount = 7;
+ Int64 offset;
+ Int64 size;
+ s32 block_order;
+ std::array<u8, 4> reserved;
+ };
+ std::array<
+ HierarchicalIntegrityVerificationLevelInformation,
+ HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1>
+ info;
+
+ struct SignatureSalt {
+ static constexpr size_t Size = 0x20;
+ std::array<u8, Size> value;
+ };
+ SignatureSalt seed;
+ } level_hash_info;
+
+ Hash master_hash;
+ } integrity_meta_info;
+ static_assert(std::is_trivial_v<IntegrityMetaInfo>);
+
+ std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding;
+ };
+
+ u16 version;
+ FsType fs_type;
+ HashType hash_type;
+ EncryptionType encryption_type;
+ MetaDataHashType meta_data_hash_type;
+ std::array<u8, 2> reserved;
+ HashData hash_data;
+ NcaPatchInfo patch_info;
+ NcaAesCtrUpperIv aes_ctr_upper_iv;
+ NcaSparseInfo sparse_info;
+ NcaCompressionInfo compression_info;
+ NcaMetaDataHashDataInfo meta_data_hash_data_info;
+ std::array<u8, 0x30> pad;
+
+ bool IsSkipLayerHashEncryption() const {
+ return this->encryption_type == EncryptionType::AesCtrSkipLayerHash ||
+ this->encryption_type == EncryptionType::AesCtrExSkipLayerHash;
+ }
+
+ Result GetHashTargetOffset(s64* out) const {
+ switch (this->hash_type) {
+ case HashType::HierarchicalIntegrityHash:
+ case HashType::HierarchicalIntegritySha3Hash:
+ *out = this->hash_data.integrity_meta_info.level_hash_info
+ .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2]
+ .offset;
+ R_SUCCEED();
+ case HashType::HierarchicalSha256Hash:
+ case HashType::HierarchicalSha3256Hash:
+ *out =
+ this->hash_data.hierarchical_sha256_data
+ .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count -
+ 1]
+ .offset;
+ R_SUCCEED();
+ default:
+ R_THROW(ResultInvalidNcaFsHeader);
+ }
+ }
+};
+static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size);
+static_assert(std::is_trivial_v<NcaFsHeader>);
+static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset);
+
+inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset =
+ offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash);
+inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset =
+ offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash);
+
+struct NcaMetaDataHashData {
+ s64 layer_info_offset;
+ NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info;
+};
+static_assert(sizeof(NcaMetaDataHashData) ==
+ sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64));
+static_assert(std::is_trivial_v<NcaMetaDataHashData>);
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp
new file mode 100644
index 000000000..a3714ab37
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp
@@ -0,0 +1,531 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
+#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+namespace {
+
+constexpr inline u32 SdkAddonVersionMin = 0x000B0000;
+constexpr inline size_t Aes128KeySize = 0x10;
+constexpr const std::array<u8, Aes128KeySize> ZeroKey{};
+
+constexpr Result CheckNcaMagic(u32 magic) {
+ // Verify the magic is not a deprecated one.
+ R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion);
+ R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion);
+ R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion);
+
+ // Verify the magic is the current one.
+ R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature);
+
+ R_SUCCEED();
+}
+
+} // namespace
+
+NcaReader::NcaReader()
+ : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false),
+ m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto),
+ m_get_decompressor() {
+ std::memset(std::addressof(m_header), 0, sizeof(m_header));
+ std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys));
+ std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key));
+}
+
+NcaReader::~NcaReader() {}
+
+Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg,
+ const NcaCompressionConfiguration& compression_cfg) {
+ // Validate preconditions.
+ ASSERT(base_storage != nullptr);
+ ASSERT(m_body_storage == nullptr);
+
+ // Create the work header storage storage.
+ VirtualFile work_header_storage;
+
+ // We need to be able to generate keys.
+ R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument);
+
+ // Generate keys for header.
+ using AesXtsStorageForNcaHeader = AesXtsStorage;
+
+ constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount>
+ HeaderKeyTypeValues = {
+ static_cast<s32>(KeyType::NcaHeaderKey1),
+ static_cast<s32>(KeyType::NcaHeaderKey2),
+ };
+
+ std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>,
+ NcaCryptoConfiguration::HeaderEncryptionKeyCount>
+ header_decryption_keys;
+ for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) {
+ crypto_cfg.generate_key(header_decryption_keys[i].data(),
+ AesXtsStorageForNcaHeader::KeySize,
+ crypto_cfg.header_encrypted_encryption_keys[i].data(),
+ AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]);
+ }
+
+ // Create the header storage.
+ std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {};
+ work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>(
+ base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(),
+ AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize,
+ NcaHeader::XtsBlockSize);
+
+ // Check that we successfully created the storage.
+ R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA);
+
+ // Read the header.
+ work_header_storage->ReadObject(std::addressof(m_header), 0);
+
+ // Validate the magic.
+ if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) {
+ // Try to use a plaintext header.
+ base_storage->ReadObject(std::addressof(m_header), 0);
+ R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result);
+
+ // Configure to use the plaintext header.
+ auto base_storage_size = base_storage->GetSize();
+ work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0);
+ R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA);
+
+ // Set encryption type as plaintext.
+ m_header_encryption_type = NcaHeader::EncryptionType::None;
+ }
+
+ // Verify the header sign1.
+ if (crypto_cfg.verify_sign1 != nullptr) {
+ const u8* sig = m_header.header_sign_1.data();
+ const size_t sig_size = NcaHeader::HeaderSignSize;
+ const u8* msg =
+ static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic)));
+ const size_t msg_size =
+ NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount;
+
+ m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1(
+ sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation);
+
+ if (!m_is_header_sign1_signature_valid) {
+ LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1");
+ }
+ }
+
+ // Validate the sdk version.
+ R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion);
+
+ // Validate the key index.
+ R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount ||
+ m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey,
+ ResultInvalidNcaKeyIndex);
+
+ // Check if we have a rights id.
+ constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{};
+ if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) {
+ // If we don't, then we don't have an external key, so we need to generate decryption keys.
+ crypto_cfg.generate_key(
+ m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize,
+ m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize,
+ Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
+ crypto_cfg.generate_key(
+ m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize,
+ m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize,
+ Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
+ crypto_cfg.generate_key(
+ m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize,
+ m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize,
+ Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
+ crypto_cfg.generate_key(
+ m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize,
+ m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize,
+ Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
+
+ // Copy the hardware speed emulation key.
+ std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(),
+ m_header.encrypted_key_area.data() +
+ NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize,
+ Aes128KeySize);
+ }
+
+ // Clear the external decryption key.
+ std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size());
+
+ // Set software key availability.
+ m_is_available_sw_key = crypto_cfg.is_available_sw_key;
+
+ // Set our decompressor function getter.
+ m_get_decompressor = compression_cfg.get_decompressor;
+
+ // Set our storages.
+ m_header_storage = std::move(work_header_storage);
+ m_body_storage = std::move(base_storage);
+
+ R_SUCCEED();
+}
+
+VirtualFile NcaReader::GetSharedBodyStorage() {
+ ASSERT(m_body_storage != nullptr);
+ return m_body_storage;
+}
+
+u32 NcaReader::GetMagic() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.magic;
+}
+
+NcaHeader::DistributionType NcaReader::GetDistributionType() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.distribution_type;
+}
+
+NcaHeader::ContentType NcaReader::GetContentType() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.content_type;
+}
+
+u8 NcaReader::GetHeaderSign1KeyGeneration() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.header1_signature_key_generation;
+}
+
+u8 NcaReader::GetKeyGeneration() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.GetProperKeyGeneration();
+}
+
+u8 NcaReader::GetKeyIndex() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.key_index;
+}
+
+u64 NcaReader::GetContentSize() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.content_size;
+}
+
+u64 NcaReader::GetProgramId() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.program_id;
+}
+
+u32 NcaReader::GetContentIndex() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.content_index;
+}
+
+u32 NcaReader::GetSdkAddonVersion() const {
+ ASSERT(m_body_storage != nullptr);
+ return m_header.sdk_addon_version;
+}
+
+void NcaReader::GetRightsId(u8* dst, size_t dst_size) const {
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size >= NcaHeader::RightsIdSize);
+
+ std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize);
+}
+
+bool NcaReader::HasFsInfo(s32 index) const {
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0;
+}
+
+s32 NcaReader::GetFsCount() const {
+ ASSERT(m_body_storage != nullptr);
+ for (s32 i = 0; i < NcaHeader::FsCountMax; i++) {
+ if (!this->HasFsInfo(i)) {
+ return i;
+ }
+ }
+ return NcaHeader::FsCountMax;
+}
+
+const Hash& NcaReader::GetFsHeaderHash(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return m_header.fs_header_hash[index];
+}
+
+void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ ASSERT(dst != nullptr);
+ std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst));
+}
+
+void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ ASSERT(dst != nullptr);
+ std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst));
+}
+
+u64 NcaReader::GetFsOffset(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector);
+}
+
+u64 NcaReader::GetFsEndOffset(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector);
+}
+
+u64 NcaReader::GetFsSize(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+ return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector -
+ m_header.fs_info[index].start_sector);
+}
+
+void NcaReader::GetEncryptedKey(void* dst, size_t size) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(dst != nullptr);
+ ASSERT(size >= NcaHeader::EncryptedKeyAreaSize);
+
+ std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize);
+}
+
+const void* NcaReader::GetDecryptionKey(s32 index) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count);
+ return m_decryption_keys[index].data();
+}
+
+bool NcaReader::HasValidInternalKey() const {
+ for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) {
+ if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize,
+ Aes128KeySize) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool NcaReader::HasInternalDecryptionKeyForAesHw() const {
+ return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw),
+ Aes128KeySize) != 0;
+}
+
+bool NcaReader::IsSoftwareAesPrioritized() const {
+ return m_is_software_aes_prioritized;
+}
+
+void NcaReader::PrioritizeSoftwareAes() {
+ m_is_software_aes_prioritized = true;
+}
+
+bool NcaReader::IsAvailableSwKey() const {
+ return m_is_available_sw_key;
+}
+
+bool NcaReader::HasExternalDecryptionKey() const {
+ return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0;
+}
+
+const void* NcaReader::GetExternalDecryptionKey() const {
+ return m_external_decryption_key.data();
+}
+
+void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) {
+ ASSERT(src != nullptr);
+ ASSERT(size == sizeof(m_external_decryption_key));
+
+ std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key));
+}
+
+void NcaReader::GetRawData(void* dst, size_t dst_size) const {
+ ASSERT(m_body_storage != nullptr);
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size >= sizeof(NcaHeader));
+
+ std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader));
+}
+
+GetDecompressorFunction NcaReader::GetDecompressor() const {
+ ASSERT(m_get_decompressor != nullptr);
+ return m_get_decompressor;
+}
+
+NcaHeader::EncryptionType NcaReader::GetEncryptionType() const {
+ return m_header_encryption_type;
+}
+
+Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const {
+ ASSERT(dst != nullptr);
+ ASSERT(0 <= index && index < NcaHeader::FsCountMax);
+
+ const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index;
+ m_header_storage->ReadObject(dst, offset);
+
+ R_SUCCEED();
+}
+
+bool NcaReader::GetHeaderSign1Valid() const {
+ return m_is_header_sign1_signature_valid;
+}
+
+void NcaReader::GetHeaderSign2(void* dst, size_t size) const {
+ ASSERT(dst != nullptr);
+ ASSERT(size == NcaHeader::HeaderSignSize);
+
+ std::memcpy(dst, m_header.header_sign_2.data(), size);
+}
+
+Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) {
+ // Reset ourselves to uninitialized.
+ m_fs_index = -1;
+
+ // Read the header.
+ R_TRY(reader.ReadHeader(std::addressof(m_data), index));
+
+ // Set our index.
+ m_fs_index = index;
+ R_SUCCEED();
+}
+
+void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const {
+ ASSERT(this->IsInitialized());
+ ASSERT(dst != nullptr);
+ ASSERT(dst_size >= sizeof(NcaFsHeader));
+
+ std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader));
+}
+
+NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() {
+ ASSERT(this->IsInitialized());
+ return m_data.hash_data;
+}
+
+const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const {
+ ASSERT(this->IsInitialized());
+ return m_data.hash_data;
+}
+
+u16 NcaFsHeaderReader::GetVersion() const {
+ ASSERT(this->IsInitialized());
+ return m_data.version;
+}
+
+s32 NcaFsHeaderReader::GetFsIndex() const {
+ ASSERT(this->IsInitialized());
+ return m_fs_index;
+}
+
+NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.fs_type;
+}
+
+NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.hash_type;
+}
+
+NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.encryption_type;
+}
+
+NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.patch_info;
+}
+
+const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.patch_info;
+}
+
+const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const {
+ ASSERT(this->IsInitialized());
+ return m_data.aes_ctr_upper_iv;
+}
+
+bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const {
+ ASSERT(this->IsInitialized());
+ return m_data.IsSkipLayerHashEncryption();
+}
+
+Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const {
+ ASSERT(out != nullptr);
+ ASSERT(this->IsInitialized());
+
+ R_RETURN(m_data.GetHashTargetOffset(out));
+}
+
+bool NcaFsHeaderReader::ExistsSparseLayer() const {
+ ASSERT(this->IsInitialized());
+ return m_data.sparse_info.generation != 0;
+}
+
+NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.sparse_info;
+}
+
+const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.sparse_info;
+}
+
+bool NcaFsHeaderReader::ExistsCompressionLayer() const {
+ ASSERT(this->IsInitialized());
+ return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0;
+}
+
+NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.compression_info;
+}
+
+const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.compression_info;
+}
+
+bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable();
+}
+
+NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info;
+}
+
+const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info;
+}
+
+NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_type;
+}
+
+bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer();
+}
+
+NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info;
+}
+
+const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_data_info;
+}
+
+NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const {
+ ASSERT(this->IsInitialized());
+ return m_data.meta_data_hash_type;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp
new file mode 100644
index 000000000..bbfaab255
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
+
+namespace FileSys {
+
+namespace {
+
+constexpr size_t HeapBlockSize = BufferPoolAlignment;
+static_assert(HeapBlockSize == 4_KiB);
+
+// A heap block is 4KiB. An order is a power of two.
+// This gives blocks of the order 32KiB, 512KiB, 4MiB.
+constexpr s32 HeapOrderMax = 7;
+constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3;
+
+constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax);
+constexpr size_t HeapAllocatableSizeMaxForLarge =
+ HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge);
+
+} // namespace
+
+size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) {
+ return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax;
+}
+
+void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) {
+ // Ensure preconditions.
+ ASSERT(m_buffer == nullptr);
+
+ // Check that we can allocate this size.
+ ASSERT(required_size <= GetAllocatableSizeMaxCore(large));
+
+ const size_t target_size =
+ std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large));
+
+ // Dummy implementation for allocate.
+ if (target_size > 0) {
+ m_buffer =
+ reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize}));
+ m_size = target_size;
+
+ // Ensure postconditions.
+ ASSERT(m_buffer != nullptr);
+ }
+}
+
+void PooledBuffer::Shrink(size_t ideal_size) {
+ ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true));
+
+ // Shrinking to zero means that we have no buffer.
+ if (ideal_size == 0) {
+ ::operator delete(m_buffer, std::align_val_t{HeapBlockSize});
+ m_buffer = nullptr;
+ m_size = ideal_size;
+ }
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.h b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h
new file mode 100644
index 000000000..9a6adbcb5
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h
@@ -0,0 +1,95 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/literals.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+using namespace Common::Literals;
+
+constexpr inline size_t BufferPoolAlignment = 4_KiB;
+constexpr inline size_t BufferPoolWorkSize = 320;
+
+class PooledBuffer {
+ YUZU_NON_COPYABLE(PooledBuffer);
+
+public:
+ // Constructor/Destructor.
+ constexpr PooledBuffer() : m_buffer(), m_size() {}
+
+ PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() {
+ this->Allocate(ideal_size, required_size);
+ }
+
+ ~PooledBuffer() {
+ this->Deallocate();
+ }
+
+ // Move and assignment.
+ explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) {
+ rhs.m_buffer = nullptr;
+ rhs.m_size = 0;
+ }
+
+ PooledBuffer& operator=(PooledBuffer&& rhs) {
+ PooledBuffer(std::move(rhs)).Swap(*this);
+ return *this;
+ }
+
+ // Allocation API.
+ void Allocate(size_t ideal_size, size_t required_size) {
+ return this->AllocateCore(ideal_size, required_size, false);
+ }
+
+ void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) {
+ return this->AllocateCore(ideal_size, required_size, true);
+ }
+
+ void Shrink(size_t ideal_size);
+
+ void Deallocate() {
+ // Shrink the buffer to empty.
+ this->Shrink(0);
+ ASSERT(m_buffer == nullptr);
+ }
+
+ char* GetBuffer() const {
+ ASSERT(m_buffer != nullptr);
+ return m_buffer;
+ }
+
+ size_t GetSize() const {
+ ASSERT(m_buffer != nullptr);
+ return m_size;
+ }
+
+public:
+ static size_t GetAllocatableSizeMax() {
+ return GetAllocatableSizeMaxCore(false);
+ }
+ static size_t GetAllocatableParticularlyLargeSizeMax() {
+ return GetAllocatableSizeMaxCore(true);
+ }
+
+private:
+ static size_t GetAllocatableSizeMaxCore(bool large);
+
+private:
+ void Swap(PooledBuffer& rhs) {
+ std::swap(m_buffer, rhs.m_buffer);
+ std::swap(m_size, rhs.m_size);
+ }
+
+ void AllocateCore(size_t ideal_size, size_t required_size, bool large);
+
+private:
+ char* m_buffer;
+ size_t m_size;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp
new file mode 100644
index 000000000..8574a11dd
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_sparse_storage.h"
+
+namespace FileSys {
+
+size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const {
+ // Validate preconditions.
+ ASSERT(this->IsInitialized());
+ ASSERT(buffer != nullptr);
+
+ // Allow zero size.
+ if (size == 0) {
+ return size;
+ }
+
+ SparseStorage* self = const_cast<SparseStorage*>(this);
+
+ if (self->GetEntryTable().IsEmpty()) {
+ BucketTree::Offsets table_offsets;
+ ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets))));
+ ASSERT(table_offsets.IsInclude(offset, size));
+
+ std::memset(buffer, 0, size);
+ } else {
+ self->OperatePerEntry<false, true>(
+ offset, size,
+ [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
+ storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
+ static_cast<size_t>(cur_size), data_offset);
+ R_SUCCEED();
+ });
+ }
+
+ return size;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.h b/src/core/file_sys/fssystem/fssystem_sparse_storage.h
new file mode 100644
index 000000000..6c196ec61
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.h
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
+
+namespace FileSys {
+
+class SparseStorage : public IndirectStorage {
+ YUZU_NON_COPYABLE(SparseStorage);
+ YUZU_NON_MOVEABLE(SparseStorage);
+
+private:
+ class ZeroStorage : public IReadOnlyStorage {
+ public:
+ ZeroStorage() {}
+ virtual ~ZeroStorage() {}
+
+ virtual size_t GetSize() const override {
+ return std::numeric_limits<size_t>::max();
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ ASSERT(buffer != nullptr || size == 0);
+
+ if (size > 0) {
+ std::memset(buffer, 0, size);
+ }
+
+ return size;
+ }
+ };
+
+public:
+ SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {}
+ virtual ~SparseStorage() {}
+
+ using IndirectStorage::Initialize;
+
+ void Initialize(s64 end_offset) {
+ this->GetEntryTable().Initialize(NodeSize, end_offset);
+ this->SetZeroStorage();
+ }
+
+ void SetDataStorage(VirtualFile storage) {
+ ASSERT(this->IsInitialized());
+
+ this->SetStorage(0, storage);
+ this->SetZeroStorage();
+ }
+
+ template <typename T>
+ void SetDataStorage(T storage, s64 offset, s64 size) {
+ ASSERT(this->IsInitialized());
+
+ this->SetStorage(0, storage, offset, size);
+ this->SetZeroStorage();
+ }
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override;
+
+private:
+ void SetZeroStorage() {
+ return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max());
+ }
+
+private:
+ VirtualFile m_zero_storage;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_switch_storage.h b/src/core/file_sys/fssystem/fssystem_switch_storage.h
new file mode 100644
index 000000000..2b43927cb
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_switch_storage.h
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/fssystem/fs_i_storage.h"
+
+namespace FileSys {
+
+class RegionSwitchStorage : public IReadOnlyStorage {
+ YUZU_NON_COPYABLE(RegionSwitchStorage);
+ YUZU_NON_MOVEABLE(RegionSwitchStorage);
+
+public:
+ struct Region {
+ s64 offset;
+ s64 size;
+ };
+
+public:
+ RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r)
+ : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)),
+ m_region(r) {}
+
+ virtual size_t Read(u8* buffer, size_t size, size_t offset) const override {
+ // Process until we're done.
+ size_t processed = 0;
+ while (processed < size) {
+ // Process on the appropriate storage.
+ s64 cur_size = 0;
+ if (this->CheckRegions(std::addressof(cur_size), offset + processed,
+ size - processed)) {
+ m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed);
+ } else {
+ m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed);
+ }
+
+ // Advance.
+ processed += cur_size;
+ }
+
+ return size;
+ }
+
+ virtual size_t GetSize() const override {
+ return m_inside_region_storage->GetSize();
+ }
+
+private:
+ bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const {
+ // Check if our region contains the access.
+ if (m_region.offset <= offset) {
+ if (offset < m_region.offset + m_region.size) {
+ if (m_region.offset + m_region.size <= offset + size) {
+ *out_current_size = m_region.offset + m_region.size - offset;
+ } else {
+ *out_current_size = size;
+ }
+ return true;
+ } else {
+ *out_current_size = size;
+ return false;
+ }
+ } else {
+ if (m_region.offset <= offset + size) {
+ *out_current_size = m_region.offset - offset;
+ } else {
+ *out_current_size = size;
+ }
+ return false;
+ }
+ }
+
+private:
+ VirtualFile m_inside_region_storage;
+ VirtualFile m_outside_region_storage;
+ Region m_region;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_utility.cpp b/src/core/file_sys/fssystem/fssystem_utility.cpp
new file mode 100644
index 000000000..ceabb8ff1
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_utility.cpp
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fssystem/fssystem_utility.h"
+
+namespace FileSys {
+
+void AddCounter(void* counter_, size_t counter_size, u64 value) {
+ u8* counter = static_cast<u8*>(counter_);
+ u64 remaining = value;
+ u8 carry = 0;
+
+ for (size_t i = 0; i < counter_size; i++) {
+ auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry;
+ carry = static_cast<u8>(sum >> (sizeof(u8) * 8));
+ auto sum8 = static_cast<u8>(sum & 0xFF);
+
+ counter[counter_size - 1 - i] = sum8;
+
+ remaining >>= (sizeof(u8) * 8);
+ if (carry == 0 && remaining == 0) {
+ break;
+ }
+ }
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fssystem/fssystem_utility.h b/src/core/file_sys/fssystem/fssystem_utility.h
new file mode 100644
index 000000000..284b8b811
--- /dev/null
+++ b/src/core/file_sys/fssystem/fssystem_utility.h
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+
+namespace FileSys {
+
+void AddCounter(void* counter, size_t counter_size, u64 value);
+
+}
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index efdf18cee..7be1322cc 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) {
void IPSwitchCompiler::ParseFlag(const std::string& line) {
if (StartsWith(line, "@flag offset_shift ")) {
// Offset Shift Flag
- offset_shift = std::stoll(line.substr(19), nullptr, 0);
+ offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
} else if (StartsWith(line, "@little-endian")) {
// Set values to read as little endian
is_little_endian = true;
@@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
if (patch_line.length() < 11)
break;
- auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
+ auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
offset += static_cast<unsigned long>(offset_shift);
std::vector<u8> replace;
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index 52c78020c..f4a774675 100644
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_,
CNMT::~CNMT() = default;
+const CNMTHeader& CNMT::GetHeader() const {
+ return header;
+}
+
u64 CNMT::GetTitleID() const {
return header.title_id;
}
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index c59ece010..68e463b5f 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -89,6 +89,7 @@ public:
std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_);
~CNMT();
+ const CNMTHeader& GetHeader() const;
u64 GetTitleID() const;
u32 GetTitleVersion() const;
TitleType GetType() const;
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
deleted file mode 100644
index 2735d053b..000000000
--- a/src/core/file_sys/nca_patch.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <algorithm>
-#include <array>
-#include <cstddef>
-#include <cstring>
-
-#include "common/assert.h"
-#include "core/crypto/aes_util.h"
-#include "core/file_sys/nca_patch.h"
-
-namespace FileSys {
-namespace {
-template <bool Subsection, typename BlockType, typename BucketType>
-std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block,
- const BucketType& buckets) {
- if constexpr (Subsection) {
- const auto& last_bucket = buckets[block.number_buckets - 1];
- if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) {
- return {block.number_buckets - 1, last_bucket.number_entries};
- }
- } else {
- ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
- }
-
- std::size_t bucket_id = std::count_if(
- block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
- [&offset](u64 base_offset) { return base_offset <= offset; });
-
- const auto& bucket = buckets[bucket_id];
-
- if (bucket.number_entries == 1) {
- return {bucket_id, 0};
- }
-
- std::size_t low = 0;
- std::size_t mid = 0;
- std::size_t high = bucket.number_entries - 1;
- while (low <= high) {
- mid = (low + high) / 2;
- if (bucket.entries[mid].address_patch > offset) {
- high = mid - 1;
- } else {
- if (mid == bucket.number_entries - 1 ||
- bucket.entries[mid + 1].address_patch > offset) {
- return {bucket_id, mid};
- }
-
- low = mid + 1;
- }
- }
- ASSERT_MSG(false, "Offset could not be found in BKTR block.");
- return {0, 0};
-}
-} // Anonymous namespace
-
-BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
- std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
- std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
- Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
- std::array<u8, 8> section_ctr_)
- : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
- subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
- base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
- encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
- section_ctr(section_ctr_) {
- for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) {
- relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0});
- }
-
- for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) {
- subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch,
- {0},
- subsection_buckets[i + 1].entries[0].ctr});
- }
-
- relocation_buckets.back().entries.push_back({relocation.size, 0, 0});
-}
-
-BKTR::~BKTR() = default;
-
-std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
- // Read out of bounds.
- if (offset >= relocation.size) {
- return 0;
- }
-
- const auto relocation_entry = GetRelocationEntry(offset);
- const auto section_offset =
- offset - relocation_entry.address_patch + relocation_entry.address_source;
- const auto bktr_read = relocation_entry.from_patch;
-
- const auto next_relocation = GetNextRelocationEntry(offset);
-
- if (offset + length > next_relocation.address_patch) {
- const u64 partition = next_relocation.address_patch - offset;
- return Read(data, partition, offset) +
- Read(data + partition, length - partition, offset + partition);
- }
-
- if (!bktr_read) {
- ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative.");
- return base_romfs->Read(data, length, section_offset - ivfc_offset);
- }
-
- if (!encrypted) {
- return bktr_romfs->Read(data, length, section_offset);
- }
-
- const auto subsection_entry = GetSubsectionEntry(section_offset);
- Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
-
- // Calculate AES IV
- std::array<u8, 16> iv{};
- auto subsection_ctr = subsection_entry.ctr;
- auto offset_iv = section_offset + base_offset;
- for (std::size_t i = 0; i < section_ctr.size(); ++i) {
- iv[i] = section_ctr[0x8 - i - 1];
- }
- offset_iv >>= 4;
- for (std::size_t i = 0; i < sizeof(u64); ++i) {
- iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF);
- offset_iv >>= 8;
- }
- for (std::size_t i = 0; i < sizeof(u32); ++i) {
- iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF);
- subsection_ctr >>= 8;
- }
- cipher.SetIV(iv);
-
- const auto next_subsection = GetNextSubsectionEntry(section_offset);
-
- if (section_offset + length > next_subsection.address_patch) {
- const u64 partition = next_subsection.address_patch - section_offset;
- return Read(data, partition, offset) +
- Read(data + partition, length - partition, offset + partition);
- }
-
- const auto block_offset = section_offset & 0xF;
- if (block_offset != 0) {
- auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF);
- cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt);
- if (length + block_offset < 0x10) {
- std::memcpy(data, block.data() + block_offset, std::min(length, block.size()));
- return std::min(length, block.size());
- }
-
- const auto read = 0x10 - block_offset;
- std::memcpy(data, block.data() + block_offset, read);
- return read + Read(data + read, length - read, offset + read);
- }
-
- const auto raw_read = bktr_romfs->Read(data, length, section_offset);
- cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt);
- return raw_read;
-}
-
-RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
- const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
- return relocation_buckets[res.first].entries[res.second];
-}
-
-RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const {
- const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
- const auto bucket = relocation_buckets[res.first];
- if (res.second + 1 < bucket.entries.size())
- return bucket.entries[res.second + 1];
- return relocation_buckets[res.first + 1].entries[0];
-}
-
-SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const {
- const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
- return subsection_buckets[res.first].entries[res.second];
-}
-
-SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const {
- const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
- const auto bucket = subsection_buckets[res.first];
- if (res.second + 1 < bucket.entries.size())
- return bucket.entries[res.second + 1];
- return subsection_buckets[res.first + 1].entries[0];
-}
-
-std::string BKTR::GetName() const {
- return base_romfs->GetName();
-}
-
-std::size_t BKTR::GetSize() const {
- return relocation.size;
-}
-
-bool BKTR::Resize(std::size_t new_size) {
- return false;
-}
-
-VirtualDir BKTR::GetContainingDirectory() const {
- return base_romfs->GetContainingDirectory();
-}
-
-bool BKTR::IsWritable() const {
- return false;
-}
-
-bool BKTR::IsReadable() const {
- return true;
-}
-
-std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) {
- return 0;
-}
-
-bool BKTR::Rename(std::string_view name) {
- return base_romfs->Rename(name);
-}
-
-} // namespace FileSys
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h
deleted file mode 100644
index 595e3ef09..000000000
--- a/src/core/file_sys/nca_patch.h
+++ /dev/null
@@ -1,145 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <memory>
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "core/crypto/key_manager.h"
-
-namespace FileSys {
-
-#pragma pack(push, 1)
-struct RelocationEntry {
- u64_le address_patch;
- u64_le address_source;
- u32 from_patch;
-};
-#pragma pack(pop)
-static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
-
-struct RelocationBucketRaw {
- INSERT_PADDING_BYTES(4);
- u32_le number_entries;
- u64_le end_offset;
- std::array<RelocationEntry, 0x332> relocation_entries;
- INSERT_PADDING_BYTES(8);
-};
-static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
-
-// Vector version of RelocationBucketRaw
-struct RelocationBucket {
- u32 number_entries;
- u64 end_offset;
- std::vector<RelocationEntry> entries;
-};
-
-struct RelocationBlock {
- INSERT_PADDING_BYTES(4);
- u32_le number_buckets;
- u64_le size;
- std::array<u64, 0x7FE> base_offsets;
-};
-static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
-
-struct SubsectionEntry {
- u64_le address_patch;
- INSERT_PADDING_BYTES(0x4);
- u32_le ctr;
-};
-static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
-
-struct SubsectionBucketRaw {
- INSERT_PADDING_BYTES(4);
- u32_le number_entries;
- u64_le end_offset;
- std::array<SubsectionEntry, 0x3FF> subsection_entries;
-};
-static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
-
-// Vector version of SubsectionBucketRaw
-struct SubsectionBucket {
- u32 number_entries;
- u64 end_offset;
- std::vector<SubsectionEntry> entries;
-};
-
-struct SubsectionBlock {
- INSERT_PADDING_BYTES(4);
- u32_le number_buckets;
- u64_le size;
- std::array<u64, 0x7FE> base_offsets;
-};
-static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
-
-inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) {
- return {raw.number_entries,
- raw.end_offset,
- {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}};
-}
-
-inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) {
- return {raw.number_entries,
- raw.end_offset,
- {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}};
-}
-
-class BKTR : public VfsFile {
-public:
- BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation,
- std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection,
- std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted,
- Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr);
- ~BKTR() override;
-
- std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
-
- std::string GetName() const override;
-
- std::size_t GetSize() const override;
-
- bool Resize(std::size_t new_size) override;
-
- VirtualDir GetContainingDirectory() const override;
-
- bool IsWritable() const override;
-
- bool IsReadable() const override;
-
- std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
-
- bool Rename(std::string_view name) override;
-
-private:
- RelocationEntry GetRelocationEntry(u64 offset) const;
- RelocationEntry GetNextRelocationEntry(u64 offset) const;
-
- SubsectionEntry GetSubsectionEntry(u64 offset) const;
- SubsectionEntry GetNextSubsectionEntry(u64 offset) const;
-
- RelocationBlock relocation;
- std::vector<RelocationBucket> relocation_buckets;
- SubsectionBlock subsection;
- std::vector<SubsectionBucket> subsection_buckets;
-
- // Should be the raw base romfs, decrypted.
- VirtualFile base_romfs;
- // Should be the raw BKTR romfs, (located at media_offset with size media_size).
- VirtualFile bktr_romfs;
-
- bool encrypted;
- Core::Crypto::Key128 key;
-
- // Base offset into NCA, used for IV calculation.
- u64 base_offset;
- // Distance between IVFC start and RomFS start, used for base reads
- u64 ivfc_offset;
- std::array<u8, 8> section_ctr;
-};
-
-} // namespace FileSys
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 2527ae606..2422cb51b 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -47,6 +47,7 @@ PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
// Actually read in now...
std::vector<u8> file_data = file->ReadBytes(metadata_size);
const std::size_t total_size = file_data.size();
+ file_data.push_back(0);
if (total_size != metadata_size) {
status = Loader::ResultStatus::ErrorIncorrectPFSFileSize;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index d3286b352..8e475f25a 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -141,8 +141,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
- if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
- update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
exefs = update->GetExeFS();
@@ -295,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
return out;
}
-bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
+bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
const auto build_id_raw = Common::HexToString(build_id_);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
- LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
+ LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
@@ -353,16 +352,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
const Service::FileSystem::FileSystemController& fs_controller) {
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
- if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
+ if ((type != ContentRecordType::Program && type != ContentRecordType::Data &&
+ type != ContentRecordType::HtmlDocument) ||
(load_dir == nullptr && sdmc_load_dir == nullptr)) {
return;
}
- auto extracted = ExtractRomFS(romfs);
- if (extracted == nullptr) {
- return;
- }
-
const auto& disabled = Settings::values.disabled_addons[title_id];
std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
@@ -387,6 +382,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext");
if (ext_dir != nullptr)
layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir));
+
+ if (type == ContentRecordType::HtmlDocument) {
+ auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html");
+ if (manual_dir != nullptr)
+ layers.push_back(std::make_shared<CachedVfsDirectory>(manual_dir));
+ }
}
// When there are no layers to apply, return early as there is no need to rebuild the RomFS
@@ -394,6 +395,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
+ auto extracted = ExtractRomFS(romfs);
+ if (extracted == nullptr) {
+ return;
+ }
+
layers.push_back(std::move(extracted));
auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
@@ -412,39 +418,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
romfs = std::move(packed);
}
-VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
- VirtualFile update_raw, bool apply_layeredfs) const {
+VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs,
+ ContentRecordType type, VirtualFile packed_update_raw,
+ bool apply_layeredfs) const {
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
title_id, static_cast<u8>(type));
-
if (type == ContentRecordType::Program || type == ContentRecordType::Data) {
LOG_INFO(Loader, "{}", log_string);
} else {
LOG_DEBUG(Loader, "{}", log_string);
}
- if (romfs == nullptr) {
- return romfs;
+ if (base_romfs == nullptr) {
+ return base_romfs;
}
+ auto romfs = base_romfs;
+
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- const auto update = content_provider.GetEntryRaw(update_tid, type);
+ const auto update_raw = content_provider.GetEntryRaw(update_tid, type);
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
- if (!update_disabled && update != nullptr) {
- const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
+ if (!update_disabled && update_raw != nullptr && base_nca != nullptr) {
+ const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
+ const auto version =
+ FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0));
}
- } else if (!update_disabled && update_raw != nullptr) {
- const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
+ } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
+ const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
@@ -608,7 +618,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
return {};
}
- const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
+ const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control);
if (romfs == nullptr) {
return {};
}
@@ -626,8 +636,8 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
// Get language code from settings
- const auto language_code =
- Service::Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue());
+ const auto language_code = Service::Set::GetLanguageCodeFromIndex(
+ static_cast<u32>(Settings::values.language_index.GetValue()));
// Convert to application language and get priority list
const auto application_language =
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 69d15e2f8..03e9c7301 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -52,7 +52,7 @@ public:
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
// Used to prevent expensive copies in NSO loader.
- [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
+ [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
// Creates a CheatList object with all
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
@@ -61,9 +61,9 @@ public:
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
- [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
+ [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs,
ContentRecordType type = ContentRecordType::Program,
- VirtualFile update_raw = nullptr,
+ VirtualFile packed_update_raw = nullptr,
bool apply_layeredfs = true) const;
// Returns a vector of pairs between patch names and patch versions.
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index a6960170c..04da93d5c 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -9,6 +9,7 @@
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
@@ -416,9 +417,9 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
if (file == nullptr)
continue;
- const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0);
+ const auto nca = std::make_shared<NCA>(parser(file, id));
if (nca->GetStatus() != Loader::ResultStatus::Success ||
- nca->GetType() != NCAContentType::Meta) {
+ nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) {
continue;
}
@@ -500,7 +501,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
- return std::make_unique<NCA>(raw, nullptr, 0);
+ return std::make_unique<NCA>(raw);
}
template <typename T>
@@ -606,9 +607,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
const auto result = RemoveExistingEntry(title_id);
// Install Metadata File
- const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data);
- if (res != InstallResult::Success) {
- return res;
+ const auto meta_result = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data);
+ if (meta_result != InstallResult::Success) {
+ return meta_result;
}
// Install all the other NCAs
@@ -621,9 +622,19 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
if (nca == nullptr) {
return InstallResult::ErrorCopyFailed;
}
- const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id);
- if (res2 != InstallResult::Success) {
- return res2;
+ if (nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
+ nca->GetTitleId() != title_id) {
+ // Create fake cnmt for patch to multiprogram application
+ const auto sub_nca_result =
+ InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy);
+ if (sub_nca_result != InstallResult::Success) {
+ return sub_nca_result;
+ }
+ continue;
+ }
+ const auto nca_result = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id);
+ if (nca_result != InstallResult::Success) {
+ return nca_result;
}
}
@@ -662,7 +673,34 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
}
+InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header,
+ const ContentRecord& base_record,
+ bool overwrite_if_exists, const VfsCopyFunction& copy) {
+ const CNMTHeader header{
+ .title_id = nca.GetTitleId(),
+ .title_version = base_header.title_version,
+ .type = base_header.type,
+ .reserved = {},
+ .table_offset = 0x10,
+ .number_content_entries = 1,
+ .number_meta_entries = 0,
+ .attributes = 0,
+ .reserved2 = {},
+ .is_committed = 0,
+ .required_download_system_version = 0,
+ .reserved3 = {},
+ };
+ const OptionalHeader opt_header{0, 0};
+ const CNMT new_cnmt(header, opt_header, {base_record}, {});
+ if (!RawInstallYuzuMeta(new_cnmt)) {
+ return InstallResult::ErrorMetaFailed;
+ }
+ return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id);
+}
+
bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
+ bool removed_data = false;
+
const auto delete_nca = [this](const NcaID& id) {
const auto path = GetRelativePathFromNcaID(id, false, true, false);
@@ -706,11 +744,20 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
const auto deleted_html = delete_nca(html_id);
const auto deleted_legal = delete_nca(legal_id);
- return deleted_meta && (deleted_meta || deleted_program || deleted_data ||
- deleted_control || deleted_html || deleted_legal);
+ removed_data |= (deleted_meta || deleted_program || deleted_data || deleted_control ||
+ deleted_html || deleted_legal);
}
- return false;
+ // If patch entries for any program exist in yuzu meta, remove them
+ for (u8 i = 0; i < 0x10; i++) {
+ const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta");
+ const auto filename = GetCNMTName(TitleType::Update, title_id + i);
+ if (meta_dir->GetFile(filename)) {
+ removed_data |= meta_dir->DeleteFile(filename);
+ }
+ }
+
+ return removed_data;
}
InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
@@ -964,7 +1011,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord
const auto res = GetEntryRaw(title_id, type);
if (res == nullptr)
return nullptr;
- return std::make_unique<NCA>(res, nullptr, 0);
+ return std::make_unique<NCA>(res);
}
std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index bd7f53eaf..64815a845 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -24,6 +24,7 @@ enum class NCAContentType : u8;
enum class TitleType : u8;
struct ContentRecord;
+struct CNMTHeader;
struct MetaRecord;
class RegisteredCache;
@@ -169,6 +170,10 @@ public:
InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
+ InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header,
+ const ContentRecord& base_record, bool overwrite_if_exists = false,
+ const VfsCopyFunction& copy = &VfsRawCopy);
+
// Removes an existing entry based on title id
bool RemoveExistingEntry(u64 title_id) const;
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index ae7a3511b..1bc07dae5 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -26,60 +26,52 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi
}
updatable = app_loader.IsRomFSUpdatable();
- ivfc_offset = app_loader.ReadRomFSIVFCOffset();
}
RomFSFactory::~RomFSFactory() = default;
void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) {
- update_raw = std::move(update_raw_file);
+ packed_update_raw = std::move(update_raw_file);
}
-ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
+VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
if (!updatable) {
return file;
}
+ const auto type = ContentRecordType::Program;
+ const auto nca = content_provider.GetEntry(current_process_title_id, type);
const PatchManager patch_manager{current_process_title_id, filesystem_controller,
content_provider};
- return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw);
+ return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw);
}
-ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
+VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
auto nca = content_provider.GetEntry(title_id, type);
if (nca == nullptr) {
- // TODO: Find the right error code to use here
- return ResultUnknown;
+ return nullptr;
}
const PatchManager patch_manager{title_id, filesystem_controller, content_provider};
- return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type);
+ return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type);
}
-ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFSWithProgramIndex(
- u64 title_id, u8 program_index, ContentRecordType type) const {
+VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
+ ContentRecordType type) const {
const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index);
return OpenPatchedRomFS(res_title_id, type);
}
-ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
- ContentRecordType type) const {
+VirtualFile RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) const {
const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type);
if (res == nullptr) {
- // TODO(DarkLordZach): Find the right error code to use here
- return ResultUnknown;
- }
-
- const auto romfs = res->GetRomFS();
- if (romfs == nullptr) {
- // TODO(DarkLordZach): Find the right error code to use here
- return ResultUnknown;
+ return nullptr;
}
- return romfs;
+ return res->GetRomFS();
}
std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage,
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 14936031f..e4809bc94 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -40,23 +40,22 @@ public:
Service::FileSystem::FileSystemController& controller);
~RomFSFactory();
- void SetPackedUpdate(VirtualFile update_raw_file);
- [[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
- [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFS(u64 title_id,
- ContentRecordType type) const;
- [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFSWithProgramIndex(
- u64 title_id, u8 program_index, ContentRecordType type) const;
- [[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage,
- ContentRecordType type) const;
-
-private:
+ void SetPackedUpdate(VirtualFile packed_update_raw);
+ [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const;
+ [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const;
+ [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
+ ContentRecordType type) const;
+ [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const;
[[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage,
ContentRecordType type) const;
+private:
VirtualFile file;
- VirtualFile update_raw;
+ VirtualFile packed_update_raw;
+
+ VirtualFile base;
+
bool updatable;
- u64 ivfc_offset;
ContentProvider& content_provider;
Service::FileSystem::FileSystemController& filesystem_controller;
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 70b36f170..a4d060007 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -108,26 +108,16 @@ SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_director
SaveDataFactory::~SaveDataFactory() = default;
-ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
- const SaveDataAttribute& meta) const {
+VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
PrintSaveDataAttributeWarnings(meta);
const auto save_directory =
GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
- auto out = dir->CreateDirectoryRelative(save_directory);
-
- // Return an error if the save data doesn't actually exist.
- if (out == nullptr) {
- // TODO(DarkLordZach): Find out correct error code.
- return ResultUnknown;
- }
-
- return out;
+ return dir->CreateDirectoryRelative(save_directory);
}
-ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
- const SaveDataAttribute& meta) const {
+VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
const auto save_directory =
GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -138,12 +128,6 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
return Create(space, meta);
}
- // Return an error if the save data doesn't actually exist.
- if (out == nullptr) {
- // TODO(Subv): Find out correct error code.
- return ResultUnknown;
- }
-
return out;
}
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index d3633ef03..45c7c81fb 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -89,8 +89,8 @@ public:
explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_);
~SaveDataFactory();
- ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
- ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
+ VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
+ VirtualDir Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index 1df022c9e..d5158cd64 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -23,7 +23,7 @@ SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_)
SDMCFactory::~SDMCFactory() = default;
-ResultVal<VirtualDir> SDMCFactory::Open() const {
+VirtualDir SDMCFactory::Open() const {
return sd_dir;
}
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 3aebfb25e..a445fdb16 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -18,7 +18,7 @@ public:
explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_);
~SDMCFactory();
- ResultVal<VirtualDir> Open() const;
+ VirtualDir Open() const;
VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
VirtualDir GetSDMCContentDirectory() const;
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index c90e6e372..68e8ec22f 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -164,24 +164,6 @@ VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType titl
return nullptr;
}
-std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const {
- if (extracted)
- LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
- std::vector<Core::Crypto::Key128> out;
- for (const auto& ticket_file : ticket_files) {
- if (ticket_file == nullptr ||
- ticket_file->GetSize() <
- Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
- continue;
- }
-
- out.emplace_back();
- ticket_file->Read(out.back().data(), out.back().size(),
- Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
- }
- return out;
-}
-
std::vector<VirtualFile> NSP::GetFiles() const {
return pfs->GetFiles();
}
@@ -208,22 +190,11 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) {
continue;
}
- if (ticket_file->GetSize() <
- Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
+ auto ticket = Core::Crypto::Ticket::Read(ticket_file);
+ if (!keys.AddTicket(ticket)) {
+ LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName());
continue;
}
-
- Core::Crypto::Key128 key{};
- ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
-
- // We get the name without the extension in order to create the rights ID.
- std::string name_only(ticket_file->GetName());
- name_only.erase(name_only.size() - 4);
-
- const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
- u128 rights_id;
- std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
- keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
@@ -249,7 +220,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
}
const auto nca = std::make_shared<NCA>(outer_file);
- if (nca->GetStatus() != Loader::ResultStatus::Success) {
+ if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) {
program_status[nca->GetTitleId()] = nca->GetStatus();
continue;
}
@@ -280,7 +251,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
continue;
}
- auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0);
+ auto next_nca = std::make_shared<NCA>(std::move(next_file));
if (next_nca->GetType() == NCAContentType::Program) {
program_status[next_nca->GetTitleId()] = next_nca->GetStatus();
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index 27f97c725..915bffca9 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -53,7 +53,6 @@ public:
TitleType title_type = TitleType::Application) const;
VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
TitleType title_type = TitleType::Application) const;
- std::vector<Core::Crypto::Key128> GetTitlekey() const;
std::vector<VirtualFile> GetFiles() const override;
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
index 3300d4f79..27755cb58 100644
--- a/src/core/frontend/applets/controller.cpp
+++ b/src/core/frontend/applets/controller.cpp
@@ -3,6 +3,8 @@
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/frontend/applets/controller.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -62,7 +64,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac
controller->Connect(true);
}
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
- !Settings::values.use_docked_mode.GetValue()) {
+ !Settings::IsDockedMode()) {
// We should *never* reach here under any normal circumstances.
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
controller->Connect(true);
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index b4081fc39..2590b20da 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/frontend/framebuffer_layout.h"
namespace Layout {
@@ -49,7 +50,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
}
FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
+ const bool is_docked = Settings::IsDockedMode();
const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width;
const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height;
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp
index 7d6373414..cf53c04d9 100644
--- a/src/core/hid/hid_core.cpp
+++ b/src/core/hid/hid_core.cpp
@@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
return NpadIdType::Player1;
}
+void HIDCore::SetLastActiveController(NpadIdType npad_id) {
+ last_active_controller = npad_id;
+}
+
+NpadIdType HIDCore::GetLastActiveController() const {
+ return last_active_controller;
+}
+
void HIDCore::EnableAllControllerConfiguration() {
player_1->EnableConfiguration();
player_2->EnableConfiguration();
diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h
index 5fe36551e..80abab18b 100644
--- a/src/core/hid/hid_core.h
+++ b/src/core/hid/hid_core.h
@@ -48,6 +48,12 @@ public:
/// Returns the first disconnected npad id
NpadIdType GetFirstDisconnectedNpadId() const;
+ /// Sets the npad id of the last active controller
+ void SetLastActiveController(NpadIdType npad_id);
+
+ /// Returns the npad id of the last controller that pushed a button
+ NpadIdType GetLastActiveController() const;
+
/// Sets all emulated controllers into configuring mode.
void EnableAllControllerConfiguration();
@@ -77,6 +83,7 @@ private:
std::unique_ptr<EmulatedConsole> console;
std::unique_ptr<EmulatedDevices> devices;
NpadStyleTag supported_style_tag{NpadStyleSet::All};
+ NpadIdType last_active_controller{NpadIdType::Handheld};
};
} // namespace Core::HID
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index 6b35f448c..00beb40dd 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -289,6 +289,19 @@ enum class GyroscopeZeroDriftMode : u32 {
Tight = 2,
};
+// This is nn::settings::system::TouchScreenMode
+enum class TouchScreenMode : u32 {
+ Stylus = 0,
+ Standard = 1,
+};
+
+// This is nn::hid::TouchScreenModeForNx
+enum class TouchScreenModeForNx : u8 {
+ UseSystemSetting,
+ Finger,
+ Heat2,
+};
+
// This is nn::hid::NpadStyleTag
struct NpadStyleTag {
union {
@@ -334,6 +347,14 @@ struct TouchState {
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
+// This is nn::hid::TouchScreenConfigurationForNx
+struct TouchScreenConfigurationForNx {
+ TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
+ INSERT_PADDING_BYTES(0xF);
+};
+static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10,
+ "TouchScreenConfigurationForNx is an invalid size");
+
struct NpadColor {
u8 r{};
u8 g{};
@@ -662,6 +683,11 @@ struct MouseState {
};
static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
+struct UniquePadId {
+ u64 id;
+};
+static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size");
+
/// Converts a NpadIdType to an array index.
constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
switch (npad_id_type) {
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
index 49bdc671e..4cfdf4558 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
@@ -35,13 +35,27 @@ namespace {
using namespace Common::Literals;
u32 GetMemorySizeForInit() {
- return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemorySize_8GB
- : Smc::MemorySize_4GB;
+ switch (Settings::values.memory_layout_mode.GetValue()) {
+ case Settings::MemoryLayout::Memory_4Gb:
+ return Smc::MemorySize_4GB;
+ case Settings::MemoryLayout::Memory_6Gb:
+ return Smc::MemorySize_6GB;
+ case Settings::MemoryLayout::Memory_8Gb:
+ return Smc::MemorySize_8GB;
+ }
+ return Smc::MemorySize_4GB;
}
Smc::MemoryArrangement GetMemoryArrangeForInit() {
- return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemoryArrangement_8GB
- : Smc::MemoryArrangement_4GB;
+ switch (Settings::values.memory_layout_mode.GetValue()) {
+ case Settings::MemoryLayout::Memory_4Gb:
+ return Smc::MemoryArrangement_4GB;
+ case Settings::MemoryLayout::Memory_6Gb:
+ return Smc::MemoryArrangement_6GB;
+ case Settings::MemoryLayout::Memory_8Gb:
+ return Smc::MemoryArrangement_8GB;
+ }
+ return Smc::MemoryArrangement_4GB;
}
} // namespace
diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp
index 90e4e8fb0..e7da7a21d 100644
--- a/src/core/hle/kernel/k_capabilities.cpp
+++ b/src/core/hle/kernel/k_capabilities.cpp
@@ -156,7 +156,6 @@ Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) {
const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize;
const size_t num_pages = 1;
const size_t size = num_pages * PageSize;
- R_UNLESS(num_pages != 0, ResultInvalidSize);
R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress);
R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress);
diff --git a/src/core/hle/kernel/k_hardware_timer.h b/src/core/hle/kernel/k_hardware_timer.h
index 00bef6ea1..27f43cd19 100644
--- a/src/core/hle/kernel/k_hardware_timer.h
+++ b/src/core/hle/kernel/k_hardware_timer.h
@@ -19,13 +19,7 @@ public:
void Initialize();
void Finalize();
- s64 GetCount() const {
- return GetTick();
- }
-
- void RegisterTask(KTimerTask* task, s64 time_from_now) {
- this->RegisterAbsoluteTask(task, GetTick() + time_from_now);
- }
+ s64 GetTick() const;
void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) {
KScopedDisableDispatch dd{m_kernel};
@@ -42,7 +36,6 @@ private:
void EnableInterrupt(s64 wakeup_time);
void DisableInterrupt();
bool GetInterruptEnabled();
- s64 GetTick() const;
void DoTask();
private:
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 02b5cada4..9bfc85b34 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -768,7 +768,7 @@ Result KPageTable::UnmapProcessMemory(KProcessAddress dst_addr, size_t size,
m_memory_block_slab_manager, num_allocator_blocks);
R_TRY(allocator_result);
- CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
+ R_TRY(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
// Apply the memory block update.
m_memory_block_manager.Update(std::addressof(allocator), dst_addr, num_pages,
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 44c7cb22f..4a099286b 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -38,7 +38,7 @@ namespace {
*/
void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority,
KProcessAddress stack_top) {
- const KProcessAddress entry_point = owner_process.GetPageTable().GetCodeRegionStart();
+ const KProcessAddress entry_point = owner_process.GetEntryPoint();
ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1));
KThread* thread = KThread::Create(system.Kernel());
@@ -81,7 +81,8 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
process->m_capabilities.InitializeForMetadatalessProcess();
process->m_is_initialized = true;
- std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr)));
+ std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
+ : static_cast<u32>(std::time(nullptr)));
std::uniform_int_distribution<u64> distribution;
std::generate(process->m_random_entropy.begin(), process->m_random_entropy.end(),
[&] { return distribution(rng); });
@@ -95,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
process->m_is_suspended = false;
process->m_schedule_count = 0;
process->m_is_handle_table_initialized = false;
+ process->m_is_hbl = false;
// Open a reference to the resource limit.
process->m_resource_limit->Open();
@@ -350,12 +352,29 @@ Result KProcess::SetActivity(ProcessActivity activity) {
R_SUCCEED();
}
-Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) {
+Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
+ bool is_hbl) {
m_program_id = metadata.GetTitleID();
m_ideal_core = metadata.GetMainThreadCore();
m_is_64bit_process = metadata.Is64BitProgram();
m_system_resource_size = metadata.GetSystemResourceSize();
m_image_size = code_size;
+ m_is_hbl = is_hbl;
+
+ if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
+ // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
+ // However, some (buggy) programs/libraries like skyline incorrectly depend on the
+ // existence of ASLR pages before the entry point, so we will adjust the load address
+ // to point to about 2GiB into the ASLR region.
+ m_code_address = 0x8000'0000;
+ } else {
+ // All other processes can be mapped at the beginning of the code region.
+ if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) {
+ m_code_address = 0x800'0000;
+ } else {
+ m_code_address = 0x20'0000;
+ }
+ }
KScopedResourceReservation memory_reservation(
m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size);
@@ -367,15 +386,15 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
// Initialize process address space
if (const Result result{m_page_table.InitializeForProcess(
metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application,
- 0x8000000, code_size, std::addressof(m_kernel.GetAppSystemResource()), m_resource_limit,
- m_kernel.System().ApplicationMemory())};
+ this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()),
+ m_resource_limit, m_kernel.System().ApplicationMemory())};
result.IsError()) {
R_RETURN(result);
}
// Map process code region
- if (const Result result{m_page_table.MapProcessCode(m_page_table.GetCodeRegionStart(),
- code_size / PageSize, KMemoryState::Code,
+ if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize,
+ KMemoryState::Code,
KMemoryPermission::None)};
result.IsError()) {
R_RETURN(result);
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index c9b37e138..146e07a57 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -177,6 +177,10 @@ public:
return m_program_id;
}
+ KProcessAddress GetEntryPoint() const {
+ return m_code_address;
+ }
+
/// Gets the resource limit descriptor for this process
KResourceLimit* GetResourceLimit() const;
@@ -334,7 +338,8 @@ public:
* @returns ResultSuccess if all relevant metadata was able to be
* loaded and parsed. Otherwise, an error code is returned.
*/
- Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size);
+ Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
+ bool is_hbl);
/**
* Starts the main application thread for this process.
@@ -364,6 +369,10 @@ public:
return GetProcessId();
}
+ bool IsHbl() const {
+ return m_is_hbl;
+ }
+
bool IsSignaled() const override;
void DoWorkerTaskImpl();
@@ -485,6 +494,9 @@ private:
/// Address indicating the location of the process' dedicated TLS region.
KProcessAddress m_plr_address = 0;
+ /// Address indicating the location of the process's entry point.
+ KProcessAddress m_code_address = 0;
+
/// Random values for svcGetInfo RandomEntropy
std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{};
@@ -518,6 +530,7 @@ private:
bool m_is_immortal{};
bool m_is_handle_table_initialized{};
bool m_is_initialized{};
+ bool m_is_hbl{};
std::atomic<u16> m_num_running_threads{};
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp
index fcee26a29..d8a63aaf8 100644
--- a/src/core/hle/kernel/k_resource_limit.cpp
+++ b/src/core/hle/kernel/k_resource_limit.cpp
@@ -5,6 +5,7 @@
#include "common/overflow.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/svc_results.h"
@@ -15,9 +16,7 @@ KResourceLimit::KResourceLimit(KernelCore& kernel)
: KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {}
KResourceLimit::~KResourceLimit() = default;
-void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) {
- m_core_timing = core_timing;
-}
+void KResourceLimit::Initialize() {}
void KResourceLimit::Finalize() {}
@@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
}
bool KResourceLimit::Reserve(LimitableResource which, s64 value) {
- return Reserve(which, value, m_core_timing->GetGlobalTimeNs().count() + DefaultTimeout);
+ return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout);
}
bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) {
@@ -117,7 +116,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) {
}
if (m_current_hints[index] + value <= m_limit_values[index] &&
- (timeout < 0 || m_core_timing->GetGlobalTimeNs().count() < timeout)) {
+ (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) {
m_waiter_count++;
m_cond_var.Wait(std::addressof(m_lock), timeout, false);
m_waiter_count--;
@@ -154,7 +153,7 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) {
KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) {
auto* resource_limit = KResourceLimit::Create(system.Kernel());
- resource_limit->Initialize(std::addressof(system.CoreTiming()));
+ resource_limit->Initialize();
// Initialize default resource limit values.
// TODO(bunnei): These values are the system defaults, the limits for service processes are
diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h
index 15e69af56..b733ec8f8 100644
--- a/src/core/hle/kernel/k_resource_limit.h
+++ b/src/core/hle/kernel/k_resource_limit.h
@@ -31,7 +31,7 @@ public:
explicit KResourceLimit(KernelCore& kernel);
~KResourceLimit() override;
- void Initialize(const Core::Timing::CoreTiming* core_timing);
+ void Initialize();
void Finalize() override;
s64 GetLimitValue(LimitableResource which) const;
@@ -57,7 +57,6 @@ private:
mutable KLightLock m_lock;
s32 m_waiter_count{};
KLightConditionVariable m_cond_var;
- const Core::Timing::CoreTiming* m_core_timing{};
};
KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size);
diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
index c485022f5..b62415da7 100644
--- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
+++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
@@ -28,7 +28,7 @@ public:
~KScopedSchedulerLockAndSleep() {
// Register the sleep.
if (m_timeout_tick > 0) {
- m_timer->RegisterTask(m_thread, m_timeout_tick);
+ m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick);
}
// Unlock the scheduler.
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index ebe7582c6..a1134b7e2 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -231,7 +231,7 @@ struct KernelCore::Impl {
void InitializeSystemResourceLimit(KernelCore& kernel,
const Core::Timing::CoreTiming& core_timing) {
system_resource_limit = KResourceLimit::Create(system.Kernel());
- system_resource_limit->Initialize(&core_timing);
+ system_resource_limit->Initialize();
KResourceLimit::Register(kernel, system_resource_limit);
const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()};
diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp
index 04cc5ea64..90ee43521 100644
--- a/src/core/hle/kernel/svc/svc_address_arbiter.cpp
+++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/kernel.h"
@@ -52,7 +53,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ
if (timeout_ns > 0) {
const s64 offset_tick(timeout_ns);
if (offset_tick > 0) {
- timeout = offset_tick + 2;
+ timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2;
if (timeout <= 0) {
timeout = std::numeric_limits<s64>::max();
}
diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp
index ca120d67e..bb678e6c5 100644
--- a/src/core/hle/kernel/svc/svc_condition_variable.cpp
+++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/kernel.h"
@@ -25,7 +26,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u
if (timeout_ns > 0) {
const s64 offset_tick(timeout_ns);
if (offset_tick > 0) {
- timeout = offset_tick + 2;
+ timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2;
if (timeout <= 0) {
timeout = std::numeric_limits<s64>::max();
}
diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp
index 4c14ce668..00b65429b 100644
--- a/src/core/hle/kernel/svc/svc_debug_string.cpp
+++ b/src/core/hle/kernel/svc/svc_debug_string.cpp
@@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) {
std::string str(len, '\0');
GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size());
- LOG_DEBUG(Debug_Emulated, "{}", str);
+ LOG_INFO(Debug_Emulated, "{}", str);
R_SUCCEED();
}
diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp
index 580cf2f75..c581c086b 100644
--- a/src/core/hle/kernel/svc/svc_exception.cpp
+++ b/src/core/hle/kernel/svc/svc_exception.cpp
@@ -3,6 +3,7 @@
#include "core/core.h"
#include "core/debugger/debugger.h"
+#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_types.h"
@@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
}
- if (system.DebuggerEnabled()) {
+ const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl();
+ const bool should_break = is_hbl || !notification_only;
+
+ if (system.DebuggerEnabled() && should_break) {
auto* thread = system.Kernel().GetCurrentEmuThread();
system.GetDebugger().NotifyThreadStopped(thread);
thread->RequestSuspend(Kernel::SuspendType::Debug);
diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp
index 373ae7c8d..6b5e1cb8d 100644
--- a/src/core/hle/kernel/svc/svc_ipc.cpp
+++ b/src/core/hle/kernel/svc/svc_ipc.cpp
@@ -5,6 +5,7 @@
#include "common/scratch_buffer.h"
#include "core/core.h"
#include "core/hle/kernel/k_client_session.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/svc.h"
@@ -82,12 +83,29 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad
R_TRY(session->SendReply());
}
+ // Convert the timeout from nanoseconds to ticks.
+ // NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
+ s64 timeout;
+ if (timeout_ns > 0) {
+ const s64 offset_tick(timeout_ns);
+ if (offset_tick > 0) {
+ timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2;
+ if (timeout <= 0) {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = timeout_ns;
+ }
+
// Wait for a message.
while (true) {
// Wait for an object.
s32 index;
Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(),
- num_handles, timeout_ns);
+ num_handles, timeout);
if (result == ResultTimedOut) {
R_RETURN(result);
}
diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp
index 732bc017e..c8e820b6a 100644
--- a/src/core/hle/kernel/svc/svc_resource_limit.cpp
+++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp
@@ -21,7 +21,7 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) {
SCOPE_EXIT({ resource_limit->Close(); });
// Initialize the resource limit.
- resource_limit->Initialize(std::addressof(system.CoreTiming()));
+ resource_limit->Initialize();
// Register the limit.
KResourceLimit::Register(kernel, resource_limit);
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp
index 366e8ed4a..8ebc1bd1c 100644
--- a/src/core/hle/kernel/svc/svc_synchronization.cpp
+++ b/src/core/hle/kernel/svc/svc_synchronization.cpp
@@ -4,6 +4,7 @@
#include "common/scope_exit.h"
#include "common/scratch_buffer.h"
#include "core/core.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/svc.h"
@@ -83,9 +84,20 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha
}
});
+ // Convert the timeout from nanoseconds to ticks.
+ s64 timeout;
+ if (timeout_ns > 0) {
+ u64 ticks = kernel.HardwareTimer().GetTick();
+ ticks += timeout_ns;
+ ticks += 2;
+
+ timeout = ticks;
+ } else {
+ timeout = timeout_ns;
+ }
+
// Wait on the objects.
- Result res =
- KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns);
+ Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout);
R_SUCCEED_IF(res == ResultSessionClosed);
R_RETURN(res);
diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp
index 92bcea72b..933b82e30 100644
--- a/src/core/hle/kernel/svc/svc_thread.cpp
+++ b/src/core/hle/kernel/svc/svc_thread.cpp
@@ -4,6 +4,7 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_thread.h"
@@ -42,9 +43,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u
R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority);
// Reserve a new thread from the process resource limit (waiting up to 100ms).
- KScopedResourceReservation thread_reservation(
- std::addressof(process), LimitableResource::ThreadCountMax, 1,
- system.CoreTiming().GetGlobalTimeNs().count() + 100000000);
+ KScopedResourceReservation thread_reservation(std::addressof(process),
+ LimitableResource::ThreadCountMax, 1,
+ kernel.HardwareTimer().GetTick() + 100000000);
R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached);
// Create the thread.
@@ -102,20 +103,31 @@ void ExitThread(Core::System& system) {
}
/// Sleep the current thread
-void SleepThread(Core::System& system, s64 nanoseconds) {
+void SleepThread(Core::System& system, s64 ns) {
auto& kernel = system.Kernel();
- const auto yield_type = static_cast<Svc::YieldType>(nanoseconds);
+ const auto yield_type = static_cast<Svc::YieldType>(ns);
- LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
+ LOG_TRACE(Kernel_SVC, "called nanoseconds={}", ns);
// When the input tick is positive, sleep.
- if (nanoseconds > 0) {
+ if (ns > 0) {
// Convert the timeout from nanoseconds to ticks.
// NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
+ s64 timeout;
+
+ const s64 offset_tick(ns);
+ if (offset_tick > 0) {
+ timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2;
+ if (timeout <= 0) {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = std::numeric_limits<s64>::max();
+ }
// Sleep.
// NOTE: Nintendo does not check the result of this sleep.
- static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds));
+ static_cast<void>(GetCurrentThread(kernel).Sleep(timeout));
} else if (yield_type == Svc::YieldType::WithoutCoreMigration) {
KScheduler::YieldWithoutCoreMigration(kernel);
} else if (yield_type == Svc::YieldType::WithCoreMigration) {
@@ -124,7 +136,6 @@ void SleepThread(Core::System& system, s64 nanoseconds) {
KScheduler::YieldToAnyThread(kernel);
} else {
// Nintendo does nothing at all if an otherwise invalid value is passed.
- ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
}
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 240f06689..dd0b27f47 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -62,7 +62,7 @@ enum class ErrorModule : u32 {
XCD = 108,
TMP451 = 109,
NIFM = 110,
- Hwopus = 111,
+ HwOpus = 111,
LSM6DS3 = 112,
Bluetooth = 113,
VI = 114,
@@ -283,159 +283,6 @@ private:
u32 description_end;
};
-/**
- * This is an optional value type. It holds a `Result` and, if that code is ResultSuccess, it
- * also holds a result of type `T`. If the code is an error code (not ResultSuccess), then trying
- * to access the inner value with operator* is undefined behavior and will assert with Unwrap().
- * Users of this class must be cognizant to check the status of the ResultVal with operator bool(),
- * Code(), Succeeded() or Failed() prior to accessing the inner value.
- *
- * An example of how it could be used:
- * \code
- * ResultVal<int> Frobnicate(float strength) {
- * if (strength < 0.f || strength > 1.0f) {
- * // Can't frobnicate too weakly or too strongly
- * return Result{ErrorModule::Common, 1};
- * } else {
- * // Frobnicated! Give caller a cookie
- * return 42;
- * }
- * }
- * \endcode
- *
- * \code
- * auto frob_result = Frobnicate(0.75f);
- * if (frob_result) {
- * // Frobbed ok
- * printf("My cookie is %d\n", *frob_result);
- * } else {
- * printf("Guess I overdid it. :( Error code: %ux\n", frob_result.Code().raw);
- * }
- * \endcode
- */
-template <typename T>
-class ResultVal {
-public:
- constexpr ResultVal() : expected{} {}
-
- constexpr ResultVal(Result code) : expected{Common::Unexpected(code)} {}
-
- constexpr ResultVal(ResultRange range) : expected{Common::Unexpected(range)} {}
-
- template <typename U>
- constexpr ResultVal(U&& val) : expected{std::forward<U>(val)} {}
-
- template <typename... Args>
- constexpr ResultVal(Args&&... args) : expected{std::in_place, std::forward<Args>(args)...} {}
-
- ~ResultVal() = default;
-
- constexpr ResultVal(const ResultVal&) = default;
- constexpr ResultVal(ResultVal&&) = default;
-
- ResultVal& operator=(const ResultVal&) = default;
- ResultVal& operator=(ResultVal&&) = default;
-
- [[nodiscard]] constexpr explicit operator bool() const noexcept {
- return expected.has_value();
- }
-
- [[nodiscard]] constexpr Result Code() const {
- return expected.has_value() ? ResultSuccess : expected.error();
- }
-
- [[nodiscard]] constexpr bool Succeeded() const {
- return expected.has_value();
- }
-
- [[nodiscard]] constexpr bool Failed() const {
- return !expected.has_value();
- }
-
- [[nodiscard]] constexpr T* operator->() {
- return std::addressof(expected.value());
- }
-
- [[nodiscard]] constexpr const T* operator->() const {
- return std::addressof(expected.value());
- }
-
- [[nodiscard]] constexpr T& operator*() & {
- return *expected;
- }
-
- [[nodiscard]] constexpr const T& operator*() const& {
- return *expected;
- }
-
- [[nodiscard]] constexpr T&& operator*() && {
- return *expected;
- }
-
- [[nodiscard]] constexpr const T&& operator*() const&& {
- return *expected;
- }
-
- [[nodiscard]] constexpr T& Unwrap() & {
- ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
- return expected.value();
- }
-
- [[nodiscard]] constexpr const T& Unwrap() const& {
- ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
- return expected.value();
- }
-
- [[nodiscard]] constexpr T&& Unwrap() && {
- ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
- return std::move(expected.value());
- }
-
- [[nodiscard]] constexpr const T&& Unwrap() const&& {
- ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
- return std::move(expected.value());
- }
-
- template <typename U>
- [[nodiscard]] constexpr T ValueOr(U&& v) const& {
- return expected.value_or(v);
- }
-
- template <typename U>
- [[nodiscard]] constexpr T ValueOr(U&& v) && {
- return expected.value_or(v);
- }
-
-private:
- // TODO (Morph): Replace this with C++23 std::expected.
- Common::Expected<T, Result> expected;
-};
-
-/**
- * Check for the success of `source` (which must evaluate to a ResultVal). If it succeeds, unwraps
- * the contained value and assigns it to `target`, which can be either an l-value expression or a
- * variable declaration. If it fails the return code is returned from the current function. Thus it
- * can be used to cascade errors out, achieving something akin to exception handling.
- */
-#define CASCADE_RESULT(target, source) \
- auto CONCAT2(check_result_L, __LINE__) = source; \
- if (CONCAT2(check_result_L, __LINE__).Failed()) { \
- return CONCAT2(check_result_L, __LINE__).Code(); \
- } \
- target = std::move(*CONCAT2(check_result_L, __LINE__))
-
-/**
- * Analogous to CASCADE_RESULT, but for a bare Result. The code will be propagated if
- * non-success, or discarded otherwise.
- */
-#define CASCADE_CODE(source) \
- do { \
- auto CONCAT2(check_result_L, __LINE__) = source; \
- if (CONCAT2(check_result_L, __LINE__).IsError()) { \
- return CONCAT2(check_result_L, __LINE__); \
- } \
- } while (false)
-
#define R_SUCCEEDED(res) (static_cast<Result>(res).IsSuccess())
#define R_FAILED(res) (static_cast<Result>(res).IsFailure())
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 2632cd3ef..b971401e6 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -765,15 +765,16 @@ Result Module::Interface::InitializeApplicationInfoBase() {
// TODO(ogniK): This should be changed to reflect the target process for when we have multiple
// processes emulated. As we don't actually have pid support we should assume we're just using
// our own process
- const auto launch_property =
- system.GetARPManager().GetLaunchProperty(system.GetApplicationProcessProgramID());
+ Glue::ApplicationLaunchProperty launch_property{};
+ const auto result = system.GetARPManager().GetLaunchProperty(
+ &launch_property, system.GetApplicationProcessProgramID());
- if (launch_property.Failed()) {
+ if (result != ResultSuccess) {
LOG_ERROR(Service_ACC, "Failed to get launch property");
return Account::ResultInvalidApplication;
}
- switch (launch_property->base_game_storage_id) {
+ switch (launch_property.base_game_storage_id) {
case FileSys::StorageId::GameCard:
application_info.application_type = ApplicationType::GameCard;
break;
@@ -785,7 +786,7 @@ Result Module::Interface::InitializeApplicationInfoBase() {
break;
default:
LOG_ERROR(Service_ACC, "Invalid game storage ID! storage_id={}",
- launch_property->base_game_storage_id);
+ launch_property.base_game_storage_id);
return Account::ResultInvalidApplication;
}
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 4f400d341..8ffdd19e7 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -6,6 +6,7 @@
#include <cinttypes>
#include <cstring>
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -45,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3};
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
enum class LaunchParameterKind : u32 {
- ApplicationSpecific = 1,
+ UserChannel = 1,
AccountPreselectedUser = 2,
};
@@ -340,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) {
void ISelfController::LockExit(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- system.SetExitLock(true);
+ system.SetExitLocked(true);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -349,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) {
void ISelfController::UnlockExit(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- system.SetExitLock(false);
+ system.SetExitLocked(false);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
+
+ if (system.GetExitRequested()) {
+ system.Exit();
+ }
}
void ISelfController::EnterFatalSection(HLERequestContext& ctx) {
@@ -833,7 +838,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight));
} else {
@@ -921,7 +926,7 @@ void IStorage::Open(HLERequestContext& ctx) {
}
void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) {
- const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()};
+ const bool use_docked_mode{Settings::IsDockedMode()};
LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
IPC::ResponseBuilder rb{ctx, 3};
@@ -1317,6 +1322,50 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
rb.PushIpcInterface<IStorage>(system, std::move(memory));
}
+ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
+ : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} {
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "PopInData"},
+ {1, nullptr, "PushOutData"},
+ {2, nullptr, "PopInteractiveInData"},
+ {3, nullptr, "PushInteractiveOutData"},
+ {5, nullptr, "GetPopInDataEvent"},
+ {6, nullptr, "GetPopInteractiveInDataEvent"},
+ {10, nullptr, "ExitProcessAndReturn"},
+ {11, nullptr, "GetLibraryAppletInfo"},
+ {12, nullptr, "GetMainAppletIdentityInfo"},
+ {13, nullptr, "CanUseApplicationCore"},
+ {14, nullptr, "GetCallerAppletIdentityInfo"},
+ {15, nullptr, "GetMainAppletApplicationControlProperty"},
+ {16, nullptr, "GetMainAppletStorageId"},
+ {17, nullptr, "GetCallerAppletIdentityInfoStack"},
+ {18, nullptr, "GetNextReturnDestinationAppletIdentityInfo"},
+ {19, nullptr, "GetDesirableKeyboardLayout"},
+ {20, nullptr, "PopExtraStorage"},
+ {25, nullptr, "GetPopExtraStorageEvent"},
+ {30, nullptr, "UnpopInData"},
+ {31, nullptr, "UnpopExtraStorage"},
+ {40, nullptr, "GetIndirectLayerProducerHandle"},
+ {50, nullptr, "ReportVisibleError"},
+ {51, nullptr, "ReportVisibleErrorWithErrorContext"},
+ {60, nullptr, "GetMainAppletApplicationDesiredLanguage"},
+ {70, nullptr, "GetCurrentApplicationId"},
+ {80, nullptr, "RequestExitToSelf"},
+ {90, nullptr, "CreateApplicationAndPushAndRequestToLaunch"},
+ {100, nullptr, "CreateGameMovieTrimmer"},
+ {101, nullptr, "ReserveResourceForMovieOperation"},
+ {102, nullptr, "UnreserveResourceForMovieOperation"},
+ {110, nullptr, "GetMainAppletAvailableUsers"},
+ {120, nullptr, "GetLaunchStorageInfoForDebug"},
+ {130, nullptr, "GetGpuErrorDetectedSystemEvent"},
+ {140, nullptr, "SetApplicationMemoryReservation"},
+ {150, nullptr, "ShouldSetGpuTimeSliceManually"},
+ };
+ RegisterHandlers(functions);
+}
+
+ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
+
IApplicationFunctions::IApplicationFunctions(Core::System& system_)
: ServiceFramework{system_, "IApplicationFunctions"}, service_context{system,
"IApplicationFunctions"} {
@@ -1337,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
{26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
{27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
- {28, nullptr, "GetSaveDataSizeMax"},
+ {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
{29, nullptr, "GetCacheStorageMax"},
{30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
{31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
@@ -1469,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto kind = rp.PopEnum<LaunchParameterKind>();
- LOG_DEBUG(Service_AM, "called, kind={:08X}", kind);
-
- if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
- const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) {
- return system.GetFileSystemController().GetBCATDirectory(tid);
- });
- const auto build_id_full = system.GetApplicationProcessBuildID();
- u64 build_id{};
- std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
-
- auto data =
- backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id});
- if (data.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IStorage>(system, std::move(*data));
- launch_popped_application_specific = true;
+ LOG_INFO(Service_AM, "called, kind={:08X}", kind);
+
+ if (kind == LaunchParameterKind::UserChannel) {
+ auto channel = system.GetUserChannel();
+ if (channel.empty()) {
+ LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(AM::ResultNoDataInChannel);
return;
}
+
+ auto data = channel.back();
+ channel.pop_back();
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IStorage>(system, std::move(data));
} else if (kind == LaunchParameterKind::AccountPreselectedUser &&
!launch_popped_account_preselect) {
+ // TODO: Verify this is hw-accurate
LaunchParameterAccountPreselectedUser params{};
params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
@@ -1501,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
params.current_user = *uuid;
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-
rb.Push(ResultSuccess);
std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
@@ -1509,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
rb.PushIpcInterface<IStorage>(system, std::move(buffer));
launch_popped_account_preselect = true;
- return;
+ } else {
+ LOG_ERROR(Service_AM, "Unknown launch parameter kind.");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(AM::ResultNoDataInChannel);
}
-
- LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(AM::ResultNoDataInChannel);
}
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) {
@@ -1534,11 +1580,13 @@ void IApplicationFunctions::EnsureSaveData(HLERequestContext& ctx) {
attribute.title_id = system.GetApplicationProcessProgramID();
attribute.user_id = user_id;
attribute.type = FileSys::SaveDataType::SaveData;
+
+ FileSys::VirtualDir save_data{};
const auto res = system.GetFileSystemController().CreateSaveData(
- FileSys::SaveDataSpaceId::NandUser, attribute);
+ &save_data, FileSys::SaveDataSpaceId::NandUser, attribute);
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(res.Code());
+ rb.Push(res);
rb.Push<u64>(0);
}
@@ -1623,26 +1671,30 @@ void IApplicationFunctions::GetDesiredLanguage(HLERequestContext& ctx) {
auto app_man = ns_am2->GetApplicationManagerInterface();
// Get desired application language
- const auto res_lang = app_man->GetApplicationDesiredLanguage(supported_languages);
- if (res_lang.Failed()) {
+ u8 desired_language{};
+ const auto res_lang =
+ app_man->GetApplicationDesiredLanguage(&desired_language, supported_languages);
+ if (res_lang != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res_lang.Code());
+ rb.Push(res_lang);
return;
}
// Convert to settings language code.
- const auto res_code = app_man->ConvertApplicationLanguageToLanguageCode(*res_lang);
- if (res_code.Failed()) {
+ u64 language_code{};
+ const auto res_code =
+ app_man->ConvertApplicationLanguageToLanguageCode(&language_code, desired_language);
+ if (res_code != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res_code.Code());
+ rb.Push(res_code);
return;
}
- LOG_DEBUG(Service_AM, "got desired_language={:016X}", *res_code);
+ LOG_DEBUG(Service_AM, "got desired_language={:016X}", language_code);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push(*res_code);
+ rb.Push(language_code);
}
void IApplicationFunctions::IsGamePlayRecordingSupported(HLERequestContext& ctx) {
@@ -1769,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
rb.PushRaw(resp);
}
+void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ constexpr u64 size_max_normal = 0xFFFFFFF;
+ constexpr u64 size_max_journal = 0xFFFFFFF;
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.Push(size_max_normal);
+ rb.Push(size_max_journal);
+}
+
void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -1800,14 +1864,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) {
}
void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called");
+
+ system.GetUserChannel().clear();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::RequestParser rp{ctx};
+ const auto storage = rp.PopIpcInterface<IStorage>().lock();
+ if (storage) {
+ system.GetUserChannel().push_back(storage->GetData());
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index d4fd163da..f86841c60 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -22,30 +22,6 @@ class Nvnflinger;
namespace Service::AM {
-// This is nn::settings::Language
-enum SystemLanguage {
- Japanese = 0,
- English = 1, // en-US
- French = 2,
- German = 3,
- Italian = 4,
- Spanish = 5,
- Chinese = 6,
- Korean = 7,
- Dutch = 8,
- Portuguese = 9,
- Russian = 10,
- Taiwanese = 11,
- BritishEnglish = 12, // en-GB
- CanadianFrench = 13,
- LatinAmericanSpanish = 14, // es-419
- // 4.0.0+
- SimplifiedChinese = 15,
- TraditionalChinese = 16,
- // 10.1.0+
- BrazilianPortuguese = 17,
-};
-
class AppletMessageQueue {
public:
// This is nn::am::AppletMessage
@@ -314,6 +290,12 @@ private:
void CreateHandleStorage(HLERequestContext& ctx);
};
+class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletSelfAccessor> {
+public:
+ explicit ILibraryAppletSelfAccessor(Core::System& system_);
+ ~ILibraryAppletSelfAccessor() override;
+};
+
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
public:
explicit IApplicationFunctions(Core::System& system_);
@@ -334,6 +316,7 @@ private:
void ExtendSaveData(HLERequestContext& ctx);
void GetSaveDataSize(HLERequestContext& ctx);
void CreateCacheStorage(HLERequestContext& ctx);
+ void GetSaveDataSizeMax(HLERequestContext& ctx);
void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
void BeginBlockingHomeButton(HLERequestContext& ctx);
@@ -357,7 +340,6 @@ private:
KernelHelpers::ServiceContext service_context;
- bool launch_popped_application_specific = false;
bool launch_popped_account_preselect = false;
s32 previous_program_index{-1};
Kernel::KEvent* gpu_error_detected_event;
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index 2764f7ceb..ee9d99a54 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -26,8 +26,10 @@ public:
{4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"},
{10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
- {20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"},
+ {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
{21, nullptr, "GetAppletCommonFunctions"},
+ {22, nullptr, "GetHomeMenuFunctions"},
+ {23, nullptr, "GetGlobalStateController"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
@@ -100,12 +102,12 @@ private:
rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
- void GetApplicationFunctions(HLERequestContext& ctx) {
+ void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IApplicationFunctions>(system);
+ rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system);
}
Nvnflinger::Nvnflinger& nvnflinger;
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp
index d1f652c09..ff77830d2 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.cpp
+++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp
@@ -7,7 +7,9 @@
#include "core/frontend/applets/mii_edit.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_mii_edit.h"
+#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/sm/sm.h"
namespace Service::AM::Applets {
@@ -56,6 +58,12 @@ void MiiEdit::Initialize() {
sizeof(MiiEditAppletInputV4));
break;
}
+
+ manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager();
+ if (manager == nullptr) {
+ manager = std::make_shared<Mii::MiiManager>();
+ }
+ manager->Initialize(metadata);
}
bool MiiEdit::TransactionComplete() const {
@@ -78,22 +86,49 @@ void MiiEdit::Execute() {
// This is a default stub for each of the MiiEdit applet modes.
switch (applet_input_common.applet_mode) {
case MiiEditAppletMode::ShowMiiEdit:
- case MiiEditAppletMode::AppendMii:
case MiiEditAppletMode::AppendMiiImage:
case MiiEditAppletMode::UpdateMiiImage:
MiiEditOutput(MiiEditResult::Success, 0);
break;
- case MiiEditAppletMode::CreateMii:
- case MiiEditAppletMode::EditMii: {
- Service::Mii::MiiManager mii_manager;
+ case MiiEditAppletMode::AppendMii: {
+ Mii::StoreData store_data{};
+ store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
+ store_data.SetNickname({u'y', u'u', u'z', u'u'});
+ store_data.SetChecksum();
+ const auto result = manager->AddOrReplace(metadata, store_data);
+
+ if (result.IsError()) {
+ MiiEditOutput(MiiEditResult::Cancel, 0);
+ break;
+ }
+
+ s32 index = manager->FindIndex(store_data.GetCreateId(), false);
+
+ if (index == -1) {
+ MiiEditOutput(MiiEditResult::Cancel, 0);
+ break;
+ }
+
+ MiiEditOutput(MiiEditResult::Success, index);
+ break;
+ }
+ case MiiEditAppletMode::CreateMii: {
+ Mii::CharInfo char_info{};
+ manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All);
- const MiiEditCharInfo char_info{
- .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
- ? applet_input_v4.char_info.mii_info
- : mii_manager.BuildDefault(0)},
+ const MiiEditCharInfo edit_char_info{
+ .mii_info{char_info},
+ };
+
+ MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
+ break;
+ }
+ case MiiEditAppletMode::EditMii: {
+ const MiiEditCharInfo edit_char_info{
+ .mii_info{applet_input_v4.char_info.mii_info},
};
- MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info);
+ MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
break;
}
default:
diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h
index 3f46fae1b..7ff34af49 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit.h
+++ b/src/core/hle/service/am/applets/applet_mii_edit.h
@@ -11,6 +11,11 @@ namespace Core {
class System;
} // namespace Core
+namespace Service::Mii {
+struct DatabaseSessionMetadata;
+class MiiManager;
+} // namespace Service::Mii
+
namespace Service::AM::Applets {
class MiiEdit final : public Applet {
@@ -40,6 +45,8 @@ private:
MiiEditAppletInputV4 applet_input_v4{};
bool is_complete{false};
+ std::shared_ptr<Mii::MiiManager> manager = nullptr;
+ Mii::DatabaseSessionMetadata metadata{};
};
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h
index 4705d019f..f3d764073 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit_types.h
+++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h
@@ -7,7 +7,8 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "core/hle/service/mii/types.h"
+#include "common/uuid.h"
+#include "core/hle/service/mii/types/char_info.h"
namespace Service::AM::Applets {
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp
index 2accf7898..1c9a1dc29 100644
--- a/src/core/hle/service/am/applets/applet_web_browser.cpp
+++ b/src/core/hle/service/am/applets/applet_web_browser.cpp
@@ -139,7 +139,7 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
- return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type);
+ return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type);
}
}
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 38c2138e8..7075ab800 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -22,6 +22,8 @@
namespace Service::AOC {
+constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400};
+
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return FileSys::GetBaseTitleID(title_id) == base;
}
@@ -54,8 +56,8 @@ public:
{0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
{1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
{2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
- {3, nullptr, "PopPurchasedProductInfo"},
- {4, nullptr, "PopPurchasedProductInfoWithUid"},
+ {3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"},
+ {4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"},
};
// clang-format on
@@ -101,6 +103,20 @@ private:
rb.PushCopyObjects(purchased_event->GetReadableEvent());
}
+ void PopPurchasedProductInfo(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AOC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultNoPurchasedProductInfoAvailable);
+ }
+
+ void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AOC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultNoPurchasedProductInfoAvailable);
+ }
+
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* purchased_event;
diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp
index 227fdd0cf..4f1aa5cc2 100644
--- a/src/core/hle/service/apm/apm_controller.cpp
+++ b/src/core/hle/service/apm/apm_controller.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core_timing.h"
#include "core/hle/service/apm/apm_controller.h"
@@ -67,8 +68,7 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
}
PerformanceMode Controller::GetCurrentPerformanceMode() const {
- return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost
- : PerformanceMode::Normal;
+ return Settings::IsDockedMode() ? PerformanceMode::Boost : PerformanceMode::Normal;
}
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp
index 7ad93be6b..66dd64fd1 100644
--- a/src/core/hle/service/audio/audctl.cpp
+++ b/src/core/hle/service/audio/audctl.cpp
@@ -22,13 +22,13 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} {
{9, nullptr, "GetAudioOutputMode"},
{10, nullptr, "SetAudioOutputMode"},
{11, nullptr, "SetForceMutePolicy"},
- {12, nullptr, "GetForceMutePolicy"},
- {13, nullptr, "GetOutputModeSetting"},
+ {12, &AudCtl::GetForceMutePolicy, "GetForceMutePolicy"},
+ {13, &AudCtl::GetOutputModeSetting, "GetOutputModeSetting"},
{14, nullptr, "SetOutputModeSetting"},
{15, nullptr, "SetOutputTarget"},
{16, nullptr, "SetInputTargetForceEnabled"},
{17, nullptr, "SetHeadphoneOutputLevelMode"},
- {18, nullptr, "GetHeadphoneOutputLevelMode"},
+ {18, &AudCtl::GetHeadphoneOutputLevelMode, "GetHeadphoneOutputLevelMode"},
{19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"},
{20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"},
{21, nullptr, "GetAudioOutputTargetForPlayReport"},
@@ -41,7 +41,7 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} {
{28, nullptr, "GetAudioOutputChannelCountForPlayReport"},
{29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"},
{30, nullptr, "SetSpeakerAutoMuteEnabled"},
- {31, nullptr, "IsSpeakerAutoMuteEnabled"},
+ {31, &AudCtl::IsSpeakerAutoMuteEnabled, "IsSpeakerAutoMuteEnabled"},
{32, nullptr, "GetActiveOutputTarget"},
{33, nullptr, "GetTargetDeviceInfo"},
{34, nullptr, "AcquireTargetNotification"},
@@ -96,4 +96,42 @@ void AudCtl::GetTargetVolumeMax(HLERequestContext& ctx) {
rb.Push(target_max_volume);
}
+void AudCtl::GetForceMutePolicy(HLERequestContext& ctx) {
+ LOG_WARNING(Audio, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(ForceMutePolicy::Disable);
+}
+
+void AudCtl::GetOutputModeSetting(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto value = rp.Pop<u32>();
+
+ LOG_WARNING(Audio, "(STUBBED) called, value={}", value);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(AudioOutputMode::PcmAuto);
+}
+
+void AudCtl::GetHeadphoneOutputLevelMode(HLERequestContext& ctx) {
+ LOG_WARNING(Audio, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(HeadphoneOutputLevelMode::Normal);
+}
+
+void AudCtl::IsSpeakerAutoMuteEnabled(HLERequestContext& ctx) {
+ const bool is_speaker_auto_mute_enabled = false;
+
+ LOG_WARNING(Audio, "(STUBBED) called, is_speaker_auto_mute_enabled={}",
+ is_speaker_auto_mute_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_speaker_auto_mute_enabled);
+}
+
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h
index 8e31ac237..d57abb383 100644
--- a/src/core/hle/service/audio/audctl.h
+++ b/src/core/hle/service/audio/audctl.h
@@ -17,8 +17,30 @@ public:
~AudCtl() override;
private:
+ enum class AudioOutputMode {
+ Invalid,
+ Pcm1ch,
+ Pcm2ch,
+ Pcm6ch,
+ PcmAuto,
+ };
+
+ enum class ForceMutePolicy {
+ Disable,
+ SpeakerMuteOnHeadphoneUnplugged,
+ };
+
+ enum class HeadphoneOutputLevelMode {
+ Normal,
+ HighPower,
+ };
+
void GetTargetVolumeMin(HLERequestContext& ctx);
void GetTargetVolumeMax(HLERequestContext& ctx);
+ void GetForceMutePolicy(HLERequestContext& ctx);
+ void GetOutputModeSetting(HLERequestContext& ctx);
+ void GetHeadphoneOutputLevelMode(HLERequestContext& ctx);
+ void IsSpeakerAutoMuteEnabled(HLERequestContext& ctx);
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 526a39130..56fee4591 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -220,7 +220,7 @@ AudInU::AudInU(Core::System& system_)
AudInU::~AudInU() = default;
void AudInU::ListAudioIns(HLERequestContext& ctx) {
- using namespace AudioCore::AudioRenderer;
+ using namespace AudioCore::Renderer;
LOG_DEBUG(Service_Audio, "called");
@@ -240,7 +240,7 @@ void AudInU::ListAudioIns(HLERequestContext& ctx) {
}
void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) {
- using namespace AudioCore::AudioRenderer;
+ using namespace AudioCore::Renderer;
LOG_DEBUG(Service_Audio, "called");
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 23f84a29f..ca683d72c 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -228,7 +228,7 @@ AudOutU::AudOutU(Core::System& system_)
AudOutU::~AudOutU() = default;
void AudOutU::ListAudioOuts(HLERequestContext& ctx) {
- using namespace AudioCore::AudioRenderer;
+ using namespace AudioCore::Renderer;
std::scoped_lock l{impl->mutex};
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 003870176..2f09cade5 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -26,7 +26,7 @@
#include "core/hle/service/ipc_helpers.h"
#include "core/memory.h"
-using namespace AudioCore::AudioRenderer;
+using namespace AudioCore::Renderer;
namespace Service::Audio {
@@ -441,10 +441,11 @@ void AudRenU::OpenAudioRenderer(HLERequestContext& ctx) {
AudioCore::AudioRendererParameterInternal params;
rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params);
- auto transfer_memory_handle = ctx.GetCopyHandle(0);
- auto process_handle = ctx.GetCopyHandle(1);
+ rp.Skip(1, false);
auto transfer_memory_size = rp.Pop<u64>();
auto applet_resource_user_id = rp.Pop<u64>();
+ auto transfer_memory_handle = ctx.GetCopyHandle(0);
+ auto process_handle = ctx.GetCopyHandle(1);
if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) {
LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!");
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index d8e9c8719..3d7993a16 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -28,7 +28,7 @@ private:
void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx);
KernelHelpers::ServiceContext service_context;
- std::unique_ptr<AudioCore::AudioRenderer::Manager> impl;
+ std::unique_ptr<AudioCore::Renderer::Manager> impl;
u32 num_audio_devices{0};
};
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h
index 3d3d3d97a..c41345f7e 100644
--- a/src/core/hle/service/audio/errors.h
+++ b/src/core/hle/service/audio/errors.h
@@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513};
constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536};
constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537};
+constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7};
+constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8};
+constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6};
+constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5};
+constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17};
+constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4};
+constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
+constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
+constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
+constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
+constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};
+
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index fa77007f3..6a7bf9416 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -1,371 +1,506 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <chrono>
-#include <cstring>
#include <memory>
#include <vector>
-#include <opus.h>
-#include <opus_multistream.h>
-
+#include "audio_core/opus/decoder.h"
+#include "audio_core/opus/parameters.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scratch_buffer.h"
+#include "core/core.h"
#include "core/hle/service/audio/hwopus.h"
#include "core/hle/service/ipc_helpers.h"
namespace Service::Audio {
-namespace {
-struct OpusDeleter {
- void operator()(OpusMSDecoder* ptr) const {
- opus_multistream_decoder_destroy(ptr);
+using namespace AudioCore::OpusDecoder;
+
+class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> {
+public:
+ explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus)
+ : ServiceFramework{system_, "IHardwareOpusDecoder"},
+ impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"},
+ {1, &IHardwareOpusDecoder::SetContext, "SetContext"},
+ {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"},
+ {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"},
+ {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
+ {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"},
+ {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"},
+ {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
+ {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"},
+ {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
}
-};
-using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
+ Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size) {
+ return impl->Initialize(params, transfer_memory, transfer_memory_size);
+ }
-struct OpusPacketHeader {
- // Packet size in bytes.
- u32_be size;
- // Indicates the final range of the codec's entropy coder.
- u32_be final_range;
-};
-static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
+ Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size) {
+ return impl->Initialize(params, transfer_memory, transfer_memory_size);
+ }
-class OpusDecoderState {
-public:
- /// Describes extra behavior that may be asked of the decoding context.
- enum class ExtraBehavior {
- /// No extra behavior.
- None,
+private:
+ void DecodeInterleavedOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
- /// Resets the decoder context back to a freshly initialized state.
- ResetContext,
- };
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
- enum class PerfTime {
- Disabled,
- Enabled,
- };
+ u32 size{};
+ u32 sample_count{};
+ auto result =
+ impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false);
+
+ LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
+
+ ctx.WriteBuffer(output_data);
- explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_)
- : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {}
-
- // Decodes interleaved Opus packets. Optionally allows reporting time taken to
- // perform the decoding, as well as any relevant extra behavior.
- void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time,
- ExtraBehavior extra_behavior) {
- if (perf_time == PerfTime::Disabled) {
- DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
- } else {
- u64 performance = 0;
- DecodeInterleavedHelper(ctx, &performance, extra_behavior);
- }
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
}
-private:
- void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance,
- ExtraBehavior extra_behavior) {
- u32 consumed = 0;
- u32 sample_count = 0;
- samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>());
-
- if (extra_behavior == ExtraBehavior::ResetContext) {
- ResetDecoderContext();
- }
-
- if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
- LOG_ERROR(Audio, "Failed to decode opus data");
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(ogniK): Use correct error code
- rb.Push(ResultUnknown);
- return;
- }
-
- const u32 param_size = performance != nullptr ? 6 : 4;
- IPC::ResponseBuilder rb{ctx, param_size};
- rb.Push(ResultSuccess);
- rb.Push<u32>(consumed);
- rb.Push<u32>(sample_count);
- if (performance) {
- rb.Push<u64>(*performance);
- }
- ctx.WriteBuffer(samples);
+ void SetContext(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_DEBUG(Service_Audio, "called");
+
+ auto input_data{ctx.ReadBuffer(0)};
+ auto result = impl->SetContext(input_data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
- bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input,
- std::span<opus_int16> output, u64* out_performance_time) const {
- const auto start_time = std::chrono::steady_clock::now();
- const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
- if (sizeof(OpusPacketHeader) > input.size()) {
- LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
- sizeof(OpusPacketHeader), input.size());
- return false;
- }
-
- OpusPacketHeader hdr{};
- std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
- if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
- LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
- sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
- return false;
- }
-
- const auto frame = input.data() + sizeof(OpusPacketHeader);
- const auto decoded_sample_count = opus_packet_get_nb_samples(
- frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
- static_cast<opus_int32>(sample_rate));
- if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
- LOG_ERROR(
- Audio,
- "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}",
- decoded_sample_count * channel_count * sizeof(u16), raw_output_sz);
- return false;
- }
-
- const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
- const auto out_sample_count =
- opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
- if (out_sample_count < 0) {
- LOG_ERROR(Audio,
- "Incorrect sample count received from opus_decode, "
- "output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
- out_sample_count, frame_size, static_cast<u32>(hdr.size));
- return false;
- }
-
- const auto end_time = std::chrono::steady_clock::now() - start_time;
- sample_count = out_sample_count;
- consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
- if (out_performance_time != nullptr) {
- *out_performance_time =
- std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
- }
-
- return true;
+ void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count,
+ input_data, output_data, false);
+
+ LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
}
- void ResetDecoderContext() {
- ASSERT(decoder != nullptr);
+ void SetContextForMultiStream(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_DEBUG(Service_Audio, "called");
+
+ auto input_data{ctx.ReadBuffer(0)};
+ auto result = impl->SetContext(input_data);
- opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
- OpusDecoderPtr decoder;
- u32 sample_rate;
- u32 channel_count;
- Common::ScratchBuffer<opus_int16> samples;
-};
+ void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
-class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
-public:
- explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_)
- : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{
- std::move(decoder_state_)} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
- {1, nullptr, "SetContext"},
- {2, nullptr, "DecodeInterleavedForMultiStreamOld"},
- {3, nullptr, "SetContextForMultiStream"},
- {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
- {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
- {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
- {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
- {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
- {9, nullptr, "DecodeInterleavedForMultiStream"},
- };
- // clang-format on
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
- RegisterHandlers(functions);
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
+ output_data, false);
+
+ LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
+ sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
}
-private:
- void DecodeInterleavedOld(HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
+ void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
+ input_data, output_data, false);
- decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
- OpusDecoderState::ExtraBehavior::None);
+ LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
+ sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
}
- void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
+ void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto reset{rp.Pop<bool>()};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
+ output_data, reset);
+
+ LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
+ reset, size, sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
+ }
+
+ void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto reset{rp.Pop<bool>()};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
+ input_data, output_data, reset);
+
+ LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
+ reset, size, sample_count, time_taken);
- decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
- OpusDecoderState::ExtraBehavior::None);
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
}
void DecodeInterleaved(HLERequestContext& ctx) {
- LOG_DEBUG(Audio, "called");
+ IPC::RequestParser rp{ctx};
+
+ auto reset{rp.Pop<bool>()};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
+ output_data, reset);
+
+ LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
+ reset, size, sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
+ }
+ void DecodeInterleavedForMultiStream(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
- : OpusDecoderState::ExtraBehavior::None;
- decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
+ auto reset{rp.Pop<bool>()};
+
+ auto input_data{ctx.ReadBuffer(0)};
+ output_data.resize_destructive(ctx.GetWriteBufferSize());
+
+ u32 size{};
+ u32 sample_count{};
+ u64 time_taken{};
+ auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
+ input_data, output_data, reset);
+
+ LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
+ reset, size, sample_count, time_taken);
+
+ ctx.WriteBuffer(output_data);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.Push(size);
+ rb.Push(sample_count);
+ rb.Push(time_taken);
}
- OpusDecoderState decoder_state;
+ std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl;
+ Common::ScratchBuffer<u8> output_data;
};
-std::size_t WorkerBufferSize(u32 channel_count) {
- ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
- constexpr int num_streams = 1;
- const int num_stereo_streams = channel_count == 2 ? 1 : 0;
- return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
-}
+void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
-// Creates the mapping table that maps the input channels to the particular
-// output channels. In the stereo case, we map the left and right input channels
-// to the left and right output channels respectively.
-//
-// However, in the monophonic case, we only map the one available channel
-// to the sole output channel. We specify 255 for the would-be right channel
-// as this is a special value defined by Opus to indicate to the decoder to
-// ignore that channel.
-std::array<u8, 2> CreateMappingTable(u32 channel_count) {
- if (channel_count == 2) {
- return {{0, 1}};
- }
+ auto params = rp.PopRaw<OpusParameters>();
+ auto transfer_memory_size{rp.Pop<u32>()};
+ auto transfer_memory_handle{ctx.GetCopyHandle(0)};
+ auto transfer_memory{
+ system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ transfer_memory_handle)};
- return {{0, 255}};
+ LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
+ params.sample_rate, params.channel_count, transfer_memory_size);
+
+ auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
+
+ OpusParametersEx ex{
+ .sample_rate = params.sample_rate,
+ .channel_count = params.channel_count,
+ .use_large_frame_size = false,
+ };
+ auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(result);
+ rb.PushIpcInterface(decoder);
}
-} // Anonymous namespace
void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto sample_rate = rp.Pop<u32>();
- const auto channel_count = rp.Pop<u32>();
+ auto params = rp.PopRaw<OpusParameters>();
- LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count);
+ u64 size{};
+ auto result = impl.GetWorkBufferSize(params, size);
- ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
- sample_rate == 12000 || sample_rate == 8000,
- "Invalid sample rate");
- ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
+ LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}",
+ params.sample_rate, params.channel_count, size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+}
+
+void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
- const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count));
- LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz);
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParameters params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParameters));
+
+ auto transfer_memory_size{rp.Pop<u32>()};
+ auto transfer_memory_handle{ctx.GetCopyHandle(0)};
+ auto transfer_memory{
+ system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ transfer_memory_handle)};
+
+ LOG_DEBUG(Service_Audio,
+ "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
+ "transfer_memory_size 0x{:X}",
+ params.sample_rate, params.channel_count, params.total_stream_count,
+ params.stereo_stream_count, transfer_memory_size);
+
+ auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
+
+ OpusMultiStreamParametersEx ex{
+ .sample_rate = params.sample_rate,
+ .channel_count = params.channel_count,
+ .total_stream_count = params.total_stream_count,
+ .stereo_stream_count = params.stereo_stream_count,
+ .use_large_frame_size = false,
+ .mappings{},
+ };
+ std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings));
+ auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(worker_buffer_sz);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(result);
+ rb.PushIpcInterface(decoder);
}
-void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
- GetWorkBufferSize(ctx);
+void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParameters params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParameters));
+
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeForMultiStream(params, size);
+
+ LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
}
-void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
- OpusMultiStreamParametersEx param;
- std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
+void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
- const auto sample_rate = param.sample_rate;
- const auto channel_count = param.channel_count;
- const auto number_streams = param.number_streams;
- const auto number_stereo_streams = param.number_stereo_streams;
+ auto params = rp.PopRaw<OpusParametersEx>();
+ auto transfer_memory_size{rp.Pop<u32>()};
+ auto transfer_memory_handle{ctx.GetCopyHandle(0)};
+ auto transfer_memory{
+ system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ transfer_memory_handle)};
- LOG_DEBUG(
- Audio,
- "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
- sample_rate, channel_count, number_streams, number_stereo_streams);
+ LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
+ params.sample_rate, params.channel_count, transfer_memory_size);
- ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
- sample_rate == 12000 || sample_rate == 8000,
- "Invalid sample rate");
+ auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
- const u32 worker_buffer_sz =
- static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams));
+ auto result =
+ decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(worker_buffer_sz);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(result);
+ rb.PushIpcInterface(decoder);
}
-void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) {
+void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto sample_rate = rp.Pop<u32>();
- const auto channel_count = rp.Pop<u32>();
- const auto buffer_sz = rp.Pop<u32>();
-
- LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
- channel_count, buffer_sz);
-
- ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
- sample_rate == 12000 || sample_rate == 8000,
- "Invalid sample rate");
- ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
-
- const std::size_t worker_sz = WorkerBufferSize(channel_count);
- ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
-
- const int num_stereo_streams = channel_count == 2 ? 1 : 0;
- const auto mapping_table = CreateMappingTable(channel_count);
-
- int error = 0;
- OpusDecoderPtr decoder{
- opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
- num_stereo_streams, mapping_table.data(), &error)};
- if (error != OPUS_OK || decoder == nullptr) {
- LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(ogniK): Use correct error code
- rb.Push(ResultUnknown);
- return;
- }
+ auto params = rp.PopRaw<OpusParametersEx>();
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IHardwareOpusDecoderManager>(
- system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeEx(params, size);
+
+ LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
}
-void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
+void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto sample_rate = rp.Pop<u32>();
- const auto channel_count = rp.Pop<u32>();
- LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count);
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParametersEx params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
- ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
- sample_rate == 12000 || sample_rate == 8000,
- "Invalid sample rate");
- ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
+ auto transfer_memory_size{rp.Pop<u32>()};
+ auto transfer_memory_handle{ctx.GetCopyHandle(0)};
+ auto transfer_memory{
+ system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ transfer_memory_handle)};
- const int num_stereo_streams = channel_count == 2 ? 1 : 0;
- const auto mapping_table = CreateMappingTable(channel_count);
+ LOG_DEBUG(Service_Audio,
+ "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
+ "use_large_frame_size {}"
+ "transfer_memory_size 0x{:X}",
+ params.sample_rate, params.channel_count, params.total_stream_count,
+ params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size);
- int error = 0;
- OpusDecoderPtr decoder{
- opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
- num_stereo_streams, mapping_table.data(), &error)};
- if (error != OPUS_OK || decoder == nullptr) {
- LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
- IPC::ResponseBuilder rb{ctx, 2};
- // TODO(ogniK): Use correct error code
- rb.Push(ResultUnknown);
- return;
- }
+ auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
+
+ auto result =
+ decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IHardwareOpusDecoderManager>(
- system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
+ rb.Push(result);
+ rb.PushIpcInterface(decoder);
+}
+
+void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParametersEx params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
+
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size);
+
+ LOG_DEBUG(Service_Audio,
+ "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
+ "use_large_frame_size {} -- returned size 0x{:X}",
+ params.sample_rate, params.channel_count, params.total_stream_count,
+ params.stereo_stream_count, params.use_large_frame_size, size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+}
+
+void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto params = rp.PopRaw<OpusParametersEx>();
+
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeExEx(params, size);
+
+ LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
+}
+
+void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto input{ctx.ReadBuffer()};
+ OpusMultiStreamParametersEx params;
+ std::memcpy(&params, input.data(), sizeof(OpusMultiStreamParametersEx));
+
+ u64 size{};
+ auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size);
+
+ LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(size);
}
-HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
+HwOpus::HwOpus(Core::System& system_)
+ : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} {
static const FunctionInfo functions[] = {
{0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"},
{1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
- {2, nullptr, "OpenOpusDecoderForMultiStream"},
- {3, nullptr, "GetWorkBufferSizeForMultiStream"},
+ {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"},
+ {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"},
{4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
{5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
- {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},
+ {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx,
+ "OpenHardwareOpusDecoderForMultiStreamEx"},
{7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
- {8, nullptr, "GetWorkBufferSizeExEx"},
- {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"},
+ {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"},
+ {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h
index ece65c02c..d3960065e 100644
--- a/src/core/hle/service/audio/hwopus.h
+++ b/src/core/hle/service/audio/hwopus.h
@@ -3,6 +3,7 @@
#pragma once
+#include "audio_core/opus/decoder_manager.h"
#include "core/hle/service/service.h"
namespace Core {
@@ -11,16 +12,6 @@ class System;
namespace Service::Audio {
-struct OpusMultiStreamParametersEx {
- u32 sample_rate;
- u32 channel_count;
- u32 number_streams;
- u32 number_stereo_streams;
- u32 use_large_frame_size;
- u32 padding;
- std::array<u32, 64> channel_mappings;
-};
-
class HwOpus final : public ServiceFramework<HwOpus> {
public:
explicit HwOpus(Core::System& system_);
@@ -28,10 +19,18 @@ public:
private:
void OpenHardwareOpusDecoder(HLERequestContext& ctx);
- void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
void GetWorkBufferSize(HLERequestContext& ctx);
+ void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx);
+ void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx);
+ void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
void GetWorkBufferSizeEx(HLERequestContext& ctx);
+ void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx);
void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx);
+ void GetWorkBufferSizeExEx(HLERequestContext& ctx);
+ void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx);
+
+ Core::System& system;
+ AudioCore::OpusDecoder::OpusDecoderManager impl;
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index 446f46b3c..9eaae4c4b 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -122,20 +122,18 @@ private:
}
void ImportTicket(HLERequestContext& ctx) {
- const auto ticket = ctx.ReadBuffer();
+ const auto raw_ticket = ctx.ReadBuffer();
[[maybe_unused]] const auto cert = ctx.ReadBuffer(1);
- if (ticket.size() < sizeof(Core::Crypto::Ticket)) {
+ if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) {
LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
return;
}
- Core::Crypto::Ticket raw{};
- std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket));
-
- if (!keys.AddTicketPersonalized(raw)) {
+ Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket);
+ if (!keys.AddTicket(ticket)) {
LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT);
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index dfcdd3ada..508db7360 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -4,6 +4,7 @@
#include <utility>
#include "common/assert.h"
+#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
#include "core/core.h"
@@ -57,8 +58,8 @@ Result VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size
return FileSys::ERROR_PATH_NOT_FOUND;
}
- const auto entry_type = GetEntryType(path);
- if (entry_type.Code() == ResultSuccess) {
+ FileSys::EntryType entry_type{};
+ if (GetEntryType(&entry_type, path) == ResultSuccess) {
return FileSys::ERROR_PATH_ALREADY_EXISTS;
}
@@ -154,10 +155,18 @@ Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
std::string src_path(Common::FS::SanitizePath(src_path_));
std::string dest_path(Common::FS::SanitizePath(dest_path_));
auto src = backing->GetFileRelative(src_path);
+ auto dst = backing->GetFileRelative(dest_path);
if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
// Use more-optimized vfs implementation rename.
- if (src == nullptr)
+ if (src == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
+ }
+
+ if (dst && Common::FS::Exists(dst->GetFullPath())) {
+ LOG_ERROR(Service_FS, "File at new_path={} already exists", dst->GetFullPath());
+ return FileSys::ERROR_PATH_ALREADY_EXISTS;
+ }
+
if (!src->Rename(Common::FS::GetFilename(dest_path))) {
// TODO(DarkLordZach): Find a better error code for this
return ResultUnknown;
@@ -210,8 +219,8 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
return ResultUnknown;
}
-ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_,
- FileSys::Mode mode) const {
+Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
+ const std::string& path_, FileSys::Mode mode) const {
const std::string path(Common::FS::SanitizePath(path_));
std::string_view npath = path;
while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) {
@@ -224,50 +233,68 @@ ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::
}
if (mode == FileSys::Mode::Append) {
- return std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize());
+ *out_file = std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize());
+ } else {
+ *out_file = file;
}
- return file;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) {
+Result VfsDirectoryServiceWrapper::OpenDirectory(FileSys::VirtualDir* out_directory,
+ const std::string& path_) {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, path);
if (dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return FileSys::ERROR_PATH_NOT_FOUND;
}
- return dir;
+ *out_directory = dir;
+ return ResultSuccess;
}
-ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
- const std::string& path_) const {
+Result VfsDirectoryServiceWrapper::GetEntryType(FileSys::EntryType* out_entry_type,
+ const std::string& path_) const {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
- if (dir == nullptr)
+ if (dir == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
+ }
+
auto filename = Common::FS::GetFilename(path);
// TODO(Subv): Some games use the '/' path, find out what this means.
- if (filename.empty())
- return FileSys::EntryType::Directory;
+ if (filename.empty()) {
+ *out_entry_type = FileSys::EntryType::Directory;
+ return ResultSuccess;
+ }
+
+ if (dir->GetFile(filename) != nullptr) {
+ *out_entry_type = FileSys::EntryType::File;
+ return ResultSuccess;
+ }
+
+ if (dir->GetSubdirectory(filename) != nullptr) {
+ *out_entry_type = FileSys::EntryType::Directory;
+ return ResultSuccess;
+ }
- if (dir->GetFile(filename) != nullptr)
- return FileSys::EntryType::File;
- if (dir->GetSubdirectory(filename) != nullptr)
- return FileSys::EntryType::Directory;
return FileSys::ERROR_PATH_NOT_FOUND;
}
-ResultVal<FileSys::FileTimeStampRaw> VfsDirectoryServiceWrapper::GetFileTimeStampRaw(
- const std::string& path) const {
+Result VfsDirectoryServiceWrapper::GetFileTimeStampRaw(
+ FileSys::FileTimeStampRaw* out_file_time_stamp_raw, const std::string& path) const {
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (dir == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
- if (GetEntryType(path).Failed()) {
+
+ FileSys::EntryType entry_type;
+ if (GetEntryType(&entry_type, path) != ResultSuccess) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
- return dir->GetFileTimeStamp(Common::FS::GetFilename(path));
+
+ *out_file_time_stamp_raw = dir->GetFileTimeStamp(Common::FS::GetFilename(path));
+ return ResultSuccess;
}
FileSystemController::FileSystemController(Core::System& system_) : system{system_} {}
@@ -310,57 +337,59 @@ void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) {
romfs_factory->SetPackedUpdate(std::move(update_raw));
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() const {
+FileSys::VirtualFile FileSystemController::OpenRomFSCurrentProcess() const {
LOG_TRACE(Service_FS, "Opening RomFS for current process");
if (romfs_factory == nullptr) {
- // TODO(bunnei): Find a better error code for this
- return ResultUnknown;
+ return nullptr;
}
return romfs_factory->OpenCurrentProcess(system.GetApplicationProcessProgramID());
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFS(
- u64 title_id, FileSys::ContentRecordType type) const {
+FileSys::VirtualFile FileSystemController::OpenPatchedRomFS(u64 title_id,
+ FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id);
if (romfs_factory == nullptr) {
- // TODO: Find a better error code for this
- return ResultUnknown;
+ return nullptr;
}
return romfs_factory->OpenPatchedRomFS(title_id, type);
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFSWithProgramIndex(
+FileSys::VirtualFile FileSystemController::OpenPatchedRomFSWithProgramIndex(
u64 title_id, u8 program_index, FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id,
program_index);
if (romfs_factory == nullptr) {
- // TODO: Find a better error code for this
- return ResultUnknown;
+ return nullptr;
}
return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type);
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
- u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
+FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
title_id, storage_id, type);
if (romfs_factory == nullptr) {
- // TODO(bunnei): Find a better error code for this
- return ResultUnknown;
+ return nullptr;
}
return romfs_factory->Open(title_id, storage_id, type);
}
-ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const {
+std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca(
+ u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
+ return romfs_factory->GetEntry(title_id, storage_id, type);
+}
+
+Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data,
+ FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& save_struct) const {
LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space,
save_struct.DebugInfo());
@@ -368,11 +397,18 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->Create(space, save_struct);
+ auto save_data = save_data_factory->Create(space, save_struct);
+ if (save_data == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data = save_data;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const {
+Result FileSystemController::OpenSaveData(FileSys::VirtualDir* out_save_data,
+ FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& attribute) const {
LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", space,
attribute.DebugInfo());
@@ -380,32 +416,50 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->Open(space, attribute);
+ auto save_data = save_data_factory->Open(space, attribute);
+ if (save_data == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data = save_data;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace(
- FileSys::SaveDataSpaceId space) const {
+Result FileSystemController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
+ FileSys::SaveDataSpaceId space) const {
LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", space);
if (save_data_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- return save_data_factory->GetSaveDataSpaceDirectory(space);
+ auto save_data_space = save_data_factory->GetSaveDataSpaceDirectory(space);
+ if (save_data_space == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data_space = save_data_space;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const {
+Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const {
LOG_TRACE(Service_FS, "Opening SDMC");
if (sdmc_factory == nullptr) {
return FileSys::ERROR_SD_CARD_NOT_FOUND;
}
- return sdmc_factory->Open();
+ auto sdmc = sdmc_factory->Open();
+ if (sdmc == nullptr) {
+ return FileSys::ERROR_SD_CARD_NOT_FOUND;
+ }
+
+ *out_sdmc = sdmc;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition(
- FileSys::BisPartitionId id) const {
+Result FileSystemController::OpenBISPartition(FileSys::VirtualDir* out_bis_partition,
+ FileSys::BisPartitionId id) const {
LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id);
if (bis_factory == nullptr) {
@@ -417,11 +471,12 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition(
return FileSys::ERROR_INVALID_ARGUMENT;
}
- return part;
+ *out_bis_partition = part;
+ return ResultSuccess;
}
-ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
- FileSys::BisPartitionId id) const {
+Result FileSystemController::OpenBISPartitionStorage(
+ FileSys::VirtualFile* out_bis_partition_storage, FileSys::BisPartitionId id) const {
LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id);
if (bis_factory == nullptr) {
@@ -433,7 +488,8 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
return FileSys::ERROR_INVALID_ARGUMENT;
}
- return part;
+ *out_bis_partition_storage = part;
+ return ResultSuccess;
}
u64 FileSystemController::GetFreeSpaceSize(FileSys::StorageId id) const {
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index a5c1c9d3e..e7e7c4c28 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -15,6 +15,7 @@ class System;
namespace FileSys {
class BISFactory;
+class NCA;
class RegisteredCache;
class RegisteredCacheUnion;
class PlaceholderCache;
@@ -64,21 +65,26 @@ public:
Result RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
void SetPackedUpdate(FileSys::VirtualFile update_raw);
- ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const;
- ResultVal<FileSys::VirtualFile> OpenPatchedRomFS(u64 title_id,
- FileSys::ContentRecordType type) const;
- ResultVal<FileSys::VirtualFile> OpenPatchedRomFSWithProgramIndex(
- u64 title_id, u8 program_index, FileSys::ContentRecordType type) const;
- ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::VirtualFile OpenRomFSCurrentProcess() const;
+ FileSys::VirtualFile OpenPatchedRomFS(u64 title_id, FileSys::ContentRecordType type) const;
+ FileSys::VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
+ FileSys::ContentRecordType type) const;
+ FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::ContentRecordType type) const;
+ std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type) const;
- ResultVal<FileSys::VirtualDir> CreateSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const;
- ResultVal<FileSys::VirtualDir> OpenSaveData(
- FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const;
- ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const;
- ResultVal<FileSys::VirtualDir> OpenSDMC() const;
- ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const;
- ResultVal<FileSys::VirtualFile> OpenBISPartitionStorage(FileSys::BisPartitionId id) const;
+
+ Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& save_struct) const;
+ Result OpenSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& save_struct) const;
+ Result OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
+ FileSys::SaveDataSpaceId space) const;
+ Result OpenSDMC(FileSys::VirtualDir* out_sdmc) const;
+ Result OpenBISPartition(FileSys::VirtualDir* out_bis_partition,
+ FileSys::BisPartitionId id) const;
+ Result OpenBISPartitionStorage(FileSys::VirtualFile* out_bis_partition_storage,
+ FileSys::BisPartitionId id) const;
u64 GetFreeSpaceSize(FileSys::StorageId id) const;
u64 GetTotalSpaceSize(FileSys::StorageId id) const;
@@ -224,26 +230,28 @@ public:
* @param mode Mode to open the file with
* @return Opened file, or error code
*/
- ResultVal<FileSys::VirtualFile> OpenFile(const std::string& path, FileSys::Mode mode) const;
+ Result OpenFile(FileSys::VirtualFile* out_file, const std::string& path,
+ FileSys::Mode mode) const;
/**
* Open a directory specified by its path
* @param path Path relative to the archive
* @return Opened directory, or error code
*/
- ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path);
+ Result OpenDirectory(FileSys::VirtualDir* out_directory, const std::string& path);
/**
* Get the type of the specified path
* @return The type of the specified path or error code
*/
- ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const;
+ Result GetEntryType(FileSys::EntryType* out_entry_type, const std::string& path) const;
/**
* Get the timestamp of the specified path
* @return The timestamp of the specified path or error code
*/
- ResultVal<FileSys::FileTimeStampRaw> GetFileTimeStampRaw(const std::string& path) const;
+ Result GetFileTimeStampRaw(FileSys::FileTimeStampRaw* out_time_stamp_raw,
+ const std::string& path) const;
private:
FileSys::VirtualDir backing;
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 427dbc8b3..6e4d26b1e 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -419,14 +419,15 @@ public:
LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode);
- auto result = backend.OpenFile(name, mode);
- if (result.Failed()) {
+ FileSys::VirtualFile vfs_file{};
+ auto result = backend.OpenFile(&vfs_file, name, mode);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
- auto file = std::make_shared<IFile>(system, result.Unwrap());
+ auto file = std::make_shared<IFile>(system, vfs_file);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -444,14 +445,15 @@ public:
LOG_DEBUG(Service_FS, "called. directory={}, filter={}", name, filter_flags);
- auto result = backend.OpenDirectory(name);
- if (result.Failed()) {
+ FileSys::VirtualDir vfs_dir{};
+ auto result = backend.OpenDirectory(&vfs_dir, name);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
- auto directory = std::make_shared<IDirectory>(system, result.Unwrap());
+ auto directory = std::make_shared<IDirectory>(system, vfs_dir);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -464,16 +466,17 @@ public:
LOG_DEBUG(Service_FS, "called. file={}", name);
- auto result = backend.GetEntryType(name);
- if (result.Failed()) {
+ FileSys::EntryType vfs_entry_type{};
+ auto result = backend.GetEntryType(&vfs_entry_type, name);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(*result));
+ rb.Push<u32>(static_cast<u32>(vfs_entry_type));
}
void Commit(HLERequestContext& ctx) {
@@ -505,16 +508,17 @@ public:
LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", name);
- auto result = backend.GetFileTimeStampRaw(name);
- if (result.Failed()) {
+ FileSys::FileTimeStampRaw vfs_timestamp{};
+ auto result = backend.GetFileTimeStampRaw(&vfs_timestamp, name);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(ResultSuccess);
- rb.PushRaw(*result);
+ rb.PushRaw(vfs_timestamp);
}
private:
@@ -572,14 +576,15 @@ private:
}
void FindAllSaves(FileSys::SaveDataSpaceId space) {
- const auto save_root = fsc.OpenSaveDataSpace(space);
+ FileSys::VirtualDir save_root{};
+ const auto result = fsc.OpenSaveDataSpace(&save_root, space);
- if (save_root.Failed() || *save_root == nullptr) {
+ if (result != ResultSuccess || save_root == nullptr) {
LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space);
return;
}
- for (const auto& type : (*save_root)->GetSubdirectories()) {
+ for (const auto& type : save_root->GetSubdirectories()) {
if (type->GetName() == "save") {
for (const auto& save_id : type->GetSubdirectories()) {
for (const auto& user_id : save_id->GetSubdirectories()) {
@@ -837,9 +842,11 @@ void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) {
void FSP_SRV::OpenSdCardFileSystem(HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
- auto filesystem =
- std::make_shared<IFileSystem>(system, fsc.OpenSDMC().Unwrap(),
- SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
+ FileSys::VirtualDir sdmc_dir{};
+ fsc.OpenSDMC(&sdmc_dir);
+
+ auto filesystem = std::make_shared<IFileSystem>(
+ system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -856,7 +863,8 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(),
uid[1], uid[0]);
- fsc.CreateSaveData(FileSys::SaveDataSpaceId::NandUser, save_struct);
+ FileSys::VirtualDir save_data_dir{};
+ fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser, save_struct);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -874,8 +882,9 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
LOG_INFO(Service_FS, "called.");
- auto dir = fsc.OpenSaveData(parameters.space_id, parameters.attribute);
- if (dir.Failed()) {
+ FileSys::VirtualDir dir{};
+ auto result = fsc.OpenSaveData(&dir, parameters.space_id, parameters.attribute);
+ if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
return;
@@ -899,8 +908,8 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
ASSERT(false);
}
- auto filesystem = std::make_shared<IFileSystem>(system, std::move(dir.Unwrap()),
- SizeGetter::FromStorageId(fsc, id));
+ auto filesystem =
+ std::make_shared<IFileSystem>(system, std::move(dir), SizeGetter::FromStorageId(fsc, id));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -970,7 +979,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) {
if (!romfs) {
auto current_romfs = fsc.OpenRomFSCurrentProcess();
- if (current_romfs.Failed()) {
+ if (!current_romfs) {
// TODO (bunnei): Find the right error code to use here
LOG_CRITICAL(Service_FS, "no file system interface available!");
IPC::ResponseBuilder rb{ctx, 2};
@@ -978,7 +987,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) {
return;
}
- romfs = current_romfs.Unwrap();
+ romfs = current_romfs;
}
auto storage = std::make_shared<IStorage>(system, romfs);
@@ -999,7 +1008,7 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
- if (data.Failed()) {
+ if (!data) {
const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
if (archive != nullptr) {
@@ -1020,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
const FileSys::PatchManager pm{title_id, fsc, content_provider};
+ auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data);
auto storage = std::make_shared<IStorage>(
- system, pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
+ system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -1051,7 +1061,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
fsc.OpenPatchedRomFSWithProgramIndex(system.GetApplicationProcessProgramID(), program_index,
FileSys::ContentRecordType::Program);
- if (patched_romfs.Failed()) {
+ if (!patched_romfs) {
// TODO: Find the right error code to use here
LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index);
@@ -1060,7 +1070,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
return;
}
- auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs.Unwrap()));
+ auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
index ed6fcb5f6..6f1151b03 100644
--- a/src/core/hle/service/glue/arp.cpp
+++ b/src/core/hle/service/glue/arp.cpp
@@ -65,18 +65,19 @@ void ARP_R::GetApplicationLaunchProperty(HLERequestContext& ctx) {
return;
}
- const auto res = manager.GetLaunchProperty(*title_id);
+ ApplicationLaunchProperty launch_property{};
+ const auto res = manager.GetLaunchProperty(&launch_property, *title_id);
- if (res.Failed()) {
+ if (res != ResultSuccess) {
LOG_ERROR(Service_ARP, "Failed to get launch property!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
return;
}
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- rb.PushRaw(*res);
+ rb.PushRaw(launch_property);
}
void ARP_R::GetApplicationLaunchPropertyWithApplicationId(HLERequestContext& ctx) {
@@ -85,18 +86,19 @@ void ARP_R::GetApplicationLaunchPropertyWithApplicationId(HLERequestContext& ctx
LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
- const auto res = manager.GetLaunchProperty(title_id);
+ ApplicationLaunchProperty launch_property{};
+ const auto res = manager.GetLaunchProperty(&launch_property, title_id);
- if (res.Failed()) {
+ if (res != ResultSuccess) {
LOG_ERROR(Service_ARP, "Failed to get launch property!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
return;
}
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- rb.PushRaw(*res);
+ rb.PushRaw(launch_property);
}
void ARP_R::GetApplicationControlProperty(HLERequestContext& ctx) {
@@ -113,16 +115,17 @@ void ARP_R::GetApplicationControlProperty(HLERequestContext& ctx) {
return;
}
- const auto res = manager.GetControlProperty(*title_id);
+ std::vector<u8> nacp_data;
+ const auto res = manager.GetControlProperty(&nacp_data, *title_id);
- if (res.Failed()) {
+ if (res != ResultSuccess) {
LOG_ERROR(Service_ARP, "Failed to get control property!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
return;
}
- ctx.WriteBuffer(*res);
+ ctx.WriteBuffer(nacp_data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -134,16 +137,17 @@ void ARP_R::GetApplicationControlPropertyWithApplicationId(HLERequestContext& ct
LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
- const auto res = manager.GetControlProperty(title_id);
+ std::vector<u8> nacp_data;
+ const auto res = manager.GetControlProperty(&nacp_data, title_id);
- if (res.Failed()) {
+ if (res != ResultSuccess) {
LOG_ERROR(Service_ARP, "Failed to get control property!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
return;
}
- ctx.WriteBuffer(*res);
+ ctx.WriteBuffer(nacp_data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/glue/glue_manager.cpp b/src/core/hle/service/glue/glue_manager.cpp
index 4bf67921b..22f001704 100644
--- a/src/core/hle/service/glue/glue_manager.cpp
+++ b/src/core/hle/service/glue/glue_manager.cpp
@@ -15,7 +15,8 @@ ARPManager::ARPManager() = default;
ARPManager::~ARPManager() = default;
-ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const {
+Result ARPManager::GetLaunchProperty(ApplicationLaunchProperty* out_launch_property,
+ u64 title_id) const {
if (title_id == 0) {
return Glue::ResultInvalidProcessId;
}
@@ -25,10 +26,11 @@ ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id)
return Glue::ResultProcessIdNotRegistered;
}
- return iter->second.launch;
+ *out_launch_property = iter->second.launch;
+ return ResultSuccess;
}
-ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const {
+Result ARPManager::GetControlProperty(std::vector<u8>* out_control_property, u64 title_id) const {
if (title_id == 0) {
return Glue::ResultInvalidProcessId;
}
@@ -38,7 +40,8 @@ ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const {
return Glue::ResultProcessIdNotRegistered;
}
- return iter->second.control;
+ *out_control_property = iter->second.control;
+ return ResultSuccess;
}
Result ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch,
diff --git a/src/core/hle/service/glue/glue_manager.h b/src/core/hle/service/glue/glue_manager.h
index 1cf53d9d9..216aa34c1 100644
--- a/src/core/hle/service/glue/glue_manager.h
+++ b/src/core/hle/service/glue/glue_manager.h
@@ -32,12 +32,12 @@ public:
// Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was
// previously registered, otherwise ResultProcessIdNotRegistered if it was never registered or
// ResultInvalidProcessId if the title ID is 0.
- ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const;
+ Result GetLaunchProperty(ApplicationLaunchProperty* out_launch_property, u64 title_id) const;
// Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to
// the provided title ID if it was previously registered, otherwise ResultProcessIdNotRegistered
// if it was never registered or ResultInvalidProcessId if the title ID is 0.
- ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const;
+ Result GetControlProperty(std::vector<u8>* out_control_property, u64 title_id) const;
// Adds a new entry to the internal database with the provided parameters, returning
// ResultProcessIdNotRegistered if attempting to re-register a title ID without an intermediate
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index 03432f7cb..63eecd42b 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -331,7 +331,7 @@ Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties()
};
// Hack: There is no touch in docked but games still allow it
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
gesture.points[id] = {
.x = static_cast<s32>(active_x * Layout::ScreenDocked::Width),
.y = static_cast<s32>(active_y * Layout::ScreenDocked::Height),
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 28818c813..146bb486d 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->system_properties.use_minus.Assign(1);
shared_memory->system_properties.is_charging_joy_dual.Assign(
battery_level.dual.is_charging);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController;
+ shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController;
shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::Handheld:
@@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->system_properties.is_charging_joy_right.Assign(
battery_level.right.is_charging);
shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
- shared_memory->applet_nfc_xcd.applet_footer.type =
- AppletFooterUiType::HandheldJoyConLeftJoyConRight;
+ shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::JoyconDual:
@@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyDual;
shared_memory->fullkey_color.fullkey = body_colors.left;
shared_memory->battery_level_dual = battery_level.left.battery_level;
shared_memory->system_properties.is_charging_joy_dual.Assign(
battery_level.left.is_charging);
} else if (controller.is_dual_left_connected) {
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly;
shared_memory->fullkey_color.fullkey = body_colors.left;
shared_memory->battery_level_dual = battery_level.left.battery_level;
shared_memory->system_properties.is_charging_joy_dual.Assign(
battery_level.left.is_charging);
} else {
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly;
shared_memory->fullkey_color.fullkey = body_colors.right;
shared_memory->battery_level_dual = battery_level.right.battery_level;
shared_memory->system_properties.is_charging_joy_dual.Assign(
@@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->system_properties.use_minus.Assign(1);
shared_memory->system_properties.is_charging_joy_left.Assign(
battery_level.left.is_charging);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal;
shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::JoyconRight:
@@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
shared_memory->system_properties.use_plus.Assign(1);
shared_memory->system_properties.is_charging_joy_right.Assign(
battery_level.right.is_charging);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal;
+ shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal;
shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
break;
case Core::HID::NpadStyleIndex::GameCube:
@@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
case Core::HID::NpadStyleIndex::SNES:
shared_memory->style_tag.lucia.Assign(1);
shared_memory->device_type.fullkey.Assign(1);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia;
+ shared_memory->applet_footer_type = AppletFooterUiType::Lucia;
break;
case Core::HID::NpadStyleIndex::N64:
shared_memory->style_tag.lagoon.Assign(1);
shared_memory->device_type.fullkey.Assign(1);
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon;
+ shared_memory->applet_footer_type = AppletFooterUiType::Lagon;
break;
case Core::HID::NpadStyleIndex::SegaGenesis:
shared_memory->style_tag.lager.Assign(1);
@@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
std::scoped_lock lock{mutex};
auto& controller = GetControllerFromNpadIdType(npad_id);
const auto controller_type = controller.device->GetNpadStyleIndex();
+
+ if (!controller.device->IsConnected() && controller.is_connected) {
+ DisconnectNpad(npad_id);
+ return;
+ }
if (!controller.device->IsConnected()) {
return;
}
+ if (controller.device->IsConnected() && !controller.is_connected) {
+ InitNewlyAddedController(npad_id);
+ }
// This function is unique to yuzu for the turbo buttons and motion to work properly
controller.device->StatusUpdate();
@@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
pad_entry.npad_buttons.l.Assign(button_state.zl);
pad_entry.npad_buttons.r.Assign(button_state.zr);
}
+
+ if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) {
+ hid_core.SetLastActiveController(npad_id);
+ }
}
void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
@@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
// Once SetSupportedStyleSet is called controllers are fully initialized
is_controller_initialized = true;
-
- // Connect all active controllers
- for (auto& controller : controller_data) {
- const auto& device = controller.device;
- if (device->IsConnected()) {
- AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
- }
- }
}
Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
@@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
.left = {},
.right = {},
};
- shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None;
+ shared_memory->applet_footer_type = AppletFooterUiType::None;
controller.is_dual_left_connected = true;
controller.is_dual_right_connected = true;
@@ -1508,6 +1511,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() {
return static_cast<Core::HID::NpadButton>(press_state.exchange(0));
}
+void Controller_NPad::ApplyNpadSystemCommonPolicy() {
+ Core::HID::NpadStyleTag styletag{};
+ styletag.fullkey.Assign(1);
+ styletag.handheld.Assign(1);
+ styletag.joycon_dual.Assign(1);
+ styletag.system_ext.Assign(1);
+ styletag.system.Assign(1);
+ SetSupportedStyleSet(styletag);
+
+ SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual);
+
+ supported_npad_id_types.clear();
+ supported_npad_id_types.resize(10);
+ supported_npad_id_types[0] = Core::HID::NpadIdType::Player1;
+ supported_npad_id_types[1] = Core::HID::NpadIdType::Player2;
+ supported_npad_id_types[2] = Core::HID::NpadIdType::Player3;
+ supported_npad_id_types[3] = Core::HID::NpadIdType::Player4;
+ supported_npad_id_types[4] = Core::HID::NpadIdType::Player5;
+ supported_npad_id_types[5] = Core::HID::NpadIdType::Player6;
+ supported_npad_id_types[6] = Core::HID::NpadIdType::Player7;
+ supported_npad_id_types[7] = Core::HID::NpadIdType::Player8;
+ supported_npad_id_types[8] = Core::HID::NpadIdType::Other;
+ supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
+}
+
bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const {
if (controller == Core::HID::NpadStyleIndex::Handheld) {
const bool support_handheld =
@@ -1518,7 +1546,7 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller
return false;
}
// Handheld shouldn't be supported in docked mode
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
return false;
}
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 776411261..949e58a4c 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -190,6 +190,8 @@ public:
// Specifically for cheat engine and other features.
Core::HID::NpadButton GetAndResetPressState();
+ void ApplyNpadSystemCommonPolicy();
+
static bool IsNpadIdValid(Core::HID::NpadIdType npad_id);
static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
static Result VerifyValidSixAxisSensorHandle(
@@ -360,7 +362,7 @@ private:
enum class AppletFooterUiType : u8 {
None = 0,
HandheldNone = 1,
- HandheldJoyConLeftOnly = 1,
+ HandheldJoyConLeftOnly = 2,
HandheldJoyConRightOnly = 3,
HandheldJoyConLeftJoyConRight = 4,
JoyDual = 5,
@@ -382,13 +384,6 @@ private:
Lagon = 21,
};
- struct AppletFooterUi {
- AppletFooterUiAttributes attributes{};
- AppletFooterUiType type{AppletFooterUiType::None};
- INSERT_PADDING_BYTES(0x5B); // Reserved
- };
- static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
-
// This is nn::hid::NpadLarkType
enum class NpadLarkType : u32 {
Invalid,
@@ -419,13 +414,6 @@ private:
U,
};
- struct AppletNfcXcd {
- union {
- AppletFooterUi applet_footer{};
- Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo;
- };
- };
-
// This is nn::hid::detail::NpadInternalState
struct NpadInternalState {
Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None};
@@ -452,7 +440,9 @@ private:
Core::HID::NpadBatteryLevel battery_level_dual{};
Core::HID::NpadBatteryLevel battery_level_left{};
Core::HID::NpadBatteryLevel battery_level_right{};
- AppletNfcXcd applet_nfc_xcd{};
+ AppletFooterUiAttributes applet_footer_attributes{};
+ AppletFooterUiType applet_footer_type{AppletFooterUiType::None};
+ INSERT_PADDING_BYTES(0x5B); // Reserved
INSERT_PADDING_BYTES(0x20); // Unknown
Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{};
NpadLarkType lark_type_l_and_main{};
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index e57a3a80e..dd00921fd 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -16,22 +16,6 @@ class EmulatedConsole;
namespace Service::HID {
class Controller_Touchscreen final : public ControllerBase {
public:
- // This is nn::hid::TouchScreenModeForNx
- enum class TouchScreenModeForNx : u8 {
- UseSystemSetting,
- Finger,
- Heat2,
- };
-
- // This is nn::hid::TouchScreenConfigurationForNx
- struct TouchScreenConfigurationForNx {
- TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
- INSERT_PADDING_BYTES_NOINIT(0x7);
- INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved
- };
- static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17,
- "TouchScreenConfigurationForNx is an invalid size");
-
explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
~Controller_Touchscreen() override;
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 2bf1d8a27..4d70006c1 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
return applet_resource;
}
-Hid::Hid(Core::System& system_)
- : ServiceFramework{system_, "hid"}, service_context{system_, service_name} {
+Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
+ : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{
+ system_,
+ service_name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Hid::CreateAppletResource, "CreateAppletResource"},
@@ -2368,7 +2370,7 @@ void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) {
void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()};
+ const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}",
@@ -2543,7 +2545,9 @@ public:
class HidSys final : public ServiceFramework<HidSys> {
public:
- explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} {
+ explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
+ : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"},
+ applet_resource{applet_resource_} {
// clang-format off
static const FunctionInfo functions[] = {
{31, nullptr, "SendKeyboardLockKeyEvent"},
@@ -2568,7 +2572,7 @@ public:
{303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"},
{304, nullptr, "EnableAssigningSingleOnSlSrPress"},
{305, nullptr, "DisableAssigningSingleOnSlSrPress"},
- {306, nullptr, "GetLastActiveNpad"},
+ {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"},
{307, nullptr, "GetNpadSystemExtStyle"},
{308, nullptr, "ApplyNpadSystemCommonPolicyFull"},
{309, nullptr, "GetNpadFullKeyGripColor"},
@@ -2624,7 +2628,7 @@ public:
{700, nullptr, "ActivateUniquePad"},
{702, nullptr, "AcquireUniquePadConnectionEventHandle"},
{703, nullptr, "GetUniquePadIds"},
- {751, nullptr, "AcquireJoyDetachOnBluetoothOffEventHandle"},
+ {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"},
{800, nullptr, "ListSixAxisSensorHandles"},
{801, nullptr, "IsSixAxisSensorUserCalibrationSupported"},
{802, nullptr, "ResetSixAxisSensorCalibrationValues"},
@@ -2650,7 +2654,7 @@ public:
{830, nullptr, "SetNotificationLedPattern"},
{831, nullptr, "SetNotificationLedPatternWithTimeout"},
{832, nullptr, "PrepareHidsForNotificationWake"},
- {850, nullptr, "IsUsbFullKeyControllerEnabled"},
+ {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
{851, nullptr, "EnableUsbFullKeyController"},
{852, nullptr, "IsUsbConnected"},
{870, nullptr, "IsHandheldButtonPressedOnConsoleMode"},
@@ -2682,7 +2686,7 @@ public:
{1150, nullptr, "SetTouchScreenMagnification"},
{1151, nullptr, "GetTouchScreenFirmwareVersion"},
{1152, nullptr, "SetTouchScreenDefaultConfiguration"},
- {1153, nullptr, "GetTouchScreenDefaultConfiguration"},
+ {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"},
{1154, nullptr, "IsFirmwareAvailableForNotification"},
{1155, nullptr, "SetForceHandheldStyleVibration"},
{1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"},
@@ -2749,37 +2753,102 @@ public:
// clang-format on
RegisterHandlers(functions);
+
+ joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent");
}
private:
void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
- // We already do this for homebrew so we can just stub it out
LOG_WARNING(Service_HID, "called");
+ GetAppletResource()
+ ->GetController<Controller_NPad>(HidController::NPad)
+ .ApplyNpadSystemCommonPolicy();
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
+ void GetLastActiveNpad(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(system.HIDCore().GetLastActiveController());
+ }
+
void GetUniquePadsFromNpad(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
- const s64 total_entries = 0;
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type);
+ const std::vector<Core::HID::UniquePadId> unique_pads{};
+
+ ctx.WriteBuffer(unique_pads);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(unique_pads.size()));
+ }
+
+ void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(joy_detach_event->GetReadableEvent());
+ }
+
+ void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
+ const bool is_enabled = false;
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled);
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(total_entries);
+ rb.Push(is_enabled);
}
+
+ void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ Core::HID::TouchScreenConfigurationForNx touchscreen_config{
+ .mode = Core::HID::TouchScreenModeForNx::Finger,
+ };
+
+ if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 &&
+ touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) {
+ touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(touchscreen_config);
+ }
+
+ std::shared_ptr<IAppletResource> GetAppletResource() {
+ if (applet_resource == nullptr) {
+ applet_resource = std::make_shared<IAppletResource>(system, service_context);
+ }
+
+ return applet_resource;
+ }
+
+ Kernel::KEvent* joy_detach_event;
+ KernelHelpers::ServiceContext service_context;
+ std::shared_ptr<IAppletResource> applet_resource;
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
+ std::shared_ptr<IAppletResource> applet_resource;
- server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system));
+ server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource));
server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system));
server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system));
- server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system));
+ server_manager->RegisterNamedService("hid:sys",
+ std::make_shared<HidSys>(system, applet_resource));
server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system));
server_manager->RegisterNamedService("irs:sys",
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index f247b83c2..0ca43de93 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -95,7 +95,7 @@ private:
class Hid final : public ServiceFramework<Hid> {
public:
- explicit Hid(Core::System& system_);
+ explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_);
~Hid() override;
std::shared_ptr<IAppletResource> GetAppletResource();
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 055c0a2db..c73035c77 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -357,7 +357,8 @@ public:
return ResultSuccess;
}
- ResultVal<VAddr> MapProcessCodeMemory(Kernel::KProcess* process, VAddr base_addr, u64 size) {
+ Result MapProcessCodeMemory(VAddr* out_map_location, Kernel::KProcess* process, VAddr base_addr,
+ u64 size) {
auto& page_table{process->GetPageTable()};
VAddr addr{};
@@ -372,20 +373,21 @@ public:
R_TRY(result);
if (ValidateRegionForMap(page_table, addr, size)) {
- return addr;
+ *out_map_location = addr;
+ return ResultSuccess;
}
}
return ERROR_INSUFFICIENT_ADDRESS_SPACE;
}
- ResultVal<VAddr> MapNro(Kernel::KProcess* process, VAddr nro_addr, std::size_t nro_size,
- VAddr bss_addr, std::size_t bss_size, std::size_t size) {
+ Result MapNro(VAddr* out_map_location, Kernel::KProcess* process, VAddr nro_addr,
+ std::size_t nro_size, VAddr bss_addr, std::size_t bss_size, std::size_t size) {
for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
auto& page_table{process->GetPageTable()};
VAddr addr{};
- CASCADE_RESULT(addr, MapProcessCodeMemory(process, nro_addr, nro_size));
+ R_TRY(MapProcessCodeMemory(&addr, process, nro_addr, nro_size));
if (bss_size) {
auto block_guard = detail::ScopeExit([&] {
@@ -411,7 +413,8 @@ public:
}
if (ValidateRegionForMap(page_table, addr, size)) {
- return addr;
+ *out_map_location = addr;
+ return ResultSuccess;
}
}
@@ -437,9 +440,9 @@ public:
CopyCode(nro_addr + nro_header.segment_headers[DATA_INDEX].memory_offset, data_start,
nro_header.segment_headers[DATA_INDEX].memory_size);
- CASCADE_CODE(process->GetPageTable().SetProcessMemoryPermission(
+ R_TRY(process->GetPageTable().SetProcessMemoryPermission(
text_start, ro_start - text_start, Kernel::Svc::MemoryPermission::ReadExecute));
- CASCADE_CODE(process->GetPageTable().SetProcessMemoryPermission(
+ R_TRY(process->GetPageTable().SetProcessMemoryPermission(
ro_start, data_start - ro_start, Kernel::Svc::MemoryPermission::Read));
return process->GetPageTable().SetProcessMemoryPermission(
@@ -542,31 +545,32 @@ public:
}
// Map memory for the NRO
- const auto map_result{MapNro(system.ApplicationProcess(), nro_address, nro_size,
- bss_address, bss_size, nro_size + bss_size)};
- if (map_result.Failed()) {
+ VAddr map_location{};
+ const auto map_result{MapNro(&map_location, system.ApplicationProcess(), nro_address,
+ nro_size, bss_address, bss_size, nro_size + bss_size)};
+ if (map_result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(map_result.Code());
+ rb.Push(map_result);
}
// Load the NRO into the mapped memory
if (const auto result{
- LoadNro(system.ApplicationProcess(), header, nro_address, *map_result)};
+ LoadNro(system.ApplicationProcess(), header, nro_address, map_location)};
result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(map_result.Code());
+ rb.Push(result);
}
// Track the loaded NRO
- nro.insert_or_assign(*map_result,
- NROInfo{hash, *map_result, nro_size, bss_address, bss_size,
+ nro.insert_or_assign(map_location,
+ NROInfo{hash, map_location, nro_size, bss_address, bss_size,
header.segment_headers[TEXT_INDEX].memory_size,
header.segment_headers[RO_INDEX].memory_size,
header.segment_headers[DATA_INDEX].memory_size, nro_address});
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push(*map_result);
+ rb.Push(map_location);
}
Result UnmapNro(const NROInfo& info) {
@@ -574,19 +578,19 @@ public:
auto& page_table{system.ApplicationProcess()->GetPageTable()};
if (info.bss_size != 0) {
- CASCADE_CODE(page_table.UnmapCodeMemory(
+ R_TRY(page_table.UnmapCodeMemory(
info.nro_address + info.text_size + info.ro_size + info.data_size, info.bss_address,
info.bss_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
}
- CASCADE_CODE(page_table.UnmapCodeMemory(
+ R_TRY(page_table.UnmapCodeMemory(
info.nro_address + info.text_size + info.ro_size,
info.src_addr + info.text_size + info.ro_size, info.data_size,
Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
- CASCADE_CODE(page_table.UnmapCodeMemory(
+ R_TRY(page_table.UnmapCodeMemory(
info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size,
Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
- CASCADE_CODE(page_table.UnmapCodeMemory(
+ R_TRY(page_table.UnmapCodeMemory(
info.nro_address, info.src_addr, info.text_size,
Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
return ResultSuccess;
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index 5c7adf97d..c28eed926 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -7,17 +7,21 @@
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/store_data.h"
+#include "core/hle/service/mii/types/ver3_store_data.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::Mii {
-constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
-
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public:
- explicit IDatabaseService(Core::System& system_)
- : ServiceFramework{system_, "IDatabaseService"} {
+ explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager,
+ bool is_system_)
+ : ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{
+ is_system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDatabaseService::IsUpdated, "IsUpdated"},
@@ -28,142 +32,134 @@ public:
{5, &IDatabaseService::UpdateLatest, "UpdateLatest"},
{6, &IDatabaseService::BuildRandom, "BuildRandom"},
{7, &IDatabaseService::BuildDefault, "BuildDefault"},
- {8, nullptr, "Get2"},
- {9, nullptr, "Get3"},
- {10, nullptr, "UpdateLatest1"},
- {11, nullptr, "FindIndex"},
- {12, nullptr, "Move"},
- {13, nullptr, "AddOrReplace"},
- {14, nullptr, "Delete"},
- {15, nullptr, "DestroyFile"},
- {16, nullptr, "DeleteFile"},
- {17, nullptr, "Format"},
+ {8, &IDatabaseService::Get2, "Get2"},
+ {9, &IDatabaseService::Get3, "Get3"},
+ {10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"},
+ {11, &IDatabaseService::FindIndex, "FindIndex"},
+ {12, &IDatabaseService::Move, "Move"},
+ {13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
+ {14, &IDatabaseService::Delete, "Delete"},
+ {15, &IDatabaseService::DestroyFile, "DestroyFile"},
+ {16, &IDatabaseService::DeleteFile, "DeleteFile"},
+ {17, &IDatabaseService::Format, "Format"},
{18, nullptr, "Import"},
{19, nullptr, "Export"},
- {20, nullptr, "IsBrokenDatabaseWithClearFlag"},
+ {20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"},
{21, &IDatabaseService::GetIndex, "GetIndex"},
{22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
{23, &IDatabaseService::Convert, "Convert"},
- {24, nullptr, "ConvertCoreDataToCharInfo"},
- {25, nullptr, "ConvertCharInfoToCoreData"},
- {26, nullptr, "Append"},
+ {24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"},
+ {25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"},
+ {26, &IDatabaseService::Append, "Append"},
};
// clang-format on
RegisterHandlers(functions);
- }
-private:
- template <typename T>
- std::vector<u8> SerializeArray(const std::vector<T>& values) {
- std::vector<u8> out(values.size() * sizeof(T));
- std::size_t offset{};
- for (const auto& value : values) {
- std::memcpy(out.data() + offset, &value, sizeof(T));
- offset += sizeof(T);
- }
- return out;
+ manager->Initialize(metadata);
}
+private:
void IsUpdated(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+ const bool is_updated = manager->IsUpdated(metadata, source_flag);
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter));
+ rb.Push<u8>(is_updated);
}
void IsFullDatabase(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called");
+ const bool is_full_database = manager->IsFullDatabase();
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(manager.IsFullDatabase());
+ rb.Push<u8>(is_full_database);
}
void GetCount(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
- LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+ const u32 mii_count = manager->GetCount(metadata, source_flag);
+
+ LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(manager.GetCount(source_flag));
+ rb.Push(mii_count);
}
void Get(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
+ const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
- LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+ u32 mii_count{};
+ std::vector<CharInfoElement> char_info_elements(output_size);
+ const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag);
- const auto result{manager.GetDefault(source_flag)};
- if (result.Failed()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
- return;
+ if (mii_count != 0) {
+ ctx.WriteBuffer(char_info_elements);
}
- if (result->size() > 0) {
- ctx.WriteBuffer(SerializeArray(*result));
- }
+ LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
+ output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(result->size()));
+ rb.Push(result);
+ rb.Push(mii_count);
}
void Get1(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
+ const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
- LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
-
- const auto result{manager.GetDefault(source_flag)};
- if (result.Failed()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
- return;
- }
+ u32 mii_count{};
+ std::vector<CharInfo> char_info(output_size);
+ const auto result = manager->Get(metadata, char_info, mii_count, source_flag);
- std::vector<CharInfo> values;
- for (const auto& element : *result) {
- values.emplace_back(element.info);
+ if (mii_count != 0) {
+ ctx.WriteBuffer(char_info);
}
- ctx.WriteBuffer(SerializeArray(values));
+ LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
+ output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(result->size()));
+ rb.Push(result);
+ rb.Push(mii_count);
}
void UpdateLatest(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto info{rp.PopRaw<CharInfo>()};
+ const auto char_info{rp.PopRaw<CharInfo>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
- LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
+ LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
- const auto result{manager.UpdateLatest(info, source_flag)};
- if (result.Failed()) {
+ CharInfo new_char_info{};
+ const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag);
+ if (result.IsFailure()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(*result);
+ rb.PushRaw(new_char_info);
}
void BuildRandom(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
-
const auto age{rp.PopRaw<Age>()};
const auto gender{rp.PopRaw<Gender>()};
const auto race{rp.PopRaw<Race>()};
@@ -172,28 +168,28 @@ private:
if (age > Age::All) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "invalid age={}", age);
+ rb.Push(ResultInvalidArgument);
return;
}
if (gender > Gender::All) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "invalid gender={}", gender);
+ rb.Push(ResultInvalidArgument);
return;
}
if (race > Race::All) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
- LOG_ERROR(Service_Mii, "invalid race={}", race);
+ rb.Push(ResultInvalidArgument);
return;
}
+ CharInfo char_info{};
+ manager->BuildRandom(char_info, age, gender, race);
+
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race));
+ rb.PushRaw(char_info);
}
void BuildDefault(HLERequestContext& ctx) {
@@ -203,16 +199,249 @@ private:
LOG_DEBUG(Service_Mii, "called with index={}", index);
if (index > 5) {
- LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
- index);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERROR_INVALID_ARGUMENT);
+ rb.Push(ResultInvalidArgument);
return;
}
+ CharInfo char_info{};
+ manager->BuildDefault(char_info, index);
+
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(manager.BuildDefault(index));
+ rb.PushRaw(char_info);
+ }
+
+ void Get2(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
+ const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()};
+
+ u32 mii_count{};
+ std::vector<StoreDataElement> store_data_elements(output_size);
+ const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag);
+
+ if (mii_count != 0) {
+ ctx.WriteBuffer(store_data_elements);
+ }
+
+ LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
+ output_size, mii_count);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(mii_count);
+ }
+
+ void Get3(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
+ const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()};
+
+ u32 mii_count{};
+ std::vector<StoreData> store_data(output_size);
+ const auto result = manager->Get(metadata, store_data, mii_count, source_flag);
+
+ if (mii_count != 0) {
+ ctx.WriteBuffer(store_data);
+ }
+
+ LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
+ output_size, mii_count);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(mii_count);
+ }
+
+ void UpdateLatest1(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto store_data{rp.PopRaw<StoreData>()};
+ const auto source_flag{rp.PopRaw<SourceFlag>()};
+
+ LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
+
+ Result result = ResultSuccess;
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ StoreData new_store_data{};
+ if (result.IsSuccess()) {
+ result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag);
+ }
+
+ if (result.IsFailure()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)};
+ rb.Push(ResultSuccess);
+ rb.PushRaw<StoreData>(new_store_data);
+ }
+
+ void FindIndex(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto create_id{rp.PopRaw<Common::UUID>()};
+ const auto is_special{rp.PopRaw<bool>()};
+
+ LOG_INFO(Service_Mii, "called with create_id={}, is_special={}",
+ create_id.FormattedString(), is_special);
+
+ const s32 index = manager->FindIndex(create_id, is_special);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(index);
+ }
+
+ void Move(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto create_id{rp.PopRaw<Common::UUID>()};
+ const auto new_index{rp.PopRaw<s32>()};
+
+ LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(),
+ new_index);
+
+ Result result = ResultSuccess;
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ if (result.IsSuccess()) {
+ const u32 count = manager->GetCount(metadata, SourceFlag::Database);
+ if (new_index < 0 || new_index >= static_cast<s32>(count)) {
+ result = ResultInvalidArgument;
+ }
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->Move(metadata, new_index, create_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void AddOrReplace(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto store_data{rp.PopRaw<StoreData>()};
+
+ LOG_INFO(Service_Mii, "called");
+
+ Result result = ResultSuccess;
+
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->AddOrReplace(metadata, store_data);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void Delete(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto create_id{rp.PopRaw<Common::UUID>()};
+
+ LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString());
+
+ Result result = ResultSuccess;
+
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->Delete(metadata, create_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void DestroyFile(HLERequestContext& ctx) {
+ // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
+ const bool is_db_test_mode_enabled = false;
+
+ LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
+
+ Result result = ResultSuccess;
+
+ if (!is_db_test_mode_enabled) {
+ result = ResultTestModeOnly;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->DestroyFile(metadata);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void DeleteFile(HLERequestContext& ctx) {
+ // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
+ const bool is_db_test_mode_enabled = false;
+
+ LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
+
+ Result result = ResultSuccess;
+
+ if (!is_db_test_mode_enabled) {
+ result = ResultTestModeOnly;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->DeleteFile();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void Format(HLERequestContext& ctx) {
+ // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
+ const bool is_db_test_mode_enabled = false;
+
+ LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
+
+ Result result = ResultSuccess;
+
+ if (!is_db_test_mode_enabled) {
+ result = ResultTestModeOnly;
+ }
+
+ if (result.IsSuccess()) {
+ result = manager->Format(metadata);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Mii, "called");
+
+ bool is_broken_with_clear_flag = false;
+ Result result = ResultSuccess;
+
+ if (!is_system) {
+ result = ResultPermissionDenied;
+ }
+
+ if (result.IsSuccess()) {
+ is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push<u8>(is_broken_with_clear_flag);
}
void GetIndex(HLERequestContext& ctx) {
@@ -221,19 +450,21 @@ private:
LOG_DEBUG(Service_Mii, "called");
- u32 index{};
+ s32 index{};
+ const auto result = manager->GetIndex(metadata, info, index);
+
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(manager.GetIndex(info, index));
+ rb.Push(result);
rb.Push(index);
}
void SetInterfaceVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- current_interface_version = rp.PopRaw<u32>();
+ const auto interface_version{rp.PopRaw<u32>()};
- LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version);
+ LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
- UNIMPLEMENTED_IF(current_interface_version != 1);
+ manager->SetInterfaceVersion(metadata, interface_version);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -241,57 +472,101 @@ private:
void Convert(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
-
const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
LOG_INFO(Service_Mii, "called");
+ CharInfo char_info{};
+ const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3);
+
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
- rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3));
+ rb.Push(result);
+ rb.PushRaw<CharInfo>(char_info);
}
- constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
- return current_interface_version >= interface_version;
+ void ConvertCoreDataToCharInfo(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto core_data{rp.PopRaw<CoreData>()};
+
+ LOG_INFO(Service_Mii, "called");
+
+ CharInfo char_info{};
+ const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data);
+
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
+ rb.Push(result);
+ rb.PushRaw<CharInfo>(char_info);
}
- MiiManager manager;
+ void ConvertCharInfoToCoreData(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto char_info{rp.PopRaw<CharInfo>()};
- u32 current_interface_version{};
- u64 current_update_counter{};
-};
+ LOG_INFO(Service_Mii, "called");
-class MiiDBModule final : public ServiceFramework<MiiDBModule> {
-public:
- explicit MiiDBModule(Core::System& system_, const char* name_)
- : ServiceFramework{system_, name_} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
- };
- // clang-format on
+ CoreData core_data{};
+ const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info);
- RegisterHandlers(functions);
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)};
+ rb.Push(result);
+ rb.PushRaw<CoreData>(core_data);
}
-private:
- void GetDatabaseService(HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDatabaseService>(system);
+ void Append(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto char_info{rp.PopRaw<CharInfo>()};
- LOG_DEBUG(Service_Mii, "called");
+ LOG_INFO(Service_Mii, "called");
+
+ const auto result = manager->Append(metadata, char_info);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
+
+ std::shared_ptr<MiiManager> manager = nullptr;
+ DatabaseSessionMetadata metadata{};
+ bool is_system{};
};
+MiiDBModule::MiiDBModule(Core::System& system_, const char* name_,
+ std::shared_ptr<MiiManager> mii_manager, bool is_system_)
+ : ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ if (manager == nullptr) {
+ manager = std::make_shared<MiiManager>();
+ }
+}
+
+MiiDBModule::~MiiDBModule() = default;
+
+void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IDatabaseService>(system, manager, is_system);
+
+ LOG_DEBUG(Service_Mii, "called");
+}
+
+std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() {
+ return manager;
+}
+
class MiiImg final : public ServiceFramework<MiiImg> {
public:
explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Initialize"},
+ {0, &MiiImg::Initialize, "Initialize"},
{10, nullptr, "Reload"},
- {11, nullptr, "GetCount"},
+ {11, &MiiImg::GetCount, "GetCount"},
{12, nullptr, "IsEmpty"},
{13, nullptr, "IsFull"},
{14, nullptr, "GetAttribute"},
@@ -308,13 +583,32 @@ public:
RegisterHandlers(functions);
}
+
+private:
+ void Initialize(HLERequestContext& ctx) {
+ LOG_INFO(Service_Mii, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void GetCount(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Mii, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(0);
+ }
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
+ std::shared_ptr<MiiManager> manager = nullptr;
- server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e"));
- server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u"));
+ server_manager->RegisterNamedService(
+ "mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true));
+ server_manager->RegisterNamedService(
+ "mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false));
server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h
index ed4e3f62b..9aa4426f6 100644
--- a/src/core/hle/service/mii/mii.h
+++ b/src/core/hle/service/mii/mii.h
@@ -3,11 +3,29 @@
#pragma once
+#include "core/hle/service/service.h"
+
namespace Core {
class System;
}
namespace Service::Mii {
+class MiiManager;
+
+class MiiDBModule final : public ServiceFramework<MiiDBModule> {
+public:
+ explicit MiiDBModule(Core::System& system_, const char* name_,
+ std::shared_ptr<MiiManager> mii_manager, bool is_system_);
+ ~MiiDBModule() override;
+
+ std::shared_ptr<MiiManager> GetMiiManager();
+
+private:
+ void GetDatabaseService(HLERequestContext& ctx);
+
+ std::shared_ptr<MiiManager> manager = nullptr;
+ bool is_system{};
+};
void LoopProcess(Core::System& system);
diff --git a/src/core/hle/service/mii/mii_database.cpp b/src/core/hle/service/mii/mii_database.cpp
new file mode 100644
index 000000000..3803e58e2
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database.cpp
@@ -0,0 +1,142 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/mii/mii_database.h"
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/mii_util.h"
+
+namespace Service::Mii {
+
+u8 NintendoFigurineDatabase::GetDatabaseLength() const {
+ return database_length;
+}
+
+bool NintendoFigurineDatabase::IsFull() const {
+ return database_length >= MaxDatabaseLength;
+}
+
+StoreData NintendoFigurineDatabase::Get(std::size_t index) const {
+ StoreData store_data = miis.at(index);
+
+ // This hack is to make external database dumps compatible
+ store_data.SetDeviceChecksum();
+
+ return store_data;
+}
+
+u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const {
+ if (magic == MiiMagic) {
+ return GetDatabaseLength();
+ }
+
+ u32 mii_count{};
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ const auto& store_data = Get(index);
+ if (!store_data.IsSpecial()) {
+ mii_count++;
+ }
+ }
+
+ return mii_count;
+}
+
+bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index,
+ const Common::UUID& create_id) const {
+ for (std::size_t index = 0; index < database_length; ++index) {
+ if (miis[index].GetCreateId() == create_id) {
+ out_index = static_cast<u32>(index);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) {
+ if (current_index == new_index) {
+ return ResultNotUpdated;
+ }
+
+ const StoreData store_data = miis[current_index];
+
+ if (new_index > current_index) {
+ // Shift left
+ const u32 index_diff = new_index - current_index;
+ for (std::size_t i = 0; i < index_diff; i++) {
+ miis[current_index + i] = miis[current_index + i + 1];
+ }
+ } else {
+ // Shift right
+ const u32 index_diff = current_index - new_index;
+ for (std::size_t i = 0; i < index_diff; i++) {
+ miis[current_index - i] = miis[current_index - i - 1];
+ }
+ }
+
+ miis[new_index] = store_data;
+ crc = GenerateDatabaseCrc();
+ return ResultSuccess;
+}
+
+void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) {
+ miis[index] = store_data;
+ crc = GenerateDatabaseCrc();
+}
+
+void NintendoFigurineDatabase::Add(const StoreData& store_data) {
+ miis[database_length] = store_data;
+ database_length++;
+ crc = GenerateDatabaseCrc();
+}
+
+void NintendoFigurineDatabase::Delete(u32 index) {
+ // Shift left
+ const s32 new_database_size = database_length - 1;
+ if (static_cast<s32>(index) < new_database_size) {
+ for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) {
+ miis[i] = miis[i + 1];
+ }
+ }
+
+ database_length = static_cast<u8>(new_database_size);
+ crc = GenerateDatabaseCrc();
+}
+
+void NintendoFigurineDatabase::CleanDatabase() {
+ miis = {};
+ version = 1;
+ magic = DatabaseMagic;
+ database_length = 0;
+ crc = GenerateDatabaseCrc();
+}
+
+void NintendoFigurineDatabase::CorruptCrc() {
+ crc = GenerateDatabaseCrc();
+ crc = ~crc;
+}
+
+Result NintendoFigurineDatabase::CheckIntegrity() {
+ if (magic != DatabaseMagic) {
+ return ResultInvalidDatabaseSignature;
+ }
+
+ if (version != 1) {
+ return ResultInvalidDatabaseVersion;
+ }
+
+ if (crc != GenerateDatabaseCrc()) {
+ return ResultInvalidDatabaseChecksum;
+ }
+
+ if (database_length >= MaxDatabaseLength) {
+ return ResultInvalidDatabaseLength;
+ }
+
+ return ResultSuccess;
+}
+
+u16 NintendoFigurineDatabase::GenerateDatabaseCrc() {
+ return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc));
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h
new file mode 100644
index 000000000..3bd240f93
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database.h
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+#include "core/hle/service/mii/types/store_data.h"
+
+namespace Service::Mii {
+
+constexpr std::size_t MaxDatabaseLength{100};
+constexpr u32 MiiMagic{0xa523b78f};
+constexpr u32 DatabaseMagic{0x4244464e}; // NFDB
+
+class NintendoFigurineDatabase {
+public:
+ /// Returns the total mii count.
+ u8 GetDatabaseLength() const;
+
+ /// Returns true if database is full.
+ bool IsFull() const;
+
+ /// Returns the mii of the specified index.
+ StoreData Get(std::size_t index) const;
+
+ /// Returns the total mii count. Ignoring special mii.
+ u32 GetCount(const DatabaseSessionMetadata& metadata) const;
+
+ /// Returns the index of a mii. If the mii isn't found returns false.
+ bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const;
+
+ /// Moves the location of a specific mii.
+ Result Move(u32 current_index, u32 new_index);
+
+ /// Replaces mii with new data.
+ void Replace(u32 index, const StoreData& store_data);
+
+ /// Adds a new mii to the end of the database.
+ void Add(const StoreData& store_data);
+
+ /// Removes mii from database and shifts left the remainding data.
+ void Delete(u32 index);
+
+ /// Deletes all contents with a fresh database
+ void CleanDatabase();
+
+ /// Intentionally sets a bad checksum
+ void CorruptCrc();
+
+ /// Returns success if database is valid otherwise returns the corresponding error code.
+ Result CheckIntegrity();
+
+private:
+ /// Returns the checksum of the database
+ u16 GenerateDatabaseCrc();
+
+ u32 magic{}; // 'NFDB'
+ std::array<StoreData, MaxDatabaseLength> miis{};
+ u8 version{};
+ u8 database_length{};
+ u16 crc{};
+};
+static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98,
+ "NintendoFigurineDatabase has incorrect size.");
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp
new file mode 100644
index 000000000..c39898594
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.cpp
@@ -0,0 +1,420 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+
+#include "core/hle/service/mii/mii_database_manager.h"
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/store_data.h"
+
+namespace Service::Mii {
+const char* DbFileName = "MiiDatabase.dat";
+
+DatabaseManager::DatabaseManager() {}
+
+Result DatabaseManager::MountSaveData() {
+ if (!is_save_data_mounted) {
+ system_save_dir =
+ Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030";
+ if (is_test_db) {
+ system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
+ "system/save/8000000000000031";
+ }
+
+ // mount point should be "mii:"
+
+ if (!Common::FS::CreateDirs(system_save_dir)) {
+ return ResultUnknown;
+ }
+ }
+
+ is_save_data_mounted = true;
+ return ResultSuccess;
+}
+
+Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) {
+ is_database_broken = false;
+ if (!is_save_data_mounted) {
+ return ResultInvalidArgument;
+ }
+
+ database.CleanDatabase();
+ update_counter++;
+ metadata.update_counter = update_counter;
+
+ const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+
+ if (!db_file.IsOpen()) {
+ return SaveDatabase();
+ }
+
+ if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) {
+ is_database_broken = true;
+ }
+
+ if (db_file.Read(database) != 1) {
+ is_database_broken = true;
+ }
+
+ if (is_database_broken) {
+ // Dragons happen here for simplicity just clean the database
+ LOG_ERROR(Service_Mii, "Mii database is corrupted");
+ database.CleanDatabase();
+ return ResultUnknown;
+ }
+
+ const auto result = database.CheckIntegrity();
+
+ if (result.IsError()) {
+ LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw);
+ database.CleanDatabase();
+ return ResultSuccess;
+ }
+
+ LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}",
+ database.GetDatabaseLength());
+ return ResultSuccess;
+}
+
+bool DatabaseManager::IsFullDatabase() const {
+ return database.GetDatabaseLength() == MaxDatabaseLength;
+}
+
+bool DatabaseManager::IsModified() const {
+ return is_moddified;
+}
+
+u64 DatabaseManager::GetUpdateCounter() const {
+ return update_counter;
+}
+
+u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const {
+ const u32 database_size = database.GetDatabaseLength();
+ if (metadata.magic == MiiMagic) {
+ return database_size;
+ }
+
+ // Special mii can't be used. Skip those.
+
+ u32 mii_count{};
+ for (std::size_t index = 0; index < database_size; ++index) {
+ const auto& store_data = database.Get(index);
+ if (store_data.IsSpecial()) {
+ continue;
+ }
+ mii_count++;
+ }
+
+ return mii_count;
+}
+
+void DatabaseManager::Get(StoreData& out_store_data, std::size_t index,
+ const DatabaseSessionMetadata& metadata) const {
+ if (metadata.magic == MiiMagic) {
+ out_store_data = database.Get(index);
+ return;
+ }
+
+ // The index refeers to the mii index without special mii.
+ // Search on the database until we find it
+
+ u32 virtual_index = 0;
+ const u32 database_size = database.GetDatabaseLength();
+ for (std::size_t i = 0; i < database_size; ++i) {
+ const auto& store_data = database.Get(i);
+ if (store_data.IsSpecial()) {
+ continue;
+ }
+ if (virtual_index == index) {
+ out_store_data = store_data;
+ return;
+ }
+ virtual_index++;
+ }
+
+ // This function doesn't fail. It returns the first mii instead
+ out_store_data = database.Get(0);
+}
+
+Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id,
+ bool is_special) const {
+ u32 index{};
+ const bool is_found = database.GetIndexByCreatorId(index, create_id);
+
+ if (!is_found) {
+ return ResultNotFound;
+ }
+
+ if (is_special) {
+ out_index = index;
+ return ResultSuccess;
+ }
+
+ if (database.Get(index).IsSpecial()) {
+ return ResultNotFound;
+ }
+
+ out_index = 0;
+
+ if (index < 1) {
+ return ResultSuccess;
+ }
+
+ for (std::size_t i = 0; i <= index; ++i) {
+ if (database.Get(i).IsSpecial()) {
+ continue;
+ }
+ out_index++;
+ }
+ return ResultSuccess;
+}
+
+Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
+ const Common::UUID& create_id) const {
+ u32 index{};
+ const bool is_found = database.GetIndexByCreatorId(index, create_id);
+
+ if (!is_found) {
+ return ResultNotFound;
+ }
+
+ if (metadata.magic == MiiMagic) {
+ out_index = index;
+ return ResultSuccess;
+ }
+
+ if (database.Get(index).IsSpecial()) {
+ return ResultNotFound;
+ }
+
+ out_index = 0;
+
+ if (index < 1) {
+ return ResultSuccess;
+ }
+
+ // The index refeers to the mii index without special mii.
+ // Search on the database until we find it
+
+ for (std::size_t i = 0; i <= index; ++i) {
+ const auto& store_data = database.Get(i);
+ if (store_data.IsSpecial()) {
+ continue;
+ }
+ out_index++;
+ }
+ return ResultSuccess;
+}
+
+Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index,
+ const Common::UUID& create_id) const {
+ const auto database_size = database.GetDatabaseLength();
+
+ if (database_size >= 1) {
+ u32 virtual_index{};
+ for (std::size_t i = 0; i < database_size; ++i) {
+ const StoreData& store_data = database.Get(i);
+ if (store_data.IsSpecial()) {
+ continue;
+ }
+ if (virtual_index == new_index) {
+ const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
+ if (!is_found) {
+ return ResultNotFound;
+ }
+ if (store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+ return ResultSuccess;
+ }
+ virtual_index++;
+ }
+ }
+
+ const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
+ if (!is_found) {
+ return ResultNotFound;
+ }
+ const StoreData& store_data = database.Get(out_index);
+ if (store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+ return ResultSuccess;
+}
+
+Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index,
+ const Common::UUID& create_id) {
+ u32 current_index{};
+ if (metadata.magic == MiiMagic) {
+ const bool is_found = database.GetIndexByCreatorId(current_index, create_id);
+ if (!is_found) {
+ return ResultNotFound;
+ }
+ } else {
+ const auto result = FindMoveIndex(current_index, new_index, create_id);
+ if (result.IsError()) {
+ return result;
+ }
+ }
+
+ const auto result = database.Move(current_index, new_index);
+ if (result.IsFailure()) {
+ return result;
+ }
+
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+ return ResultSuccess;
+}
+
+Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata,
+ const StoreData& store_data) {
+ if (store_data.IsValid() != ValidationResult::NoErrors) {
+ return ResultInvalidStoreData;
+ }
+ if (metadata.magic != MiiMagic && store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+
+ u32 index{};
+ const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId());
+ if (is_found) {
+ const StoreData& old_store_data = database.Get(index);
+
+ if (store_data.IsSpecial() != old_store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+
+ database.Replace(index, store_data);
+ } else {
+ if (database.IsFull()) {
+ return ResultDatabaseFull;
+ }
+
+ database.Add(store_data);
+ }
+
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+ return ResultSuccess;
+}
+
+Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
+ u32 index{};
+ const bool is_found = database.GetIndexByCreatorId(index, create_id);
+ if (!is_found) {
+ return ResultNotFound;
+ }
+
+ if (metadata.magic != MiiMagic) {
+ const auto& store_data = database.Get(index);
+ if (store_data.IsSpecial()) {
+ return ResultInvalidOperation;
+ }
+ }
+
+ database.Delete(index);
+
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+ return ResultSuccess;
+}
+
+Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
+ if (char_info.Verify() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo2;
+ }
+ if (char_info.GetType() == 1) {
+ return ResultInvalidCharInfoType;
+ }
+
+ u32 index{};
+ StoreData store_data{};
+
+ // Loop until the mii we created is not on the database
+ do {
+ store_data.BuildWithCharInfo(char_info);
+ } while (database.GetIndexByCreatorId(index, store_data.GetCreateId()));
+
+ const Result result = store_data.Restore();
+
+ if (result.IsSuccess() || result == ResultNotUpdated) {
+ return AddOrReplace(metadata, store_data);
+ }
+
+ return result;
+}
+
+Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) {
+ database.CorruptCrc();
+
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+
+ const auto result = SaveDatabase();
+ database.CleanDatabase();
+
+ return result;
+}
+
+Result DatabaseManager::DeleteFile() {
+ const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName);
+ // TODO: Return proper FS error here
+ return result ? ResultSuccess : ResultUnknown;
+}
+
+void DatabaseManager::Format(DatabaseSessionMetadata& metadata) {
+ database.CleanDatabase();
+ is_moddified = true;
+ update_counter++;
+ metadata.update_counter = update_counter;
+}
+
+Result DatabaseManager::SaveDatabase() {
+ // TODO: Replace unknown error codes with proper FS error codes when available
+
+ if (!Common::FS::Exists(system_save_dir / DbFileName)) {
+ if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
+ LOG_ERROR(Service_Mii, "Failed to create mii database");
+ return ResultUnknown;
+ }
+ }
+
+ const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName);
+ if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) {
+ if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) {
+ LOG_ERROR(Service_Mii, "Failed to delete mii database");
+ return ResultUnknown;
+ }
+ if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
+ LOG_ERROR(Service_Mii, "Failed to create mii database");
+ return ResultUnknown;
+ }
+ }
+
+ const Common::FS::IOFile db_file{system_save_dir / DbFileName,
+ Common::FS::FileAccessMode::ReadWrite,
+ Common::FS::FileType::BinaryFile};
+
+ if (db_file.Write(database) != 1) {
+ LOG_ERROR(Service_Mii, "Failed to save mii database");
+ return ResultUnknown;
+ }
+
+ is_moddified = false;
+ return ResultSuccess;
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h
new file mode 100644
index 000000000..52c32be82
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.h
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/fs/fs.h"
+#include "core/hle/result.h"
+#include "core/hle/service/mii/mii_database.h"
+
+namespace Service::Mii {
+class CharInfo;
+class StoreData;
+
+class DatabaseManager {
+public:
+ DatabaseManager();
+ Result MountSaveData();
+ Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken);
+
+ bool IsFullDatabase() const;
+ bool IsModified() const;
+ u64 GetUpdateCounter() const;
+
+ void Get(StoreData& out_store_data, std::size_t index,
+ const DatabaseSessionMetadata& metadata) const;
+ u32 GetCount(const DatabaseSessionMetadata& metadata) const;
+
+ Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const;
+ Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
+ const Common::UUID& create_id) const;
+ Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const;
+
+ Result Move(DatabaseSessionMetadata& metadata, u32 current_index,
+ const Common::UUID& create_id);
+ Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data);
+ Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
+ Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
+
+ Result DestroyFile(DatabaseSessionMetadata& metadata);
+ Result DeleteFile();
+ void Format(DatabaseSessionMetadata& metadata);
+
+ Result SaveDatabase();
+
+private:
+ // This is the global value of
+ // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
+ bool is_test_db{};
+
+ bool is_moddified{};
+ bool is_save_data_mounted{};
+ u64 update_counter{};
+ NintendoFigurineDatabase database{};
+
+ std::filesystem::path system_save_dir{};
+};
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index c920650f5..dcfd6b2e2 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -1,698 +1,486 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <cstring>
-#include <random>
-
-#include "common/assert.h"
#include "common/logging/log.h"
-#include "common/string_util.h"
-
-#include "core/hle/service/acc/profile_manager.h"
+#include "core/hle/service/mii/mii_database_manager.h"
#include "core/hle/service/mii/mii_manager.h"
-#include "core/hle/service/mii/raw_data.h"
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/core_data.h"
+#include "core/hle/service/mii/types/raw_data.h"
+#include "core/hle/service/mii/types/store_data.h"
+#include "core/hle/service/mii/types/ver3_store_data.h"
namespace Service::Mii {
+constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
-namespace {
+MiiManager::MiiManager() {}
-constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
+Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) {
+ database_manager.MountSaveData();
+ database_manager.Initialize(metadata, is_broken_with_clear_flag);
+ return ResultSuccess;
+}
-constexpr std::size_t BaseMiiCount{2};
-constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
+void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
+ StoreData store_data{};
+ store_data.BuildDefault(index);
+ out_char_info.SetFromStoreData(store_data);
+}
-constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'};
-constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7};
-constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13};
-constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23};
-constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0};
-constexpr std::array<u8, 62> EyeRotateLookup{
- {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
- 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
- 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
- 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}};
-constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07,
- 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06,
- 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}};
-
-template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
-std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
- std::array<T, DestArraySize> out{};
- std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
- return out;
+void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const {
+ StoreData store_data{};
+ store_data.BuildBase(gender);
+ out_char_info.SetFromStoreData(store_data);
}
-CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
- MiiStoreBitFields bf;
- std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
-
- return {
- .uuid = data.data.uuid,
- .name = ResizeArray<char16_t, 10, 11>(data.data.name),
- .font_region = static_cast<u8>(bf.font_region.Value()),
- .favorite_color = static_cast<u8>(bf.favorite_color.Value()),
- .gender = static_cast<u8>(bf.gender.Value()),
- .height = static_cast<u8>(bf.height.Value()),
- .build = static_cast<u8>(bf.build.Value()),
- .type = static_cast<u8>(bf.type.Value()),
- .region_move = static_cast<u8>(bf.region_move.Value()),
- .faceline_type = static_cast<u8>(bf.faceline_type.Value()),
- .faceline_color = static_cast<u8>(bf.faceline_color.Value()),
- .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()),
- .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()),
- .hair_type = static_cast<u8>(bf.hair_type.Value()),
- .hair_color = static_cast<u8>(bf.hair_color.Value()),
- .hair_flip = static_cast<u8>(bf.hair_flip.Value()),
- .eye_type = static_cast<u8>(bf.eye_type.Value()),
- .eye_color = static_cast<u8>(bf.eye_color.Value()),
- .eye_scale = static_cast<u8>(bf.eye_scale.Value()),
- .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()),
- .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()),
- .eye_x = static_cast<u8>(bf.eye_x.Value()),
- .eye_y = static_cast<u8>(bf.eye_y.Value()),
- .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()),
- .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()),
- .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()),
- .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()),
- .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()),
- .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()),
- .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3),
- .nose_type = static_cast<u8>(bf.nose_type.Value()),
- .nose_scale = static_cast<u8>(bf.nose_scale.Value()),
- .nose_y = static_cast<u8>(bf.nose_y.Value()),
- .mouth_type = static_cast<u8>(bf.mouth_type.Value()),
- .mouth_color = static_cast<u8>(bf.mouth_color.Value()),
- .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()),
- .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()),
- .mouth_y = static_cast<u8>(bf.mouth_y.Value()),
- .beard_color = static_cast<u8>(bf.beard_color.Value()),
- .beard_type = static_cast<u8>(bf.beard_type.Value()),
- .mustache_type = static_cast<u8>(bf.mustache_type.Value()),
- .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()),
- .mustache_y = static_cast<u8>(bf.mustache_y.Value()),
- .glasses_type = static_cast<u8>(bf.glasses_type.Value()),
- .glasses_color = static_cast<u8>(bf.glasses_color.Value()),
- .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()),
- .glasses_y = static_cast<u8>(bf.glasses_y.Value()),
- .mole_type = static_cast<u8>(bf.mole_type.Value()),
- .mole_scale = static_cast<u8>(bf.mole_scale.Value()),
- .mole_x = static_cast<u8>(bf.mole_x.Value()),
- .mole_y = static_cast<u8>(bf.mole_y.Value()),
- .padding = 0,
- };
+void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const {
+ StoreData store_data{};
+ store_data.BuildRandom(age, gender, race);
+ out_char_info.SetFromStoreData(store_data);
}
-u16 GenerateCrc16(const void* data, std::size_t size) {
- s32 crc{};
- for (std::size_t i = 0; i < size; i++) {
- crc ^= static_cast<const u8*>(data)[i] << 8;
- for (std::size_t j = 0; j < 8; j++) {
- crc <<= 1;
- if ((crc & 0x10000) != 0) {
- crc = (crc ^ 0x1021) & 0xFFFF;
- }
- }
- }
- return Common::swap16(static_cast<u16>(crc));
+bool MiiManager::IsFullDatabase() const {
+ return database_manager.IsFullDatabase();
}
-template <typename T>
-T GetRandomValue(T min, T max) {
- std::random_device device;
- std::mt19937 gen(device());
- std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max));
- return static_cast<T>(distribution(gen));
+void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const {
+ metadata.interface_version = version;
}
-template <typename T>
-T GetRandomValue(T max) {
- return GetRandomValue<T>({}, max);
+bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return false;
+ }
+
+ const u64 metadata_update_counter = metadata.update_counter;
+ const u64 database_update_counter = database_manager.GetUpdateCounter();
+ metadata.update_counter = database_update_counter;
+ return metadata_update_counter != database_update_counter;
}
-MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) {
- MiiStoreBitFields bf{};
-
- if (gender == Gender::All) {
- gender = GetRandomValue<Gender>(Gender::Maximum);
- }
-
- bf.gender.Assign(gender);
- bf.favorite_color.Assign(GetRandomValue<u8>(11));
- bf.region_move.Assign(0);
- bf.font_region.Assign(FontRegion::Standard);
- bf.type.Assign(0);
- bf.height.Assign(64);
- bf.build.Assign(64);
-
- if (age == Age::All) {
- const auto temp{GetRandomValue<int>(10)};
- if (temp >= 8) {
- age = Age::Old;
- } else if (temp >= 4) {
- age = Age::Normal;
- } else {
- age = Age::Young;
- }
+u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
+ u32 mii_count{};
+ if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
+ mii_count += DefaultMiiCount;
+ }
+ if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
+ mii_count += database_manager.GetCount(metadata);
}
+ return mii_count;
+}
- if (race == Race::All) {
- const auto temp{GetRandomValue<int>(10)};
- if (temp >= 8) {
- race = Race::Black;
- } else if (temp >= 4) {
- race = Race::White;
- } else {
- race = Race::Asian;
- }
+Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index,
+ const Common::UUID& create_id) {
+ const auto result = database_manager.Move(metadata, index, create_id);
+
+ if (result.IsFailure()) {
+ return result;
}
- u32 axis_y{};
- if (gender == Gender::Female && age == Age::Young) {
- axis_y = GetRandomValue<u32>(3);
- }
-
- const std::size_t index{3 * static_cast<std::size_t>(age) +
- 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
-
- const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)};
- const auto faceline_color_info{RawData::RandomMiiFacelineColor.at(
- 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
- const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
- const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
- const auto hair_type_info{RawData::RandomMiiHairType.at(index)};
- const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
- static_cast<std::size_t>(age))};
- const auto eye_type_info{RawData::RandomMiiEyeType.at(index)};
- const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
- const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
- const auto nose_type_info{RawData::RandomMiiNoseType.at(index)};
- const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)};
- const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
-
- bf.faceline_type.Assign(
- faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
- bf.faceline_color.Assign(
- faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
- bf.faceline_wrinkle.Assign(
- faceline_wrinkle_info
- .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
- bf.faceline_makeup.Assign(
- faceline_makeup_info
- .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
-
- bf.hair_type.Assign(
- hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]);
- bf.hair_color.Assign(
- HairColorLookup[hair_color_info
- .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]);
- bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum));
-
- bf.eye_type.Assign(
- eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]);
-
- const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
- const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
- const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
- const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]};
-
- bf.eye_color.Assign(
- EyeColorLookup[eye_color_info
- .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]);
- bf.eye_scale.Assign(4);
- bf.eye_aspect.Assign(3);
- bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate);
- bf.eye_x.Assign(2);
- bf.eye_y.Assign(axis_y + 12);
-
- bf.eyebrow_type.Assign(
- eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
-
- const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
- const auto eyebrow_y{race == Race::Asian ? 9 : 10};
- const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6};
- const auto eyebrow_rotate{
- 32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]};
-
- bf.eyebrow_color.Assign(bf.hair_color);
- bf.eyebrow_scale.Assign(4);
- bf.eyebrow_aspect.Assign(3);
- bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate);
- bf.eyebrow_x.Assign(2);
- bf.eyebrow_y.Assign(axis_y + eyebrow_y);
-
- const auto nose_scale{gender == Gender::Female ? 3 : 4};
-
- bf.nose_type.Assign(
- nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]);
- bf.nose_scale.Assign(nose_scale);
- bf.nose_y.Assign(axis_y + 9);
-
- const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0};
-
- bf.mouth_type.Assign(
- mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
- bf.mouth_color.Assign(MouthColorLookup[mouth_color]);
- bf.mouth_scale.Assign(4);
- bf.mouth_aspect.Assign(3);
- bf.mouth_y.Assign(axis_y + 13);
-
- bf.beard_color.Assign(bf.hair_color);
- bf.mustache_scale.Assign(4);
-
- if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) {
- const auto mustache_and_beard_flag{
- GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)};
-
- auto beard_type{BeardType::None};
- auto mustache_type{MustacheType::None};
-
- if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
- BeardAndMustacheFlag::Beard) {
- beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5);
- }
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
+ }
- if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
- BeardAndMustacheFlag::Mustache) {
- mustache_type =
- GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5);
- }
+ return database_manager.SaveDatabase();
+}
- bf.mustache_type.Assign(mustache_type);
- bf.beard_type.Assign(beard_type);
- bf.mustache_y.Assign(10);
- } else {
- bf.mustache_type.Assign(MustacheType::None);
- bf.beard_type.Assign(BeardType::None);
- bf.mustache_y.Assign(axis_y + 10);
- }
-
- const auto glasses_type_start{GetRandomValue<std::size_t>(100)};
- u8 glasses_type{};
- while (glasses_type_start < glasses_type_info.values[glasses_type]) {
- if (++glasses_type >= glasses_type_info.values_count) {
- ASSERT(false);
- break;
- }
+Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) {
+ const auto result = database_manager.AddOrReplace(metadata, store_data);
+
+ if (result.IsFailure()) {
+ return result;
+ }
+
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
}
- bf.glasses_type.Assign(glasses_type);
- bf.glasses_color.Assign(GlassesColorLookup[0]);
- bf.glasses_scale.Assign(4);
- bf.glasses_y.Assign(axis_y + 10);
+ return database_manager.SaveDatabase();
+}
- bf.mole_type.Assign(0);
- bf.mole_scale.Assign(4);
- bf.mole_x.Assign(2);
- bf.mole_y.Assign(20);
+Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
+ const auto result = database_manager.Delete(metadata, create_id);
- return {DefaultMiiName, bf, user_id};
+ if (result.IsFailure()) {
+ return result;
+ }
+
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
+ }
+
+ return database_manager.SaveDatabase();
}
-MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) {
- MiiStoreBitFields bf{};
-
- bf.font_region.Assign(info.font_region);
- bf.favorite_color.Assign(info.favorite_color);
- bf.gender.Assign(info.gender);
- bf.height.Assign(info.height);
- bf.build.Assign(info.weight);
- bf.type.Assign(info.type);
- bf.region_move.Assign(info.region);
- bf.faceline_type.Assign(info.face_type);
- bf.faceline_color.Assign(info.face_color);
- bf.faceline_wrinkle.Assign(info.face_wrinkle);
- bf.faceline_makeup.Assign(info.face_makeup);
- bf.hair_type.Assign(info.hair_type);
- bf.hair_color.Assign(HairColorLookup[info.hair_color]);
- bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip));
- bf.eye_type.Assign(info.eye_type);
- bf.eye_color.Assign(EyeColorLookup[info.eye_color]);
- bf.eye_scale.Assign(info.eye_scale);
- bf.eye_aspect.Assign(info.eye_aspect);
- bf.eye_rotate.Assign(info.eye_rotate);
- bf.eye_x.Assign(info.eye_x);
- bf.eye_y.Assign(info.eye_y);
- bf.eyebrow_type.Assign(info.eyebrow_type);
- bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]);
- bf.eyebrow_scale.Assign(info.eyebrow_scale);
- bf.eyebrow_aspect.Assign(info.eyebrow_aspect);
- bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
- bf.eyebrow_x.Assign(info.eyebrow_x);
- bf.eyebrow_y.Assign(info.eyebrow_y - 3);
- bf.nose_type.Assign(info.nose_type);
- bf.nose_scale.Assign(info.nose_scale);
- bf.nose_y.Assign(info.nose_y);
- bf.mouth_type.Assign(info.mouth_type);
- bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]);
- bf.mouth_scale.Assign(info.mouth_scale);
- bf.mouth_aspect.Assign(info.mouth_aspect);
- bf.mouth_y.Assign(info.mouth_y);
- bf.beard_color.Assign(HairColorLookup[info.beard_color]);
- bf.beard_type.Assign(static_cast<BeardType>(info.beard_type));
- bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type));
- bf.mustache_scale.Assign(info.mustache_scale);
- bf.mustache_y.Assign(info.mustache_y);
- bf.glasses_type.Assign(info.glasses_type);
- bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]);
- bf.glasses_scale.Assign(info.glasses_scale);
- bf.glasses_y.Assign(info.glasses_y);
- bf.mole_type.Assign(info.mole_type);
- bf.mole_scale.Assign(info.mole_scale);
- bf.mole_x.Assign(info.mole_x);
- bf.mole_y.Assign(info.mole_y);
-
- return {DefaultMiiName, bf, user_id};
+s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const {
+ s32 index{};
+ const auto result = database_manager.FindIndex(index, create_id, is_special);
+ if (result.IsError()) {
+ index = -1;
+ }
+ return index;
}
-} // namespace
+Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
+ s32& out_index) const {
+ if (char_info.Verify() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
+ }
+
+ s32 index{};
+ const bool is_special = metadata.magic == MiiMagic;
+ const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special);
-MiiStoreData::MiiStoreData() = default;
+ if (result.IsError()) {
+ index = -1;
+ }
-MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields,
- const Common::UUID& user_id) {
- data.name = name;
- data.uuid = Common::UUID::MakeRandomRFC4122V4();
+ if (index == -1) {
+ return ResultNotFound;
+ }
- std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields));
- data_crc = GenerateCrc16(data.data.data(), sizeof(data));
- device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID));
+ out_index = index;
+ return ResultSuccess;
}
-MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {}
+Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
+ const auto result = database_manager.Append(metadata, char_info);
-bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) {
- if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
- return false;
+ if (result.IsError()) {
+ return ResultNotFound;
}
- const bool result{current_update_counter != update_counter};
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
+ }
- current_update_counter = update_counter;
+ return database_manager.SaveDatabase();
+}
- return result;
+bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) {
+ const bool is_broken = is_broken_with_clear_flag;
+ if (is_broken_with_clear_flag) {
+ is_broken_with_clear_flag = false;
+ database_manager.Format(metadata);
+ database_manager.SaveDatabase();
+ }
+ return is_broken;
}
-bool MiiManager::IsFullDatabase() const {
- // TODO(bunnei): We don't implement the Mii database, so it cannot be full
- return false;
+Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) {
+ is_broken_with_clear_flag = true;
+ return database_manager.DestroyFile(metadata);
}
-u32 MiiManager::GetCount(SourceFlag source_flag) const {
- std::size_t count{};
- if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
- // TODO(bunnei): We don't implement the Mii database, but when we do, update this
- count += 0;
- }
- if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
- count += (DefaultMiiCount - BaseMiiCount);
+Result MiiManager::DeleteFile() {
+ return database_manager.DeleteFile();
+}
+
+Result MiiManager::Format(DatabaseSessionMetadata& metadata) {
+ database_manager.Format(metadata);
+
+ if (!database_manager.IsModified()) {
+ return ResultNotUpdated;
}
- return static_cast<u32>(count);
+ return database_manager.SaveDatabase();
}
-ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info,
- SourceFlag source_flag) {
- if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
- return ERROR_CANNOT_FIND_ENTRY;
+Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
+ if (!mii_v3.IsValid()) {
+ return ResultInvalidCharInfo;
}
- // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
- return ERROR_CANNOT_FIND_ENTRY;
-}
+ StoreData store_data{};
+ mii_v3.BuildToStoreData(store_data);
+ const auto name = store_data.GetNickname();
+ if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
+ store_data.SetInvalidName();
+ }
-CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
- return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id));
+ out_char_info.SetFromStoreData(store_data);
+ return ResultSuccess;
}
-CharInfo MiiManager::BuildDefault(std::size_t index) {
- return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
+Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info,
+ const CoreData& core_data) const {
+ if (core_data.IsValid() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
+ }
+
+ StoreData store_data{};
+ store_data.BuildWithCoreData(core_data);
+ const auto name = store_data.GetNickname();
+ if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
+ store_data.SetInvalidName();
+ }
+
+ out_char_info.SetFromStoreData(store_data);
+ return ResultSuccess;
}
-CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
- Service::Mii::MiiManager manager;
- auto mii = manager.BuildDefault(0);
+Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data,
+ const CharInfo& char_info) const {
+ if (char_info.Verify() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
+ }
- if (!ValidateV3Info(mii_v3)) {
- return mii;
+ out_core_data.BuildFromCharInfo(char_info);
+ const auto name = out_core_data.GetNickname();
+ if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) {
+ out_core_data.SetNickname(out_core_data.GetInvalidNickname());
}
- // TODO: We are ignoring a bunch of data from the mii_v3
+ return ResultSuccess;
+}
- mii.gender = static_cast<u8>(mii_v3.mii_information.gender);
- mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color);
- mii.height = mii_v3.height;
- mii.build = mii_v3.build;
+Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
+ const CharInfo& char_info, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return ResultNotFound;
+ }
- // Copy name until string terminator
- mii.name = {};
- for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
- mii.name[index] = mii_v3.mii_name[index];
- if (mii.name[index] == 0) {
- break;
+ if (metadata.IsInterfaceVersionSupported(1)) {
+ if (char_info.Verify() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
}
}
- mii.font_region = mii_v3.region_information.character_set;
+ u32 index{};
+ Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId());
- mii.faceline_type = mii_v3.appearance_bits1.face_shape;
- mii.faceline_color = mii_v3.appearance_bits1.skin_color;
- mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
- mii.faceline_make = mii_v3.appearance_bits2.makeup;
+ if (result.IsError()) {
+ return result;
+ }
+
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
+
+ if (store_data.GetType() != char_info.GetType()) {
+ return ResultNotFound;
+ }
- mii.hair_type = mii_v3.hair_style;
- mii.hair_color = mii_v3.appearance_bits3.hair_color;
- mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
+ out_char_info.SetFromStoreData(store_data);
- mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
- mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
- mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
- mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
- mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
- mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
- mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
+ if (char_info == out_char_info) {
+ return ResultNotUpdated;
+ }
- mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
- mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
- mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
- mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
- mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
- mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
- mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
+ return ResultSuccess;
+}
- mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
- mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
- mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
+Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
+ const StoreData& store_data, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return ResultNotFound;
+ }
- mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
- mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
- mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
- mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
- mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
+ if (metadata.IsInterfaceVersionSupported(1)) {
+ if (store_data.IsValid() != ValidationResult::NoErrors) {
+ return ResultInvalidCharInfo;
+ }
+ }
- mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
- mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
- mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
+ u32 index{};
+ Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId());
- mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
- mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
+ if (result.IsError()) {
+ return result;
+ }
- mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
- mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
- mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
- mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
+ database_manager.Get(out_store_data, index, metadata);
- mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
- mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
- mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
- mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
+ if (out_store_data.GetType() != store_data.GetType()) {
+ return ResultNotFound;
+ }
- // TODO: Validate mii data
+ if (store_data == out_store_data) {
+ return ResultNotUpdated;
+ }
- return mii;
+ return ResultSuccess;
}
-Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const {
- Service::Mii::MiiManager manager;
- Ver3StoreData mii_v3{};
-
- // TODO: We are ignoring a bunch of data from the mii_v3
+Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
+ std::span<CharInfoElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return BuildDefault(out_elements, out_count, source_flag);
+ }
- mii_v3.version = 1;
- mii_v3.mii_information.gender.Assign(mii.gender);
- mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
- mii_v3.height = mii.height;
- mii_v3.build = mii.build;
+ const auto mii_count = database_manager.GetCount(metadata);
- // Copy name until string terminator
- mii_v3.mii_name = {};
- for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
- mii_v3.mii_name[index] = mii.name[index];
- if (mii_v3.mii_name[index] == 0) {
- break;
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
}
- }
- mii_v3.region_information.character_set.Assign(mii.font_region);
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
- mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
- mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
- mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
+ out_elements[out_count].source = Source::Database;
+ out_elements[out_count].char_info.SetFromStoreData(store_data);
+ out_count++;
+ }
- mii_v3.hair_style = mii.hair_type;
- mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
+ // Include default Mii at the end of the list
+ return BuildDefault(out_elements, out_count, source_flag);
+}
- mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
- mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
- mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
- mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
- mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
- mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
+Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
+ u32& out_count, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return BuildDefault(out_char_info, out_count, source_flag);
+ }
- mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
- mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
- mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
- mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
- mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
- mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
+ const auto mii_count = database_manager.GetCount(metadata);
- mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
- mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
- mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
- mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
- mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
- mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
- mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
- mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
- mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
- mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
+ out_char_info[out_count].SetFromStoreData(store_data);
+ out_count++;
+ }
- mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
+ // Include default Mii at the end of the list
+ return BuildDefault(out_char_info, out_count, source_flag);
+}
- mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
- mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
+Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
+ std::span<StoreDataElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return BuildDefault(out_elements, out_count, source_flag);
+ }
- mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
- mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
- mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
- mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
+ const auto mii_count = database_manager.GetCount(metadata);
- // These types are converted to V3 from a table
- mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]);
- mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]);
- mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]);
- mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]);
- mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]);
- mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]);
- mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]);
- mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]);
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
- mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16));
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
- // TODO: Validate mii_v3 data
+ out_elements[out_count].store_data = store_data;
+ out_elements[out_count].source = Source::Database;
+ out_count++;
+ }
- return mii_v3;
+ // Include default Mii at the end of the list
+ return BuildDefault(out_elements, out_count, source_flag);
}
-NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const {
- return {
- .faceline_color = static_cast<u8>(mii.faceline_color & 0xf),
- .hair_color = static_cast<u8>(mii.hair_color & 0x7f),
- .eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
- .eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
- .mouth_color = static_cast<u8>(mii.mouth_color & 0x7f),
- .beard_color = static_cast<u8>(mii.beard_color & 0x7f),
- .glass_color = static_cast<u8>(mii.glasses_color & 0x7f),
- .glass_type = static_cast<u8>(mii.glasses_type & 0x1f),
- };
+Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
+ u32& out_count, SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
+ return BuildDefault(out_store_data, out_count, source_flag);
+ }
+
+ const auto mii_count = database_manager.GetCount(metadata);
+
+ for (std::size_t index = 0; index < mii_count; ++index) {
+ if (out_store_data.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
+
+ StoreData store_data{};
+ database_manager.Get(store_data, index, metadata);
+
+ out_store_data[out_count] = store_data;
+ out_count++;
+ }
+
+ // Include default Mii at the end of the list
+ return BuildDefault(out_store_data, out_count, source_flag);
}
+Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
+ return ResultSuccess;
+ }
-bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
- bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
-
- is_valid = is_valid && (mii_v3.mii_name[0] != 0);
-
- is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
- is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
- is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
- is_valid = is_valid && (mii_v3.height < 128);
- is_valid = is_valid && (mii_v3.build < 128);
-
- is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
- is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
- is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
- is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
-
- is_valid = is_valid && (mii_v3.hair_style < 132);
- is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
-
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
- is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
-
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
- is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
-
- is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
- is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
- is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
-
- is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
- is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
- is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
- is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
- is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
-
- is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
- is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
- is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
-
- is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
- is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
-
- is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
- is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
- is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
- is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
-
- is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
- is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
- is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
- is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
-
- return is_valid;
+ StoreData store_data{};
+
+ for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
+ if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
+
+ store_data.BuildDefault(static_cast<u32>(index));
+
+ out_elements[out_count].source = Source::Default;
+ out_elements[out_count].char_info.SetFromStoreData(store_data);
+ out_count++;
+ }
+
+ return ResultSuccess;
}
-ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
- std::vector<MiiInfoElement> result;
+Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
+ return ResultSuccess;
+ }
+
+ StoreData store_data{};
+
+ for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
+ if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
+
+ store_data.BuildDefault(static_cast<u32>(index));
+
+ out_char_info[out_count].SetFromStoreData(store_data);
+ out_count++;
+ }
+
+ return ResultSuccess;
+}
+Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
- return result;
+ return ResultSuccess;
}
- for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) {
- result.emplace_back(BuildDefault(index), Source::Default);
+ for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
+ if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
+
+ out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index));
+ out_elements[out_count].source = Source::Default;
+ out_count++;
}
- return result;
+ return ResultSuccess;
}
-Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) {
- constexpr u32 INVALID_INDEX{0xFFFFFFFF};
+Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
+ SourceFlag source_flag) const {
+ if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
+ return ResultSuccess;
+ }
+
+ for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
+ if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
+ return ResultInvalidArgumentSize;
+ }
- index = INVALID_INDEX;
+ out_char_info[out_count].BuildDefault(static_cast<u32>(index));
+ out_count++;
+ }
- // TODO(bunnei): We don't implement the Mii database, so we can't have an index
- return ERROR_CANNOT_FIND_ENTRY;
+ return ResultSuccess;
}
} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index 5525fcd1c..48d8e8bb7 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -3,39 +3,85 @@
#pragma once
-#include <vector>
+#include <span>
#include "core/hle/result.h"
-#include "core/hle/service/mii/types.h"
+#include "core/hle/service/mii/mii_database_manager.h"
+#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii {
+class CharInfo;
+class CoreData;
+class StoreData;
+class Ver3StoreData;
-// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
-// with providing an easy interface for HLE emulation of the mii service.
+struct CharInfoElement;
+struct StoreDataElement;
+
+// The Mii manager is responsible for handling mii operations along with providing an easy interface
+// for HLE emulation of the mii service.
class MiiManager {
public:
MiiManager();
+ Result Initialize(DatabaseSessionMetadata& metadata);
+
+ // Auto generated mii
+ void BuildDefault(CharInfo& out_char_info, u32 index) const;
+ void BuildBase(CharInfo& out_char_info, Gender gender) const;
+ void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
- bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter);
+ // Database operations
bool IsFullDatabase() const;
- u32 GetCount(SourceFlag source_flag) const;
- ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
- CharInfo BuildRandom(Age age, Gender gender, Race race);
- CharInfo BuildDefault(std::size_t index);
- CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
- bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
- ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
- Result GetIndex(const CharInfo& info, u32& index);
+ void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const;
+ bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
+ u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
+ Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id);
+ Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data);
+ Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
+ s32 FindIndex(const Common::UUID& create_id, bool is_special) const;
+ Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
+ s32& out_index) const;
+ Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
+
+ // Test database operations
+ bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata);
+ Result DestroyFile(DatabaseSessionMetadata& metadata);
+ Result DeleteFile();
+ Result Format(DatabaseSessionMetadata& metadata);
- // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData
- Ver3StoreData BuildFromStoreData(const CharInfo& mii) const;
+ // Mii conversions
+ Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
+ Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const;
+ Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const;
+ Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
+ const CharInfo& char_info, SourceFlag source_flag) const;
+ Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
+ const StoreData& store_data, SourceFlag source_flag) const;
- // This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData
- NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const;
+ // Overloaded getters
+ Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
+ u32& out_count, SourceFlag source_flag) const;
+ Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
+ u32& out_count, SourceFlag source_flag) const;
+ Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements,
+ u32& out_count, SourceFlag source_flag) const;
+ Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
+ u32& out_count, SourceFlag source_flag) const;
private:
- const Common::UUID user_id{};
- u64 update_counter{};
+ Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
+ SourceFlag source_flag) const;
+ Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
+ SourceFlag source_flag) const;
+ Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count,
+ SourceFlag source_flag) const;
+ Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
+ SourceFlag source_flag) const;
+
+ DatabaseManager database_manager{};
+
+ // This should be a global value
+ bool is_broken_with_clear_flag{};
};
}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h
new file mode 100644
index 000000000..e2c36e556
--- /dev/null
+++ b/src/core/hle/service/mii/mii_result.h
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Mii {
+
+constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1};
+constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2};
+constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
+constexpr Result ResultNotFound{ErrorModule::Mii, 4};
+constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
+constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
+constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101};
+constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103};
+constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104};
+constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105};
+constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107};
+constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
+constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
+constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
+constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204};
+constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205};
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h
new file mode 100644
index 000000000..f43efd83c
--- /dev/null
+++ b/src/core/hle/service/mii/mii_types.h
@@ -0,0 +1,692 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <type_traits>
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/uuid.h"
+
+namespace Service::Mii {
+
+constexpr std::size_t MaxNameSize = 10;
+constexpr u8 MaxHeight = 127;
+constexpr u8 MaxBuild = 127;
+constexpr u8 MaxType = 1;
+constexpr u8 MaxRegionMove = 3;
+constexpr u8 MaxEyeScale = 7;
+constexpr u8 MaxEyeAspect = 6;
+constexpr u8 MaxEyeRotate = 7;
+constexpr u8 MaxEyeX = 12;
+constexpr u8 MaxEyeY = 18;
+constexpr u8 MaxEyebrowScale = 8;
+constexpr u8 MaxEyebrowAspect = 6;
+constexpr u8 MaxEyebrowRotate = 11;
+constexpr u8 MaxEyebrowX = 12;
+constexpr u8 MaxEyebrowY = 15;
+constexpr u8 MaxNoseScale = 8;
+constexpr u8 MaxNoseY = 18;
+constexpr u8 MaxMouthScale = 8;
+constexpr u8 MaxMoutAspect = 6;
+constexpr u8 MaxMouthY = 18;
+constexpr u8 MaxMustacheScale = 8;
+constexpr u8 MaxMustacheY = 16;
+constexpr u8 MaxGlassScale = 7;
+constexpr u8 MaxGlassY = 20;
+constexpr u8 MaxMoleScale = 8;
+constexpr u8 MaxMoleX = 16;
+constexpr u8 MaxMoleY = 30;
+constexpr u8 MaxVer3CommonColor = 7;
+constexpr u8 MaxVer3GlassType = 8;
+
+enum class Age : u8 {
+ Young,
+ Normal,
+ Old,
+ All, // Default
+
+ Max = All,
+};
+
+enum class Gender : u8 {
+ Male,
+ Female,
+ All, // Default
+
+ Max = Female,
+};
+
+enum class Race : u8 {
+ Black,
+ White,
+ Asian,
+ All, // Default
+
+ Max = All,
+};
+
+enum class HairType : u8 {
+ NormalLong, // Default
+ NormalShort,
+ NormalMedium,
+ NormalExtraLong,
+ NormalLongBottom,
+ NormalTwoPeaks,
+ PartingLong,
+ FrontLock,
+ PartingShort,
+ PartingExtraLongCurved,
+ PartingExtraLong,
+ PartingMiddleLong,
+ PartingSquared,
+ PartingLongBottom,
+ PeaksTop,
+ PeaksSquared,
+ PartingPeaks,
+ PeaksLongBottom,
+ Peaks,
+ PeaksRounded,
+ PeaksSide,
+ PeaksMedium,
+ PeaksLong,
+ PeaksRoundedLong,
+ PartingFrontPeaks,
+ PartingLongFront,
+ PartingLongRounded,
+ PartingFrontPeaksLong,
+ PartingExtraLongRounded,
+ LongRounded,
+ NormalUnknown1,
+ NormalUnknown2,
+ NormalUnknown3,
+ NormalUnknown4,
+ NormalUnknown5,
+ NormalUnknown6,
+ DreadLocks,
+ PlatedMats,
+ Caps,
+ Afro,
+ PlatedMatsLong,
+ Beanie,
+ Short,
+ ShortTopLongSide,
+ ShortUnknown1,
+ ShortUnknown2,
+ MilitaryParting,
+ Military,
+ ShortUnknown3,
+ ShortUnknown4,
+ ShortUnknown5,
+ ShortUnknown6,
+ NoneTop,
+ None,
+ LongUnknown1,
+ LongUnknown2,
+ LongUnknown3,
+ LongUnknown4,
+ LongUnknown5,
+ LongUnknown6,
+ LongUnknown7,
+ LongUnknown8,
+ LongUnknown9,
+ LongUnknown10,
+ LongUnknown11,
+ LongUnknown12,
+ LongUnknown13,
+ LongUnknown14,
+ LongUnknown15,
+ LongUnknown16,
+ LongUnknown17,
+ LongUnknown18,
+ LongUnknown19,
+ LongUnknown20,
+ LongUnknown21,
+ LongUnknown22,
+ LongUnknown23,
+ LongUnknown24,
+ LongUnknown25,
+ LongUnknown26,
+ LongUnknown27,
+ LongUnknown28,
+ LongUnknown29,
+ LongUnknown30,
+ LongUnknown31,
+ LongUnknown32,
+ LongUnknown33,
+ LongUnknown34,
+ LongUnknown35,
+ LongUnknown36,
+ LongUnknown37,
+ LongUnknown38,
+ LongUnknown39,
+ LongUnknown40,
+ LongUnknown41,
+ LongUnknown42,
+ LongUnknown43,
+ LongUnknown44,
+ LongUnknown45,
+ LongUnknown46,
+ LongUnknown47,
+ LongUnknown48,
+ LongUnknown49,
+ LongUnknown50,
+ LongUnknown51,
+ LongUnknown52,
+ LongUnknown53,
+ LongUnknown54,
+ LongUnknown55,
+ LongUnknown56,
+ LongUnknown57,
+ LongUnknown58,
+ LongUnknown59,
+ LongUnknown60,
+ LongUnknown61,
+ LongUnknown62,
+ LongUnknown63,
+ LongUnknown64,
+ LongUnknown65,
+ LongUnknown66,
+ TwoMediumFrontStrandsOneLongBackPonyTail,
+ TwoFrontStrandsLongBackPonyTail,
+ PartingFrontTwoLongBackPonyTails,
+ TwoFrontStrandsOneLongBackPonyTail,
+ LongBackPonyTail,
+ LongFrontTwoLongBackPonyTails,
+ StrandsTwoShortSidedPonyTails,
+ TwoMediumSidedPonyTails,
+ ShortFrontTwoBackPonyTails,
+ TwoShortSidedPonyTails,
+ TwoLongSidedPonyTails,
+ LongFrontTwoBackPonyTails,
+
+ Max = LongFrontTwoBackPonyTails,
+};
+
+enum class MoleType : u8 {
+ None, // Default
+ OneDot,
+
+ Max = OneDot,
+};
+
+enum class HairFlip : u8 {
+ Left, // Default
+ Right,
+
+ Max = Right,
+};
+
+enum class CommonColor : u8 {
+ // For simplicity common colors aren't listed
+ Max = 99,
+ Count = 100,
+};
+
+enum class FavoriteColor : u8 {
+ Red, // Default
+ Orange,
+ Yellow,
+ LimeGreen,
+ Green,
+ Blue,
+ LightBlue,
+ Pink,
+ Purple,
+ Brown,
+ White,
+ Black,
+
+ Max = Black,
+};
+
+enum class EyeType : u8 {
+ Normal, // Default
+ NormalLash,
+ WhiteLash,
+ WhiteNoBottom,
+ OvalAngledWhite,
+ AngryWhite,
+ DotLashType1,
+ Line,
+ DotLine,
+ OvalWhite,
+ RoundedWhite,
+ NormalShadow,
+ CircleWhite,
+ Circle,
+ CircleWhiteStroke,
+ NormalOvalNoBottom,
+ NormalOvalLarge,
+ NormalRoundedNoBottom,
+ SmallLash,
+ Small,
+ TwoSmall,
+ NormalLongLash,
+ WhiteTwoLashes,
+ WhiteThreeLashes,
+ DotAngry,
+ DotAngled,
+ Oval,
+ SmallWhite,
+ WhiteAngledNoBottom,
+ WhiteAngledNoLeft,
+ SmallWhiteTwoLashes,
+ LeafWhiteLash,
+ WhiteLargeNoBottom,
+ Dot,
+ DotLashType2,
+ DotThreeLashes,
+ WhiteOvalTop,
+ WhiteOvalBottom,
+ WhiteOvalBottomFlat,
+ WhiteOvalTwoLashes,
+ WhiteOvalThreeLashes,
+ WhiteOvalNoBottomTwoLashes,
+ DotWhite,
+ WhiteOvalTopFlat,
+ WhiteThinLeaf,
+ StarThreeLashes,
+ LineTwoLashes,
+ CrowsFeet,
+ WhiteNoBottomFlat,
+ WhiteNoBottomRounded,
+ WhiteSmallBottomLine,
+ WhiteNoBottomLash,
+ WhiteNoPartialBottomLash,
+ WhiteOvalBottomLine,
+ WhiteNoBottomLashTopLine,
+ WhiteNoPartialBottomTwoLashes,
+ NormalTopLine,
+ WhiteOvalLash,
+ RoundTired,
+ WhiteLarge,
+
+ Max = WhiteLarge,
+};
+
+enum class MouthType : u8 {
+ Neutral, // Default
+ NeutralLips,
+ Smile,
+ SmileStroke,
+ SmileTeeth,
+ LipsSmall,
+ LipsLarge,
+ Wave,
+ WaveAngrySmall,
+ NeutralStrokeLarge,
+ TeethSurprised,
+ LipsExtraLarge,
+ LipsUp,
+ NeutralDown,
+ Surprised,
+ TeethMiddle,
+ NeutralStroke,
+ LipsExtraSmall,
+ Malicious,
+ LipsDual,
+ NeutralComma,
+ NeutralUp,
+ TeethLarge,
+ WaveAngry,
+ LipsSexy,
+ SmileInverted,
+ LipsSexyOutline,
+ SmileRounded,
+ LipsTeeth,
+ NeutralOpen,
+ TeethRounded,
+ WaveAngrySmallInverted,
+ NeutralCommaInverted,
+ TeethFull,
+ SmileDownLine,
+ Kiss,
+
+ Max = Kiss,
+};
+
+enum class FontRegion : u8 {
+ Standard, // Default
+ China,
+ Korea,
+ Taiwan,
+
+ Max = Taiwan,
+};
+
+enum class FacelineType : u8 {
+ Sharp, // Default
+ Rounded,
+ SharpRounded,
+ SharpRoundedSmall,
+ Large,
+ LargeRounded,
+ SharpSmall,
+ Flat,
+ Bump,
+ Angular,
+ FlatRounded,
+ AngularSmall,
+
+ Max = AngularSmall,
+};
+
+enum class FacelineColor : u8 {
+ Beige, // Default
+ WarmBeige,
+ Natural,
+ Honey,
+ Chestnut,
+ Porcelain,
+ Ivory,
+ WarmIvory,
+ Almond,
+ Espresso,
+
+ Max = Espresso,
+ Count = Max + 1,
+};
+
+enum class FacelineWrinkle : u8 {
+ None, // Default
+ TearTroughs,
+ FacialPain,
+ Cheeks,
+ Folds,
+ UnderTheEyes,
+ SplitChin,
+ Chin,
+ BrowDroop,
+ MouthFrown,
+ CrowsFeet,
+ FoldsCrowsFrown,
+
+ Max = FoldsCrowsFrown,
+};
+
+enum class FacelineMake : u8 {
+ None, // Default
+ CheekPorcelain,
+ CheekNatural,
+ EyeShadowBlue,
+ CheekBlushPorcelain,
+ CheekBlushNatural,
+ CheekPorcelainEyeShadowBlue,
+ CheekPorcelainEyeShadowNatural,
+ CheekBlushPorcelainEyeShadowEspresso,
+ Freckles,
+ LionsManeBeard,
+ StubbleBeard,
+
+ Max = StubbleBeard,
+};
+
+enum class EyebrowType : u8 {
+ FlatAngledLarge, // Default
+ LowArchRoundedThin,
+ SoftAngledLarge,
+ MediumArchRoundedThin,
+ RoundedMedium,
+ LowArchMedium,
+ RoundedThin,
+ UpThin,
+ MediumArchRoundedMedium,
+ RoundedLarge,
+ UpLarge,
+ FlatAngledLargeInverted,
+ MediumArchFlat,
+ AngledThin,
+ HorizontalLarge,
+ HighArchFlat,
+ Flat,
+ MediumArchLarge,
+ LowArchThin,
+ RoundedThinInverted,
+ HighArchLarge,
+ Hairy,
+ Dotted,
+ None,
+
+ Max = None,
+};
+
+enum class NoseType : u8 {
+ Normal, // Default
+ Rounded,
+ Dot,
+ Arrow,
+ Roman,
+ Triangle,
+ Button,
+ RoundedInverted,
+ Potato,
+ Grecian,
+ Snub,
+ Aquiline,
+ ArrowLeft,
+ RoundedLarge,
+ Hooked,
+ Fat,
+ Droopy,
+ ArrowLarge,
+
+ Max = ArrowLarge,
+};
+
+enum class BeardType : u8 {
+ None,
+ Goatee,
+ GoateeLong,
+ LionsManeLong,
+ LionsMane,
+ Full,
+
+ Min = Goatee,
+ Max = Full,
+};
+
+enum class MustacheType : u8 {
+ None,
+ Walrus,
+ Pencil,
+ Horseshoe,
+ Normal,
+ Toothbrush,
+
+ Min = Walrus,
+ Max = Toothbrush,
+};
+
+enum class GlassType : u8 {
+ None,
+ Oval,
+ Wayfarer,
+ Rectangle,
+ TopRimless,
+ Rounded,
+ Oversized,
+ CatEye,
+ Square,
+ BottomRimless,
+ SemiOpaqueRounded,
+ SemiOpaqueCatEye,
+ SemiOpaqueOval,
+ SemiOpaqueRectangle,
+ SemiOpaqueAviator,
+ OpaqueRounded,
+ OpaqueCatEye,
+ OpaqueOval,
+ OpaqueRectangle,
+ OpaqueAviator,
+
+ Max = OpaqueAviator,
+ Count = Max + 1,
+};
+
+enum class BeardAndMustacheFlag : u32 {
+ Beard = 1,
+ Mustache,
+ All = Beard | Mustache,
+};
+DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
+
+enum class Source : u32 {
+ Database = 0,
+ Default = 1,
+ Account = 2,
+ Friend = 3,
+};
+
+enum class SourceFlag : u32 {
+ None = 0,
+ Database = 1 << 0,
+ Default = 1 << 1,
+};
+DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
+
+enum class ValidationResult : u32 {
+ NoErrors = 0x0,
+ InvalidBeardColor = 0x1,
+ InvalidBeardType = 0x2,
+ InvalidBuild = 0x3,
+ InvalidEyeAspect = 0x4,
+ InvalidEyeColor = 0x5,
+ InvalidEyeRotate = 0x6,
+ InvalidEyeScale = 0x7,
+ InvalidEyeType = 0x8,
+ InvalidEyeX = 0x9,
+ InvalidEyeY = 0xa,
+ InvalidEyebrowAspect = 0xb,
+ InvalidEyebrowColor = 0xc,
+ InvalidEyebrowRotate = 0xd,
+ InvalidEyebrowScale = 0xe,
+ InvalidEyebrowType = 0xf,
+ InvalidEyebrowX = 0x10,
+ InvalidEyebrowY = 0x11,
+ InvalidFacelineColor = 0x12,
+ InvalidFacelineMake = 0x13,
+ InvalidFacelineWrinkle = 0x14,
+ InvalidFacelineType = 0x15,
+ InvalidColor = 0x16,
+ InvalidFont = 0x17,
+ InvalidGender = 0x18,
+ InvalidGlassColor = 0x19,
+ InvalidGlassScale = 0x1a,
+ InvalidGlassType = 0x1b,
+ InvalidGlassY = 0x1c,
+ InvalidHairColor = 0x1d,
+ InvalidHairFlip = 0x1e,
+ InvalidHairType = 0x1f,
+ InvalidHeight = 0x20,
+ InvalidMoleScale = 0x21,
+ InvalidMoleType = 0x22,
+ InvalidMoleX = 0x23,
+ InvalidMoleY = 0x24,
+ InvalidMouthAspect = 0x25,
+ InvalidMouthColor = 0x26,
+ InvalidMouthScale = 0x27,
+ InvalidMouthType = 0x28,
+ InvalidMouthY = 0x29,
+ InvalidMustacheScale = 0x2a,
+ InvalidMustacheType = 0x2b,
+ InvalidMustacheY = 0x2c,
+ InvalidNoseScale = 0x2e,
+ InvalidNoseType = 0x2f,
+ InvalidNoseY = 0x30,
+ InvalidRegionMove = 0x31,
+ InvalidCreateId = 0x32,
+ InvalidName = 0x33,
+ InvalidChecksum = 0x34,
+ InvalidType = 0x35,
+};
+
+struct Nickname {
+ std::array<char16_t, MaxNameSize> data{};
+
+ // Checks for null or dirty strings
+ bool IsValid() const {
+ if (data[0] == 0) {
+ return false;
+ }
+
+ std::size_t index = 1;
+ while (data[index] != 0) {
+ index++;
+ }
+ while (index < MaxNameSize && data[index] == 0) {
+ index++;
+ }
+ return index == MaxNameSize;
+ }
+};
+static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size");
+
+struct DefaultMii {
+ u32 face_type{};
+ u32 face_color{};
+ u32 face_wrinkle{};
+ u32 face_makeup{};
+ u32 hair_type{};
+ u32 hair_color{};
+ u32 hair_flip{};
+ u32 eye_type{};
+ u32 eye_color{};
+ u32 eye_scale{};
+ u32 eye_aspect{};
+ u32 eye_rotate{};
+ u32 eye_x{};
+ u32 eye_y{};
+ u32 eyebrow_type{};
+ u32 eyebrow_color{};
+ u32 eyebrow_scale{};
+ u32 eyebrow_aspect{};
+ u32 eyebrow_rotate{};
+ u32 eyebrow_x{};
+ u32 eyebrow_y{};
+ u32 nose_type{};
+ u32 nose_scale{};
+ u32 nose_y{};
+ u32 mouth_type{};
+ u32 mouth_color{};
+ u32 mouth_scale{};
+ u32 mouth_aspect{};
+ u32 mouth_y{};
+ u32 mustache_type{};
+ u32 beard_type{};
+ u32 beard_color{};
+ u32 mustache_scale{};
+ u32 mustache_y{};
+ u32 glasses_type{};
+ u32 glasses_color{};
+ u32 glasses_scale{};
+ u32 glasses_y{};
+ u32 mole_type{};
+ u32 mole_scale{};
+ u32 mole_x{};
+ u32 mole_y{};
+ u32 height{};
+ u32 weight{};
+ u32 gender{};
+ u32 favorite_color{};
+ u32 region_move{};
+ u32 font_region{};
+ u32 type{};
+ Nickname nickname;
+};
+static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size.");
+
+struct DatabaseSessionMetadata {
+ u32 interface_version;
+ u32 magic;
+ u64 update_counter;
+
+ bool IsInterfaceVersionSupported(u32 version) const {
+ return version <= interface_version;
+ }
+};
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h
new file mode 100644
index 000000000..3534fa31d
--- /dev/null
+++ b/src/core/hle/service/mii/mii_util.h
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <random>
+#include <span>
+
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "common/uuid.h"
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii {
+class MiiUtil {
+public:
+ static u16 CalculateCrc16(const void* data, std::size_t size) {
+ s32 crc{};
+ for (std::size_t i = 0; i < size; i++) {
+ crc ^= static_cast<const u8*>(data)[i] << 8;
+ for (std::size_t j = 0; j < 8; j++) {
+ crc <<= 1;
+ if ((crc & 0x10000) != 0) {
+ crc = (crc ^ 0x1021) & 0xFFFF;
+ }
+ }
+ }
+ return Common::swap16(static_cast<u16>(crc));
+ }
+
+ static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) {
+ constexpr u16 magic{0x1021};
+ s32 crc{};
+
+ for (std::size_t i = 0; i < uuid.uuid.size(); i++) {
+ for (std::size_t j = 0; j < 8; j++) {
+ crc <<= 1;
+ if ((crc & 0x10000) != 0) {
+ crc = crc ^ magic;
+ }
+ }
+ crc ^= uuid.uuid[i];
+ }
+
+ // As much as this looks wrong this is what N's does
+
+ for (std::size_t i = 0; i < data_size * 8; i++) {
+ crc <<= 1;
+ if ((crc & 0x10000) != 0) {
+ crc = crc ^ magic;
+ }
+ }
+
+ return Common::swap16(static_cast<u16>(crc));
+ }
+
+ static Common::UUID MakeCreateId() {
+ return Common::UUID::MakeRandomRFC4122V4();
+ }
+
+ static Common::UUID GetDeviceId() {
+ // This should be nn::settings::detail::GetMiiAuthorId()
+ return Common::UUID::MakeDefault();
+ }
+
+ template <typename T>
+ static T GetRandomValue(T min, T max) {
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<u64> distribution(static_cast<u64>(min),
+ static_cast<u64>(max));
+ return static_cast<T>(distribution(gen));
+ }
+
+ template <typename T>
+ static T GetRandomValue(T max) {
+ return GetRandomValue<T>({}, max);
+ }
+
+ static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) {
+ // TODO: This function needs to check against the font tables
+ return true;
+ }
+};
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h
deleted file mode 100644
index c2bec68d4..000000000
--- a/src/core/hle/service/mii/raw_data.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-
-#include "core/hle/service/mii/types.h"
-
-namespace Service::Mii::RawData {
-
-extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline;
-extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType;
-extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType;
-extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType;
-extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType;
-extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType;
-
-} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h
deleted file mode 100644
index c48d08d79..000000000
--- a/src/core/hle/service/mii/types.h
+++ /dev/null
@@ -1,553 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <type_traits>
-
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/uuid.h"
-
-namespace Service::Mii {
-
-enum class Age : u32 {
- Young,
- Normal,
- Old,
- All,
-};
-
-enum class BeardType : u32 {
- None,
- Beard1,
- Beard2,
- Beard3,
- Beard4,
- Beard5,
-};
-
-enum class BeardAndMustacheFlag : u32 {
- Beard = 1,
- Mustache,
- All = Beard | Mustache,
-};
-DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
-
-enum class FontRegion : u32 {
- Standard,
- China,
- Korea,
- Taiwan,
-};
-
-enum class Gender : u32 {
- Male,
- Female,
- All,
- Maximum = Female,
-};
-
-enum class HairFlip : u32 {
- Left,
- Right,
- Maximum = Right,
-};
-
-enum class MustacheType : u32 {
- None,
- Mustache1,
- Mustache2,
- Mustache3,
- Mustache4,
- Mustache5,
-};
-
-enum class Race : u32 {
- Black,
- White,
- Asian,
- All,
-};
-
-enum class Source : u32 {
- Database = 0,
- Default = 1,
- Account = 2,
- Friend = 3,
-};
-
-enum class SourceFlag : u32 {
- None = 0,
- Database = 1 << 0,
- Default = 1 << 1,
-};
-DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
-
-// nn::mii::CharInfo
-struct CharInfo {
- Common::UUID uuid;
- std::array<char16_t, 11> name;
- u8 font_region;
- u8 favorite_color;
- u8 gender;
- u8 height;
- u8 build;
- u8 type;
- u8 region_move;
- u8 faceline_type;
- u8 faceline_color;
- u8 faceline_wrinkle;
- u8 faceline_make;
- u8 hair_type;
- u8 hair_color;
- u8 hair_flip;
- u8 eye_type;
- u8 eye_color;
- u8 eye_scale;
- u8 eye_aspect;
- u8 eye_rotate;
- u8 eye_x;
- u8 eye_y;
- u8 eyebrow_type;
- u8 eyebrow_color;
- u8 eyebrow_scale;
- u8 eyebrow_aspect;
- u8 eyebrow_rotate;
- u8 eyebrow_x;
- u8 eyebrow_y;
- u8 nose_type;
- u8 nose_scale;
- u8 nose_y;
- u8 mouth_type;
- u8 mouth_color;
- u8 mouth_scale;
- u8 mouth_aspect;
- u8 mouth_y;
- u8 beard_color;
- u8 beard_type;
- u8 mustache_type;
- u8 mustache_scale;
- u8 mustache_y;
- u8 glasses_type;
- u8 glasses_color;
- u8 glasses_scale;
- u8 glasses_y;
- u8 mole_type;
- u8 mole_scale;
- u8 mole_x;
- u8 mole_y;
- u8 padding;
-};
-static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
-static_assert(std::has_unique_object_representations_v<CharInfo>,
- "All bits of CharInfo must contribute to its value.");
-
-#pragma pack(push, 4)
-
-struct MiiInfoElement {
- MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
-
- CharInfo info{};
- Source source{};
-};
-static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
-
-struct MiiStoreBitFields {
- union {
- u32 word_0{};
-
- BitField<0, 8, u32> hair_type;
- BitField<8, 7, u32> height;
- BitField<15, 1, u32> mole_type;
- BitField<16, 7, u32> build;
- BitField<23, 1, HairFlip> hair_flip;
- BitField<24, 7, u32> hair_color;
- BitField<31, 1, u32> type;
- };
-
- union {
- u32 word_1{};
-
- BitField<0, 7, u32> eye_color;
- BitField<7, 1, Gender> gender;
- BitField<8, 7, u32> eyebrow_color;
- BitField<16, 7, u32> mouth_color;
- BitField<24, 7, u32> beard_color;
- };
-
- union {
- u32 word_2{};
-
- BitField<0, 7, u32> glasses_color;
- BitField<8, 6, u32> eye_type;
- BitField<14, 2, u32> region_move;
- BitField<16, 6, u32> mouth_type;
- BitField<22, 2, FontRegion> font_region;
- BitField<24, 5, u32> eye_y;
- BitField<29, 3, u32> glasses_scale;
- };
-
- union {
- u32 word_3{};
-
- BitField<0, 5, u32> eyebrow_type;
- BitField<5, 3, MustacheType> mustache_type;
- BitField<8, 5, u32> nose_type;
- BitField<13, 3, BeardType> beard_type;
- BitField<16, 5, u32> nose_y;
- BitField<21, 3, u32> mouth_aspect;
- BitField<24, 5, u32> mouth_y;
- BitField<29, 3, u32> eyebrow_aspect;
- };
-
- union {
- u32 word_4{};
-
- BitField<0, 5, u32> mustache_y;
- BitField<5, 3, u32> eye_rotate;
- BitField<8, 5, u32> glasses_y;
- BitField<13, 3, u32> eye_aspect;
- BitField<16, 5, u32> mole_x;
- BitField<21, 3, u32> eye_scale;
- BitField<24, 5, u32> mole_y;
- };
-
- union {
- u32 word_5{};
-
- BitField<0, 5, u32> glasses_type;
- BitField<8, 4, u32> favorite_color;
- BitField<12, 4, u32> faceline_type;
- BitField<16, 4, u32> faceline_color;
- BitField<20, 4, u32> faceline_wrinkle;
- BitField<24, 4, u32> faceline_makeup;
- BitField<28, 4, u32> eye_x;
- };
-
- union {
- u32 word_6{};
-
- BitField<0, 4, u32> eyebrow_scale;
- BitField<4, 4, u32> eyebrow_rotate;
- BitField<8, 4, u32> eyebrow_x;
- BitField<12, 4, u32> eyebrow_y;
- BitField<16, 4, u32> nose_scale;
- BitField<20, 4, u32> mouth_scale;
- BitField<24, 4, u32> mustache_scale;
- BitField<28, 4, u32> mole_scale;
- };
-};
-static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
-static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
- "MiiStoreBitFields is not trivially copyable.");
-
-// This is nn::mii::Ver3StoreData
-// Based on citra HLE::Applets::MiiData and PretendoNetwork.
-// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
-// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
-struct Ver3StoreData {
- u8 version;
- union {
- u8 raw;
-
- BitField<0, 1, u8> allow_copying;
- BitField<1, 1, u8> profanity_flag;
- BitField<2, 2, u8> region_lock;
- BitField<4, 2, u8> character_set;
- } region_information;
- u16_be mii_id;
- u64_be system_id;
- u32_be specialness_and_creation_date;
- std::array<u8, 0x6> creator_mac;
- u16_be padding;
- union {
- u16 raw;
-
- BitField<0, 1, u16> gender;
- BitField<1, 4, u16> birth_month;
- BitField<5, 5, u16> birth_day;
- BitField<10, 4, u16> favorite_color;
- BitField<14, 1, u16> favorite;
- } mii_information;
- std::array<char16_t, 0xA> mii_name;
- u8 height;
- u8 build;
- union {
- u8 raw;
-
- BitField<0, 1, u8> disable_sharing;
- BitField<1, 4, u8> face_shape;
- BitField<5, 3, u8> skin_color;
- } appearance_bits1;
- union {
- u8 raw;
-
- BitField<0, 4, u8> wrinkles;
- BitField<4, 4, u8> makeup;
- } appearance_bits2;
- u8 hair_style;
- union {
- u8 raw;
-
- BitField<0, 3, u8> hair_color;
- BitField<3, 1, u8> flip_hair;
- } appearance_bits3;
- union {
- u32 raw;
-
- BitField<0, 6, u32> eye_type;
- BitField<6, 3, u32> eye_color;
- BitField<9, 4, u32> eye_scale;
- BitField<13, 3, u32> eye_vertical_stretch;
- BitField<16, 5, u32> eye_rotation;
- BitField<21, 4, u32> eye_spacing;
- BitField<25, 5, u32> eye_y_position;
- } appearance_bits4;
- union {
- u32 raw;
-
- BitField<0, 5, u32> eyebrow_style;
- BitField<5, 3, u32> eyebrow_color;
- BitField<8, 4, u32> eyebrow_scale;
- BitField<12, 3, u32> eyebrow_yscale;
- BitField<16, 4, u32> eyebrow_rotation;
- BitField<21, 4, u32> eyebrow_spacing;
- BitField<25, 5, u32> eyebrow_y_position;
- } appearance_bits5;
- union {
- u16 raw;
-
- BitField<0, 5, u16> nose_type;
- BitField<5, 4, u16> nose_scale;
- BitField<9, 5, u16> nose_y_position;
- } appearance_bits6;
- union {
- u16 raw;
-
- BitField<0, 6, u16> mouth_type;
- BitField<6, 3, u16> mouth_color;
- BitField<9, 4, u16> mouth_scale;
- BitField<13, 3, u16> mouth_horizontal_stretch;
- } appearance_bits7;
- union {
- u8 raw;
-
- BitField<0, 5, u8> mouth_y_position;
- BitField<5, 3, u8> mustache_type;
- } appearance_bits8;
- u8 allow_copying;
- union {
- u16 raw;
-
- BitField<0, 3, u16> bear_type;
- BitField<3, 3, u16> facial_hair_color;
- BitField<6, 4, u16> mustache_scale;
- BitField<10, 5, u16> mustache_y_position;
- } appearance_bits9;
- union {
- u16 raw;
-
- BitField<0, 4, u16> glasses_type;
- BitField<4, 3, u16> glasses_color;
- BitField<7, 4, u16> glasses_scale;
- BitField<11, 5, u16> glasses_y_position;
- } appearance_bits10;
- union {
- u16 raw;
-
- BitField<0, 1, u16> mole_enabled;
- BitField<1, 4, u16> mole_scale;
- BitField<5, 5, u16> mole_x_position;
- BitField<10, 5, u16> mole_y_position;
- } appearance_bits11;
-
- std::array<u16_le, 0xA> author_name;
- INSERT_PADDING_BYTES(0x2);
- u16_be crc;
-};
-static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
-
-struct NfpStoreDataExtension {
- u8 faceline_color;
- u8 hair_color;
- u8 eye_color;
- u8 eyebrow_color;
- u8 mouth_color;
- u8 beard_color;
- u8 glass_color;
- u8 glass_type;
-};
-static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
-
-constexpr std::array<u8, 0x10> Ver3FacelineColorTable{
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
-};
-
-constexpr std::array<u8, 100> Ver3HairColorTable{
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0,
- 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
- 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4,
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5,
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7,
- 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4,
-};
-
-constexpr std::array<u8, 100> Ver3EyeColorTable{
- 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4,
- 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
- 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4,
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
- 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2,
- 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
-};
-
-constexpr std::array<u8, 100> Ver3MouthlineColorTable{
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
- 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
- 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
- 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
- 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3,
- 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3,
-};
-
-constexpr std::array<u8, 100> Ver3GlassColorTable{
- 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3,
- 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
- 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
- 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5,
- 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4,
- 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
-};
-
-constexpr std::array<u8, 20> Ver3GlassTypeTable{
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1,
- 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7,
-};
-
-struct MiiStoreData {
- using Name = std::array<char16_t, 10>;
-
- MiiStoreData();
- MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields,
- const Common::UUID& user_id);
-
- // This corresponds to the above structure MiiStoreBitFields. I did it like this because the
- // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
- // not suitable for our uses.
- struct {
- std::array<u8, 0x1C> data{};
- static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
-
- Name name{};
- Common::UUID uuid{};
- } data;
-
- u16 data_crc{};
- u16 device_crc{};
-};
-static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
-
-struct MiiStoreDataElement {
- MiiStoreData data{};
- Source source{};
-};
-static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
-
-struct MiiDatabase {
- u32 magic{}; // 'NFDB'
- std::array<MiiStoreData, 0x64> miis{};
- INSERT_PADDING_BYTES(1);
- u8 count{};
- u16 crc{};
-};
-static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
-
-struct RandomMiiValues {
- std::array<u8, 0xbc> values{};
-};
-static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
-
-struct RandomMiiData4 {
- Gender gender{};
- Age age{};
- Race race{};
- u32 values_count{};
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
-
-struct RandomMiiData3 {
- u32 arg_1;
- u32 arg_2;
- u32 values_count;
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
-
-struct RandomMiiData2 {
- u32 arg_1;
- u32 values_count;
- std::array<u32, 47> values{};
-};
-static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
-
-struct DefaultMii {
- u32 face_type{};
- u32 face_color{};
- u32 face_wrinkle{};
- u32 face_makeup{};
- u32 hair_type{};
- u32 hair_color{};
- u32 hair_flip{};
- u32 eye_type{};
- u32 eye_color{};
- u32 eye_scale{};
- u32 eye_aspect{};
- u32 eye_rotate{};
- u32 eye_x{};
- u32 eye_y{};
- u32 eyebrow_type{};
- u32 eyebrow_color{};
- u32 eyebrow_scale{};
- u32 eyebrow_aspect{};
- u32 eyebrow_rotate{};
- u32 eyebrow_x{};
- u32 eyebrow_y{};
- u32 nose_type{};
- u32 nose_scale{};
- u32 nose_y{};
- u32 mouth_type{};
- u32 mouth_color{};
- u32 mouth_scale{};
- u32 mouth_aspect{};
- u32 mouth_y{};
- u32 mustache_type{};
- u32 beard_type{};
- u32 beard_color{};
- u32 mustache_scale{};
- u32 mustache_y{};
- u32 glasses_type{};
- u32 glasses_color{};
- u32 glasses_scale{};
- u32 glasses_y{};
- u32 mole_type{};
- u32 mole_scale{};
- u32 mole_x{};
- u32 mole_y{};
- u32 height{};
- u32 weight{};
- Gender gender{};
- u32 favorite_color{};
- u32 region{};
- FontRegion font_region{};
- u32 type{};
- INSERT_PADDING_WORDS(5);
-};
-static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size.");
-
-#pragma pack(pop)
-
-} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp
new file mode 100644
index 000000000..c82186c73
--- /dev/null
+++ b/src/core/hle/service/mii/types/char_info.cpp
@@ -0,0 +1,482 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/store_data.h"
+
+namespace Service::Mii {
+
+void CharInfo::SetFromStoreData(const StoreData& store_data) {
+ name = store_data.GetNickname();
+ null_terminator = '\0';
+ create_id = store_data.GetCreateId();
+ font_region = store_data.GetFontRegion();
+ favorite_color = store_data.GetFavoriteColor();
+ gender = store_data.GetGender();
+ height = store_data.GetHeight();
+ build = store_data.GetBuild();
+ type = store_data.GetType();
+ region_move = store_data.GetRegionMove();
+ faceline_type = store_data.GetFacelineType();
+ faceline_color = store_data.GetFacelineColor();
+ faceline_wrinkle = store_data.GetFacelineWrinkle();
+ faceline_make = store_data.GetFacelineMake();
+ hair_type = store_data.GetHairType();
+ hair_color = store_data.GetHairColor();
+ hair_flip = store_data.GetHairFlip();
+ eye_type = store_data.GetEyeType();
+ eye_color = store_data.GetEyeColor();
+ eye_scale = store_data.GetEyeScale();
+ eye_aspect = store_data.GetEyeAspect();
+ eye_rotate = store_data.GetEyeRotate();
+ eye_x = store_data.GetEyeX();
+ eye_y = store_data.GetEyeY();
+ eyebrow_type = store_data.GetEyebrowType();
+ eyebrow_color = store_data.GetEyebrowColor();
+ eyebrow_scale = store_data.GetEyebrowScale();
+ eyebrow_aspect = store_data.GetEyebrowAspect();
+ eyebrow_rotate = store_data.GetEyebrowRotate();
+ eyebrow_x = store_data.GetEyebrowX();
+ eyebrow_y = store_data.GetEyebrowY();
+ nose_type = store_data.GetNoseType();
+ nose_scale = store_data.GetNoseScale();
+ nose_y = store_data.GetNoseY();
+ mouth_type = store_data.GetMouthType();
+ mouth_color = store_data.GetMouthColor();
+ mouth_scale = store_data.GetMouthScale();
+ mouth_aspect = store_data.GetMouthAspect();
+ mouth_y = store_data.GetMouthY();
+ beard_color = store_data.GetBeardColor();
+ beard_type = store_data.GetBeardType();
+ mustache_type = store_data.GetMustacheType();
+ mustache_scale = store_data.GetMustacheScale();
+ mustache_y = store_data.GetMustacheY();
+ glass_type = store_data.GetGlassType();
+ glass_color = store_data.GetGlassColor();
+ glass_scale = store_data.GetGlassScale();
+ glass_y = store_data.GetGlassY();
+ mole_type = store_data.GetMoleType();
+ mole_scale = store_data.GetMoleScale();
+ mole_x = store_data.GetMoleX();
+ mole_y = store_data.GetMoleY();
+ padding = '\0';
+}
+
+ValidationResult CharInfo::Verify() const {
+ if (!create_id.IsValid()) {
+ return ValidationResult::InvalidCreateId;
+ }
+ if (!name.IsValid()) {
+ return ValidationResult::InvalidName;
+ }
+ if (font_region > FontRegion::Max) {
+ return ValidationResult::InvalidFont;
+ }
+ if (favorite_color > FavoriteColor::Max) {
+ return ValidationResult::InvalidColor;
+ }
+ if (gender > Gender::Max) {
+ return ValidationResult::InvalidGender;
+ }
+ if (height > MaxHeight) {
+ return ValidationResult::InvalidHeight;
+ }
+ if (build > MaxBuild) {
+ return ValidationResult::InvalidBuild;
+ }
+ if (type > MaxType) {
+ return ValidationResult::InvalidType;
+ }
+ if (region_move > MaxRegionMove) {
+ return ValidationResult::InvalidRegionMove;
+ }
+ if (faceline_type > FacelineType::Max) {
+ return ValidationResult::InvalidFacelineType;
+ }
+ if (faceline_color > FacelineColor::Max) {
+ return ValidationResult::InvalidFacelineColor;
+ }
+ if (faceline_wrinkle > FacelineWrinkle::Max) {
+ return ValidationResult::InvalidFacelineWrinkle;
+ }
+ if (faceline_make > FacelineMake::Max) {
+ return ValidationResult::InvalidFacelineMake;
+ }
+ if (hair_type > HairType::Max) {
+ return ValidationResult::InvalidHairType;
+ }
+ if (hair_color > CommonColor::Max) {
+ return ValidationResult::InvalidHairColor;
+ }
+ if (hair_flip > HairFlip::Max) {
+ return ValidationResult::InvalidHairFlip;
+ }
+ if (eye_type > EyeType::Max) {
+ return ValidationResult::InvalidEyeType;
+ }
+ if (eye_color > CommonColor::Max) {
+ return ValidationResult::InvalidEyeColor;
+ }
+ if (eye_scale > MaxEyeScale) {
+ return ValidationResult::InvalidEyeScale;
+ }
+ if (eye_aspect > MaxEyeAspect) {
+ return ValidationResult::InvalidEyeAspect;
+ }
+ if (eye_rotate > MaxEyeX) {
+ return ValidationResult::InvalidEyeRotate;
+ }
+ if (eye_x > MaxEyeX) {
+ return ValidationResult::InvalidEyeX;
+ }
+ if (eye_y > MaxEyeY) {
+ return ValidationResult::InvalidEyeY;
+ }
+ if (eyebrow_type > EyebrowType::Max) {
+ return ValidationResult::InvalidEyebrowType;
+ }
+ if (eyebrow_color > CommonColor::Max) {
+ return ValidationResult::InvalidEyebrowColor;
+ }
+ if (eyebrow_scale > MaxEyebrowScale) {
+ return ValidationResult::InvalidEyebrowScale;
+ }
+ if (eyebrow_aspect > MaxEyebrowAspect) {
+ return ValidationResult::InvalidEyebrowAspect;
+ }
+ if (eyebrow_rotate > MaxEyebrowRotate) {
+ return ValidationResult::InvalidEyebrowRotate;
+ }
+ if (eyebrow_x > MaxEyebrowX) {
+ return ValidationResult::InvalidEyebrowX;
+ }
+ if (eyebrow_y - 3 > MaxEyebrowY) {
+ return ValidationResult::InvalidEyebrowY;
+ }
+ if (nose_type > NoseType::Max) {
+ return ValidationResult::InvalidNoseType;
+ }
+ if (nose_scale > MaxNoseScale) {
+ return ValidationResult::InvalidNoseScale;
+ }
+ if (nose_y > MaxNoseY) {
+ return ValidationResult::InvalidNoseY;
+ }
+ if (mouth_type > MouthType::Max) {
+ return ValidationResult::InvalidMouthType;
+ }
+ if (mouth_color > CommonColor::Max) {
+ return ValidationResult::InvalidMouthColor;
+ }
+ if (mouth_scale > MaxMouthScale) {
+ return ValidationResult::InvalidMouthScale;
+ }
+ if (mouth_aspect > MaxMoutAspect) {
+ return ValidationResult::InvalidMouthAspect;
+ }
+ if (mouth_y > MaxMouthY) {
+ return ValidationResult::InvalidMoleY;
+ }
+ if (beard_color > CommonColor::Max) {
+ return ValidationResult::InvalidBeardColor;
+ }
+ if (beard_type > BeardType::Max) {
+ return ValidationResult::InvalidBeardType;
+ }
+ if (mustache_type > MustacheType::Max) {
+ return ValidationResult::InvalidMustacheType;
+ }
+ if (mustache_scale > MaxMustacheScale) {
+ return ValidationResult::InvalidMustacheScale;
+ }
+ if (mustache_y > MaxMustacheY) {
+ return ValidationResult::InvalidMustacheY;
+ }
+ if (glass_type > GlassType::Max) {
+ return ValidationResult::InvalidGlassType;
+ }
+ if (glass_color > CommonColor::Max) {
+ return ValidationResult::InvalidGlassColor;
+ }
+ if (glass_scale > MaxGlassScale) {
+ return ValidationResult::InvalidGlassScale;
+ }
+ if (glass_y > MaxGlassY) {
+ return ValidationResult::InvalidGlassY;
+ }
+ if (mole_type > MoleType::Max) {
+ return ValidationResult::InvalidMoleType;
+ }
+ if (mole_scale > MaxMoleScale) {
+ return ValidationResult::InvalidMoleScale;
+ }
+ if (mole_x > MaxMoleX) {
+ return ValidationResult::InvalidMoleX;
+ }
+ if (mole_y > MaxMoleY) {
+ return ValidationResult::InvalidMoleY;
+ }
+ return ValidationResult::NoErrors;
+}
+
+Common::UUID CharInfo::GetCreateId() const {
+ return create_id;
+}
+
+Nickname CharInfo::GetNickname() const {
+ return name;
+}
+
+FontRegion CharInfo::GetFontRegion() const {
+ return font_region;
+}
+
+FavoriteColor CharInfo::GetFavoriteColor() const {
+ return favorite_color;
+}
+
+Gender CharInfo::GetGender() const {
+ return gender;
+}
+
+u8 CharInfo::GetHeight() const {
+ return height;
+}
+
+u8 CharInfo::GetBuild() const {
+ return build;
+}
+
+u8 CharInfo::GetType() const {
+ return type;
+}
+
+u8 CharInfo::GetRegionMove() const {
+ return region_move;
+}
+
+FacelineType CharInfo::GetFacelineType() const {
+ return faceline_type;
+}
+
+FacelineColor CharInfo::GetFacelineColor() const {
+ return faceline_color;
+}
+
+FacelineWrinkle CharInfo::GetFacelineWrinkle() const {
+ return faceline_wrinkle;
+}
+
+FacelineMake CharInfo::GetFacelineMake() const {
+ return faceline_make;
+}
+
+HairType CharInfo::GetHairType() const {
+ return hair_type;
+}
+
+CommonColor CharInfo::GetHairColor() const {
+ return hair_color;
+}
+
+HairFlip CharInfo::GetHairFlip() const {
+ return hair_flip;
+}
+
+EyeType CharInfo::GetEyeType() const {
+ return eye_type;
+}
+
+CommonColor CharInfo::GetEyeColor() const {
+ return eye_color;
+}
+
+u8 CharInfo::GetEyeScale() const {
+ return eye_scale;
+}
+
+u8 CharInfo::GetEyeAspect() const {
+ return eye_aspect;
+}
+
+u8 CharInfo::GetEyeRotate() const {
+ return eye_rotate;
+}
+
+u8 CharInfo::GetEyeX() const {
+ return eye_x;
+}
+
+u8 CharInfo::GetEyeY() const {
+ return eye_y;
+}
+
+EyebrowType CharInfo::GetEyebrowType() const {
+ return eyebrow_type;
+}
+
+CommonColor CharInfo::GetEyebrowColor() const {
+ return eyebrow_color;
+}
+
+u8 CharInfo::GetEyebrowScale() const {
+ return eyebrow_scale;
+}
+
+u8 CharInfo::GetEyebrowAspect() const {
+ return eyebrow_aspect;
+}
+
+u8 CharInfo::GetEyebrowRotate() const {
+ return eyebrow_rotate;
+}
+
+u8 CharInfo::GetEyebrowX() const {
+ return eyebrow_x;
+}
+
+u8 CharInfo::GetEyebrowY() const {
+ return eyebrow_y;
+}
+
+NoseType CharInfo::GetNoseType() const {
+ return nose_type;
+}
+
+u8 CharInfo::GetNoseScale() const {
+ return nose_scale;
+}
+
+u8 CharInfo::GetNoseY() const {
+ return nose_y;
+}
+
+MouthType CharInfo::GetMouthType() const {
+ return mouth_type;
+}
+
+CommonColor CharInfo::GetMouthColor() const {
+ return mouth_color;
+}
+
+u8 CharInfo::GetMouthScale() const {
+ return mouth_scale;
+}
+
+u8 CharInfo::GetMouthAspect() const {
+ return mouth_aspect;
+}
+
+u8 CharInfo::GetMouthY() const {
+ return mouth_y;
+}
+
+CommonColor CharInfo::GetBeardColor() const {
+ return beard_color;
+}
+
+BeardType CharInfo::GetBeardType() const {
+ return beard_type;
+}
+
+MustacheType CharInfo::GetMustacheType() const {
+ return mustache_type;
+}
+
+u8 CharInfo::GetMustacheScale() const {
+ return mustache_scale;
+}
+
+u8 CharInfo::GetMustacheY() const {
+ return mustache_y;
+}
+
+GlassType CharInfo::GetGlassType() const {
+ return glass_type;
+}
+
+CommonColor CharInfo::GetGlassColor() const {
+ return glass_color;
+}
+
+u8 CharInfo::GetGlassScale() const {
+ return glass_scale;
+}
+
+u8 CharInfo::GetGlassY() const {
+ return glass_y;
+}
+
+MoleType CharInfo::GetMoleType() const {
+ return mole_type;
+}
+
+u8 CharInfo::GetMoleScale() const {
+ return mole_scale;
+}
+
+u8 CharInfo::GetMoleX() const {
+ return mole_x;
+}
+
+u8 CharInfo::GetMoleY() const {
+ return mole_y;
+}
+
+bool CharInfo::operator==(const CharInfo& info) {
+ bool is_identical = info.Verify() == ValidationResult::NoErrors;
+ is_identical &= name.data == info.GetNickname().data;
+ is_identical &= create_id == info.GetCreateId();
+ is_identical &= font_region == info.GetFontRegion();
+ is_identical &= favorite_color == info.GetFavoriteColor();
+ is_identical &= gender == info.GetGender();
+ is_identical &= height == info.GetHeight();
+ is_identical &= build == info.GetBuild();
+ is_identical &= type == info.GetType();
+ is_identical &= region_move == info.GetRegionMove();
+ is_identical &= faceline_type == info.GetFacelineType();
+ is_identical &= faceline_color == info.GetFacelineColor();
+ is_identical &= faceline_wrinkle == info.GetFacelineWrinkle();
+ is_identical &= faceline_make == info.GetFacelineMake();
+ is_identical &= hair_type == info.GetHairType();
+ is_identical &= hair_color == info.GetHairColor();
+ is_identical &= hair_flip == info.GetHairFlip();
+ is_identical &= eye_type == info.GetEyeType();
+ is_identical &= eye_color == info.GetEyeColor();
+ is_identical &= eye_scale == info.GetEyeScale();
+ is_identical &= eye_aspect == info.GetEyeAspect();
+ is_identical &= eye_rotate == info.GetEyeRotate();
+ is_identical &= eye_x == info.GetEyeX();
+ is_identical &= eye_y == info.GetEyeY();
+ is_identical &= eyebrow_type == info.GetEyebrowType();
+ is_identical &= eyebrow_color == info.GetEyebrowColor();
+ is_identical &= eyebrow_scale == info.GetEyebrowScale();
+ is_identical &= eyebrow_aspect == info.GetEyebrowAspect();
+ is_identical &= eyebrow_rotate == info.GetEyebrowRotate();
+ is_identical &= eyebrow_x == info.GetEyebrowX();
+ is_identical &= eyebrow_y == info.GetEyebrowY();
+ is_identical &= nose_type == info.GetNoseType();
+ is_identical &= nose_scale == info.GetNoseScale();
+ is_identical &= nose_y == info.GetNoseY();
+ is_identical &= mouth_type == info.GetMouthType();
+ is_identical &= mouth_color == info.GetMouthColor();
+ is_identical &= mouth_scale == info.GetMouthScale();
+ is_identical &= mouth_aspect == info.GetMouthAspect();
+ is_identical &= mouth_y == info.GetMouthY();
+ is_identical &= beard_color == info.GetBeardColor();
+ is_identical &= beard_type == info.GetBeardType();
+ is_identical &= mustache_type == info.GetMustacheType();
+ is_identical &= mustache_scale == info.GetMustacheScale();
+ is_identical &= mustache_y == info.GetMustacheY();
+ is_identical &= glass_type == info.GetGlassType();
+ is_identical &= glass_color == info.GetGlassColor();
+ is_identical &= glass_scale == info.GetGlassScale();
+ is_identical &= glass_y == info.GetGlassY();
+ is_identical &= mole_type == info.GetMoleType();
+ is_identical &= mole_scale == info.GetMoleScale();
+ is_identical &= mole_x == info.GetMoleX();
+ is_identical &= mole_y == info.GetMoleY();
+ return is_identical;
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h
new file mode 100644
index 000000000..d0c457fd5
--- /dev/null
+++ b/src/core/hle/service/mii/types/char_info.h
@@ -0,0 +1,137 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii {
+class StoreData;
+
+// This is nn::mii::detail::CharInfoRaw
+class CharInfo {
+public:
+ void SetFromStoreData(const StoreData& store_data_raw);
+
+ ValidationResult Verify() const;
+
+ Common::UUID GetCreateId() const;
+ Nickname GetNickname() const;
+ FontRegion GetFontRegion() const;
+ FavoriteColor GetFavoriteColor() const;
+ Gender GetGender() const;
+ u8 GetHeight() const;
+ u8 GetBuild() const;
+ u8 GetType() const;
+ u8 GetRegionMove() const;
+ FacelineType GetFacelineType() const;
+ FacelineColor GetFacelineColor() const;
+ FacelineWrinkle GetFacelineWrinkle() const;
+ FacelineMake GetFacelineMake() const;
+ HairType GetHairType() const;
+ CommonColor GetHairColor() const;
+ HairFlip GetHairFlip() const;
+ EyeType GetEyeType() const;
+ CommonColor GetEyeColor() const;
+ u8 GetEyeScale() const;
+ u8 GetEyeAspect() const;
+ u8 GetEyeRotate() const;
+ u8 GetEyeX() const;
+ u8 GetEyeY() const;
+ EyebrowType GetEyebrowType() const;
+ CommonColor GetEyebrowColor() const;
+ u8 GetEyebrowScale() const;
+ u8 GetEyebrowAspect() const;
+ u8 GetEyebrowRotate() const;
+ u8 GetEyebrowX() const;
+ u8 GetEyebrowY() const;
+ NoseType GetNoseType() const;
+ u8 GetNoseScale() const;
+ u8 GetNoseY() const;
+ MouthType GetMouthType() const;
+ CommonColor GetMouthColor() const;
+ u8 GetMouthScale() const;
+ u8 GetMouthAspect() const;
+ u8 GetMouthY() const;
+ CommonColor GetBeardColor() const;
+ BeardType GetBeardType() const;
+ MustacheType GetMustacheType() const;
+ u8 GetMustacheScale() const;
+ u8 GetMustacheY() const;
+ GlassType GetGlassType() const;
+ CommonColor GetGlassColor() const;
+ u8 GetGlassScale() const;
+ u8 GetGlassY() const;
+ MoleType GetMoleType() const;
+ u8 GetMoleScale() const;
+ u8 GetMoleX() const;
+ u8 GetMoleY() const;
+
+ bool operator==(const CharInfo& info);
+
+private:
+ Common::UUID create_id{};
+ Nickname name{};
+ u16 null_terminator{};
+ FontRegion font_region{};
+ FavoriteColor favorite_color{};
+ Gender gender{};
+ u8 height{};
+ u8 build{};
+ u8 type{};
+ u8 region_move{};
+ FacelineType faceline_type{};
+ FacelineColor faceline_color{};
+ FacelineWrinkle faceline_wrinkle{};
+ FacelineMake faceline_make{};
+ HairType hair_type{};
+ CommonColor hair_color{};
+ HairFlip hair_flip{};
+ EyeType eye_type{};
+ CommonColor eye_color{};
+ u8 eye_scale{};
+ u8 eye_aspect{};
+ u8 eye_rotate{};
+ u8 eye_x{};
+ u8 eye_y{};
+ EyebrowType eyebrow_type{};
+ CommonColor eyebrow_color{};
+ u8 eyebrow_scale{};
+ u8 eyebrow_aspect{};
+ u8 eyebrow_rotate{};
+ u8 eyebrow_x{};
+ u8 eyebrow_y{};
+ NoseType nose_type{};
+ u8 nose_scale{};
+ u8 nose_y{};
+ MouthType mouth_type{};
+ CommonColor mouth_color{};
+ u8 mouth_scale{};
+ u8 mouth_aspect{};
+ u8 mouth_y{};
+ CommonColor beard_color{};
+ BeardType beard_type{};
+ MustacheType mustache_type{};
+ u8 mustache_scale{};
+ u8 mustache_y{};
+ GlassType glass_type{};
+ CommonColor glass_color{};
+ u8 glass_scale{};
+ u8 glass_y{};
+ MoleType mole_type{};
+ u8 mole_scale{};
+ u8 mole_x{};
+ u8 mole_y{};
+ u8 padding{};
+};
+static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
+static_assert(std::has_unique_object_representations_v<CharInfo>,
+ "All bits of CharInfo must contribute to its value.");
+
+struct CharInfoElement {
+ CharInfo char_info{};
+ Source source{};
+};
+static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size.");
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp
new file mode 100644
index 000000000..1068031e3
--- /dev/null
+++ b/src/core/hle/service/mii/types/core_data.cpp
@@ -0,0 +1,805 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/core_data.h"
+#include "core/hle/service/mii/types/raw_data.h"
+
+namespace Service::Mii {
+
+void CoreData::SetDefault() {
+ data = {};
+ name = GetDefaultNickname();
+}
+
+void CoreData::BuildRandom(Age age, Gender gender, Race race) {
+ if (gender == Gender::All) {
+ gender = MiiUtil::GetRandomValue(Gender::Max);
+ }
+
+ if (age == Age::All) {
+ const auto random{MiiUtil::GetRandomValue<int>(10)};
+ if (random >= 8) {
+ age = Age::Old;
+ } else if (random >= 4) {
+ age = Age::Normal;
+ } else {
+ age = Age::Young;
+ }
+ }
+
+ if (race == Race::All) {
+ const auto random{MiiUtil::GetRandomValue<int>(10)};
+ if (random >= 8) {
+ race = Race::Black;
+ } else if (random >= 4) {
+ race = Race::White;
+ } else {
+ race = Race::Asian;
+ }
+ }
+
+ SetGender(gender);
+ SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max));
+ SetRegionMove(0);
+ SetFontRegion(FontRegion::Standard);
+ SetType(0);
+ SetHeight(64);
+ SetBuild(64);
+
+ u32 axis_y{};
+ if (gender == Gender::Female && age == Age::Young) {
+ axis_y = MiiUtil::GetRandomValue<u32>(3);
+ }
+
+ const std::size_t index{3 * static_cast<std::size_t>(age) +
+ 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
+
+ const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)};
+ const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at(
+ 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
+ const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
+ const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
+ const auto& hair_type_info{RawData::RandomMiiHairType.at(index)};
+ const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
+ static_cast<std::size_t>(age))};
+ const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)};
+ const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
+ const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
+ const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)};
+ const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)};
+ const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
+
+ data.faceline_type.Assign(
+ faceline_type_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
+ data.faceline_color.Assign(
+ faceline_color_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
+ data.faceline_wrinkle.Assign(
+ faceline_wrinkle_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
+ data.faceline_makeup.Assign(
+ faceline_makeup_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
+
+ data.hair_type.Assign(
+ hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]);
+ SetHairColor(RawData::GetHairColorFromVer3(
+ hair_color_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)]));
+ SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max));
+
+ data.eye_type.Assign(
+ eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]);
+
+ const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
+ const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
+ const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
+ const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]};
+
+ SetEyeColor(RawData::GetEyeColorFromVer3(
+ eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)]));
+ SetEyeScale(4);
+ SetEyeAspect(3);
+ SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate));
+ SetEyeX(2);
+ SetEyeY(static_cast<u8>(axis_y + 12));
+
+ data.eyebrow_type.Assign(
+ eyebrow_type_info
+ .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
+
+ const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
+ const auto eyebrow_y{race == Race::Asian ? 9 : 10};
+ const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6};
+ const auto eyebrow_rotate{
+ 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]};
+
+ SetEyebrowColor(GetHairColor());
+ SetEyebrowScale(4);
+ SetEyebrowAspect(3);
+ SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate));
+ SetEyebrowX(2);
+ SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y));
+
+ data.nose_type.Assign(
+ nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]);
+ SetNoseScale(gender == Gender::Female ? 3 : 4);
+ SetNoseY(static_cast<u8>(axis_y + 9));
+
+ const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0};
+
+ data.mouth_type.Assign(
+ mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
+ SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color));
+ SetMouthScale(4);
+ SetMouthAspect(3);
+ SetMouthY(static_cast<u8>(axis_y + 13));
+
+ SetBeardColor(GetHairColor());
+ SetMustacheScale(4);
+
+ if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) {
+ const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)};
+
+ auto beard_type{BeardType::None};
+ auto mustache_type{MustacheType::None};
+
+ if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
+ BeardAndMustacheFlag::Beard) {
+ beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max);
+ }
+
+ if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
+ BeardAndMustacheFlag::Mustache) {
+ mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max);
+ }
+
+ SetMustacheType(mustache_type);
+ SetBeardType(beard_type);
+ SetMustacheY(10);
+ } else {
+ SetMustacheType(MustacheType::None);
+ SetBeardType(BeardType::None);
+ SetMustacheY(static_cast<u8>(axis_y + 10));
+ }
+
+ const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)};
+ u8 glasses_type{};
+ while (glasses_type_start < glasses_type_info.values[glasses_type]) {
+ if (++glasses_type >= glasses_type_info.values_count) {
+ glasses_type = 0;
+ break;
+ }
+ }
+
+ SetGlassType(static_cast<GlassType>(glasses_type));
+ SetGlassColor(RawData::GetGlassColorFromVer3(0));
+ SetGlassScale(4);
+ SetGlassY(static_cast<u8>(axis_y + 10));
+
+ SetMoleType(MoleType::None);
+ SetMoleScale(4);
+ SetMoleX(2);
+ SetMoleY(20);
+}
+
+void CoreData::BuildFromCharInfo(const CharInfo& char_info) {
+ name = char_info.GetNickname();
+ SetFontRegion(char_info.GetFontRegion());
+ SetFavoriteColor(char_info.GetFavoriteColor());
+ SetGender(char_info.GetGender());
+ SetHeight(char_info.GetHeight());
+ SetBuild(char_info.GetBuild());
+ SetType(char_info.GetType());
+ SetRegionMove(char_info.GetRegionMove());
+ SetFacelineType(char_info.GetFacelineType());
+ SetFacelineColor(char_info.GetFacelineColor());
+ SetFacelineWrinkle(char_info.GetFacelineWrinkle());
+ SetFacelineMake(char_info.GetFacelineMake());
+ SetHairType(char_info.GetHairType());
+ SetHairColor(char_info.GetHairColor());
+ SetHairFlip(char_info.GetHairFlip());
+ SetEyeType(char_info.GetEyeType());
+ SetEyeColor(char_info.GetEyeColor());
+ SetEyeScale(char_info.GetEyeScale());
+ SetEyeAspect(char_info.GetEyeAspect());
+ SetEyeRotate(char_info.GetEyeRotate());
+ SetEyeX(char_info.GetEyeX());
+ SetEyeY(char_info.GetEyeY());
+ SetEyebrowType(char_info.GetEyebrowType());
+ SetEyebrowColor(char_info.GetEyebrowColor());
+ SetEyebrowScale(char_info.GetEyebrowScale());
+ SetEyebrowAspect(char_info.GetEyebrowAspect());
+ SetEyebrowRotate(char_info.GetEyebrowRotate());
+ SetEyebrowX(char_info.GetEyebrowX());
+ SetEyebrowY(char_info.GetEyebrowY() - 3);
+ SetNoseType(char_info.GetNoseType());
+ SetNoseScale(char_info.GetNoseScale());
+ SetNoseY(char_info.GetNoseY());
+ SetMouthType(char_info.GetMouthType());
+ SetMouthColor(char_info.GetMouthColor());
+ SetMouthScale(char_info.GetMouthScale());
+ SetMouthAspect(char_info.GetMouthAspect());
+ SetMouthY(char_info.GetMouthY());
+ SetBeardColor(char_info.GetBeardColor());
+ SetBeardType(char_info.GetBeardType());
+ SetMustacheType(char_info.GetMustacheType());
+ SetMustacheScale(char_info.GetMustacheScale());
+ SetMustacheY(char_info.GetMustacheY());
+ SetGlassType(char_info.GetGlassType());
+ SetGlassColor(char_info.GetGlassColor());
+ SetGlassScale(char_info.GetGlassScale());
+ SetGlassY(char_info.GetGlassY());
+ SetMoleType(char_info.GetMoleType());
+ SetMoleScale(char_info.GetMoleScale());
+ SetMoleX(char_info.GetMoleX());
+ SetMoleY(char_info.GetMoleY());
+}
+
+ValidationResult CoreData::IsValid() const {
+ if (!name.IsValid()) {
+ return ValidationResult::InvalidName;
+ }
+ if (GetFontRegion() > FontRegion::Max) {
+ return ValidationResult::InvalidFont;
+ }
+ if (GetFavoriteColor() > FavoriteColor::Max) {
+ return ValidationResult::InvalidColor;
+ }
+ if (GetGender() > Gender::Max) {
+ return ValidationResult::InvalidGender;
+ }
+ if (GetHeight() > MaxHeight) {
+ return ValidationResult::InvalidHeight;
+ }
+ if (GetBuild() > MaxBuild) {
+ return ValidationResult::InvalidBuild;
+ }
+ if (GetType() > MaxType) {
+ return ValidationResult::InvalidType;
+ }
+ if (GetRegionMove() > MaxRegionMove) {
+ return ValidationResult::InvalidRegionMove;
+ }
+ if (GetFacelineType() > FacelineType::Max) {
+ return ValidationResult::InvalidFacelineType;
+ }
+ if (GetFacelineColor() > FacelineColor::Max) {
+ return ValidationResult::InvalidFacelineColor;
+ }
+ if (GetFacelineWrinkle() > FacelineWrinkle::Max) {
+ return ValidationResult::InvalidFacelineWrinkle;
+ }
+ if (GetFacelineMake() > FacelineMake::Max) {
+ return ValidationResult::InvalidFacelineMake;
+ }
+ if (GetHairType() > HairType::Max) {
+ return ValidationResult::InvalidHairType;
+ }
+ if (GetHairColor() > CommonColor::Max) {
+ return ValidationResult::InvalidHairColor;
+ }
+ if (GetHairFlip() > HairFlip::Max) {
+ return ValidationResult::InvalidHairFlip;
+ }
+ if (GetEyeType() > EyeType::Max) {
+ return ValidationResult::InvalidEyeType;
+ }
+ if (GetEyeColor() > CommonColor::Max) {
+ return ValidationResult::InvalidEyeColor;
+ }
+ if (GetEyeScale() > MaxEyeScale) {
+ return ValidationResult::InvalidEyeScale;
+ }
+ if (GetEyeAspect() > MaxEyeAspect) {
+ return ValidationResult::InvalidEyeAspect;
+ }
+ if (GetEyeRotate() > MaxEyeRotate) {
+ return ValidationResult::InvalidEyeRotate;
+ }
+ if (GetEyeX() > MaxEyeX) {
+ return ValidationResult::InvalidEyeX;
+ }
+ if (GetEyeY() > MaxEyeY) {
+ return ValidationResult::InvalidEyeY;
+ }
+ if (GetEyebrowType() > EyebrowType::Max) {
+ return ValidationResult::InvalidEyebrowType;
+ }
+ if (GetEyebrowColor() > CommonColor::Max) {
+ return ValidationResult::InvalidEyebrowColor;
+ }
+ if (GetEyebrowScale() > MaxEyebrowScale) {
+ return ValidationResult::InvalidEyebrowScale;
+ }
+ if (GetEyebrowAspect() > MaxEyebrowAspect) {
+ return ValidationResult::InvalidEyebrowAspect;
+ }
+ if (GetEyebrowRotate() > MaxEyebrowRotate) {
+ return ValidationResult::InvalidEyebrowRotate;
+ }
+ if (GetEyebrowX() > MaxEyebrowX) {
+ return ValidationResult::InvalidEyebrowX;
+ }
+ if (GetEyebrowY() > MaxEyebrowY) {
+ return ValidationResult::InvalidEyebrowY;
+ }
+ if (GetNoseType() > NoseType::Max) {
+ return ValidationResult::InvalidNoseType;
+ }
+ if (GetNoseScale() > MaxNoseScale) {
+ return ValidationResult::InvalidNoseScale;
+ }
+ if (GetNoseY() > MaxNoseY) {
+ return ValidationResult::InvalidNoseY;
+ }
+ if (GetMouthType() > MouthType::Max) {
+ return ValidationResult::InvalidMouthType;
+ }
+ if (GetMouthColor() > CommonColor::Max) {
+ return ValidationResult::InvalidMouthColor;
+ }
+ if (GetMouthScale() > MaxMouthScale) {
+ return ValidationResult::InvalidMouthScale;
+ }
+ if (GetMouthAspect() > MaxMoutAspect) {
+ return ValidationResult::InvalidMouthAspect;
+ }
+ if (GetMouthY() > MaxMouthY) {
+ return ValidationResult::InvalidMouthY;
+ }
+ if (GetBeardColor() > CommonColor::Max) {
+ return ValidationResult::InvalidBeardColor;
+ }
+ if (GetBeardType() > BeardType::Max) {
+ return ValidationResult::InvalidBeardType;
+ }
+ if (GetMustacheType() > MustacheType::Max) {
+ return ValidationResult::InvalidMustacheType;
+ }
+ if (GetMustacheScale() > MaxMustacheScale) {
+ return ValidationResult::InvalidMustacheScale;
+ }
+ if (GetMustacheY() > MaxMustacheY) {
+ return ValidationResult::InvalidMustacheY;
+ }
+ if (GetGlassType() > GlassType::Max) {
+ return ValidationResult::InvalidGlassType;
+ }
+ if (GetGlassColor() > CommonColor::Max) {
+ return ValidationResult::InvalidGlassColor;
+ }
+ if (GetGlassScale() > MaxGlassScale) {
+ return ValidationResult::InvalidGlassScale;
+ }
+ if (GetGlassY() > MaxGlassY) {
+ return ValidationResult::InvalidGlassY;
+ }
+ if (GetMoleType() > MoleType::Max) {
+ return ValidationResult::InvalidMoleType;
+ }
+ if (GetMoleScale() > MaxMoleScale) {
+ return ValidationResult::InvalidMoleScale;
+ }
+ if (GetMoleX() > MaxMoleX) {
+ return ValidationResult::InvalidMoleX;
+ }
+ if (GetMoleY() > MaxMoleY) {
+ return ValidationResult::InvalidMoleY;
+ }
+ return ValidationResult::NoErrors;
+}
+
+void CoreData::SetFontRegion(FontRegion value) {
+ data.font_region.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetFavoriteColor(FavoriteColor value) {
+ data.favorite_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetGender(Gender value) {
+ data.gender.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetHeight(u8 value) {
+ data.height.Assign(value);
+}
+
+void CoreData::SetBuild(u8 value) {
+ data.build.Assign(value);
+}
+
+void CoreData::SetType(u8 value) {
+ data.type.Assign(value);
+}
+
+void CoreData::SetRegionMove(u8 value) {
+ data.region_move.Assign(value);
+}
+
+void CoreData::SetFacelineType(FacelineType value) {
+ data.faceline_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetFacelineColor(FacelineColor value) {
+ data.faceline_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetFacelineWrinkle(FacelineWrinkle value) {
+ data.faceline_wrinkle.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetFacelineMake(FacelineMake value) {
+ data.faceline_makeup.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetHairType(HairType value) {
+ data.hair_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetHairColor(CommonColor value) {
+ data.hair_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetHairFlip(HairFlip value) {
+ data.hair_flip.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyeType(EyeType value) {
+ data.eye_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyeColor(CommonColor value) {
+ data.eye_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyeScale(u8 value) {
+ data.eye_scale.Assign(value);
+}
+
+void CoreData::SetEyeAspect(u8 value) {
+ data.eye_aspect.Assign(value);
+}
+
+void CoreData::SetEyeRotate(u8 value) {
+ data.eye_rotate.Assign(value);
+}
+
+void CoreData::SetEyeX(u8 value) {
+ data.eye_x.Assign(value);
+}
+
+void CoreData::SetEyeY(u8 value) {
+ data.eye_y.Assign(value);
+}
+
+void CoreData::SetEyebrowType(EyebrowType value) {
+ data.eyebrow_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyebrowColor(CommonColor value) {
+ data.eyebrow_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetEyebrowScale(u8 value) {
+ data.eyebrow_scale.Assign(value);
+}
+
+void CoreData::SetEyebrowAspect(u8 value) {
+ data.eyebrow_aspect.Assign(value);
+}
+
+void CoreData::SetEyebrowRotate(u8 value) {
+ data.eyebrow_rotate.Assign(value);
+}
+
+void CoreData::SetEyebrowX(u8 value) {
+ data.eyebrow_x.Assign(value);
+}
+
+void CoreData::SetEyebrowY(u8 value) {
+ data.eyebrow_y.Assign(value);
+}
+
+void CoreData::SetNoseType(NoseType value) {
+ data.nose_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetNoseScale(u8 value) {
+ data.nose_scale.Assign(value);
+}
+
+void CoreData::SetNoseY(u8 value) {
+ data.nose_y.Assign(value);
+}
+
+void CoreData::SetMouthType(MouthType value) {
+ data.mouth_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMouthColor(CommonColor value) {
+ data.mouth_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMouthScale(u8 value) {
+ data.mouth_scale.Assign(value);
+}
+
+void CoreData::SetMouthAspect(u8 value) {
+ data.mouth_aspect.Assign(value);
+}
+
+void CoreData::SetMouthY(u8 value) {
+ data.mouth_y.Assign(value);
+}
+
+void CoreData::SetBeardColor(CommonColor value) {
+ data.beard_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetBeardType(BeardType value) {
+ data.beard_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMustacheType(MustacheType value) {
+ data.mustache_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMustacheScale(u8 value) {
+ data.mustache_scale.Assign(value);
+}
+
+void CoreData::SetMustacheY(u8 value) {
+ data.mustache_y.Assign(value);
+}
+
+void CoreData::SetGlassType(GlassType value) {
+ data.glasses_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetGlassColor(CommonColor value) {
+ data.glasses_color.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetGlassScale(u8 value) {
+ data.glasses_scale.Assign(value);
+}
+
+void CoreData::SetGlassY(u8 value) {
+ data.glasses_y.Assign(value);
+}
+
+void CoreData::SetMoleType(MoleType value) {
+ data.mole_type.Assign(static_cast<u32>(value));
+}
+
+void CoreData::SetMoleScale(u8 value) {
+ data.mole_scale.Assign(value);
+}
+
+void CoreData::SetMoleX(u8 value) {
+ data.mole_x.Assign(value);
+}
+
+void CoreData::SetMoleY(u8 value) {
+ data.mole_y.Assign(value);
+}
+
+void CoreData::SetNickname(Nickname nickname) {
+ name = nickname;
+}
+
+FontRegion CoreData::GetFontRegion() const {
+ return static_cast<FontRegion>(data.font_region.Value());
+}
+
+FavoriteColor CoreData::GetFavoriteColor() const {
+ return static_cast<FavoriteColor>(data.favorite_color.Value());
+}
+
+Gender CoreData::GetGender() const {
+ return static_cast<Gender>(data.gender.Value());
+}
+
+u8 CoreData::GetHeight() const {
+ return static_cast<u8>(data.height.Value());
+}
+
+u8 CoreData::GetBuild() const {
+ return static_cast<u8>(data.build.Value());
+}
+
+u8 CoreData::GetType() const {
+ return static_cast<u8>(data.type.Value());
+}
+
+u8 CoreData::GetRegionMove() const {
+ return static_cast<u8>(data.region_move.Value());
+}
+
+FacelineType CoreData::GetFacelineType() const {
+ return static_cast<FacelineType>(data.faceline_type.Value());
+}
+
+FacelineColor CoreData::GetFacelineColor() const {
+ return static_cast<FacelineColor>(data.faceline_color.Value());
+}
+
+FacelineWrinkle CoreData::GetFacelineWrinkle() const {
+ return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value());
+}
+
+FacelineMake CoreData::GetFacelineMake() const {
+ return static_cast<FacelineMake>(data.faceline_makeup.Value());
+}
+
+HairType CoreData::GetHairType() const {
+ return static_cast<HairType>(data.hair_type.Value());
+}
+
+CommonColor CoreData::GetHairColor() const {
+ return static_cast<CommonColor>(data.hair_color.Value());
+}
+
+HairFlip CoreData::GetHairFlip() const {
+ return static_cast<HairFlip>(data.hair_flip.Value());
+}
+
+EyeType CoreData::GetEyeType() const {
+ return static_cast<EyeType>(data.eye_type.Value());
+}
+
+CommonColor CoreData::GetEyeColor() const {
+ return static_cast<CommonColor>(data.eye_color.Value());
+}
+
+u8 CoreData::GetEyeScale() const {
+ return static_cast<u8>(data.eye_scale.Value());
+}
+
+u8 CoreData::GetEyeAspect() const {
+ return static_cast<u8>(data.eye_aspect.Value());
+}
+
+u8 CoreData::GetEyeRotate() const {
+ return static_cast<u8>(data.eye_rotate.Value());
+}
+
+u8 CoreData::GetEyeX() const {
+ return static_cast<u8>(data.eye_x.Value());
+}
+
+u8 CoreData::GetEyeY() const {
+ return static_cast<u8>(data.eye_y.Value());
+}
+
+EyebrowType CoreData::GetEyebrowType() const {
+ return static_cast<EyebrowType>(data.eyebrow_type.Value());
+}
+
+CommonColor CoreData::GetEyebrowColor() const {
+ return static_cast<CommonColor>(data.eyebrow_color.Value());
+}
+
+u8 CoreData::GetEyebrowScale() const {
+ return static_cast<u8>(data.eyebrow_scale.Value());
+}
+
+u8 CoreData::GetEyebrowAspect() const {
+ return static_cast<u8>(data.eyebrow_aspect.Value());
+}
+
+u8 CoreData::GetEyebrowRotate() const {
+ return static_cast<u8>(data.eyebrow_rotate.Value());
+}
+
+u8 CoreData::GetEyebrowX() const {
+ return static_cast<u8>(data.eyebrow_x.Value());
+}
+
+u8 CoreData::GetEyebrowY() const {
+ return static_cast<u8>(data.eyebrow_y.Value());
+}
+
+NoseType CoreData::GetNoseType() const {
+ return static_cast<NoseType>(data.nose_type.Value());
+}
+
+u8 CoreData::GetNoseScale() const {
+ return static_cast<u8>(data.nose_scale.Value());
+}
+
+u8 CoreData::GetNoseY() const {
+ return static_cast<u8>(data.nose_y.Value());
+}
+
+MouthType CoreData::GetMouthType() const {
+ return static_cast<MouthType>(data.mouth_type.Value());
+}
+
+CommonColor CoreData::GetMouthColor() const {
+ return static_cast<CommonColor>(data.mouth_color.Value());
+}
+
+u8 CoreData::GetMouthScale() const {
+ return static_cast<u8>(data.mouth_scale.Value());
+}
+
+u8 CoreData::GetMouthAspect() const {
+ return static_cast<u8>(data.mouth_aspect.Value());
+}
+
+u8 CoreData::GetMouthY() const {
+ return static_cast<u8>(data.mouth_y.Value());
+}
+
+CommonColor CoreData::GetBeardColor() const {
+ return static_cast<CommonColor>(data.beard_color.Value());
+}
+
+BeardType CoreData::GetBeardType() const {
+ return static_cast<BeardType>(data.beard_type.Value());
+}
+
+MustacheType CoreData::GetMustacheType() const {
+ return static_cast<MustacheType>(data.mustache_type.Value());
+}
+
+u8 CoreData::GetMustacheScale() const {
+ return static_cast<u8>(data.mustache_scale.Value());
+}
+
+u8 CoreData::GetMustacheY() const {
+ return static_cast<u8>(data.mustache_y.Value());
+}
+
+GlassType CoreData::GetGlassType() const {
+ return static_cast<GlassType>(data.glasses_type.Value());
+}
+
+CommonColor CoreData::GetGlassColor() const {
+ return static_cast<CommonColor>(data.glasses_color.Value());
+}
+
+u8 CoreData::GetGlassScale() const {
+ return static_cast<u8>(data.glasses_scale.Value());
+}
+
+u8 CoreData::GetGlassY() const {
+ return static_cast<u8>(data.glasses_y.Value());
+}
+
+MoleType CoreData::GetMoleType() const {
+ return static_cast<MoleType>(data.mole_type.Value());
+}
+
+u8 CoreData::GetMoleScale() const {
+ return static_cast<u8>(data.mole_scale.Value());
+}
+
+u8 CoreData::GetMoleX() const {
+ return static_cast<u8>(data.mole_x.Value());
+}
+
+u8 CoreData::GetMoleY() const {
+ return static_cast<u8>(data.mole_y.Value());
+}
+
+Nickname CoreData::GetNickname() const {
+ return name;
+}
+
+Nickname CoreData::GetDefaultNickname() const {
+ return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'};
+}
+
+Nickname CoreData::GetInvalidNickname() const {
+ return {u'?', u'?', u'?'};
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h
new file mode 100644
index 000000000..8897e4f3b
--- /dev/null
+++ b/src/core/hle/service/mii/types/core_data.h
@@ -0,0 +1,219 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii {
+class CharInfo;
+
+struct StoreDataBitFields {
+ union {
+ u32 word_0{};
+
+ BitField<0, 8, u32> hair_type;
+ BitField<8, 7, u32> height;
+ BitField<15, 1, u32> mole_type;
+ BitField<16, 7, u32> build;
+ BitField<23, 1, u32> hair_flip;
+ BitField<24, 7, u32> hair_color;
+ BitField<31, 1, u32> type;
+ };
+
+ union {
+ u32 word_1{};
+
+ BitField<0, 7, u32> eye_color;
+ BitField<7, 1, u32> gender;
+ BitField<8, 7, u32> eyebrow_color;
+ BitField<16, 7, u32> mouth_color;
+ BitField<24, 7, u32> beard_color;
+ };
+
+ union {
+ u32 word_2{};
+
+ BitField<0, 7, u32> glasses_color;
+ BitField<8, 6, u32> eye_type;
+ BitField<14, 2, u32> region_move;
+ BitField<16, 6, u32> mouth_type;
+ BitField<22, 2, u32> font_region;
+ BitField<24, 5, u32> eye_y;
+ BitField<29, 3, u32> glasses_scale;
+ };
+
+ union {
+ u32 word_3{};
+
+ BitField<0, 5, u32> eyebrow_type;
+ BitField<5, 3, u32> mustache_type;
+ BitField<8, 5, u32> nose_type;
+ BitField<13, 3, u32> beard_type;
+ BitField<16, 5, u32> nose_y;
+ BitField<21, 3, u32> mouth_aspect;
+ BitField<24, 5, u32> mouth_y;
+ BitField<29, 3, u32> eyebrow_aspect;
+ };
+
+ union {
+ u32 word_4{};
+
+ BitField<0, 5, u32> mustache_y;
+ BitField<5, 3, u32> eye_rotate;
+ BitField<8, 5, u32> glasses_y;
+ BitField<13, 3, u32> eye_aspect;
+ BitField<16, 5, u32> mole_x;
+ BitField<21, 3, u32> eye_scale;
+ BitField<24, 5, u32> mole_y;
+ };
+
+ union {
+ u32 word_5{};
+
+ BitField<0, 5, u32> glasses_type;
+ BitField<8, 4, u32> favorite_color;
+ BitField<12, 4, u32> faceline_type;
+ BitField<16, 4, u32> faceline_color;
+ BitField<20, 4, u32> faceline_wrinkle;
+ BitField<24, 4, u32> faceline_makeup;
+ BitField<28, 4, u32> eye_x;
+ };
+
+ union {
+ u32 word_6{};
+
+ BitField<0, 4, u32> eyebrow_scale;
+ BitField<4, 4, u32> eyebrow_rotate;
+ BitField<8, 4, u32> eyebrow_x;
+ BitField<12, 4, u32> eyebrow_y;
+ BitField<16, 4, u32> nose_scale;
+ BitField<20, 4, u32> mouth_scale;
+ BitField<24, 4, u32> mustache_scale;
+ BitField<28, 4, u32> mole_scale;
+ };
+};
+static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size.");
+static_assert(std::is_trivially_copyable_v<StoreDataBitFields>,
+ "StoreDataBitFields is not trivially copyable.");
+
+class CoreData {
+public:
+ void SetDefault();
+ void BuildRandom(Age age, Gender gender, Race race);
+ void BuildFromCharInfo(const CharInfo& char_info);
+
+ ValidationResult IsValid() const;
+
+ void SetFontRegion(FontRegion value);
+ void SetFavoriteColor(FavoriteColor value);
+ void SetGender(Gender value);
+ void SetHeight(u8 value);
+ void SetBuild(u8 value);
+ void SetType(u8 value);
+ void SetRegionMove(u8 value);
+ void SetFacelineType(FacelineType value);
+ void SetFacelineColor(FacelineColor value);
+ void SetFacelineWrinkle(FacelineWrinkle value);
+ void SetFacelineMake(FacelineMake value);
+ void SetHairType(HairType value);
+ void SetHairColor(CommonColor value);
+ void SetHairFlip(HairFlip value);
+ void SetEyeType(EyeType value);
+ void SetEyeColor(CommonColor value);
+ void SetEyeScale(u8 value);
+ void SetEyeAspect(u8 value);
+ void SetEyeRotate(u8 value);
+ void SetEyeX(u8 value);
+ void SetEyeY(u8 value);
+ void SetEyebrowType(EyebrowType value);
+ void SetEyebrowColor(CommonColor value);
+ void SetEyebrowScale(u8 value);
+ void SetEyebrowAspect(u8 value);
+ void SetEyebrowRotate(u8 value);
+ void SetEyebrowX(u8 value);
+ void SetEyebrowY(u8 value);
+ void SetNoseType(NoseType value);
+ void SetNoseScale(u8 value);
+ void SetNoseY(u8 value);
+ void SetMouthType(MouthType value);
+ void SetMouthColor(CommonColor value);
+ void SetMouthScale(u8 value);
+ void SetMouthAspect(u8 value);
+ void SetMouthY(u8 value);
+ void SetBeardColor(CommonColor value);
+ void SetBeardType(BeardType value);
+ void SetMustacheType(MustacheType value);
+ void SetMustacheScale(u8 value);
+ void SetMustacheY(u8 value);
+ void SetGlassType(GlassType value);
+ void SetGlassColor(CommonColor value);
+ void SetGlassScale(u8 value);
+ void SetGlassY(u8 value);
+ void SetMoleType(MoleType value);
+ void SetMoleScale(u8 value);
+ void SetMoleX(u8 value);
+ void SetMoleY(u8 value);
+ void SetNickname(Nickname nickname);
+
+ FontRegion GetFontRegion() const;
+ FavoriteColor GetFavoriteColor() const;
+ Gender GetGender() const;
+ u8 GetHeight() const;
+ u8 GetBuild() const;
+ u8 GetType() const;
+ u8 GetRegionMove() const;
+ FacelineType GetFacelineType() const;
+ FacelineColor GetFacelineColor() const;
+ FacelineWrinkle GetFacelineWrinkle() const;
+ FacelineMake GetFacelineMake() const;
+ HairType GetHairType() const;
+ CommonColor GetHairColor() const;
+ HairFlip GetHairFlip() const;
+ EyeType GetEyeType() const;
+ CommonColor GetEyeColor() const;
+ u8 GetEyeScale() const;
+ u8 GetEyeAspect() const;
+ u8 GetEyeRotate() const;
+ u8 GetEyeX() const;
+ u8 GetEyeY() const;
+ EyebrowType GetEyebrowType() const;
+ CommonColor GetEyebrowColor() const;
+ u8 GetEyebrowScale() const;
+ u8 GetEyebrowAspect() const;
+ u8 GetEyebrowRotate() const;
+ u8 GetEyebrowX() const;
+ u8 GetEyebrowY() const;
+ NoseType GetNoseType() const;
+ u8 GetNoseScale() const;
+ u8 GetNoseY() const;
+ MouthType GetMouthType() const;
+ CommonColor GetMouthColor() const;
+ u8 GetMouthScale() const;
+ u8 GetMouthAspect() const;
+ u8 GetMouthY() const;
+ CommonColor GetBeardColor() const;
+ BeardType GetBeardType() const;
+ MustacheType GetMustacheType() const;
+ u8 GetMustacheScale() const;
+ u8 GetMustacheY() const;
+ GlassType GetGlassType() const;
+ CommonColor GetGlassColor() const;
+ u8 GetGlassScale() const;
+ u8 GetGlassY() const;
+ MoleType GetMoleType() const;
+ u8 GetMoleScale() const;
+ u8 GetMoleX() const;
+ u8 GetMoleY() const;
+ Nickname GetNickname() const;
+ Nickname GetDefaultNickname() const;
+ Nickname GetInvalidNickname() const;
+
+private:
+ StoreDataBitFields data{};
+ Nickname name{};
+};
+static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
+static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable.");
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp
index 1442280c8..0e1a07fd7 100644
--- a/src/core/hle/service/mii/raw_data.cpp
+++ b/src/core/hle/service/mii/types/raw_data.cpp
@@ -1,11 +1,88 @@
// SPDX-FileCopyrightText: Ryujinx Team and Contributors
// SPDX-License-Identifier: MIT
-#include "core/hle/service/mii/raw_data.h"
+#include "core/hle/service/mii/types/raw_data.h"
namespace Service::Mii::RawData {
-const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
+constexpr std::array<u8, static_cast<u8>(FacelineColor::Count)> FromVer3FacelineColorTable{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
+};
+
+constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3HairColorTable{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0,
+ 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7,
+ 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4,
+};
+
+constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3EyeColorTable{
+ 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4,
+ 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
+ 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2,
+ 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
+};
+
+constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3MouthlineColorTable{
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
+ 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
+ 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
+ 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
+ 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3,
+ 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3,
+};
+
+constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3GlassColorTable{
+ 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3,
+ 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
+ 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5,
+ 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
+};
+
+constexpr std::array<u8, static_cast<u8>(GlassType::Count)> FromVer3GlassTypeTable{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1,
+ 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7,
+};
+
+constexpr std::array<u8, 8> Ver3FacelineColorTable{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
+};
+
+constexpr std::array<u8, 8> Ver3HairColorTable{
+ 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+};
+
+constexpr std::array<u8, 6> Ver3EyeColorTable{
+ 0x8, 0x9, 0xa, 0xb, 0xc, 0xd,
+};
+
+constexpr std::array<u8, 5> Ver3MouthColorTable{
+ 0x13, 0x14, 0x15, 0x16, 0x17,
+};
+
+constexpr std::array<u8, 7> Ver3GlassColorTable{
+ 0x8, 0xe, 0xf, 0x10, 0x11, 0x12, 0x0,
+};
+
+const std::array<u8, 62> EyeRotateLookup{
+ 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
+ 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
+ 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
+};
+
+const std::array<u8, 24> EyebrowRotateLookup{
+ 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08,
+ 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05,
+};
+
+const std::array<Service::Mii::DefaultMii, 2> BaseMii{
Service::Mii::DefaultMii{
.face_type = 0,
.face_color = 0,
@@ -51,11 +128,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Male,
+ .gender = 0,
.favorite_color = 0,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -102,12 +180,16 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Female,
+ .gender = 1,
.favorite_color = 0,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
+};
+
+const std::array<Service::Mii::DefaultMii, 6> DefaultMii{
Service::Mii::DefaultMii{
.face_type = 0,
.face_color = 4,
@@ -153,11 +235,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Male,
+ .gender = 0,
.favorite_color = 4,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -204,11 +287,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Male,
+ .gender = 0,
.favorite_color = 5,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -255,11 +339,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Male,
+ .gender = 0,
.favorite_color = 0,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -306,11 +391,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Female,
+ .gender = 1,
.favorite_color = 2,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -357,11 +443,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Female,
+ .gender = 1,
.favorite_color = 6,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
Service::Mii::DefaultMii{
.face_type = 0,
@@ -408,176 +495,177 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
.mole_y = 20,
.height = 64,
.weight = 64,
- .gender = Gender::Female,
+ .gender = 1,
.favorite_color = 7,
- .region = 0,
- .font_region = FontRegion::Standard,
+ .region_move = 0,
+ .font_region = 0,
.type = 0,
+ .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiFaceline{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 13,
.values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 13,
.values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 10,
.values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
},
};
-const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{
- Service::Mii::RandomMiiData3{
+const std::array<RandomMiiData3, 6> RandomMiiFacelineColor{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 0,
.values_count = 10,
.values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 1,
.values_count = 10,
.values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 2,
.values_count = 10,
.values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 0,
.values_count = 10,
.values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 1,
.values_count = 10,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 2,
.values_count = 10,
@@ -585,407 +673,407 @@ const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiHairType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 30,
.values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45,
47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 31,
.values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47,
48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 31,
.values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47,
48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 38,
.values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50,
51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 39,
.values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 39,
.values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 18,
.values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 19,
.values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 19,
.values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 39,
.values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 42,
.values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50,
61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 42,
.values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50,
61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 44,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50,
58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 44,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58,
60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 44,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58,
60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 24,
.values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14,
16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 27,
.values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17,
18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 27,
.values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17,
18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85},
@@ -993,55 +1081,55 @@ const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{
};
const std::array<RandomMiiData3, 9> RandomMiiHairColor{
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 0,
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 1,
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 0,
.arg_2 = 2,
.values_count = 20,
.values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 0,
.values_count = 20,
.values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 1,
.values_count = 20,
.values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 1,
.arg_2 = 2,
.values_count = 20,
.values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 2,
.arg_2 = 0,
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 2,
.arg_2 = 1,
.values_count = 20,
.values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3},
},
- Service::Mii::RandomMiiData3{
+ RandomMiiData3{
.arg_1 = 2,
.arg_2 = 2,
.values_count = 20,
@@ -1049,598 +1137,642 @@ const std::array<RandomMiiData3, 9> RandomMiiHairColor{
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiEyeType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 26,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27,
29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 26,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27,
29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 27,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27,
29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 35,
.values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29,
31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 35,
.values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29,
31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 35,
.values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29,
31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 30,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21,
22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 30,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21,
22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 30,
.values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22,
26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 39,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27,
28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 39,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27,
28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 40,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26,
27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 46,
.values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37,
38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 46,
.values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37,
38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 46,
.values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18,
19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37,
38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 34,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23,
24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 34,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23,
24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 35,
.values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24,
25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
},
};
-const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{
- Service::Mii::RandomMiiData2{
+const std::array<RandomMiiData2, 3> RandomMiiEyeColor{
+ RandomMiiData2{
.arg_1 = 0,
.values_count = 10,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
},
- Service::Mii::RandomMiiData2{
+ RandomMiiData2{
.arg_1 = 1,
.values_count = 10,
.values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5},
},
- Service::Mii::RandomMiiData2{
+ RandomMiiData2{
.arg_1 = 2,
.values_count = 10,
.values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiEyebrowType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 23,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 23,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 23,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 21,
.values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 21,
.values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 21,
.values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 9,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 9,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 9,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 11,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 11,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 11,
.values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 9,
.values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 9,
.values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 9,
.values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiNoseType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 11,
.values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 11,
.values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 11,
.values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 15,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 18,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 15,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 8,
.values = {0, 1, 3, 4, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 8,
.values = {0, 1, 3, 4, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 8,
.values = {0, 1, 3, 4, 8, 10, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 12,
.values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 11,
.values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 10,
.values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 12,
.values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 11,
.values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 10,
.values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14},
},
};
-const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Black,
+const std::array<RandomMiiData4, 18> RandomMiiMouthType{
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 25,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18,
19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 27,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17,
18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 28,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17,
18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 24,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 26,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 26,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 24,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 26,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Male,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Male),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 26,
.values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Black),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15,
17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Black),
.values_count = 26,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Black,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Black),
.values_count = 26,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::White),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15,
17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::White),
.values_count = 26,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::White,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::White),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Young,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Young),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 24,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14,
15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Normal,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Normal),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
},
- Service::Mii::RandomMiiData4{
- .gender = Gender::Female,
- .age = Age::Old,
- .race = Race::Asian,
+ RandomMiiData4{
+ .gender = static_cast<u32>(Gender::Female),
+ .age = static_cast<u32>(Age::Old),
+ .race = static_cast<u32>(Race::Asian),
.values_count = 25,
.values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
},
};
-const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{
- Service::Mii::RandomMiiData2{
+const std::array<RandomMiiData2, 3> RandomMiiGlassType{
+ RandomMiiData2{
.arg_1 = 0,
- .values_count = 9,
- .values = {90, 94, 96, 100, 0, 0, 0, 0, 0},
+ .values_count = 4,
+ .values = {90, 94, 96, 100},
},
- Service::Mii::RandomMiiData2{
+ RandomMiiData2{
.arg_1 = 1,
- .values_count = 9,
- .values = {83, 86, 90, 93, 94, 96, 98, 100, 0},
+ .values_count = 8,
+ .values = {83, 86, 90, 93, 94, 96, 98, 100},
},
- Service::Mii::RandomMiiData2{
+ RandomMiiData2{
.arg_1 = 2,
- .values_count = 9,
- .values = {78, 83, 0, 93, 0, 0, 98, 100, 0},
+ .values_count = 8,
+ .values = {78, 83, 0, 93, 0, 0, 98, 100},
},
};
+u8 FromVer3GetFacelineColor(u8 color) {
+ return FromVer3FacelineColorTable[color];
+}
+
+u8 FromVer3GetHairColor(u8 color) {
+ return FromVer3HairColorTable[color];
+}
+
+u8 FromVer3GetEyeColor(u8 color) {
+ return FromVer3EyeColorTable[color];
+}
+
+u8 FromVer3GetMouthlineColor(u8 color) {
+ return FromVer3MouthlineColorTable[color];
+}
+
+u8 FromVer3GetGlassColor(u8 color) {
+ return FromVer3GlassColorTable[color];
+}
+
+u8 FromVer3GetGlassType(u8 type) {
+ return FromVer3GlassTypeTable[type];
+}
+
+FacelineColor GetFacelineColorFromVer3(u32 color) {
+ return static_cast<FacelineColor>(Ver3FacelineColorTable[color]);
+}
+
+CommonColor GetHairColorFromVer3(u32 color) {
+ return static_cast<CommonColor>(Ver3HairColorTable[color]);
+}
+
+CommonColor GetEyeColorFromVer3(u32 color) {
+ return static_cast<CommonColor>(Ver3EyeColorTable[color]);
+}
+
+CommonColor GetMouthColorFromVer3(u32 color) {
+ return static_cast<CommonColor>(Ver3MouthColorTable[color]);
+}
+
+CommonColor GetGlassColorFromVer3(u32 color) {
+ return static_cast<CommonColor>(Ver3GlassColorTable[color]);
+}
+
} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/types/raw_data.h b/src/core/hle/service/mii/types/raw_data.h
new file mode 100644
index 000000000..9a4cfa738
--- /dev/null
+++ b/src/core/hle/service/mii/types/raw_data.h
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii::RawData {
+
+struct RandomMiiValues {
+ std::array<u8, 188> values{};
+};
+static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
+
+struct RandomMiiData4 {
+ u32 gender{};
+ u32 age{};
+ u32 race{};
+ u32 values_count{};
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
+
+struct RandomMiiData3 {
+ u32 arg_1;
+ u32 arg_2;
+ u32 values_count;
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
+
+struct RandomMiiData2 {
+ u32 arg_1;
+ u32 values_count;
+ std::array<u32, 47> values{};
+};
+static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
+
+extern const std::array<Service::Mii::DefaultMii, 2> BaseMii;
+extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii;
+
+extern const std::array<u8, 62> EyeRotateLookup;
+extern const std::array<u8, 24> EyebrowRotateLookup;
+
+extern const std::array<RandomMiiData4, 18> RandomMiiFaceline;
+extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor;
+extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle;
+extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup;
+extern const std::array<RandomMiiData4, 18> RandomMiiHairType;
+extern const std::array<RandomMiiData3, 9> RandomMiiHairColor;
+extern const std::array<RandomMiiData4, 18> RandomMiiEyeType;
+extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor;
+extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType;
+extern const std::array<RandomMiiData4, 18> RandomMiiNoseType;
+extern const std::array<RandomMiiData4, 18> RandomMiiMouthType;
+extern const std::array<RandomMiiData2, 3> RandomMiiGlassType;
+
+u8 FromVer3GetFacelineColor(u8 color);
+u8 FromVer3GetHairColor(u8 color);
+u8 FromVer3GetEyeColor(u8 color);
+u8 FromVer3GetMouthlineColor(u8 color);
+u8 FromVer3GetGlassColor(u8 color);
+u8 FromVer3GetGlassType(u8 type);
+
+FacelineColor GetFacelineColorFromVer3(u32 color);
+CommonColor GetHairColorFromVer3(u32 color);
+CommonColor GetEyeColorFromVer3(u32 color);
+CommonColor GetMouthColorFromVer3(u32 color);
+CommonColor GetGlassColorFromVer3(u32 color);
+
+} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp
new file mode 100644
index 000000000..127221fdb
--- /dev/null
+++ b/src/core/hle/service/mii/types/store_data.cpp
@@ -0,0 +1,676 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/mii/mii_result.h"
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/raw_data.h"
+#include "core/hle/service/mii/types/store_data.h"
+
+namespace Service::Mii {
+
+void StoreData::BuildDefault(u32 mii_index) {
+ const auto& default_mii = RawData::DefaultMii[mii_index];
+ core_data.SetDefault();
+
+ core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
+ core_data.SetFacelineColor(
+ RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
+ core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
+ core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
+
+ core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
+ core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
+ core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
+ core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
+ core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
+ core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
+ core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
+ core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
+ core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
+ core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
+
+ core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
+ core_data.SetEyebrowColor(
+ RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
+ core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
+ core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
+ core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
+ core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
+ core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
+
+ core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
+ core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
+ core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
+
+ core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
+ core_data.SetMouthColor(
+ RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
+ core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
+ core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
+ core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
+
+ core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
+ core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
+ core_data.SetBeardColor(
+ RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
+ core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
+ core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
+
+ core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
+ core_data.SetGlassColor(
+ RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
+ core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
+ core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
+
+ core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
+ core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
+ core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
+ core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
+
+ core_data.SetHeight(static_cast<u8>(default_mii.height));
+ core_data.SetBuild(static_cast<u8>(default_mii.weight));
+ core_data.SetGender(static_cast<Gender>(default_mii.gender));
+ core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
+ core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
+ core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
+ core_data.SetType(static_cast<u8>(default_mii.type));
+ core_data.SetNickname(default_mii.nickname);
+
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+void StoreData::BuildBase(Gender gender) {
+ const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0];
+ core_data.SetDefault();
+
+ core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
+ core_data.SetFacelineColor(
+ RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
+ core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
+ core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
+
+ core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
+ core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
+ core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
+ core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
+ core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
+ core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
+ core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
+ core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
+ core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
+ core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
+
+ core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
+ core_data.SetEyebrowColor(
+ RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
+ core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
+ core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
+ core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
+ core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
+ core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
+
+ core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
+ core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
+ core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
+
+ core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type));
+ core_data.SetMouthColor(
+ RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
+ core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
+ core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
+ core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
+
+ core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
+ core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
+ core_data.SetBeardColor(
+ RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
+ core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
+ core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
+
+ core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
+ core_data.SetGlassColor(
+ RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
+ core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
+ core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
+
+ core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
+ core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
+ core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
+ core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
+
+ core_data.SetHeight(static_cast<u8>(default_mii.height));
+ core_data.SetBuild(static_cast<u8>(default_mii.weight));
+ core_data.SetGender(static_cast<Gender>(default_mii.gender));
+ core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
+ core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
+ core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
+ core_data.SetType(static_cast<u8>(default_mii.type));
+ core_data.SetNickname(default_mii.nickname);
+
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+void StoreData::BuildRandom(Age age, Gender gender, Race race) {
+ core_data.BuildRandom(age, gender, race);
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+void StoreData::BuildWithCharInfo(const CharInfo& char_info) {
+ core_data.BuildFromCharInfo(char_info);
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+void StoreData::BuildWithCoreData(const CoreData& in_core_data) {
+ core_data = in_core_data;
+ create_id = MiiUtil::MakeCreateId();
+ SetChecksum();
+}
+
+Result StoreData::Restore() {
+ // TODO: Implement this
+ return ResultNotUpdated;
+}
+
+ValidationResult StoreData::IsValid() const {
+ if (core_data.IsValid() != ValidationResult::NoErrors) {
+ return core_data.IsValid();
+ }
+ if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) {
+ return ValidationResult::InvalidChecksum;
+ }
+ const auto device_id = MiiUtil::GetDeviceId();
+ if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) {
+ return ValidationResult::InvalidChecksum;
+ }
+ return ValidationResult::NoErrors;
+}
+
+bool StoreData::IsSpecial() const {
+ return GetType() == 1;
+}
+
+void StoreData::SetFontRegion(FontRegion value) {
+ core_data.SetFontRegion(value);
+}
+
+void StoreData::SetFavoriteColor(FavoriteColor value) {
+ core_data.SetFavoriteColor(value);
+}
+
+void StoreData::SetGender(Gender value) {
+ core_data.SetGender(value);
+}
+
+void StoreData::SetHeight(u8 value) {
+ core_data.SetHeight(value);
+}
+
+void StoreData::SetBuild(u8 value) {
+ core_data.SetBuild(value);
+}
+
+void StoreData::SetType(u8 value) {
+ core_data.SetType(value);
+}
+
+void StoreData::SetRegionMove(u8 value) {
+ core_data.SetRegionMove(value);
+}
+
+void StoreData::SetFacelineType(FacelineType value) {
+ core_data.SetFacelineType(value);
+}
+
+void StoreData::SetFacelineColor(FacelineColor value) {
+ core_data.SetFacelineColor(value);
+}
+
+void StoreData::SetFacelineWrinkle(FacelineWrinkle value) {
+ core_data.SetFacelineWrinkle(value);
+}
+
+void StoreData::SetFacelineMake(FacelineMake value) {
+ core_data.SetFacelineMake(value);
+}
+
+void StoreData::SetHairType(HairType value) {
+ core_data.SetHairType(value);
+}
+
+void StoreData::SetHairColor(CommonColor value) {
+ core_data.SetHairColor(value);
+}
+
+void StoreData::SetHairFlip(HairFlip value) {
+ core_data.SetHairFlip(value);
+}
+
+void StoreData::SetEyeType(EyeType value) {
+ core_data.SetEyeType(value);
+}
+
+void StoreData::SetEyeColor(CommonColor value) {
+ core_data.SetEyeColor(value);
+}
+
+void StoreData::SetEyeScale(u8 value) {
+ core_data.SetEyeScale(value);
+}
+
+void StoreData::SetEyeAspect(u8 value) {
+ core_data.SetEyeAspect(value);
+}
+
+void StoreData::SetEyeRotate(u8 value) {
+ core_data.SetEyeRotate(value);
+}
+
+void StoreData::SetEyeX(u8 value) {
+ core_data.SetEyeX(value);
+}
+
+void StoreData::SetEyeY(u8 value) {
+ core_data.SetEyeY(value);
+}
+
+void StoreData::SetEyebrowType(EyebrowType value) {
+ core_data.SetEyebrowType(value);
+}
+
+void StoreData::SetEyebrowColor(CommonColor value) {
+ core_data.SetEyebrowColor(value);
+}
+
+void StoreData::SetEyebrowScale(u8 value) {
+ core_data.SetEyebrowScale(value);
+}
+
+void StoreData::SetEyebrowAspect(u8 value) {
+ core_data.SetEyebrowAspect(value);
+}
+
+void StoreData::SetEyebrowRotate(u8 value) {
+ core_data.SetEyebrowRotate(value);
+}
+
+void StoreData::SetEyebrowX(u8 value) {
+ core_data.SetEyebrowX(value);
+}
+
+void StoreData::SetEyebrowY(u8 value) {
+ core_data.SetEyebrowY(value);
+}
+
+void StoreData::SetNoseType(NoseType value) {
+ core_data.SetNoseType(value);
+}
+
+void StoreData::SetNoseScale(u8 value) {
+ core_data.SetNoseScale(value);
+}
+
+void StoreData::SetNoseY(u8 value) {
+ core_data.SetNoseY(value);
+}
+
+void StoreData::SetMouthType(MouthType value) {
+ core_data.SetMouthType(value);
+}
+
+void StoreData::SetMouthColor(CommonColor value) {
+ core_data.SetMouthColor(value);
+}
+
+void StoreData::SetMouthScale(u8 value) {
+ core_data.SetMouthScale(value);
+}
+
+void StoreData::SetMouthAspect(u8 value) {
+ core_data.SetMouthAspect(value);
+}
+
+void StoreData::SetMouthY(u8 value) {
+ core_data.SetMouthY(value);
+}
+
+void StoreData::SetBeardColor(CommonColor value) {
+ core_data.SetBeardColor(value);
+}
+
+void StoreData::SetBeardType(BeardType value) {
+ core_data.SetBeardType(value);
+}
+
+void StoreData::SetMustacheType(MustacheType value) {
+ core_data.SetMustacheType(value);
+}
+
+void StoreData::SetMustacheScale(u8 value) {
+ core_data.SetMustacheScale(value);
+}
+
+void StoreData::SetMustacheY(u8 value) {
+ core_data.SetMustacheY(value);
+}
+
+void StoreData::SetGlassType(GlassType value) {
+ core_data.SetGlassType(value);
+}
+
+void StoreData::SetGlassColor(CommonColor value) {
+ core_data.SetGlassColor(value);
+}
+
+void StoreData::SetGlassScale(u8 value) {
+ core_data.SetGlassScale(value);
+}
+
+void StoreData::SetGlassY(u8 value) {
+ core_data.SetGlassY(value);
+}
+
+void StoreData::SetMoleType(MoleType value) {
+ core_data.SetMoleType(value);
+}
+
+void StoreData::SetMoleScale(u8 value) {
+ core_data.SetMoleScale(value);
+}
+
+void StoreData::SetMoleX(u8 value) {
+ core_data.SetMoleX(value);
+}
+
+void StoreData::SetMoleY(u8 value) {
+ core_data.SetMoleY(value);
+}
+
+void StoreData::SetNickname(Nickname value) {
+ core_data.SetNickname(value);
+}
+
+void StoreData::SetInvalidName() {
+ const auto& invalid_name = core_data.GetInvalidNickname();
+ core_data.SetNickname(invalid_name);
+ SetChecksum();
+}
+
+void StoreData::SetChecksum() {
+ SetDataChecksum();
+ SetDeviceChecksum();
+}
+
+void StoreData::SetDataChecksum() {
+ data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID));
+}
+
+void StoreData::SetDeviceChecksum() {
+ const auto device_id = MiiUtil::GetDeviceId();
+ device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData));
+}
+
+Common::UUID StoreData::GetCreateId() const {
+ return create_id;
+}
+
+FontRegion StoreData::GetFontRegion() const {
+ return static_cast<FontRegion>(core_data.GetFontRegion());
+}
+
+FavoriteColor StoreData::GetFavoriteColor() const {
+ return core_data.GetFavoriteColor();
+}
+
+Gender StoreData::GetGender() const {
+ return core_data.GetGender();
+}
+
+u8 StoreData::GetHeight() const {
+ return core_data.GetHeight();
+}
+
+u8 StoreData::GetBuild() const {
+ return core_data.GetBuild();
+}
+
+u8 StoreData::GetType() const {
+ return core_data.GetType();
+}
+
+u8 StoreData::GetRegionMove() const {
+ return core_data.GetRegionMove();
+}
+
+FacelineType StoreData::GetFacelineType() const {
+ return core_data.GetFacelineType();
+}
+
+FacelineColor StoreData::GetFacelineColor() const {
+ return core_data.GetFacelineColor();
+}
+
+FacelineWrinkle StoreData::GetFacelineWrinkle() const {
+ return core_data.GetFacelineWrinkle();
+}
+
+FacelineMake StoreData::GetFacelineMake() const {
+ return core_data.GetFacelineMake();
+}
+
+HairType StoreData::GetHairType() const {
+ return core_data.GetHairType();
+}
+
+CommonColor StoreData::GetHairColor() const {
+ return core_data.GetHairColor();
+}
+
+HairFlip StoreData::GetHairFlip() const {
+ return core_data.GetHairFlip();
+}
+
+EyeType StoreData::GetEyeType() const {
+ return core_data.GetEyeType();
+}
+
+CommonColor StoreData::GetEyeColor() const {
+ return core_data.GetEyeColor();
+}
+
+u8 StoreData::GetEyeScale() const {
+ return core_data.GetEyeScale();
+}
+
+u8 StoreData::GetEyeAspect() const {
+ return core_data.GetEyeAspect();
+}
+
+u8 StoreData::GetEyeRotate() const {
+ return core_data.GetEyeRotate();
+}
+
+u8 StoreData::GetEyeX() const {
+ return core_data.GetEyeX();
+}
+
+u8 StoreData::GetEyeY() const {
+ return core_data.GetEyeY();
+}
+
+EyebrowType StoreData::GetEyebrowType() const {
+ return core_data.GetEyebrowType();
+}
+
+CommonColor StoreData::GetEyebrowColor() const {
+ return core_data.GetEyebrowColor();
+}
+
+u8 StoreData::GetEyebrowScale() const {
+ return core_data.GetEyebrowScale();
+}
+
+u8 StoreData::GetEyebrowAspect() const {
+ return core_data.GetEyebrowAspect();
+}
+
+u8 StoreData::GetEyebrowRotate() const {
+ return core_data.GetEyebrowRotate();
+}
+
+u8 StoreData::GetEyebrowX() const {
+ return core_data.GetEyebrowX();
+}
+
+u8 StoreData::GetEyebrowY() const {
+ return core_data.GetEyebrowY();
+}
+
+NoseType StoreData::GetNoseType() const {
+ return core_data.GetNoseType();
+}
+
+u8 StoreData::GetNoseScale() const {
+ return core_data.GetNoseScale();
+}
+
+u8 StoreData::GetNoseY() const {
+ return core_data.GetNoseY();
+}
+
+MouthType StoreData::GetMouthType() const {
+ return core_data.GetMouthType();
+}
+
+CommonColor StoreData::GetMouthColor() const {
+ return core_data.GetMouthColor();
+}
+
+u8 StoreData::GetMouthScale() const {
+ return core_data.GetMouthScale();
+}
+
+u8 StoreData::GetMouthAspect() const {
+ return core_data.GetMouthAspect();
+}
+
+u8 StoreData::GetMouthY() const {
+ return core_data.GetMouthY();
+}
+
+CommonColor StoreData::GetBeardColor() const {
+ return core_data.GetBeardColor();
+}
+
+BeardType StoreData::GetBeardType() const {
+ return core_data.GetBeardType();
+}
+
+MustacheType StoreData::GetMustacheType() const {
+ return core_data.GetMustacheType();
+}
+
+u8 StoreData::GetMustacheScale() const {
+ return core_data.GetMustacheScale();
+}
+
+u8 StoreData::GetMustacheY() const {
+ return core_data.GetMustacheY();
+}
+
+GlassType StoreData::GetGlassType() const {
+ return core_data.GetGlassType();
+}
+
+CommonColor StoreData::GetGlassColor() const {
+ return core_data.GetGlassColor();
+}
+
+u8 StoreData::GetGlassScale() const {
+ return core_data.GetGlassScale();
+}
+
+u8 StoreData::GetGlassY() const {
+ return core_data.GetGlassY();
+}
+
+MoleType StoreData::GetMoleType() const {
+ return core_data.GetMoleType();
+}
+
+u8 StoreData::GetMoleScale() const {
+ return core_data.GetMoleScale();
+}
+
+u8 StoreData::GetMoleX() const {
+ return core_data.GetMoleX();
+}
+
+u8 StoreData::GetMoleY() const {
+ return core_data.GetMoleY();
+}
+
+Nickname StoreData::GetNickname() const {
+ return core_data.GetNickname();
+}
+
+bool StoreData::operator==(const StoreData& data) {
+ bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors;
+ is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
+ is_identical &= GetCreateId() == data.GetCreateId();
+ is_identical &= GetFontRegion() == data.GetFontRegion();
+ is_identical &= GetFavoriteColor() == data.GetFavoriteColor();
+ is_identical &= GetGender() == data.GetGender();
+ is_identical &= GetHeight() == data.GetHeight();
+ is_identical &= GetBuild() == data.GetBuild();
+ is_identical &= GetType() == data.GetType();
+ is_identical &= GetRegionMove() == data.GetRegionMove();
+ is_identical &= GetFacelineType() == data.GetFacelineType();
+ is_identical &= GetFacelineColor() == data.GetFacelineColor();
+ is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle();
+ is_identical &= GetFacelineMake() == data.GetFacelineMake();
+ is_identical &= GetHairType() == data.GetHairType();
+ is_identical &= GetHairColor() == data.GetHairColor();
+ is_identical &= GetHairFlip() == data.GetHairFlip();
+ is_identical &= GetEyeType() == data.GetEyeType();
+ is_identical &= GetEyeColor() == data.GetEyeColor();
+ is_identical &= GetEyeScale() == data.GetEyeScale();
+ is_identical &= GetEyeAspect() == data.GetEyeAspect();
+ is_identical &= GetEyeRotate() == data.GetEyeRotate();
+ is_identical &= GetEyeX() == data.GetEyeX();
+ is_identical &= GetEyeY() == data.GetEyeY();
+ is_identical &= GetEyebrowType() == data.GetEyebrowType();
+ is_identical &= GetEyebrowColor() == data.GetEyebrowColor();
+ is_identical &= GetEyebrowScale() == data.GetEyebrowScale();
+ is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect();
+ is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate();
+ is_identical &= GetEyebrowX() == data.GetEyebrowX();
+ is_identical &= GetEyebrowY() == data.GetEyebrowY();
+ is_identical &= GetNoseType() == data.GetNoseType();
+ is_identical &= GetNoseScale() == data.GetNoseScale();
+ is_identical &= GetNoseY() == data.GetNoseY();
+ is_identical &= GetMouthType() == data.GetMouthType();
+ is_identical &= GetMouthColor() == data.GetMouthColor();
+ is_identical &= GetMouthScale() == data.GetMouthScale();
+ is_identical &= GetMouthAspect() == data.GetMouthAspect();
+ is_identical &= GetMouthY() == data.GetMouthY();
+ is_identical &= GetBeardColor() == data.GetBeardColor();
+ is_identical &= GetBeardType() == data.GetBeardType();
+ is_identical &= GetMustacheType() == data.GetMustacheType();
+ is_identical &= GetMustacheScale() == data.GetMustacheScale();
+ is_identical &= GetMustacheY() == data.GetMustacheY();
+ is_identical &= GetGlassType() == data.GetGlassType();
+ is_identical &= GetGlassColor() == data.GetGlassColor();
+ is_identical &= GetGlassScale() == data.GetGlassScale();
+ is_identical &= GetGlassY() == data.GetGlassY();
+ is_identical &= GetMoleType() == data.GetMoleType();
+ is_identical &= GetMoleScale() == data.GetMoleScale();
+ is_identical &= GetMoleX() == data.GetMoleX();
+ is_identical &= data.GetMoleY() == data.GetMoleY();
+ return is_identical;
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h
new file mode 100644
index 000000000..ed5dfb949
--- /dev/null
+++ b/src/core/hle/service/mii/types/store_data.h
@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+#include "core/hle/service/mii/mii_types.h"
+#include "core/hle/service/mii/types/core_data.h"
+
+namespace Service::Mii {
+
+class StoreData {
+public:
+ void BuildDefault(u32 mii_index);
+ void BuildBase(Gender gender);
+ void BuildRandom(Age age, Gender gender, Race race);
+ void BuildWithCharInfo(const CharInfo& char_info);
+ void BuildWithCoreData(const CoreData& in_core_data);
+ Result Restore();
+
+ ValidationResult IsValid() const;
+
+ bool IsSpecial() const;
+
+ void SetFontRegion(FontRegion value);
+ void SetFavoriteColor(FavoriteColor value);
+ void SetGender(Gender value);
+ void SetHeight(u8 value);
+ void SetBuild(u8 value);
+ void SetType(u8 value);
+ void SetRegionMove(u8 value);
+ void SetFacelineType(FacelineType value);
+ void SetFacelineColor(FacelineColor value);
+ void SetFacelineWrinkle(FacelineWrinkle value);
+ void SetFacelineMake(FacelineMake value);
+ void SetHairType(HairType value);
+ void SetHairColor(CommonColor value);
+ void SetHairFlip(HairFlip value);
+ void SetEyeType(EyeType value);
+ void SetEyeColor(CommonColor value);
+ void SetEyeScale(u8 value);
+ void SetEyeAspect(u8 value);
+ void SetEyeRotate(u8 value);
+ void SetEyeX(u8 value);
+ void SetEyeY(u8 value);
+ void SetEyebrowType(EyebrowType value);
+ void SetEyebrowColor(CommonColor value);
+ void SetEyebrowScale(u8 value);
+ void SetEyebrowAspect(u8 value);
+ void SetEyebrowRotate(u8 value);
+ void SetEyebrowX(u8 value);
+ void SetEyebrowY(u8 value);
+ void SetNoseType(NoseType value);
+ void SetNoseScale(u8 value);
+ void SetNoseY(u8 value);
+ void SetMouthType(MouthType value);
+ void SetMouthColor(CommonColor value);
+ void SetMouthScale(u8 value);
+ void SetMouthAspect(u8 value);
+ void SetMouthY(u8 value);
+ void SetBeardColor(CommonColor value);
+ void SetBeardType(BeardType value);
+ void SetMustacheType(MustacheType value);
+ void SetMustacheScale(u8 value);
+ void SetMustacheY(u8 value);
+ void SetGlassType(GlassType value);
+ void SetGlassColor(CommonColor value);
+ void SetGlassScale(u8 value);
+ void SetGlassY(u8 value);
+ void SetMoleType(MoleType value);
+ void SetMoleScale(u8 value);
+ void SetMoleX(u8 value);
+ void SetMoleY(u8 value);
+ void SetNickname(Nickname nickname);
+ void SetInvalidName();
+ void SetChecksum();
+ void SetDataChecksum();
+ void SetDeviceChecksum();
+
+ Common::UUID GetCreateId() const;
+ FontRegion GetFontRegion() const;
+ FavoriteColor GetFavoriteColor() const;
+ Gender GetGender() const;
+ u8 GetHeight() const;
+ u8 GetBuild() const;
+ u8 GetType() const;
+ u8 GetRegionMove() const;
+ FacelineType GetFacelineType() const;
+ FacelineColor GetFacelineColor() const;
+ FacelineWrinkle GetFacelineWrinkle() const;
+ FacelineMake GetFacelineMake() const;
+ HairType GetHairType() const;
+ CommonColor GetHairColor() const;
+ HairFlip GetHairFlip() const;
+ EyeType GetEyeType() const;
+ CommonColor GetEyeColor() const;
+ u8 GetEyeScale() const;
+ u8 GetEyeAspect() const;
+ u8 GetEyeRotate() const;
+ u8 GetEyeX() const;
+ u8 GetEyeY() const;
+ EyebrowType GetEyebrowType() const;
+ CommonColor GetEyebrowColor() const;
+ u8 GetEyebrowScale() const;
+ u8 GetEyebrowAspect() const;
+ u8 GetEyebrowRotate() const;
+ u8 GetEyebrowX() const;
+ u8 GetEyebrowY() const;
+ NoseType GetNoseType() const;
+ u8 GetNoseScale() const;
+ u8 GetNoseY() const;
+ MouthType GetMouthType() const;
+ CommonColor GetMouthColor() const;
+ u8 GetMouthScale() const;
+ u8 GetMouthAspect() const;
+ u8 GetMouthY() const;
+ CommonColor GetBeardColor() const;
+ BeardType GetBeardType() const;
+ MustacheType GetMustacheType() const;
+ u8 GetMustacheScale() const;
+ u8 GetMustacheY() const;
+ GlassType GetGlassType() const;
+ CommonColor GetGlassColor() const;
+ u8 GetGlassScale() const;
+ u8 GetGlassY() const;
+ MoleType GetMoleType() const;
+ u8 GetMoleScale() const;
+ u8 GetMoleX() const;
+ u8 GetMoleY() const;
+ Nickname GetNickname() const;
+
+ bool operator==(const StoreData& data);
+
+private:
+ CoreData core_data{};
+ Common::UUID create_id{};
+ u16 data_crc{};
+ u16 device_crc{};
+};
+static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
+static_assert(std::is_trivially_copyable_v<StoreData>,
+ "StoreData type must be trivially copyable.");
+
+struct StoreDataElement {
+ StoreData store_data{};
+ Source source{};
+};
+static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size.");
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp
new file mode 100644
index 000000000..a019cc9f7
--- /dev/null
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -0,0 +1,241 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/mii/mii_util.h"
+#include "core/hle/service/mii/types/raw_data.h"
+#include "core/hle/service/mii/types/store_data.h"
+#include "core/hle/service/mii/types/ver3_store_data.h"
+
+namespace Service::Mii {
+
+void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
+ faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf;
+ hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f;
+ eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f;
+ eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f;
+ mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f;
+ beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f;
+ glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f;
+ glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f;
+}
+
+void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
+ out_store_data.BuildBase(Gender::Male);
+
+ out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value()));
+ out_store_data.SetFavoriteColor(
+ static_cast<FavoriteColor>(mii_information.favorite_color.Value()));
+ out_store_data.SetHeight(height);
+ out_store_data.SetBuild(build);
+
+ out_store_data.SetNickname(mii_name);
+ out_store_data.SetFontRegion(
+ static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value())));
+
+ out_store_data.SetFacelineType(
+ static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
+ out_store_data.SetFacelineColor(
+ RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value()));
+ out_store_data.SetFacelineWrinkle(
+ static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value()));
+ out_store_data.SetFacelineMake(
+ static_cast<FacelineMake>(appearance_bits2.faceline_make.Value()));
+
+ out_store_data.SetHairType(static_cast<HairType>(hair_type));
+ out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value()));
+ out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value()));
+
+ out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value()));
+ out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value()));
+ out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value()));
+ out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value()));
+ out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value()));
+ out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value()));
+ out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value()));
+
+ out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value()));
+ out_store_data.SetEyebrowColor(
+ RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value()));
+ out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value()));
+ out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value()));
+ out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value()));
+ out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value()));
+ out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3));
+
+ out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value()));
+ out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value()));
+ out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value()));
+
+ out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value()));
+ out_store_data.SetMouthColor(
+ RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value()));
+ out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value()));
+ out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value()));
+ out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value()));
+
+ out_store_data.SetMustacheType(
+ static_cast<MustacheType>(appearance_bits8.mustache_type.Value()));
+ out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value()));
+ out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value()));
+
+ out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value()));
+ out_store_data.SetBeardColor(
+ RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value()));
+
+ // Glass type is compatible as it is. It doesn't need a table
+ out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value()));
+ out_store_data.SetGlassColor(
+ RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value()));
+ out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value()));
+ out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value()));
+
+ out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value()));
+ out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value()));
+ out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value()));
+ out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value()));
+
+ out_store_data.SetChecksum();
+}
+
+void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
+ version = 1;
+ mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
+ mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
+ height = store_data.GetHeight();
+ build = store_data.GetBuild();
+
+ mii_name = store_data.GetNickname();
+ region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion()));
+
+ appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType()));
+ appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle()));
+ appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake()));
+
+ hair_type = static_cast<u8>(store_data.GetHairType());
+ appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip()));
+
+ appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType()));
+ appearance_bits4.eye_scale.Assign(store_data.GetEyeScale());
+ appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect());
+ appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate());
+ appearance_bits4.eye_x.Assign(store_data.GetEyeX());
+ appearance_bits4.eye_y.Assign(store_data.GetEyeY());
+
+ appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType()));
+ appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale());
+ appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect());
+ appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate());
+ appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX());
+ appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY());
+
+ appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType()));
+ appearance_bits6.nose_scale.Assign(store_data.GetNoseScale());
+ appearance_bits6.nose_y.Assign(store_data.GetNoseY());
+
+ appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType()));
+ appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale());
+ appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect());
+ appearance_bits8.mouth_y.Assign(store_data.GetMouthY());
+
+ appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType()));
+ appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale());
+ appearance_bits9.mustache_y.Assign(store_data.GetMustacheY());
+
+ appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType()));
+
+ appearance_bits10.glass_scale.Assign(store_data.GetGlassScale());
+ appearance_bits10.glass_y.Assign(store_data.GetGlassY());
+
+ appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType()));
+ appearance_bits11.mole_scale.Assign(store_data.GetMoleScale());
+ appearance_bits11.mole_x.Assign(store_data.GetMoleX());
+ appearance_bits11.mole_y.Assign(store_data.GetMoleY());
+
+ // These types are converted to V3 from a table
+ appearance_bits1.faceline_color.Assign(
+ RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor())));
+ appearance_bits3.hair_color.Assign(
+ RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor())));
+ appearance_bits4.eye_color.Assign(
+ RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor())));
+ appearance_bits5.eyebrow_color.Assign(
+ RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor())));
+ appearance_bits7.mouth_color.Assign(
+ RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor())));
+ appearance_bits9.beard_color.Assign(
+ RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor())));
+ appearance_bits10.glass_color.Assign(
+ RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor())));
+ appearance_bits10.glass_type.Assign(
+ RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType())));
+
+ crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16));
+}
+
+u32 Ver3StoreData::IsValid() const {
+ bool is_valid = version == 0 || version == 3;
+
+ is_valid = is_valid && (mii_name.data[0] != '\0');
+
+ is_valid = is_valid && (mii_information.birth_month < 13);
+ is_valid = is_valid && (mii_information.birth_day < 32);
+ is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max));
+ is_valid = is_valid && (height <= MaxHeight);
+ is_valid = is_valid && (build <= MaxBuild);
+
+ is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max));
+ is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2);
+ is_valid =
+ is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max));
+ is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max));
+
+ is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max));
+ is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor);
+
+ is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max));
+ is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2);
+ is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale);
+ is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect);
+ is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate);
+ is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX);
+ is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY);
+
+ is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max));
+ is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor);
+ is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale);
+ is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect);
+ is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate);
+ is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX);
+ is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY);
+
+ is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max));
+ is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale);
+ is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY);
+
+ is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max));
+ is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3);
+ is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale);
+ is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect);
+ is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY);
+
+ is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max));
+ is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale);
+ is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY);
+
+ is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
+ is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
+
+ is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType);
+ is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2);
+ is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale);
+ is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY);
+
+ is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
+ is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);
+ is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX);
+ is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY);
+
+ return is_valid;
+}
+
+} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/types/ver3_store_data.h b/src/core/hle/service/mii/types/ver3_store_data.h
new file mode 100644
index 000000000..47907bf7d
--- /dev/null
+++ b/src/core/hle/service/mii/types/ver3_store_data.h
@@ -0,0 +1,160 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/mii/mii_types.h"
+
+namespace Service::Mii {
+class StoreData;
+
+// This is nn::mii::Ver3StoreData
+// Based on citra HLE::Applets::MiiData and PretendoNetwork.
+// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
+// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
+
+struct NfpStoreDataExtension {
+ void SetFromStoreData(const StoreData& store_data);
+
+ u8 faceline_color;
+ u8 hair_color;
+ u8 eye_color;
+ u8 eyebrow_color;
+ u8 mouth_color;
+ u8 beard_color;
+ u8 glass_color;
+ u8 glass_type;
+};
+static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
+
+#pragma pack(push, 4)
+class Ver3StoreData {
+public:
+ void BuildToStoreData(StoreData& out_store_data) const;
+ void BuildFromStoreData(const StoreData& store_data);
+
+ u32 IsValid() const;
+
+ u8 version;
+ union {
+ u8 raw;
+
+ BitField<0, 1, u8> allow_copying;
+ BitField<1, 1, u8> profanity_flag;
+ BitField<2, 2, u8> region_lock;
+ BitField<4, 2, u8> font_region;
+ } region_information;
+ u16_be mii_id;
+ u64_be system_id;
+ u32_be specialness_and_creation_date;
+ std::array<u8, 6> creator_mac;
+ u16_be padding;
+ union {
+ u16 raw;
+
+ BitField<0, 1, u16> gender;
+ BitField<1, 4, u16> birth_month;
+ BitField<5, 5, u16> birth_day;
+ BitField<10, 4, u16> favorite_color;
+ BitField<14, 1, u16> favorite;
+ } mii_information;
+ Nickname mii_name;
+ u8 height;
+ u8 build;
+ union {
+ u8 raw;
+
+ BitField<0, 1, u8> disable_sharing;
+ BitField<1, 4, u8> faceline_type;
+ BitField<5, 3, u8> faceline_color;
+ } appearance_bits1;
+ union {
+ u8 raw;
+
+ BitField<0, 4, u8> faceline_wrinkle;
+ BitField<4, 4, u8> faceline_make;
+ } appearance_bits2;
+ u8 hair_type;
+ union {
+ u8 raw;
+
+ BitField<0, 3, u8> hair_color;
+ BitField<3, 1, u8> hair_flip;
+ } appearance_bits3;
+ union {
+ u32 raw;
+
+ BitField<0, 6, u32> eye_type;
+ BitField<6, 3, u32> eye_color;
+ BitField<9, 4, u32> eye_scale;
+ BitField<13, 3, u32> eye_aspect;
+ BitField<16, 5, u32> eye_rotate;
+ BitField<21, 4, u32> eye_x;
+ BitField<25, 5, u32> eye_y;
+ } appearance_bits4;
+ union {
+ u32 raw;
+
+ BitField<0, 5, u32> eyebrow_type;
+ BitField<5, 3, u32> eyebrow_color;
+ BitField<8, 4, u32> eyebrow_scale;
+ BitField<12, 3, u32> eyebrow_aspect;
+ BitField<16, 4, u32> eyebrow_rotate;
+ BitField<21, 4, u32> eyebrow_x;
+ BitField<25, 5, u32> eyebrow_y;
+ } appearance_bits5;
+ union {
+ u16 raw;
+
+ BitField<0, 5, u16> nose_type;
+ BitField<5, 4, u16> nose_scale;
+ BitField<9, 5, u16> nose_y;
+ } appearance_bits6;
+ union {
+ u16 raw;
+
+ BitField<0, 6, u16> mouth_type;
+ BitField<6, 3, u16> mouth_color;
+ BitField<9, 4, u16> mouth_scale;
+ BitField<13, 3, u16> mouth_aspect;
+ } appearance_bits7;
+ union {
+ u8 raw;
+
+ BitField<0, 5, u8> mouth_y;
+ BitField<5, 3, u8> mustache_type;
+ } appearance_bits8;
+ u8 allow_copying;
+ union {
+ u16 raw;
+
+ BitField<0, 3, u16> beard_type;
+ BitField<3, 3, u16> beard_color;
+ BitField<6, 4, u16> mustache_scale;
+ BitField<10, 5, u16> mustache_y;
+ } appearance_bits9;
+ union {
+ u16 raw;
+
+ BitField<0, 4, u16> glass_type;
+ BitField<4, 3, u16> glass_color;
+ BitField<7, 4, u16> glass_scale;
+ BitField<11, 5, u16> glass_y;
+ } appearance_bits10;
+ union {
+ u16 raw;
+
+ BitField<0, 1, u16> mole_type;
+ BitField<1, 4, u16> mole_scale;
+ BitField<5, 5, u16> mole_x;
+ BitField<10, 5, u16> mole_y;
+ } appearance_bits11;
+
+ Nickname author_name;
+ INSERT_PADDING_BYTES(0x2);
+ u16_be crc;
+};
+static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
+#pragma pack(pop)
+
+}; // namespace Service::Mii
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 49446bc42..05951d8cb 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -28,7 +28,6 @@
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii_manager.h"
-#include "core/hle/service/mii/types.h"
#include "core/hle/service/nfc/common/amiibo_crypto.h"
#include "core/hle/service/nfc/common/device.h"
#include "core/hle/service/nfc/mifare_result.h"
@@ -440,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
+
return ResultSuccess;
}
@@ -681,12 +681,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const {
return ResultRegistrationIsNotInitialized;
}
- Service::Mii::MiiManager manager;
+ Mii::CharInfo char_info{};
+ Mii::StoreData store_data{};
+ tag_data.owner_mii.BuildToStoreData(store_data);
+ char_info.SetFromStoreData(store_data);
+
const auto& settings = tag_data.settings;
// TODO: Validate this data
register_info = {
- .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
+ .mii_char_info = char_info,
.creation_date = settings.init_date.GetWriteDate(),
.amiibo_name = GetAmiiboName(settings),
.font_region = settings.settings.font_region,
@@ -713,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info
return ResultRegistrationIsNotInitialized;
}
- Service::Mii::MiiManager manager;
+ Mii::StoreData store_data{};
const auto& settings = tag_data.settings;
+ tag_data.owner_mii.BuildToStoreData(store_data);
// TODO: Validate and complete this data
register_info = {
- .mii_store_data = {},
+ .mii_store_data = store_data,
.creation_date = settings.init_date.GetWriteDate(),
.amiibo_name = GetAmiiboName(settings),
.font_region = settings.settings.font_region,
@@ -825,8 +830,11 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
return ResultWrongDeviceState;
}
- Service::Mii::MiiManager manager;
- const auto mii = manager.BuildDefault(0);
+ Service::Mii::StoreData store_data{};
+ Service::Mii::NfpStoreDataExtension extension{};
+ store_data.BuildBase(Mii::Gender::Male);
+ extension.SetFromStoreData(store_data);
+
auto& settings = tag_data.settings;
if (tag_data.settings.settings.amiibo_initialized == 0) {
@@ -835,8 +843,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
}
SetAmiiboName(settings, register_info.amiibo_name);
- tag_data.owner_mii = manager.BuildFromStoreData(mii);
- tag_data.mii_extension = manager.SetFromStoreData(mii);
+ tag_data.owner_mii.BuildFromStoreData(store_data);
+ tag_data.mii_extension = extension;
tag_data.unknown = 0;
tag_data.unknown2 = {};
settings.country_code_id = 0;
@@ -868,17 +876,19 @@ Result NfcDevice::RestoreAmiibo() {
}
Result NfcDevice::Format() {
- auto result1 = DeleteApplicationArea();
- auto result2 = DeleteRegisterInfo();
+ Result result = ResultSuccess;
- if (result1.IsError()) {
- return result1;
+ if (device_state == DeviceState::TagFound) {
+ result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
}
- if (result2.IsError()) {
- return result2;
+ if (result.IsError()) {
+ return result;
}
+ DeleteApplicationArea();
+ DeleteRegisterInfo();
+
return Flush();
}
@@ -1364,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
// Convert from utf16 to utf8
const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
- memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
+ memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1);
return amiibo_name;
}
@@ -1453,7 +1463,7 @@ void NfcDevice::UpdateRegisterInfoCrc() {
void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
const NFP::EncryptedNTAG215File& encrypted_file) const {
- Service::Mii::MiiManager manager;
+ Service::Mii::StoreData store_data{};
auto& settings = stubbed_tag_data.settings;
stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
@@ -1467,7 +1477,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
settings.settings.font_region.Assign(0);
settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
- stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
+ store_data.BuildBase(Mii::Gender::Male);
+ stubbed_tag_data.owner_mii.BuildFromStoreData(store_data);
// Admin info
settings.settings.amiibo_initialized.Assign(1);
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index aed12a7f8..f96d21220 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -6,7 +6,9 @@
#include <array>
#include "common/swap.h"
-#include "core/hle/service/mii/types.h"
+#include "core/hle/service/mii/types/char_info.h"
+#include "core/hle/service/mii/types/store_data.h"
+#include "core/hle/service/mii/types/ver3_store_data.h"
#include "core/hle/service/nfc/nfc_types.h"
namespace Service::NFP {
@@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
// This is nn::nfp::RegisterInfoPrivate
struct RegisterInfoPrivate {
- Service::Mii::MiiStoreData mii_store_data;
+ Service::Mii::StoreData mii_store_data;
WriteDate creation_date;
AmiiboName amiibo_name;
u8 font_region;
diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp
new file mode 100644
index 000000000..c26019ec0
--- /dev/null
+++ b/src/core/hle/service/ngc/ngc.cpp
@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/ngc/ngc.h"
+#include "core/hle/service/server_manager.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NGC {
+
+class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
+public:
+ explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &NgctServiceImpl::Match, "Match"},
+ {1, &NgctServiceImpl::Filter, "Filter"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Match(HLERequestContext& ctx) {
+ const auto buffer = ctx.ReadBuffer();
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(buffer.data()), buffer.size());
+
+ LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ // Return false since we don't censor anything
+ rb.Push(false);
+ }
+
+ void Filter(HLERequestContext& ctx) {
+ const auto buffer = ctx.ReadBuffer();
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(buffer.data()), buffer.size());
+
+ LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
+
+ // Return the same string since we don't censor anything
+ ctx.WriteBuffer(buffer);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+};
+
+class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
+public:
+ explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"},
+ {1, &NgcServiceImpl::Check, "Check"},
+ {2, &NgcServiceImpl::Mask, "Mask"},
+ {3, &NgcServiceImpl::Reload, "Reload"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ static constexpr u32 NgcContentVersion = 1;
+
+ // This is nn::ngc::detail::ProfanityFilterOption
+ struct ProfanityFilterOption {
+ INSERT_PADDING_BYTES_NOINIT(0x20);
+ };
+ static_assert(sizeof(ProfanityFilterOption) == 0x20,
+ "ProfanityFilterOption has incorrect size");
+
+ void GetContentVersion(HLERequestContext& ctx) {
+ LOG_INFO(Service_NGC, "(STUBBED) called");
+
+ // This calls nn::ngc::ProfanityFilter::GetContentVersion
+ const u32 version = NgcContentVersion;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(version);
+ }
+
+ void Check(HLERequestContext& ctx) {
+ LOG_INFO(Service_NGC, "(STUBBED) called");
+
+ struct InputParameters {
+ u32 flags;
+ ProfanityFilterOption option;
+ };
+
+ IPC::RequestParser rp{ctx};
+ [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
+ [[maybe_unused]] const auto input = ctx.ReadBuffer(0);
+
+ // This calls nn::ngc::ProfanityFilter::CheckProfanityWords
+ const u32 out_flags = 0;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(out_flags);
+ }
+
+ void Mask(HLERequestContext& ctx) {
+ LOG_INFO(Service_NGC, "(STUBBED) called");
+
+ struct InputParameters {
+ u32 flags;
+ ProfanityFilterOption option;
+ };
+
+ IPC::RequestParser rp{ctx};
+ [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
+ const auto input = ctx.ReadBuffer(0);
+
+ // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText
+ const u32 out_flags = 0;
+ ctx.WriteBuffer(input);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(out_flags);
+ }
+
+ void Reload(HLERequestContext& ctx) {
+ LOG_INFO(Service_NGC, "(STUBBED) called");
+
+ // This reloads the database.
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+};
+
+void LoopProcess(Core::System& system) {
+ auto server_manager = std::make_unique<ServerManager>(system);
+
+ server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
+ server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
+ ServerManager::RunServer(std::move(server_manager));
+}
+
+} // namespace Service::NGC
diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngc/ngc.h
index 27c34dad4..823b1aa81 100644
--- a/src/core/hle/service/ngct/ngct.h
+++ b/src/core/hle/service/ngc/ngc.h
@@ -7,8 +7,8 @@ namespace Core {
class System;
}
-namespace Service::NGCT {
+namespace Service::NGC {
void LoopProcess(Core::System& system);
-} // namespace Service::NGCT
+} // namespace Service::NGC
diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp
deleted file mode 100644
index 493c80ed2..000000000
--- a/src/core/hle/service/ngct/ngct.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/string_util.h"
-#include "core/core.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/ngct/ngct.h"
-#include "core/hle/service/server_manager.h"
-#include "core/hle/service/service.h"
-
-namespace Service::NGCT {
-
-class IService final : public ServiceFramework<IService> {
-public:
- explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IService::Match, "Match"},
- {1, &IService::Filter, "Filter"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void Match(HLERequestContext& ctx) {
- const auto buffer = ctx.ReadBuffer();
- const auto text = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(buffer.data()), buffer.size());
-
- LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- // Return false since we don't censor anything
- rb.Push(false);
- }
-
- void Filter(HLERequestContext& ctx) {
- const auto buffer = ctx.ReadBuffer();
- const auto text = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(buffer.data()), buffer.size());
-
- LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
-
- // Return the same string since we don't censor anything
- ctx.WriteBuffer(buffer);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-};
-
-void LoopProcess(Core::System& system) {
- auto server_manager = std::make_unique<ServerManager>(system);
-
- server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system));
- ServerManager::RunServer(std::move(server_manager));
-}
-
-} // namespace Service::NGCT
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 376067a95..6e0baf0be 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -392,24 +392,25 @@ void IApplicationManagerInterface::GetApplicationDesiredLanguage(HLERequestConte
IPC::RequestParser rp{ctx};
const auto supported_languages = rp.Pop<u32>();
- const auto res = GetApplicationDesiredLanguage(supported_languages);
- if (res.Succeeded()) {
+ u8 desired_language{};
+ const auto res = GetApplicationDesiredLanguage(&desired_language, supported_languages);
+ if (res == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push<u32>(*res);
+ rb.Push<u32>(desired_language);
} else {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
}
}
-ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
- const u32 supported_languages) {
+Result IApplicationManagerInterface::GetApplicationDesiredLanguage(u8* out_desired_language,
+ const u32 supported_languages) {
LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages);
// Get language code from settings
const auto language_code =
- Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue());
+ Set::GetLanguageCodeFromIndex(static_cast<s32>(Settings::values.language_index.GetValue()));
// Convert to application language, get priority list
const auto application_language = ConvertToApplicationLanguage(language_code);
@@ -430,7 +431,8 @@ ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
for (const auto lang : *priority_list) {
const auto supported_flag = GetSupportedLanguageFlag(lang);
if (supported_languages == 0 || (supported_languages & supported_flag) == supported_flag) {
- return static_cast<u8>(lang);
+ *out_desired_language = static_cast<u8>(lang);
+ return ResultSuccess;
}
}
@@ -444,19 +446,20 @@ void IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
IPC::RequestParser rp{ctx};
const auto application_language = rp.Pop<u8>();
- const auto res = ConvertApplicationLanguageToLanguageCode(application_language);
- if (res.Succeeded()) {
+ u64 language_code{};
+ const auto res = ConvertApplicationLanguageToLanguageCode(&language_code, application_language);
+ if (res == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push(*res);
+ rb.Push(language_code);
} else {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(res.Code());
+ rb.Push(res);
}
}
-ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
- u8 application_language) {
+Result IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
+ u64* out_language_code, u8 application_language) {
const auto language_code =
ConvertToLanguageCode(static_cast<ApplicationLanguage>(application_language));
if (language_code == std::nullopt) {
@@ -464,7 +467,8 @@ ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguag
return Service::NS::ResultApplicationLanguageNotFound;
}
- return static_cast<u64>(*language_code);
+ *out_language_code = static_cast<u64>(*language_code);
+ return ResultSuccess;
}
IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_)
@@ -618,12 +622,13 @@ void IReadOnlyApplicationControlDataInterface::GetApplicationControlData(HLERequ
static_assert(sizeof(RequestParameters) == 0x10, "RequestParameters has incorrect size.");
IPC::RequestParser rp{ctx};
+ std::vector<u8> nacp_data{};
const auto parameters{rp.PopRaw<RequestParameters>()};
- const auto nacp_data{system.GetARPManager().GetControlProperty(parameters.application_id)};
- const auto result = nacp_data ? ResultSuccess : ResultUnknown;
+ const auto result =
+ system.GetARPManager().GetControlProperty(&nacp_data, parameters.application_id);
- if (nacp_data) {
- ctx.WriteBuffer(nacp_data->data(), nacp_data->size());
+ if (result == ResultSuccess) {
+ ctx.WriteBuffer(nacp_data.data(), nacp_data.size());
}
IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 203388e1f..175dad780 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -28,8 +28,9 @@ public:
explicit IApplicationManagerInterface(Core::System& system_);
~IApplicationManagerInterface() override;
- ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages);
- ResultVal<u64> ConvertApplicationLanguageToLanguageCode(u8 application_language);
+ Result GetApplicationDesiredLanguage(u8* out_desired_language, u32 supported_languages);
+ Result ConvertApplicationLanguageToLanguageCode(u64* out_language_code,
+ u8 application_language);
private:
void GetApplicationControlData(HLERequestContext& ctx);
diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp
index a51ca5444..0ca05257e 100644
--- a/src/core/hle/service/nvdrv/core/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/core/nvmap.cpp
@@ -160,8 +160,8 @@ u32 NvMap::PinHandle(NvMap::Handle::Id handle) {
u32 address{};
auto& smmu_allocator = host1x.Allocator();
auto& smmu_memory_manager = host1x.MemoryManager();
- while (!(address =
- smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) {
+ while ((address = smmu_allocator.Allocate(
+ static_cast<u32>(handle_description->aligned_size))) == 0) {
// Free handles until the allocation succeeds
std::scoped_lock queueLock(unmap_queue_lock);
if (auto freeHandleDesc{unmap_queue.front()}) {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index 07e570a9f..7d7bb8687 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -204,9 +204,11 @@ void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
if (!mapping->fixed) {
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+ u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
+ u64 aligned_size{Common::AlignUp(mapping->size, page_size)};
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
- static_cast<u32>(mapping->size >> page_size_bits));
+ static_cast<u32>(aligned_size >> page_size_bits));
}
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
index b16f9933f..dc6917d5d 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
@@ -449,6 +449,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
case NativeWindowScalingMode::ScaleToWindow:
case NativeWindowScalingMode::ScaleCrop:
case NativeWindowScalingMode::NoScaleCrop:
+ case NativeWindowScalingMode::PreserveAspectRatio:
break;
default:
LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode);
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 5f55cd31e..21f31f7a0 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -183,7 +183,7 @@ std::optional<u32> Nvnflinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
return layer->GetBinderId();
}
-ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) {
+Result Nvnflinger::FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
@@ -191,7 +191,7 @@ ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) {
return VI::ResultNotFound;
}
- return display->GetVSyncEvent();
+ return display->GetVSyncEvent(out_vsync_event);
}
VI::Display* Nvnflinger::FindDisplay(u64 display_id) {
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index ef236303a..f478c2bc6 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -82,7 +82,7 @@ public:
///
/// If an invalid display ID is provided, then VI::ResultNotFound is returned.
/// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned.
- [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id);
+ [[nodiscard]] Result FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id);
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
diff --git a/src/core/hle/service/nvnflinger/window.h b/src/core/hle/service/nvnflinger/window.h
index 61cca5b01..36d6cde3d 100644
--- a/src/core/hle/service/nvnflinger/window.h
+++ b/src/core/hle/service/nvnflinger/window.h
@@ -41,6 +41,7 @@ enum class NativeWindowScalingMode : s32 {
ScaleToWindow = 1,
ScaleCrop = 2,
NoScaleCrop = 3,
+ PreserveAspectRatio = 4,
};
/// Transform parameter for QueueBuffer
diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp
index 14ba67b4c..889f27c31 100644
--- a/src/core/hle/service/olsc/olsc.cpp
+++ b/src/core/hle/service/olsc/olsc.cpp
@@ -8,15 +8,16 @@
namespace Service::OLSC {
-class OLSC final : public ServiceFramework<OLSC> {
+class IOlscServiceForApplication final : public ServiceFramework<IOlscServiceForApplication> {
public:
- explicit OLSC(Core::System& system_) : ServiceFramework{system_, "olsc:u"} {
+ explicit IOlscServiceForApplication(Core::System& system_)
+ : ServiceFramework{system_, "olsc:u"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &OLSC::Initialize, "Initialize"},
+ {0, &IOlscServiceForApplication::Initialize, "Initialize"},
{10, nullptr, "VerifySaveDataBackupLicenseAsync"},
- {13, &OLSC::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"},
- {14, &OLSC::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"},
+ {13, &IOlscServiceForApplication::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"},
+ {14, &IOlscServiceForApplication::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"},
{15, nullptr, "SetCustomData"},
{16, nullptr, "DeleteSaveDataBackupSetting"},
{18, nullptr, "GetSaveDataBackupInfoCache"},
@@ -72,10 +73,155 @@ private:
bool initialized{};
};
+class INativeHandleHolder final : public ServiceFramework<INativeHandleHolder> {
+public:
+ explicit INativeHandleHolder(Core::System& system_)
+ : ServiceFramework{system_, "INativeHandleHolder"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetNativeHandle"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class ITransferTaskListController final : public ServiceFramework<ITransferTaskListController> {
+public:
+ explicit ITransferTaskListController(Core::System& system_)
+ : ServiceFramework{system_, "ITransferTaskListController"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "Unknown0"},
+ {1, nullptr, "Unknown1"},
+ {2, nullptr, "Unknown2"},
+ {3, nullptr, "Unknown3"},
+ {4, nullptr, "Unknown4"},
+ {5, &ITransferTaskListController::GetNativeHandleHolder , "GetNativeHandleHolder"},
+ {6, nullptr, "Unknown6"},
+ {7, nullptr, "Unknown7"},
+ {8, nullptr, "GetRemoteStorageController"},
+ {9, &ITransferTaskListController::GetNativeHandleHolder, "GetNativeHandleHolder2"},
+ {10, nullptr, "Unknown10"},
+ {11, nullptr, "Unknown11"},
+ {12, nullptr, "Unknown12"},
+ {13, nullptr, "Unknown13"},
+ {14, nullptr, "Unknown14"},
+ {15, nullptr, "Unknown15"},
+ {16, nullptr, "Unknown16"},
+ {17, nullptr, "Unknown17"},
+ {18, nullptr, "Unknown18"},
+ {19, nullptr, "Unknown19"},
+ {20, nullptr, "Unknown20"},
+ {21, nullptr, "Unknown21"},
+ {22, nullptr, "Unknown22"},
+ {23, nullptr, "Unknown23"},
+ {24, nullptr, "Unknown24"},
+ {25, nullptr, "Unknown25"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetNativeHandleHolder(HLERequestContext& ctx) {
+ LOG_INFO(Service_OLSC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<INativeHandleHolder>(system);
+ }
+};
+
+class IOlscServiceForSystemService final : public ServiceFramework<IOlscServiceForSystemService> {
+public:
+ explicit IOlscServiceForSystemService(Core::System& system_)
+ : ServiceFramework{system_, "olsc:s"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IOlscServiceForSystemService::OpenTransferTaskListController, "OpenTransferTaskListController"},
+ {1, nullptr, "OpenRemoteStorageController"},
+ {2, nullptr, "OpenDaemonController"},
+ {10, nullptr, "Unknown10"},
+ {11, nullptr, "Unknown11"},
+ {12, nullptr, "Unknown12"},
+ {13, nullptr, "Unknown13"},
+ {100, nullptr, "ListLastTransferTaskErrorInfo"},
+ {101, nullptr, "GetLastErrorInfoCount"},
+ {102, nullptr, "RemoveLastErrorInfoOld"},
+ {103, nullptr, "GetLastErrorInfo"},
+ {104, nullptr, "GetLastErrorEventHolder"},
+ {105, nullptr, "GetLastTransferTaskErrorInfo"},
+ {200, nullptr, "GetDataTransferPolicyInfo"},
+ {201, nullptr, "RemoveDataTransferPolicyInfo"},
+ {202, nullptr, "UpdateDataTransferPolicyOld"},
+ {203, nullptr, "UpdateDataTransferPolicy"},
+ {204, nullptr, "CleanupDataTransferPolicyInfo"},
+ {205, nullptr, "RequestDataTransferPolicy"},
+ {300, nullptr, "GetAutoTransferSeriesInfo"},
+ {301, nullptr, "UpdateAutoTransferSeriesInfo"},
+ {400, nullptr, "CleanupSaveDataArchiveInfoType1"},
+ {900, nullptr, "CleanupTransferTask"},
+ {902, nullptr, "CleanupSeriesInfoType0"},
+ {903, nullptr, "CleanupSaveDataArchiveInfoType0"},
+ {904, nullptr, "CleanupApplicationAutoTransferSetting"},
+ {905, nullptr, "CleanupErrorHistory"},
+ {906, nullptr, "SetLastError"},
+ {907, nullptr, "AddSaveDataArchiveInfoType0"},
+ {908, nullptr, "RemoveSeriesInfoType0"},
+ {909, nullptr, "GetSeriesInfoType0"},
+ {910, nullptr, "RemoveLastErrorInfo"},
+ {911, nullptr, "CleanupSeriesInfoType1"},
+ {912, nullptr, "RemoveSeriesInfoType1"},
+ {913, nullptr, "GetSeriesInfoType1"},
+ {1000, nullptr, "UpdateIssueOld"},
+ {1010, nullptr, "Unknown1010"},
+ {1011, nullptr, "ListIssueInfoOld"},
+ {1012, nullptr, "GetIssueOld"},
+ {1013, nullptr, "GetIssue2Old"},
+ {1014, nullptr, "GetIssue3Old"},
+ {1020, nullptr, "RepairIssueOld"},
+ {1021, nullptr, "RepairIssueWithUserIdOld"},
+ {1022, nullptr, "RepairIssue2Old"},
+ {1023, nullptr, "RepairIssue3Old"},
+ {1024, nullptr, "Unknown1024"},
+ {1100, nullptr, "UpdateIssue"},
+ {1110, nullptr, "Unknown1110"},
+ {1111, nullptr, "ListIssueInfo"},
+ {1112, nullptr, "GetIssue"},
+ {1113, nullptr, "GetIssue2"},
+ {1114, nullptr, "GetIssue3"},
+ {1120, nullptr, "RepairIssue"},
+ {1121, nullptr, "RepairIssueWithUserId"},
+ {1122, nullptr, "RepairIssue2"},
+ {1123, nullptr, "RepairIssue3"},
+ {1124, nullptr, "Unknown1124"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void OpenTransferTaskListController(HLERequestContext& ctx) {
+ LOG_INFO(Service_OLSC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<ITransferTaskListController>(system);
+ }
+};
+
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- server_manager->RegisterNamedService("olsc:u", std::make_shared<OLSC>(system));
+ server_manager->RegisterNamedService("olsc:u",
+ std::make_shared<IOlscServiceForApplication>(system));
+ server_manager->RegisterNamedService("olsc:s",
+ std::make_shared<IOlscServiceForSystemService>(system));
+
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index f966c5c8b..5db1703d1 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.cpp
@@ -6,6 +6,7 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/pctl/pctl.h"
#include "core/hle/service/pctl/pctl_module.h"
#include "core/hle/service/server_manager.h"
@@ -24,7 +25,8 @@ constexpr Result ResultNoRestrictionEnabled{ErrorModule::PCTL, 181};
class IParentalControlService final : public ServiceFramework<IParentalControlService> {
public:
explicit IParentalControlService(Core::System& system_, Capability capability_)
- : ServiceFramework{system_, "IParentalControlService"}, capability{capability_} {
+ : ServiceFramework{system_, "IParentalControlService"}, capability{capability_},
+ service_context{system_, "IParentalControlService"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &IParentalControlService::Initialize, "Initialize"},
@@ -33,7 +35,7 @@ public:
{1003, nullptr, "ConfirmResumeApplicationPermission"},
{1004, nullptr, "ConfirmSnsPostPermission"},
{1005, nullptr, "ConfirmSystemSettingsPermission"},
- {1006, nullptr, "IsRestrictionTemporaryUnlocked"},
+ {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"},
{1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
{1008, nullptr, "EnterRestrictedSystemSettings"},
{1009, nullptr, "LeaveRestrictedSystemSettings"},
@@ -47,14 +49,14 @@ public:
{1017, &IParentalControlService::EndFreeCommunication, "EndFreeCommunication"},
{1018, &IParentalControlService::IsFreeCommunicationAvailable, "IsFreeCommunicationAvailable"},
{1031, &IParentalControlService::IsRestrictionEnabled, "IsRestrictionEnabled"},
- {1032, nullptr, "GetSafetyLevel"},
+ {1032, &IParentalControlService::GetSafetyLevel, "GetSafetyLevel"},
{1033, nullptr, "SetSafetyLevel"},
{1034, nullptr, "GetSafetyLevelSettings"},
- {1035, nullptr, "GetCurrentSettings"},
+ {1035, &IParentalControlService::GetCurrentSettings, "GetCurrentSettings"},
{1036, nullptr, "SetCustomSafetyLevelSettings"},
{1037, nullptr, "GetDefaultRatingOrganization"},
{1038, nullptr, "SetDefaultRatingOrganization"},
- {1039, nullptr, "GetFreeCommunicationApplicationListCount"},
+ {1039, &IParentalControlService::GetFreeCommunicationApplicationListCount, "GetFreeCommunicationApplicationListCount"},
{1042, nullptr, "AddToFreeCommunicationApplicationList"},
{1043, nullptr, "DeleteSettings"},
{1044, nullptr, "GetFreeCommunicationApplicationList"},
@@ -76,7 +78,7 @@ public:
{1206, nullptr, "GetPinCodeLength"},
{1207, nullptr, "GetPinCodeChangedEvent"},
{1208, nullptr, "GetPinCode"},
- {1403, nullptr, "IsPairingActive"},
+ {1403, &IParentalControlService::IsPairingActive, "IsPairingActive"},
{1406, nullptr, "GetSettingsLastUpdated"},
{1411, nullptr, "GetPairingAccountInfo"},
{1421, nullptr, "GetAccountNickname"},
@@ -84,18 +86,18 @@ public:
{1425, nullptr, "RequestPostEvents"},
{1426, nullptr, "GetPostEventInterval"},
{1427, nullptr, "SetPostEventInterval"},
- {1432, nullptr, "GetSynchronizationEvent"},
+ {1432, &IParentalControlService::GetSynchronizationEvent, "GetSynchronizationEvent"},
{1451, nullptr, "StartPlayTimer"},
{1452, nullptr, "StopPlayTimer"},
{1453, nullptr, "IsPlayTimerEnabled"},
{1454, nullptr, "GetPlayTimerRemainingTime"},
{1455, nullptr, "IsRestrictedByPlayTimer"},
- {1456, nullptr, "GetPlayTimerSettings"},
- {1457, nullptr, "GetPlayTimerEventToRequestSuspension"},
- {1458, nullptr, "IsPlayTimerAlarmDisabled"},
+ {1456, &IParentalControlService::GetPlayTimerSettings, "GetPlayTimerSettings"},
+ {1457, &IParentalControlService::GetPlayTimerEventToRequestSuspension, "GetPlayTimerEventToRequestSuspension"},
+ {1458, &IParentalControlService::IsPlayTimerAlarmDisabled, "IsPlayTimerAlarmDisabled"},
{1471, nullptr, "NotifyWrongPinCodeInputManyTimes"},
{1472, nullptr, "CancelNetworkRequest"},
- {1473, nullptr, "GetUnlinkedEvent"},
+ {1473, &IParentalControlService::GetUnlinkedEvent, "GetUnlinkedEvent"},
{1474, nullptr, "ClearUnlinkedEvent"},
{1601, nullptr, "DisableAllFeatures"},
{1602, nullptr, "PostEnableAllFeatures"},
@@ -131,6 +133,12 @@ public:
};
// clang-format on
RegisterHandlers(functions);
+
+ synchronization_event =
+ service_context.CreateEvent("IParentalControlService::SynchronizationEvent");
+ unlinked_event = service_context.CreateEvent("IParentalControlService::UnlinkedEvent");
+ request_suspension_event =
+ service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent");
}
private:
@@ -228,6 +236,17 @@ private:
states.free_communication = true;
}
+ void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
+ const bool is_temporary_unlocked = false;
+
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, is_temporary_unlocked={}",
+ is_temporary_unlocked);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_temporary_unlocked);
+ }
+
void ConfirmStereoVisionPermission(HLERequestContext& ctx) {
LOG_DEBUG(Service_PCTL, "called");
states.stereo_vision = true;
@@ -268,6 +287,34 @@ private:
rb.Push(pin_code[0] != '\0');
}
+ void GetSafetyLevel(HLERequestContext& ctx) {
+ const u32 safety_level = 0;
+
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, safety_level={}", safety_level);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(safety_level);
+ }
+
+ void GetCurrentSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_PCTL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(restriction_settings);
+ }
+
+ void GetFreeCommunicationApplicationListCount(HLERequestContext& ctx) {
+ const u32 count = 4;
+
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, count={}", count);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(count);
+ }
+
void ConfirmStereoVisionRestrictionConfigurable(HLERequestContext& ctx) {
LOG_DEBUG(Service_PCTL, "called");
@@ -300,6 +347,61 @@ private:
}
}
+ void IsPairingActive(HLERequestContext& ctx) {
+ const bool is_pairing_active = false;
+
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, is_pairing_active={}", is_pairing_active);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_pairing_active);
+ }
+
+ void GetSynchronizationEvent(HLERequestContext& ctx) {
+ LOG_INFO(Service_PCTL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(synchronization_event->GetReadableEvent());
+ }
+
+ void GetPlayTimerSettings(HLERequestContext& ctx) {
+ LOG_WARNING(Service_PCTL, "(STUBBED) called");
+
+ const PlayTimerSettings timer_settings{};
+
+ IPC::ResponseBuilder rb{ctx, 15};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(timer_settings);
+ }
+
+ void GetPlayTimerEventToRequestSuspension(HLERequestContext& ctx) {
+ LOG_INFO(Service_PCTL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(request_suspension_event->GetReadableEvent());
+ }
+
+ void IsPlayTimerAlarmDisabled(HLERequestContext& ctx) {
+ const bool is_play_timer_alarm_disabled = false;
+
+ LOG_INFO(Service_PCTL, "called, is_play_timer_alarm_disabled={}",
+ is_play_timer_alarm_disabled);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_play_timer_alarm_disabled);
+ }
+
+ void GetUnlinkedEvent(HLERequestContext& ctx) {
+ LOG_INFO(Service_PCTL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(unlinked_event->GetReadableEvent());
+ }
+
void SetStereoVisionRestriction(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto can_use = rp.Pop<bool>();
@@ -364,10 +466,30 @@ private:
bool disabled{};
};
+ // This is nn::pctl::RestrictionSettings
+ struct RestrictionSettings {
+ u8 rating_age;
+ bool sns_post_restriction;
+ bool free_communication_restriction;
+ };
+ static_assert(sizeof(RestrictionSettings) == 0x3, "RestrictionSettings has incorrect size.");
+
+ // This is nn::pctl::PlayTimerSettings
+ struct PlayTimerSettings {
+ std::array<u32, 13> settings;
+ };
+ static_assert(sizeof(PlayTimerSettings) == 0x34, "PlayTimerSettings has incorrect size.");
+
States states{};
ParentalControlSettings settings{};
+ RestrictionSettings restriction_settings{};
std::array<char, 8> pin_code{};
Capability capability{};
+
+ Kernel::KEvent* synchronization_event;
+ Kernel::KEvent* unlinked_event;
+ Kernel::KEvent* request_suspension_event;
+ KernelHelpers::ServiceContext service_context;
};
void Module::Interface::CreateService(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp
index d1e99b184..e2e399534 100644
--- a/src/core/hle/service/server_manager.cpp
+++ b/src/core/hle/service/server_manager.cpp
@@ -102,16 +102,17 @@ Result ServerManager::RegisterNamedService(const std::string& service_name,
m_system.ServiceManager().RegisterService(service_name, max_sessions, handler)));
// Get the registered port.
- auto port = m_system.ServiceManager().GetServicePort(service_name);
- ASSERT(port.Succeeded());
+ Kernel::KPort* port{};
+ ASSERT(
+ R_SUCCEEDED(m_system.ServiceManager().GetServicePort(std::addressof(port), service_name)));
// Open a new reference to the server port.
- (*port)->GetServerPort().Open();
+ port->GetServerPort().Open();
// Begin tracking the server port.
{
std::scoped_lock ll{m_list_mutex};
- m_ports.emplace(std::addressof((*port)->GetServerPort()), std::move(handler));
+ m_ports.emplace(std::addressof(port->GetServerPort()), std::move(handler));
}
// Signal the wakeup event.
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 69cdb5918..0ad607391 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -43,7 +43,7 @@
#include "core/hle/service/ncm/ncm.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfp/nfp.h"
-#include "core/hle/service/ngct/ngct.h"
+#include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nim/nim.h"
#include "core/hle/service/npns/npns.h"
@@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
- kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); });
+ kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 45b2c43b7..d539ed0f4 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -79,8 +79,8 @@ protected:
using HandlerFnP = void (Self::*)(HLERequestContext&);
/// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
- [[nodiscard]] std::scoped_lock<std::mutex> LockService() {
- return std::scoped_lock{lock_service};
+ [[nodiscard]] virtual std::unique_lock<std::mutex> LockService() {
+ return std::unique_lock{lock_service};
}
/// System context that the service operates under.
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index f5788b481..2082b8ef7 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -11,66 +11,6 @@
namespace Service::Set {
namespace {
-constexpr std::array<LanguageCode, 18> available_language_codes = {{
- LanguageCode::JA,
- LanguageCode::EN_US,
- LanguageCode::FR,
- LanguageCode::DE,
- LanguageCode::IT,
- LanguageCode::ES,
- LanguageCode::ZH_CN,
- LanguageCode::KO,
- LanguageCode::NL,
- LanguageCode::PT,
- LanguageCode::RU,
- LanguageCode::ZH_TW,
- LanguageCode::EN_GB,
- LanguageCode::FR_CA,
- LanguageCode::ES_419,
- LanguageCode::ZH_HANS,
- LanguageCode::ZH_HANT,
- LanguageCode::PT_BR,
-}};
-
-enum class KeyboardLayout : u64 {
- Japanese = 0,
- EnglishUs = 1,
- EnglishUsInternational = 2,
- EnglishUk = 3,
- French = 4,
- FrenchCa = 5,
- Spanish = 6,
- SpanishLatin = 7,
- German = 8,
- Italian = 9,
- Portuguese = 10,
- Russian = 11,
- Korean = 12,
- ChineseSimplified = 13,
- ChineseTraditional = 14,
-};
-
-constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 18> language_to_layout{{
- {LanguageCode::JA, KeyboardLayout::Japanese},
- {LanguageCode::EN_US, KeyboardLayout::EnglishUs},
- {LanguageCode::FR, KeyboardLayout::French},
- {LanguageCode::DE, KeyboardLayout::German},
- {LanguageCode::IT, KeyboardLayout::Italian},
- {LanguageCode::ES, KeyboardLayout::Spanish},
- {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified},
- {LanguageCode::KO, KeyboardLayout::Korean},
- {LanguageCode::NL, KeyboardLayout::EnglishUsInternational},
- {LanguageCode::PT, KeyboardLayout::Portuguese},
- {LanguageCode::RU, KeyboardLayout::Russian},
- {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional},
- {LanguageCode::EN_GB, KeyboardLayout::EnglishUk},
- {LanguageCode::FR_CA, KeyboardLayout::FrenchCa},
- {LanguageCode::ES_419, KeyboardLayout::SpanishLatin},
- {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified},
- {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional},
- {LanguageCode::PT_BR, KeyboardLayout::Portuguese},
-}};
-
constexpr std::size_t PRE_4_0_0_MAX_ENTRIES = 0xF;
constexpr std::size_t POST_4_0_0_MAX_ENTRIES = 0x40;
@@ -93,7 +33,8 @@ void GetAvailableLanguageCodesImpl(HLERequestContext& ctx, std::size_t max_entri
}
void GetKeyCodeMapImpl(HLERequestContext& ctx) {
- const auto language_code = available_language_codes[Settings::values.language_index.GetValue()];
+ const auto language_code =
+ available_language_codes[static_cast<s32>(Settings::values.language_index.GetValue())];
const auto key_code =
std::find_if(language_to_layout.cbegin(), language_to_layout.cend(),
[=](const auto& element) { return element.first == language_code; });
@@ -162,7 +103,7 @@ void SET::GetQuestFlag(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(Settings::values.quest_flag.GetValue()));
+ rb.Push(static_cast<s32>(Settings::values.quest_flag.GetValue()));
}
void SET::GetLanguageCode(HLERequestContext& ctx) {
@@ -170,7 +111,8 @@ void SET::GetLanguageCode(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.PushEnum(available_language_codes[Settings::values.language_index.GetValue()]);
+ rb.PushEnum(
+ available_language_codes[static_cast<s32>(Settings::values.language_index.GetValue())]);
}
void SET::GetRegionCode(HLERequestContext& ctx) {
@@ -178,7 +120,7 @@ void SET::GetRegionCode(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(Settings::values.region_index.GetValue());
+ rb.Push(static_cast<u32>(Settings::values.region_index.GetValue()));
}
void SET::GetKeyCodeMap(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 7fd3a7654..b61a3560d 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -32,6 +32,67 @@ enum class LanguageCode : u64 {
ZH_HANT = 0x00746E61482D687A,
PT_BR = 0x00000052422D7470,
};
+
+enum class KeyboardLayout : u64 {
+ Japanese = 0,
+ EnglishUs = 1,
+ EnglishUsInternational = 2,
+ EnglishUk = 3,
+ French = 4,
+ FrenchCa = 5,
+ Spanish = 6,
+ SpanishLatin = 7,
+ German = 8,
+ Italian = 9,
+ Portuguese = 10,
+ Russian = 11,
+ Korean = 12,
+ ChineseSimplified = 13,
+ ChineseTraditional = 14,
+};
+
+constexpr std::array<LanguageCode, 18> available_language_codes = {{
+ LanguageCode::JA,
+ LanguageCode::EN_US,
+ LanguageCode::FR,
+ LanguageCode::DE,
+ LanguageCode::IT,
+ LanguageCode::ES,
+ LanguageCode::ZH_CN,
+ LanguageCode::KO,
+ LanguageCode::NL,
+ LanguageCode::PT,
+ LanguageCode::RU,
+ LanguageCode::ZH_TW,
+ LanguageCode::EN_GB,
+ LanguageCode::FR_CA,
+ LanguageCode::ES_419,
+ LanguageCode::ZH_HANS,
+ LanguageCode::ZH_HANT,
+ LanguageCode::PT_BR,
+}};
+
+static constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 18> language_to_layout{{
+ {LanguageCode::JA, KeyboardLayout::Japanese},
+ {LanguageCode::EN_US, KeyboardLayout::EnglishUs},
+ {LanguageCode::FR, KeyboardLayout::French},
+ {LanguageCode::DE, KeyboardLayout::German},
+ {LanguageCode::IT, KeyboardLayout::Italian},
+ {LanguageCode::ES, KeyboardLayout::Spanish},
+ {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified},
+ {LanguageCode::KO, KeyboardLayout::Korean},
+ {LanguageCode::NL, KeyboardLayout::EnglishUsInternational},
+ {LanguageCode::PT, KeyboardLayout::Portuguese},
+ {LanguageCode::RU, KeyboardLayout::Russian},
+ {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional},
+ {LanguageCode::EN_GB, KeyboardLayout::EnglishUk},
+ {LanguageCode::FR_CA, KeyboardLayout::FrenchCa},
+ {LanguageCode::ES_419, KeyboardLayout::SpanishLatin},
+ {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified},
+ {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional},
+ {LanguageCode::PT_BR, KeyboardLayout::Portuguese},
+}};
+
LanguageCode GetLanguageCodeFromIndex(std::size_t idx);
class SET final : public ServiceFramework<SET> {
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 2e38d1cfc..165b97dad 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -4,10 +4,12 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/string_util.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/system_archive/system_version.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/set/set.h"
#include "core/hle/service/set/set_sys.h"
namespace Service::Set {
@@ -73,6 +75,16 @@ void GetFirmwareVersionImpl(HLERequestContext& ctx, GetFirmwareVersionType type)
}
} // Anonymous namespace
+void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ language_code_setting = rp.PopEnum<LanguageCode>();
+
+ LOG_INFO(Service_SET, "called, language_code={}", language_code_setting);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version1);
@@ -83,21 +95,113 @@ void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version2);
}
+void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(account_settings);
+}
+
+void SET_SYS::SetAccountSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ account_settings = rp.PopRaw<AccountSettings>();
+
+ LOG_INFO(Service_SET, "called, account_settings_flags={}", account_settings.flags);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetEulaVersions(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ ctx.WriteBuffer(eula_versions);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(eula_versions.size()));
+}
+
+void SET_SYS::SetEulaVersions(HLERequestContext& ctx) {
+ const auto elements = ctx.GetReadBufferNumElements<EulaVersion>();
+ const auto buffer_data = ctx.ReadBuffer();
+
+ LOG_INFO(Service_SET, "called, elements={}", elements);
+
+ eula_versions.resize(elements);
+ for (std::size_t index = 0; index < elements; index++) {
+ const std::size_t start_index = index * sizeof(EulaVersion);
+ memcpy(eula_versions.data() + start_index, buffer_data.data() + start_index,
+ sizeof(EulaVersion));
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void SET_SYS::GetColorSetId(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
IPC::ResponseBuilder rb{ctx, 3};
-
rb.Push(ResultSuccess);
rb.PushEnum(color_set);
}
void SET_SYS::SetColorSetId(HLERequestContext& ctx) {
- LOG_DEBUG(Service_SET, "called");
-
IPC::RequestParser rp{ctx};
color_set = rp.PopEnum<ColorSet>();
+ LOG_DEBUG(Service_SET, "called, color_set={}", color_set);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetNotificationSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 8};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(notification_settings);
+}
+
+void SET_SYS::SetNotificationSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ notification_settings = rp.PopRaw<NotificationSettings>();
+
+ LOG_INFO(Service_SET, "called, flags={}, volume={}, head_time={}:{}, tailt_time={}:{}",
+ notification_settings.flags.raw, notification_settings.volume,
+ notification_settings.start_time.hour, notification_settings.start_time.minute,
+ notification_settings.stop_time.hour, notification_settings.stop_time.minute);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetAccountNotificationSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ ctx.WriteBuffer(account_notifications);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(account_notifications.size()));
+}
+
+void SET_SYS::SetAccountNotificationSettings(HLERequestContext& ctx) {
+ const auto elements = ctx.GetReadBufferNumElements<AccountNotificationSettings>();
+ const auto buffer_data = ctx.ReadBuffer();
+
+ LOG_INFO(Service_SET, "called, elements={}", elements);
+
+ account_notifications.resize(elements);
+ for (std::size_t index = 0; index < elements; index++) {
+ const std::size_t start_index = index * sizeof(AccountNotificationSettings);
+ memcpy(account_notifications.data() + start_index, buffer_data.data() + start_index,
+ sizeof(AccountNotificationSettings));
+ }
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
@@ -177,17 +281,218 @@ void SET_SYS::GetSettingsItemValue(HLERequestContext& ctx) {
rb.Push(response);
}
+void SET_SYS::GetTvSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 10};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(tv_settings);
+}
+
+void SET_SYS::SetTvSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ tv_settings = rp.PopRaw<TvSettings>();
+
+ LOG_INFO(Service_SET,
+ "called, flags={}, cmu_mode={}, constrast_ratio={}, hdmi_content_type={}, "
+ "rgb_range={}, tv_gama={}, tv_resolution={}, tv_underscan={}",
+ tv_settings.flags.raw, tv_settings.cmu_mode, tv_settings.constrast_ratio,
+ tv_settings.hdmi_content_type, tv_settings.rgb_range, tv_settings.tv_gama,
+ tv_settings.tv_resolution, tv_settings.tv_underscan);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetQuestFlag(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(QuestFlag::Retail);
+}
+
+void SET_SYS::SetRegionCode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ region_code = rp.PopEnum<RegionCode>();
+
+ LOG_INFO(Service_SET, "called, region_code={}", region_code);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetPrimaryAlbumStorage(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(PrimaryAlbumStorage::SdCard);
+}
+
+void SET_SYS::GetSleepSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(sleep_settings);
+}
+
+void SET_SYS::SetSleepSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ sleep_settings = rp.PopRaw<SleepSettings>();
+
+ LOG_INFO(Service_SET, "called, flags={}, handheld_sleep_plan={}, console_sleep_plan={}",
+ sleep_settings.flags.raw, sleep_settings.handheld_sleep_plan,
+ sleep_settings.console_sleep_plan);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetInitialLaunchSettings(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called");
+ IPC::ResponseBuilder rb{ctx, 10};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(launch_settings);
+}
+
+void SET_SYS::SetInitialLaunchSettings(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ launch_settings = rp.PopRaw<InitialLaunchSettings>();
+
+ LOG_INFO(Service_SET, "called, flags={}, timestamp={}", launch_settings.flags.raw,
+ launch_settings.timestamp.time_point);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void SET_SYS::GetDeviceNickName(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
+
+ ctx.WriteBuffer(::Settings::values.device_name.GetValue());
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
- ctx.WriteBuffer(::Settings::values.device_name.GetValue());
+}
+
+void SET_SYS::SetDeviceNickName(HLERequestContext& ctx) {
+ const std::string device_name = Common::StringFromBuffer(ctx.ReadBuffer());
+
+ LOG_INFO(Service_SET, "called, device_name={}", device_name);
+
+ ::Settings::values.device_name = device_name;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetProductModel(HLERequestContext& ctx) {
+ const u32 product_model = 1;
+
+ LOG_WARNING(Service_SET, "(STUBBED) called, product_model={}", product_model);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(product_model);
+}
+
+void SET_SYS::GetMiiAuthorId(HLERequestContext& ctx) {
+ const auto author_id = Common::UUID::MakeDefault();
+
+ LOG_WARNING(Service_SET, "(STUBBED) called, author_id={}", author_id.FormattedString());
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(author_id);
+}
+
+void SET_SYS::GetAutoUpdateEnableFlag(HLERequestContext& ctx) {
+ u8 auto_update_flag{};
+
+ LOG_WARNING(Service_SET, "(STUBBED) called, auto_update_flag={}", auto_update_flag);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(auto_update_flag);
+}
+
+void SET_SYS::GetBatteryPercentageFlag(HLERequestContext& ctx) {
+ u8 battery_percentage_flag{1};
+
+ LOG_WARNING(Service_SET, "(STUBBED) called, battery_percentage_flag={}",
+ battery_percentage_flag);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(battery_percentage_flag);
+}
+
+void SET_SYS::GetErrorReportSharePermission(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(ErrorReportSharePermission::Denied);
+}
+
+void SET_SYS::GetAppletLaunchFlags(HLERequestContext& ctx) {
+ LOG_INFO(Service_SET, "called, applet_launch_flag={}", applet_launch_flag);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(applet_launch_flag);
+}
+
+void SET_SYS::SetAppletLaunchFlags(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ applet_launch_flag = rp.Pop<u32>();
+
+ LOG_INFO(Service_SET, "called, applet_launch_flag={}", applet_launch_flag);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void SET_SYS::GetKeyboardLayout(HLERequestContext& ctx) {
+ const auto language_code =
+ available_language_codes[static_cast<s32>(::Settings::values.language_index.GetValue())];
+ const auto key_code =
+ std::find_if(language_to_layout.cbegin(), language_to_layout.cend(),
+ [=](const auto& element) { return element.first == language_code; });
+
+ KeyboardLayout selected_keyboard_layout = KeyboardLayout::EnglishUs;
+ if (key_code != language_to_layout.end()) {
+ selected_keyboard_layout = key_code->second;
+ }
+
+ LOG_INFO(Service_SET, "called, selected_keyboard_layout={}", selected_keyboard_layout);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(selected_keyboard_layout));
+}
+
+void SET_SYS::GetChineseTraditionalInputMethod(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(ChineseTraditionalInputMethod::Unknown0);
+}
+
+void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(false);
}
SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "SetLanguageCode"},
+ {0, &SET_SYS::SetLanguageCode, "SetLanguageCode"},
{1, nullptr, "SetNetworkSettings"},
{2, nullptr, "GetNetworkSettings"},
{3, &SET_SYS::GetFirmwareVersion, "GetFirmwareVersion"},
@@ -203,35 +508,35 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{14, nullptr, "SetExternalSteadyClockSourceId"},
{15, nullptr, "GetUserSystemClockContext"},
{16, nullptr, "SetUserSystemClockContext"},
- {17, nullptr, "GetAccountSettings"},
- {18, nullptr, "SetAccountSettings"},
+ {17, &SET_SYS::GetAccountSettings, "GetAccountSettings"},
+ {18, &SET_SYS::SetAccountSettings, "SetAccountSettings"},
{19, nullptr, "GetAudioVolume"},
{20, nullptr, "SetAudioVolume"},
- {21, nullptr, "GetEulaVersions"},
- {22, nullptr, "SetEulaVersions"},
+ {21, &SET_SYS::GetEulaVersions, "GetEulaVersions"},
+ {22, &SET_SYS::SetEulaVersions, "SetEulaVersions"},
{23, &SET_SYS::GetColorSetId, "GetColorSetId"},
{24, &SET_SYS::SetColorSetId, "SetColorSetId"},
{25, nullptr, "GetConsoleInformationUploadFlag"},
{26, nullptr, "SetConsoleInformationUploadFlag"},
{27, nullptr, "GetAutomaticApplicationDownloadFlag"},
{28, nullptr, "SetAutomaticApplicationDownloadFlag"},
- {29, nullptr, "GetNotificationSettings"},
- {30, nullptr, "SetNotificationSettings"},
- {31, nullptr, "GetAccountNotificationSettings"},
- {32, nullptr, "SetAccountNotificationSettings"},
+ {29, &SET_SYS::GetNotificationSettings, "GetNotificationSettings"},
+ {30, &SET_SYS::SetNotificationSettings, "SetNotificationSettings"},
+ {31, &SET_SYS::GetAccountNotificationSettings, "GetAccountNotificationSettings"},
+ {32, &SET_SYS::SetAccountNotificationSettings, "SetAccountNotificationSettings"},
{35, nullptr, "GetVibrationMasterVolume"},
{36, nullptr, "SetVibrationMasterVolume"},
{37, &SET_SYS::GetSettingsItemValueSize, "GetSettingsItemValueSize"},
{38, &SET_SYS::GetSettingsItemValue, "GetSettingsItemValue"},
- {39, nullptr, "GetTvSettings"},
- {40, nullptr, "SetTvSettings"},
+ {39, &SET_SYS::GetTvSettings, "GetTvSettings"},
+ {40, &SET_SYS::SetTvSettings, "SetTvSettings"},
{41, nullptr, "GetEdid"},
{42, nullptr, "SetEdid"},
{43, nullptr, "GetAudioOutputMode"},
{44, nullptr, "SetAudioOutputMode"},
{45, nullptr, "IsForceMuteOnHeadphoneRemoved"},
{46, nullptr, "SetForceMuteOnHeadphoneRemoved"},
- {47, nullptr, "GetQuestFlag"},
+ {47, &SET_SYS::GetQuestFlag, "GetQuestFlag"},
{48, nullptr, "SetQuestFlag"},
{49, nullptr, "GetDataDeletionSettings"},
{50, nullptr, "SetDataDeletionSettings"},
@@ -241,13 +546,13 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{54, nullptr, "SetDeviceTimeZoneLocationName"},
{55, nullptr, "GetWirelessCertificationFileSize"},
{56, nullptr, "GetWirelessCertificationFile"},
- {57, nullptr, "SetRegionCode"},
+ {57, &SET_SYS::SetRegionCode, "SetRegionCode"},
{58, nullptr, "GetNetworkSystemClockContext"},
{59, nullptr, "SetNetworkSystemClockContext"},
{60, nullptr, "IsUserSystemClockAutomaticCorrectionEnabled"},
{61, nullptr, "SetUserSystemClockAutomaticCorrectionEnabled"},
{62, nullptr, "GetDebugModeFlag"},
- {63, nullptr, "GetPrimaryAlbumStorage"},
+ {63, &SET_SYS::GetPrimaryAlbumStorage, "GetPrimaryAlbumStorage"},
{64, nullptr, "SetPrimaryAlbumStorage"},
{65, nullptr, "GetUsb30EnableFlag"},
{66, nullptr, "SetUsb30EnableFlag"},
@@ -255,15 +560,15 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{68, nullptr, "GetSerialNumber"},
{69, nullptr, "GetNfcEnableFlag"},
{70, nullptr, "SetNfcEnableFlag"},
- {71, nullptr, "GetSleepSettings"},
- {72, nullptr, "SetSleepSettings"},
+ {71, &SET_SYS::GetSleepSettings, "GetSleepSettings"},
+ {72, &SET_SYS::SetSleepSettings, "SetSleepSettings"},
{73, nullptr, "GetWirelessLanEnableFlag"},
{74, nullptr, "SetWirelessLanEnableFlag"},
- {75, nullptr, "GetInitialLaunchSettings"},
- {76, nullptr, "SetInitialLaunchSettings"},
+ {75, &SET_SYS::GetInitialLaunchSettings, "GetInitialLaunchSettings"},
+ {76, &SET_SYS::SetInitialLaunchSettings, "SetInitialLaunchSettings"},
{77, &SET_SYS::GetDeviceNickName, "GetDeviceNickName"},
- {78, nullptr, "SetDeviceNickName"},
- {79, nullptr, "GetProductModel"},
+ {78, &SET_SYS::SetDeviceNickName, "SetDeviceNickName"},
+ {79, &SET_SYS::GetProductModel, "GetProductModel"},
{80, nullptr, "GetLdnChannel"},
{81, nullptr, "SetLdnChannel"},
{82, nullptr, "AcquireTelemetryDirtyFlagEventHandle"},
@@ -274,16 +579,16 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{87, nullptr, "SetPtmFuelGaugeParameter"},
{88, nullptr, "GetBluetoothEnableFlag"},
{89, nullptr, "SetBluetoothEnableFlag"},
- {90, nullptr, "GetMiiAuthorId"},
+ {90, &SET_SYS::GetMiiAuthorId, "GetMiiAuthorId"},
{91, nullptr, "SetShutdownRtcValue"},
{92, nullptr, "GetShutdownRtcValue"},
{93, nullptr, "AcquireFatalDirtyFlagEventHandle"},
{94, nullptr, "GetFatalDirtyFlags"},
- {95, nullptr, "GetAutoUpdateEnableFlag"},
+ {95, &SET_SYS::GetAutoUpdateEnableFlag, "GetAutoUpdateEnableFlag"},
{96, nullptr, "SetAutoUpdateEnableFlag"},
{97, nullptr, "GetNxControllerSettings"},
{98, nullptr, "SetNxControllerSettings"},
- {99, nullptr, "GetBatteryPercentageFlag"},
+ {99, &SET_SYS::GetBatteryPercentageFlag, "GetBatteryPercentageFlag"},
{100, nullptr, "SetBatteryPercentageFlag"},
{101, nullptr, "GetExternalRtcResetFlag"},
{102, nullptr, "SetExternalRtcResetFlag"},
@@ -308,10 +613,10 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{121, nullptr, "SetPushNotificationActivityModeOnSleep"},
{122, nullptr, "GetServiceDiscoveryControlSettings"},
{123, nullptr, "SetServiceDiscoveryControlSettings"},
- {124, nullptr, "GetErrorReportSharePermission"},
+ {124, &SET_SYS::GetErrorReportSharePermission, "GetErrorReportSharePermission"},
{125, nullptr, "SetErrorReportSharePermission"},
- {126, nullptr, "GetAppletLaunchFlags"},
- {127, nullptr, "SetAppletLaunchFlags"},
+ {126, &SET_SYS::GetAppletLaunchFlags, "GetAppletLaunchFlags"},
+ {127, &SET_SYS::SetAppletLaunchFlags, "SetAppletLaunchFlags"},
{128, nullptr, "GetConsoleSixAxisSensorAccelerationBias"},
{129, nullptr, "SetConsoleSixAxisSensorAccelerationBias"},
{130, nullptr, "GetConsoleSixAxisSensorAngularVelocityBias"},
@@ -320,7 +625,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{133, nullptr, "SetConsoleSixAxisSensorAccelerationGain"},
{134, nullptr, "GetConsoleSixAxisSensorAngularVelocityGain"},
{135, nullptr, "SetConsoleSixAxisSensorAngularVelocityGain"},
- {136, nullptr, "GetKeyboardLayout"},
+ {136, &SET_SYS::GetKeyboardLayout, "GetKeyboardLayout"},
{137, nullptr, "SetKeyboardLayout"},
{138, nullptr, "GetWebInspectorFlag"},
{139, nullptr, "GetAllowedSslHosts"},
@@ -354,7 +659,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{167, nullptr, "SetUsb30DeviceEnableFlag"},
{168, nullptr, "GetThemeId"},
{169, nullptr, "SetThemeId"},
- {170, nullptr, "GetChineseTraditionalInputMethod"},
+ {170, &SET_SYS::GetChineseTraditionalInputMethod, "GetChineseTraditionalInputMethod"},
{171, nullptr, "SetChineseTraditionalInputMethod"},
{172, nullptr, "GetPtmCycleCountReliability"},
{173, nullptr, "SetPtmCycleCountReliability"},
@@ -385,12 +690,16 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{198, nullptr, "SetButtonConfigRegisteredSettingsEmbedded"},
{199, nullptr, "GetButtonConfigRegisteredSettings"},
{200, nullptr, "SetButtonConfigRegisteredSettings"},
- {201, nullptr, "GetFieldTestingFlag"},
+ {201, &SET_SYS::GetFieldTestingFlag, "GetFieldTestingFlag"},
{202, nullptr, "SetFieldTestingFlag"},
{203, nullptr, "GetPanelCrcMode"},
{204, nullptr, "SetPanelCrcMode"},
{205, nullptr, "GetNxControllerSettingsEx"},
{206, nullptr, "SetNxControllerSettingsEx"},
+ {207, nullptr, "GetHearingProtectionSafeguardFlag"},
+ {208, nullptr, "SetHearingProtectionSafeguardFlag"},
+ {209, nullptr, "GetHearingProtectionSafeguardRemainingTime"},
+ {210, nullptr, "SetHearingProtectionSafeguardRemainingTime"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 1efbcc97a..c7dba2a9e 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -3,7 +3,9 @@
#pragma once
+#include "common/uuid.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/time/clock_types.h"
namespace Core {
class System;
@@ -23,15 +25,331 @@ private:
BasicBlack = 1,
};
- void GetSettingsItemValueSize(HLERequestContext& ctx);
- void GetSettingsItemValue(HLERequestContext& ctx);
+ /// Indicates the current console is a retail or kiosk unit
+ enum class QuestFlag : u8 {
+ Retail = 0,
+ Kiosk = 1,
+ };
+
+ /// This is nn::settings::system::TvResolution
+ enum class TvResolution : u32 {
+ Auto,
+ Resolution1080p,
+ Resolution720p,
+ Resolution480p,
+ };
+
+ /// This is nn::settings::system::HdmiContentType
+ enum class HdmiContentType : u32 {
+ None,
+ Graphics,
+ Cinema,
+ Photo,
+ Game,
+ };
+
+ /// This is nn::settings::system::RgbRange
+ enum class RgbRange : u32 {
+ Auto,
+ Full,
+ Limited,
+ };
+
+ /// This is nn::settings::system::CmuMode
+ enum class CmuMode : u32 {
+ None,
+ ColorInvert,
+ HighContrast,
+ GrayScale,
+ };
+
+ /// This is nn::settings::system::PrimaryAlbumStorage
+ enum class PrimaryAlbumStorage : u32 {
+ Nand,
+ SdCard,
+ };
+
+ /// This is nn::settings::system::NotificationVolume
+ enum class NotificationVolume : u32 {
+ Mute,
+ Low,
+ High,
+ };
+
+ /// This is nn::settings::system::ChineseTraditionalInputMethod
+ enum class ChineseTraditionalInputMethod : u32 {
+ Unknown0 = 0,
+ Unknown1 = 1,
+ Unknown2 = 2,
+ };
+
+ /// This is nn::settings::system::ErrorReportSharePermission
+ enum class ErrorReportSharePermission : u32 {
+ NotConfirmed,
+ Granted,
+ Denied,
+ };
+
+ /// This is nn::settings::system::FriendPresenceOverlayPermission
+ enum class FriendPresenceOverlayPermission : u8 {
+ NotConfirmed,
+ NoDisplay,
+ FavoriteFriends,
+ Friends,
+ };
+
+ /// This is nn::settings::system::HandheldSleepPlan
+ enum class HandheldSleepPlan : u32 {
+ Sleep1Min,
+ Sleep3Min,
+ Sleep5Min,
+ Sleep10Min,
+ Sleep30Min,
+ Never,
+ };
+
+ /// This is nn::settings::system::ConsoleSleepPlan
+ enum class ConsoleSleepPlan : u32 {
+ Sleep1Hour,
+ Sleep2Hour,
+ Sleep3Hour,
+ Sleep6Hour,
+ Sleep12Hour,
+ Never,
+ };
+
+ /// This is nn::settings::system::RegionCode
+ enum class RegionCode : u32 {
+ Japan,
+ Usa,
+ Europe,
+ Australia,
+ HongKongTaiwanKorea,
+ China,
+ };
+
+ /// This is nn::settings::system::EulaVersionClockType
+ enum class EulaVersionClockType : u32 {
+ NetworkSystemClock,
+ SteadyClock,
+ };
+
+ /// This is nn::settings::system::SleepFlag
+ struct SleepFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> SleepsWhilePlayingMedia;
+ BitField<1, 1, u32> WakesAtPowerStateChange;
+ };
+ };
+ static_assert(sizeof(SleepFlag) == 4, "TvFlag is an invalid size");
+
+ /// This is nn::settings::system::TvFlag
+ struct TvFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> Allows4k;
+ BitField<1, 1, u32> Allows3d;
+ BitField<2, 1, u32> AllowsCec;
+ BitField<3, 1, u32> PreventsScreenBurnIn;
+ };
+ };
+ static_assert(sizeof(TvFlag) == 4, "TvFlag is an invalid size");
+
+ /// This is nn::settings::system::InitialLaunchFlag
+ struct InitialLaunchFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> InitialLaunchCompletionFlag;
+ BitField<8, 1, u32> InitialLaunchUserAdditionFlag;
+ BitField<16, 1, u32> InitialLaunchTimestampFlag;
+ };
+ };
+ static_assert(sizeof(InitialLaunchFlag) == 4, "InitialLaunchFlag is an invalid size");
+
+ /// This is nn::settings::system::NotificationFlag
+ struct NotificationFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> RingtoneFlag;
+ BitField<1, 1, u32> DownloadCompletionFlag;
+ BitField<8, 1, u32> EnablesNews;
+ BitField<9, 1, u32> IncomingLampFlag;
+ };
+ };
+ static_assert(sizeof(NotificationFlag) == 4, "NotificationFlag is an invalid size");
+
+ /// This is nn::settings::system::AccountNotificationFlag
+ struct AccountNotificationFlag {
+ union {
+ u32 raw{};
+
+ BitField<0, 1, u32> FriendOnlineFlag;
+ BitField<1, 1, u32> FriendRequestFlag;
+ BitField<8, 1, u32> CoralInvitationFlag;
+ };
+ };
+ static_assert(sizeof(AccountNotificationFlag) == 4,
+ "AccountNotificationFlag is an invalid size");
+
+ /// This is nn::settings::system::TvSettings
+ struct TvSettings {
+ TvFlag flags;
+ TvResolution tv_resolution;
+ HdmiContentType hdmi_content_type;
+ RgbRange rgb_range;
+ CmuMode cmu_mode;
+ u32 tv_underscan;
+ f32 tv_gama;
+ f32 constrast_ratio;
+ };
+ static_assert(sizeof(TvSettings) == 0x20, "TvSettings is an invalid size");
+
+ /// This is nn::settings::system::NotificationTime
+ struct NotificationTime {
+ u32 hour;
+ u32 minute;
+ };
+ static_assert(sizeof(NotificationTime) == 0x8, "NotificationTime is an invalid size");
+
+ /// This is nn::settings::system::NotificationSettings
+ struct NotificationSettings {
+ NotificationFlag flags;
+ NotificationVolume volume;
+ NotificationTime start_time;
+ NotificationTime stop_time;
+ };
+ static_assert(sizeof(NotificationSettings) == 0x18, "NotificationSettings is an invalid size");
+
+ /// This is nn::settings::system::AccountSettings
+ struct AccountSettings {
+ u32 flags;
+ };
+ static_assert(sizeof(AccountSettings) == 0x4, "AccountSettings is an invalid size");
+
+ /// This is nn::settings::system::AccountNotificationSettings
+ struct AccountNotificationSettings {
+ Common::UUID uid;
+ AccountNotificationFlag flags;
+ FriendPresenceOverlayPermission friend_presence_permission;
+ FriendPresenceOverlayPermission friend_invitation_permission;
+ INSERT_PADDING_BYTES(0x2);
+ };
+ static_assert(sizeof(AccountNotificationSettings) == 0x18,
+ "AccountNotificationSettings is an invalid size");
+
+ /// This is nn::settings::system::InitialLaunchSettings
+ struct SleepSettings {
+ SleepFlag flags;
+ HandheldSleepPlan handheld_sleep_plan;
+ ConsoleSleepPlan console_sleep_plan;
+ };
+ static_assert(sizeof(SleepSettings) == 0xc, "SleepSettings is incorrect size");
+
+ /// This is nn::settings::system::InitialLaunchSettings
+ struct InitialLaunchSettings {
+ InitialLaunchFlag flags;
+ INSERT_PADDING_BYTES(0x4);
+ Time::Clock::SteadyClockTimePoint timestamp;
+ };
+ static_assert(sizeof(InitialLaunchSettings) == 0x20, "InitialLaunchSettings is incorrect size");
+
+ /// This is nn::settings::system::InitialLaunchSettings
+ struct EulaVersion {
+ u32 version;
+ RegionCode region_code;
+ EulaVersionClockType clock_type;
+ INSERT_PADDING_BYTES(0x4);
+ s64 posix_time;
+ Time::Clock::SteadyClockTimePoint timestamp;
+ };
+ static_assert(sizeof(EulaVersion) == 0x30, "EulaVersion is incorrect size");
+
+ void SetLanguageCode(HLERequestContext& ctx);
void GetFirmwareVersion(HLERequestContext& ctx);
void GetFirmwareVersion2(HLERequestContext& ctx);
+ void GetAccountSettings(HLERequestContext& ctx);
+ void SetAccountSettings(HLERequestContext& ctx);
+ void GetEulaVersions(HLERequestContext& ctx);
+ void SetEulaVersions(HLERequestContext& ctx);
void GetColorSetId(HLERequestContext& ctx);
void SetColorSetId(HLERequestContext& ctx);
+ void GetNotificationSettings(HLERequestContext& ctx);
+ void SetNotificationSettings(HLERequestContext& ctx);
+ void GetAccountNotificationSettings(HLERequestContext& ctx);
+ void SetAccountNotificationSettings(HLERequestContext& ctx);
+ void GetSettingsItemValueSize(HLERequestContext& ctx);
+ void GetSettingsItemValue(HLERequestContext& ctx);
+ void GetTvSettings(HLERequestContext& ctx);
+ void SetTvSettings(HLERequestContext& ctx);
+ void GetQuestFlag(HLERequestContext& ctx);
+ void SetRegionCode(HLERequestContext& ctx);
+ void GetPrimaryAlbumStorage(HLERequestContext& ctx);
+ void GetSleepSettings(HLERequestContext& ctx);
+ void SetSleepSettings(HLERequestContext& ctx);
+ void GetInitialLaunchSettings(HLERequestContext& ctx);
+ void SetInitialLaunchSettings(HLERequestContext& ctx);
void GetDeviceNickName(HLERequestContext& ctx);
+ void SetDeviceNickName(HLERequestContext& ctx);
+ void GetProductModel(HLERequestContext& ctx);
+ void GetMiiAuthorId(HLERequestContext& ctx);
+ void GetAutoUpdateEnableFlag(HLERequestContext& ctx);
+ void GetBatteryPercentageFlag(HLERequestContext& ctx);
+ void GetErrorReportSharePermission(HLERequestContext& ctx);
+ void GetAppletLaunchFlags(HLERequestContext& ctx);
+ void SetAppletLaunchFlags(HLERequestContext& ctx);
+ void GetKeyboardLayout(HLERequestContext& ctx);
+ void GetChineseTraditionalInputMethod(HLERequestContext& ctx);
+ void GetFieldTestingFlag(HLERequestContext& ctx);
+
+ AccountSettings account_settings{
+ .flags = {},
+ };
ColorSet color_set = ColorSet::BasicWhite;
+
+ NotificationSettings notification_settings{
+ .flags = {0x300},
+ .volume = NotificationVolume::High,
+ .start_time = {.hour = 9, .minute = 0},
+ .stop_time = {.hour = 21, .minute = 0},
+ };
+
+ std::vector<AccountNotificationSettings> account_notifications{};
+
+ TvSettings tv_settings{
+ .flags = {0xc},
+ .tv_resolution = TvResolution::Auto,
+ .hdmi_content_type = HdmiContentType::Game,
+ .rgb_range = RgbRange::Auto,
+ .cmu_mode = CmuMode::None,
+ .tv_underscan = {},
+ .tv_gama = 1.0f,
+ .constrast_ratio = 0.5f,
+ };
+
+ InitialLaunchSettings launch_settings{
+ .flags = {0x10001},
+ .timestamp = {},
+ };
+
+ SleepSettings sleep_settings{
+ .flags = {0x3},
+ .handheld_sleep_plan = HandheldSleepPlan::Sleep10Min,
+ .console_sleep_plan = ConsoleSleepPlan::Sleep1Hour,
+ };
+
+ u32 applet_launch_flag{};
+
+ std::vector<EulaVersion> eula_versions{};
+
+ RegionCode region_code;
+
+ LanguageCode language_code_setting;
};
} // namespace Service::Set
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 1608fa24c..9ab718e0a 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -52,8 +52,7 @@ static Result ValidateServiceName(const std::string& name) {
Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
SessionRequestHandlerPtr handler) {
-
- CASCADE_CODE(ValidateServiceName(name));
+ R_TRY(ValidateServiceName(name));
std::scoped_lock lk{lock};
if (registered_services.find(name) != registered_services.end()) {
@@ -77,7 +76,7 @@ Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
}
Result ServiceManager::UnregisterService(const std::string& name) {
- CASCADE_CODE(ValidateServiceName(name));
+ R_TRY(ValidateServiceName(name));
std::scoped_lock lk{lock};
const auto iter = registered_services.find(name);
@@ -92,8 +91,8 @@ Result ServiceManager::UnregisterService(const std::string& name) {
return ResultSuccess;
}
-ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name) {
- CASCADE_CODE(ValidateServiceName(name));
+Result ServiceManager::GetServicePort(Kernel::KPort** out_port, const std::string& name) {
+ R_TRY(ValidateServiceName(name));
std::scoped_lock lk{lock};
auto it = service_ports.find(name);
@@ -102,7 +101,8 @@ ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name
return Service::SM::ResultNotRegistered;
}
- return it->second;
+ *out_port = it->second;
+ return ResultSuccess;
}
/**
@@ -122,32 +122,34 @@ void SM::Initialize(HLERequestContext& ctx) {
}
void SM::GetService(HLERequestContext& ctx) {
- auto result = GetServiceImpl(ctx);
+ Kernel::KClientSession* client_session{};
+ auto result = GetServiceImpl(&client_session, ctx);
if (ctx.GetIsDeferred()) {
// Don't overwrite the command buffer.
return;
}
- if (result.Succeeded()) {
+ if (result == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
- rb.Push(result.Code());
- rb.PushMoveObjects(result.Unwrap());
+ rb.Push(result);
+ rb.PushMoveObjects(client_session);
} else {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
}
}
void SM::GetServiceTipc(HLERequestContext& ctx) {
- auto result = GetServiceImpl(ctx);
+ Kernel::KClientSession* client_session{};
+ auto result = GetServiceImpl(&client_session, ctx);
if (ctx.GetIsDeferred()) {
// Don't overwrite the command buffer.
return;
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
- rb.Push(result.Code());
- rb.PushMoveObjects(result.Succeeded() ? result.Unwrap() : nullptr);
+ rb.Push(result);
+ rb.PushMoveObjects(result == ResultSuccess ? client_session : nullptr);
}
static std::string PopServiceName(IPC::RequestParser& rp) {
@@ -161,7 +163,7 @@ static std::string PopServiceName(IPC::RequestParser& rp) {
return result;
}
-ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) {
+Result SM::GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx) {
if (!ctx.GetManager()->GetIsInitializedForSm()) {
return Service::SM::ResultInvalidClient;
}
@@ -170,18 +172,18 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) {
std::string name(PopServiceName(rp));
// Find the named port.
- auto port_result = service_manager.GetServicePort(name);
- if (port_result.Code() == Service::SM::ResultInvalidServiceName) {
+ Kernel::KPort* port{};
+ auto port_result = service_manager.GetServicePort(&port, name);
+ if (port_result == Service::SM::ResultInvalidServiceName) {
LOG_ERROR(Service_SM, "Invalid service name '{}'", name);
return Service::SM::ResultInvalidServiceName;
}
- if (port_result.Failed()) {
+ if (port_result != ResultSuccess) {
LOG_INFO(Service_SM, "Waiting for service {} to become available", name);
ctx.SetIsDeferred();
return Service::SM::ResultNotRegistered;
}
- auto& port = port_result.Unwrap();
// Create a new session.
Kernel::KClientSession* session{};
@@ -192,7 +194,8 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) {
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId());
- return session;
+ *out_client_session = session;
+ return ResultSuccess;
}
void SM::RegisterService(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 6697f4007..14bfaf8c2 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -42,7 +42,7 @@ private:
void RegisterService(HLERequestContext& ctx);
void UnregisterService(HLERequestContext& ctx);
- ResultVal<Kernel::KClientSession*> GetServiceImpl(HLERequestContext& ctx);
+ Result GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx);
ServiceManager& service_manager;
Kernel::KernelCore& kernel;
@@ -55,7 +55,7 @@ public:
Result RegisterService(std::string name, u32 max_sessions, SessionRequestHandlerPtr handler);
Result UnregisterService(const std::string& name);
- ResultVal<Kernel::KPort*> GetServicePort(const std::string& name);
+ Result GetServicePort(Kernel::KPort** out_port, const std::string& name);
template <Common::DerivedFrom<SessionRequestHandler> T>
std::shared_ptr<T> GetService(const std::string& service_name) const {
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 11f8efbac..85849d5f3 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) {
}
void BSD::Select(HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ LOG_DEBUG(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -1029,6 +1029,11 @@ BSD::~BSD() {
}
}
+std::unique_lock<std::mutex> BSD::LockService() {
+ // Do not lock socket IClient instances.
+ return {};
+}
+
BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} {
// clang-format off
static const FunctionInfo functions[] = {
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 430edb97c..161f22b9b 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -186,6 +186,9 @@ private:
// Callback identifier for the OnProxyPacketReceived event.
Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received;
+
+protected:
+ virtual std::unique_lock<std::mutex> LockService() override;
};
class BSDCFG final : public ServiceFramework<BSDCFG> {
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
index 5dfcaabb1..491b76d48 100644
--- a/src/core/hle/service/sockets/nsd.cpp
+++ b/src/core/hle/service/sockets/nsd.cpp
@@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 {
Dp,
};
+// This is nn::nsd::EnvironmentIdentifier
+struct EnvironmentIdentifier {
+ std::array<u8, 8> identifier;
+};
+static_assert(sizeof(EnvironmentIdentifier) == 0x8);
+
NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
@@ -54,7 +60,7 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
RegisterHandlers(functions);
}
-static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) {
+static std::string ResolveImpl(const std::string& fqdn_in) {
// The real implementation makes various substitutions.
// For now we just return the string as-is, which is good enough when not
// connecting to real Nintendo servers.
@@ -64,13 +70,10 @@ static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) {
static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) {
const auto res = ResolveImpl(fqdn_in);
- if (res.Failed()) {
- return res.Code();
- }
- if (res->size() >= fqdn_out.size()) {
+ if (res.size() >= fqdn_out.size()) {
return ResultOverflow;
}
- std::memcpy(fqdn_out.data(), res->c_str(), res->size() + 1);
+ std::memcpy(fqdn_out.data(), res.c_str(), res.size() + 1);
return ResultSuccess;
}
@@ -104,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) {
}
void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) {
- const std::string environment_identifier = "lp1";
- ctx.WriteBuffer(environment_identifier);
+ constexpr EnvironmentIdentifier lp1 = {
+ .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}};
+ ctx.WriteBuffer(lp1);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index 22e4a6f49..c657c4efd 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -150,6 +150,12 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
const std::string host = Common::StringFromBuffer(host_buffer);
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
+ // Prevent resolution of Nintendo servers
+ if (host.find("srv.nintendo.net") != std::string::npos) {
+ LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
+ return {0, GetAddrInfoError::AGAIN};
+ }
+
auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt);
if (!res.has_value()) {
return {0, Translate(res.error())};
@@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
const auto host_buffer = ctx.ReadBuffer(0);
const std::string host = Common::StringFromBuffer(host_buffer);
+ // Prevent resolution of Nintendo servers
+ if (host.find("srv.nintendo.net") != std::string::npos) {
+ LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
+ return {0, GetAddrInfoError::AGAIN};
+ }
+
std::optional<std::string> service = std::nullopt;
if (ctx.CanReadBuffer(1)) {
const std::span<const u8> service_buffer = ctx.ReadBuffer(1);
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index 77426c46e..f86af01a4 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -18,7 +18,9 @@ enum class Errno : u32 {
AGAIN = 11,
INVAL = 22,
MFILE = 24,
+ PIPE = 32,
MSGSIZE = 90,
+ CONNABORTED = 103,
CONNRESET = 104,
NOTCONN = 107,
TIMEDOUT = 110,
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
index c1187209f..aed05250c 100644
--- a/src/core/hle/service/sockets/sockets_translate.cpp
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) {
return Errno::INVAL;
case Network::Errno::MFILE:
return Errno::MFILE;
+ case Network::Errno::PIPE:
+ return Errno::PIPE;
case Network::Errno::NOTCONN:
return Errno::NOTCONN;
case Network::Errno::TIMEDOUT:
return Errno::TIMEDOUT;
+ case Network::Errno::CONNABORTED:
+ return Errno::CONNABORTED;
case Network::Errno::CONNRESET:
return Errno::CONNRESET;
case Network::Errno::INPROGRESS:
diff --git a/src/core/hle/service/spl/spl_module.cpp b/src/core/hle/service/spl/spl_module.cpp
index 0227d4393..549e6f4fa 100644
--- a/src/core/hle/service/spl/spl_module.cpp
+++ b/src/core/hle/service/spl/spl_module.cpp
@@ -19,7 +19,8 @@ namespace Service::SPL {
Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
const char* name)
: ServiceFramework{system_, name}, module{std::move(module_)},
- rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {}
+ rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
+ : static_cast<u32>(std::time(nullptr))) {}
Module::Interface::~Interface() = default;
@@ -29,10 +30,10 @@ void Module::Interface::GetConfig(HLERequestContext& ctx) {
// This should call svcCallSecureMonitor with the appropriate args.
// Since we do not have it implemented yet, we will use this for now.
- const auto smc_result = GetConfigImpl(config_item);
- const auto result_code = smc_result.Code();
+ u64 smc_result{};
+ const auto result_code = GetConfigImpl(&smc_result, config_item);
- if (smc_result.Failed()) {
+ if (result_code != ResultSuccess) {
LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item,
result_code.raw);
@@ -41,11 +42,11 @@ void Module::Interface::GetConfig(HLERequestContext& ctx) {
}
LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item,
- result_code.raw, *smc_result);
+ result_code.raw, smc_result);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(result_code);
- rb.Push(*smc_result);
+ rb.Push(smc_result);
}
void Module::Interface::ModularExponentiate(HLERequestContext& ctx) {
@@ -98,7 +99,7 @@ void Module::Interface::GetBootReason(HLERequestContext& ctx) {
rb.Push(ResultSecureMonitorNotImplemented);
}
-ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const {
+Result Module::Interface::GetConfigImpl(u64* out_config, ConfigItem config_item) const {
switch (config_item) {
case ConfigItem::DisableProgramVerification:
case ConfigItem::DramId:
@@ -120,40 +121,50 @@ ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const {
return ResultSecureMonitorNotImplemented;
case ConfigItem::ExosphereApiVersion:
// Get information about the current exosphere version.
- return (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) |
- (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) |
- (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) |
- (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware()));
+ *out_config = (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) |
+ (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) |
+ (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) |
+ (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware()));
+ return ResultSuccess;
case ConfigItem::ExosphereNeedsReboot:
// We are executing, so we aren't in the process of rebooting.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereNeedsShutdown:
// We are executing, so we aren't in the process of shutting down.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereGitCommitHash:
// Get information about the current exosphere git commit hash.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereHasRcmBugPatch:
// Get information about whether this unit has the RCM bug patched.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereBlankProdInfo:
// Get whether this unit should simulate a "blanked" PRODINFO.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereAllowCalWrites:
// Get whether this unit should allow writing to the calibration partition.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereEmummcType:
// Get what kind of emummc this unit has active.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExospherePayloadAddress:
// Gets the physical address of the reboot payload buffer, if one exists.
return ResultSecureMonitorNotInitialized;
case ConfigItem::ExosphereLogConfiguration:
// Get the log configuration.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
case ConfigItem::ExosphereForceEnableUsb30:
// Get whether usb 3.0 should be force-enabled.
- return u64{0};
+ *out_config = u64{0};
+ return ResultSuccess;
default:
return ResultSecureMonitorInvalidArgument;
}
diff --git a/src/core/hle/service/spl/spl_module.h b/src/core/hle/service/spl/spl_module.h
index e074e115d..06dcffa6c 100644
--- a/src/core/hle/service/spl/spl_module.h
+++ b/src/core/hle/service/spl/spl_module.h
@@ -35,7 +35,7 @@ public:
std::shared_ptr<Module> module;
private:
- ResultVal<u64> GetConfigImpl(ConfigItem config_item) const;
+ Result GetConfigImpl(u64* out_config, ConfigItem config_item) const;
std::mt19937 rng;
};
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index 9c96f9763..6c8427b0d 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -4,6 +4,7 @@
#include "common/string_util.h"
#include "core/core.h"
+#include "core/hle/result.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
@@ -138,15 +139,14 @@ private:
bool do_not_close_socket = false;
bool get_server_cert_chain = false;
std::shared_ptr<Network::SocketBase> socket;
- bool did_set_host_name = false;
bool did_handshake = false;
- ResultVal<s32> SetSocketDescriptorImpl(s32 fd) {
+ Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
LOG_DEBUG(Service_SSL, "called, fd={}", fd);
ASSERT(!did_handshake);
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
- s32 ret_fd;
+
// Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor
if (do_not_close_socket) {
auto res = bsd->DuplicateSocketImpl(fd);
@@ -156,9 +156,9 @@ private:
}
fd = *res;
fd_to_close = fd;
- ret_fd = fd;
+ *out_fd = fd;
} else {
- ret_fd = -1;
+ *out_fd = -1;
}
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(fd);
if (!sock.has_value()) {
@@ -167,17 +167,13 @@ private:
}
socket = std::move(*sock);
backend->SetSocket(socket);
- return ret_fd;
+ return ResultSuccess;
}
Result SetHostNameImpl(const std::string& hostname) {
LOG_DEBUG(Service_SSL, "called. hostname={}", hostname);
ASSERT(!did_handshake);
- Result res = backend->SetHostName(hostname);
- if (res == ResultSuccess) {
- did_set_host_name = true;
- }
- return res;
+ return backend->SetHostName(hostname);
}
Result SetVerifyOptionImpl(u32 option) {
@@ -207,9 +203,6 @@ private:
Result DoHandshakeImpl() {
ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; });
- ASSERT_OR_EXECUTE_MSG(
- did_set_host_name, { return ResultInternalError; },
- "Expected SetHostName before DoHandshake");
Result res = backend->DoHandshake();
did_handshake = res.IsSuccess();
return res;
@@ -247,34 +240,36 @@ private:
return ret;
}
- ResultVal<std::vector<u8>> ReadImpl(size_t size) {
+ Result ReadImpl(std::vector<u8>* out_data, size_t size) {
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
- std::vector<u8> res(size);
- ResultVal<size_t> actual = backend->Read(res);
- if (actual.Failed()) {
- return actual.Code();
+ size_t actual_size{};
+ Result res = backend->Read(&actual_size, *out_data);
+ if (res != ResultSuccess) {
+ return res;
}
- res.resize(*actual);
+ out_data->resize(actual_size);
return res;
}
- ResultVal<size_t> WriteImpl(std::span<const u8> data) {
+ Result WriteImpl(size_t* out_size, std::span<const u8> data) {
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
- return backend->Write(data);
+ return backend->Write(out_size, data);
}
- ResultVal<s32> PendingImpl() {
+ Result PendingImpl(s32* out_pending) {
LOG_WARNING(Service_SSL, "(STUBBED) called.");
- return 0;
+ *out_pending = 0;
+ return ResultSuccess;
}
void SetSocketDescriptor(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const s32 fd = rp.Pop<s32>();
- const ResultVal<s32> res = SetSocketDescriptorImpl(fd);
+ const s32 in_fd = rp.Pop<s32>();
+ s32 out_fd{-1};
+ const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(res.Code());
- rb.Push<s32>(res.ValueOr(-1));
+ rb.Push(res);
+ rb.Push<s32>(out_fd);
}
void SetHostName(HLERequestContext& ctx) {
@@ -313,14 +308,15 @@ private:
};
static_assert(sizeof(OutputParameters) == 0x8);
- const Result res = DoHandshakeImpl();
+ Result res = DoHandshakeImpl();
OutputParameters out{};
if (res == ResultSuccess) {
- auto certs = backend->GetServerCerts();
- if (certs.Succeeded()) {
- const std::vector<u8> certs_buf = SerializeServerCerts(*certs);
+ std::vector<std::vector<u8>> certs;
+ res = backend->GetServerCerts(&certs);
+ if (res == ResultSuccess) {
+ const std::vector<u8> certs_buf = SerializeServerCerts(certs);
ctx.WriteBuffer(certs_buf);
- out.certs_count = static_cast<u32>(certs->size());
+ out.certs_count = static_cast<u32>(certs.size());
out.certs_size = static_cast<u32>(certs_buf.size());
}
}
@@ -330,29 +326,32 @@ private:
}
void Read(HLERequestContext& ctx) {
- const ResultVal<std::vector<u8>> res = ReadImpl(ctx.GetWriteBufferSize());
+ std::vector<u8> output_bytes;
+ const Result res = ReadImpl(&output_bytes, ctx.GetWriteBufferSize());
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(res.Code());
- if (res.Succeeded()) {
- rb.Push(static_cast<u32>(res->size()));
- ctx.WriteBuffer(*res);
+ rb.Push(res);
+ if (res == ResultSuccess) {
+ rb.Push(static_cast<u32>(output_bytes.size()));
+ ctx.WriteBuffer(output_bytes);
} else {
rb.Push(static_cast<u32>(0));
}
}
void Write(HLERequestContext& ctx) {
- const ResultVal<size_t> res = WriteImpl(ctx.ReadBuffer());
+ size_t write_size{0};
+ const Result res = WriteImpl(&write_size, ctx.ReadBuffer());
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(res.Code());
- rb.Push(static_cast<u32>(res.ValueOr(0)));
+ rb.Push(res);
+ rb.Push(static_cast<u32>(write_size));
}
void Pending(HLERequestContext& ctx) {
- const ResultVal<s32> res = PendingImpl();
+ s32 pending_size{0};
+ const Result res = PendingImpl(&pending_size);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(res.Code());
- rb.Push<s32>(res.ValueOr(0));
+ rb.Push(res);
+ rb.Push<s32>(pending_size);
}
void SetSessionCacheMode(HLERequestContext& ctx) {
@@ -438,13 +437,14 @@ private:
void CreateConnection(HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "called");
- auto backend_res = CreateSSLConnectionBackend();
+ std::unique_ptr<SSLConnectionBackend> backend;
+ const Result res = CreateSSLConnectionBackend(&backend);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(backend_res.Code());
- if (backend_res.Succeeded()) {
+ rb.Push(res);
+ if (res == ResultSuccess) {
rb.PushIpcInterface<ISslConnection>(system, ssl_version, shared_data,
- std::move(*backend_res));
+ std::move(backend));
}
}
diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h
index 409f4367c..a2ec8e694 100644
--- a/src/core/hle/service/ssl/ssl_backend.h
+++ b/src/core/hle/service/ssl/ssl_backend.h
@@ -35,11 +35,11 @@ public:
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
virtual Result SetHostName(const std::string& hostname) = 0;
virtual Result DoHandshake() = 0;
- virtual ResultVal<size_t> Read(std::span<u8> data) = 0;
- virtual ResultVal<size_t> Write(std::span<const u8> data) = 0;
- virtual ResultVal<std::vector<std::vector<u8>>> GetServerCerts() = 0;
+ virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
+ virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
+ virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
};
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend();
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
} // namespace Service::SSL
diff --git a/src/core/hle/service/ssl/ssl_backend_none.cpp b/src/core/hle/service/ssl/ssl_backend_none.cpp
index 2f4f23c42..a7fafd0a3 100644
--- a/src/core/hle/service/ssl/ssl_backend_none.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_none.cpp
@@ -7,7 +7,7 @@
namespace Service::SSL {
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
LOG_ERROR(Service_SSL,
"Can't create SSL connection because no SSL backend is available on this platform");
return ResultInternalError;
diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp
index 6ca869dbf..5714e6f3c 100644
--- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp
@@ -105,31 +105,30 @@ public:
return ResultInternalError;
}
}
- return HandleReturn("SSL_do_handshake", 0, ret).Code();
+ return HandleReturn("SSL_do_handshake", 0, ret);
}
- ResultVal<size_t> Read(std::span<u8> data) override {
- size_t actual;
- const int ret = SSL_read_ex(ssl, data.data(), data.size(), &actual);
- return HandleReturn("SSL_read_ex", actual, ret);
+ Result Read(size_t* out_size, std::span<u8> data) override {
+ const int ret = SSL_read_ex(ssl, data.data(), data.size(), out_size);
+ return HandleReturn("SSL_read_ex", out_size, ret);
}
- ResultVal<size_t> Write(std::span<const u8> data) override {
- size_t actual;
- const int ret = SSL_write_ex(ssl, data.data(), data.size(), &actual);
- return HandleReturn("SSL_write_ex", actual, ret);
+ Result Write(size_t* out_size, std::span<const u8> data) override {
+ const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
+ return HandleReturn("SSL_write_ex", out_size, ret);
}
- ResultVal<size_t> HandleReturn(const char* what, size_t actual, int ret) {
+ Result HandleReturn(const char* what, size_t* actual, int ret) {
const int ssl_err = SSL_get_error(ssl, ret);
CheckOpenSSLErrors();
switch (ssl_err) {
case SSL_ERROR_NONE:
- return actual;
+ return ResultSuccess;
case SSL_ERROR_ZERO_RETURN:
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what);
// DoHandshake special-cases this, but for Read and Write:
- return size_t(0);
+ *actual = 0;
+ return ResultSuccess;
case SSL_ERROR_WANT_READ:
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what);
return ResultWouldBlock;
@@ -139,20 +138,20 @@ public:
default:
if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) {
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what);
- return size_t(0);
+ *actual = 0;
+ return ResultSuccess;
}
LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err);
return ResultInternalError;
}
}
- ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
+ Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
if (!chain) {
LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr");
return ResultInternalError;
}
- std::vector<std::vector<u8>> ret;
int count = sk_X509_num(chain);
ASSERT(count >= 0);
for (int i = 0; i < count; i++) {
@@ -161,16 +160,15 @@ public:
unsigned char* buf = nullptr;
int len = i2d_X509(x509, &buf);
ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; });
- ret.emplace_back(buf, buf + len);
+ out_certs->emplace_back(buf, buf + len);
OPENSSL_free(buf);
}
- return ret;
+ return ResultSuccess;
}
~SSLConnectionBackendOpenSSL() {
- // these are null-tolerant:
+ // this is null-tolerant:
SSL_free(ssl);
- BIO_free(bio);
}
static void KeyLogCallback(const SSL* ssl, const char* line) {
@@ -253,13 +251,13 @@ public:
std::shared_ptr<Network::SocketBase> socket;
};
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendOpenSSL>();
- const Result res = conn->Init();
- if (res.IsFailure()) {
- return res;
- }
- return conn;
+
+ R_TRY(conn->Init());
+
+ *out_backend = std::move(conn);
+ return ResultSuccess;
}
namespace {
diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp
index d8074339a..212057cfc 100644
--- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp
@@ -31,9 +31,9 @@ CredHandle cred_handle;
static void OneTimeInit() {
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
schannel_cred.dwFlags =
- SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
- SCH_CRED_AUTO_CRED_VALIDATION | // validate certs
- SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
+ SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
+ SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names
+ SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
// ^ I'm assuming that nobody would want to connect Yuzu to a
// service that requires some OS-provided corporate client
// certificate, and presenting one to some arbitrary server
@@ -227,16 +227,15 @@ public:
ciphertext_read_buf.size());
}
- const SECURITY_STATUS ret =
- InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt : nullptr,
- // Caller ensured we have set a hostname:
- const_cast<char*>(hostname.value().c_str()), req,
- 0, // Reserved1
- 0, // TargetDataRep not used with Schannel
- initial_call_done ? &input_desc : nullptr,
- 0, // Reserved2
- initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
- nullptr); // ptsExpiry
+ char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr;
+ const SECURITY_STATUS ret = InitializeSecurityContextA(
+ &cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req,
+ 0, // Reserved1
+ 0, // TargetDataRep not used with Schannel
+ initial_call_done ? &input_desc : nullptr,
+ 0, // Reserved2
+ initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
+ nullptr); // ptsExpiry
if (output_buffers[0].pvBuffer) {
const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
@@ -299,21 +298,22 @@ public:
return ResultSuccess;
}
- ResultVal<size_t> Read(std::span<u8> data) override {
+ Result Read(size_t* out_size, std::span<u8> data) override {
+ *out_size = 0;
if (handshake_state != HandshakeState::Connected) {
LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake");
return ResultInternalError;
}
if (data.size() == 0 || got_read_eof) {
- return size_t(0);
+ return ResultSuccess;
}
while (1) {
if (!cleartext_read_buf.empty()) {
- const size_t read_size = std::min(cleartext_read_buf.size(), data.size());
- std::memcpy(data.data(), cleartext_read_buf.data(), read_size);
+ *out_size = std::min(cleartext_read_buf.size(), data.size());
+ std::memcpy(data.data(), cleartext_read_buf.data(), *out_size);
cleartext_read_buf.erase(cleartext_read_buf.begin(),
- cleartext_read_buf.begin() + read_size);
- return read_size;
+ cleartext_read_buf.begin() + *out_size);
+ return ResultSuccess;
}
if (!ciphertext_read_buf.empty()) {
SecBuffer empty{
@@ -366,7 +366,8 @@ public:
case SEC_I_CONTEXT_EXPIRED:
// Server hung up by sending close_notify.
got_read_eof = true;
- return size_t(0);
+ *out_size = 0;
+ return ResultSuccess;
default:
LOG_ERROR(Service_SSL, "DecryptMessage failed: {}",
Common::NativeErrorToString(ret));
@@ -379,18 +380,21 @@ public:
}
if (ciphertext_read_buf.empty()) {
got_read_eof = true;
- return size_t(0);
+ *out_size = 0;
+ return ResultSuccess;
}
}
}
- ResultVal<size_t> Write(std::span<const u8> data) override {
+ Result Write(size_t* out_size, std::span<const u8> data) override {
+ *out_size = 0;
+
if (handshake_state != HandshakeState::Connected) {
LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake");
return ResultInternalError;
}
if (data.size() == 0) {
- return size_t(0);
+ return ResultSuccess;
}
data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage));
if (!cleartext_write_buf.empty()) {
@@ -402,7 +406,7 @@ public:
LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer");
return ResultInternalError;
}
- return WriteAlreadyEncryptedData();
+ return WriteAlreadyEncryptedData(out_size);
} else {
cleartext_write_buf.assign(data.begin(), data.end());
}
@@ -448,21 +452,21 @@ public:
tmp_data_buf.end());
ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(),
trailer_buf.end());
- return WriteAlreadyEncryptedData();
+ return WriteAlreadyEncryptedData(out_size);
}
- ResultVal<size_t> WriteAlreadyEncryptedData() {
+ Result WriteAlreadyEncryptedData(size_t* out_size) {
const Result r = FlushCiphertextWriteBuf();
if (r != ResultSuccess) {
return r;
}
// write buf is empty
- const size_t cleartext_bytes_written = cleartext_write_buf.size();
+ *out_size = cleartext_write_buf.size();
cleartext_write_buf.clear();
- return cleartext_bytes_written;
+ return ResultSuccess;
}
- ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
+ Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
PCCERT_CONTEXT returned_cert = nullptr;
const SECURITY_STATUS ret =
QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert);
@@ -473,16 +477,16 @@ public:
return ResultInternalError;
}
PCCERT_CONTEXT some_cert = nullptr;
- std::vector<std::vector<u8>> certs;
- while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) {
- certs.emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
- static_cast<u8*>(some_cert->pbCertEncoded) +
- some_cert->cbCertEncoded);
+ while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) !=
+ nullptr) {
+ out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
+ static_cast<u8*>(some_cert->pbCertEncoded) +
+ some_cert->cbCertEncoded);
}
- std::reverse(certs.begin(),
- certs.end()); // Windows returns certs in reverse order from what we want
+ std::reverse(out_certs->begin(),
+ out_certs->end()); // Windows returns certs in reverse order from what we want
CertFreeCertificateContext(returned_cert);
- return certs;
+ return ResultSuccess;
}
~SSLConnectionBackendSchannel() {
@@ -532,13 +536,13 @@ public:
size_t read_buf_fill_size = 0;
};
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendSchannel>();
- const Result res = conn->Init();
- if (res.IsFailure()) {
- return res;
- }
- return conn;
+
+ R_TRY(conn->Init());
+
+ *out_backend = std::move(conn);
+ return ResultSuccess;
}
} // namespace Service::SSL
diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
index b3083cbad..c48914f64 100644
--- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
@@ -100,27 +100,23 @@ public:
Result DoHandshake() override {
OSStatus status = SSLHandshake(context);
- return HandleReturn("SSLHandshake", 0, status).Code();
+ return HandleReturn("SSLHandshake", 0, status);
}
- ResultVal<size_t> Read(std::span<u8> data) override {
- size_t actual;
- OSStatus status = SSLRead(context, data.data(), data.size(), &actual);
- ;
- return HandleReturn("SSLRead", actual, status);
+ Result Read(size_t* out_size, std::span<u8> data) override {
+ OSStatus status = SSLRead(context, data.data(), data.size(), out_size);
+ return HandleReturn("SSLRead", out_size, status);
}
- ResultVal<size_t> Write(std::span<const u8> data) override {
- size_t actual;
- OSStatus status = SSLWrite(context, data.data(), data.size(), &actual);
- ;
- return HandleReturn("SSLWrite", actual, status);
+ Result Write(size_t* out_size, std::span<const u8> data) override {
+ OSStatus status = SSLWrite(context, data.data(), data.size(), out_size);
+ return HandleReturn("SSLWrite", out_size, status);
}
- ResultVal<size_t> HandleReturn(const char* what, size_t actual, OSStatus status) {
+ Result HandleReturn(const char* what, size_t* actual, OSStatus status) {
switch (status) {
case 0:
- return actual;
+ return ResultSuccess;
case errSSLWouldBlock:
return ResultWouldBlock;
default: {
@@ -136,22 +132,21 @@ public:
}
}
- ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
+ Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
CFReleaser<SecTrustRef> trust;
OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
if (status) {
LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
return ResultInternalError;
}
- std::vector<std::vector<u8>> ret;
for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
const u8* ptr = CFDataGetBytePtr(data);
- ret.emplace_back(ptr, ptr + CFDataGetLength(data));
+ out_certs->emplace_back(ptr, ptr + CFDataGetLength(data));
}
- return ret;
+ return ResultSuccess;
}
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
@@ -210,13 +205,13 @@ private:
std::shared_ptr<Network::SocketBase> socket;
};
-ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
+Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
- const Result res = conn->Init();
- if (res.IsFailure()) {
- return res;
- }
- return conn;
+
+ R_TRY(conn->Init());
+
+ *out_backend = std::move(conn);
+ return ResultSuccess;
}
} // namespace Service::SSL
diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp
index 5d60be67a..1b96de37a 100644
--- a/src/core/hle/service/time/time_zone_content_manager.cpp
+++ b/src/core/hle/service/time/time_zone_content_manager.cpp
@@ -3,6 +3,7 @@
#include <chrono>
#include <sstream>
+#include <utility>
#include "common/logging/log.h"
#include "common/settings.h"
@@ -46,14 +47,14 @@ static FileSys::VirtualDir GetTimeZoneBinary(Core::System& system) {
return FileSys::ExtractRomFS(romfs);
}
-static std::vector<std::string> BuildLocationNameCache(Core::System& system) {
- const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)};
- if (!extracted_romfs) {
+static std::vector<std::string> BuildLocationNameCache(
+ const FileSys::VirtualDir& time_zone_binary) {
+ if (!time_zone_binary) {
LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid);
return {};
}
- const FileSys::VirtualFile binary_list{extracted_romfs->GetFile("binaryList.txt")};
+ const FileSys::VirtualFile binary_list{time_zone_binary->GetFile("binaryList.txt")};
if (!binary_list) {
LOG_ERROR(Service_Time, "{:016X} has no file binaryList.txt!", time_zone_binary_titleid);
return {};
@@ -73,10 +74,12 @@ static std::vector<std::string> BuildLocationNameCache(Core::System& system) {
}
TimeZoneContentManager::TimeZoneContentManager(Core::System& system_)
- : system{system_}, location_name_cache{BuildLocationNameCache(system)} {}
+ : system{system_}, time_zone_binary{GetTimeZoneBinary(system)},
+ location_name_cache{BuildLocationNameCache(time_zone_binary)} {}
void TimeZoneContentManager::Initialize(TimeManager& time_manager) {
- const auto timezone_setting = Settings::GetTimeZoneString();
+ const auto timezone_setting =
+ Settings::GetTimeZoneString(Settings::values.time_zone_index.GetValue());
if (FileSys::VirtualFile vfs_file;
GetTimeZoneInfoFile(timezone_setting, vfs_file) == ResultSuccess) {
@@ -111,13 +114,12 @@ Result TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_n
return ERROR_TIME_NOT_FOUND;
}
- const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)};
- if (!extracted_romfs) {
+ if (!time_zone_binary) {
LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid);
return ERROR_TIME_NOT_FOUND;
}
- const FileSys::VirtualDir zoneinfo_dir{extracted_romfs->GetSubdirectory("zoneinfo")};
+ const FileSys::VirtualDir zoneinfo_dir{time_zone_binary->GetSubdirectory("zoneinfo")};
if (!zoneinfo_dir) {
LOG_ERROR(Service_Time, "{:016X} has no directory zoneinfo!", time_zone_binary_titleid);
return ERROR_TIME_NOT_FOUND;
diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h
index 3d94b6428..a6f9698bc 100644
--- a/src/core/hle/service/time/time_zone_content_manager.h
+++ b/src/core/hle/service/time/time_zone_content_manager.h
@@ -6,6 +6,7 @@
#include <string>
#include <vector>
+#include "core/file_sys/vfs_types.h"
#include "core/hle/service/time/time_zone_manager.h"
namespace Core {
@@ -41,6 +42,7 @@ private:
Core::System& system;
TimeZoneManager time_zone_manager;
+ const FileSys::VirtualDir time_zone_binary;
const std::vector<std::string> location_name_cache;
};
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index 69af2868a..f0b5eff8a 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -58,14 +58,15 @@ const Layer& Display::GetLayer(std::size_t index) const {
return *layers.at(index);
}
-ResultVal<Kernel::KReadableEvent*> Display::GetVSyncEvent() {
+Result Display::GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event) {
if (got_vsync_event) {
return ResultPermissionDenied;
}
got_vsync_event = true;
- return GetVSyncEventUnchecked();
+ *out_vsync_event = GetVSyncEventUnchecked();
+ return ResultSuccess;
}
Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() {
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 3f31d1f32..101cbce20 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -85,7 +85,7 @@ public:
* @returns The internal Vsync event if it has not yet been retrieved,
* VI::ResultPermissionDenied otherwise.
*/
- [[nodiscard]] ResultVal<Kernel::KReadableEvent*> GetVSyncEvent();
+ [[nodiscard]] Result GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event);
/// Gets the internal vsync event.
Kernel::KReadableEvent* GetVSyncEventUnchecked();
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 1b193f00c..2eb978379 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -217,7 +217,7 @@ private:
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- if (Settings::values.use_docked_mode.GetValue()) {
+ if (Settings::IsDockedMode()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight));
} else {
@@ -683,9 +683,9 @@ private:
LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
- const auto vsync_event = nv_flinger.FindVsyncEvent(display_id);
- if (vsync_event.Failed()) {
- const auto result = vsync_event.Code();
+ Kernel::KReadableEvent* vsync_event{};
+ const auto result = nv_flinger.FindVsyncEvent(&vsync_event, display_id);
+ if (result != ResultSuccess) {
if (result == ResultNotFound) {
LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
}
@@ -697,7 +697,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(*vsync_event);
+ rb.PushCopyObjects(vsync_event);
}
void ConvertScalingMode(HLERequestContext& ctx) {
@@ -705,15 +705,16 @@ private:
const auto mode = rp.PopEnum<NintendoScaleMode>();
LOG_DEBUG(Service_VI, "called mode={}", mode);
- const auto converted_mode = ConvertScalingModeImpl(mode);
+ ConvertedScaleMode converted_mode{};
+ const auto result = ConvertScalingModeImpl(&converted_mode, mode);
- if (converted_mode.Succeeded()) {
+ if (result == ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.PushEnum(*converted_mode);
+ rb.PushEnum(converted_mode);
} else {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(converted_mode.Code());
+ rb.Push(result);
}
}
@@ -760,18 +761,24 @@ private:
rb.Push(alignment);
}
- static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) {
+ static Result ConvertScalingModeImpl(ConvertedScaleMode* out_scaling_mode,
+ NintendoScaleMode mode) {
switch (mode) {
case NintendoScaleMode::None:
- return ConvertedScaleMode::None;
+ *out_scaling_mode = ConvertedScaleMode::None;
+ return ResultSuccess;
case NintendoScaleMode::Freeze:
- return ConvertedScaleMode::Freeze;
+ *out_scaling_mode = ConvertedScaleMode::Freeze;
+ return ResultSuccess;
case NintendoScaleMode::ScaleToWindow:
- return ConvertedScaleMode::ScaleToWindow;
+ *out_scaling_mode = ConvertedScaleMode::ScaleToWindow;
+ return ResultSuccess;
case NintendoScaleMode::ScaleAndCrop:
- return ConvertedScaleMode::ScaleAndCrop;
+ *out_scaling_mode = ConvertedScaleMode::ScaleAndCrop;
+ return ResultSuccess;
case NintendoScaleMode::PreserveAspectRatio:
- return ConvertedScaleMode::PreserveAspectRatio;
+ *out_scaling_mode = ConvertedScaleMode::PreserveAspectRatio;
+ return ResultSuccess;
default:
LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode);
return ResultOperationFailed;
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index 28f89c599..a983f23ea 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -39,19 +39,41 @@ namespace Network {
namespace {
+enum class CallType {
+ Send,
+ Other,
+};
+
#ifdef _WIN32
using socklen_t = int;
+SOCKET interrupt_socket = static_cast<SOCKET>(-1);
+
+void InterruptSocketOperations() {
+ closesocket(interrupt_socket);
+}
+
+void AcknowledgeInterrupt() {
+ interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+}
+
void Initialize() {
WSADATA wsa_data;
(void)WSAStartup(MAKEWORD(2, 2), &wsa_data);
+
+ AcknowledgeInterrupt();
}
void Finalize() {
+ InterruptSocketOperations();
WSACleanup();
}
+SOCKET GetInterruptSocket() {
+ return interrupt_socket;
+}
+
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result;
@@ -96,7 +118,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) {
return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
}
-Errno TranslateNativeError(int e) {
+Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
switch (e) {
case 0:
return Errno::SUCCESS;
@@ -112,6 +134,14 @@ Errno TranslateNativeError(int e) {
return Errno::AGAIN;
case WSAECONNREFUSED:
return Errno::CONNREFUSED;
+ case WSAECONNABORTED:
+ if (call_type == CallType::Send) {
+ // Winsock yields WSAECONNABORTED from `send` in situations where Unix
+ // systems, and actual Switches, yield EPIPE.
+ return Errno::PIPE;
+ } else {
+ return Errno::CONNABORTED;
+ }
case WSAECONNRESET:
return Errno::CONNRESET;
case WSAEHOSTUNREACH:
@@ -144,9 +174,42 @@ constexpr int SD_RECEIVE = SHUT_RD;
constexpr int SD_SEND = SHUT_WR;
constexpr int SD_BOTH = SHUT_RDWR;
-void Initialize() {}
+int interrupt_pipe_fd[2] = {-1, -1};
-void Finalize() {}
+void Initialize() {
+ if (pipe(interrupt_pipe_fd) != 0) {
+ LOG_ERROR(Network, "Failed to create interrupt pipe!");
+ }
+ int flags = fcntl(interrupt_pipe_fd[0], F_GETFL);
+ ASSERT_MSG(fcntl(interrupt_pipe_fd[0], F_SETFL, flags | O_NONBLOCK) == 0,
+ "Failed to set nonblocking state for interrupt pipe");
+}
+
+void Finalize() {
+ if (interrupt_pipe_fd[0] >= 0) {
+ close(interrupt_pipe_fd[0]);
+ }
+ if (interrupt_pipe_fd[1] >= 0) {
+ close(interrupt_pipe_fd[1]);
+ }
+}
+
+void InterruptSocketOperations() {
+ u8 value = 0;
+ ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1);
+}
+
+void AcknowledgeInterrupt() {
+ u8 value = 0;
+ ssize_t ret = read(interrupt_pipe_fd[0], &value, sizeof(value));
+ if (ret != 1 && errno != EAGAIN && errno != EWOULDBLOCK) {
+ LOG_ERROR(Network, "Failed to acknowledge interrupt on shutdown");
+ }
+}
+
+SOCKET GetInterruptSocket() {
+ return interrupt_pipe_fd[0];
+}
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result;
@@ -198,7 +261,7 @@ bool EnableNonBlock(int fd, bool enable) {
return fcntl(fd, F_SETFL, flags) == 0;
}
-Errno TranslateNativeError(int e) {
+Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
switch (e) {
case 0:
return Errno::SUCCESS;
@@ -208,6 +271,10 @@ Errno TranslateNativeError(int e) {
return Errno::INVAL;
case EMFILE:
return Errno::MFILE;
+ case EPIPE:
+ return Errno::PIPE;
+ case ECONNABORTED:
+ return Errno::CONNABORTED;
case ENOTCONN:
return Errno::NOTCONN;
case EAGAIN:
@@ -236,13 +303,13 @@ Errno TranslateNativeError(int e) {
#endif
-Errno GetAndLogLastError() {
+Errno GetAndLogLastError(CallType call_type = CallType::Other) {
#ifdef _WIN32
int e = WSAGetLastError();
#else
int e = errno;
#endif
- const Errno err = TranslateNativeError(e);
+ const Errno err = TranslateNativeError(e, call_type);
if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) {
// These happen during normal operation, so only log them at debug level.
LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
@@ -473,10 +540,24 @@ NetworkInstance::~NetworkInstance() {
Finalize();
}
+void CancelPendingSocketOperations() {
+ InterruptSocketOperations();
+}
+
+void RestartSocketOperations() {
+ AcknowledgeInterrupt();
+}
+
std::optional<IPv4Address> GetHostIPv4Address() {
const auto network_interface = Network::GetSelectedNetworkInterface();
if (!network_interface.has_value()) {
- LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface");
+ // Only print the error once to avoid log spam
+ static bool print_error = true;
+ if (print_error) {
+ LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface");
+ print_error = false;
+ }
+
return {};
}
@@ -537,7 +618,14 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
return result;
});
- const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout);
+ host_pollfds.push_back(WSAPOLLFD{
+ .fd = GetInterruptSocket(),
+ .events = POLLIN,
+ .revents = 0,
+ });
+
+ const int result =
+ WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), timeout);
if (result == 0) {
ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(),
[](WSAPOLLFD fd) { return fd.revents == 0; }));
@@ -604,6 +692,24 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
+
+ std::vector<WSAPOLLFD> host_pollfds{
+ WSAPOLLFD{fd, POLLIN, 0},
+ WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
+ };
+
+ while (true) {
+ const int pollres =
+ WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), -1);
+ if (host_pollfds[1].revents != 0) {
+ // Interrupt signaled before a client could be accepted, break
+ return {AcceptResult{}, Errno::AGAIN};
+ }
+ if (pollres > 0) {
+ break;
+ }
+ }
+
const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen);
if (new_socket == INVALID_SOCKET) {
@@ -725,13 +831,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) {
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
ASSERT(flags == 0);
+ int native_flags = 0;
+#if YUZU_UNIX
+ native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE
+#endif
const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
- static_cast<int>(message.size()), 0);
+ static_cast<int>(message.size()), native_flags);
if (result != SOCKET_ERROR) {
return {static_cast<s32>(result), Errno::SUCCESS};
}
- return {-1, GetAndLogLastError()};
+ return {-1, GetAndLogLastError(CallType::Send)};
}
std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
@@ -753,7 +863,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
return {static_cast<s32>(result), Errno::SUCCESS};
}
- return {-1, GetAndLogLastError()};
+ return {-1, GetAndLogLastError(CallType::Send)};
}
Errno Socket::Close() {
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
index badcb8369..b7b7d773a 100644
--- a/src/core/internal_network/network.h
+++ b/src/core/internal_network/network.h
@@ -33,10 +33,12 @@ enum class Errno {
BADF,
INVAL,
MFILE,
+ PIPE,
NOTCONN,
AGAIN,
CONNREFUSED,
CONNRESET,
+ CONNABORTED,
HOSTUNREACH,
NETDOWN,
NETUNREACH,
@@ -94,6 +96,9 @@ public:
~NetworkInstance();
};
+void CancelPendingSocketOperations();
+void RestartSocketOperations();
+
#ifdef _WIN32
constexpr IPv4Address TranslateIPv4(in_addr addr) {
auto& bytes = addr.S_un.S_un_b;
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp
index 4c909a6d3..7c37f660b 100644
--- a/src/core/internal_network/network_interface.cpp
+++ b/src/core/internal_network/network_interface.cpp
@@ -200,7 +200,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() {
});
if (res == network_interfaces.end()) {
- LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
+ // Only print the error once to avoid log spam
+ static bool print_error = true;
+ if (print_error) {
+ LOG_ERROR(Network, "Couldn't find selected interface \"{}\"",
+ selected_network_interface);
+ print_error = false;
+ }
+
return std::nullopt;
}
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index e04ad19db..5a42dea48 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -18,7 +18,7 @@ namespace Loader {
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
bool override_update_)
- : AppLoader(std::move(file_)), override_update(override_update_) {
+ : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
const auto file_dir = file->GetContainingDirectory();
// Title ID
@@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
}
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
- FileSys::VirtualDir directory, bool override_update_)
+ FileSys::VirtualDir directory, bool override_update_, bool is_hbl_)
: AppLoader(directory->GetFile("main")), dir(std::move(directory)),
- override_update(override_update_) {}
+ override_update(override_update_), is_hbl(is_hbl_) {}
FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) {
if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) {
@@ -147,13 +147,13 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
// Setup the process code layout
- if (process.LoadFromMetadata(metadata, code_size).IsError()) {
+ if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) {
return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
}
// Load NSO modules
modules.clear();
- const VAddr base_address{GetInteger(process.GetPageTable().GetCodeRegionStart())};
+ const VAddr base_address{GetInteger(process.GetEntryPoint())};
VAddr next_load_addr{base_address};
const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
system.GetContentProvider()};
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index f7702225e..1e9f765c9 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -27,7 +27,8 @@ public:
// Overload to accept exefs directory. Must contain 'main' and 'main.npdm'
explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory,
- bool override_update_ = false);
+ bool override_update_ = false,
+ bool is_hbl_ = false);
/**
* Identifies whether or not the given file is a deconstructed ROM directory.
@@ -62,6 +63,7 @@ private:
std::string name;
u64 title_id{};
bool override_update;
+ bool is_hbl;
Modules modules;
};
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index ffe976b94..bf56a08b4 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -90,13 +90,14 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
codeset.DataSegment().size += kip->GetBSSSize();
// Setup the process code layout
- if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size())
+ if (process
+ .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
.IsError()) {
return {ResultStatus::ErrorNotInitialized, {}};
}
codeset.memory = std::move(program_image);
- const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart());
+ const VAddr base_address = GetInteger(process.GetEntryPoint());
process.LoadModule(std::move(codeset), base_address);
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address);
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index f24474ed8..b6e355622 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown";
}
-constexpr std::array<const char*, 66> RESULT_MESSAGES{
+constexpr std::array<const char*, 68> RESULT_MESSAGES{
"The operation completed successfully.",
"The loader requested to load is already loaded.",
"The operation is not implemented.",
@@ -135,7 +135,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
"The titlekey and/or titlekek is incorrect or the section header is invalid.",
"The XCI file is missing a Program-type NCA.",
"The NCA file is not an application.",
- "The ExeFS partition could not be found.",
+ "The Program-type NCA contains no executable. An update may be required.",
"The XCI file has a bad header.",
"The XCI file is missing a partition.",
"The file could not be found or does not exist.",
@@ -169,12 +169,14 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
"The BKTR-type NCA has a bad Subsection block.",
"The BKTR-type NCA has a bad Relocation bucket.",
"The BKTR-type NCA has a bad Subsection bucket.",
- "The BKTR-type NCA is missing the base RomFS.",
+ "Game updates cannot be loaded directly. Load the base game instead.",
"The NSP or XCI does not contain an update in addition to the base game.",
"The KIP file has a bad header.",
"The KIP BLZ decompression of the section failed unexpectedly.",
"The INI file has a bad header.",
"The INI file contains more than the maximum allowable number of KIP files.",
+ "Integrity verification could not be performed for this file.",
+ "Integrity verification failed.",
};
std::string GetResultStatusString(ResultStatus status) {
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 7a2a52fd4..b4828f7cd 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -3,6 +3,7 @@
#pragma once
+#include <functional>
#include <iosfwd>
#include <memory>
#include <optional>
@@ -79,8 +80,6 @@ enum class ResultStatus : u16 {
ErrorBadPFSHeader,
ErrorIncorrectPFSFileSize,
ErrorBadNCAHeader,
- ErrorCompressedNCA,
- ErrorSparseNCA,
ErrorMissingProductionKeyFile,
ErrorMissingHeaderKey,
ErrorIncorrectHeaderKey,
@@ -134,6 +133,8 @@ enum class ResultStatus : u16 {
ErrorBLZDecompressionFailed,
ErrorBadINIHeader,
ErrorINITooManyKIPs,
+ ErrorIntegrityVerificationNotImplemented,
+ ErrorIntegrityVerificationFailed,
};
std::string GetResultStatusString(ResultStatus status);
@@ -172,6 +173,13 @@ public:
virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0;
/**
+ * Try to verify the integrity of the file.
+ */
+ virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
+ return ResultStatus::ErrorIntegrityVerificationNotImplemented;
+ }
+
+ /**
* Get the code (typically .code section) of the application
*
* @param[out] buffer Reference to buffer to store data
@@ -276,16 +284,6 @@ public:
}
/**
- * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS)
- * data. Needed for BKTR patching.
- *
- * @return IVFC offset for RomFS.
- */
- virtual u64 ReadRomFSIVFCOffset() const {
- return 0;
- }
-
- /**
* Get the title of the application
*
* @param[out] title Reference to store the application title into
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index cf35b1249..3b7b005ff 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -76,10 +76,6 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
return nca_loader->ReadRomFS(dir);
}
-u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
- return nca_loader->ReadRomFSIVFCOffset();
-}
-
ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
return nca_loader->ReadProgramId(out_program_id);
}
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index d7f70db43..81df2bbcd 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -39,7 +39,6 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
- u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadBanner(std::vector<u8>& buffer) override;
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 513af194d..4feb6968a 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -3,13 +3,18 @@
#include <utility>
+#include "common/hex_util.h"
+#include "common/scope_exit.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs_factory.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h"
+#include "mbedtls/sha256.h"
namespace Loader {
@@ -43,9 +48,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
return {ResultStatus::ErrorNCANotProgram, {}};
}
- const auto exefs = nca->GetExeFS();
+ auto exefs = nca->GetExeFS();
if (exefs == nullptr) {
- return {ResultStatus::ErrorNoExeFS, {}};
+ LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update");
+
+ // This NCA may be a sparse base of an installed title.
+ // Try to fetch the ExeFS from the installed update.
+ const auto& installed = system.GetContentProvider();
+ const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()),
+ FileSys::ContentRecordType::Program);
+
+ if (update_nca) {
+ exefs = update_nca->GetExeFS();
+ }
+
+ if (exefs == nullptr) {
+ return {ResultStatus::ErrorNoExeFS, {}};
+ }
}
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
@@ -64,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
return load_result;
}
+ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
+ using namespace Common::Literals;
+
+ constexpr size_t NcaFileNameWithHashLength = 36;
+ constexpr size_t NcaFileNameHashLength = 32;
+ constexpr size_t NcaSha256HashLength = 32;
+ constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2;
+
+ // Get the file name.
+ const auto name = file->GetName();
+
+ // We won't try to verify meta NCAs.
+ if (name.ends_with(".cnmt.nca")) {
+ return ResultStatus::Success;
+ }
+
+ // Check if we can verify this file. NCAs should be named after their hashes.
+ if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) {
+ LOG_WARNING(Loader, "Unable to validate NCA with name {}", name);
+ return ResultStatus::ErrorIntegrityVerificationNotImplemented;
+ }
+
+ // Get the expected truncated hash of the NCA.
+ const auto input_hash =
+ Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false);
+
+ // Declare buffer to read into.
+ std::vector<u8> buffer(4_MiB);
+
+ // Initialize sha256 verification context.
+ mbedtls_sha256_context ctx;
+ mbedtls_sha256_init(&ctx);
+ mbedtls_sha256_starts_ret(&ctx, 0);
+
+ // Ensure we maintain a clean state on exit.
+ SCOPE_EXIT({ mbedtls_sha256_free(&ctx); });
+
+ // Declare counters.
+ const size_t total_size = file->GetSize();
+ size_t processed_size = 0;
+
+ // Begin iterating the file.
+ while (processed_size < total_size) {
+ // Refill the buffer.
+ const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size);
+ const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size);
+
+ // Update the hash function with the buffer contents.
+ mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size);
+
+ // Update counters.
+ processed_size += read_size;
+
+ // Call the progress function.
+ if (!progress_callback(processed_size, total_size)) {
+ return ResultStatus::ErrorIntegrityVerificationFailed;
+ }
+ }
+
+ // Finalize context and compute the output hash.
+ std::array<u8, NcaSha256HashLength> output_hash;
+ mbedtls_sha256_finish_ret(&ctx, output_hash.data());
+
+ // Compare to expected.
+ if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) {
+ LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name);
+ return ResultStatus::ErrorIntegrityVerificationFailed;
+ }
+
+ // File verified.
+ return ResultStatus::Success;
+}
+
ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
if (nca == nullptr) {
return ResultStatus::ErrorNotInitialized;
@@ -77,14 +169,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
return ResultStatus::Success;
}
-u64 AppLoader_NCA::ReadRomFSIVFCOffset() const {
- if (nca == nullptr) {
- return 0;
- }
-
- return nca->GetBaseIVFCOffset();
-}
-
ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) {
return ResultStatus::ErrorNotInitialized;
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index d22d9146e..96779e27f 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -39,8 +39,9 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
+ ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
+
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
- u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadBanner(std::vector<u8>& buffer) override;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 506808b5d..69f1a54ed 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -196,14 +196,15 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
// Setup the process code layout
- if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size())
+ if (process
+ .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
.IsError()) {
return false;
}
// Load codeset for current process
codeset.memory = std::move(program_image);
- process.LoadModule(std::move(codeset), process.GetPageTable().GetCodeRegionStart());
+ process.LoadModule(std::move(codeset), process.GetEntryPoint());
return true;
}
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 74cc9579f..1350da8dc 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
}
// Apply patches if necessary
- if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
+ const auto name = nso_file.GetName();
+ if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
program_image.size());
- pi_header = pm->PatchNSO(pi_header, nso_file.GetName());
+ pi_header = pm->PatchNSO(pi_header, name);
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
}
@@ -167,7 +168,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::S
modules.clear();
// Load module
- const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart());
+ const VAddr base_address = GetInteger(process.GetEntryPoint());
if (!LoadModule(process, system, *file, base_address, true, true)) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 80663e0e0..f4ab75b77 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_,
}
if (nsp->IsExtractedType()) {
- secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
+ secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(
+ nsp->GetExeFS(), false, file->GetName() == "hbl.nsp");
} else {
const auto control_nca =
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
@@ -117,12 +118,44 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
return result;
}
-ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
- return secondary_loader->ReadRomFS(out_file);
+ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
+ // Extracted-type NSPs can't be verified.
+ if (nsp->IsExtractedType()) {
+ return ResultStatus::ErrorIntegrityVerificationNotImplemented;
+ }
+
+ // Get list of all NCAs.
+ const auto ncas = nsp->GetNCAsCollapsed();
+
+ size_t total_size = 0;
+ size_t processed_size = 0;
+
+ // Loop over NCAs, collecting the total size to verify.
+ for (const auto& nca : ncas) {
+ total_size += nca->GetBaseFile()->GetSize();
+ }
+
+ // Loop over NCAs again, verifying each.
+ for (const auto& nca : ncas) {
+ AppLoader_NCA loader_nca(nca->GetBaseFile());
+
+ const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) {
+ return progress_callback(processed_size + nca_processed_size, total_size);
+ };
+
+ const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback);
+ if (verification_result != ResultStatus::Success) {
+ return verification_result;
+ }
+
+ processed_size += nca->GetBaseFile()->GetSize();
+ }
+
+ return ResultStatus::Success;
}
-u64 AppLoader_NSP::ReadRomFSIVFCOffset() const {
- return secondary_loader->ReadRomFSIVFCOffset();
+ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
+ return secondary_loader->ReadRomFS(out_file);
}
ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 003cc345c..7ce436c67 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -45,8 +45,9 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
+ ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
+
ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override;
- u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index c7b1b3815..12d72c380 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -85,12 +85,42 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S
return result;
}
-ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
- return nca_loader->ReadRomFS(out_file);
+ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
+ // Verify secure partition, as it is the only thing we can process.
+ auto secure_partition = xci->GetSecurePartitionNSP();
+
+ // Get list of all NCAs.
+ const auto ncas = secure_partition->GetNCAsCollapsed();
+
+ size_t total_size = 0;
+ size_t processed_size = 0;
+
+ // Loop over NCAs, collecting the total size to verify.
+ for (const auto& nca : ncas) {
+ total_size += nca->GetBaseFile()->GetSize();
+ }
+
+ // Loop over NCAs again, verifying each.
+ for (const auto& nca : ncas) {
+ AppLoader_NCA loader_nca(nca->GetBaseFile());
+
+ const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) {
+ return progress_callback(processed_size + nca_processed_size, total_size);
+ };
+
+ const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback);
+ if (verification_result != ResultStatus::Success) {
+ return verification_result;
+ }
+
+ processed_size += nca->GetBaseFile()->GetSize();
+ }
+
+ return ResultStatus::Success;
}
-u64 AppLoader_XCI::ReadRomFSIVFCOffset() const {
- return nca_loader->ReadRomFSIVFCOffset();
+ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
+ return nca_loader->ReadRomFS(out_file);
}
ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 2affb6c6e..b02e136d3 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -45,8 +45,9 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
+ ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
+
ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override;
- u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 513bc4edb..fa5273402 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -24,6 +24,16 @@
namespace Core::Memory {
+namespace {
+
+bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr,
+ const std::size_t size) {
+ const Common::ProcessAddress max_addr = 1ULL << table.GetAddressSpaceBits();
+ return addr + size >= addr && addr + size <= max_addr;
+}
+
+} // namespace
+
// Implementation class used to keep the specifics of the memory subsystem hidden
// from outside classes. This also allows modification to the internals of the memory
// subsystem without needing to rebuild all files that make use of the memory interface.
@@ -191,6 +201,11 @@ struct Memory::Impl {
std::size_t page_offset = addr & YUZU_PAGEMASK;
bool user_accessible = true;
+ if (!AddressSpaceContains(page_table, addr, size)) [[unlikely]] {
+ on_unmapped(size, addr);
+ return false;
+ }
+
while (remaining_size) {
const std::size_t copy_amount =
std::min(static_cast<std::size_t>(YUZU_PAGESIZE) - page_offset, remaining_size);
@@ -421,7 +436,7 @@ struct Memory::Impl {
}
void MarkRegionDebug(u64 vaddr, u64 size, bool debug) {
- if (vaddr == 0) {
+ if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) {
return;
}
@@ -478,7 +493,7 @@ struct Memory::Impl {
}
void RasterizerMarkRegionCached(u64 vaddr, u64 size, bool cached) {
- if (vaddr == 0) {
+ if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) {
return;
}
@@ -615,7 +630,7 @@ struct Memory::Impl {
// AARCH64 masks the upper 16 bit of all memory accesses
vaddr = vaddr & 0xffffffffffffULL;
- if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
+ if (!AddressSpaceContains(*current_page_table, vaddr, 1)) [[unlikely]] {
on_unmapped();
return nullptr;
}
diff --git a/src/core/memory.h b/src/core/memory.h
index 2eb61ffd3..13047a545 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -509,9 +509,9 @@ class GuestMemory {
public:
GuestMemory() = delete;
- explicit GuestMemory(M& memory_, u64 addr_, std::size_t size_,
+ explicit GuestMemory(M& memory, u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr)
- : memory{memory_}, addr{addr_}, size{size_} {
+ : m_memory{memory}, m_addr{addr}, m_size{size} {
static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write);
if constexpr (FLAGS & GuestMemoryFlags::Read) {
Read(addr, size, backup);
@@ -521,89 +521,97 @@ public:
~GuestMemory() = default;
T* data() noexcept {
- return data_span.data();
+ return m_data_span.data();
}
const T* data() const noexcept {
- return data_span.data();
+ return m_data_span.data();
+ }
+
+ size_t size() const noexcept {
+ return m_size;
+ }
+
+ size_t size_bytes() const noexcept {
+ return this->size() * sizeof(T);
}
[[nodiscard]] T* begin() noexcept {
- return data();
+ return this->data();
}
[[nodiscard]] const T* begin() const noexcept {
- return data();
+ return this->data();
}
[[nodiscard]] T* end() noexcept {
- return data() + size;
+ return this->data() + this->size();
}
[[nodiscard]] const T* end() const noexcept {
- return data() + size;
+ return this->data() + this->size();
}
T& operator[](size_t index) noexcept {
- return data_span[index];
+ return m_data_span[index];
}
const T& operator[](size_t index) const noexcept {
- return data_span[index];
+ return m_data_span[index];
}
- void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept {
- addr = addr_;
- size = size_;
- addr_changed = true;
+ void SetAddressAndSize(u64 addr, std::size_t size) noexcept {
+ m_addr = addr;
+ m_size = size;
+ m_addr_changed = true;
}
- std::span<T> Read(u64 addr_, std::size_t size_,
+ std::span<T> Read(u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr) noexcept {
- addr = addr_;
- size = size_;
- if (size == 0) {
- is_data_copy = true;
+ m_addr = addr;
+ m_size = size;
+ if (m_size == 0) {
+ m_is_data_copy = true;
return {};
}
- if (TrySetSpan()) {
+ if (this->TrySetSpan()) {
if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- memory.FlushRegion(addr, size * sizeof(T));
+ m_memory.FlushRegion(m_addr, this->size_bytes());
}
} else {
if (backup) {
- backup->resize_destructive(size);
- data_span = *backup;
+ backup->resize_destructive(this->size());
+ m_data_span = *backup;
} else {
- data_copy.resize(size);
- data_span = std::span(data_copy);
+ m_data_copy.resize(this->size());
+ m_data_span = std::span(m_data_copy);
}
- is_data_copy = true;
- span_valid = true;
+ m_is_data_copy = true;
+ m_span_valid = true;
if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- memory.ReadBlock(addr, data_span.data(), size * sizeof(T));
+ m_memory.ReadBlock(m_addr, this->data(), this->size_bytes());
} else {
- memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T));
+ m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes());
}
}
- return data_span;
+ return m_data_span;
}
void Write(std::span<T> write_data) noexcept {
if constexpr (FLAGS & GuestMemoryFlags::Cached) {
- memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T));
+ m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes());
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- memory.WriteBlock(addr, write_data.data(), size * sizeof(T));
+ m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes());
} else {
- memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T));
+ m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes());
}
}
bool TrySetSpan() noexcept {
- if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) {
- data_span = {reinterpret_cast<T*>(ptr), size};
- span_valid = true;
+ if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) {
+ m_data_span = {reinterpret_cast<T*>(ptr), this->size()};
+ m_span_valid = true;
return true;
}
return false;
@@ -611,36 +619,36 @@ public:
protected:
bool IsDataCopy() const noexcept {
- return is_data_copy;
+ return m_is_data_copy;
}
bool AddressChanged() const noexcept {
- return addr_changed;
+ return m_addr_changed;
}
- M& memory;
- u64 addr;
- size_t size;
- std::span<T> data_span{};
- std::vector<T> data_copy;
- bool span_valid{false};
- bool is_data_copy{false};
- bool addr_changed{false};
+ M& m_memory;
+ u64 m_addr{};
+ size_t m_size{};
+ std::span<T> m_data_span{};
+ std::vector<T> m_data_copy{};
+ bool m_span_valid{false};
+ bool m_is_data_copy{false};
+ bool m_addr_changed{false};
};
template <typename M, typename T, GuestMemoryFlags FLAGS>
class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> {
public:
GuestMemoryScoped() = delete;
- explicit GuestMemoryScoped(M& memory_, u64 addr_, std::size_t size_,
+ explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr)
- : GuestMemory<M, T, FLAGS>(memory_, addr_, size_, backup) {
+ : GuestMemory<M, T, FLAGS>(memory, addr, size, backup) {
if constexpr (!(FLAGS & GuestMemoryFlags::Read)) {
if (!this->TrySetSpan()) {
if (backup) {
- this->data_span = *backup;
- this->span_valid = true;
- this->is_data_copy = true;
+ this->m_data_span = *backup;
+ this->m_span_valid = true;
+ this->m_is_data_copy = true;
}
}
}
@@ -648,24 +656,21 @@ public:
~GuestMemoryScoped() {
if constexpr (FLAGS & GuestMemoryFlags::Write) {
- if (this->size == 0) [[unlikely]] {
+ if (this->size() == 0) [[unlikely]] {
return;
}
if (this->AddressChanged() || this->IsDataCopy()) {
- ASSERT(this->span_valid);
+ ASSERT(this->m_span_valid);
if constexpr (FLAGS & GuestMemoryFlags::Cached) {
- this->memory.WriteBlockCached(this->addr, this->data_span.data(),
- this->size * sizeof(T));
+ this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes());
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- this->memory.WriteBlock(this->addr, this->data_span.data(),
- this->size * sizeof(T));
+ this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes());
} else {
- this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(),
- this->size * sizeof(T));
+ this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes());
}
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
- this->memory.InvalidateRegion(this->addr, this->size * sizeof(T));
+ this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes());
}
}
}
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 7b52f61a7..a06e99166 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
return {};
}
- const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10));
+ const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
value;
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index b5b3e7eda..ed875d444 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -117,8 +117,8 @@ json GetProcessorStateDataAuto(Core::System& system) {
arm.SaveContext(context);
return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
- GetInteger(process->GetPageTable().GetCodeRegionStart()),
- context.sp, context.pc, context.pstate, context.cpu_registers);
+ GetInteger(process->GetEntryPoint()), context.sp, context.pc,
+ context.pstate, context.cpu_registers);
}
json GetBacktraceData(Core::System& system) {
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 7a2f3c90a..c26179e03 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -14,6 +14,7 @@
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h"
@@ -61,13 +62,13 @@ static const char* TranslateRenderer(Settings::RendererBackend backend) {
return "Unknown";
}
-static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) {
+static const char* TranslateGPUAccuracyLevel(Settings::GpuAccuracy backend) {
switch (backend) {
- case Settings::GPUAccuracy::Normal:
+ case Settings::GpuAccuracy::Normal:
return "Normal";
- case Settings::GPUAccuracy::High:
+ case Settings::GpuAccuracy::High:
return "High";
- case Settings::GPUAccuracy::Extreme:
+ case Settings::GpuAccuracy::Extreme:
return "Extreme";
}
return "Unknown";
@@ -77,9 +78,9 @@ static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) {
switch (backend) {
case Settings::NvdecEmulation::Off:
return "Off";
- case Settings::NvdecEmulation::CPU:
+ case Settings::NvdecEmulation::Cpu:
return "CPU";
- case Settings::NvdecEmulation::GPU:
+ case Settings::NvdecEmulation::Gpu:
return "GPU";
}
return "Unknown";
@@ -91,14 +92,26 @@ static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) {
return "Immediate";
case Settings::VSyncMode::Mailbox:
return "Mailbox";
- case Settings::VSyncMode::FIFO:
+ case Settings::VSyncMode::Fifo:
return "FIFO";
- case Settings::VSyncMode::FIFORelaxed:
+ case Settings::VSyncMode::FifoRelaxed:
return "FIFO Relaxed";
}
return "Unknown";
}
+static constexpr const char* TranslateASTCDecodeMode(Settings::AstcDecodeMode mode) {
+ switch (mode) {
+ case Settings::AstcDecodeMode::Cpu:
+ return "CPU";
+ case Settings::AstcDecodeMode::Gpu:
+ return "GPU";
+ case Settings::AstcDecodeMode::CpuAsynchronous:
+ return "CPU Asynchronous";
+ }
+ return "Unknown";
+}
+
u64 GetTelemetryId() {
u64 telemetry_id{};
const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id";
@@ -240,7 +253,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
// Log user configuration information
constexpr auto field_type = Telemetry::FieldType::UserConfig;
- AddField(field_type, "Audio_SinkId", Settings::values.sink_id.GetValue());
+ AddField(field_type, "Audio_SinkId",
+ Settings::CanonicalizeEnum(Settings::values.sink_id.GetValue()));
AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
AddField(field_type, "Renderer_Backend",
TranslateRenderer(Settings::values.renderer_backend.GetValue()));
@@ -254,14 +268,15 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
Settings::values.use_asynchronous_gpu_emulation.GetValue());
AddField(field_type, "Renderer_NvdecEmulation",
TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue()));
- AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue());
+ AddField(field_type, "Renderer_AccelerateASTC",
+ TranslateASTCDecodeMode(Settings::values.accelerate_astc.GetValue()));
AddField(field_type, "Renderer_UseVsync",
TranslateVSyncMode(Settings::values.vsync_mode.GetValue()));
AddField(field_type, "Renderer_ShaderBackend",
static_cast<u32>(Settings::values.shader_backend.GetValue()));
AddField(field_type, "Renderer_UseAsynchronousShaders",
Settings::values.use_asynchronous_shaders.GetValue());
- AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue());
+ AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode());
}
bool TelemetrySession::SubmitTestcase() {
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp
new file mode 100644
index 000000000..947fa6cb3
--- /dev/null
+++ b/src/core/tools/renderdoc.cpp
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <renderdoc_app.h>
+
+#include "common/assert.h"
+#include "common/dynamic_library.h"
+#include "core/tools/renderdoc.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+namespace Tools {
+
+RenderdocAPI::RenderdocAPI() {
+#ifdef WIN32
+ if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
+ const auto RENDERDOC_GetAPI =
+ reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI"));
+ const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
+ ASSERT(ret == 1);
+ }
+#else
+#ifdef ANDROID
+ static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
+#else
+ static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
+#endif
+ if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
+ const auto RENDERDOC_GetAPI =
+ reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
+ const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
+ ASSERT(ret == 1);
+ }
+#endif
+}
+
+RenderdocAPI::~RenderdocAPI() = default;
+
+void RenderdocAPI::ToggleCapture() {
+ if (!rdoc_api) [[unlikely]] {
+ return;
+ }
+ if (!is_capturing) {
+ rdoc_api->StartFrameCapture(NULL, NULL);
+ } else {
+ rdoc_api->EndFrameCapture(NULL, NULL);
+ }
+ is_capturing = !is_capturing;
+}
+
+} // namespace Tools
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h
new file mode 100644
index 000000000..0e5e43da5
--- /dev/null
+++ b/src/core/tools/renderdoc.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+struct RENDERDOC_API_1_6_0;
+
+namespace Tools {
+
+class RenderdocAPI {
+public:
+ explicit RenderdocAPI();
+ ~RenderdocAPI();
+
+ void ToggleCapture();
+
+private:
+ RENDERDOC_API_1_6_0* rdoc_api{};
+ bool is_capturing{false};
+};
+
+} // namespace Tools
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) {
if (auto room = network.GetRoom().lock()) {
AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
.id = preferred_game_id};
- if (!room->Create(room_name, room_description, bind_address, port, password, max_members,
- username, preferred_game_info, std::move(verify_backend), ban_list,
- enable_yuzu_mods)) {
+ if (!room->Create(room_name, room_description, bind_address, static_cast<u16>(port),
+ password, max_members, username, preferred_game_info,
+ std::move(verify_backend), ban_list, enable_yuzu_mods)) {
LOG_INFO(Network, "Failed to create room: ");
return -1;
}
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
if (MSVC)
target_compile_options(input_common PRIVATE
- /W4
-
/we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/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:
return input_engine->SupportsNfc(identifier);
}
- Common::Input::NfcState StartNfcPolling() {
+ Common::Input::NfcState StartNfcPolling() override {
return input_engine->StartNfcPolling(identifier);
}
- Common::Input::NfcState StopNfcPolling() {
+ Common::Input::NfcState StopNfcPolling() override {
return input_engine->StopNfcPolling(identifier);
}
- Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) {
+ Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) override {
return input_engine->ReadAmiiboData(identifier, out_data);
}
@@ -852,11 +852,11 @@ public:
}
Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request,
- Common::Input::MifareRequest& out_data) {
+ Common::Input::MifareRequest& out_data) override {
return input_engine->ReadMifareData(identifier, request, out_data);
}
- Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) {
+ Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) override {
return input_engine->WriteMifareData(identifier, request);
}
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() {
std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE
do {
for (std::size_t i = 2; i < result_ip.size(); ++i) {
- result_ip[i] = dis(random_gen);
+ result_ip[i] = static_cast<u8>(dis(random_gen));
}
} while (!IsValidFakeIPAddress(result_ip));
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)
if (MSVC)
target_compile_options(shader_recompiler PRIVATE
- /W4
-
/we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/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,
const IR::Value& coord, const IR::Value& derivatives,
const IR::Value& offset, const IR::Value& lod_clamp) {
const auto info{inst.Flags<IR::TextureInstInfo>()};
- ScopedRegister dpdx, dpdy;
+ ScopedRegister dpdx, dpdy, coords;
const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp};
if (multi_component) {
// Allocate this early to avoid aliasing other registers
dpdx = ScopedRegister{ctx.reg_alloc};
dpdy = ScopedRegister{ctx.reg_alloc};
+ if (info.num_derivates >= 3) {
+ coords = ScopedRegister{ctx.reg_alloc};
+ }
}
const auto sparse_inst{PrepareSparse(inst)};
const std::string_view sparse_mod{sparse_inst ? ".SPARSE" : ""};
@@ -580,15 +583,27 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
"MOV.F {}.y,{}.w;",
dpdx.reg, derivatives_vec, dpdx.reg, derivatives_vec, dpdy.reg, derivatives_vec,
dpdy.reg, derivatives_vec);
+ Register final_coord;
+ if (info.num_derivates >= 3) {
+ ctx.Add("MOV.F {}.z,{}.x;"
+ "MOV.F {}.z,{}.y;",
+ dpdx.reg, coord_vec, dpdy.reg, coord_vec);
+ ctx.Add("MOV.F {}.x,0;"
+ "MOV.F {}.y,0;",
+ "MOV.F {}.z,0;", coords.reg, coords.reg, coords.reg);
+ final_coord = coords.reg;
+ } else {
+ final_coord = coord_vec;
+ }
if (info.has_lod_clamp) {
const ScalarF32 lod_clamp_value{ctx.reg_alloc.Consume(lod_clamp)};
ctx.Add("MOV.F {}.w,{};"
"TXD.F.LODCLAMP{} {},{},{},{},{},{}{};",
- dpdy.reg, lod_clamp_value, sparse_mod, ret, coord_vec, dpdx.reg, dpdy.reg,
+ dpdy.reg, lod_clamp_value, sparse_mod, ret, final_coord, dpdx.reg, dpdy.reg,
texture, type, offset_vec);
} else {
- ctx.Add("TXD.F{} {},{},{},{},{},{}{};", sparse_mod, ret, coord_vec, dpdx.reg, dpdy.reg,
- texture, type, offset_vec);
+ ctx.Add("TXD.F{} {},{},{},{},{},{}{};", sparse_mod, ret, final_coord, dpdx.reg,
+ dpdy.reg, texture, type, offset_vec);
}
} else {
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,
if (sparse_inst) {
throw NotImplementedException("EmitImageGradient Sparse");
}
- if (!offset.IsEmpty()) {
+ if (!offset.IsEmpty() && info.num_derivates <= 2) {
throw NotImplementedException("EmitImageGradient offset");
}
const auto texture{Texture(ctx, info, index)};
@@ -556,6 +556,12 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp};
const auto derivatives_vec{ctx.var_alloc.Consume(derivatives)};
if (multi_component) {
+ if (info.num_derivates >= 3) {
+ const auto offset_vec{ctx.var_alloc.Consume(offset)};
+ ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yw, {}.y));", texel, texture,
+ coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec);
+ return;
+ }
ctx.Add("{}=textureGrad({},{},vec2({}.xz),vec2({}.yz));", texel, texture, coords,
derivatives_vec, derivatives_vec);
} 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:
}
}
+ explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivates_1, Id derivates_2,
+ Id offset, Id lod_clamp) {
+ if (!Sirit::ValidId(derivates_1) || !Sirit::ValidId(derivates_2)) {
+ throw LogicError("Derivates must be present");
+ }
+ boost::container::static_vector<Id, 3> deriv_1_accum{
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 0),
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 2),
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 0),
+ };
+ boost::container::static_vector<Id, 3> deriv_2_accum{
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 1),
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_1, 3),
+ ctx.OpCompositeExtract(ctx.F32[1], derivates_2, 1),
+ };
+ const Id derivates_id1{ctx.OpCompositeConstruct(
+ ctx.F32[3], std::span{deriv_1_accum.data(), deriv_1_accum.size()})};
+ const Id derivates_id2{ctx.OpCompositeConstruct(
+ ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})};
+ Add(spv::ImageOperandsMask::Grad, derivates_id1, derivates_id2);
+ if (Sirit::ValidId(offset)) {
+ Add(spv::ImageOperandsMask::Offset, offset);
+ }
+ if (has_lod_clamp) {
+ Add(spv::ImageOperandsMask::MinLod, lod_clamp);
+ }
+ }
+
std::span<const Id> Span() const noexcept {
return std::span{operands.data(), operands.size()};
}
@@ -176,9 +204,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
if (def.count > 1) {
throw NotImplementedException("Indirect texture sample");
}
- const Id sampler_id{def.id};
- const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)};
- return ctx.OpImage(ctx.image_buffer_type, id);
+ return ctx.OpLoad(ctx.image_buffer_type, def.id);
} else {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) {
@@ -524,8 +550,11 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id derivates, Id offset, Id lod_clamp) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
- const ImageOperands operands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates,
- offset, lod_clamp);
+ const auto operands =
+ info.num_derivates == 3
+ ? ImageOperands(ctx, info.has_lod_clamp != 0, derivates, offset, {}, lod_clamp)
+ : ImageOperands(ctx, info.has_lod_clamp != 0, derivates, info.num_derivates, offset,
+ lod_clamp);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
index 2a12feddc..dde0f6e9c 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image_atomic.cpp
@@ -7,15 +7,12 @@
namespace Shader::Backend::SPIRV {
namespace {
-Id Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) {
- if (!index.IsImmediate()) {
- throw NotImplementedException("Indirect image indexing");
- }
+Id Image(EmitContext& ctx, IR::TextureInstInfo info) {
if (info.type == TextureType::Buffer) {
- const ImageBufferDefinition def{ctx.image_buffers.at(index.U32())};
+ const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
return def.id;
} else {
- const ImageDefinition def{ctx.images.at(index.U32())};
+ const ImageDefinition def{ctx.images.at(info.descriptor_index)};
return def.id;
}
}
@@ -28,8 +25,12 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
+ if (!index.IsImmediate() || index.U32() != 0) {
+ // TODO: handle layers
+ throw NotImplementedException("Image indexing");
+ }
const auto info{inst->Flags<IR::TextureInstInfo>()};
- const Id image{Image(ctx, index, info)};
+ const Id image{Image(ctx, info)};
const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))};
const auto [scope, semantics]{AtomicArgs(ctx)};
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index bec5db173..57df6fc34 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -1242,9 +1242,8 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
}
const spv::ImageFormat format{spv::ImageFormat::Unknown};
image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
- sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
- const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)};
+ const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
texture_buffers.reserve(info.texture_buffer_descriptors.size());
for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
if (desc.count != 1) {
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:
Id output_u32{};
Id image_buffer_type{};
- Id sampled_texture_buffer_type{};
Id image_u32{};
std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};
diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h
index 26e8307c1..15285ab0a 100644
--- a/src/shader_recompiler/environment.h
+++ b/src/shader_recompiler/environment.h
@@ -39,7 +39,7 @@ public:
[[nodiscard]] virtual std::optional<ReplaceConstant> GetReplaceConstBuffer(u32 bank,
u32 offset) = 0;
- virtual void Dump(u64 hash) = 0;
+ virtual void Dump(u64 pipeline_hash, u64 shader_hash) = 0;
[[nodiscard]] const ProgramHeader& SPH() const noexcept {
return sph;
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 {
BitField<23, 2, u32> gather_component;
BitField<25, 2, u32> num_derivates;
BitField<27, 3, ImageFormat> image_format;
+ BitField<30, 1, u32> ndv_is_active;
};
static_assert(sizeof(TextureInstInfo) <= sizeof(u32));
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) {
} const fswzadd{insn};
if (fswzadd.ndv != 0) {
- throw NotImplementedException("FSWZADD NDV");
+ LOG_WARNING(Shader, "(STUBBED) FSWZADD - NDV mode");
}
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
BitField<12, 4, u64> mov32i_mask;
} const mov{insn};
- if ((is_mov32i ? mov.mov32i_mask : mov.mask) != 0xf) {
- throw NotImplementedException("Non-full move mask");
+ u64 mask = is_mov32i ? mov.mov32i_mask : mov.mask;
+ if (mask != 0xf && mask != 0x1) {
+ LOG_WARNING(Shader, "(STUBBED) Masked Mov");
+ return;
}
v.X(mov.dest_reg, src);
}
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 {
LOG_WARNING(Shader, "(STUBBED) SR_AFFINITY");
return ir.Imm32(0); // This is the default value hardware returns.
default:
- throw NotImplementedException("S2R special register {}", special_register);
+ LOG_CRITICAL(Shader, "(STUBBED) Special register {}", special_register);
+ return ir.Imm32(0); // This is the default value hardware returns.
}
}
} // 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) {
}
void TranslatorVisitor::RAM(u64) {
- ThrowNotImplemented(Opcode::RAM);
+ LOG_WARNING(Shader, "(STUBBED) RAM Instruction");
}
void TranslatorVisitor::RET(u64) {
@@ -221,7 +221,7 @@ void TranslatorVisitor::RTT(u64) {
}
void TranslatorVisitor::SAM(u64) {
- ThrowNotImplemented(Opcode::SAM);
+ LOG_WARNING(Shader, "(STUBBED) SAM Instruction");
}
void 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,
info.is_depth.Assign(tex.dc != 0 ? 1 : 0);
info.has_bias.Assign(blod == Blod::LB || blod == Blod::LBA ? 1 : 0);
info.has_lod_clamp.Assign(lc ? 1 : 0);
+ info.ndv_is_active.Assign(tex.ndv != 0 ? 1 : 0);
const IR::Value sample{[&]() -> IR::Value {
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 @@
#include "shader_recompiler/environment.h"
#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/frontend/ir/modifiers.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/ir_opt/passes.h"
@@ -410,7 +411,49 @@ void FoldSelect(IR::Inst& inst) {
}
}
+void FoldFPAdd32(IR::Inst& inst) {
+ if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a + b; })) {
+ return;
+ }
+ const IR::Value lhs_value{inst.Arg(0)};
+ const IR::Value rhs_value{inst.Arg(1)};
+ const auto check_neutral = [](const IR::Value& one_operand) {
+ return one_operand.IsImmediate() && std::abs(one_operand.F32()) == 0.0f;
+ };
+ if (check_neutral(lhs_value)) {
+ inst.ReplaceUsesWith(rhs_value);
+ }
+ if (check_neutral(rhs_value)) {
+ inst.ReplaceUsesWith(lhs_value);
+ }
+}
+
+bool FoldDerivateYFromCorrection(IR::Inst& inst) {
+ const IR::Value lhs_value{inst.Arg(0)};
+ const IR::Value rhs_value{inst.Arg(1)};
+ IR::Inst* const lhs_op{lhs_value.InstRecursive()};
+ IR::Inst* const rhs_op{rhs_value.InstRecursive()};
+ if (lhs_op->GetOpcode() == IR::Opcode::YDirection) {
+ if (rhs_op->GetOpcode() != IR::Opcode::DPdyFine) {
+ return false;
+ }
+ inst.ReplaceUsesWith(rhs_value);
+ return true;
+ }
+ if (rhs_op->GetOpcode() != IR::Opcode::YDirection) {
+ return false;
+ }
+ if (lhs_op->GetOpcode() != IR::Opcode::DPdyFine) {
+ return false;
+ }
+ inst.ReplaceUsesWith(lhs_value);
+ return true;
+}
+
void FoldFPMul32(IR::Inst& inst) {
+ if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a * b; })) {
+ return;
+ }
const auto control{inst.Flags<IR::FpControl>()};
if (control.no_contraction) {
return;
@@ -421,6 +464,9 @@ void FoldFPMul32(IR::Inst& inst) {
if (lhs_value.IsImmediate() || rhs_value.IsImmediate()) {
return;
}
+ if (FoldDerivateYFromCorrection(inst)) {
+ return;
+ }
IR::Inst* const lhs_op{lhs_value.InstRecursive()};
IR::Inst* const rhs_op{rhs_value.InstRecursive()};
if (lhs_op->GetOpcode() != IR::Opcode::FPMul32 ||
@@ -622,7 +668,12 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) {
}
const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)};
if (value_2 != value_3) {
- return;
+ if (!value_2.IsImmediate() || !value_3.IsImmediate()) {
+ return;
+ }
+ if (Common::BitCast<u32>(value_2.F32()) != value_3.U32()) {
+ return;
+ }
}
const IR::Value index{inst2->Arg(1)};
const IR::Value clamp{inst2->Arg(2)};
@@ -648,6 +699,169 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) {
}
}
+bool FindGradient3DDerivates(std::array<IR::Value, 3>& results, IR::Value coord) {
+ if (coord.IsImmediate()) {
+ return false;
+ }
+ const auto check_through_shuffle = [](IR::Value input, IR::Value& result) {
+ const IR::Value value_1{GetThroughCast(input.Resolve(), IR::Opcode::BitCastF32U32)};
+ IR::Inst* const inst2{value_1.InstRecursive()};
+ if (inst2->GetOpcode() != IR::Opcode::ShuffleIndex) {
+ return false;
+ }
+ const IR::Value index{inst2->Arg(1).Resolve()};
+ const IR::Value clamp{inst2->Arg(2).Resolve()};
+ const IR::Value segmentation_mask{inst2->Arg(3).Resolve()};
+ if (!index.IsImmediate() || !clamp.IsImmediate() || !segmentation_mask.IsImmediate()) {
+ return false;
+ }
+ if (index.U32() != 3 && clamp.U32() != 3) {
+ return false;
+ }
+ result = GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32);
+ return true;
+ };
+ IR::Inst* const inst = coord.InstRecursive();
+ if (inst->GetOpcode() != IR::Opcode::FSwizzleAdd) {
+ return false;
+ }
+ std::array<IR::Value, 3> temporary_values;
+ IR::Value value_1 = inst->Arg(0).Resolve();
+ IR::Value value_2 = inst->Arg(1).Resolve();
+ IR::Value value_3 = inst->Arg(2).Resolve();
+ std::array<u32, 4> swizzles_mask_a{};
+ std::array<u32, 4> swizzles_mask_b{};
+ const auto resolve_mask = [](std::array<u32, 4>& mask_results, IR::Value mask) {
+ u32 value = mask.U32();
+ for (size_t i = 0; i < 4; i++) {
+ mask_results[i] = (value >> (i * 2)) & 0x3;
+ }
+ };
+ resolve_mask(swizzles_mask_a, value_3);
+ size_t coordinate_index = 0;
+ const auto resolve_pending = [&](IR::Value resolve_v) {
+ IR::Inst* const inst_r = resolve_v.InstRecursive();
+ if (inst_r->GetOpcode() != IR::Opcode::FSwizzleAdd) {
+ return false;
+ }
+ if (!check_through_shuffle(inst_r->Arg(0).Resolve(), temporary_values[1])) {
+ return false;
+ }
+ if (!check_through_shuffle(inst_r->Arg(1).Resolve(), temporary_values[2])) {
+ return false;
+ }
+ resolve_mask(swizzles_mask_b, inst_r->Arg(2).Resolve());
+ return true;
+ };
+ if (value_1.IsImmediate() || value_2.IsImmediate()) {
+ return false;
+ }
+ bool should_continue = false;
+ if (resolve_pending(value_1)) {
+ should_continue = check_through_shuffle(value_2, temporary_values[0]);
+ coordinate_index = 0;
+ }
+ if (resolve_pending(value_2)) {
+ should_continue = check_through_shuffle(value_1, temporary_values[0]);
+ coordinate_index = 2;
+ }
+ if (!should_continue) {
+ return false;
+ }
+ // figure which is which
+ size_t zero_mask_a = 0;
+ size_t zero_mask_b = 0;
+ for (size_t i = 0; i < 4; i++) {
+ if (swizzles_mask_a[i] == 2 || swizzles_mask_b[i] == 2) {
+ // last operand can be inversed, we cannot determine a result.
+ return false;
+ }
+ zero_mask_a |= static_cast<size_t>(swizzles_mask_a[i] == 3 ? 1 : 0) << i;
+ zero_mask_b |= static_cast<size_t>(swizzles_mask_b[i] == 3 ? 1 : 0) << i;
+ }
+ static constexpr size_t ddx_pattern = 0b1010;
+ static constexpr size_t ddx_pattern_inv = ~ddx_pattern & 0b00001111;
+ if (std::popcount(zero_mask_a) != 2) {
+ return false;
+ }
+ if (std::popcount(zero_mask_b) != 2) {
+ return false;
+ }
+ if (zero_mask_a == zero_mask_b) {
+ return false;
+ }
+ results[0] = temporary_values[coordinate_index];
+
+ if (coordinate_index == 0) {
+ if (zero_mask_b == ddx_pattern || zero_mask_b == ddx_pattern_inv) {
+ results[1] = temporary_values[1];
+ results[2] = temporary_values[2];
+ return true;
+ }
+ results[2] = temporary_values[1];
+ results[1] = temporary_values[2];
+ } else {
+ const auto assign_result = [&results](IR::Value temporary_value, size_t mask) {
+ if (mask == ddx_pattern || mask == ddx_pattern_inv) {
+ results[1] = temporary_value;
+ return;
+ }
+ results[2] = temporary_value;
+ };
+ assign_result(temporary_values[1], zero_mask_b);
+ assign_result(temporary_values[0], zero_mask_a);
+ }
+
+ return true;
+}
+
+void FoldImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
+ IR::TextureInstInfo info = inst.Flags<IR::TextureInstInfo>();
+ auto orig_opcode = inst.GetOpcode();
+ if (info.ndv_is_active == 0) {
+ return;
+ }
+ if (info.type != TextureType::Color3D) {
+ return;
+ }
+ const IR::Value handle{inst.Arg(0)};
+ const IR::Value coords{inst.Arg(1)};
+ const IR::Value bias_lc{inst.Arg(2)};
+ const IR::Value offset{inst.Arg(3)};
+ if (!offset.IsImmediate()) {
+ return;
+ }
+ IR::Inst* const inst2 = coords.InstRecursive();
+ std::array<std::array<IR::Value, 3>, 3> results_matrix;
+ for (size_t i = 0; i < 3; i++) {
+ if (!FindGradient3DDerivates(results_matrix[i], inst2->Arg(i).Resolve())) {
+ return;
+ }
+ }
+ IR::F32 lod_clamp{};
+ if (info.has_lod_clamp != 0) {
+ if (!bias_lc.IsImmediate()) {
+ lod_clamp = IR::F32{bias_lc.InstRecursive()->Arg(1).Resolve()};
+ } else {
+ lod_clamp = IR::F32{bias_lc};
+ }
+ }
+ IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
+ IR::Value new_coords =
+ ir.CompositeConstruct(results_matrix[0][0], results_matrix[1][0], results_matrix[2][0]);
+ IR::Value derivatives_1 = ir.CompositeConstruct(results_matrix[0][1], results_matrix[0][2],
+ results_matrix[1][1], results_matrix[1][2]);
+ IR::Value derivatives_2 = ir.CompositeConstruct(results_matrix[2][1], results_matrix[2][2]);
+ info.num_derivates.Assign(3);
+ IR::Value new_gradient_instruction =
+ ir.ImageGradient(handle, new_coords, derivatives_1, derivatives_2, lod_clamp, info);
+ IR::Inst* const new_inst = new_gradient_instruction.InstRecursive();
+ if (orig_opcode == IR::Opcode::ImageSampleImplicitLod) {
+ new_inst->ReplaceOpcode(IR::Opcode::ImageGradient);
+ }
+ inst.ReplaceUsesWith(new_gradient_instruction);
+}
+
void FoldConstBuffer(Environment& env, IR::Block& block, IR::Inst& inst) {
const IR::Value bank{inst.Arg(0)};
const IR::Value offset{inst.Arg(1)};
@@ -743,6 +957,12 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) {
case IR::Opcode::SelectF32:
case IR::Opcode::SelectF64:
return FoldSelect(inst);
+ case IR::Opcode::FPNeg32:
+ FoldWhenAllImmediates(inst, [](f32 a) { return -a; });
+ return;
+ case IR::Opcode::FPAdd32:
+ FoldFPAdd32(inst);
+ return;
case IR::Opcode::FPMul32:
return FoldFPMul32(inst);
case IR::Opcode::LogicalAnd:
@@ -858,6 +1078,11 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) {
FoldDriverConstBuffer(env, block, inst, 1);
}
break;
+ case IR::Opcode::BindlessImageSampleImplicitLod:
+ case IR::Opcode::BoundImageSampleImplicitLod:
+ case IR::Opcode::ImageSampleImplicitLod:
+ FoldImageSampleImplicitLod(block, inst);
+ break;
default:
break;
}
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]") {
// Pushing more values than space available should partially succeed.
{
std::vector<char> to_push(6);
- std::iota(to_push.begin(), to_push.end(), 88);
+ std::iota(to_push.begin(), to_push.end(), static_cast<char>(88));
const std::size_t count = buf.Push(to_push);
REQUIRE(count == 3U);
}
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 7f79111e0..9b13ccbab 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -275,6 +275,8 @@ add_library(video_core STATIC
vulkan_common/nsight_aftermath_tracker.cpp
vulkan_common/nsight_aftermath_tracker.h
vulkan_common/vma.cpp
+ vulkan_common/vma.h
+ vulkan_common/vulkan.h
)
create_target_directory_groups(video_core)
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
MarkWrittenBuffer(buffer_id, *cpu_addr, size);
break;
case ObtainBufferOperation::DiscardWrite: {
- IntervalType interval{*cpu_addr, size};
+ VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64);
+ VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64);
+ IntervalType interval{cpu_addr_start, cpu_addr_end};
ClearDownload(interval);
+ common_ranges.subtract(interval);
break;
}
default:
@@ -1159,6 +1162,11 @@ void BufferCache<P>::UpdateDrawIndirect() {
.size = static_cast<u32>(size),
.buffer_id = FindBuffer(*cpu_addr, static_cast<u32>(size)),
};
+ VAddr cpu_addr_start = Common::AlignDown(*cpu_addr, 64);
+ VAddr cpu_addr_end = Common::AlignUp(*cpu_addr + size, 64);
+ IntervalType interval{cpu_addr_start, cpu_addr_end};
+ ClearDownload(interval);
+ common_ranges.subtract(interval);
};
if (current_draw_indirect->include_count) {
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 @@
namespace Tegra {
constexpr u32 MacroRegistersStart = 0xE00;
+constexpr u32 ComputeInline = 0x6D;
DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
Control::ChannelState& channel_state_)
@@ -83,12 +84,35 @@ bool DmaPusher::Step() {
dma_state.dma_get, command_list_header.size * sizeof(u32));
}
}
- Core::Memory::GpuGuestMemory<Tegra::CommandHeader,
- Core::Memory::GuestMemoryFlags::UnsafeRead>
- headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers);
- ProcessCommands(headers);
+ const auto safe_process = [&] {
+ Core::Memory::GpuGuestMemory<Tegra::CommandHeader,
+ Core::Memory::GuestMemoryFlags::SafeRead>
+ headers(memory_manager, dma_state.dma_get, command_list_header.size,
+ &command_headers);
+ ProcessCommands(headers);
+ };
+ const auto unsafe_process = [&] {
+ Core::Memory::GpuGuestMemory<Tegra::CommandHeader,
+ Core::Memory::GuestMemoryFlags::UnsafeRead>
+ headers(memory_manager, dma_state.dma_get, command_list_header.size,
+ &command_headers);
+ ProcessCommands(headers);
+ };
+ if (Settings::IsGPULevelHigh()) {
+ if (dma_state.method >= MacroRegistersStart) {
+ unsafe_process();
+ return true;
+ }
+ if (subchannel_type[dma_state.subchannel] == Engines::EngineTypes::KeplerCompute &&
+ dma_state.method == ComputeInline) {
+ unsafe_process();
+ return true;
+ }
+ safe_process();
+ return true;
+ }
+ unsafe_process();
}
-
return true;
}
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:
void DispatchCalls();
- void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id) {
+ void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id,
+ Engines::EngineTypes engine_type) {
subchannels[subchannel_id] = engine;
+ subchannel_type[subchannel_id] = engine_type;
}
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
@@ -170,6 +172,7 @@ private:
const bool ib_enable{true}; ///< IB mode enabled
std::array<Engines::EngineInterface*, max_subchannels> subchannels{};
+ std::array<Engines::EngineTypes, max_subchannels> subchannel_type;
GPU& gpu;
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 @@
namespace Tegra::Engines {
+enum class EngineTypes : u32 {
+ KeplerCompute,
+ Maxwell3D,
+ Fermi2D,
+ MaxwellDMA,
+ KeplerMemory,
+};
+
class EngineInterface {
public:
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:
/// Binds a rasterizer to this engine.
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
+ GPUVAddr ExecTargetAddress() const {
+ return regs.dest.Address();
+ }
+
+ u32 GetUploadSize() const {
+ return copy_size;
+ }
+
private:
void ProcessData(std::span<const u8> read_buffer);
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
switch (method) {
case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
+ UploadInfo info{.upload_address = upload_address,
+ .exec_address = upload_state.ExecTargetAddress(),
+ .copy_size = upload_state.GetUploadSize()};
+ uploads.push_back(info);
upload_state.ProcessExec(regs.exec_upload.linear != 0);
break;
}
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
+ upload_address = current_dma_segment;
upload_state.ProcessData(method_argument, is_last_call);
break;
}
- case KEPLER_COMPUTE_REG_INDEX(launch):
+ case KEPLER_COMPUTE_REG_INDEX(launch): {
+ const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
+
+ for (auto& data : uploads) {
+ const GPUVAddr offset = data.exec_address - launch_desc_loc;
+ if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) &&
+ memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) {
+ indirect_compute = {data.upload_address};
+ }
+ }
+ uploads.clear();
ProcessLaunch();
+ indirect_compute = std::nullopt;
break;
+ }
default:
break;
}
@@ -62,6 +79,7 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun
u32 methods_pending) {
switch (method) {
case KEPLER_COMPUTE_REG_INDEX(data_upload):
+ upload_address = current_dma_segment;
upload_state.ProcessData(base_start, amount);
return;
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 @@
#include <array>
#include <cstddef>
+#include <optional>
#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
@@ -36,6 +37,9 @@ namespace Tegra::Engines {
#define KEPLER_COMPUTE_REG_INDEX(field_name) \
(offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32))
+#define LAUNCH_REG_INDEX(field_name) \
+ (offsetof(Tegra::Engines::KeplerCompute::LaunchParams, field_name) / sizeof(u32))
+
class KeplerCompute final : public EngineInterface {
public:
explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager);
@@ -201,6 +205,10 @@ public:
void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) override;
+ std::optional<GPUVAddr> GetIndirectComputeAddress() const {
+ return indirect_compute;
+ }
+
private:
void ProcessLaunch();
@@ -216,6 +224,15 @@ private:
MemoryManager& memory_manager;
VideoCore::RasterizerInterface* rasterizer = nullptr;
Upload::State upload_state;
+ GPUVAddr upload_address;
+
+ struct UploadInfo {
+ GPUVAddr upload_address;
+ GPUVAddr exec_address;
+ u32 copy_size;
+ };
+ std::vector<UploadInfo> uploads;
+ std::optional<GPUVAddr> indirect_compute{};
};
#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() {
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
num_vertices = std::max(
num_vertices, address_size / std::max(attribute.SizeInBytes(), array.stride.Value()));
+ break;
}
return num_vertices;
}
@@ -269,10 +270,13 @@ size_t Maxwell3D::EstimateIndexBufferSize() {
std::numeric_limits<u32>::max()};
const size_t byte_size = regs.index_buffer.FormatSizeInBytes();
const size_t log2_byte_size = Common::Log2Ceil64(byte_size);
+ const size_t cap{GetMaxCurrentVertices() * 3 * byte_size};
+ const size_t lower_cap =
+ std::min<size_t>(static_cast<size_t>(end_address - start_address), cap);
return std::min<size_t>(
memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) /
byte_size,
- static_cast<size_t>(end_address - start_address));
+ lower_cap);
}
u32 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..279f0daa1 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
+#include "common/polyfill_ranges.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/memory.h"
@@ -108,10 +109,11 @@ void MaxwellDMA::Launch() {
const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A;
if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) {
ASSERT(regs.remap_const.component_size_minus_one == 3);
- accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value);
+ accelerate.BufferClear(regs.offset_out, regs.line_length_in,
+ regs.remap_const.remap_consta_value);
read_buffer.resize_destructive(regs.line_length_in * sizeof(u32));
std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in);
- std::ranges::fill(span, regs.remap_consta_value);
+ std::ranges::fill(span, regs.remap_const.remap_consta_value);
memory_manager.WriteBlockUnsafe(regs.offset_out,
reinterpret_cast<u8*>(read_buffer.data()),
regs.line_length_in * sizeof(u32));
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index 69e26cb32..1a43e24b6 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -214,14 +214,15 @@ public:
NO_WRITE = 6,
};
- PackedGPUVAddr address;
+ u32 remap_consta_value;
+ u32 remap_constb_value;
union {
+ BitField<0, 12, u32> dst_components_raw;
BitField<0, 3, Swizzle> dst_x;
BitField<4, 3, Swizzle> dst_y;
BitField<8, 3, Swizzle> dst_z;
BitField<12, 3, Swizzle> dst_w;
- BitField<0, 12, u32> dst_components_raw;
BitField<16, 2, u32> component_size_minus_one;
BitField<20, 2, u32> num_src_components_minus_one;
BitField<24, 2, u32> num_dst_components_minus_one;
@@ -274,55 +275,57 @@ private:
struct Regs {
union {
struct {
- u32 reserved[0x40];
+ INSERT_PADDING_BYTES_NOINIT(0x100);
u32 nop;
- u32 reserved01[0xf];
+ INSERT_PADDING_BYTES_NOINIT(0x3C);
u32 pm_trigger;
- u32 reserved02[0x3f];
+ INSERT_PADDING_BYTES_NOINIT(0xFC);
Semaphore semaphore;
- u32 reserved03[0x2];
+ INSERT_PADDING_BYTES_NOINIT(0x8);
RenderEnable render_enable;
PhysMode src_phys_mode;
PhysMode dst_phys_mode;
- u32 reserved04[0x26];
+ INSERT_PADDING_BYTES_NOINIT(0x98);
LaunchDMA launch_dma;
- u32 reserved05[0x3f];
+ INSERT_PADDING_BYTES_NOINIT(0xFC);
PackedGPUVAddr offset_in;
PackedGPUVAddr offset_out;
s32 pitch_in;
s32 pitch_out;
u32 line_length_in;
u32 line_count;
- u32 reserved06[0xb6];
- u32 remap_consta_value;
- u32 remap_constb_value;
+ INSERT_PADDING_BYTES_NOINIT(0x2E0);
RemapConst remap_const;
DMA::Parameters dst_params;
- u32 reserved07[0x1];
+ INSERT_PADDING_BYTES_NOINIT(0x4);
DMA::Parameters src_params;
- u32 reserved08[0x275];
+ INSERT_PADDING_BYTES_NOINIT(0x9D4);
u32 pm_trigger_end;
- u32 reserved09[0x3ba];
+ INSERT_PADDING_BYTES_NOINIT(0xEE8);
};
std::array<u32, NUM_REGS> reg_array;
};
} regs{};
+ static_assert(sizeof(Regs) == NUM_REGS * 4);
#define ASSERT_REG_POSITION(field_name, position) \
- static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \
+ static_assert(offsetof(MaxwellDMA::Regs, field_name) == position, \
"Field " #field_name " has invalid position")
- ASSERT_REG_POSITION(launch_dma, 0xC0);
- ASSERT_REG_POSITION(offset_in, 0x100);
- ASSERT_REG_POSITION(offset_out, 0x102);
- ASSERT_REG_POSITION(pitch_in, 0x104);
- ASSERT_REG_POSITION(pitch_out, 0x105);
- ASSERT_REG_POSITION(line_length_in, 0x106);
- ASSERT_REG_POSITION(line_count, 0x107);
- ASSERT_REG_POSITION(remap_const, 0x1C0);
- ASSERT_REG_POSITION(dst_params, 0x1C3);
- ASSERT_REG_POSITION(src_params, 0x1CA);
-
+ ASSERT_REG_POSITION(semaphore, 0x240);
+ ASSERT_REG_POSITION(render_enable, 0x254);
+ ASSERT_REG_POSITION(src_phys_mode, 0x260);
+ ASSERT_REG_POSITION(launch_dma, 0x300);
+ ASSERT_REG_POSITION(offset_in, 0x400);
+ ASSERT_REG_POSITION(offset_out, 0x408);
+ ASSERT_REG_POSITION(pitch_in, 0x410);
+ ASSERT_REG_POSITION(pitch_out, 0x414);
+ ASSERT_REG_POSITION(line_length_in, 0x418);
+ ASSERT_REG_POSITION(line_count, 0x41C);
+ ASSERT_REG_POSITION(remap_const, 0x700);
+ ASSERT_REG_POSITION(dst_params, 0x70C);
+ ASSERT_REG_POSITION(src_params, 0x728);
+ ASSERT_REG_POSITION(pm_trigger_end, 0x1114);
#undef ASSERT_REG_POSITION
};
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) {
bound_engines[method_call.subchannel] = engine_id;
switch (engine_id) {
case EngineID::FERMI_TWOD_A:
- dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel);
+ dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel,
+ EngineTypes::Fermi2D);
break;
case EngineID::MAXWELL_B:
- dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel);
+ dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel,
+ EngineTypes::Maxwell3D);
break;
case EngineID::KEPLER_COMPUTE_B:
- dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel);
+ dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel,
+ EngineTypes::KeplerCompute);
break;
case EngineID::MAXWELL_DMA_COPY_A:
- dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel);
+ dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel,
+ EngineTypes::MaxwellDMA);
break;
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
- dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel);
+ dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel,
+ EngineTypes::KeplerMemory);
break;
default:
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 da07a556f..8d7da50fc 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -247,7 +247,7 @@ void Codec::Initialize() {
av_codec = avcodec_find_decoder(codec);
InitializeAvCodecContext();
- if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::GPU) {
+ if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) {
InitializeGpuDecoder();
}
if (const int res = avcodec_open2(av_codec_ctx, av_codec, nullptr); res < 0) {
@@ -319,6 +319,7 @@ void Codec::Decode() {
LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
return;
}
+ bool is_interlaced = initial_frame->interlaced_frame != 0;
if (av_codec_ctx->hw_device_ctx) {
final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed");
@@ -334,7 +335,7 @@ void Codec::Decode() {
UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
return;
}
- if (!final_frame->interlaced_frame) {
+ if (!is_interlaced) {
av_frames.push(std::move(final_frame));
} else {
if (!filters_initialized) {
diff --git a/src/video_core/host1x/codecs/h264.cpp b/src/video_core/host1x/codecs/h264.cpp
index 862904e39..ece79b1e2 100644
--- a/src/video_core/host1x/codecs/h264.cpp
+++ b/src/video_core/host1x/codecs/h264.cpp
@@ -84,7 +84,7 @@ std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters
// TODO (ameerj): Where do we get this number, it seems to be particular for each stream
const auto nvdec_decoding = Settings::values.nvdec_emulation.GetValue();
- const bool uses_gpu_decoding = nvdec_decoding == Settings::NvdecEmulation::GPU;
+ const bool uses_gpu_decoding = nvdec_decoding == Settings::NvdecEmulation::Gpu;
const u32 max_num_ref_frames = uses_gpu_decoding ? 6u : 16u;
writer.WriteUe(max_num_ref_frames);
writer.WriteBit(false);
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
vulkan_blit_depth_stencil.frag
vulkan_color_clear.frag
vulkan_color_clear.vert
+ vulkan_depthstencil_clear.frag
vulkan_fidelityfx_fsr_easu_fp16.comp
vulkan_fidelityfx_fsr_easu_fp32.comp
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;
END_PUSH_CONSTANTS
struct EncodingData {
- uint encoding;
- uint num_bits;
- uint bit_value;
- uint quint_trit_value;
+ uint data;
};
-struct TexelWeightParams {
- uvec2 size;
- uint max_weight;
- bool dual_plane;
- bool error_state;
- bool void_extent_ldr;
- bool void_extent_hdr;
-};
-
-layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 {
+layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBufferU32 {
uvec4 astc_data[];
};
-layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly image2DArray dest_image;
+layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
@@ -60,64 +48,21 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uint BYTES_PER_BLOCK_LOG2 = 4;
-const int JUST_BITS = 0;
-const int QUINT = 1;
-const int TRIT = 2;
+const uint JUST_BITS = 0u;
+const uint QUINT = 1u;
+const uint TRIT = 2u;
// ASTC Encodings data, sorted in ascending order based on their BitLength value
// (see GetBitLength() function)
-EncodingData encoding_values[22] = EncodingData[](
- EncodingData(JUST_BITS, 0, 0, 0), EncodingData(JUST_BITS, 1, 0, 0), EncodingData(TRIT, 0, 0, 0),
- EncodingData(JUST_BITS, 2, 0, 0), EncodingData(QUINT, 0, 0, 0), EncodingData(TRIT, 1, 0, 0),
- EncodingData(JUST_BITS, 3, 0, 0), EncodingData(QUINT, 1, 0, 0), EncodingData(TRIT, 2, 0, 0),
- EncodingData(JUST_BITS, 4, 0, 0), EncodingData(QUINT, 2, 0, 0), EncodingData(TRIT, 3, 0, 0),
- EncodingData(JUST_BITS, 5, 0, 0), EncodingData(QUINT, 3, 0, 0), EncodingData(TRIT, 4, 0, 0),
- EncodingData(JUST_BITS, 6, 0, 0), EncodingData(QUINT, 4, 0, 0), EncodingData(TRIT, 5, 0, 0),
- EncodingData(JUST_BITS, 7, 0, 0), EncodingData(QUINT, 5, 0, 0), EncodingData(TRIT, 6, 0, 0),
- EncodingData(JUST_BITS, 8, 0, 0)
-);
-
-// The following constants are expanded variants of the Replicate()
-// function calls corresponding to the following arguments:
-// value: index into the generated table
-// num_bits: the after "REPLICATE" in the table name. i.e. 4 is num_bits in REPLICATE_4.
-// to_bit: the integer after "TO_"
-const uint REPLICATE_BIT_TO_7_TABLE[2] = uint[](0, 127);
-const uint REPLICATE_1_BIT_TO_9_TABLE[2] = uint[](0, 511);
-
-const uint REPLICATE_1_BIT_TO_8_TABLE[2] = uint[](0, 255);
-const uint REPLICATE_2_BIT_TO_8_TABLE[4] = uint[](0, 85, 170, 255);
-const uint REPLICATE_3_BIT_TO_8_TABLE[8] = uint[](0, 36, 73, 109, 146, 182, 219, 255);
-const uint REPLICATE_4_BIT_TO_8_TABLE[16] =
- uint[](0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255);
-const uint REPLICATE_5_BIT_TO_8_TABLE[32] =
- uint[](0, 8, 16, 24, 33, 41, 49, 57, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165,
- 173, 181, 189, 198, 206, 214, 222, 231, 239, 247, 255);
-const uint REPLICATE_1_BIT_TO_6_TABLE[2] = uint[](0, 63);
-const uint REPLICATE_2_BIT_TO_6_TABLE[4] = uint[](0, 21, 42, 63);
-const uint REPLICATE_3_BIT_TO_6_TABLE[8] = uint[](0, 9, 18, 27, 36, 45, 54, 63);
-const uint REPLICATE_4_BIT_TO_6_TABLE[16] =
- uint[](0, 4, 8, 12, 17, 21, 25, 29, 34, 38, 42, 46, 51, 55, 59, 63);
-const uint REPLICATE_5_BIT_TO_6_TABLE[32] =
- uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 33, 35, 37, 39, 41, 43, 45,
- 47, 49, 51, 53, 55, 57, 59, 61, 63);
-const uint REPLICATE_6_BIT_TO_8_TABLE[64] =
- uint[](0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 65, 69, 73, 77, 81, 85, 89,
- 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162,
- 166, 170, 174, 178, 182, 186, 190, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235,
- 239, 243, 247, 251, 255);
-const uint REPLICATE_7_BIT_TO_8_TABLE[128] =
- uint[](0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44,
- 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88,
- 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126,
- 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163,
- 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199,
- 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235,
- 237, 239, 241, 243, 245, 247, 249, 251, 253, 255);
+const uint encoding_values[22] = uint[](
+ (JUST_BITS), (JUST_BITS | (1u << 8u)), (TRIT), (JUST_BITS | (2u << 8u)),
+ (QUINT), (TRIT | (1u << 8u)), (JUST_BITS | (3u << 8u)), (QUINT | (1u << 8u)),
+ (TRIT | (2u << 8u)), (JUST_BITS | (4u << 8u)), (QUINT | (2u << 8u)), (TRIT | (3u << 8u)),
+ (JUST_BITS | (5u << 8u)), (QUINT | (3u << 8u)), (TRIT | (4u << 8u)), (JUST_BITS | (6u << 8u)),
+ (QUINT | (4u << 8u)), (TRIT | (5u << 8u)), (JUST_BITS | (7u << 8u)), (QUINT | (5u << 8u)),
+ (TRIT | (6u << 8u)), (JUST_BITS | (8u << 8u)));
// Input ASTC texture globals
-uint current_index = 0;
-int bitsread = 0;
int total_bitsread = 0;
uvec4 local_buff;
@@ -125,50 +70,60 @@ uvec4 local_buff;
uvec4 color_endpoint_data;
int color_bitsread = 0;
-// Four values, two endpoints, four maximum partitions
-uint color_values[32];
-int colvals_index = 0;
-
-// Weight data globals
-uvec4 texel_weight_data;
-int texel_bitsread = 0;
+// Global "vector" to be pushed into when decoding
+// At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode
+// At most will require BLOCK_WIDTH x BLOCK_HEIGHT x 2 in dual plane mode
+// So the maximum would be 144 (12 x 12) elements, x 2 for two planes
+#define DIVCEIL(number, divisor) (number + divisor - 1) / divisor
+#define ARRAY_NUM_ELEMENTS 144
+#define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4)
+uint result_vector[ARRAY_NUM_ELEMENTS * 2];
-bool texel_flag = false;
-
-// Global "vectors" to be pushed into when decoding
-EncodingData result_vector[144];
int result_index = 0;
+uint result_vector_max_index;
+bool result_limit_reached = false;
-EncodingData texel_vector[144];
-int texel_vector_index = 0;
+// EncodingData helpers
+uint Encoding(EncodingData val) {
+ return bitfieldExtract(val.data, 0, 8);
+}
+uint NumBits(EncodingData val) {
+ return bitfieldExtract(val.data, 8, 8);
+}
+uint BitValue(EncodingData val) {
+ return bitfieldExtract(val.data, 16, 8);
+}
+uint QuintTritValue(EncodingData val) {
+ return bitfieldExtract(val.data, 24, 8);
+}
-uint unquantized_texel_weights[2][144];
+void Encoding(inout EncodingData val, uint v) {
+ val.data = bitfieldInsert(val.data, v, 0, 8);
+}
+void NumBits(inout EncodingData val, uint v) {
+ val.data = bitfieldInsert(val.data, v, 8, 8);
+}
+void BitValue(inout EncodingData val, uint v) {
+ val.data = bitfieldInsert(val.data, v, 16, 8);
+}
+void QuintTritValue(inout EncodingData val, uint v) {
+ val.data = bitfieldInsert(val.data, v, 24, 8);
+}
-uint SwizzleOffset(uvec2 pos) {
- uint x = pos.x;
- uint y = pos.y;
- return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
- (y % 2) * 16 + (x % 16);
+EncodingData CreateEncodingData(uint encoding, uint num_bits, uint bit_val, uint quint_trit_val) {
+ return EncodingData(((encoding) << 0u) | ((num_bits) << 8u) |
+ ((bit_val) << 16u) | ((quint_trit_val) << 24u));
}
-// Replicates low num_bits such that [(to_bit - 1):(to_bit - 1 - from_bit)]
-// is the same as [(num_bits - 1):0] and repeats all the way down.
-uint Replicate(uint val, uint num_bits, uint to_bit) {
- const uint v = val & uint((1 << num_bits) - 1);
- uint res = v;
- uint reslen = num_bits;
- while (reslen < to_bit) {
- uint comp = 0;
- if (num_bits > to_bit - reslen) {
- uint newshift = to_bit - reslen;
- comp = num_bits - newshift;
- num_bits = newshift;
- }
- res = uint(res << num_bits);
- res = uint(res | (v >> comp));
- reslen += num_bits;
+
+void ResultEmplaceBack(EncodingData val) {
+ if (result_index >= result_vector_max_index) {
+ // Alert callers to avoid decoding more than needed by this phase
+ result_limit_reached = true;
+ return;
}
- return res;
+ result_vector[result_index] = val.data;
+ ++result_index;
}
uvec4 ReplicateByteTo16(uvec4 value) {
@@ -176,64 +131,40 @@ uvec4 ReplicateByteTo16(uvec4 value) {
}
uint ReplicateBitTo7(uint value) {
- return REPLICATE_BIT_TO_7_TABLE[value];
+ return value * 127;
}
uint ReplicateBitTo9(uint value) {
- return REPLICATE_1_BIT_TO_9_TABLE[value];
+ return value * 511;
}
-uint FastReplicate(uint value, uint num_bits, uint to_bit) {
- if (num_bits == 0) {
+uint ReplicateBits(uint value, uint num_bits, uint to_bit) {
+ if (value == 0 || num_bits == 0) {
return 0;
}
- if (num_bits == to_bit) {
+ if (num_bits >= to_bit) {
return value;
}
- if (to_bit == 6) {
- switch (num_bits) {
- case 1:
- return REPLICATE_1_BIT_TO_6_TABLE[value];
- case 2:
- return REPLICATE_2_BIT_TO_6_TABLE[value];
- case 3:
- return REPLICATE_3_BIT_TO_6_TABLE[value];
- case 4:
- return REPLICATE_4_BIT_TO_6_TABLE[value];
- case 5:
- return REPLICATE_5_BIT_TO_6_TABLE[value];
- default:
- break;
- }
- } else { /* if (to_bit == 8) */
- switch (num_bits) {
- case 1:
- return REPLICATE_1_BIT_TO_8_TABLE[value];
- case 2:
- return REPLICATE_2_BIT_TO_8_TABLE[value];
- case 3:
- return REPLICATE_3_BIT_TO_8_TABLE[value];
- case 4:
- return REPLICATE_4_BIT_TO_8_TABLE[value];
- case 5:
- return REPLICATE_5_BIT_TO_8_TABLE[value];
- case 6:
- return REPLICATE_6_BIT_TO_8_TABLE[value];
- case 7:
- return REPLICATE_7_BIT_TO_8_TABLE[value];
- default:
- break;
- }
+ const uint v = value & uint((1 << num_bits) - 1);
+ uint res = v;
+ uint reslen = num_bits;
+ while (reslen < to_bit) {
+ const uint num_dst_bits_to_shift_up = min(num_bits, to_bit - reslen);
+ const uint num_src_bits_to_shift_down = num_bits - num_dst_bits_to_shift_up;
+
+ res <<= num_dst_bits_to_shift_up;
+ res |= (v >> num_src_bits_to_shift_down);
+ reslen += num_bits;
}
- return Replicate(value, num_bits, to_bit);
+ return res;
}
uint FastReplicateTo8(uint value, uint num_bits) {
- return FastReplicate(value, num_bits, 8);
+ return ReplicateBits(value, num_bits, 8);
}
uint FastReplicateTo6(uint value, uint num_bits) {
- return FastReplicate(value, num_bits, 6);
+ return ReplicateBits(value, num_bits, 6);
}
uint Div3Floor(uint v) {
@@ -266,15 +197,15 @@ uint Hash52(uint p) {
return p;
}
-uint Select2DPartition(uint seed, uint x, uint y, uint partition_count, bool small_block) {
- if (small_block) {
+uint Select2DPartition(uint seed, uint x, uint y, uint partition_count) {
+ if ((block_dims.y * block_dims.x) < 32) {
x <<= 1;
y <<= 1;
}
seed += (partition_count - 1) * 1024;
- uint rnum = Hash52(uint(seed));
+ const uint rnum = Hash52(uint(seed));
uint seed1 = uint(rnum & 0xF);
uint seed2 = uint((rnum >> 4) & 0xF);
uint seed3 = uint((rnum >> 8) & 0xF);
@@ -342,53 +273,52 @@ uint ExtractBits(uvec4 payload, int offset, int bits) {
if (bits <= 0) {
return 0;
}
- int last_offset = offset + bits - 1;
- int shifted_offset = offset >> 5;
+ if (bits > 32) {
+ return 0;
+ }
+ const int last_offset = offset + bits - 1;
+ const int shifted_offset = offset >> 5;
if ((last_offset >> 5) == shifted_offset) {
return bitfieldExtract(payload[shifted_offset], offset & 31, bits);
}
- int first_bits = 32 - (offset & 31);
- int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits));
- int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits));
+ const int first_bits = 32 - (offset & 31);
+ const int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits));
+ const int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits));
return result_first | (result_second << first_bits);
}
uint StreamBits(uint num_bits) {
- int int_bits = int(num_bits);
- uint ret = ExtractBits(local_buff, total_bitsread, int_bits);
+ const int int_bits = int(num_bits);
+ const uint ret = ExtractBits(local_buff, total_bitsread, int_bits);
total_bitsread += int_bits;
return ret;
}
+void SkipBits(uint num_bits) {
+ const int int_bits = int(num_bits);
+ total_bitsread += int_bits;
+}
+
uint StreamColorBits(uint num_bits) {
- uint ret = 0;
- int int_bits = int(num_bits);
- if (texel_flag) {
- ret = ExtractBits(texel_weight_data, texel_bitsread, int_bits);
- texel_bitsread += int_bits;
- } else {
- ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits);
- color_bitsread += int_bits;
- }
+ const int int_bits = int(num_bits);
+ const uint ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits);
+ color_bitsread += int_bits;
return ret;
}
-void ResultEmplaceBack(EncodingData val) {
- if (texel_flag) {
- texel_vector[texel_vector_index] = val;
- ++texel_vector_index;
- } else {
- result_vector[result_index] = val;
- ++result_index;
- }
+EncodingData GetEncodingFromVector(uint index) {
+ const uint data = result_vector[index];
+ return EncodingData(data);
}
// Returns the number of bits required to encode n_vals values.
uint GetBitLength(uint n_vals, uint encoding_index) {
- uint total_bits = encoding_values[encoding_index].num_bits * n_vals;
- if (encoding_values[encoding_index].encoding == TRIT) {
+ const EncodingData encoding_value = EncodingData(encoding_values[encoding_index]);
+ const uint encoding = Encoding(encoding_value);
+ uint total_bits = NumBits(encoding_value) * n_vals;
+ if (encoding == TRIT) {
total_bits += Div5Ceil(n_vals * 8);
- } else if (encoding_values[encoding_index].encoding == QUINT) {
+ } else if (encoding == QUINT) {
total_bits += Div3Ceil(n_vals * 7);
}
return total_bits;
@@ -403,7 +333,7 @@ uint GetNumWeightValues(uvec2 size, bool dual_plane) {
}
uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) {
- uint n_vals = GetNumWeightValues(size, dual_plane);
+ const uint n_vals = GetNumWeightValues(size, dual_plane);
return GetBitLength(n_vals, max_weight);
}
@@ -412,87 +342,74 @@ uint BitsBracket(uint bits, uint pos) {
}
uint BitsOp(uint bits, uint start, uint end) {
- if (start == end) {
- return BitsBracket(bits, start);
- } else if (start > end) {
- uint t = start;
- start = end;
- end = t;
- }
-
- uint mask = (1 << (end - start + 1)) - 1;
+ const uint mask = (1 << (end - start + 1)) - 1;
return ((bits >> start) & mask);
}
void DecodeQuintBlock(uint num_bits) {
- uint m[3];
- uint q[3];
- uint Q;
+ uvec3 m;
+ uvec4 qQ;
m[0] = StreamColorBits(num_bits);
- Q = StreamColorBits(3);
+ qQ.w = StreamColorBits(3);
m[1] = StreamColorBits(num_bits);
- Q |= StreamColorBits(2) << 3;
+ qQ.w |= StreamColorBits(2) << 3;
m[2] = StreamColorBits(num_bits);
- Q |= StreamColorBits(2) << 5;
- if (BitsOp(Q, 1, 2) == 3 && BitsOp(Q, 5, 6) == 0) {
- q[0] = 4;
- q[1] = 4;
- q[2] = (BitsBracket(Q, 0) << 2) | ((BitsBracket(Q, 4) & ~BitsBracket(Q, 0)) << 1) |
- (BitsBracket(Q, 3) & ~BitsBracket(Q, 0));
+ qQ.w |= StreamColorBits(2) << 5;
+ if (BitsOp(qQ.w, 1, 2) == 3 && BitsOp(qQ.w, 5, 6) == 0) {
+ qQ.x = 4;
+ qQ.y = 4;
+ qQ.z = (BitsBracket(qQ.w, 0) << 2) | ((BitsBracket(qQ.w, 4) & ~BitsBracket(qQ.w, 0)) << 1) |
+ (BitsBracket(qQ.w, 3) & ~BitsBracket(qQ.w, 0));
} else {
uint C = 0;
- if (BitsOp(Q, 1, 2) == 3) {
- q[2] = 4;
- C = (BitsOp(Q, 3, 4) << 3) | ((~BitsOp(Q, 5, 6) & 3) << 1) | BitsBracket(Q, 0);
+ if (BitsOp(qQ.w, 1, 2) == 3) {
+ qQ.z = 4;
+ C = (BitsOp(qQ.w, 3, 4) << 3) | ((~BitsOp(qQ.w, 5, 6) & 3) << 1) | BitsBracket(qQ.w, 0);
} else {
- q[2] = BitsOp(Q, 5, 6);
- C = BitsOp(Q, 0, 4);
+ qQ.z = BitsOp(qQ.w, 5, 6);
+ C = BitsOp(qQ.w, 0, 4);
}
if (BitsOp(C, 0, 2) == 5) {
- q[1] = 4;
- q[0] = BitsOp(C, 3, 4);
+ qQ.y = 4;
+ qQ.x = BitsOp(C, 3, 4);
} else {
- q[1] = BitsOp(C, 3, 4);
- q[0] = BitsOp(C, 0, 2);
+ qQ.y = BitsOp(C, 3, 4);
+ qQ.x = BitsOp(C, 0, 2);
}
}
for (uint i = 0; i < 3; i++) {
- EncodingData val;
- val.encoding = QUINT;
- val.num_bits = num_bits;
- val.bit_value = m[i];
- val.quint_trit_value = q[i];
+ const EncodingData val = CreateEncodingData(QUINT, num_bits, m[i], qQ[i]);
ResultEmplaceBack(val);
}
}
void DecodeTritBlock(uint num_bits) {
- uint m[5];
- uint t[5];
- uint T;
+ uvec4 m;
+ uvec4 t;
+ uvec3 Tm5t5;
m[0] = StreamColorBits(num_bits);
- T = StreamColorBits(2);
+ Tm5t5.x = StreamColorBits(2);
m[1] = StreamColorBits(num_bits);
- T |= StreamColorBits(2) << 2;
+ Tm5t5.x |= StreamColorBits(2) << 2;
m[2] = StreamColorBits(num_bits);
- T |= StreamColorBits(1) << 4;
+ Tm5t5.x |= StreamColorBits(1) << 4;
m[3] = StreamColorBits(num_bits);
- T |= StreamColorBits(2) << 5;
- m[4] = StreamColorBits(num_bits);
- T |= StreamColorBits(1) << 7;
+ Tm5t5.x |= StreamColorBits(2) << 5;
+ Tm5t5.y = StreamColorBits(num_bits);
+ Tm5t5.x |= StreamColorBits(1) << 7;
uint C = 0;
- if (BitsOp(T, 2, 4) == 7) {
- C = (BitsOp(T, 5, 7) << 2) | BitsOp(T, 0, 1);
- t[4] = 2;
+ if (BitsOp(Tm5t5.x, 2, 4) == 7) {
+ C = (BitsOp(Tm5t5.x, 5, 7) << 2) | BitsOp(Tm5t5.x, 0, 1);
+ Tm5t5.z = 2;
t[3] = 2;
} else {
- C = BitsOp(T, 0, 4);
- if (BitsOp(T, 5, 6) == 3) {
- t[4] = 2;
- t[3] = BitsBracket(T, 7);
+ C = BitsOp(Tm5t5.x, 0, 4);
+ if (BitsOp(Tm5t5.x, 5, 6) == 3) {
+ Tm5t5.z = 2;
+ t[3] = BitsBracket(Tm5t5.x, 7);
} else {
- t[4] = BitsBracket(T, 7);
- t[3] = BitsOp(T, 5, 6);
+ Tm5t5.z = BitsBracket(Tm5t5.x, 7);
+ t[3] = BitsOp(Tm5t5.x, 5, 6);
}
}
if (BitsOp(C, 0, 1) == 3) {
@@ -508,31 +425,31 @@ void DecodeTritBlock(uint num_bits) {
t[1] = BitsOp(C, 2, 3);
t[0] = (BitsBracket(C, 1) << 1) | (BitsBracket(C, 0) & ~BitsBracket(C, 1));
}
- for (uint i = 0; i < 5; i++) {
- EncodingData val;
- val.encoding = TRIT;
- val.num_bits = num_bits;
- val.bit_value = m[i];
- val.quint_trit_value = t[i];
+ for (uint i = 0; i < 4; i++) {
+ const EncodingData val = CreateEncodingData(TRIT, num_bits, m[i], t[i]);
ResultEmplaceBack(val);
}
+ const EncodingData val = CreateEncodingData(TRIT, num_bits, Tm5t5.y, Tm5t5.z);
+ ResultEmplaceBack(val);
}
void DecodeIntegerSequence(uint max_range, uint num_values) {
- EncodingData val = encoding_values[max_range];
+ EncodingData val = EncodingData(encoding_values[max_range]);
+ const uint encoding = Encoding(val);
+ const uint num_bits = NumBits(val);
uint vals_decoded = 0;
- while (vals_decoded < num_values) {
- switch (val.encoding) {
+ while (vals_decoded < num_values && !result_limit_reached) {
+ switch (encoding) {
case QUINT:
- DecodeQuintBlock(val.num_bits);
+ DecodeQuintBlock(num_bits);
vals_decoded += 3;
break;
case TRIT:
- DecodeTritBlock(val.num_bits);
+ DecodeTritBlock(num_bits);
vals_decoded += 5;
break;
case JUST_BITS:
- val.bit_value = StreamColorBits(val.num_bits);
+ BitValue(val, StreamColorBits(num_bits));
ResultEmplaceBack(val);
vals_decoded++;
break;
@@ -540,7 +457,7 @@ void DecodeIntegerSequence(uint max_range, uint num_values) {
}
}
-void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
+void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, out uint color_values[32]) {
uint num_values = 0;
for (uint i = 0; i < num_partitions; i++) {
num_values += ((modes[i] >> 2) + 1) << 1;
@@ -549,7 +466,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
// TODO(ameerj): profile with binary search
int range = 0;
while (++range < encoding_values.length()) {
- uint bit_length = GetBitLength(num_values, range);
+ const uint bit_length = GetBitLength(num_values, range);
if (bit_length > color_data_bits) {
break;
}
@@ -560,48 +477,49 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
if (out_index >= num_values) {
break;
}
- EncodingData val = result_vector[itr];
- uint bitlen = val.num_bits;
- uint bitval = val.bit_value;
+ const EncodingData val = GetEncodingFromVector(itr);
+ const uint encoding = Encoding(val);
+ const uint bitlen = NumBits(val);
+ const uint bitval = BitValue(val);
uint A = 0, B = 0, C = 0, D = 0;
A = ReplicateBitTo9((bitval & 1));
- switch (val.encoding) {
+ switch (encoding) {
case JUST_BITS:
- color_values[out_index++] = FastReplicateTo8(bitval, bitlen);
+ color_values[++out_index] = FastReplicateTo8(bitval, bitlen);
break;
case TRIT: {
- D = val.quint_trit_value;
+ D = QuintTritValue(val);
switch (bitlen) {
case 1:
C = 204;
break;
case 2: {
C = 93;
- uint b = (bitval >> 1) & 1;
+ const uint b = (bitval >> 1) & 1;
B = (b << 8) | (b << 4) | (b << 2) | (b << 1);
break;
}
case 3: {
C = 44;
- uint cb = (bitval >> 1) & 3;
+ const uint cb = (bitval >> 1) & 3;
B = (cb << 7) | (cb << 2) | cb;
break;
}
case 4: {
C = 22;
- uint dcb = (bitval >> 1) & 7;
+ const uint dcb = (bitval >> 1) & 7;
B = (dcb << 6) | dcb;
break;
}
case 5: {
C = 11;
- uint edcb = (bitval >> 1) & 0xF;
+ const uint edcb = (bitval >> 1) & 0xF;
B = (edcb << 5) | (edcb >> 2);
break;
}
case 6: {
C = 5;
- uint fedcb = (bitval >> 1) & 0x1F;
+ const uint fedcb = (bitval >> 1) & 0x1F;
B = (fedcb << 4) | (fedcb >> 4);
break;
}
@@ -609,32 +527,32 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
break;
}
case QUINT: {
- D = val.quint_trit_value;
+ D = QuintTritValue(val);
switch (bitlen) {
case 1:
C = 113;
break;
case 2: {
C = 54;
- uint b = (bitval >> 1) & 1;
+ const uint b = (bitval >> 1) & 1;
B = (b << 8) | (b << 3) | (b << 2);
break;
}
case 3: {
C = 26;
- uint cb = (bitval >> 1) & 3;
+ const uint cb = (bitval >> 1) & 3;
B = (cb << 7) | (cb << 1) | (cb >> 1);
break;
}
case 4: {
C = 13;
- uint dcb = (bitval >> 1) & 7;
+ const uint dcb = (bitval >> 1) & 7;
B = (dcb << 6) | (dcb >> 1);
break;
}
case 5: {
C = 6;
- uint edcb = (bitval >> 1) & 0xF;
+ const uint edcb = (bitval >> 1) & 0xF;
B = (edcb << 5) | (edcb >> 3);
break;
}
@@ -642,11 +560,11 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
break;
}
}
- if (val.encoding != JUST_BITS) {
+ if (encoding != JUST_BITS) {
uint T = (D * C) + B;
T ^= A;
T = (A & 0x80) | (T >> 2);
- color_values[out_index++] = T;
+ color_values[++out_index] = T;
}
}
}
@@ -664,139 +582,136 @@ ivec2 BitTransferSigned(int a, int b) {
}
uvec4 ClampByte(ivec4 color) {
- for (uint i = 0; i < 4; ++i) {
- color[i] = (color[i] < 0) ? 0 : ((color[i] > 255) ? 255 : color[i]);
- }
- return uvec4(color);
+ return uvec4(clamp(color, 0, 255));
}
ivec4 BlueContract(int a, int r, int g, int b) {
return ivec4(a, (r + b) >> 1, (g + b) >> 1, b);
}
-void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) {
+void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, uint color_values[32],
+ inout uint colvals_index) {
#define READ_UINT_VALUES(N) \
- uint v[N]; \
+ uvec4 V[2]; \
for (uint i = 0; i < N; i++) { \
- v[i] = color_values[colvals_index++]; \
+ V[i / 4][i % 4] = color_values[++colvals_index]; \
}
-
#define READ_INT_VALUES(N) \
- int v[N]; \
+ ivec4 V[2]; \
for (uint i = 0; i < N; i++) { \
- v[i] = int(color_values[colvals_index++]); \
+ V[i / 4][i % 4] = int(color_values[++colvals_index]); \
}
switch (color_endpoint_mode) {
case 0: {
READ_UINT_VALUES(2)
- ep1 = uvec4(0xFF, v[0], v[0], v[0]);
- ep2 = uvec4(0xFF, v[1], v[1], v[1]);
+ ep1 = uvec4(0xFF, V[0].x, V[0].x, V[0].x);
+ ep2 = uvec4(0xFF, V[0].y, V[0].y, V[0].y);
break;
}
case 1: {
READ_UINT_VALUES(2)
- uint L0 = (v[0] >> 2) | (v[1] & 0xC0);
- uint L1 = min(L0 + (v[1] & 0x3F), 0xFFU);
+ const uint L0 = (V[0].x >> 2) | (V[0].y & 0xC0);
+ const uint L1 = min(L0 + (V[0].y & 0x3F), 0xFFU);
ep1 = uvec4(0xFF, L0, L0, L0);
ep2 = uvec4(0xFF, L1, L1, L1);
break;
}
case 4: {
READ_UINT_VALUES(4)
- ep1 = uvec4(v[2], v[0], v[0], v[0]);
- ep2 = uvec4(v[3], v[1], v[1], v[1]);
+ ep1 = uvec4(V[0].z, V[0].x, V[0].x, V[0].x);
+ ep2 = uvec4(V[0].w, V[0].y, V[0].y, V[0].y);
break;
}
case 5: {
READ_INT_VALUES(4)
- ivec2 transferred = BitTransferSigned(v[1], v[0]);
- v[1] = transferred.x;
- v[0] = transferred.y;
- transferred = BitTransferSigned(v[3], v[2]);
- v[3] = transferred.x;
- v[2] = transferred.y;
- ep1 = ClampByte(ivec4(v[2], v[0], v[0], v[0]));
- ep2 = ClampByte(ivec4(v[2] + v[3], v[0] + v[1], v[0] + v[1], v[0] + v[1]));
+ ivec2 transferred = BitTransferSigned(V[0].y, V[0].x);
+ V[0].y = transferred.x;
+ V[0].x = transferred.y;
+ transferred = BitTransferSigned(V[0].w, V[0].z);
+ V[0].w = transferred.x;
+ V[0].z = transferred.y;
+ ep1 = ClampByte(ivec4(V[0].z, V[0].x, V[0].x, V[0].x));
+ 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));
break;
}
case 6: {
READ_UINT_VALUES(4)
- ep1 = uvec4(0xFF, (v[0] * v[3]) >> 8, (v[1] * v[3]) >> 8, (v[2] * v[3]) >> 8);
- ep2 = uvec4(0xFF, v[0], v[1], v[2]);
+ ep1 = uvec4(0xFF, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8);
+ ep2 = uvec4(0xFF, V[0].x, V[0].y, V[0].z);
break;
}
case 8: {
READ_UINT_VALUES(6)
- if ((v[1] + v[3] + v[5]) >= (v[0] + v[2] + v[4])) {
- ep1 = uvec4(0xFF, v[0], v[2], v[4]);
- ep2 = uvec4(0xFF, v[1], v[3], v[5]);
+ if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) {
+ ep1 = uvec4(0xFF, V[0].x, V[0].z, V[1].x);
+ ep2 = uvec4(0xFF, V[0].y, V[0].w, V[1].y);
} else {
- ep1 = uvec4(BlueContract(0xFF, int(v[1]), int(v[3]), int(v[5])));
- ep2 = uvec4(BlueContract(0xFF, int(v[0]), int(v[2]), int(v[4])));
+ ep1 = uvec4(BlueContract(0xFF, int(V[0].y), int(V[0].w), int(V[1].y)));
+ ep2 = uvec4(BlueContract(0xFF, int(V[0].x), int(V[0].z), int(V[1].x)));
}
break;
}
case 9: {
READ_INT_VALUES(6)
- ivec2 transferred = BitTransferSigned(v[1], v[0]);
- v[1] = transferred.x;
- v[0] = transferred.y;
- transferred = BitTransferSigned(v[3], v[2]);
- v[3] = transferred.x;
- v[2] = transferred.y;
- transferred = BitTransferSigned(v[5], v[4]);
- v[5] = transferred.x;
- v[4] = transferred.y;
- if ((v[1] + v[3] + v[5]) >= 0) {
- ep1 = ClampByte(ivec4(0xFF, v[0], v[2], v[4]));
- ep2 = ClampByte(ivec4(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5]));
+ ivec2 transferred = BitTransferSigned(V[0].y, V[0].x);
+ V[0].y = transferred.x;
+ V[0].x = transferred.y;
+ transferred = BitTransferSigned(V[0].w, V[0].z);
+ V[0].w = transferred.x;
+ V[0].z = transferred.y;
+ transferred = BitTransferSigned(V[1].y, V[1].x);
+ V[1].y = transferred.x;
+ V[1].x = transferred.y;
+ if ((V[0].y + V[0].w + V[1].y) >= 0) {
+ ep1 = ClampByte(ivec4(0xFF, V[0].x, V[0].z, V[1].x));
+ ep2 = ClampByte(ivec4(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y));
} else {
- ep1 = ClampByte(BlueContract(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5]));
- ep2 = ClampByte(BlueContract(0xFF, v[0], v[2], v[4]));
+ ep1 = ClampByte(BlueContract(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y));
+ ep2 = ClampByte(BlueContract(0xFF, V[0].x, V[0].z, V[1].x));
}
break;
}
case 10: {
READ_UINT_VALUES(6)
- ep1 = uvec4(v[4], (v[0] * v[3]) >> 8, (v[1] * v[3]) >> 8, (v[2] * v[3]) >> 8);
- ep2 = uvec4(v[5], v[0], v[1], v[2]);
+ 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);
+ ep2 = uvec4(V[1].y, V[0].x, V[0].y, V[0].z);
break;
}
case 12: {
READ_UINT_VALUES(8)
- if ((v[1] + v[3] + v[5]) >= (v[0] + v[2] + v[4])) {
- ep1 = uvec4(v[6], v[0], v[2], v[4]);
- ep2 = uvec4(v[7], v[1], v[3], v[5]);
+ if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) {
+ ep1 = uvec4(V[1].z, V[0].x, V[0].z, V[1].x);
+ ep2 = uvec4(V[1].w, V[0].y, V[0].w, V[1].y);
} else {
- ep1 = uvec4(BlueContract(int(v[7]), int(v[1]), int(v[3]), int(v[5])));
- ep2 = uvec4(BlueContract(int(v[6]), int(v[0]), int(v[2]), int(v[4])));
+ ep1 = uvec4(BlueContract(int(V[1].w), int(V[0].y), int(V[0].w), int(V[1].y)));
+ ep2 = uvec4(BlueContract(int(V[1].z), int(V[0].x), int(V[0].z), int(V[1].x)));
}
break;
}
case 13: {
READ_INT_VALUES(8)
- ivec2 transferred = BitTransferSigned(v[1], v[0]);
- v[1] = transferred.x;
- v[0] = transferred.y;
- transferred = BitTransferSigned(v[3], v[2]);
- v[3] = transferred.x;
- v[2] = transferred.y;
-
- transferred = BitTransferSigned(v[5], v[4]);
- v[5] = transferred.x;
- v[4] = transferred.y;
-
- transferred = BitTransferSigned(v[7], v[6]);
- v[7] = transferred.x;
- v[6] = transferred.y;
-
- if ((v[1] + v[3] + v[5]) >= 0) {
- ep1 = ClampByte(ivec4(v[6], v[0], v[2], v[4]));
- ep2 = ClampByte(ivec4(v[7] + v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5]));
+ ivec2 transferred = BitTransferSigned(V[0].y, V[0].x);
+ V[0].y = transferred.x;
+ V[0].x = transferred.y;
+ transferred = BitTransferSigned(V[0].w, V[0].z);
+ V[0].w = transferred.x;
+ V[0].z = transferred.y;
+
+ transferred = BitTransferSigned(V[1].y, V[1].x);
+ V[1].y = transferred.x;
+ V[1].x = transferred.y;
+
+ transferred = BitTransferSigned(V[1].w, V[1].z);
+ V[1].w = transferred.x;
+ V[1].z = transferred.y;
+
+ if ((V[0].y + V[0].w + V[1].y) >= 0) {
+ ep1 = ClampByte(ivec4(V[1].z, V[0].x, V[0].z, V[1].x));
+ 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));
} else {
- ep1 = ClampByte(BlueContract(v[6] + v[7], v[0] + v[1], v[2] + v[3], v[4] + v[5]));
- ep2 = ClampByte(BlueContract(v[6], v[0], v[2], v[4]));
+ 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));
+ ep2 = ClampByte(BlueContract(V[1].z, V[0].x, V[0].z, V[1].x));
}
break;
}
@@ -812,36 +727,34 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) {
}
uint UnquantizeTexelWeight(EncodingData val) {
- uint bitval = val.bit_value;
- uint bitlen = val.num_bits;
- uint A = ReplicateBitTo7((bitval & 1));
+ const uint encoding = Encoding(val);
+ const uint bitlen = NumBits(val);
+ const uint bitval = BitValue(val);
+ const uint A = ReplicateBitTo7((bitval & 1));
uint B = 0, C = 0, D = 0;
uint result = 0;
- switch (val.encoding) {
+ const uint bitlen_0_results[5] = {0, 16, 32, 48, 64};
+ switch (encoding) {
case JUST_BITS:
- result = FastReplicateTo6(bitval, bitlen);
- break;
+ return FastReplicateTo6(bitval, bitlen);
case TRIT: {
- D = val.quint_trit_value;
+ D = QuintTritValue(val);
switch (bitlen) {
- case 0: {
- uint results[3] = {0, 32, 63};
- result = results[D];
- break;
- }
+ case 0:
+ return bitlen_0_results[D * 2];
case 1: {
C = 50;
break;
}
case 2: {
C = 23;
- uint b = (bitval >> 1) & 1;
+ const uint b = (bitval >> 1) & 1;
B = (b << 6) | (b << 2) | b;
break;
}
case 3: {
C = 11;
- uint cb = (bitval >> 1) & 3;
+ const uint cb = (bitval >> 1) & 3;
B = (cb << 5) | cb;
break;
}
@@ -851,20 +764,17 @@ uint UnquantizeTexelWeight(EncodingData val) {
break;
}
case QUINT: {
- D = val.quint_trit_value;
+ D = QuintTritValue(val);
switch (bitlen) {
- case 0: {
- uint results[5] = {0, 16, 32, 47, 63};
- result = results[D];
- break;
- }
+ case 0:
+ return bitlen_0_results[D];
case 1: {
C = 28;
break;
}
case 2: {
C = 13;
- uint b = (bitval >> 1) & 1;
+ const uint b = (bitval >> 1) & 1;
B = (b << 6) | (b << 1);
break;
}
@@ -872,7 +782,7 @@ uint UnquantizeTexelWeight(EncodingData val) {
break;
}
}
- if (val.encoding != JUST_BITS && bitlen > 0) {
+ if (encoding != JUST_BITS && bitlen > 0) {
result = D * C + B;
result ^= A;
result = (A & 0x20) | (result >> 2);
@@ -883,61 +793,77 @@ uint UnquantizeTexelWeight(EncodingData val) {
return result;
}
-void UnquantizeTexelWeights(bool dual_plane, uvec2 size) {
- uint weight_idx = 0;
- uint unquantized[2][144];
- uint area = size.x * size.y;
- for (uint itr = 0; itr < texel_vector_index; itr++) {
- unquantized[0][weight_idx] = UnquantizeTexelWeight(texel_vector[itr]);
- if (dual_plane) {
- ++itr;
- unquantized[1][weight_idx] = UnquantizeTexelWeight(texel_vector[itr]);
- if (itr == texel_vector_index) {
- break;
- }
- }
- if (++weight_idx >= (area))
- break;
+void UnquantizeTexelWeights(uvec2 size, bool is_dual_plane) {
+ const uint num_planes = is_dual_plane ? 2 : 1;
+ const uint area = size.x * size.y;
+ const uint loop_count = min(result_index, area * num_planes);
+ for (uint itr = 0; itr < loop_count; ++itr) {
+ result_vector[itr] =
+ UnquantizeTexelWeight(GetEncodingFromVector(itr));
}
+}
+
+uint GetUnquantizedTexelWieght(uint offset_base, uint plane, bool is_dual_plane) {
+ const uint offset = is_dual_plane ? 2 * offset_base + plane : offset_base;
+ return result_vector[offset];
+}
+uvec4 GetUnquantizedWeightVector(uint t, uint s, uvec2 size, uint plane_index, bool is_dual_plane) {
const uint Ds = uint((block_dims.x * 0.5f + 1024) / (block_dims.x - 1));
const uint Dt = uint((block_dims.y * 0.5f + 1024) / (block_dims.y - 1));
- const uint k_plane_scale = dual_plane ? 2 : 1;
- for (uint plane = 0; plane < k_plane_scale; plane++) {
- for (uint t = 0; t < block_dims.y; t++) {
- for (uint s = 0; s < block_dims.x; s++) {
- uint cs = Ds * s;
- uint ct = Dt * t;
- uint gs = (cs * (size.x - 1) + 32) >> 6;
- uint gt = (ct * (size.y - 1) + 32) >> 6;
- uint js = gs >> 4;
- uint fs = gs & 0xF;
- uint jt = gt >> 4;
- uint ft = gt & 0x0F;
- uint w11 = (fs * ft + 8) >> 4;
- uint w10 = ft - w11;
- uint w01 = fs - w11;
- uint w00 = 16 - fs - ft + w11;
- uvec4 w = uvec4(w00, w01, w10, w11);
- uint v0 = jt * size.x + js;
-
- uvec4 p = uvec4(0);
- if (v0 < area) {
- p.x = unquantized[plane][v0];
- }
- if ((v0 + 1) < (area)) {
- p.y = unquantized[plane][v0 + 1];
- }
- if ((v0 + size.x) < (area)) {
- p.z = unquantized[plane][(v0 + size.x)];
- }
- if ((v0 + size.x + 1) < (area)) {
- p.w = unquantized[plane][(v0 + size.x + 1)];
- }
- unquantized_texel_weights[plane][t * block_dims.x + s] = (uint(dot(p, w)) + 8) >> 4;
- }
+ const uint area = size.x * size.y;
+
+ const uint cs = Ds * s;
+ const uint ct = Dt * t;
+ const uint gs = (cs * (size.x - 1) + 32) >> 6;
+ const uint gt = (ct * (size.y - 1) + 32) >> 6;
+ const uint js = gs >> 4;
+ const uint fs = gs & 0xF;
+ const uint jt = gt >> 4;
+ const uint ft = gt & 0x0F;
+ const uint w11 = (fs * ft + 8) >> 4;
+ const uint w10 = ft - w11;
+ const uint w01 = fs - w11;
+ const uint w00 = 16 - fs - ft + w11;
+ const uvec4 w = uvec4(w00, w01, w10, w11);
+ const uint v0 = jt * size.x + js;
+
+ uvec4 p0 = uvec4(0);
+ uvec4 p1 = uvec4(0);
+
+ if (v0 < area) {
+ const uint offset_base = v0;
+ p0.x = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane);
+ p1.x = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane);
+ }
+ if ((v0 + 1) < (area)) {
+ const uint offset_base = v0 + 1;
+ p0.y = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane);
+ p1.y = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane);
+ }
+ if ((v0 + size.x) < (area)) {
+ const uint offset_base = v0 + size.x;
+ p0.z = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane);
+ p1.z = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane);
+ }
+ if ((v0 + size.x + 1) < (area)) {
+ const uint offset_base = v0 + size.x + 1;
+ p0.w = GetUnquantizedTexelWieght(offset_base, 0, is_dual_plane);
+ p1.w = GetUnquantizedTexelWieght(offset_base, 1, is_dual_plane);
+ }
+
+ const uint primary_weight = (uint(dot(p0, w)) + 8) >> 4;
+
+ uvec4 weight_vec = uvec4(primary_weight);
+
+ if (is_dual_plane) {
+ const uint secondary_weight = (uint(dot(p1, w)) + 8) >> 4;
+ for (uint c = 0; c < 4; c++) {
+ const bool is_secondary = ((plane_index + 1u) & 3u) == c;
+ weight_vec[c] = is_secondary ? secondary_weight : primary_weight;
}
}
+ return weight_vec;
}
int FindLayout(uint mode) {
@@ -971,80 +897,96 @@ int FindLayout(uint mode) {
return 5;
}
-TexelWeightParams DecodeBlockInfo() {
- TexelWeightParams params = TexelWeightParams(uvec2(0), 0, false, false, false, false);
- uint mode = StreamBits(11);
+
+void FillError(ivec3 coord) {
+ for (uint j = 0; j < block_dims.y; j++) {
+ for (uint i = 0; i < block_dims.x; i++) {
+ imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
+ }
+ }
+}
+
+void FillVoidExtentLDR(ivec3 coord) {
+ SkipBits(52);
+ const uint r_u = StreamBits(16);
+ const uint g_u = StreamBits(16);
+ const uint b_u = StreamBits(16);
+ const uint a_u = StreamBits(16);
+ const float a = float(a_u) / 65535.0f;
+ const float r = float(r_u) / 65535.0f;
+ const float g = float(g_u) / 65535.0f;
+ const float b = float(b_u) / 65535.0f;
+ for (uint j = 0; j < block_dims.y; j++) {
+ for (uint i = 0; i < block_dims.x; i++) {
+ imageStore(dest_image, coord + ivec3(i, j, 0), vec4(r, g, b, a));
+ }
+ }
+}
+
+bool IsError(uint mode) {
if ((mode & 0x1ff) == 0x1fc) {
if ((mode & 0x200) != 0) {
- params.void_extent_hdr = true;
- } else {
- params.void_extent_ldr = true;
+ // params.void_extent_hdr = true;
+ return true;
}
if ((mode & 0x400) == 0 || StreamBits(1) == 0) {
- params.error_state = true;
+ return true;
}
- return params;
+ return false;
}
if ((mode & 0xf) == 0) {
- params.error_state = true;
- return params;
+ return true;
}
if ((mode & 3) == 0 && (mode & 0x1c0) == 0x1c0) {
- params.error_state = true;
- return params;
+ return true;
}
+ return false;
+}
+
+uvec2 DecodeBlockSize(uint mode) {
uint A, B;
- uint mode_layout = FindLayout(mode);
- switch (mode_layout) {
+ switch (FindLayout(mode)) {
case 0:
A = (mode >> 5) & 0x3;
B = (mode >> 7) & 0x3;
- params.size = uvec2(B + 4, A + 2);
- break;
+ return uvec2(B + 4, A + 2);
case 1:
A = (mode >> 5) & 0x3;
B = (mode >> 7) & 0x3;
- params.size = uvec2(B + 8, A + 2);
- break;
+ return uvec2(B + 8, A + 2);
case 2:
A = (mode >> 5) & 0x3;
B = (mode >> 7) & 0x3;
- params.size = uvec2(A + 2, B + 8);
- break;
+ return uvec2(A + 2, B + 8);
case 3:
A = (mode >> 5) & 0x3;
B = (mode >> 7) & 0x1;
- params.size = uvec2(A + 2, B + 6);
- break;
+ return uvec2(A + 2, B + 6);
case 4:
A = (mode >> 5) & 0x3;
B = (mode >> 7) & 0x1;
- params.size = uvec2(B + 2, A + 2);
- break;
+ return uvec2(B + 2, A + 2);
case 5:
A = (mode >> 5) & 0x3;
- params.size = uvec2(12, A + 2);
- break;
+ return uvec2(12, A + 2);
case 6:
A = (mode >> 5) & 0x3;
- params.size = uvec2(A + 2, 12);
- break;
+ return uvec2(A + 2, 12);
case 7:
- params.size = uvec2(6, 10);
- break;
+ return uvec2(6, 10);
case 8:
- params.size = uvec2(10, 6);
- break;
+ return uvec2(10, 6);
case 9:
A = (mode >> 5) & 0x3;
B = (mode >> 9) & 0x3;
- params.size = uvec2(A + 6, B + 6);
- break;
+ return uvec2(A + 6, B + 6);
default:
- params.error_state = true;
- break;
+ return uvec2(0);
}
- params.dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0);
+}
+
+uint DecodeMaxWeight(uint mode) {
+ const uint mode_layout = FindLayout(mode);
uint weight_index = (mode & 0x10) != 0 ? 1 : 0;
if (mode_layout < 5) {
weight_index |= (mode & 0x3) << 1;
@@ -1053,64 +995,34 @@ TexelWeightParams DecodeBlockInfo() {
}
weight_index -= 2;
if ((mode_layout != 9) && ((mode & 0x200) != 0)) {
- const int max_weights[6] = int[6](7, 8, 9, 10, 11, 12);
- params.max_weight = max_weights[weight_index];
- } else {
- const int max_weights[6] = int[6](1, 2, 3, 4, 5, 6);
- params.max_weight = max_weights[weight_index];
- }
- return params;
-}
-
-void FillError(ivec3 coord) {
- for (uint j = 0; j < block_dims.y; j++) {
- for (uint i = 0; i < block_dims.x; i++) {
- imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
- }
- }
-}
-
-void FillVoidExtentLDR(ivec3 coord) {
- StreamBits(52);
- uint r_u = StreamBits(16);
- uint g_u = StreamBits(16);
- uint b_u = StreamBits(16);
- uint a_u = StreamBits(16);
- float a = float(a_u) / 65535.0f;
- float r = float(r_u) / 65535.0f;
- float g = float(g_u) / 65535.0f;
- float b = float(b_u) / 65535.0f;
- for (uint j = 0; j < block_dims.y; j++) {
- for (uint i = 0; i < block_dims.x; i++) {
- imageStore(dest_image, coord + ivec3(i, j, 0), vec4(r, g, b, a));
- }
+ weight_index += 6;
}
+ return weight_index + 1;
}
void DecompressBlock(ivec3 coord) {
- TexelWeightParams params = DecodeBlockInfo();
- if (params.error_state) {
- FillError(coord);
- return;
- }
- if (params.void_extent_hdr) {
+ uint mode = StreamBits(11);
+ if (IsError(mode)) {
FillError(coord);
return;
}
- if (params.void_extent_ldr) {
+ if ((mode & 0x1ff) == 0x1fc) {
+ // params.void_extent_ldr = true;
FillVoidExtentLDR(coord);
return;
}
- if ((params.size.x > block_dims.x) || (params.size.y > block_dims.y)) {
+ const uvec2 size_params = DecodeBlockSize(mode);
+ if ((size_params.x > block_dims.x) || (size_params.y > block_dims.y)) {
FillError(coord);
return;
}
- uint num_partitions = StreamBits(2) + 1;
- if (num_partitions > 4 || (num_partitions == 4 && params.dual_plane)) {
+ const uint num_partitions = StreamBits(2) + 1;
+ const uint mode_layout = FindLayout(mode);
+ const bool dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0);
+ if (num_partitions > 4 || (num_partitions == 4 && dual_plane)) {
FillError(coord);
return;
}
- int plane_index = -1;
uint partition_index = 1;
uvec4 color_endpoint_mode = uvec4(0);
uint ced_pointer = 0;
@@ -1122,8 +1034,9 @@ void DecompressBlock(ivec3 coord) {
partition_index = StreamBits(10);
base_cem = StreamBits(6);
}
- uint base_mode = base_cem & 3;
- uint weight_bits = GetPackedBitSize(params.size, params.dual_plane, params.max_weight);
+ const uint base_mode = base_cem & 3;
+ const uint max_weight = DecodeMaxWeight(mode);
+ const uint weight_bits = GetPackedBitSize(size_params, dual_plane, max_weight);
uint remaining_bits = 128 - weight_bits - total_bitsread;
uint extra_cem_bits = 0;
if (base_mode > 0) {
@@ -1142,10 +1055,7 @@ void DecompressBlock(ivec3 coord) {
}
}
remaining_bits -= extra_cem_bits;
- uint plane_selector_bits = 0;
- if (params.dual_plane) {
- plane_selector_bits = 2;
- }
+ const uint plane_selector_bits = dual_plane ? 2 : 0;
remaining_bits -= plane_selector_bits;
if (remaining_bits > 128) {
// Bad data, more remaining bits than 4 bytes
@@ -1153,17 +1063,17 @@ void DecompressBlock(ivec3 coord) {
return;
}
// Read color data...
- uint color_data_bits = remaining_bits;
+ const uint color_data_bits = remaining_bits;
while (remaining_bits > 0) {
- int nb = int(min(remaining_bits, 32U));
- uint b = StreamBits(nb);
+ const int nb = int(min(remaining_bits, 32U));
+ const uint b = StreamBits(nb);
color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb));
++ced_pointer;
remaining_bits -= nb;
}
- plane_index = int(StreamBits(plane_selector_bits));
+ const uint plane_index = uint(StreamBits(plane_selector_bits));
if (base_mode > 0) {
- uint extra_cem = StreamBits(extra_cem_bits);
+ const uint extra_cem = StreamBits(extra_cem_bits);
uint cem = (extra_cem << 6) | base_cem;
cem >>= 2;
uvec4 C = uvec4(0);
@@ -1185,70 +1095,80 @@ void DecompressBlock(ivec3 coord) {
color_endpoint_mode[i] |= M[i];
}
} else if (num_partitions > 1) {
- uint cem = base_cem >> 2;
+ const uint cem = base_cem >> 2;
for (uint i = 0; i < num_partitions; i++) {
color_endpoint_mode[i] = cem;
}
}
- DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits);
- uvec4 endpoints[4][2];
- for (uint i = 0; i < num_partitions; i++) {
- ComputeEndpoints(endpoints[i][0], endpoints[i][1], color_endpoint_mode[i]);
+ uvec4 endpoints0[4];
+ uvec4 endpoints1[4];
+ {
+ // This decode phase should at most push 32 elements into the vector
+ result_vector_max_index = 32;
+ uint color_values[32];
+ uint colvals_index = 0;
+ DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits, color_values);
+ for (uint i = 0; i < num_partitions; i++) {
+ ComputeEndpoints(endpoints0[i], endpoints1[i], color_endpoint_mode[i], color_values,
+ colvals_index);
+ }
}
+ color_endpoint_data = local_buff;
+ color_endpoint_data = bitfieldReverse(color_endpoint_data).wzyx;
+ const uint clear_byte_start = (weight_bits >> 3) + 1;
- texel_weight_data = local_buff;
- texel_weight_data = bitfieldReverse(texel_weight_data).wzyx;
- uint clear_byte_start =
- (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) >> 3) + 1;
-
- uint byte_insert = ExtractBits(texel_weight_data, int(clear_byte_start - 1) * 8, 8) &
- uint(
- ((1 << (GetPackedBitSize(params.size, params.dual_plane, params.max_weight) % 8)) - 1));
- uint vec_index = (clear_byte_start - 1) >> 2;
- texel_weight_data[vec_index] =
- bitfieldInsert(texel_weight_data[vec_index], byte_insert, int((clear_byte_start - 1) % 4) * 8, 8);
+ const uint byte_insert = ExtractBits(color_endpoint_data, int(clear_byte_start - 1) * 8, 8) &
+ uint(((1 << (weight_bits % 8)) - 1));
+ const uint vec_index = (clear_byte_start - 1) >> 2;
+ color_endpoint_data[vec_index] = bitfieldInsert(color_endpoint_data[vec_index], byte_insert,
+ int((clear_byte_start - 1) % 4) * 8, 8);
for (uint i = clear_byte_start; i < 16; ++i) {
- uint idx = i >> 2;
- texel_weight_data[idx] = bitfieldInsert(texel_weight_data[idx], 0, int(i % 4) * 8, 8);
+ const uint idx = i >> 2;
+ color_endpoint_data[idx] = bitfieldInsert(color_endpoint_data[idx], 0, int(i % 4) * 8, 8);
}
- texel_flag = true; // use texel "vector" and bit stream in integer decoding
- DecodeIntegerSequence(params.max_weight, GetNumWeightValues(params.size, params.dual_plane));
- UnquantizeTexelWeights(params.dual_plane, params.size);
+ // Re-init vector variables for next decode phase
+ result_index = 0;
+ color_bitsread = 0;
+ result_limit_reached = false;
+ // The limit for the Unquantize phase, avoids decoding more data than needed.
+ result_vector_max_index = size_params.x * size_params.y;
+ if (dual_plane) {
+ result_vector_max_index *= 2;
+ }
+ DecodeIntegerSequence(max_weight, GetNumWeightValues(size_params, dual_plane));
+
+ UnquantizeTexelWeights(size_params, dual_plane);
for (uint j = 0; j < block_dims.y; j++) {
for (uint i = 0; i < block_dims.x; i++) {
uint local_partition = 0;
if (num_partitions > 1) {
- local_partition = Select2DPartition(partition_index, i, j, num_partitions,
- (block_dims.y * block_dims.x) < 32);
- }
- vec4 p;
- uvec4 C0 = ReplicateByteTo16(endpoints[local_partition][0]);
- uvec4 C1 = ReplicateByteTo16(endpoints[local_partition][1]);
- uvec4 plane_vec = uvec4(0);
- uvec4 weight_vec = uvec4(0);
- for (uint c = 0; c < 4; c++) {
- if (params.dual_plane && (((plane_index + 1) & 3) == c)) {
- plane_vec[c] = 1;
- }
- weight_vec[c] = unquantized_texel_weights[plane_vec[c]][j * block_dims.x + i];
+ local_partition = Select2DPartition(partition_index, i, j, num_partitions);
}
- vec4 Cf = vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64);
- p = (Cf / 65535.0);
+ const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]);
+ const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]);
+ const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane);
+ const vec4 Cf =
+ vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64);
+ const vec4 p = (Cf / 65535.0f);
imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar);
}
}
}
+uint SwizzleOffset(uvec2 pos) {
+ const uint x = pos.x;
+ const uint y = pos.y;
+ return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 +
+ ((x % 32) / 16) * 32 + (y % 2) * 16 + (x % 16);
+}
+
void main() {
uvec3 pos = gl_GlobalInvocationID;
pos.x <<= BYTES_PER_BLOCK_LOG2;
-
- // Read as soon as possible due to its latency
const uint swizzle = SwizzleOffset(pos.xy);
-
const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0;
@@ -1262,8 +1182,6 @@ void main() {
if (any(greaterThanEqual(coord, imageSize(dest_image)))) {
return;
}
- current_index = 0;
- bitsread = 0;
local_buff = astc_data[offset / 16];
DecompressBlock(coord);
}
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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#version 460 core
+
+layout (push_constant) uniform PushConstants {
+ vec4 clear_depth;
+};
+
+void main() {
+ gl_FragDepth = clear_depth.x;
+}
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))
namespace Tegra {
-static void Dump(u64 hash, std::span<const u32> code) {
+static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) {
const auto base_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)};
const auto macro_dir{base_dir / "macros"};
if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) {
LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories");
return;
}
- const auto name{macro_dir / fmt::format("{:016x}.macro", hash)};
+ auto name{macro_dir / fmt::format("{:016x}.macro", hash)};
+
+ if (decompiled) {
+ auto new_name{macro_dir / fmt::format("decompiled_{:016x}.macro", hash)};
+ if (Common::FS::Exists(name)) {
+ (void)Common::FS::RenameFile(name, new_name);
+ return;
+ }
+ name = new_name;
+ }
+
std::fstream macro_file(name, std::ios::out | std::ios::binary);
if (!macro_file) {
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) {
if (!mid_method.has_value()) {
cache_info.lle_program = Compile(macro_code->second);
cache_info.hash = Common::HashValue(macro_code->second);
- if (Settings::values.dump_macros) {
- Dump(cache_info.hash, macro_code->second);
- }
} else {
const auto& macro_cached = uploaded_macro_code[mid_method.value()];
const auto rebased_method = method - mid_method.value();
@@ -102,9 +109,6 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
code.size() * sizeof(u32));
cache_info.hash = Common::HashValue(code);
cache_info.lle_program = Compile(code);
- if (Settings::values.dump_macros) {
- Dump(cache_info.hash, code);
- }
}
auto hle_program = hle_macros->GetHLEProgram(cache_info.hash);
@@ -117,6 +121,10 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
MICROPROFILE_SCOPE(MacroHLE);
cache_info.hle_program->Execute(parameters, method);
}
+
+ if (Settings::values.dump_macros) {
+ Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program);
+ }
}
}
diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
index f9ca55c36..d70501860 100644
--- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp
@@ -34,13 +34,13 @@ ComputePipeline::ComputePipeline(const Device& device, TextureCache& texture_cac
: texture_cache{texture_cache_}, buffer_cache{buffer_cache_},
program_manager{program_manager_}, info{info_} {
switch (device.GetShaderBackend()) {
- case Settings::ShaderBackend::GLSL:
+ case Settings::ShaderBackend::Glsl:
source_program = CreateProgram(code, GL_COMPUTE_SHADER);
break;
- case Settings::ShaderBackend::GLASM:
+ case Settings::ShaderBackend::Glasm:
assembly_program = CompileProgram(code, GL_COMPUTE_PROGRAM_NV);
break;
- case Settings::ShaderBackend::SPIRV:
+ case Settings::ShaderBackend::SpirV:
source_program = CreateProgram(code_v, GL_COMPUTE_SHADER);
break;
}
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 33e63c17d..94258ccd0 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -106,6 +106,43 @@ bool IsASTCSupported() {
return true;
}
+static bool HasSlowSoftwareAstc(std::string_view vendor_name, std::string_view renderer) {
+// ifdef for Unix reduces string comparisons for non-Windows drivers, and Intel
+#ifdef YUZU_UNIX
+ // Sorted vaguely by how likely a vendor is to appear
+ if (vendor_name == "AMD") {
+ // RadeonSI
+ return true;
+ }
+ if (vendor_name == "Intel") {
+ // Must be inside YUZU_UNIX ifdef as the Windows driver uses the same vendor string
+ // iris, crocus
+ const bool is_intel_dg = (renderer.find("DG") != std::string_view::npos);
+ return is_intel_dg;
+ }
+ if (vendor_name == "nouveau") {
+ return true;
+ }
+ if (vendor_name == "X.Org") {
+ // R600
+ return true;
+ }
+#endif
+ if (vendor_name == "Collabora Ltd") {
+ // Zink
+ return true;
+ }
+ if (vendor_name == "Microsoft Corporation") {
+ // d3d12
+ return true;
+ }
+ if (vendor_name == "Mesa/X.org") {
+ // llvmpipe, softpipe, virgl
+ return true;
+ }
+ return false;
+}
+
[[nodiscard]] bool IsDebugToolAttached(std::span<const std::string_view> extensions) {
const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
return nsight || HasExtension(extensions, "GL_EXT_debug_tool") ||
@@ -120,12 +157,16 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) {
}
vendor_name = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
+ const std::string_view renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
const std::vector extensions = GetExtensions();
const bool is_nvidia = vendor_name == "NVIDIA Corporation";
const bool is_amd = vendor_name == "ATI Technologies Inc.";
const bool is_intel = vendor_name == "Intel";
+ const bool has_slow_software_astc =
+ !is_nvidia && !is_amd && HasSlowSoftwareAstc(vendor_name, renderer);
+
#ifdef __unix__
constexpr bool is_linux = true;
#else
@@ -152,7 +193,7 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) {
has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted");
has_texture_shadow_lod = HasExtension(extensions, "GL_EXT_texture_shadow_lod");
- has_astc = IsASTCSupported();
+ has_astc = !has_slow_software_astc && IsASTCSupported();
has_variable_aoffi = TestVariableAoffi();
has_component_indexing_bug = is_amd;
has_precise_bug = TestPreciseBug();
@@ -177,15 +218,15 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) {
has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data;
shader_backend = Settings::values.shader_backend.GetValue();
- use_assembly_shaders = shader_backend == Settings::ShaderBackend::GLASM &&
+ use_assembly_shaders = shader_backend == Settings::ShaderBackend::Glasm &&
GLAD_GL_NV_gpu_program5 && GLAD_GL_NV_compute_program5 &&
GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2;
- if (shader_backend == Settings::ShaderBackend::GLASM && !use_assembly_shaders) {
+ if (shader_backend == Settings::ShaderBackend::Glasm && !use_assembly_shaders) {
LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported");
- shader_backend = Settings::ShaderBackend::GLSL;
+ shader_backend = Settings::ShaderBackend::Glsl;
}
- if (shader_backend == Settings::ShaderBackend::GLSL && is_nvidia) {
+ if (shader_backend == Settings::ShaderBackend::Glsl && is_nvidia) {
const std::string_view driver_version = version.substr(13);
const int version_major =
std::atoi(driver_version.substr(0, driver_version.find(".")).data());
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index 71f720c63..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
ASSERT(num_textures <= MAX_TEXTURES);
ASSERT(num_images <= MAX_IMAGES);
- const bool assembly_shaders{assembly_programs[0].handle != 0};
+ const auto backend = device.GetShaderBackend();
+ const bool assembly_shaders{backend == Settings::ShaderBackend::Glasm};
use_storage_buffers =
!assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();
writes_global_memory &= !use_storage_buffers;
@@ -230,24 +231,23 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c
GenerateTransformFeedbackState();
}
const bool in_parallel = thread_worker != nullptr;
- const auto backend = device.GetShaderBackend();
auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv),
shader_notify, backend, in_parallel,
force_context_flush](ShaderContext::Context*) mutable {
for (size_t stage = 0; stage < 5; ++stage) {
switch (backend) {
- case Settings::ShaderBackend::GLSL:
+ case Settings::ShaderBackend::Glsl:
if (!sources_[stage].empty()) {
source_programs[stage] = CreateProgram(sources_[stage], Stage(stage));
}
break;
- case Settings::ShaderBackend::GLASM:
+ case Settings::ShaderBackend::Glasm:
if (!sources_[stage].empty()) {
assembly_programs[stage] =
CompileProgram(sources_[stage], AssemblyStage(stage));
}
break;
- case Settings::ShaderBackend::SPIRV:
+ case Settings::ShaderBackend::SpirV:
if (!sources_spirv_[stage].empty()) {
source_programs[stage] = CreateProgram(sources_spirv_[stage], Stage(stage));
}
@@ -559,15 +559,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
}
void GraphicsPipeline::ConfigureTransformFeedbackImpl() const {
- glTransformFeedbackStreamAttribsNV(num_xfb_attribs, xfb_attribs.data(), num_xfb_strides,
- xfb_streams.data(), GL_INTERLEAVED_ATTRIBS);
+ glTransformFeedbackAttribsNV(num_xfb_attribs, xfb_attribs.data(), GL_SEPARATE_ATTRIBS);
}
void GraphicsPipeline::GenerateTransformFeedbackState() {
// TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal
// when this is required.
GLint* cursor{xfb_attribs.data()};
- GLint* current_stream{xfb_streams.data()};
for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) {
const auto& layout = key.xfb_state.layouts[feedback];
@@ -575,15 +573,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() {
if (layout.varying_count == 0) {
continue;
}
- *current_stream = static_cast<GLint>(feedback);
- if (current_stream != xfb_streams.data()) {
- // When stepping one stream, push the expected token
- cursor[0] = GL_NEXT_BUFFER_NV;
- cursor[1] = 0;
- cursor[2] = 0;
- cursor += XFB_ENTRY_STRIDE;
- }
- ++current_stream;
const auto& locations = key.xfb_state.varyings[feedback];
std::optional<u32> current_index;
@@ -619,7 +608,6 @@ void GraphicsPipeline::GenerateTransformFeedbackState() {
}
}
num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE);
- num_xfb_strides = static_cast<GLsizei>(current_stream - xfb_streams.data());
}
void 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:
static constexpr std::size_t XFB_ENTRY_STRIDE = 3;
GLsizei num_xfb_attribs{};
- GLsizei num_xfb_strides{};
std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{};
- std::array<GLint, Maxwell::NumTransformFeedbackBuffers> xfb_streams{};
std::mutex built_mutex;
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() {
pipeline->SetEngine(kepler_compute, gpu_memory);
pipeline->Configure();
const auto& qmd{kepler_compute->launch_description};
+ auto indirect_address = kepler_compute->GetIndirectComputeAddress();
+ if (indirect_address) {
+ // DispatchIndirect
+ static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
+ const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite;
+ const auto [buffer, offset] =
+ buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op);
+ glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, buffer->Handle());
+ glDispatchComputeIndirect(static_cast<GLintptr>(offset));
+ return;
+ }
glDispatchCompute(qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z);
++num_queued_commands;
has_written_global_memory |= pipeline->WritesGlobalMemory();
@@ -1335,7 +1346,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
}
const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height);
static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
- const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
+ const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing
+ : VideoCommon::ObtainBufferOperation::MarkAsWritten;
const auto [buffer, offset] =
buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op);
@@ -1344,8 +1356,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
const std::span copy_span{&copy, 1};
if constexpr (IS_IMAGE_UPLOAD) {
+ texture_cache.PrepareImage(image_id, true, false);
image->UploadMemory(buffer->Handle(), offset, copy_span);
} else {
+ if (offset % BytesPerBlock(image->info.format)) {
+ return false;
+ }
texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span,
buffer_operand.address, buffer_size);
}
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 7e1d7f92e..2888e0238 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -445,7 +445,8 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key,
std::span<Shader::Environment* const> envs, bool use_shader_workers,
bool force_context_flush) try {
- LOG_INFO(Render_OpenGL, "0x{:016x}", key.Hash());
+ auto hash = key.Hash();
+ LOG_INFO(Render_OpenGL, "0x{:016x}", hash);
size_t env_index{};
u32 total_storage_buffers{};
std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs;
@@ -474,7 +475,7 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
if (Settings::values.dump_shaders) {
- env.Dump(key.unique_hashes[index]);
+ env.Dump(hash, key.unique_hashes[index]);
}
if (!uses_vertex_a || index != 1) {
@@ -522,14 +523,14 @@ std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
const auto runtime_info{
MakeRuntimeInfo(key, program, previous_program, glasm_use_storage_buffers, use_glasm)};
switch (device.GetShaderBackend()) {
- case Settings::ShaderBackend::GLSL:
+ case Settings::ShaderBackend::Glsl:
ConvertLegacyToGeneric(program, runtime_info);
sources[stage_index] = EmitGLSL(profile, runtime_info, program, binding);
break;
- case Settings::ShaderBackend::GLASM:
+ case Settings::ShaderBackend::Glasm:
sources[stage_index] = EmitGLASM(profile, runtime_info, program, binding);
break;
- case Settings::ShaderBackend::SPIRV:
+ case Settings::ShaderBackend::SpirV:
ConvertLegacyToGeneric(program, runtime_info);
sources_spirv[stage_index] = EmitSPIRV(profile, runtime_info, program, binding);
break;
@@ -566,12 +567,13 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
ShaderContext::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env,
bool force_context_flush) try {
- LOG_INFO(Render_OpenGL, "0x{:016x}", key.Hash());
+ auto hash = key.Hash();
+ LOG_INFO(Render_OpenGL, "0x{:016x}", hash);
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
if (Settings::values.dump_shaders) {
- env.Dump(key.Hash());
+ env.Dump(hash, key.unique_hash);
}
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
@@ -582,13 +584,13 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
std::string code{};
std::vector<u32> code_spirv;
switch (device.GetShaderBackend()) {
- case Settings::ShaderBackend::GLSL:
+ case Settings::ShaderBackend::Glsl:
code = EmitGLSL(profile, program);
break;
- case Settings::ShaderBackend::GLASM:
+ case Settings::ShaderBackend::Glasm:
code = EmitGLASM(profile, info, program);
break;
- case Settings::ShaderBackend::SPIRV:
+ case Settings::ShaderBackend::SpirV:
code_spirv = EmitSPIRV(profile, program);
break;
}
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 3b446be07..9cafd2983 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -232,10 +232,9 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
[[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime,
const VideoCommon::ImageInfo& info) {
if (IsPixelFormatASTC(info.format) && info.size.depth == 1 && !runtime.HasNativeASTC()) {
- return Settings::values.accelerate_astc.GetValue() &&
+ return Settings::values.accelerate_astc.GetValue() == Settings::AstcDecodeMode::Gpu &&
Settings::values.astc_recompression.GetValue() ==
- Settings::AstcRecompression::Uncompressed &&
- !Settings::values.async_astc.GetValue();
+ Settings::AstcRecompression::Uncompressed;
}
// Disable other accelerated uploads for now as they don't implement swizzled uploads
return false;
@@ -267,7 +266,8 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
[[nodiscard]] bool CanBeDecodedAsync(const TextureCacheRuntime& runtime,
const VideoCommon::ImageInfo& info) {
if (IsPixelFormatASTC(info.format) && !runtime.HasNativeASTC()) {
- return Settings::values.async_astc.GetValue();
+ return Settings::values.accelerate_astc.GetValue() ==
+ Settings::AstcDecodeMode::CpuAsynchronous;
}
return false;
}
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 2a74c1d05..6b8d4e554 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -473,7 +473,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
glBindTextureUnit(0, screen_info.display_texture);
auto anti_aliasing = Settings::values.anti_aliasing.GetValue();
- if (anti_aliasing > Settings::AntiAliasing::LastAA) {
+ if (anti_aliasing >= Settings::AntiAliasing::MaxEnum) {
LOG_ERROR(Render_OpenGL, "Invalid antialiasing option selected {}", anti_aliasing);
anti_aliasing = Settings::AntiAliasing::None;
Settings::values.anti_aliasing.SetValue(anti_aliasing);
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,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
static constexpr GLuint BINDING_INPUT_BUFFER = 0;
static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
+ program_manager.LocalMemoryWarmup();
const Extent2D tile_size{
.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 28d4b15a0..1032c9d12 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -3,6 +3,8 @@
#include <algorithm>
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+
#include "common/settings.h"
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
@@ -14,12 +16,12 @@
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
#include "video_core/host_shaders/vulkan_color_clear_frag_spv.h"
#include "video_core/host_shaders/vulkan_color_clear_vert_spv.h"
+#include "video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h"
#include "video_core/renderer_vulkan/blit_image.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
-#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/surface.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -427,6 +429,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)),
clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)),
clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)),
+ clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)),
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
@@ -592,6 +595,28 @@ void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_ma
scheduler.InvalidateState();
}
+void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear,
+ f32 clear_depth, u8 stencil_mask, u32 stencil_ref,
+ u32 stencil_compare_mask, const Region2D& dst_region) {
+ const BlitDepthStencilPipelineKey key{
+ .renderpass = dst_framebuffer->RenderPass(),
+ .depth_clear = depth_clear,
+ .stencil_mask = stencil_mask,
+ .stencil_compare_mask = stencil_compare_mask,
+ .stencil_ref = stencil_ref,
+ };
+ const VkPipeline pipeline = FindOrEmplaceClearStencilPipeline(key);
+ const VkPipelineLayout layout = *clear_color_pipeline_layout;
+ scheduler.RequestRenderpass(dst_framebuffer);
+ scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+ BindBlitState(cmdbuf, dst_region);
+ cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
+ cmdbuf.Draw(3, 1, 0, 0);
+ });
+ scheduler.InvalidateState();
+}
+
void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
const VkPipelineLayout layout = *one_texture_pipeline_layout;
@@ -819,6 +844,61 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
return *clear_color_pipelines.back();
}
+VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
+ const BlitDepthStencilPipelineKey& key) {
+ const auto it = std::ranges::find(clear_stencil_keys, key);
+ if (it != clear_stencil_keys.end()) {
+ return *clear_stencil_pipelines[std::distance(clear_stencil_keys.begin(), it)];
+ }
+ clear_stencil_keys.push_back(key);
+ const std::array stages = MakeStages(*clear_color_vert, *clear_stencil_frag);
+ const auto stencil = VkStencilOpState{
+ .failOp = VK_STENCIL_OP_KEEP,
+ .passOp = VK_STENCIL_OP_REPLACE,
+ .depthFailOp = VK_STENCIL_OP_KEEP,
+ .compareOp = VK_COMPARE_OP_ALWAYS,
+ .compareMask = key.stencil_compare_mask,
+ .writeMask = key.stencil_mask,
+ .reference = key.stencil_ref,
+ };
+ const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .depthTestEnable = VK_FALSE,
+ .depthWriteEnable = key.depth_clear,
+ .depthCompareOp = VK_COMPARE_OP_ALWAYS,
+ .depthBoundsTestEnable = VK_FALSE,
+ .stencilTestEnable = VK_TRUE,
+ .front = stencil,
+ .back = stencil,
+ .minDepthBounds = 0.0f,
+ .maxDepthBounds = 0.0f,
+ };
+ clear_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stageCount = static_cast<u32>(stages.size()),
+ .pStages = stages.data(),
+ .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pTessellationState = nullptr,
+ .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pDepthStencilState = &depth_stencil_ci,
+ .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
+ .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .layout = *clear_color_pipeline_layout,
+ .renderPass = key.renderpass,
+ .subpass = 0,
+ .basePipelineHandle = VK_NULL_HANDLE,
+ .basePipelineIndex = 0,
+ }));
+ return *clear_stencil_pipelines.back();
+}
+
void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass,
bool is_target_depth) {
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 {
Tegra::Engines::Fermi2D::Operation operation;
};
+struct BlitDepthStencilPipelineKey {
+ constexpr auto operator<=>(const BlitDepthStencilPipelineKey&) const noexcept = default;
+
+ VkRenderPass renderpass;
+ bool depth_clear;
+ u8 stencil_mask;
+ u32 stencil_compare_mask;
+ u32 stencil_ref;
+};
+
class BlitImageHelper {
public:
explicit BlitImageHelper(const Device& device, Scheduler& scheduler,
@@ -64,6 +74,10 @@ public:
void ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
const std::array<f32, 4>& clear_color, const Region2D& dst_region);
+ void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth,
+ u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask,
+ const Region2D& dst_region);
+
private:
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
const ImageView& src_image_view);
@@ -76,6 +90,8 @@ private:
[[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key);
[[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key);
+ [[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline(
+ const BlitDepthStencilPipelineKey& key);
void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth);
@@ -108,6 +124,7 @@ private:
vk::ShaderModule blit_depth_stencil_frag;
vk::ShaderModule clear_color_vert;
vk::ShaderModule clear_color_frag;
+ vk::ShaderModule clear_stencil_frag;
vk::ShaderModule convert_depth_to_float_frag;
vk::ShaderModule convert_float_to_depth_frag;
vk::ShaderModule convert_abgr8_to_d24s8_frag;
@@ -122,6 +139,8 @@ private:
std::vector<vk::Pipeline> blit_depth_stencil_pipelines;
std::vector<BlitImagePipelineKey> clear_color_keys;
std::vector<vk::Pipeline> clear_color_pipelines;
+ std::vector<BlitDepthStencilPipelineKey> clear_stencil_keys;
+ std::vector<vk::Pipeline> clear_stencil_pipelines;
vk::Pipeline convert_d32_to_r32_pipeline;
vk::Pipeline convert_r32_to_d32_pipeline;
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..208e88533 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 {
{VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM
{VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM
{VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT
- {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable | Storage}, // A2R10G10B10_UNORM
+ {VK_FORMAT_A2R10G10B10_UNORM_PACK32, Attachable}, // A2R10G10B10_UNORM
{VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle)
{VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled)
{VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM
@@ -185,7 +185,7 @@ struct FormatTuple {
{VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB
{VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB
{VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB
- {VK_FORMAT_R4G4B4A4_UNORM_PACK16}, // A4B4G4R4_UNORM
+ {VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT}, // A4B4G4R4_UNORM
{VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM
{VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB
{VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB
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(
return fmt::format("{}", fmt::join(available_extensions, ","));
}
-DebugCallback MakeDebugCallback(const vk::Instance& instance, const vk::InstanceDispatch& dld) {
- if (!Settings::values.renderer_debug) {
- return DebugCallback{};
- }
- const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld);
- const auto it = std::ranges::find_if(*properties, [](const auto& prop) {
- return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0;
- });
- if (it != properties->end()) {
- return CreateDebugUtilsCallback(instance);
- } else {
- return CreateDebugReportCallback(instance);
- }
-}
-
} // Anonymous namespace
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
@@ -103,7 +88,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())),
instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
Settings::values.renderer_debug.GetValue())),
- debug_callback(MakeDebugCallback(instance, dld)),
+ debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance)
+ : vk::DebugUtilsMessenger{}),
surface(CreateSurface(instance, render_window.GetWindowInfo())),
device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(),
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 ca22c0baa..590bc1c64 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -7,11 +7,12 @@
#include <string>
#include <variant>
+#include "video_core/renderer_vulkan/vk_rasterizer.h"
+
#include "common/dynamic_library.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/renderer_vulkan/vk_present_manager.h"
-#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
@@ -34,8 +35,6 @@ class GPU;
namespace Vulkan {
-using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>;
-
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
VkSurfaceKHR surface);
@@ -74,7 +73,7 @@ private:
vk::InstanceDispatch dld;
vk::Instance instance;
- DebugCallback debug_callback;
+ vk::DebugUtilsMessenger debug_messenger;
vk::SurfaceKHR surface;
ScreenInfo screen_info;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index ad3b29f0e..31928bb94 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -566,7 +566,7 @@ void BlitScreen::CreateDescriptorPool() {
const VkDescriptorPoolCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
- .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+ .flags = 0,
.maxSets = static_cast<u32>(image_count),
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
.pPoolSizes = pool_sizes.data(),
@@ -576,7 +576,7 @@ void BlitScreen::CreateDescriptorPool() {
const VkDescriptorPoolCreateInfo ci_aa{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
- .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+ .flags = 0,
.maxSets = static_cast<u32>(image_count),
.poolSizeCount = static_cast<u32>(pool_sizes_aa.size()),
.pPoolSizes = pool_sizes_aa.data(),
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index f8cd2a5d8..e15865d16 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -7,8 +7,9 @@
#include <span>
#include <vector>
-#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+
+#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
@@ -528,17 +529,20 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
buffer_handles.push_back(handle);
}
if (device.IsExtExtendedDynamicStateSupported()) {
- scheduler.Record([bindings_ = std::move(bindings),
+ scheduler.Record([this, bindings_ = std::move(bindings),
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index,
- bindings_.max_index - bindings_.min_index,
+ std::min(bindings_.max_index - bindings_.min_index,
+ device.GetMaxVertexInputBindings()),
buffer_handles_.data(), bindings_.offsets.data(),
bindings_.sizes.data(), bindings_.strides.data());
});
} else {
- scheduler.Record([bindings_ = std::move(bindings),
+ scheduler.Record([this, bindings_ = std::move(bindings),
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
- cmdbuf.BindVertexBuffers(bindings_.min_index, bindings_.max_index - bindings_.min_index,
+ cmdbuf.BindVertexBuffers(bindings_.min_index,
+ std::min(bindings_.max_index - bindings_.min_index,
+ device.GetMaxVertexInputBindings()),
buffer_handles_.data(), bindings_.offsets.data());
});
}
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 3bc8553e1..54ee030ce 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -6,6 +6,8 @@
#include <optional>
#include <utility>
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+
#include "common/assert.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
@@ -16,7 +18,6 @@
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
-#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/texture_cache/accelerated_swizzle.h"
#include "video_core/texture_cache/types.h"
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
index b5ae6443c..6048a301f 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
@@ -77,7 +77,7 @@ static void AllocatePool(const Device& device, DescriptorBank& bank) {
bank.pools.push_back(device.GetLogical().CreateDescriptorPool({
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
- .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+ .flags = 0,
.maxSets = sets_per_pool,
.poolSizeCount = static_cast<u32>(pool_cursor),
.pPoolSizes = std::data(pool_sizes),
diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp
index 9bcdca2fb..ce8f3f3c2 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.cpp
+++ b/src/video_core/renderer_vulkan/vk_fsr.cpp
@@ -150,7 +150,7 @@ void FSR::CreateDescriptorPool() {
const VkDescriptorPoolCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
- .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+ .flags = 0,
.maxSets = static_cast<u32>(image_count * 2),
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
.pPoolSizes = pool_sizes.data(),
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index ad35cacac..f2fd2670f 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -7,9 +7,10 @@
#include <boost/container/small_vector.hpp>
#include <boost/container/static_vector.hpp>
+#include "video_core/renderer_vulkan/pipeline_helper.h"
+
#include "common/bit_field.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
-#include "video_core/renderer_vulkan/pipeline_helper.h"
#include "video_core/renderer_vulkan/pipeline_statistics.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 4f84d8497..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
texture_cache{texture_cache_}, shader_notify{shader_notify_},
use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()},
- workers(device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY
- ? 1
- : (std::max(std::thread::hardware_concurrency(), 2U) - 1),
- "VkPipelineBuilder"),
+#ifdef ANDROID
+ workers(1, "VkPipelineBuilder"),
+#else
+ workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"),
+#endif
serialization_thread(1, "VkPipelineSerialization") {
const auto& float_control{device.FloatControlProperties()};
const VkDriverId driver_id{device.GetDriverID()};
@@ -584,7 +585,8 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
ShaderPools& pools, const GraphicsPipelineCacheKey& key,
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
bool build_in_parallel) try {
- LOG_INFO(Render_Vulkan, "0x{:016x}", key.Hash());
+ auto hash = key.Hash();
+ LOG_INFO(Render_Vulkan, "0x{:016x}", hash);
size_t env_index{0};
std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs;
const bool uses_vertex_a{key.unique_hashes[0] != 0};
@@ -610,9 +612,6 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
- if (Settings::values.dump_shaders) {
- env.Dump(key.unique_hashes[index]);
- }
if (!uses_vertex_a || index != 1) {
// Normal path
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
@@ -623,6 +622,10 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
}
+ if (Settings::values.dump_shaders) {
+ env.Dump(hash, key.unique_hashes[index]);
+ }
+
if (programs[index].info.requires_layer_emulation) {
layer_source_program = &programs[index];
}
@@ -663,6 +666,19 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
std::move(modules), infos);
} catch (const Shader::Exception& exception) {
+ auto hash = key.Hash();
+ size_t env_index{0};
+ for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
+ if (key.unique_hashes[index] == 0) {
+ continue;
+ }
+ Shader::Environment& env{*envs[env_index]};
+ ++env_index;
+
+ const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
+ Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);
+ env.Dump(hash, key.unique_hashes[index]);
+ }
LOG_ERROR(Render_Vulkan, "{}", exception.what());
return nullptr;
}
@@ -712,18 +728,19 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env,
PipelineStatistics* statistics, bool build_in_parallel) try {
+ auto hash = key.Hash();
if (device.HasBrokenCompute()) {
- LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", key.Hash());
+ LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", hash);
return nullptr;
}
- LOG_INFO(Render_Vulkan, "0x{:016x}", key.Hash());
+ LOG_INFO(Render_Vulkan, "0x{:016x}", hash);
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
// Dump it before error.
if (Settings::values.dump_shaders) {
- env.Dump(key.Hash());
+ env.Dump(hash, key.unique_hash);
}
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 456bb040e..01e76a82c 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -6,6 +6,8 @@
#include <memory>
#include <mutex>
+#include "video_core/renderer_vulkan/renderer_vulkan.h"
+
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
@@ -18,7 +20,6 @@
#include "video_core/renderer_vulkan/blit_image.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
-#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
@@ -427,15 +428,27 @@ void RasterizerVulkan::Clear(u32 layer_count) {
if (aspect_flags == 0) {
return;
}
- scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil,
- clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) {
- VkClearAttachment attachment;
- attachment.aspectMask = aspect_flags;
- attachment.colorAttachment = 0;
- attachment.clearValue.depthStencil.depth = clear_depth;
- attachment.clearValue.depthStencil.stencil = clear_stencil;
- cmdbuf.ClearAttachments(attachment, clear_rect);
- });
+
+ if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) {
+ Region2D dst_region = {
+ Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
+ Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
+ .y = clear_rect.rect.offset.y +
+ static_cast<s32>(clear_rect.rect.extent.height)}};
+ blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth,
+ static_cast<u8>(regs.stencil_front_mask), regs.clear_stencil,
+ regs.stencil_front_func_mask, dst_region);
+ } else {
+ scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil,
+ clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) {
+ VkClearAttachment attachment;
+ attachment.aspectMask = aspect_flags;
+ attachment.colorAttachment = 0;
+ attachment.clearValue.depthStencil.depth = clear_depth;
+ attachment.clearValue.depthStencil.stencil = clear_stencil;
+ cmdbuf.ClearAttachments(attachment, clear_rect);
+ });
+ }
}
void RasterizerVulkan::DispatchCompute() {
@@ -450,6 +463,20 @@ void RasterizerVulkan::DispatchCompute() {
pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache);
const auto& qmd{kepler_compute->launch_description};
+ auto indirect_address = kepler_compute->GetIndirectComputeAddress();
+ if (indirect_address) {
+ // DispatchIndirect
+ static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
+ const auto post_op = VideoCommon::ObtainBufferOperation::DiscardWrite;
+ const auto [buffer, offset] =
+ buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op);
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([indirect_buffer = buffer->Handle(),
+ indirect_offset = offset](vk::CommandBuffer cmdbuf) {
+ cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset);
+ });
+ return;
+ }
const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
@@ -829,7 +856,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
}
const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height);
static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
- const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
+ const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing
+ : VideoCommon::ObtainBufferOperation::MarkAsWritten;
const auto [buffer, offset] =
buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op);
@@ -838,8 +866,12 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
const std::span copy_span{&copy, 1};
if constexpr (IS_IMAGE_UPLOAD) {
+ texture_cache.PrepareImage(image_id, true, false);
image->UploadMemory(buffer->Handle(), offset, copy_span);
} else {
+ if (offset % BytesPerBlock(image->info.format)) {
+ return false;
+ }
texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span,
buffer_operand.address, buffer_size);
}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 73257d964..b31982485 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -7,13 +7,14 @@
#include <boost/container/static_vector.hpp>
+#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+
#include "common/common_types.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/rasterizer_accelerated.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/blit_image.h"
-#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_fence_manager.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 17ef61147..89fd31b4f 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -6,11 +6,12 @@
#include <thread>
#include <utility>
+#include "video_core/renderer_vulkan/vk_query_cache.h"
+
#include "common/microprofile.h"
#include "common/thread.h"
#include "video_core/renderer_vulkan/vk_command_pool.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
-#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index d3cddac69..81ef98f61 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -45,8 +45,8 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox,
return mode;
}
switch (mode) {
- case Settings::VSyncMode::FIFO:
- case Settings::VSyncMode::FIFORelaxed:
+ case Settings::VSyncMode::Fifo:
+ case Settings::VSyncMode::FifoRelaxed:
if (has_mailbox) {
return Settings::VSyncMode::Mailbox;
} else if (has_imm) {
@@ -59,8 +59,8 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox,
}();
if ((setting == Settings::VSyncMode::Mailbox && !has_mailbox) ||
(setting == Settings::VSyncMode::Immediate && !has_imm) ||
- (setting == Settings::VSyncMode::FIFORelaxed && !has_fifo_relaxed)) {
- setting = Settings::VSyncMode::FIFO;
+ (setting == Settings::VSyncMode::FifoRelaxed && !has_fifo_relaxed)) {
+ setting = Settings::VSyncMode::Fifo;
}
switch (setting) {
@@ -68,9 +68,9 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox,
return VK_PRESENT_MODE_IMMEDIATE_KHR;
case Settings::VSyncMode::Mailbox:
return VK_PRESENT_MODE_MAILBOX_KHR;
- case Settings::VSyncMode::FIFO:
+ case Settings::VSyncMode::Fifo:
return VK_PRESENT_MODE_FIFO_KHR;
- case Settings::VSyncMode::FIFORelaxed:
+ case Settings::VSyncMode::FifoRelaxed:
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
default:
return VK_PRESENT_MODE_FIFO_KHR;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index ed048f7b8..f25842476 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -11,6 +11,8 @@
#include "common/bit_util.h"
#include "common/settings.h"
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+
#include "video_core/engines/fermi_2d.h"
#include "video_core/renderer_vulkan/blit_image.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
@@ -18,7 +20,6 @@
#include "video_core/renderer_vulkan/vk_render_pass_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
-#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/texture_cache/formatter.h"
#include "video_core/texture_cache/samples_helper.h"
#include "video_core/texture_cache/util.h"
@@ -599,7 +600,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
}
void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle,
- bool emulate_bgr565) {
+ bool emulate_bgr565, bool emulate_a4b4g4r4) {
switch (format) {
case PixelFormat::A1B5G5R5_UNORM:
std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
@@ -615,6 +616,11 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4
case PixelFormat::G4R4_UNORM:
std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed);
break;
+ case PixelFormat::A4B4G4R4_UNORM:
+ if (emulate_a4b4g4r4) {
+ std::ranges::reverse(swizzle);
+ }
+ break;
default:
break;
}
@@ -817,7 +823,7 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
: device{device_}, scheduler{scheduler_}, memory_allocator{memory_allocator_},
staging_buffer_pool{staging_buffer_pool_}, blit_image_helper{blit_image_helper_},
render_pass_cache{render_pass_cache_}, resolution{Settings::values.resolution_info} {
- if (Settings::values.accelerate_astc) {
+ if (Settings::values.accelerate_astc.GetValue() == Settings::AstcDecodeMode::Gpu) {
astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue, memory_allocator);
}
@@ -1313,12 +1319,19 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
runtime->ViewFormats(info.format))),
aspect_mask(ImageAspectMask(info.format)) {
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
- if (Settings::values.async_astc.GetValue()) {
+ switch (Settings::values.accelerate_astc.GetValue()) {
+ case Settings::AstcDecodeMode::Gpu:
+ if (Settings::values.astc_recompression.GetValue() ==
+ Settings::AstcRecompression::Uncompressed &&
+ info.size.depth == 1) {
+ flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
+ }
+ break;
+ case Settings::AstcDecodeMode::CpuAsynchronous:
flags |= VideoCommon::ImageFlagBits::AsynchronousDecode;
- } else if (Settings::values.astc_recompression.GetValue() ==
- Settings::AstcRecompression::Uncompressed &&
- Settings::values.accelerate_astc.GetValue() && info.size.depth == 1) {
- flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
+ break;
+ default:
+ break;
}
flags |= VideoCommon::ImageFlagBits::Converted;
flags |= VideoCommon::ImageFlagBits::CostlyLoad;
@@ -1653,7 +1666,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
};
if (!info.IsRenderTarget()) {
swizzle = info.Swizzle();
- TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565());
+ TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565(),
+ !device->IsExt4444FormatsSupported());
if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 6621210ea..565ce19a9 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -5,11 +5,12 @@
#include <span>
+#include "video_core/texture_cache/texture_cache_base.h"
+
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/texture_cache/image_view_base.h"
-#include "video_core/texture_cache/texture_cache_base.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
diff --git a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
index 460d8d59d..04a51f2d1 100644
--- a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
+++ b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp
@@ -62,7 +62,7 @@ void TurboMode::Run(std::stop_token stop_token) {
auto descriptor_pool = dld.CreateDescriptorPool(VkDescriptorPoolCreateInfo{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
- .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+ .flags = 0,
.maxSets = 1,
.poolSizeCount = 1,
.pPoolSizes = &pool_size,
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
index 01701201d..e81cd031b 100644
--- a/src/video_core/shader_cache.cpp
+++ b/src/video_core/shader_cache.cpp
@@ -51,6 +51,11 @@ bool ShaderCache::RefreshStages(std::array<u64, 6>& unique_hashes) {
}
const auto& shader_config{maxwell3d->regs.pipelines[index]};
const auto program{static_cast<Tegra::Engines::Maxwell3D::Regs::ShaderType>(index)};
+ if (program == Tegra::Engines::Maxwell3D::Regs::ShaderType::Pixel &&
+ !maxwell3d->regs.rasterize_enable) {
+ unique_hashes[index] = 0;
+ continue;
+ }
const GPUVAddr shader_addr{base_addr + shader_config.offset};
const std::optional<VAddr> cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)};
if (!cpu_shader_addr) {
diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h
index de8e08002..a76896620 100644
--- a/src/video_core/shader_cache.h
+++ b/src/video_core/shader_cache.h
@@ -70,7 +70,7 @@ public:
protected:
struct GraphicsEnvironments {
std::array<GraphicsEnvironment, NUM_PROGRAMS> envs;
- std::array<Shader::Environment*, NUM_PROGRAMS> env_ptrs;
+ std::array<Shader::Environment*, NUM_PROGRAMS> env_ptrs{};
std::span<Shader::Environment* const> Span() const noexcept {
return std::span(env_ptrs.begin(), std::ranges::find(env_ptrs, nullptr));
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
index c7cb56243..4edbe5700 100644
--- a/src/video_core/shader_environment.cpp
+++ b/src/video_core/shader_environment.cpp
@@ -102,7 +102,8 @@ static std::string_view StageToPrefix(Shader::Stage stage) {
}
}
-static void DumpImpl(u64 hash, const u64* code, u32 read_highest, u32 read_lowest,
+static void DumpImpl(u64 pipeline_hash, u64 shader_hash, std::span<const u64> code,
+ [[maybe_unused]] u32 read_highest, [[maybe_unused]] u32 read_lowest,
u32 initial_offset, Shader::Stage stage) {
const auto shader_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)};
const auto base_dir{shader_dir / "shaders"};
@@ -111,13 +112,18 @@ static void DumpImpl(u64 hash, const u64* code, u32 read_highest, u32 read_lowes
return;
}
const auto prefix = StageToPrefix(stage);
- const auto name{base_dir / fmt::format("{}{:016x}.ash", prefix, hash)};
- const size_t real_size = read_highest - read_lowest + initial_offset;
- const size_t padding_needed = ((32 - (real_size % 32)) % 32);
+ const auto name{base_dir /
+ fmt::format("{:016x}_{}_{:016x}.ash", pipeline_hash, prefix, shader_hash)};
std::fstream shader_file(name, std::ios::out | std::ios::binary);
+ ASSERT(initial_offset % sizeof(u64) == 0);
const size_t jump_index = initial_offset / sizeof(u64);
- shader_file.write(reinterpret_cast<const char*>(code + jump_index), real_size);
- for (size_t i = 0; i < padding_needed; i++) {
+ const size_t code_size = code.size_bytes() - initial_offset;
+ shader_file.write(reinterpret_cast<const char*>(&code[jump_index]), code_size);
+
+ // + 1 instruction, due to the fact that we skip the final self branch instruction in the code,
+ // but we need to consider it for padding, otherwise nvdisasm rages.
+ const size_t padding_needed = (32 - ((code_size + INST_SIZE) % 32)) % 32;
+ for (size_t i = 0; i < INST_SIZE + padding_needed; i++) {
shader_file.put(0);
}
}
@@ -197,8 +203,8 @@ u64 GenericEnvironment::CalculateHash() const {
return Common::CityHash64(data.get(), size);
}
-void GenericEnvironment::Dump(u64 hash) {
- DumpImpl(hash, code.data(), read_highest, read_lowest, initial_offset, stage);
+void GenericEnvironment::Dump(u64 pipeline_hash, u64 shader_hash) {
+ DumpImpl(pipeline_hash, shader_hash, code, read_highest, read_lowest, initial_offset, stage);
}
void GenericEnvironment::Serialize(std::ofstream& file) const {
@@ -282,6 +288,7 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
bool via_header_index, u32 raw) {
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
+ ASSERT(handle.first <= tic_limit);
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
Tegra::Texture::TICEntry entry;
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
@@ -465,8 +472,8 @@ void FileEnvironment::Deserialize(std::ifstream& file) {
.read(reinterpret_cast<char*>(&read_highest), sizeof(read_highest))
.read(reinterpret_cast<char*>(&viewport_transform_state), sizeof(viewport_transform_state))
.read(reinterpret_cast<char*>(&stage), sizeof(stage));
- code = std::make_unique<u64[]>(Common::DivCeil(code_size, sizeof(u64)));
- file.read(reinterpret_cast<char*>(code.get()), code_size);
+ code.resize(Common::DivCeil(code_size, sizeof(u64)));
+ file.read(reinterpret_cast<char*>(code.data()), code_size);
for (size_t i = 0; i < num_texture_types; ++i) {
u32 key;
Shader::TextureType type;
@@ -509,8 +516,8 @@ void FileEnvironment::Deserialize(std::ifstream& file) {
is_propietary_driver = texture_bound == 2;
}
-void FileEnvironment::Dump(u64 hash) {
- DumpImpl(hash, code.get(), read_highest, read_lowest, initial_offset, stage);
+void FileEnvironment::Dump(u64 pipeline_hash, u64 shader_hash) {
+ DumpImpl(pipeline_hash, shader_hash, code, read_highest, read_lowest, initial_offset, stage);
}
u64 FileEnvironment::ReadInstruction(u32 address) {
diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h
index a0f61cbda..b90f3d44e 100644
--- a/src/video_core/shader_environment.h
+++ b/src/video_core/shader_environment.h
@@ -58,7 +58,7 @@ public:
[[nodiscard]] u64 CalculateHash() const;
- void Dump(u64 hash) override;
+ void Dump(u64 pipeline_hash, u64 shader_hash) override;
void Serialize(std::ofstream& file) const;
@@ -188,10 +188,10 @@ public:
return cbuf_replacements.size() != 0;
}
- void Dump(u64 hash) override;
+ void Dump(u64 pipeline_hash, u64 shader_hash) override;
private:
- std::unique_ptr<u64[]> code;
+ std::vector<u64> code;
std::unordered_map<u32, Shader::TextureType> texture_types;
std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
std::unordered_map<u64, u32> cbuf_values;
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index 11ced6c38..56307d030 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -140,6 +140,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::D32_FLOAT;
case Hash(TextureFormat::Z16, UNORM):
return PixelFormat::D16_UNORM;
+ case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
+ return PixelFormat::D16_UNORM;
case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
return PixelFormat::S8_UINT_D24_UNORM;
case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
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
return nullptr;
}
const auto& image_map_ids = it->second;
+ boost::container::small_vector<const ImageBase*, 4> valid_images;
for (const ImageMapId map_id : image_map_ids) {
const ImageMapView& map = slot_map_views[map_id];
const ImageBase& image = slot_images[map.image_id];
@@ -728,8 +729,20 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
if (image.image_view_ids.empty()) {
continue;
}
- return &slot_image_views[image.image_view_ids.at(0)];
+ valid_images.push_back(&image);
}
+
+ if (valid_images.size() == 1) [[likely]] {
+ return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
+ }
+
+ if (valid_images.size() > 0) [[unlikely]] {
+ std::ranges::sort(valid_images, [](const auto* a, const auto* b) {
+ return a->modification_tick > b->modification_tick;
+ });
+ return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
+ }
+
return nullptr;
}
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:
/// Create channel state.
void CreateChannel(Tegra::Control::ChannelState& channel) final override;
+ /// Prepare an image to be used
+ void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
+
std::recursive_mutex mutex;
private:
@@ -387,9 +390,6 @@ private:
/// Synchronize image aliases, copying data if needed
void SynchronizeAliases(ImageId image_id);
- /// Prepare an image to be used
- void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
-
/// Prepare an image view to be used
void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate);
diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp
index d8b88d9bc..39c08b5ae 100644
--- a/src/video_core/textures/texture.cpp
+++ b/src/video_core/textures/texture.cpp
@@ -72,12 +72,12 @@ float TSCEntry::MaxAnisotropy() const noexcept {
}
const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue();
s32 added_anisotropic{};
- if (anisotropic_settings == 0) {
+ if (anisotropic_settings == Settings::AnisotropyMode::Automatic) {
added_anisotropic = Settings::values.resolution_info.up_scale >>
Settings::values.resolution_info.down_shift;
added_anisotropic = std::max(added_anisotropic - 1, 0);
} else {
- added_anisotropic = Settings::values.max_anisotropy.GetValue() - 1U;
+ added_anisotropic = static_cast<u32>(Settings::values.max_anisotropy.GetValue()) - 1U;
}
return static_cast<float>(1U << (max_anisotropy + added_anisotropic));
}
diff --git a/src/video_core/vulkan_common/vma.cpp b/src/video_core/vulkan_common/vma.cpp
index 1fe2cf52b..addf10762 100644
--- a/src/video_core/vulkan_common/vma.cpp
+++ b/src/video_core/vulkan_common/vma.cpp
@@ -2,7 +2,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#define VMA_IMPLEMENTATION
-#define VMA_STATIC_VULKAN_FUNCTIONS 0
-#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
-#include <vk_mem_alloc.h> \ No newline at end of file
+#include "video_core/vulkan_common/vma.h"
diff --git a/src/video_core/vulkan_common/vma.h b/src/video_core/vulkan_common/vma.h
new file mode 100644
index 000000000..6e25aa1bd
--- /dev/null
+++ b/src/video_core/vulkan_common/vma.h
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "video_core/vulkan_common/vulkan.h"
+
+#define VMA_STATIC_VULKAN_FUNCTIONS 0
+#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
+
+#include <vk_mem_alloc.h>
diff --git a/src/video_core/vulkan_common/vulkan.h b/src/video_core/vulkan_common/vulkan.h
new file mode 100644
index 000000000..62aa13291
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan.h
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#define VK_NO_PROTOTYPES
+#ifdef _WIN32
+#define VK_USE_PLATFORM_WIN32_KHR
+#elif defined(__APPLE__)
+#define VK_USE_PLATFORM_METAL_EXT
+#elif defined(__ANDROID__)
+#define VK_USE_PLATFORM_ANDROID_KHR
+#else
+#define VK_USE_PLATFORM_XLIB_KHR
+#define VK_USE_PLATFORM_WAYLAND_KHR
+#endif
+
+#include <vulkan/vulkan.h>
+
+// Sanitize macros
+#undef CreateEvent
+#undef CreateSemaphore
+#undef Always
+#undef False
+#undef None
+#undef True
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,
return VK_FALSE;
}
-VkBool32 DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType,
- uint64_t object, size_t location, int32_t messageCode,
- const char* pLayerPrefix, const char* pMessage, void* pUserData) {
- const VkDebugReportFlagBitsEXT severity = static_cast<VkDebugReportFlagBitsEXT>(flags);
- const std::string_view message{pMessage};
- if (severity & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
- LOG_CRITICAL(Render_Vulkan, "{}", message);
- } else if (severity & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
- LOG_WARNING(Render_Vulkan, "{}", message);
- } else if (severity & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
- LOG_INFO(Render_Vulkan, "{}", message);
- } else if (severity & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
- LOG_DEBUG(Render_Vulkan, "{}", message);
- }
- return VK_FALSE;
-}
} // Anonymous namespace
vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
@@ -98,15 +82,4 @@ vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
});
}
-vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance) {
- return instance.CreateDebugReportCallback({
- .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
- .pNext = nullptr,
- .flags = VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
- VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT,
- .pfnCallback = DebugReportCallback,
- .pUserData = nullptr,
- });
-}
-
} // 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 {
vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance);
-vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance);
-
} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index e44c06e55..18185610f 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -15,6 +15,7 @@
#include "common/polyfill_ranges.h"
#include "common/settings.h"
#include "video_core/vulkan_common/nsight_aftermath_tracker.h"
+#include "video_core/vulkan_common/vma.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -22,8 +23,6 @@
#include <adrenotools/bcenabler.h>
#endif
-#include <vk_mem_alloc.h>
-
namespace Vulkan {
using namespace Common::Literals;
namespace {
@@ -72,6 +71,16 @@ constexpr std::array R8G8B8_SSCALED{
VK_FORMAT_UNDEFINED,
};
+constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{
+ VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_FORMAT_UNDEFINED,
+};
+
+constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
+ VK_FORMAT_R4G4B4A4_UNORM_PACK16,
+ VK_FORMAT_UNDEFINED,
+};
+
} // namespace Alternatives
enum class NvidiaArchitecture {
@@ -104,6 +113,10 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
return Alternatives::R16G16B16_SSCALED.data();
case VK_FORMAT_R8G8B8_SSCALED:
return Alternatives::R8G8B8_SSCALED.data();
+ case VK_FORMAT_R32G32B32_SFLOAT:
+ return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
+ case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT:
+ return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data();
default:
return nullptr;
}
@@ -131,6 +144,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
VK_FORMAT_A2B10G10R10_UINT_PACK32,
VK_FORMAT_A2B10G10R10_UNORM_PACK32,
VK_FORMAT_A2B10G10R10_USCALED_PACK32,
+ VK_FORMAT_A2R10G10B10_UNORM_PACK32,
VK_FORMAT_A8B8G8R8_SINT_PACK32,
VK_FORMAT_A8B8G8R8_SNORM_PACK32,
VK_FORMAT_A8B8G8R8_SRGB_PACK32,
@@ -231,6 +245,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
VK_FORMAT_R32_SINT,
VK_FORMAT_R32_UINT,
VK_FORMAT_R4G4B4A4_UNORM_PACK16,
+ VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,
VK_FORMAT_R4G4_UNORM_PACK8,
VK_FORMAT_R5G5B5A1_UNORM_PACK16,
VK_FORMAT_R5G6B5_UNORM_PACK16,
@@ -327,6 +342,43 @@ std::vector<const char*> ExtensionListForVulkan(
} // Anonymous namespace
+void Device::RemoveExtension(bool& extension, const std::string& extension_name) {
+ extension = false;
+ loaded_extensions.erase(extension_name);
+}
+
+void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) {
+ if (loaded_extensions.contains(extension_name) && !is_suitable) {
+ LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name);
+ this->RemoveExtension(is_suitable, extension_name);
+ }
+}
+
+template <typename Feature>
+void Device::RemoveExtensionFeature(bool& extension, Feature& feature,
+ const std::string& extension_name) {
+ // Unload extension.
+ this->RemoveExtension(extension, extension_name);
+
+ // Save sType and pNext for chain.
+ VkStructureType sType = feature.sType;
+ void* pNext = feature.pNext;
+
+ // Clear feature struct and restore chain.
+ feature = {};
+ feature.sType = sType;
+ feature.pNext = pNext;
+}
+
+template <typename Feature>
+void Device::RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature,
+ const std::string& extension_name) {
+ if (loaded_extensions.contains(extension_name) && !is_suitable) {
+ LOG_WARNING(Render_Vulkan, "Removing features for unsuitable extension {}", extension_name);
+ this->RemoveExtensionFeature(is_suitable, feature, extension_name);
+ }
+}
+
Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface,
const vk::InstanceDispatch& dld_)
: instance{instance_}, dld{dld_}, physical{physical_},
@@ -399,21 +451,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (is_qualcomm || is_turnip) {
LOG_WARNING(Render_Vulkan,
"Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color");
- extensions.custom_border_color = false;
- loaded_extensions.erase(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
+ VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
}
if (is_qualcomm) {
must_emulate_scaled_formats = true;
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken VK_EXT_extended_dynamic_state");
- extensions.extended_dynamic_state = false;
- loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
LOG_WARNING(Render_Vulkan,
"Qualcomm drivers have a slow VK_KHR_push_descriptor implementation");
- extensions.push_descriptor = false;
- loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
+ RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
// Patch the driver to enable BCn textures.
@@ -442,15 +493,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
must_emulate_scaled_formats = true;
LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
- extensions.extended_dynamic_state = false;
- loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2");
- features.extended_dynamic_state2.extendedDynamicState2 = false;
- features.extended_dynamic_state2.extendedDynamicState2LogicOp = false;
- features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
- extensions.extended_dynamic_state2 = false;
- loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
}
if (is_nvidia) {
@@ -466,8 +514,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
case NvidiaArchitecture::VoltaOrOlder:
if (nv_major_version < 527) {
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
- extensions.push_descriptor = false;
- loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
+ RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
break;
}
@@ -482,8 +529,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) {
LOG_WARNING(Render_Vulkan,
"RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state");
- extensions.extended_dynamic_state = false;
- loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.extended_dynamic_state,
+ features.extended_dynamic_state,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
}
}
if (extensions.extended_dynamic_state2 && is_radv) {
@@ -492,11 +540,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
LOG_WARNING(
Render_Vulkan,
"RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2");
- features.extended_dynamic_state2.extendedDynamicState2 = false;
- features.extended_dynamic_state2.extendedDynamicState2LogicOp = false;
- features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
- extensions.extended_dynamic_state2 = false;
- loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.extended_dynamic_state2,
+ features.extended_dynamic_state2,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
}
}
if (extensions.extended_dynamic_state2 && is_qualcomm) {
@@ -506,11 +552,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
// Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2.
LOG_WARNING(Render_Vulkan,
"Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2");
- features.extended_dynamic_state2.extendedDynamicState2 = false;
- features.extended_dynamic_state2.extendedDynamicState2LogicOp = false;
- features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
- extensions.extended_dynamic_state2 = false;
- loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.extended_dynamic_state2,
+ features.extended_dynamic_state2,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
}
}
if (extensions.extended_dynamic_state3 && is_radv) {
@@ -527,6 +571,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
dynamic_state3_enables = false;
}
}
+ if (extensions.extended_dynamic_state3 && is_amd_driver) {
+ LOG_WARNING(Render_Vulkan,
+ "AMD drivers have broken extendedDynamicState3ColorBlendEquation");
+ features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
+ features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false;
+ dynamic_state3_blending = false;
+ }
if (extensions.vertex_input_dynamic_state && is_radv) {
// TODO(ameerj): Blacklist only offending driver versions
// TODO(ameerj): Confirm if RDNA1 is affected
@@ -535,9 +586,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (is_rdna2) {
LOG_WARNING(Render_Vulkan,
"RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware");
- features.vertex_input_dynamic_state.vertexInputDynamicState = false;
- extensions.vertex_input_dynamic_state = false;
- loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
+ features.vertex_input_dynamic_state,
+ VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
}
if (extensions.vertex_input_dynamic_state && is_qualcomm) {
@@ -548,21 +599,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
LOG_WARNING(
Render_Vulkan,
"Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state");
- features.vertex_input_dynamic_state.vertexInputDynamicState = false;
- extensions.vertex_input_dynamic_state = false;
- loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
+ features.vertex_input_dynamic_state,
+ VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
}
sets_per_pool = 64;
- if (extensions.extended_dynamic_state3 && is_amd_driver &&
- properties.properties.driverVersion >= VK_MAKE_API_VERSION(0, 2, 0, 270)) {
- LOG_WARNING(Render_Vulkan,
- "AMD drivers after 23.5.2 have broken extendedDynamicState3ColorBlendEquation");
- features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
- features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false;
- dynamic_state3_blending = false;
- }
if (is_amd_driver) {
// AMD drivers need a higher amount of Sets per Pool in certain circumstances like in XC2.
sets_per_pool = 96;
@@ -578,8 +621,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (!features.shader_float16_int8.shaderFloat16) {
LOG_WARNING(Render_Vulkan,
"AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax");
- extensions.sampler_filter_minmax = false;
- loaded_extensions.erase(VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME);
+ RemoveExtension(extensions.sampler_filter_minmax,
+ VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME);
}
}
@@ -587,8 +630,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
const u32 version = (properties.properties.driverVersion << 3) >> 3;
if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) {
LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state");
- extensions.vertex_input_dynamic_state = false;
- loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
+ features.vertex_input_dynamic_state,
+ VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
}
if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) {
@@ -615,8 +659,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
// mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc
LOG_WARNING(Render_Vulkan,
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
- extensions.push_descriptor = false;
- loaded_extensions.erase(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
+ RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
}
if (is_mvk) {
@@ -965,7 +1008,7 @@ bool Device::GetSuitability(bool requires_swapchain) {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR;
SetNext(next, properties.push_descriptor);
}
- if (extensions.subgroup_size_control) {
+ if (extensions.subgroup_size_control || features.subgroup_size_control.subgroupSizeControl) {
properties.subgroup_size_control.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES;
SetNext(next, properties.subgroup_size_control);
@@ -1009,34 +1052,29 @@ bool Device::GetSuitability(bool requires_swapchain) {
return suitable;
}
-void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) {
- if (loaded_extensions.contains(extension_name) && !is_suitable) {
- LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name);
- loaded_extensions.erase(extension_name);
- }
-}
-
void Device::RemoveUnsuitableExtensions() {
// VK_EXT_custom_border_color
extensions.custom_border_color = features.custom_border_color.customBorderColors &&
features.custom_border_color.customBorderColorWithoutFormat;
- RemoveExtensionIfUnsuitable(extensions.custom_border_color,
- VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
+ VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
// VK_EXT_depth_clip_control
extensions.depth_clip_control = features.depth_clip_control.depthClipControl;
- RemoveExtensionIfUnsuitable(extensions.depth_clip_control,
- VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
+ VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
// VK_EXT_extended_dynamic_state
extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState;
- RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state,
- VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state,
+ features.extended_dynamic_state,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
// VK_EXT_extended_dynamic_state2
extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2;
- RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state2,
- VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state2,
+ features.extended_dynamic_state2,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
// VK_EXT_extended_dynamic_state3
dynamic_state3_blending =
@@ -1050,35 +1088,38 @@ void Device::RemoveUnsuitableExtensions() {
extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables;
dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3;
dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3;
- RemoveExtensionIfUnsuitable(extensions.extended_dynamic_state3,
- VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state3,
+ features.extended_dynamic_state3,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
// VK_EXT_provoking_vertex
extensions.provoking_vertex =
features.provoking_vertex.provokingVertexLast &&
features.provoking_vertex.transformFeedbackPreservesProvokingVertex;
- RemoveExtensionIfUnsuitable(extensions.provoking_vertex,
- VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.provoking_vertex, features.provoking_vertex,
+ VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
// VK_KHR_shader_atomic_int64
extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics &&
features.shader_atomic_int64.shaderSharedInt64Atomics;
- RemoveExtensionIfUnsuitable(extensions.shader_atomic_int64,
- VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64,
+ VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
// VK_EXT_shader_demote_to_helper_invocation
extensions.shader_demote_to_helper_invocation =
features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation;
- RemoveExtensionIfUnsuitable(extensions.shader_demote_to_helper_invocation,
- VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.shader_demote_to_helper_invocation,
+ features.shader_demote_to_helper_invocation,
+ VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME);
// VK_EXT_subgroup_size_control
extensions.subgroup_size_control =
features.subgroup_size_control.subgroupSizeControl &&
properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize &&
properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize;
- RemoveExtensionIfUnsuitable(extensions.subgroup_size_control,
- VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.subgroup_size_control,
+ features.subgroup_size_control,
+ VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
// VK_EXT_transform_feedback
extensions.transform_feedback =
@@ -1088,24 +1129,27 @@ void Device::RemoveUnsuitableExtensions() {
properties.transform_feedback.maxTransformFeedbackBuffers > 0 &&
properties.transform_feedback.transformFeedbackQueries &&
properties.transform_feedback.transformFeedbackDraw;
- RemoveExtensionIfUnsuitable(extensions.transform_feedback,
- VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback,
+ VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
// VK_EXT_vertex_input_dynamic_state
extensions.vertex_input_dynamic_state =
features.vertex_input_dynamic_state.vertexInputDynamicState;
- RemoveExtensionIfUnsuitable(extensions.vertex_input_dynamic_state,
- VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.vertex_input_dynamic_state,
+ features.vertex_input_dynamic_state,
+ VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
// VK_KHR_pipeline_executable_properties
if (Settings::values.renderer_shader_feedback.GetValue()) {
extensions.pipeline_executable_properties =
features.pipeline_executable_properties.pipelineExecutableInfo;
- RemoveExtensionIfUnsuitable(extensions.pipeline_executable_properties,
- VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.pipeline_executable_properties,
+ features.pipeline_executable_properties,
+ VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
} else {
- extensions.pipeline_executable_properties = false;
- loaded_extensions.erase(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
+ RemoveExtensionFeature(extensions.pipeline_executable_properties,
+ features.pipeline_executable_properties,
+ VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
}
// VK_KHR_workgroup_memory_explicit_layout
@@ -1115,8 +1159,9 @@ void Device::RemoveUnsuitableExtensions() {
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess &&
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess &&
features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout;
- RemoveExtensionIfUnsuitable(extensions.workgroup_memory_explicit_layout,
- VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME);
+ RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout,
+ features.workgroup_memory_explicit_layout,
+ VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME);
}
void 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 80c38bfad..8c5355a28 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -20,7 +20,6 @@ VK_DEFINE_HANDLE(VmaAllocator)
// Vulkan version in the macro describes the minimum version required for feature availability.
// If the Vulkan version is lower than the required version, the named extension is required.
#define FOR_EACH_VK_FEATURE_1_1(FEATURE) \
- FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control) \
FEATURE(KHR, 16BitStorage, 16BIT_STORAGE, bit16_storage) \
FEATURE(KHR, ShaderAtomicInt64, SHADER_ATOMIC_INT64, shader_atomic_int64) \
FEATURE(KHR, ShaderDrawParameters, SHADER_DRAW_PARAMETERS, shader_draw_parameters) \
@@ -36,7 +35,8 @@ VK_DEFINE_HANDLE(VmaAllocator)
#define FOR_EACH_VK_FEATURE_1_3(FEATURE) \
FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \
- shader_demote_to_helper_invocation)
+ shader_demote_to_helper_invocation) \
+ FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control)
// Define all features which may be used by the implementation and require an extension here.
#define FOR_EACH_VK_FEATURE_EXT(FEATURE) \
@@ -45,6 +45,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
+ FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
@@ -97,6 +98,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
+ EXTENSION_NAME(VK_EXT_4444_FORMATS_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \
@@ -144,6 +146,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \
FEATURE_NAME(custom_border_color, customBorderColors) \
FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \
+ FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \
FEATURE_NAME(index_type_uint8, indexTypeUint8) \
FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \
FEATURE_NAME(provoking_vertex, provokingVertexLast) \
@@ -493,6 +496,11 @@ public:
return extensions.extended_dynamic_state3;
}
+ /// Returns true if the device supports VK_EXT_4444_formats.
+ bool IsExt4444FormatsSupported() const {
+ return features.format_a4b4g4r4.formatA4B4G4R4;
+ }
+
/// Returns true if the device supports VK_EXT_extended_dynamic_state3.
bool IsExtExtendedDynamicState3BlendingSupported() const {
return dynamic_state3_blending;
@@ -644,8 +652,17 @@ private:
// Remove extensions which have incomplete feature support.
void RemoveUnsuitableExtensions();
+
+ void RemoveExtension(bool& extension, const std::string& extension_name);
void RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name);
+ template <typename Feature>
+ void RemoveExtensionFeature(bool& extension, Feature& feature,
+ const std::string& extension_name);
+ template <typename Feature>
+ void RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature,
+ const std::string& extension_name);
+
/// Sets up queue families.
void SetupFamilies(VkSurfaceKHR surface);
diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp
index 6a294c1da..180657a75 100644
--- a/src/video_core/vulkan_common/vulkan_instance.cpp
+++ b/src/video_core/vulkan_common/vulkan_instance.cpp
@@ -14,19 +14,6 @@
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
-// Include these late to avoid polluting previous headers
-#if defined(_WIN32)
-#include <windows.h>
-// ensure include order
-#include <vulkan/vulkan_win32.h>
-#elif defined(__ANDROID__)
-#include <vulkan/vulkan_android.h>
-#elif !defined(__APPLE__)
-#include <X11/Xlib.h>
-#include <vulkan/vulkan_wayland.h>
-#include <vulkan/vulkan_xlib.h>
-#endif
-
namespace Vulkan {
namespace {
@@ -54,9 +41,6 @@ namespace {
bool enable_validation) {
std::vector<const char*> extensions;
extensions.reserve(6);
-#ifdef __APPLE__
- extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
-#endif
switch (window_type) {
case Core::Frontend::WindowSystemType::Headless:
break;
@@ -87,11 +71,14 @@ namespace {
if (window_type != Core::Frontend::WindowSystemType::Headless) {
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
}
- if (enable_validation) {
- const bool debug_utils =
- AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME});
- extensions.push_back(debug_utils ? VK_EXT_DEBUG_UTILS_EXTENSION_NAME
- : VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
+#ifdef __APPLE__
+ if (AreExtensionsSupported(dld, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME})) {
+ extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
+ }
+#endif
+ if (enable_validation &&
+ AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) {
+ extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
}
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(
#else
auto library = std::make_shared<Common::DynamicLibrary>();
#ifdef __APPLE__
+ const auto libvulkan_filename =
+ Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.1.dylib";
+ const auto libmoltenvk_filename =
+ Common::FS::GetBundleDirectory() / "Contents/Frameworks/libMoltenVK.dylib";
+ const char* library_paths[] = {std::getenv("LIBVULKAN_PATH"), libvulkan_filename.c_str(),
+ libmoltenvk_filename.c_str()};
// Check if a path to a specific Vulkan library has been specified.
- char* const libvulkan_env = std::getenv("LIBVULKAN_PATH");
- if (!libvulkan_env || !library->Open(libvulkan_env)) {
- // Use the libvulkan.dylib from the application bundle.
- const auto filename =
- Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.dylib";
- void(library->Open(Common::FS::PathToUTF8String(filename).c_str()));
+ for (const auto& library_path : library_paths) {
+ if (library_path && library->Open(library_path)) {
+ break;
+ }
}
#else
std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 42f3ee0b4..3ef381a38 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -11,12 +11,11 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/polyfill_ranges.h"
+#include "video_core/vulkan_common/vma.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
-#include <vk_mem_alloc.h>
-
namespace Vulkan {
namespace {
struct Range {
diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp
index cfea4cd7b..e45f8e43f 100644
--- a/src/video_core/vulkan_common/vulkan_surface.cpp
+++ b/src/video_core/vulkan_common/vulkan_surface.cpp
@@ -6,19 +6,6 @@
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
-// Include these late to avoid polluting previous headers
-#ifdef _WIN32
-#include <windows.h>
-// ensure include order
-#include <vulkan/vulkan_win32.h>
-#elif defined(__ANDROID__)
-#include <vulkan/vulkan_android.h>
-#elif !defined(__APPLE__)
-#include <X11/Xlib.h>
-#include <vulkan/vulkan_wayland.h>
-#include <vulkan/vulkan_xlib.h>
-#endif
-
namespace Vulkan {
vk::SurfaceKHR CreateSurface(
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 2fa29793a..c3f388d89 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -9,11 +9,9 @@
#include "common/common_types.h"
#include "common/logging/log.h"
-
+#include "video_core/vulkan_common/vma.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
-#include <vk_mem_alloc.h>
-
namespace Vulkan::vk {
namespace {
@@ -94,6 +92,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCmdCopyImage);
X(vkCmdCopyImageToBuffer);
X(vkCmdDispatch);
+ X(vkCmdDispatchIndirect);
X(vkCmdDraw);
X(vkCmdDrawIndexed);
X(vkCmdDrawIndirect);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 32bd75ad8..049fa8038 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -12,23 +12,8 @@
#include <utility>
#include <vector>
-#define VK_NO_PROTOTYPES
-#ifdef _WIN32
-#define VK_USE_PLATFORM_WIN32_KHR
-#elif defined(__APPLE__)
-#define VK_USE_PLATFORM_METAL_EXT
-#endif
-#include <vulkan/vulkan.h>
-
-// Sanitize macros
-#ifdef CreateEvent
-#undef CreateEvent
-#endif
-#ifdef CreateSemaphore
-#undef CreateSemaphore
-#endif
-
#include "common/common_types.h"
+#include "video_core/vulkan_common/vulkan.h"
#ifdef _MSC_VER
#pragma warning(disable : 26812) // Disable prefer enum class over enum
@@ -218,6 +203,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkCmdCopyImage vkCmdCopyImage{};
PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{};
PFN_vkCmdDispatch vkCmdDispatch{};
+ PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{};
PFN_vkCmdDraw vkCmdDraw{};
PFN_vkCmdDrawIndexed vkCmdDrawIndexed{};
PFN_vkCmdDrawIndirect vkCmdDrawIndirect{};
@@ -1224,6 +1210,10 @@ public:
dld->vkCmdDispatch(handle, x, y, z);
}
+ void DispatchIndirect(VkBuffer indirect_buffer, VkDeviceSize offset) const noexcept {
+ dld->vkCmdDispatchIndirect(handle, indirect_buffer, offset);
+ }
+
void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers,
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 @@
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // for deprecated OpenSSL functions
#endif
#include <jwt/jwt.hpp>
#if defined(__GNUC__) || defined(__clang__)
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index fe98e3605..8f86a1553 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -143,6 +143,10 @@ add_executable(yuzu
configuration/configure_web.ui
configuration/input_profiles.cpp
configuration/input_profiles.h
+ configuration/shared_translation.cpp
+ configuration/shared_translation.h
+ configuration/shared_widget.cpp
+ configuration/shared_widget.h
debugger/console.cpp
debugger/console.h
debugger/controller.cpp
@@ -231,6 +235,12 @@ if (WIN32 AND YUZU_CRASH_DUMPS)
target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
endif()
+if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+ target_compile_definitions(yuzu PRIVATE
+ $<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>
+ )
+endif()
+
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
@@ -303,6 +313,18 @@ if (APPLE)
target_sources(yuzu PRIVATE ${MACOSX_ICON})
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
+
+ if (NOT USE_SYSTEM_MOLTENVK)
+ set(MOLTENVK_PLATFORM "macOS")
+ set(MOLTENVK_VERSION "v1.2.5")
+ download_moltenvk_external(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION})
+ endif()
+ find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
+ message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
+ set_source_files_properties(${MOLTENVK_LIBRARY} PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks
+ XCODE_FILE_ATTRIBUTES "CodeSignOnCopy")
+ target_sources(yuzu PRIVATE ${MOLTENVK_LIBRARY})
+
elseif(WIN32)
# compile as a win32 gui application instead of a console application
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() {
}
const auto amiibo_name = std::string(register_info.amiibo_name.data());
- const auto owner_name = Common::UTF16ToUTF8(register_info.mii_char_info.name.data());
+ const auto owner_name =
+ Common::UTF16ToUTF8(register_info.mii_char_info.GetNickname().data.data());
const auto creation_date =
QDate(register_info.creation_date.year, register_info.creation_date.month,
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 @@
#include <thread>
#include "common/assert.h"
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
@@ -226,9 +228,11 @@ int QtControllerSelectorDialog::exec() {
}
void QtControllerSelectorDialog::ApplyConfiguration() {
- const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
- Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
- OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system);
+ const bool pre_docked_mode = Settings::IsDockedMode();
+ const bool docked_mode_selected = ui->radioDocked->isChecked();
+ Settings::values.use_docked_mode.SetValue(
+ docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
+ OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
@@ -616,8 +620,8 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
- ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
- ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
+ ui->radioDocked->setChecked(Settings::IsDockedMode());
+ ui->radioUndocked->setChecked(!Settings::IsDockedMode());
// Also force into undocked mode if the controller type is handheld.
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 @@
#include <glad/glad.h>
#include <QtCore/qglobal.h>
+#include "common/settings_enums.h"
+#include "uisettings.h"
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA
#include <QCamera>
#include <QCameraImageCapture>
@@ -916,7 +918,6 @@ void GRenderWindow::ReleaseRenderTarget() {
void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
auto& renderer = system.Renderer();
- const f32 res_scale = Settings::values.resolution_info.up_factor;
if (renderer.IsScreenshotPending()) {
LOG_WARNING(Render,
@@ -924,7 +925,18 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
return;
}
- const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
+ const Layout::FramebufferLayout layout{[]() {
+ u32 height = UISettings::values.screenshot_height.GetValue();
+ if (height == 0) {
+ height = Settings::IsDockedMode() ? Layout::ScreenDocked::Height
+ : Layout::ScreenUndocked::Height;
+ height *= Settings::values.resolution_info.up_factor;
+ }
+ const u32 width =
+ UISettings::CalculateWidth(height, Settings::values.aspect_ratio.GetValue());
+ return Layout::DefaultFrameLayout(width, height);
+ }()};
+
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
renderer.RequestScreenshot(
screenshot_image.bits(),
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 195d3556c..1de093447 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -1,12 +1,15 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
#include <array>
#include <QKeySequence>
#include <QSettings>
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
+#include "common/settings_common.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
@@ -16,9 +19,8 @@
namespace FS = Common::FS;
-Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
- global = config_type == ConfigType::GlobalConfig;
-
+Config::Config(const std::string& config_name, ConfigType config_type)
+ : type(config_type), global{config_type == ConfigType::GlobalConfig} {
Initialize(config_name);
}
@@ -84,15 +86,15 @@ const std::map<Settings::ScalingFilter, QString> Config::scaling_filter_texts_ma
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
};
-const std::map<bool, QString> Config::use_docked_mode_texts_map = {
- {true, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
- {false, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
+const std::map<Settings::ConsoleMode, QString> Config::use_docked_mode_texts_map = {
+ {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
+ {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
};
-const std::map<Settings::GPUAccuracy, QString> Config::gpu_accuracy_texts_map = {
- {Settings::GPUAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
- {Settings::GPUAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
- {Settings::GPUAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
+const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = {
+ {Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
+ {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
+ {Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
};
const std::map<Settings::RendererBackend, QString> Config::renderer_backend_texts_map = {
@@ -102,9 +104,9 @@ const std::map<Settings::RendererBackend, QString> Config::renderer_backend_text
};
const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
- {Settings::ShaderBackend::GLSL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
- {Settings::ShaderBackend::GLASM, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
- {Settings::ShaderBackend::SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
+ {Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
+ {Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
+ {Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
};
// This shouldn't have anything except static initializers (no functions). So
@@ -171,66 +173,6 @@ bool Config::IsCustomConfig() {
return type == ConfigType::PerGameConfig;
}
-/* {Read,Write}BasicSetting and WriteGlobalSetting templates must be defined here before their
- * usages later in this file. This allows explicit definition of some types that don't work
- * nicely with the general version.
- */
-
-// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant, nor
-// can it implicitly convert a QVariant back to a {std::,Q}string
-template <>
-void Config::ReadBasicSetting(Settings::Setting<std::string>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const auto default_value = QString::fromStdString(setting.GetDefault());
- if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
- setting.SetValue(default_value.toStdString());
- } else {
- setting.SetValue(qt_config->value(name, default_value).toString().toStdString());
- }
-}
-
-template <typename Type, bool ranged>
-void Config::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const Type default_value = setting.GetDefault();
- if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
- setting.SetValue(default_value);
- } else {
- setting.SetValue(
- static_cast<QVariant>(qt_config->value(name, default_value)).value<Type>());
- }
-}
-
-// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant
-template <>
-void Config::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const std::string& value = setting.GetValue();
- qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
- qt_config->setValue(name, QString::fromStdString(value));
-}
-
-template <typename Type, bool ranged>
-void Config::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const Type value = setting.GetValue();
- qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
- qt_config->setValue(name, value);
-}
-
-template <typename Type, bool ranged>
-void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const Type& value = setting.GetValue(global);
- if (!global) {
- qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
- }
- if (global || !setting.UsingGlobal()) {
- qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
- qt_config->setValue(name, value);
- }
-}
-
void Config::ReadPlayerValue(std::size_t player_index) {
const QString player_prefix = [this, player_index] {
if (type == ConfigType::InputProfile) {
@@ -351,15 +293,9 @@ void Config::ReadPlayerValue(std::size_t player_index) {
player_motions = default_param;
}
}
-
- if (player_index == 0) {
- ReadMousePanningValues();
- }
}
void Config::ReadDebugValues() {
- ReadBasicSetting(Settings::values.debug_pad_enabled);
-
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
@@ -393,14 +329,6 @@ void Config::ReadDebugValues() {
}
}
-void Config::ReadKeyboardValues() {
- ReadBasicSetting(Settings::values.keyboard_enabled);
-}
-
-void Config::ReadMouseValues() {
- ReadBasicSetting(Settings::values.mouse_enabled);
-}
-
void Config::ReadTouchscreenValues() {
Settings::values.touchscreen.enabled =
ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool();
@@ -414,9 +342,6 @@ void Config::ReadTouchscreenValues() {
}
void Config::ReadHidbusValues() {
- Settings::values.enable_ring_controller =
- ReadSetting(QStringLiteral("enable_ring_controller"), true).toBool();
-
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
auto& ringcon_analogs = Settings::values.ringcon_analogs;
@@ -430,20 +355,10 @@ void Config::ReadHidbusValues() {
}
}
-void Config::ReadIrCameraValues() {
- ReadBasicSetting(Settings::values.enable_ir_sensor);
- ReadBasicSetting(Settings::values.ir_sensor_device);
-}
-
void Config::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
- if (global) {
- ReadBasicSetting(Settings::values.sink_id);
- ReadBasicSetting(Settings::values.audio_output_device_id);
- ReadBasicSetting(Settings::values.audio_input_device_id);
- }
- ReadGlobalSetting(Settings::values.volume);
+ ReadCategory(Settings::Category::Audio);
qt_config->endGroup();
}
@@ -451,63 +366,32 @@ void Config::ReadAudioValues() {
void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
+ ReadCategory(Settings::Category::Controls);
+
Settings::values.players.SetGlobal(!IsCustomConfig());
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
ReadPlayerValue(p);
}
- ReadGlobalSetting(Settings::values.use_docked_mode);
// Disable docked mode if handheld is selected
const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
if (controller_type == Settings::ControllerType::Handheld) {
Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
- Settings::values.use_docked_mode.SetValue(false);
+ Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
}
- ReadGlobalSetting(Settings::values.vibration_enabled);
- ReadGlobalSetting(Settings::values.enable_accurate_vibrations);
- ReadGlobalSetting(Settings::values.motion_enabled);
if (IsCustomConfig()) {
qt_config->endGroup();
return;
}
ReadDebugValues();
- ReadKeyboardValues();
- ReadMouseValues();
ReadTouchscreenValues();
- ReadMousePanningValues();
ReadMotionTouchValues();
ReadHidbusValues();
- ReadIrCameraValues();
-
-#ifdef _WIN32
- ReadBasicSetting(Settings::values.enable_raw_input);
-#else
- Settings::values.enable_raw_input = false;
-#endif
- ReadBasicSetting(Settings::values.emulate_analog_keyboard);
- ReadBasicSetting(Settings::values.enable_joycon_driver);
- ReadBasicSetting(Settings::values.enable_procon_driver);
- ReadBasicSetting(Settings::values.random_amiibo_id);
-
- ReadBasicSetting(Settings::values.tas_enable);
- ReadBasicSetting(Settings::values.tas_loop);
- ReadBasicSetting(Settings::values.pause_tas_on_load);
-
- ReadBasicSetting(Settings::values.controller_navigation);
qt_config->endGroup();
}
-void Config::ReadMousePanningValues() {
- ReadBasicSetting(Settings::values.mouse_panning);
- ReadBasicSetting(Settings::values.mouse_panning_x_sensitivity);
- ReadBasicSetting(Settings::values.mouse_panning_y_sensitivity);
- ReadBasicSetting(Settings::values.mouse_panning_deadzone_counterweight);
- ReadBasicSetting(Settings::values.mouse_panning_decay_strength);
- ReadBasicSetting(Settings::values.mouse_panning_min_decay);
-}
-
void Config::ReadMotionTouchValues() {
int num_touch_from_button_maps =
qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
@@ -541,19 +425,14 @@ void Config::ReadMotionTouchValues() {
}
qt_config->endArray();
- ReadBasicSetting(Settings::values.touch_device);
- ReadBasicSetting(Settings::values.touch_from_button_map_index);
Settings::values.touch_from_button_map_index = std::clamp(
Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
- ReadBasicSetting(Settings::values.udp_input_servers);
- ReadBasicSetting(Settings::values.enable_udp_controller);
}
void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- ReadGlobalSetting(Settings::values.use_multi_core);
- ReadGlobalSetting(Settings::values.use_unsafe_extended_memory_layout);
+ ReadCategory(Settings::Category::Core);
qt_config->endGroup();
}
@@ -561,7 +440,6 @@ void Config::ReadCoreValues() {
void Config::ReadDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
- ReadBasicSetting(Settings::values.use_virtual_sd);
FS::SetYuzuPath(
FS::YuzuPath::NANDDir,
qt_config
@@ -597,9 +475,7 @@ void Config::ReadDataStorageValues() {
.toString()
.toStdString());
- ReadBasicSetting(Settings::values.gamecard_inserted);
- ReadBasicSetting(Settings::values.gamecard_current_game);
- ReadBasicSetting(Settings::values.gamecard_path);
+ ReadCategory(Settings::Category::DataStorage);
qt_config->endGroup();
}
@@ -611,29 +487,17 @@ void Config::ReadDebuggingValues() {
Settings::values.record_frame_times =
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
- ReadBasicSetting(Settings::values.use_gdbstub);
- ReadBasicSetting(Settings::values.gdbstub_port);
- ReadBasicSetting(Settings::values.program_args);
- ReadBasicSetting(Settings::values.dump_exefs);
- ReadBasicSetting(Settings::values.dump_nso);
- ReadBasicSetting(Settings::values.enable_fs_access_log);
- ReadBasicSetting(Settings::values.reporting_services);
- ReadBasicSetting(Settings::values.quest_flag);
- ReadBasicSetting(Settings::values.disable_macro_jit);
- ReadBasicSetting(Settings::values.disable_macro_hle);
- ReadBasicSetting(Settings::values.extended_logging);
- ReadBasicSetting(Settings::values.use_debug_asserts);
- ReadBasicSetting(Settings::values.use_auto_stub);
- ReadBasicSetting(Settings::values.enable_all_controllers);
- ReadBasicSetting(Settings::values.create_crash_dumps);
- ReadBasicSetting(Settings::values.perform_vulkan_check);
+ ReadCategory(Settings::Category::Debugging);
+ ReadCategory(Settings::Category::DebuggingGraphics);
qt_config->endGroup();
}
void Config::ReadServiceValues() {
qt_config->beginGroup(QStringLiteral("Services"));
- ReadBasicSetting(Settings::values.network_interface);
+
+ ReadCategory(Settings::Category::Services);
+
qt_config->endGroup();
}
@@ -659,8 +523,7 @@ void Config::ReadDisabledAddOnValues() {
void Config::ReadMiscellaneousValues() {
qt_config->beginGroup(QStringLiteral("Miscellaneous"));
- ReadBasicSetting(Settings::values.log_filter);
- ReadBasicSetting(Settings::values.use_dev_keys);
+ ReadCategory(Settings::Category::Miscellaneous);
qt_config->endGroup();
}
@@ -710,36 +573,9 @@ void Config::ReadPathValues() {
void Config::ReadCpuValues() {
qt_config->beginGroup(QStringLiteral("Cpu"));
- ReadBasicSetting(Settings::values.cpu_accuracy_first_time);
- if (Settings::values.cpu_accuracy_first_time) {
- Settings::values.cpu_accuracy.SetValue(Settings::values.cpu_accuracy.GetDefault());
- Settings::values.cpu_accuracy_first_time.SetValue(false);
- } else {
- ReadGlobalSetting(Settings::values.cpu_accuracy);
- }
-
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_unfuse_fma);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_reduce_fp_error);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_inaccurate_nan);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_fastmem_check);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_ignore_global_monitor);
-
- if (global) {
- ReadBasicSetting(Settings::values.cpu_debug_mode);
- ReadBasicSetting(Settings::values.cpuopt_page_tables);
- ReadBasicSetting(Settings::values.cpuopt_block_linking);
- ReadBasicSetting(Settings::values.cpuopt_return_stack_buffer);
- ReadBasicSetting(Settings::values.cpuopt_fast_dispatcher);
- ReadBasicSetting(Settings::values.cpuopt_context_elimination);
- ReadBasicSetting(Settings::values.cpuopt_const_prop);
- ReadBasicSetting(Settings::values.cpuopt_misc_ir);
- ReadBasicSetting(Settings::values.cpuopt_reduce_misalign_checks);
- ReadBasicSetting(Settings::values.cpuopt_fastmem);
- ReadBasicSetting(Settings::values.cpuopt_fastmem_exclusives);
- ReadBasicSetting(Settings::values.cpuopt_recompile_exclusives);
- ReadBasicSetting(Settings::values.cpuopt_ignore_memory_aborts);
- }
+ ReadCategory(Settings::Category::Cpu);
+ ReadCategory(Settings::Category::CpuDebug);
+ ReadCategory(Settings::Category::CpuUnsafe);
qt_config->endGroup();
}
@@ -747,47 +583,9 @@ void Config::ReadCpuValues() {
void Config::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
- ReadGlobalSetting(Settings::values.renderer_backend);
- ReadGlobalSetting(Settings::values.async_presentation);
- ReadGlobalSetting(Settings::values.renderer_force_max_clock);
- ReadGlobalSetting(Settings::values.vulkan_device);
- ReadGlobalSetting(Settings::values.fullscreen_mode);
- ReadGlobalSetting(Settings::values.aspect_ratio);
- ReadGlobalSetting(Settings::values.resolution_setup);
- ReadGlobalSetting(Settings::values.scaling_filter);
- ReadGlobalSetting(Settings::values.fsr_sharpening_slider);
- ReadGlobalSetting(Settings::values.anti_aliasing);
- ReadGlobalSetting(Settings::values.max_anisotropy);
- ReadGlobalSetting(Settings::values.speed_limit);
- ReadGlobalSetting(Settings::values.use_disk_shader_cache);
- ReadGlobalSetting(Settings::values.gpu_accuracy);
- ReadGlobalSetting(Settings::values.use_asynchronous_gpu_emulation);
- ReadGlobalSetting(Settings::values.nvdec_emulation);
- ReadGlobalSetting(Settings::values.accelerate_astc);
- ReadGlobalSetting(Settings::values.async_astc);
- ReadGlobalSetting(Settings::values.astc_recompression);
- ReadGlobalSetting(Settings::values.use_reactive_flushing);
- ReadGlobalSetting(Settings::values.shader_backend);
- ReadGlobalSetting(Settings::values.use_asynchronous_shaders);
- ReadGlobalSetting(Settings::values.use_fast_gpu_time);
- ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
- ReadGlobalSetting(Settings::values.enable_compute_pipelines);
- ReadGlobalSetting(Settings::values.use_video_framerate);
- ReadGlobalSetting(Settings::values.barrier_feedback_loops);
- ReadGlobalSetting(Settings::values.bg_red);
- ReadGlobalSetting(Settings::values.bg_green);
- ReadGlobalSetting(Settings::values.bg_blue);
-
- if (global) {
- Settings::values.vsync_mode.SetValue(static_cast<Settings::VSyncMode>(
- ReadSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()),
- static_cast<u32>(Settings::values.vsync_mode.GetDefault()))
- .value<u32>()));
- ReadBasicSetting(Settings::values.renderer_debug);
- ReadBasicSetting(Settings::values.renderer_shader_feedback);
- ReadBasicSetting(Settings::values.enable_nsight_aftermath);
- ReadBasicSetting(Settings::values.disable_shader_loop_safety_checks);
- }
+ ReadCategory(Settings::Category::Renderer);
+ ReadCategory(Settings::Category::RendererAdvanced);
+ ReadCategory(Settings::Category::RendererDebug);
qt_config->endGroup();
}
@@ -795,8 +593,7 @@ void Config::ReadRendererValues() {
void Config::ReadScreenshotValues() {
qt_config->beginGroup(QStringLiteral("Screenshots"));
- UISettings::values.enable_screenshot_save_as =
- ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool();
+ ReadCategory(Settings::Category::Screenshots);
FS::SetYuzuPath(
FS::YuzuPath::ScreenshotsDir,
qt_config
@@ -834,41 +631,8 @@ void Config::ReadShortcutValues() {
void Config::ReadSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
- ReadGlobalSetting(Settings::values.language_index);
-
- ReadGlobalSetting(Settings::values.region_index);
-
- ReadGlobalSetting(Settings::values.time_zone_index);
-
- bool rng_seed_enabled;
- ReadSettingGlobal(rng_seed_enabled, QStringLiteral("rng_seed_enabled"), false);
- bool rng_seed_global =
- global || qt_config->value(QStringLiteral("rng_seed/use_global"), true).toBool();
- Settings::values.rng_seed.SetGlobal(rng_seed_global);
- if (global || !rng_seed_global) {
- if (rng_seed_enabled) {
- Settings::values.rng_seed.SetValue(ReadSetting(QStringLiteral("rng_seed"), 0).toUInt());
- } else {
- Settings::values.rng_seed.SetValue(std::nullopt);
- }
- }
-
- if (global) {
- ReadBasicSetting(Settings::values.current_user);
- Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
- Service::Account::MAX_USERS - 1);
-
- const auto custom_rtc_enabled =
- ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool();
- if (custom_rtc_enabled) {
- Settings::values.custom_rtc = ReadSetting(QStringLiteral("custom_rtc"), 0).toLongLong();
- } else {
- Settings::values.custom_rtc = std::nullopt;
- }
- ReadBasicSetting(Settings::values.device_name);
- }
-
- ReadGlobalSetting(Settings::values.sound_index);
+ ReadCategory(Settings::Category::System);
+ ReadCategory(Settings::Category::SystemAudio);
qt_config->endGroup();
}
@@ -881,8 +645,6 @@ void Config::ReadUIValues() {
QStringLiteral("theme"),
QString::fromUtf8(UISettings::themes[static_cast<size_t>(default_theme)].second))
.toString();
- ReadBasicSetting(UISettings::values.enable_discord_presence);
- ReadBasicSetting(UISettings::values.select_user_on_boot);
ReadUIGamelistValues();
ReadUILayoutValues();
@@ -891,20 +653,8 @@ void Config::ReadUIValues() {
ReadShortcutValues();
ReadMultiplayerValues();
- ReadBasicSetting(UISettings::values.single_window_mode);
- ReadBasicSetting(UISettings::values.fullscreen);
- ReadBasicSetting(UISettings::values.display_titlebar);
- ReadBasicSetting(UISettings::values.show_filter_bar);
- ReadBasicSetting(UISettings::values.show_status_bar);
- ReadBasicSetting(UISettings::values.confirm_before_closing);
- ReadBasicSetting(UISettings::values.first_start);
- ReadBasicSetting(UISettings::values.callout_flags);
- ReadBasicSetting(UISettings::values.show_console);
- ReadBasicSetting(UISettings::values.pause_when_in_background);
- ReadBasicSetting(UISettings::values.mute_when_in_background);
- ReadBasicSetting(UISettings::values.hide_mouse);
- ReadBasicSetting(UISettings::values.controller_applet_disabled);
- ReadBasicSetting(UISettings::values.disable_web_applet);
+ ReadCategory(Settings::Category::Ui);
+ ReadCategory(Settings::Category::UiGeneral);
qt_config->endGroup();
}
@@ -912,16 +662,8 @@ void Config::ReadUIValues() {
void Config::ReadUIGamelistValues() {
qt_config->beginGroup(QStringLiteral("UIGameList"));
- ReadBasicSetting(UISettings::values.show_add_ons);
- ReadBasicSetting(UISettings::values.show_compat);
- ReadBasicSetting(UISettings::values.show_size);
- ReadBasicSetting(UISettings::values.show_types);
- ReadBasicSetting(UISettings::values.game_icon_size);
- ReadBasicSetting(UISettings::values.folder_icon_size);
- ReadBasicSetting(UISettings::values.row_1_text_id);
- ReadBasicSetting(UISettings::values.row_2_text_id);
- ReadBasicSetting(UISettings::values.cache_game_list);
- ReadBasicSetting(UISettings::values.favorites_expanded);
+ ReadCategory(Settings::Category::UiGameList);
+
const int favorites_size = qt_config->beginReadArray(QStringLiteral("favorites"));
for (int i = 0; i < favorites_size; i++) {
qt_config->setArrayIndex(i);
@@ -944,7 +686,8 @@ void Config::ReadUILayoutValues() {
ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
UISettings::values.microprofile_geometry =
ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
- ReadBasicSetting(UISettings::values.microprofile_visible);
+
+ ReadCategory(Settings::Category::UiLayout);
qt_config->endGroup();
}
@@ -952,10 +695,7 @@ void Config::ReadUILayoutValues() {
void Config::ReadWebServiceValues() {
qt_config->beginGroup(QStringLiteral("WebService"));
- ReadBasicSetting(Settings::values.enable_telemetry);
- ReadBasicSetting(Settings::values.web_api_url);
- ReadBasicSetting(Settings::values.yuzu_username);
- ReadBasicSetting(Settings::values.yuzu_token);
+ ReadCategory(Settings::Category::WebService);
qt_config->endGroup();
}
@@ -963,17 +703,7 @@ void Config::ReadWebServiceValues() {
void Config::ReadMultiplayerValues() {
qt_config->beginGroup(QStringLiteral("Multiplayer"));
- ReadBasicSetting(UISettings::values.multiplayer_nickname);
- ReadBasicSetting(UISettings::values.multiplayer_ip);
- ReadBasicSetting(UISettings::values.multiplayer_port);
- ReadBasicSetting(UISettings::values.multiplayer_room_nickname);
- ReadBasicSetting(UISettings::values.multiplayer_room_name);
- ReadBasicSetting(UISettings::values.multiplayer_room_port);
- ReadBasicSetting(UISettings::values.multiplayer_host_type);
- ReadBasicSetting(UISettings::values.multiplayer_port);
- ReadBasicSetting(UISettings::values.multiplayer_max_player);
- ReadBasicSetting(UISettings::values.multiplayer_game_id);
- ReadBasicSetting(UISettings::values.multiplayer_room_description);
+ ReadCategory(Settings::Category::Multiplayer);
// Read ban list back
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
@@ -996,11 +726,20 @@ void Config::ReadMultiplayerValues() {
qt_config->endGroup();
}
+void Config::ReadNetworkValues() {
+ qt_config->beginGroup(QString::fromStdString("Services"));
+
+ ReadCategory(Settings::Category::Network);
+
+ qt_config->endGroup();
+}
+
void Config::ReadValues() {
if (global) {
ReadDataStorageValues();
ReadDebuggingValues();
ReadDisabledAddOnValues();
+ ReadNetworkValues();
ReadServiceValues();
ReadUIValues();
ReadWebServiceValues();
@@ -1077,14 +816,9 @@ void Config::SavePlayerValue(std::size_t player_index) {
QString::fromStdString(player.motions[i]),
QString::fromStdString(default_param));
}
-
- if (player_index == 0) {
- SaveMousePanningValues();
- }
}
void Config::SaveDebugValues() {
- WriteBasicSetting(Settings::values.debug_pad_enabled);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(QStringLiteral("debug_pad_") +
@@ -1103,10 +837,6 @@ void Config::SaveDebugValues() {
}
}
-void Config::SaveMouseValues() {
- WriteBasicSetting(Settings::values.mouse_enabled);
-}
-
void Config::SaveTouchscreenValues() {
const auto& touchscreen = Settings::values.touchscreen;
@@ -1117,21 +847,7 @@ void Config::SaveTouchscreenValues() {
WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
}
-void Config::SaveMousePanningValues() {
- // Don't overwrite values.mouse_panning
- WriteBasicSetting(Settings::values.mouse_panning_x_sensitivity);
- WriteBasicSetting(Settings::values.mouse_panning_y_sensitivity);
- WriteBasicSetting(Settings::values.mouse_panning_deadzone_counterweight);
- WriteBasicSetting(Settings::values.mouse_panning_decay_strength);
- WriteBasicSetting(Settings::values.mouse_panning_min_decay);
-}
-
void Config::SaveMotionTouchValues() {
- WriteBasicSetting(Settings::values.touch_device);
- WriteBasicSetting(Settings::values.touch_from_button_map_index);
- WriteBasicSetting(Settings::values.udp_input_servers);
- WriteBasicSetting(Settings::values.enable_udp_controller);
-
qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
qt_config->setArrayIndex(static_cast<int>(p));
@@ -1152,8 +868,6 @@ void Config::SaveMotionTouchValues() {
}
void Config::SaveHidbusValues() {
- WriteBasicSetting(Settings::values.enable_ring_controller);
-
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
WriteSetting(QStringLiteral("ring_controller"),
@@ -1161,11 +875,6 @@ void Config::SaveHidbusValues() {
QString::fromStdString(default_param));
}
-void Config::SaveIrCameraValues() {
- WriteBasicSetting(Settings::values.enable_ir_sensor);
- WriteBasicSetting(Settings::values.ir_sensor_device);
-}
-
void Config::SaveValues() {
if (global) {
SaveDataStorageValues();
@@ -1182,18 +891,14 @@ void Config::SaveValues() {
SaveRendererValues();
SaveAudioValues();
SaveSystemValues();
+
qt_config->sync();
}
void Config::SaveAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
- if (global) {
- WriteBasicSetting(Settings::values.sink_id);
- WriteBasicSetting(Settings::values.audio_output_device_id);
- WriteBasicSetting(Settings::values.audio_input_device_id);
- }
- WriteGlobalSetting(Settings::values.volume);
+ WriteCategory(Settings::Category::Audio);
qt_config->endGroup();
}
@@ -1201,6 +906,8 @@ void Config::SaveAudioValues() {
void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
+ WriteCategory(Settings::Category::Controls);
+
Settings::values.players.SetGlobal(!IsCustomConfig());
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
SavePlayerValue(p);
@@ -1210,28 +917,9 @@ void Config::SaveControlValues() {
return;
}
SaveDebugValues();
- SaveMouseValues();
SaveTouchscreenValues();
- SaveMousePanningValues();
SaveMotionTouchValues();
SaveHidbusValues();
- SaveIrCameraValues();
-
- WriteGlobalSetting(Settings::values.use_docked_mode);
- WriteGlobalSetting(Settings::values.vibration_enabled);
- WriteGlobalSetting(Settings::values.enable_accurate_vibrations);
- WriteGlobalSetting(Settings::values.motion_enabled);
- WriteBasicSetting(Settings::values.enable_raw_input);
- WriteBasicSetting(Settings::values.enable_joycon_driver);
- WriteBasicSetting(Settings::values.enable_procon_driver);
- WriteBasicSetting(Settings::values.random_amiibo_id);
- WriteBasicSetting(Settings::values.keyboard_enabled);
- WriteBasicSetting(Settings::values.emulate_analog_keyboard);
- WriteBasicSetting(Settings::values.controller_navigation);
-
- WriteBasicSetting(Settings::values.tas_enable);
- WriteBasicSetting(Settings::values.tas_loop);
- WriteBasicSetting(Settings::values.pause_tas_on_load);
qt_config->endGroup();
}
@@ -1239,8 +927,7 @@ void Config::SaveControlValues() {
void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- WriteGlobalSetting(Settings::values.use_multi_core);
- WriteGlobalSetting(Settings::values.use_unsafe_extended_memory_layout);
+ WriteCategory(Settings::Category::Core);
qt_config->endGroup();
}
@@ -1248,7 +935,6 @@ void Config::SaveCoreValues() {
void Config::SaveDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
- WriteBasicSetting(Settings::values.use_virtual_sd);
WriteSetting(QStringLiteral("nand_directory"),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
@@ -1265,9 +951,7 @@ void Config::SaveDataStorageValues() {
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
- WriteBasicSetting(Settings::values.gamecard_inserted);
- WriteBasicSetting(Settings::values.gamecard_current_game);
- WriteBasicSetting(Settings::values.gamecard_path);
+ WriteCategory(Settings::Category::DataStorage);
qt_config->endGroup();
}
@@ -1277,19 +961,9 @@ void Config::SaveDebuggingValues() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
- WriteBasicSetting(Settings::values.use_gdbstub);
- WriteBasicSetting(Settings::values.gdbstub_port);
- WriteBasicSetting(Settings::values.program_args);
- WriteBasicSetting(Settings::values.dump_exefs);
- WriteBasicSetting(Settings::values.dump_nso);
- WriteBasicSetting(Settings::values.enable_fs_access_log);
- WriteBasicSetting(Settings::values.quest_flag);
- WriteBasicSetting(Settings::values.use_debug_asserts);
- WriteBasicSetting(Settings::values.disable_macro_jit);
- WriteBasicSetting(Settings::values.disable_macro_hle);
- WriteBasicSetting(Settings::values.enable_all_controllers);
- WriteBasicSetting(Settings::values.create_crash_dumps);
- WriteBasicSetting(Settings::values.perform_vulkan_check);
+
+ WriteCategory(Settings::Category::Debugging);
+ WriteCategory(Settings::Category::DebuggingGraphics);
qt_config->endGroup();
}
@@ -1297,7 +971,7 @@ void Config::SaveDebuggingValues() {
void Config::SaveNetworkValues() {
qt_config->beginGroup(QStringLiteral("Services"));
- WriteBasicSetting(Settings::values.network_interface);
+ WriteCategory(Settings::Category::Network);
qt_config->endGroup();
}
@@ -1324,8 +998,7 @@ void Config::SaveDisabledAddOnValues() {
void Config::SaveMiscellaneousValues() {
qt_config->beginGroup(QStringLiteral("Miscellaneous"));
- WriteBasicSetting(Settings::values.log_filter);
- WriteBasicSetting(Settings::values.use_dev_keys);
+ WriteCategory(Settings::Category::Miscellaneous);
qt_config->endGroup();
}
@@ -1353,34 +1026,9 @@ void Config::SavePathValues() {
void Config::SaveCpuValues() {
qt_config->beginGroup(QStringLiteral("Cpu"));
- WriteBasicSetting(Settings::values.cpu_accuracy_first_time);
- WriteSetting(QStringLiteral("cpu_accuracy"),
- static_cast<u32>(Settings::values.cpu_accuracy.GetValue(global)),
- static_cast<u32>(Settings::values.cpu_accuracy.GetDefault()),
- Settings::values.cpu_accuracy.UsingGlobal());
-
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_unfuse_fma);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_reduce_fp_error);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_inaccurate_nan);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_fastmem_check);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_ignore_global_monitor);
-
- if (global) {
- WriteBasicSetting(Settings::values.cpu_debug_mode);
- WriteBasicSetting(Settings::values.cpuopt_page_tables);
- WriteBasicSetting(Settings::values.cpuopt_block_linking);
- WriteBasicSetting(Settings::values.cpuopt_return_stack_buffer);
- WriteBasicSetting(Settings::values.cpuopt_fast_dispatcher);
- WriteBasicSetting(Settings::values.cpuopt_context_elimination);
- WriteBasicSetting(Settings::values.cpuopt_const_prop);
- WriteBasicSetting(Settings::values.cpuopt_misc_ir);
- WriteBasicSetting(Settings::values.cpuopt_reduce_misalign_checks);
- WriteBasicSetting(Settings::values.cpuopt_fastmem);
- WriteBasicSetting(Settings::values.cpuopt_fastmem_exclusives);
- WriteBasicSetting(Settings::values.cpuopt_recompile_exclusives);
- WriteBasicSetting(Settings::values.cpuopt_ignore_memory_aborts);
- }
+ WriteCategory(Settings::Category::Cpu);
+ WriteCategory(Settings::Category::CpuDebug);
+ WriteCategory(Settings::Category::CpuUnsafe);
qt_config->endGroup();
}
@@ -1388,76 +1036,9 @@ void Config::SaveCpuValues() {
void Config::SaveRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
- WriteSetting(QString::fromStdString(Settings::values.renderer_backend.GetLabel()),
- static_cast<u32>(Settings::values.renderer_backend.GetValue(global)),
- static_cast<u32>(Settings::values.renderer_backend.GetDefault()),
- Settings::values.renderer_backend.UsingGlobal());
- WriteGlobalSetting(Settings::values.async_presentation);
- WriteGlobalSetting(Settings::values.renderer_force_max_clock);
- WriteGlobalSetting(Settings::values.vulkan_device);
- WriteSetting(QString::fromStdString(Settings::values.fullscreen_mode.GetLabel()),
- static_cast<u32>(Settings::values.fullscreen_mode.GetValue(global)),
- static_cast<u32>(Settings::values.fullscreen_mode.GetDefault()),
- Settings::values.fullscreen_mode.UsingGlobal());
- WriteGlobalSetting(Settings::values.aspect_ratio);
- WriteSetting(QString::fromStdString(Settings::values.resolution_setup.GetLabel()),
- static_cast<u32>(Settings::values.resolution_setup.GetValue(global)),
- static_cast<u32>(Settings::values.resolution_setup.GetDefault()),
- Settings::values.resolution_setup.UsingGlobal());
- WriteSetting(QString::fromStdString(Settings::values.scaling_filter.GetLabel()),
- static_cast<u32>(Settings::values.scaling_filter.GetValue(global)),
- static_cast<u32>(Settings::values.scaling_filter.GetDefault()),
- Settings::values.scaling_filter.UsingGlobal());
- WriteSetting(QString::fromStdString(Settings::values.fsr_sharpening_slider.GetLabel()),
- static_cast<u32>(Settings::values.fsr_sharpening_slider.GetValue(global)),
- static_cast<u32>(Settings::values.fsr_sharpening_slider.GetDefault()),
- Settings::values.fsr_sharpening_slider.UsingGlobal());
- WriteSetting(QString::fromStdString(Settings::values.anti_aliasing.GetLabel()),
- static_cast<u32>(Settings::values.anti_aliasing.GetValue(global)),
- static_cast<u32>(Settings::values.anti_aliasing.GetDefault()),
- Settings::values.anti_aliasing.UsingGlobal());
- WriteGlobalSetting(Settings::values.max_anisotropy);
- WriteGlobalSetting(Settings::values.speed_limit);
- WriteGlobalSetting(Settings::values.use_disk_shader_cache);
- WriteSetting(QString::fromStdString(Settings::values.gpu_accuracy.GetLabel()),
- static_cast<u32>(Settings::values.gpu_accuracy.GetValue(global)),
- static_cast<u32>(Settings::values.gpu_accuracy.GetDefault()),
- Settings::values.gpu_accuracy.UsingGlobal());
- WriteGlobalSetting(Settings::values.use_asynchronous_gpu_emulation);
- WriteSetting(QString::fromStdString(Settings::values.nvdec_emulation.GetLabel()),
- static_cast<u32>(Settings::values.nvdec_emulation.GetValue(global)),
- static_cast<u32>(Settings::values.nvdec_emulation.GetDefault()),
- Settings::values.nvdec_emulation.UsingGlobal());
- WriteGlobalSetting(Settings::values.accelerate_astc);
- WriteGlobalSetting(Settings::values.async_astc);
- WriteSetting(QString::fromStdString(Settings::values.astc_recompression.GetLabel()),
- static_cast<u32>(Settings::values.astc_recompression.GetValue(global)),
- static_cast<u32>(Settings::values.astc_recompression.GetDefault()),
- Settings::values.astc_recompression.UsingGlobal());
- WriteGlobalSetting(Settings::values.use_reactive_flushing);
- WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()),
- static_cast<u32>(Settings::values.shader_backend.GetValue(global)),
- static_cast<u32>(Settings::values.shader_backend.GetDefault()),
- Settings::values.shader_backend.UsingGlobal());
- WriteGlobalSetting(Settings::values.use_asynchronous_shaders);
- WriteGlobalSetting(Settings::values.use_fast_gpu_time);
- WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
- WriteGlobalSetting(Settings::values.enable_compute_pipelines);
- WriteGlobalSetting(Settings::values.use_video_framerate);
- WriteGlobalSetting(Settings::values.barrier_feedback_loops);
- WriteGlobalSetting(Settings::values.bg_red);
- WriteGlobalSetting(Settings::values.bg_green);
- WriteGlobalSetting(Settings::values.bg_blue);
-
- if (global) {
- WriteSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()),
- static_cast<u32>(Settings::values.vsync_mode.GetValue()),
- static_cast<u32>(Settings::values.vsync_mode.GetDefault()));
- WriteBasicSetting(Settings::values.renderer_debug);
- WriteBasicSetting(Settings::values.renderer_shader_feedback);
- WriteBasicSetting(Settings::values.enable_nsight_aftermath);
- WriteBasicSetting(Settings::values.disable_shader_loop_safety_checks);
- }
+ WriteCategory(Settings::Category::Renderer);
+ WriteCategory(Settings::Category::RendererAdvanced);
+ WriteCategory(Settings::Category::RendererDebug);
qt_config->endGroup();
}
@@ -1465,9 +1046,9 @@ void Config::SaveRendererValues() {
void Config::SaveScreenshotValues() {
qt_config->beginGroup(QStringLiteral("Screenshots"));
- WriteBasicSetting(UISettings::values.enable_screenshot_save_as);
WriteSetting(QStringLiteral("screenshot_path"),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)));
+ WriteCategory(Settings::Category::Screenshots);
qt_config->endGroup();
}
@@ -1498,27 +1079,8 @@ void Config::SaveShortcutValues() {
void Config::SaveSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
- WriteGlobalSetting(Settings::values.language_index);
- WriteGlobalSetting(Settings::values.region_index);
- WriteGlobalSetting(Settings::values.time_zone_index);
-
- WriteSetting(QStringLiteral("rng_seed_enabled"),
- Settings::values.rng_seed.GetValue(global).has_value(), false,
- Settings::values.rng_seed.UsingGlobal());
- WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.GetValue(global).value_or(0),
- 0, Settings::values.rng_seed.UsingGlobal());
-
- if (global) {
- WriteBasicSetting(Settings::values.current_user);
-
- WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(),
- false);
- WriteSetting(QStringLiteral("custom_rtc"),
- QVariant::fromValue<long long>(Settings::values.custom_rtc.value_or(0)), 0);
- WriteBasicSetting(Settings::values.device_name);
- }
-
- WriteGlobalSetting(Settings::values.sound_index);
+ WriteCategory(Settings::Category::System);
+ WriteCategory(Settings::Category::SystemAudio);
qt_config->endGroup();
}
@@ -1526,10 +1088,11 @@ void Config::SaveSystemValues() {
void Config::SaveUIValues() {
qt_config->beginGroup(QStringLiteral("UI"));
+ WriteCategory(Settings::Category::Ui);
+ WriteCategory(Settings::Category::UiGeneral);
+
WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
QString::fromUtf8(UISettings::themes[static_cast<size_t>(default_theme)].second));
- WriteBasicSetting(UISettings::values.enable_discord_presence);
- WriteBasicSetting(UISettings::values.select_user_on_boot);
SaveUIGamelistValues();
SaveUILayoutValues();
@@ -1538,37 +1101,14 @@ void Config::SaveUIValues() {
SaveShortcutValues();
SaveMultiplayerValues();
- WriteBasicSetting(UISettings::values.single_window_mode);
- WriteBasicSetting(UISettings::values.fullscreen);
- WriteBasicSetting(UISettings::values.display_titlebar);
- WriteBasicSetting(UISettings::values.show_filter_bar);
- WriteBasicSetting(UISettings::values.show_status_bar);
- WriteBasicSetting(UISettings::values.confirm_before_closing);
- WriteBasicSetting(UISettings::values.first_start);
- WriteBasicSetting(UISettings::values.callout_flags);
- WriteBasicSetting(UISettings::values.show_console);
- WriteBasicSetting(UISettings::values.pause_when_in_background);
- WriteBasicSetting(UISettings::values.mute_when_in_background);
- WriteBasicSetting(UISettings::values.hide_mouse);
- WriteBasicSetting(UISettings::values.controller_applet_disabled);
- WriteBasicSetting(UISettings::values.disable_web_applet);
-
qt_config->endGroup();
}
void Config::SaveUIGamelistValues() {
qt_config->beginGroup(QStringLiteral("UIGameList"));
- WriteBasicSetting(UISettings::values.show_add_ons);
- WriteBasicSetting(UISettings::values.show_compat);
- WriteBasicSetting(UISettings::values.show_size);
- WriteBasicSetting(UISettings::values.show_types);
- WriteBasicSetting(UISettings::values.game_icon_size);
- WriteBasicSetting(UISettings::values.folder_icon_size);
- WriteBasicSetting(UISettings::values.row_1_text_id);
- WriteBasicSetting(UISettings::values.row_2_text_id);
- WriteBasicSetting(UISettings::values.cache_game_list);
- WriteBasicSetting(UISettings::values.favorites_expanded);
+ WriteCategory(Settings::Category::UiGameList);
+
qt_config->beginWriteArray(QStringLiteral("favorites"));
for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
qt_config->setArrayIndex(i);
@@ -1589,7 +1129,8 @@ void Config::SaveUILayoutValues() {
WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
WriteSetting(QStringLiteral("microProfileDialogGeometry"),
UISettings::values.microprofile_geometry);
- WriteBasicSetting(UISettings::values.microprofile_visible);
+
+ WriteCategory(Settings::Category::UiLayout);
qt_config->endGroup();
}
@@ -1597,10 +1138,7 @@ void Config::SaveUILayoutValues() {
void Config::SaveWebServiceValues() {
qt_config->beginGroup(QStringLiteral("WebService"));
- WriteBasicSetting(Settings::values.enable_telemetry);
- WriteBasicSetting(Settings::values.web_api_url);
- WriteBasicSetting(Settings::values.yuzu_username);
- WriteBasicSetting(Settings::values.yuzu_token);
+ WriteCategory(Settings::Category::WebService);
qt_config->endGroup();
}
@@ -1608,17 +1146,7 @@ void Config::SaveWebServiceValues() {
void Config::SaveMultiplayerValues() {
qt_config->beginGroup(QStringLiteral("Multiplayer"));
- WriteBasicSetting(UISettings::values.multiplayer_nickname);
- WriteBasicSetting(UISettings::values.multiplayer_ip);
- WriteBasicSetting(UISettings::values.multiplayer_port);
- WriteBasicSetting(UISettings::values.multiplayer_room_nickname);
- WriteBasicSetting(UISettings::values.multiplayer_room_name);
- WriteBasicSetting(UISettings::values.multiplayer_room_port);
- WriteBasicSetting(UISettings::values.multiplayer_host_type);
- WriteBasicSetting(UISettings::values.multiplayer_port);
- WriteBasicSetting(UISettings::values.multiplayer_max_player);
- WriteBasicSetting(UISettings::values.multiplayer_game_id);
- WriteBasicSetting(UISettings::values.multiplayer_room_description);
+ WriteCategory(Settings::Category::Multiplayer);
// Write ban list
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
@@ -1653,27 +1181,6 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)
return result;
}
-template <typename Type, bool ranged>
-void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting) {
- QString name = QString::fromStdString(setting.GetLabel());
- const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
- setting.SetGlobal(use_global);
- if (global || !use_global) {
- setting.SetValue(static_cast<QVariant>(
- ReadSetting(name, QVariant::fromValue<Type>(setting.GetDefault())))
- .value<Type>());
- }
-}
-
-template <typename Type>
-void Config::ReadSettingGlobal(Type& setting, const QString& name,
- const QVariant& default_value) const {
- const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
- if (global || !use_global) {
- setting = ReadSetting(name, default_value).value<Type>();
- }
-}
-
void Config::WriteSetting(const QString& name, const QVariant& value) {
qt_config->setValue(name, value);
}
@@ -1727,3 +1234,72 @@ void Config::ClearControlPlayerValues() {
const std::string& Config::GetConfigFilePath() const {
return qt_config_loc;
}
+
+static auto FindRelevantList(Settings::Category category) {
+ auto& map = Settings::values.linkage.by_category;
+ if (map.contains(category)) {
+ return Settings::values.linkage.by_category[category];
+ }
+ return UISettings::values.linkage.by_category[category];
+}
+
+void Config::ReadCategory(Settings::Category category) {
+ const auto& settings = FindRelevantList(category);
+ std::for_each(settings.begin(), settings.end(),
+ [&](const auto& setting) { ReadSettingGeneric(setting); });
+}
+
+void Config::WriteCategory(Settings::Category category) {
+ const auto& settings = FindRelevantList(category);
+ std::for_each(settings.begin(), settings.end(),
+ [&](const auto& setting) { WriteSettingGeneric(setting); });
+}
+
+void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) {
+ if (!setting->Save() || (!setting->Switchable() && !global)) {
+ return;
+ }
+ const QString name = QString::fromStdString(setting->GetLabel());
+ const auto default_value =
+ QVariant::fromValue<QString>(QString::fromStdString(setting->DefaultToString()));
+
+ bool use_global = true;
+ if (setting->Switchable() && !global) {
+ use_global = qt_config->value(name + QStringLiteral("/use_global"), true).value<bool>();
+ setting->SetGlobal(use_global);
+ }
+
+ if (global || !use_global) {
+ const bool is_default =
+ qt_config->value(name + QStringLiteral("/default"), true).value<bool>();
+ if (!is_default) {
+ setting->LoadString(
+ qt_config->value(name, default_value).value<QString>().toStdString());
+ } else {
+ // Empty string resets the Setting to default
+ setting->LoadString("");
+ }
+ }
+}
+
+void Config::WriteSettingGeneric(Settings::BasicSetting* const setting) const {
+ if (!setting->Save()) {
+ return;
+ }
+ const QVariant value = QVariant::fromValue(QString::fromStdString(setting->ToString()));
+ const QVariant default_value =
+ QVariant::fromValue(QString::fromStdString(setting->DefaultToString()));
+ const QString label = QString::fromStdString(setting->GetLabel());
+ if (setting->Switchable()) {
+ if (!global) {
+ qt_config->setValue(label + QStringLiteral("/use_global"), setting->UsingGlobal());
+ }
+ if (global || !setting->UsingGlobal()) {
+ qt_config->setValue(label + QStringLiteral("/default"), value == default_value);
+ qt_config->setValue(label, value);
+ }
+ } else if (global) {
+ qt_config->setValue(label + QStringLiteral("/default"), value == default_value);
+ qt_config->setValue(label, value);
+ }
+}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 1211389d2..727feebfb 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -9,6 +9,7 @@
#include <QMetaType>
#include <QVariant>
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "yuzu/uisettings.h"
class QSettings;
@@ -51,8 +52,8 @@ public:
static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map;
static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map;
- static const std::map<bool, QString> use_docked_mode_texts_map;
- static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map;
+ static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map;
+ static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map;
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
@@ -74,7 +75,6 @@ private:
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
- void ReadMousePanningValues();
void ReadMotionTouchValues();
void ReadHidbusValues();
void ReadIrCameraValues();
@@ -99,13 +99,13 @@ private:
void ReadUILayoutValues();
void ReadWebServiceValues();
void ReadMultiplayerValues();
+ void ReadNetworkValues();
void SaveValues();
void SavePlayerValue(std::size_t player_index);
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
- void SaveMousePanningValues();
void SaveMotionTouchValues();
void SaveHidbusValues();
void SaveIrCameraValues();
@@ -141,18 +141,6 @@ private:
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
/**
- * Only reads a setting from the qt_config if the current config is a global config, or if the
- * current config is a custom config and the setting is overriding the global setting. Otherwise
- * it does nothing.
- *
- * @param setting The variable to be modified
- * @param name The setting's identifier
- * @param default_value The value to use when the setting is not already present in the config
- */
- template <typename Type>
- void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const;
-
- /**
* Writes a setting to the qt_config.
*
* @param name The setting's idetentifier
@@ -166,50 +154,20 @@ private:
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
bool use_global);
- /**
- * Reads a value from the qt_config and applies it to the setting, using its label and default
- * value. If the config is a custom config, this will also read the global state of the setting
- * and apply that information to it.
- *
- * @param The setting
- */
- template <typename Type, bool ranged>
- void ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting);
-
- /**
- * Sets a value to the qt_config using the setting's label and default value. If the config is a
- * custom config, it will apply the global state, and the custom value if needed.
- *
- * @param The setting
- */
- template <typename Type, bool ranged>
- void WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting);
-
- /**
- * Reads a value from the qt_config using the setting's label and default value and applies the
- * value to the setting.
- *
- * @param The setting
- */
- template <typename Type, bool ranged>
- void ReadBasicSetting(Settings::Setting<Type, ranged>& setting);
-
- /** Sets a value from the setting in the qt_config using the setting's label and default value.
- *
- * @param The setting
- */
- template <typename Type, bool ranged>
- void WriteBasicSetting(const Settings::Setting<Type, ranged>& setting);
+ void ReadCategory(Settings::Category category);
+ void WriteCategory(Settings::Category category);
+ void ReadSettingGeneric(Settings::BasicSetting* const setting);
+ void WriteSettingGeneric(Settings::BasicSetting* const setting) const;
- ConfigType type;
+ const ConfigType type;
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
- bool global;
+ const bool global;
};
// These metatype declarations cannot be in common/settings.h because core is devoid of QT
-Q_DECLARE_METATYPE(Settings::CPUAccuracy);
-Q_DECLARE_METATYPE(Settings::GPUAccuracy);
+Q_DECLARE_METATYPE(Settings::CpuAccuracy);
+Q_DECLARE_METATYPE(Settings::GpuAccuracy);
Q_DECLARE_METATYPE(Settings::FullscreenMode);
Q_DECLARE_METATYPE(Settings::NvdecEmulation);
Q_DECLARE_METATYPE(Settings::ResolutionSetup);
@@ -218,3 +176,4 @@ Q_DECLARE_METATYPE(Settings::AntiAliasing);
Q_DECLARE_METATYPE(Settings::RendererBackend);
Q_DECLARE_METATYPE(Settings::ShaderBackend);
Q_DECLARE_METATYPE(Settings::AstcRecompression);
+Q_DECLARE_METATYPE(Settings::AstcDecodeMode);
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp
index ac42cc7fc..0ed6146a0 100644
--- a/src/yuzu/configuration/configuration_shared.cpp
+++ b/src/yuzu/configuration/configuration_shared.cpp
@@ -1,104 +1,19 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <QCheckBox>
-#include <QObject>
-#include <QString>
-#include "common/settings.h"
+#include <memory>
+#include <type_traits>
+#include <vector>
#include "yuzu/configuration/configuration_shared.h"
-#include "yuzu/configuration/configure_per_game.h"
-void ConfigurationShared::ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting,
- const QCheckBox* checkbox,
- const CheckState& tracker) {
- if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
- setting->SetValue(checkbox->checkState());
- } else if (!Settings::IsConfiguringGlobal()) {
- if (tracker == CheckState::Global) {
- setting->SetGlobal(true);
- } else {
- setting->SetGlobal(false);
- setting->SetValue(checkbox->checkState());
- }
- }
-}
+namespace ConfigurationShared {
-void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
- const Settings::SwitchableSetting<bool>* setting) {
- if (setting->UsingGlobal()) {
- checkbox->setCheckState(Qt::PartiallyChecked);
- } else {
- checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked);
+Tab::Tab(std::shared_ptr<std::vector<Tab*>> group, QWidget* parent) : QWidget(parent) {
+ if (group != nullptr) {
+ group->push_back(this);
}
}
-void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
- if (highlighted) {
- widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")
- .arg(widget->objectName()));
- } else {
- widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }")
- .arg(widget->objectName()));
- }
- widget->show();
-}
+Tab::~Tab() = default;
-void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
- const Settings::SwitchableSetting<bool>& setting,
- CheckState& tracker) {
- if (setting.UsingGlobal()) {
- tracker = CheckState::Global;
- } else {
- tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off;
- }
- SetHighlight(checkbox, tracker != CheckState::Global);
- QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] {
- tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
- static_cast<int>(CheckState::Count));
- if (tracker == CheckState::Global) {
- checkbox->setChecked(setting.GetValue(true));
- }
- SetHighlight(checkbox, tracker != CheckState::Global);
- });
-}
-
-void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state,
- bool global_state, CheckState& tracker) {
- if (global) {
- tracker = CheckState::Global;
- } else {
- tracker = (state == global_state) ? CheckState::On : CheckState::Off;
- }
- SetHighlight(checkbox, tracker != CheckState::Global);
- QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] {
- tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
- static_cast<int>(CheckState::Count));
- if (tracker == CheckState::Global) {
- checkbox->setChecked(global_state);
- }
- SetHighlight(checkbox, tracker != CheckState::Global);
- });
-}
-
-void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) {
- InsertGlobalItem(combobox, global);
- QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target,
- [target](int index) { SetHighlight(target, index != 0); });
-}
-
-void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) {
- const QString use_global_text =
- ConfigurePerGame::tr("Use global configuration (%1)").arg(combobox->itemText(global_index));
- combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text);
- combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX);
-}
-
-int ConfigurationShared::GetComboboxIndex(int global_setting_index, const QComboBox* combobox) {
- if (Settings::IsConfiguringGlobal()) {
- return combobox->currentIndex();
- }
- if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- return global_setting_index;
- }
- return combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET;
-}
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h
index 04c88758c..31897a6b0 100644
--- a/src/yuzu/configuration/configuration_shared.h
+++ b/src/yuzu/configuration/configuration_shared.h
@@ -3,73 +3,25 @@
#pragma once
-#include <QCheckBox>
-#include <QComboBox>
-#include "common/settings.h"
+#include <memory>
+#include <vector>
+#include <QString>
+#include <QWidget>
+#include <qobjectdefs.h>
-namespace ConfigurationShared {
-
-constexpr int USE_GLOBAL_INDEX = 0;
-constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1;
-constexpr int USE_GLOBAL_OFFSET = 2;
-
-// CheckBoxes require a tracker for their state since we emulate a tristate CheckBox
-enum class CheckState {
- Off, // Checkbox overrides to off/false
- On, // Checkbox overrides to on/true
- Global, // Checkbox defers to the global state
- Count, // Simply the number of states, not a valid checkbox state
-};
-
-// Global-aware apply and set functions
-
-// ApplyPerGameSetting, given a Settings::Setting and a Qt UI element, properly applies a Setting
-void ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting, const QCheckBox* checkbox,
- const CheckState& tracker);
-template <typename Type, bool ranged>
-void ApplyPerGameSetting(Settings::SwitchableSetting<Type, ranged>* setting,
- const QComboBox* combobox) {
- if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
- setting->SetValue(static_cast<Type>(combobox->currentIndex()));
- } else if (!Settings::IsConfiguringGlobal()) {
- if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- setting->SetGlobal(true);
- } else {
- setting->SetGlobal(false);
- setting->SetValue(static_cast<Type>(combobox->currentIndex() -
- ConfigurationShared::USE_GLOBAL_OFFSET));
- }
- }
-}
+class QObject;
-// Sets a Qt UI element given a Settings::Setting
-void SetPerGameSetting(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>* setting);
-
-template <typename Type, bool ranged>
-void SetPerGameSetting(QComboBox* combobox,
- const Settings::SwitchableSetting<Type, ranged>* setting) {
- combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
- : static_cast<int>(setting->GetValue()) +
- ConfigurationShared::USE_GLOBAL_OFFSET);
-}
-
-// (Un)highlights a Qt UI element
-void SetHighlight(QWidget* widget, bool highlighted);
-
-// Sets up a QCheckBox like a tristate one, given a Setting
-void SetColoredTristate(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>& setting,
- CheckState& tracker);
-void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
- CheckState& tracker);
+namespace ConfigurationShared {
-// Sets up coloring of a QWidget `target` based on the state of a QComboBox, and calls
-// InsertGlobalItem
-void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
+class Tab : public QWidget {
+ Q_OBJECT
-// Adds the "Use Global Configuration" selection and separator to the beginning of a QComboBox
-void InsertGlobalItem(QComboBox* combobox, int global_index);
+public:
+ explicit Tab(std::shared_ptr<std::vector<Tab*>> group, QWidget* parent = nullptr);
+ ~Tab();
-// Returns the correct index of a QComboBox taking into account global configuration
-int GetComboboxIndex(int global_setting_index, const QComboBox* combobox);
+ virtual void ApplyConfiguration() = 0;
+ virtual void SetConfiguration() = 0;
+};
} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index eb8078467..573c40801 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -48,11 +48,34 @@
</layout>
</item>
<item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
</property>
- </widget>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Some settings are only available when a game is not running.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index fcd6d61a0..9ccfb2435 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -1,87 +1,112 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <map>
#include <memory>
+#include <vector>
+#include <QComboBox>
#include "audio_core/sink/sink.h"
#include "audio_core/sink/sink_details.h"
+#include "common/common_types.h"
#include "common/settings.h"
+#include "common/settings_common.h"
#include "core/core.h"
#include "ui_configure_audio.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_audio.h"
+#include "yuzu/configuration/shared_translation.h"
+#include "yuzu/configuration/shared_widget.h"
#include "yuzu/uisettings.h"
-ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} {
+ConfigureAudio::ConfigureAudio(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} {
ui->setupUi(this);
+ Setup(builder);
- InitializeAudioSinkComboBox();
+ SetConfiguration();
+}
- connect(ui->volume_slider, &QSlider::valueChanged, this,
- &ConfigureAudio::SetVolumeIndicatorText);
- connect(ui->sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
- &ConfigureAudio::UpdateAudioDevices);
+ConfigureAudio::~ConfigureAudio() = default;
- ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
- ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal());
+void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
+ auto& layout = *ui->audio_widget->layout();
- SetupPerGameUI();
+ std::vector<Settings::BasicSetting*> settings;
- SetConfiguration();
+ std::map<u32, QWidget*> hold;
- const bool is_powered_on = system_.IsPoweredOn();
- ui->sink_combo_box->setEnabled(!is_powered_on);
- ui->output_combo_box->setEnabled(!is_powered_on);
- ui->input_combo_box->setEnabled(!is_powered_on);
-}
+ auto push = [&](Settings::Category category) {
+ for (auto* setting : Settings::values.linkage.by_category[category]) {
+ settings.push_back(setting);
+ }
+ };
-ConfigureAudio::~ConfigureAudio() = default;
+ push(Settings::Category::Audio);
+ push(Settings::Category::SystemAudio);
-void ConfigureAudio::SetConfiguration() {
- SetOutputSinkFromSinkID();
+ for (auto* setting : settings) {
+ auto* widget = builder.BuildWidget(setting, apply_funcs);
- // The device list cannot be pre-populated (nor listed) until the output sink is known.
- UpdateAudioDevices(ui->sink_combo_box->currentIndex());
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
- SetAudioDevicesFromDeviceID();
+ hold.emplace(std::pair{setting->Id(), widget});
+
+ if (setting->Id() == Settings::values.sink_id.Id()) {
+ // TODO (lat9nq): Let the system manage sink_id
+ sink_combo_box = widget->combobox;
+ InitializeAudioSinkComboBox();
+
+ connect(sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ &ConfigureAudio::UpdateAudioDevices);
+ } else if (setting->Id() == Settings::values.audio_output_device_id.Id()) {
+ // Keep track of output (and input) device comboboxes to populate them with system
+ // devices, which are determined at run time
+ output_device_combo_box = widget->combobox;
+ } else if (setting->Id() == Settings::values.audio_input_device_id.Id()) {
+ input_device_combo_box = widget->combobox;
+ }
+ }
- const auto volume_value = static_cast<int>(Settings::values.volume.GetValue());
- ui->volume_slider->setValue(volume_value);
- ui->toggle_background_mute->setChecked(UISettings::values.mute_when_in_background.GetValue());
+ for (const auto& [id, widget] : hold) {
+ layout.addWidget(widget);
+ }
+}
+void ConfigureAudio::SetConfiguration() {
if (!Settings::IsConfiguringGlobal()) {
- if (Settings::values.volume.UsingGlobal()) {
- ui->volume_combo_box->setCurrentIndex(0);
- ui->volume_slider->setEnabled(false);
- } else {
- ui->volume_combo_box->setCurrentIndex(1);
- ui->volume_slider->setEnabled(true);
- }
- ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index);
- ConfigurationShared::SetHighlight(ui->mode_label,
- !Settings::values.sound_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->volume_layout,
- !Settings::values.volume.UsingGlobal());
- } else {
- ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue());
+ return;
}
- SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
+
+ SetOutputSinkFromSinkID();
+
+ // The device list cannot be pre-populated (nor listed) until the output sink is known.
+ UpdateAudioDevices(sink_combo_box->currentIndex());
+
+ SetAudioDevicesFromDeviceID();
}
void ConfigureAudio::SetOutputSinkFromSinkID() {
- [[maybe_unused]] const QSignalBlocker blocker(ui->sink_combo_box);
+ [[maybe_unused]] const QSignalBlocker blocker(sink_combo_box);
int new_sink_index = 0;
- const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue());
- for (int index = 0; index < ui->sink_combo_box->count(); index++) {
- if (ui->sink_combo_box->itemText(index) == sink_id) {
+ const QString sink_id = QString::fromStdString(Settings::values.sink_id.ToString());
+ for (int index = 0; index < sink_combo_box->count(); index++) {
+ if (sink_combo_box->itemText(index) == sink_id) {
new_sink_index = index;
break;
}
}
- ui->sink_combo_box->setCurrentIndex(new_sink_index);
+ sink_combo_box->setCurrentIndex(new_sink_index);
}
void ConfigureAudio::SetAudioDevicesFromDeviceID() {
@@ -89,57 +114,42 @@ void ConfigureAudio::SetAudioDevicesFromDeviceID() {
const QString output_device_id =
QString::fromStdString(Settings::values.audio_output_device_id.GetValue());
- for (int index = 0; index < ui->output_combo_box->count(); index++) {
- if (ui->output_combo_box->itemText(index) == output_device_id) {
+ for (int index = 0; index < output_device_combo_box->count(); index++) {
+ if (output_device_combo_box->itemText(index) == output_device_id) {
new_device_index = index;
break;
}
}
- ui->output_combo_box->setCurrentIndex(new_device_index);
+ output_device_combo_box->setCurrentIndex(new_device_index);
new_device_index = -1;
const QString input_device_id =
QString::fromStdString(Settings::values.audio_input_device_id.GetValue());
- for (int index = 0; index < ui->input_combo_box->count(); index++) {
- if (ui->input_combo_box->itemText(index) == input_device_id) {
+ for (int index = 0; index < input_device_combo_box->count(); index++) {
+ if (input_device_combo_box->itemText(index) == input_device_id) {
new_device_index = index;
break;
}
}
- ui->input_combo_box->setCurrentIndex(new_device_index);
-}
-
-void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
- ui->volume_indicator->setText(tr("%1%", "Volume percentage (e.g. 50%)").arg(percentage));
+ input_device_combo_box->setCurrentIndex(new_device_index);
}
void ConfigureAudio::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound);
+ const bool is_powered_on = system.IsPoweredOn();
+ for (const auto& apply_func : apply_funcs) {
+ apply_func(is_powered_on);
+ }
if (Settings::IsConfiguringGlobal()) {
- Settings::values.sink_id =
- ui->sink_combo_box->itemText(ui->sink_combo_box->currentIndex()).toStdString();
+ Settings::values.sink_id.LoadString(
+ sink_combo_box->itemText(sink_combo_box->currentIndex()).toStdString());
Settings::values.audio_output_device_id.SetValue(
- ui->output_combo_box->itemText(ui->output_combo_box->currentIndex()).toStdString());
+ output_device_combo_box->itemText(output_device_combo_box->currentIndex())
+ .toStdString());
Settings::values.audio_input_device_id.SetValue(
- ui->input_combo_box->itemText(ui->input_combo_box->currentIndex()).toStdString());
- UISettings::values.mute_when_in_background = ui->toggle_background_mute->isChecked();
-
- // Guard if during game and set to game-specific value
- if (Settings::values.volume.UsingGlobal()) {
- const auto volume = static_cast<u8>(ui->volume_slider->value());
- Settings::values.volume.SetValue(volume);
- }
- } else {
- if (ui->volume_combo_box->currentIndex() == 0) {
- Settings::values.volume.SetGlobal(true);
- } else {
- Settings::values.volume.SetGlobal(false);
- const auto volume = static_cast<u8>(ui->volume_slider->value());
- Settings::values.volume.SetValue(volume);
- }
+ input_device_combo_box->itemText(input_device_combo_box->currentIndex()).toStdString());
}
}
@@ -152,54 +162,31 @@ void ConfigureAudio::changeEvent(QEvent* event) {
}
void ConfigureAudio::UpdateAudioDevices(int sink_index) {
- ui->output_combo_box->clear();
- ui->output_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
+ output_device_combo_box->clear();
+ output_device_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
- const std::string sink_id = ui->sink_combo_box->itemText(sink_index).toStdString();
+ const auto sink_id =
+ Settings::ToEnum<Settings::AudioEngine>(sink_combo_box->itemText(sink_index).toStdString());
for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, false)) {
- ui->output_combo_box->addItem(QString::fromStdString(device));
+ output_device_combo_box->addItem(QString::fromStdString(device));
}
- ui->input_combo_box->clear();
- ui->input_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
+ input_device_combo_box->clear();
+ input_device_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, true)) {
- ui->input_combo_box->addItem(QString::fromStdString(device));
+ input_device_combo_box->addItem(QString::fromStdString(device));
}
}
void ConfigureAudio::InitializeAudioSinkComboBox() {
- ui->sink_combo_box->clear();
- ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
+ sink_combo_box->clear();
+ sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
for (const auto& id : AudioCore::Sink::GetSinkIDs()) {
- ui->sink_combo_box->addItem(QString::fromUtf8(id.data(), static_cast<s32>(id.length())));
+ sink_combo_box->addItem(QString::fromStdString(Settings::CanonicalizeEnum(id)));
}
}
void ConfigureAudio::RetranslateUI() {
ui->retranslateUi(this);
- SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
-}
-
-void ConfigureAudio::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal());
- ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
- return;
- }
-
- ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->mode_label,
- Settings::values.sound_index.GetValue(true));
-
- connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
- ui->volume_slider->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
- });
-
- ui->sink_combo_box->setVisible(false);
- ui->sink_label->setVisible(false);
- ui->output_combo_box->setVisible(false);
- ui->output_label->setVisible(false);
- ui->input_combo_box->setVisible(false);
- ui->input_label->setVisible(false);
}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index 0d03aae1d..79538e81c 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -3,30 +3,35 @@
#pragma once
+#include <functional>
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
+
+class QComboBox;
namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureAudio;
}
-class ConfigureAudio : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureAudio : public ConfigurationShared::Tab {
public:
- explicit ConfigureAudio(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureAudio(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
~ConfigureAudio() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
void changeEvent(QEvent* event) override;
@@ -39,11 +44,16 @@ private:
void SetOutputSinkFromSinkID();
void SetAudioDevicesFromDeviceID();
- void SetVolumeIndicatorText(int percentage);
- void SetupPerGameUI();
+ void Setup(const ConfigurationShared::Builder& builder);
std::unique_ptr<Ui::ConfigureAudio> ui;
const Core::System& system;
+
+ std::vector<std::function<void(bool)>> apply_funcs{};
+
+ QComboBox* sink_combo_box;
+ QComboBox* output_device_combo_box;
+ QComboBox* input_device_combo_box;
};
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index 4128c83ad..1181aeb00 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -21,80 +21,14 @@
</property>
<layout class="QVBoxLayout">
<item>
- <layout class="QHBoxLayout" name="engine_layout">
- <item>
- <widget class="QLabel" name="sink_label">
- <property name="text">
- <string>Output Engine:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="sink_combo_box"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="output_layout">
- <item>
- <widget class="QLabel" name="output_label">
- <property name="text">
- <string>Output Device:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="output_combo_box"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="input_layout">
- <item>
- <widget class="QLabel" name="input_label">
- <property name="text">
- <string>Input Device:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="input_combo_box"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="mode_layout">
- <item>
- <widget class="QLabel" name="mode_label">
- <property name="text">
- <string>Sound Output Mode:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="combo_sound">
- <item>
- <property name="text">
- <string>Mono</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Stereo</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Surround</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QWidget" name="volume_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <widget class="QWidget" name="audio_widget" native="true">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777213</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -107,89 +41,9 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <item>
- <widget class="QComboBox" name="volume_combo_box">
- <item>
- <property name="text">
- <string>Use global volume</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Set volume:</string>
- </property>
- </item>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="volume_label">
- <property name="text">
- <string>Volume:</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>30</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QSlider" name="volume_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximum">
- <number>200</number>
- </property>
- <property name="pageStep">
- <number>5</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="volume_indicator">
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>0 %</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
</layout>
</widget>
</item>
- <item>
- <layout class="QHBoxLayout" name="mute_layout">
- <item>
- <widget class="QCheckBox" name="toggle_background_mute">
- <property name="text">
- <string>Mute audio when in background</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index 3d69fb03f..a51359903 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -1,88 +1,92 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <memory>
+#include <typeinfo>
+#include <vector>
+#include <QComboBox>
#include "common/common_types.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
+#include "configuration/shared_widget.h"
#include "core/core.h"
#include "ui_configure_cpu.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_cpu.h"
-ConfigureCpu::ConfigureCpu(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureCpu>()}, system{system_} {
+ConfigureCpu::ConfigureCpu(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureCpu>()}, system{system_},
+ combobox_translations(builder.ComboboxTranslations()) {
ui->setupUi(this);
- SetupPerGameUI();
+ Setup(builder);
SetConfiguration();
- connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ connect(accuracy_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureCpu::UpdateGroup);
}
ConfigureCpu::~ConfigureCpu() = default;
-void ConfigureCpu::SetConfiguration() {
- const bool runtime_lock = !system.IsPoweredOn();
-
- ui->accuracy->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_ignore_standard_fpcr->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_ignore_global_monitor->setEnabled(runtime_lock);
-
- ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue());
- ui->cpuopt_unsafe_reduce_fp_error->setChecked(
- Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue());
- ui->cpuopt_unsafe_ignore_standard_fpcr->setChecked(
- Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue());
- ui->cpuopt_unsafe_inaccurate_nan->setChecked(
- Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue());
- ui->cpuopt_unsafe_fastmem_check->setChecked(
- Settings::values.cpuopt_unsafe_fastmem_check.GetValue());
- ui->cpuopt_unsafe_ignore_global_monitor->setChecked(
- Settings::values.cpuopt_unsafe_ignore_global_monitor.GetValue());
-
- if (Settings::IsConfiguringGlobal()) {
- ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy.GetValue()));
- } else {
- ConfigurationShared::SetPerGameSetting(ui->accuracy, &Settings::values.cpu_accuracy);
- ConfigurationShared::SetHighlight(ui->widget_accuracy,
- !Settings::values.cpu_accuracy.UsingGlobal());
+void ConfigureCpu::SetConfiguration() {}
+void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
+ auto* accuracy_layout = ui->widget_accuracy->layout();
+ auto* unsafe_layout = ui->unsafe_widget->layout();
+ std::map<u32, QWidget*> unsafe_hold{};
+
+ std::vector<Settings::BasicSetting*> settings;
+ const auto push = [&](Settings::Category category) {
+ for (const auto setting : Settings::values.linkage.by_category[category]) {
+ settings.push_back(setting);
+ }
+ };
+
+ push(Settings::Category::Cpu);
+ push(Settings::Category::CpuUnsafe);
+
+ for (const auto setting : settings) {
+ auto* widget = builder.BuildWidget(setting, apply_funcs);
+
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
+
+ if (setting->Id() == Settings::values.cpu_accuracy.Id()) {
+ // Keep track of cpu_accuracy combobox to display/hide the unsafe settings
+ accuracy_layout->addWidget(widget);
+ accuracy_combobox = widget->combobox;
+ } else {
+ // Presently, all other settings here are unsafe checkboxes
+ unsafe_hold.insert({setting->Id(), widget});
+ }
}
- UpdateGroup(ui->accuracy->currentIndex());
+
+ for (const auto& [label, widget] : unsafe_hold) {
+ unsafe_layout->addWidget(widget);
+ }
+
+ UpdateGroup(accuracy_combobox->currentIndex());
}
void ConfigureCpu::UpdateGroup(int index) {
- if (!Settings::IsConfiguringGlobal()) {
- index -= ConfigurationShared::USE_GLOBAL_OFFSET;
- }
- const auto accuracy = static_cast<Settings::CPUAccuracy>(index);
- ui->unsafe_group->setVisible(accuracy == Settings::CPUAccuracy::Unsafe);
+ const auto accuracy = static_cast<Settings::CpuAccuracy>(
+ combobox_translations.at(Settings::EnumMetadata<Settings::CpuAccuracy>::Index())[index]
+ .first);
+ ui->unsafe_group->setVisible(accuracy == Settings::CpuAccuracy::Unsafe);
}
void ConfigureCpu::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpu_accuracy, ui->accuracy);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_unfuse_fma,
- ui->cpuopt_unsafe_unfuse_fma,
- cpuopt_unsafe_unfuse_fma);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_reduce_fp_error,
- ui->cpuopt_unsafe_reduce_fp_error,
- cpuopt_unsafe_reduce_fp_error);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_ignore_standard_fpcr,
- ui->cpuopt_unsafe_ignore_standard_fpcr,
- cpuopt_unsafe_ignore_standard_fpcr);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan,
- ui->cpuopt_unsafe_inaccurate_nan,
- cpuopt_unsafe_inaccurate_nan);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_fastmem_check,
- ui->cpuopt_unsafe_fastmem_check,
- cpuopt_unsafe_fastmem_check);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_ignore_global_monitor,
- ui->cpuopt_unsafe_ignore_global_monitor,
- cpuopt_unsafe_ignore_global_monitor);
+ const bool is_powered_on = system.IsPoweredOn();
+ for (const auto& apply_func : apply_funcs) {
+ apply_func(is_powered_on);
+ }
}
void ConfigureCpu::changeEvent(QEvent* event) {
@@ -96,32 +100,3 @@ void ConfigureCpu::changeEvent(QEvent* event) {
void ConfigureCpu::RetranslateUI() {
ui->retranslateUi(this);
}
-
-void ConfigureCpu::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- return;
- }
-
- ConfigurationShared::SetColoredComboBox(
- ui->accuracy, ui->widget_accuracy,
- static_cast<u32>(Settings::values.cpu_accuracy.GetValue(true)));
-
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_unfuse_fma,
- Settings::values.cpuopt_unsafe_unfuse_fma,
- cpuopt_unsafe_unfuse_fma);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_reduce_fp_error,
- Settings::values.cpuopt_unsafe_reduce_fp_error,
- cpuopt_unsafe_reduce_fp_error);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_ignore_standard_fpcr,
- Settings::values.cpuopt_unsafe_ignore_standard_fpcr,
- cpuopt_unsafe_ignore_standard_fpcr);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan,
- Settings::values.cpuopt_unsafe_inaccurate_nan,
- cpuopt_unsafe_inaccurate_nan);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_fastmem_check,
- Settings::values.cpuopt_unsafe_fastmem_check,
- cpuopt_unsafe_fastmem_check);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_ignore_global_monitor,
- Settings::values.cpuopt_unsafe_ignore_global_monitor,
- cpuopt_unsafe_ignore_global_monitor);
-}
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
index 86d928ca3..61a6de7aa 100644
--- a/src/yuzu/configuration/configure_cpu.h
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -4,29 +4,34 @@
#pragma once
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/shared_translation.h"
+
+class QComboBox;
namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureCpu;
}
-class ConfigureCpu : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureCpu : public ConfigurationShared::Tab {
public:
- explicit ConfigureCpu(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureCpu(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
~ConfigureCpu() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
void changeEvent(QEvent* event) override;
@@ -34,16 +39,14 @@ private:
void UpdateGroup(int index);
- void SetupPerGameUI();
+ void Setup(const ConfigurationShared::Builder& builder);
std::unique_ptr<Ui::ConfigureCpu> ui;
- ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma;
- ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error;
- ConfigurationShared::CheckState cpuopt_unsafe_ignore_standard_fpcr;
- ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan;
- ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check;
- ConfigurationShared::CheckState cpuopt_unsafe_ignore_global_monitor;
-
const Core::System& system;
+
+ const ConfigurationShared::ComboboxTranslationMap& combobox_translations;
+ std::vector<std::function<void(bool)>> apply_funcs{};
+
+ QComboBox* accuracy_combobox;
};
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index 8ae569ee6..f734e842e 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -16,9 +16,12 @@
<property name="accessibleName">
<string>CPU</string>
</property>
- <layout class="QVBoxLayout">
+ <layout class="QVBoxLayout" name="vboxlayout_2" stretch="0">
<item>
- <layout class="QVBoxLayout">
+ <layout class="QVBoxLayout" name="vboxlayout">
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
@@ -27,38 +30,19 @@
<layout class="QVBoxLayout">
<item>
<widget class="QWidget" name="widget_accuracy" native="true">
- <layout class="QHBoxLayout" name="layout_accuracy">
- <item>
- <widget class="QLabel" name="label_accuracy">
- <property name="text">
- <string>Accuracy:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="accuracy">
- <item>
- <property name="text">
- <string>Auto</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Accurate</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Unsafe</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Paranoid (disables most optimizations)</string>
- </property>
- </item>
- </widget>
- </item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
</layout>
</widget>
</item>
@@ -75,10 +59,6 @@
</layout>
</widget>
</item>
- </layout>
- </item>
- <item>
- <layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="unsafe_group">
<property name="title">
@@ -96,105 +76,44 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Unfuse FMA (improve performance on CPUs without FMA)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Faster FRSQRTE and FRECPE</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_ignore_standard_fpcr">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Faster ASIMD instructions (32 bits only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Inaccurate NaN handling</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_fastmem_check">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves speed by eliminating a safety check before every memory read/write in guest. Disabling it may allow a game to read/write the emulator's memory.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Disable address space checks</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_ignore_global_monitor">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Ignore global monitor</string>
- </property>
+ <widget class="QWidget" name="unsafe_widget" native="true">
+ <layout class="QVBoxLayout" name="unsafe_layout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
</widget>
</item>
</layout>
</widget>
</item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="label_disable_info">
- <property name="text">
- <string>CPU settings are available only when game is not running.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
</layout>
</widget>
<resources/>
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() {
ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
+ ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
+ ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
ui->enable_graphics_debugging->setEnabled(runtime_lock);
ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
+ Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
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 15acefe33..66b8b7459 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -2,360 +2,556 @@
<ui version="4.0">
<class>ConfigureDebug</class>
<widget class="QScrollArea" name="ConfigureDebug">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>831</width>
+ <height>760</height>
+ </rect>
+ </property>
<property name="widgetResizable">
<bool>true</bool>
</property>
- <widget class="QWidget">
- <layout class="QVBoxLayout" name="verticalLayout_1">
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Debugger</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_11">
- <item>
- <widget class="QCheckBox" name="toggle_gdbstub">
- <property name="text">
- <string>Enable GDB Stub</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <widget class="QWidget" name="widget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>842</width>
+ <height>741</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_1">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Debugger</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QWidget" name="debug_widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
+ <property name="leftMargin">
+ <number>0</number>
</property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="label_11">
- <property name="text">
- <string>Port:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- <item>
- <widget class="QSpinBox" name="gdbport_spinbox">
- <property name="minimum">
- <number>1024</number>
+ <property name="rightMargin">
+ <number>0</number>
</property>
- <property name="maximum">
- <number>65535</number>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Logging</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_1">
- <item row="0" column="0" colspan="2">
- <layout class="QHBoxLayout" name="horizontalLayout_1">
- <item>
- <widget class="QLabel" name="label_1">
- <property name="text">
- <string>Global Log Filter</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="log_filter_edit"/>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="toggle_console">
- <property name="text">
- <string>Show Log in Console</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QPushButton" name="open_log_button">
- <property name="text">
- <string>Open Log Location</string>
- </property>
+ <item>
+ <widget class="QCheckBox" name="toggle_gdbstub">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Enable GDB Stub</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalWidget_3" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_11">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="gdbport_spinbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>1024</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="extended_logging">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
- </property>
- <property name="text">
- <string>Enable Extended Logging**</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_3">
- <property name="title">
- <string>Homebrew</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <item>
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Arguments String</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="homebrew_args_edit"/>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_4">
- <property name="title">
- <string>Graphics</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QCheckBox" name="enable_graphics_debugging">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, the graphics API enters a slower debugging mode</string>
- </property>
- <property name="text">
- <string>Enable Graphics Debugging</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="enable_nsight_aftermath">
- <property name="toolTip">
- <string>When checked, it enables Nsight Aftermath crash dumps</string>
- </property>
- <property name="text">
- <string>Enable Nsight Aftermath</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="dump_shaders">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
- </property>
- <property name="text">
- <string>Dump Game Shaders</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QCheckBox" name="dump_macros">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it will dump all the macro programs of the GPU</string>
- </property>
- <property name="text">
- <string>Dump Maxwell Macros</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="disable_macro_jit">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string>
- </property>
- <property name="text">
- <string>Disable Macro JIT</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QCheckBox" name="disable_macro_hle">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
- </property>
- <property name="text">
- <string>Disable Macro HLE</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="enable_shader_feedback">
- <property name="toolTip">
- <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
- </property>
- <property name="text">
- <string>Enable Shader Feedback</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="disable_loop_safety_checks">
- <property name="toolTip">
- <string>When checked, it executes shaders without loop logic changes</string>
- </property>
- <property name="text">
- <string>Disable Loop safety checks</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_5">
- <property name="title">
- <string>Debugging</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="2" column="0">
- <widget class="QCheckBox" name="reporting_services">
- <property name="text">
- <string>Enable Verbose Reporting Services**</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QCheckBox" name="fs_access_log">
- <property name="text">
- <string>Enable FS Access Log</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="dump_audio_commands">
- <property name="toolTip">
- <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
- </property>
- <property name="text">
- <string>Dump Audio Commands To Console**</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="create_crash_dumps">
- <property name="text">
- <string>Create Minidump After Crash</string>
- </property>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Logging</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_1">
+ <item row="1" column="1">
+ <widget class="QPushButton" name="open_log_button">
+ <property name="text">
+ <string>Open Log Location</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QWidget" name="logging_widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Global Log Filter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="log_filter_edit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="extended_logging">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
+ </property>
+ <property name="text">
+ <string>Enable Extended Logging**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="toggle_console">
+ <property name="text">
+ <string>Show Log in Console</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
</layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_6">
- <property name="title">
- <string>Advanced</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <item row="0" column="0">
- <widget class="QCheckBox" name="quest_flag">
- <property name="text">
- <string>Kiosk (Quest) Mode</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="enable_cpu_debugging">
- <property name="text">
- <string>Enable CPU Debugging</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="use_debug_asserts">
- <property name="text">
- <string>Enable Debug Asserts</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="use_auto_stub">
- <property name="text">
- <string>Enable Auto-Stub**</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="enable_all_controllers">
- <property name="text">
- <string>Enable All Controller Types</string>
- </property>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Homebrew</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Arguments String</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="homebrew_args_edit"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="title">
+ <string>Graphics</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="disable_loop_safety_checks">
+ <property name="toolTip">
+ <string>When checked, it executes shaders without loop logic changes</string>
+ </property>
+ <property name="text">
+ <string>Disable Loop safety checks</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QCheckBox" name="disable_macro_hle">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
+ </property>
+ <property name="text">
+ <string>Disable Macro HLE</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QCheckBox" name="dump_macros">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it will dump all the macro programs of the GPU</string>
+ </property>
+ <property name="text">
+ <string>Dump Maxwell Macros</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="enable_nsight_aftermath">
+ <property name="toolTip">
+ <string>When checked, it enables Nsight Aftermath crash dumps</string>
+ </property>
+ <property name="text">
+ <string>Enable Nsight Aftermath</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="enable_shader_feedback">
+ <property name="toolTip">
+ <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
+ </property>
+ <property name="text">
+ <string>Enable Shader Feedback</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="disable_macro_jit">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string>
+ </property>
+ <property name="text">
+ <string>Disable Macro JIT</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="0">
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="enable_graphics_debugging">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, the graphics API enters a slower debugging mode</string>
+ </property>
+ <property name="text">
+ <string>Enable Graphics Debugging</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="dump_shaders">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
+ </property>
+ <property name="text">
+ <string>Dump Game Shaders</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="enable_renderdoc_hotkey">
+ <property name="text">
+ <string>Enable Renderdoc Hotkey</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="disable_web_applet">
- <property name="text">
- <string>Disable Web Applet</string>
- </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_6">
+ <property name="title">
+ <string>Advanced</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="perform_vulkan_check">
+ <property name="toolTip">
+ <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string>
+ </property>
+ <property name="text">
+ <string>Perform Startup Vulkan Check</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="disable_web_applet">
+ <property name="text">
+ <string>Disable Web Applet</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="enable_all_controllers">
+ <property name="text">
+ <string>Enable All Controller Types</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="use_auto_stub">
+ <property name="text">
+ <string>Enable Auto-Stub**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="quest_flag">
+ <property name="text">
+ <string>Kiosk (Quest) Mode</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="enable_cpu_debugging">
+ <property name="text">
+ <string>Enable CPU Debugging</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="use_debug_asserts">
+ <property name="text">
+ <string>Enable Debug Asserts</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</widget>
</item>
- <item row="3" column="0">
- <widget class="QCheckBox" name="perform_vulkan_check">
- <property name="toolTip">
- <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string>
- </property>
- <property name="text">
- <string>Perform Startup Vulkan Check</string>
- </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="title">
+ <string>Debugging</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="fs_access_log">
+ <property name="text">
+ <string>Enable FS Access Log</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="create_crash_dumps">
+ <property name="text">
+ <string>Create Minidump After Crash</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="dump_audio_commands">
+ <property name="toolTip">
+ <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
+ </property>
+ <property name="text">
+ <string>Dump Audio Commands To Console**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="reporting_services">
+ <property name="text">
+ <string>Enable Verbose Reporting Services**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</widget>
</item>
</layout>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_5">
- <property name="font">
- <font>
- <italic>true</italic>
- </font>
- </property>
- <property name="text">
- <string>**This will be reset automatically when yuzu closes.</string>
- </property>
- <property name="indent">
- <number>20</number>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>**This will be reset automatically when yuzu closes.</string>
+ </property>
+ <property name="indent">
+ <number>20</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</widget>
<tabstops>
<tabstop>log_filter_edit</tabstop>
@@ -366,14 +562,11 @@
<tabstop>enable_graphics_debugging</tabstop>
<tabstop>enable_shader_feedback</tabstop>
<tabstop>enable_nsight_aftermath</tabstop>
- <tabstop>disable_macro_jit</tabstop>
- <tabstop>disable_loop_safety_checks</tabstop>
<tabstop>fs_access_log</tabstop>
<tabstop>reporting_services</tabstop>
<tabstop>quest_flag</tabstop>
<tabstop>enable_cpu_debugging</tabstop>
<tabstop>use_debug_asserts</tabstop>
- <tabstop>use_auto_stub</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index bdf83ebfe..0ad95cc02 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -4,6 +4,7 @@
#include <memory>
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "ui_configure.h"
#include "vk_device_info.h"
@@ -32,23 +33,28 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_, bool enable_web_config)
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
- registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_,
- this)},
- cpu_tab{std::make_unique<ConfigureCpu>(system_, this)},
+ registry(registry_), system{system_}, builder{std::make_unique<ConfigurationShared::Builder>(
+ this, !system_.IsPoweredOn())},
+ audio_tab{std::make_unique<ConfigureAudio>(system_, nullptr, *builder, this)},
+ cpu_tab{std::make_unique<ConfigureCpu>(system_, nullptr, *builder, this)},
debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
filesystem_tab{std::make_unique<ConfigureFilesystem>(this)},
- general_tab{std::make_unique<ConfigureGeneral>(system_, this)},
- graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)},
+ general_tab{std::make_unique<ConfigureGeneral>(system_, nullptr, *builder, this)},
+ graphics_advanced_tab{
+ std::make_unique<ConfigureGraphicsAdvanced>(system_, nullptr, *builder, this)},
+ ui_tab{std::make_unique<ConfigureUi>(system_, this)},
graphics_tab{std::make_unique<ConfigureGraphics>(
system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); },
- this)},
+ [this](Settings::AspectRatio ratio, Settings::ResolutionSetup setup) {
+ ui_tab->UpdateScreenshotInfo(ratio, setup);
+ },
+ nullptr, *builder, this)},
hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)},
input_tab{std::make_unique<ConfigureInput>(system_, this)},
network_tab{std::make_unique<ConfigureNetwork>(system_, this)},
profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)},
- system_tab{std::make_unique<ConfigureSystem>(system_, this)},
- ui_tab{std::make_unique<ConfigureUi>(system_, this)}, web_tab{std::make_unique<ConfigureWeb>(
- this)} {
+ system_tab{std::make_unique<ConfigureSystem>(system_, nullptr, *builder, this)},
+ web_tab{std::make_unique<ConfigureWeb>(this)} {
Settings::SetConfiguringGlobal(true);
ui->setupUi(this);
@@ -95,6 +101,9 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
adjustSize();
ui->selectorList->setCurrentRow(0);
+
+ // Selects the leftmost button on the bottom bar (Cancel as of writing)
+ ui->buttonBox->setFocus();
}
ConfigureDialog::~ConfigureDialog() = default;
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 2a08b7fee..b28ce288c 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -6,6 +6,9 @@
#include <memory>
#include <vector>
#include <QDialog>
+#include "configuration/shared_widget.h"
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/shared_translation.h"
#include "yuzu/vk_device_info.h"
namespace Core {
@@ -69,6 +72,8 @@ private:
HotkeyRegistry& registry;
Core::System& system;
+ std::unique_ptr<ConfigurationShared::Builder> builder;
+ std::vector<ConfigurationShared::Tab*> tab_group;
std::unique_ptr<ConfigureAudio> audio_tab;
std::unique_ptr<ConfigureCpu> cpu_tab;
@@ -76,12 +81,12 @@ private:
std::unique_ptr<ConfigureFilesystem> filesystem_tab;
std::unique_ptr<ConfigureGeneral> general_tab;
std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab;
+ std::unique_ptr<ConfigureUi> ui_tab;
std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureHotkeys> hotkeys_tab;
std::unique_ptr<ConfigureInput> input_tab;
std::unique_ptr<ConfigureNetwork> network_tab;
std::unique_ptr<ConfigureProfileManager> profile_tab;
std::unique_ptr<ConfigureSystem> system_tab;
- std::unique_ptr<ConfigureUi> ui_tab;
std::unique_ptr<ConfigureWeb> web_tab;
};
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 2f55159f5..c727fadd1 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -3,57 +3,60 @@
#include <functional>
#include <utility>
+#include <vector>
#include <QMessageBox>
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_general.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_general.h"
+#include "yuzu/configuration/shared_widget.h"
#include "yuzu/uisettings.h"
-ConfigureGeneral::ConfigureGeneral(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureGeneral>()}, system{system_} {
+ConfigureGeneral::ConfigureGeneral(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGeneral>()}, system{system_} {
ui->setupUi(this);
- SetupPerGameUI();
+ Setup(builder);
SetConfiguration();
- if (Settings::IsConfiguringGlobal()) {
- connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit,
- [this]() { ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked()); });
- }
-
connect(ui->button_reset_defaults, &QPushButton::clicked, this,
&ConfigureGeneral::ResetDefaults);
+
+ if (!Settings::IsConfiguringGlobal()) {
+ ui->button_reset_defaults->setVisible(false);
+ }
}
ConfigureGeneral::~ConfigureGeneral() = default;
-void ConfigureGeneral::SetConfiguration() {
- const bool runtime_lock = !system.IsPoweredOn();
+void ConfigureGeneral::SetConfiguration() {}
+
+void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
+ QLayout& layout = *ui->general_widget->layout();
- ui->use_multi_core->setEnabled(runtime_lock);
- ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue());
+ std::map<u32, QWidget*> hold{};
- ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());
- ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot.GetValue());
- ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background.GetValue());
- ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse.GetValue());
- ui->toggle_controller_applet_disabled->setEnabled(runtime_lock);
- ui->toggle_controller_applet_disabled->setChecked(
- UISettings::values.controller_applet_disabled.GetValue());
+ for (const auto setting :
+ UISettings::values.linkage.by_category[Settings::Category::UiGeneral]) {
+ auto* widget = builder.BuildWidget(setting, apply_funcs);
- ui->toggle_speed_limit->setChecked(Settings::values.use_speed_limit.GetValue());
- ui->speed_limit->setValue(Settings::values.speed_limit.GetValue());
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
- ui->button_reset_defaults->setEnabled(runtime_lock);
+ hold.emplace(setting->Id(), widget);
+ }
- if (Settings::IsConfiguringGlobal()) {
- ui->speed_limit->setEnabled(Settings::values.use_speed_limit.GetValue());
- } else {
- ui->speed_limit->setEnabled(Settings::values.use_speed_limit.GetValue() &&
- use_speed_limit != ConfigurationShared::CheckState::Global);
+ for (const auto& [id, widget] : hold) {
+ layout.addWidget(widget);
}
}
@@ -77,32 +80,9 @@ void ConfigureGeneral::ResetDefaults() {
}
void ConfigureGeneral::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core,
- use_multi_core);
-
- if (Settings::IsConfiguringGlobal()) {
- UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
- UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
- UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
- UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
- UISettings::values.controller_applet_disabled =
- ui->toggle_controller_applet_disabled->isChecked();
-
- // Guard if during game and set to game-specific value
- if (Settings::values.use_speed_limit.UsingGlobal()) {
- Settings::values.use_speed_limit.SetValue(ui->toggle_speed_limit->checkState() ==
- Qt::Checked);
- Settings::values.speed_limit.SetValue(ui->speed_limit->value());
- }
- } else {
- bool global_speed_limit = use_speed_limit == ConfigurationShared::CheckState::Global;
- Settings::values.use_speed_limit.SetGlobal(global_speed_limit);
- Settings::values.speed_limit.SetGlobal(global_speed_limit);
- if (!global_speed_limit) {
- Settings::values.use_speed_limit.SetValue(ui->toggle_speed_limit->checkState() ==
- Qt::Checked);
- Settings::values.speed_limit.SetValue(ui->speed_limit->value());
- }
+ bool powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(powered_on);
}
}
@@ -117,33 +97,3 @@ void ConfigureGeneral::changeEvent(QEvent* event) {
void ConfigureGeneral::RetranslateUI() {
ui->retranslateUi(this);
}
-
-void ConfigureGeneral::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- // Disables each setting if:
- // - A game is running (thus settings in use), and
- // - A non-global setting is applied.
- ui->toggle_speed_limit->setEnabled(Settings::values.use_speed_limit.UsingGlobal());
- ui->speed_limit->setEnabled(Settings::values.speed_limit.UsingGlobal());
-
- return;
- }
-
- ui->toggle_check_exit->setVisible(false);
- ui->toggle_user_on_boot->setVisible(false);
- ui->toggle_background_pause->setVisible(false);
- ui->toggle_hide_mouse->setVisible(false);
- ui->toggle_controller_applet_disabled->setVisible(false);
-
- ui->button_reset_defaults->setVisible(false);
-
- ConfigurationShared::SetColoredTristate(ui->toggle_speed_limit,
- Settings::values.use_speed_limit, use_speed_limit);
- ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
- use_multi_core);
-
- connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit, [this]() {
- ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() &&
- (use_speed_limit != ConfigurationShared::CheckState::Global));
- });
-}
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index 7ff63f425..2d953f679 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -5,48 +5,49 @@
#include <functional>
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
namespace Core {
class System;
}
class ConfigureDialog;
-
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
class HotkeyRegistry;
namespace Ui {
class ConfigureGeneral;
}
-class ConfigureGeneral : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureGeneral : public ConfigurationShared::Tab {
public:
- explicit ConfigureGeneral(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureGeneral(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder,
+ QWidget* parent = nullptr);
~ConfigureGeneral() override;
void SetResetCallback(std::function<void()> callback);
void ResetDefaults();
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
+ void Setup(const ConfigurationShared::Builder& builder);
+
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void SetupPerGameUI();
-
std::function<void()> reset_callback;
std::unique_ptr<Ui::ConfigureGeneral> ui;
- ConfigurationShared::CheckState use_speed_limit;
- ConfigurationShared::CheckState use_multi_core;
+ std::vector<std::function<void(bool)>> apply_funcs{};
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index fe757d011..a10e7d3a5 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -26,77 +26,22 @@
</property>
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
<item>
- <layout class="QVBoxLayout" name="GeneralVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QCheckBox" name="toggle_speed_limit">
- <property name="text">
- <string>Limit Speed Percent</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QSpinBox" name="speed_limit">
- <property name="suffix">
- <string>%</string>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>9999</number>
- </property>
- <property name="value">
- <number>100</number>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QCheckBox" name="use_multi_core">
- <property name="text">
- <string>Multicore CPU Emulation</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_check_exit">
- <property name="text">
- <string>Confirm exit while emulation is running</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_user_on_boot">
- <property name="text">
- <string>Prompt for user on game boot</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_background_pause">
- <property name="text">
- <string>Pause emulation when in background</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_hide_mouse">
- <property name="text">
- <string>Hide mouse on inactivity</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_controller_applet_disabled">
- <property name="text">
- <string>Disable controller applet</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QWidget" name="general_widget" native="true">
+ <layout class="QVBoxLayout" name="GeneralVerticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index a4965524a..fd6bebf0f 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -7,6 +7,7 @@
#include <iterator>
#include <string>
#include <tuple>
+#include <typeinfo>
#include <utility>
#include <vector>
#include <QBoxLayout>
@@ -15,23 +16,30 @@
#include <QComboBox>
#include <QIcon>
#include <QLabel>
+#include <QLineEdit>
#include <QPixmap>
#include <QPushButton>
#include <QSlider>
#include <QStringLiteral>
#include <QtCore/qobjectdefs.h>
+#include <qabstractbutton.h>
+#include <qboxlayout.h>
+#include <qcombobox.h>
#include <qcoreevent.h>
#include <qglobal.h>
+#include <qgridlayout.h>
#include <vulkan/vulkan_core.h>
#include "common/common_types.h"
#include "common/dynamic_library.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "ui_configure_graphics.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
+#include "yuzu/configuration/shared_widget.h"
#include "yuzu/qt_common.h"
#include "yuzu/uisettings.h"
#include "yuzu/vk_device_info.h"
@@ -46,9 +54,9 @@ static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) {
return VK_PRESENT_MODE_IMMEDIATE_KHR;
case Settings::VSyncMode::Mailbox:
return VK_PRESENT_MODE_MAILBOX_KHR;
- case Settings::VSyncMode::FIFO:
+ case Settings::VSyncMode::Fifo:
return VK_PRESENT_MODE_FIFO_KHR;
- case Settings::VSyncMode::FIFORelaxed:
+ case Settings::VSyncMode::FifoRelaxed:
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
default:
return VK_PRESENT_MODE_FIFO_KHR;
@@ -62,50 +70,70 @@ static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode)
case VK_PRESENT_MODE_MAILBOX_KHR:
return Settings::VSyncMode::Mailbox;
case VK_PRESENT_MODE_FIFO_KHR:
- return Settings::VSyncMode::FIFO;
+ return Settings::VSyncMode::Fifo;
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
- return Settings::VSyncMode::FIFORelaxed;
+ return Settings::VSyncMode::FifoRelaxed;
default:
- return Settings::VSyncMode::FIFO;
+ return Settings::VSyncMode::Fifo;
}
}
-ConfigureGraphics::ConfigureGraphics(const Core::System& system_,
- std::vector<VkDeviceInfo::Record>& records_,
- const std::function<void()>& expose_compute_option_,
- QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, records{records_},
- expose_compute_option{expose_compute_option_}, system{system_} {
+ConfigureGraphics::ConfigureGraphics(
+ const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_,
+ const std::function<void()>& expose_compute_option_,
+ const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>&
+ update_aspect_ratio_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : ConfigurationShared::Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphics>()},
+ records{records_}, expose_compute_option{expose_compute_option_},
+ update_aspect_ratio{update_aspect_ratio_}, system{system_},
+ combobox_translations{builder.ComboboxTranslations()},
+ shader_mapping{
+ combobox_translations.at(Settings::EnumMetadata<Settings::ShaderBackend>::Index())} {
vulkan_device = Settings::values.vulkan_device.GetValue();
RetrieveVulkanDevices();
ui->setupUi(this);
+ Setup(builder);
+
for (const auto& device : vulkan_devices) {
- ui->device->addItem(device);
+ vulkan_device_combobox->addItem(device);
}
- ui->backend->addItem(QStringLiteral("GLSL"));
- ui->backend->addItem(tr("GLASM (Assembly Shaders, NVIDIA Only)"));
- ui->backend->addItem(tr("SPIR-V (Experimental, Mesa Only)"));
-
- SetupPerGameUI();
+ UpdateBackgroundColorButton(QColor::fromRgb(Settings::values.bg_red.GetValue(),
+ Settings::values.bg_green.GetValue(),
+ Settings::values.bg_blue.GetValue()));
+ UpdateAPILayout();
+ PopulateVSyncModeSelection(); //< must happen after UpdateAPILayout
- SetConfiguration();
+ // VSync setting needs to be determined after populating the VSync combobox
+ if (Settings::IsConfiguringGlobal()) {
+ const auto vsync_mode_setting = Settings::values.vsync_mode.GetValue();
+ const auto vsync_mode = VSyncSettingToMode(vsync_mode_setting);
+ int index{};
+ for (const auto mode : vsync_mode_combobox_enum_map) {
+ if (mode == vsync_mode) {
+ break;
+ }
+ index++;
+ }
+ if (static_cast<unsigned long>(index) < vsync_mode_combobox_enum_map.size()) {
+ vsync_mode_combobox->setCurrentIndex(index);
+ }
+ }
- connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] {
+ connect(api_combobox, qOverload<int>(&QComboBox::activated), this, [this] {
UpdateAPILayout();
PopulateVSyncModeSelection();
- if (!Settings::IsConfiguringGlobal()) {
- ConfigurationShared::SetHighlight(
- ui->api_widget, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX);
- }
});
- connect(ui->device, qOverload<int>(&QComboBox::activated), this, [this](int device) {
- UpdateDeviceSelection(device);
- PopulateVSyncModeSelection();
- });
- connect(ui->backend, qOverload<int>(&QComboBox::activated), this,
+ connect(vulkan_device_combobox, qOverload<int>(&QComboBox::activated), this,
+ [this](int device) {
+ UpdateDeviceSelection(device);
+ PopulateVSyncModeSelection();
+ });
+ connect(shader_backend_combobox, qOverload<int>(&QComboBox::activated), this,
[this](int backend) { UpdateShaderBackendSelection(backend); });
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
@@ -116,39 +144,61 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_,
UpdateBackgroundColorButton(new_bg_color);
});
- ui->api->setEnabled(!UISettings::values.has_broken_vulkan && ui->api->isEnabled());
+ const auto& update_screenshot_info = [this, &builder]() {
+ const auto& combobox_enumerations = builder.ComboboxTranslations().at(
+ Settings::EnumMetadata<Settings::AspectRatio>::Index());
+ const auto index = aspect_ratio_combobox->currentIndex();
+ const auto ratio = static_cast<Settings::AspectRatio>(combobox_enumerations[index].first);
+
+ const auto& combobox_enumerations_resolution = builder.ComboboxTranslations().at(
+ Settings::EnumMetadata<Settings::ResolutionSetup>::Index());
+ const auto res_index = resolution_combobox->currentIndex();
+ const auto setup = static_cast<Settings::ResolutionSetup>(
+ combobox_enumerations_resolution[res_index].first);
+
+ update_aspect_ratio(ratio, setup);
+ };
+
+ connect(aspect_ratio_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ update_screenshot_info);
+ connect(resolution_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ update_screenshot_info);
+
+ api_combobox->setEnabled(!UISettings::values.has_broken_vulkan && api_combobox->isEnabled());
ui->api_widget->setEnabled(
(!UISettings::values.has_broken_vulkan || Settings::IsConfiguringGlobal()) &&
ui->api_widget->isEnabled());
- ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
- ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
- connect(ui->fsr_sharpening_slider, &QSlider::valueChanged, this,
- &ConfigureGraphics::SetFSRIndicatorText);
- ui->fsr_sharpening_combobox->setVisible(!Settings::IsConfiguringGlobal());
- ui->fsr_sharpening_label->setVisible(Settings::IsConfiguringGlobal());
+ if (Settings::IsConfiguringGlobal()) {
+ ui->bg_widget->setEnabled(Settings::values.bg_red.UsingGlobal());
+ }
}
void ConfigureGraphics::PopulateVSyncModeSelection() {
+ if (!Settings::IsConfiguringGlobal()) {
+ return;
+ }
+
const Settings::RendererBackend backend{GetCurrentGraphicsBackend()};
if (backend == Settings::RendererBackend::Null) {
- ui->vsync_mode_combobox->setEnabled(false);
+ vsync_mode_combobox->setEnabled(false);
return;
}
- ui->vsync_mode_combobox->setEnabled(true);
+ vsync_mode_combobox->setEnabled(true);
const int current_index = //< current selected vsync mode from combobox
- ui->vsync_mode_combobox->currentIndex();
+ vsync_mode_combobox->currentIndex();
const auto current_mode = //< current selected vsync mode as a VkPresentModeKHR
current_index == -1 ? VSyncSettingToMode(Settings::values.vsync_mode.GetValue())
: vsync_mode_combobox_enum_map[current_index];
int index{};
- const int device{ui->device->currentIndex()}; //< current selected Vulkan device
+ const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device
+
const auto& present_modes = //< relevant vector of present modes for the selected device or API
- backend == Settings::RendererBackend::Vulkan ? device_present_modes[device]
- : default_present_modes;
+ backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device]
+ : default_present_modes;
- ui->vsync_mode_combobox->clear();
+ vsync_mode_combobox->clear();
vsync_mode_combobox_enum_map.clear();
vsync_mode_combobox_enum_map.reserve(present_modes.size());
for (const auto present_mode : present_modes) {
@@ -157,10 +207,10 @@ void ConfigureGraphics::PopulateVSyncModeSelection() {
continue;
}
- ui->vsync_mode_combobox->insertItem(index, mode_name);
+ vsync_mode_combobox->insertItem(index, mode_name);
vsync_mode_combobox_enum_map.push_back(present_mode);
if (present_mode == current_mode) {
- ui->vsync_mode_combobox->setCurrentIndex(index);
+ vsync_mode_combobox->setCurrentIndex(index);
}
index++;
}
@@ -186,112 +236,132 @@ void ConfigureGraphics::UpdateShaderBackendSelection(int backend) {
ConfigureGraphics::~ConfigureGraphics() = default;
-void ConfigureGraphics::SetConfiguration() {
- const bool runtime_lock = !system.IsPoweredOn();
-
- ui->api_widget->setEnabled(runtime_lock);
- ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
- ui->use_disk_shader_cache->setEnabled(runtime_lock);
- ui->nvdec_emulation_widget->setEnabled(runtime_lock);
- ui->resolution_combobox->setEnabled(runtime_lock);
- ui->accelerate_astc->setEnabled(runtime_lock);
- ui->vsync_mode_layout->setEnabled(runtime_lock ||
- Settings::values.renderer_backend.GetValue() ==
- Settings::RendererBackend::Vulkan);
- ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
- ui->use_asynchronous_gpu_emulation->setChecked(
- Settings::values.use_asynchronous_gpu_emulation.GetValue());
- ui->accelerate_astc->setChecked(Settings::values.accelerate_astc.GetValue());
+void ConfigureGraphics::SetConfiguration() {}
+
+void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
+ QLayout* api_layout = ui->api_widget->layout();
+ QWidget* api_grid_widget = new QWidget(this);
+ QVBoxLayout* api_grid_layout = new QVBoxLayout(api_grid_widget);
+ api_grid_layout->setContentsMargins(0, 0, 0, 0);
+ api_layout->addWidget(api_grid_widget);
+
+ QLayout& graphics_layout = *ui->graphics_widget->layout();
+
+ std::map<u32, QWidget*> hold_graphics;
+ std::vector<QWidget*> hold_api;
+
+ for (const auto setting : Settings::values.linkage.by_category[Settings::Category::Renderer]) {
+ ConfigurationShared::Widget* widget = [&]() {
+ if (setting->Id() == Settings::values.fsr_sharpening_slider.Id()) {
+ // FSR needs a reversed slider and a 0.5 multiplier
+ return builder.BuildWidget(
+ setting, apply_funcs, ConfigurationShared::RequestType::ReverseSlider, true,
+ 0.5f, nullptr, tr("%", "FSR sharpening percentage (e.g. 50%)"));
+ } else {
+ return builder.BuildWidget(setting, apply_funcs);
+ }
+ }();
- if (Settings::IsConfiguringGlobal()) {
- ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue()));
- ui->fullscreen_mode_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.fullscreen_mode.GetValue()));
- ui->nvdec_emulation->setCurrentIndex(
- static_cast<int>(Settings::values.nvdec_emulation.GetValue()));
- ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
- ui->resolution_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.resolution_setup.GetValue()));
- ui->scaling_filter_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.scaling_filter.GetValue()));
- ui->fsr_sharpening_slider->setValue(Settings::values.fsr_sharpening_slider.GetValue());
- ui->anti_aliasing_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.anti_aliasing.GetValue()));
- } else {
- ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
- ConfigurationShared::SetHighlight(ui->api_widget,
- !Settings::values.renderer_backend.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->nvdec_emulation,
- &Settings::values.nvdec_emulation);
- ConfigurationShared::SetHighlight(ui->nvdec_emulation_widget,
- !Settings::values.nvdec_emulation.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->fullscreen_mode_combobox,
- &Settings::values.fullscreen_mode);
- ConfigurationShared::SetHighlight(ui->fullscreen_mode_label,
- !Settings::values.fullscreen_mode.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox,
- &Settings::values.aspect_ratio);
- ConfigurationShared::SetHighlight(ui->ar_label,
- !Settings::values.aspect_ratio.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->resolution_combobox,
- &Settings::values.resolution_setup);
- ConfigurationShared::SetHighlight(ui->resolution_label,
- !Settings::values.resolution_setup.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->scaling_filter_combobox,
- &Settings::values.scaling_filter);
- ConfigurationShared::SetHighlight(ui->scaling_filter_label,
- !Settings::values.scaling_filter.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->anti_aliasing_combobox,
- &Settings::values.anti_aliasing);
- ConfigurationShared::SetHighlight(ui->anti_aliasing_label,
- !Settings::values.anti_aliasing.UsingGlobal());
-
- ui->fsr_sharpening_combobox->setCurrentIndex(
- Settings::values.fsr_sharpening_slider.UsingGlobal() ? 0 : 1);
- ui->fsr_sharpening_slider->setEnabled(
- !Settings::values.fsr_sharpening_slider.UsingGlobal());
- ui->fsr_sharpening_value->setEnabled(!Settings::values.fsr_sharpening_slider.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->fsr_sharpening_layout,
- !Settings::values.fsr_sharpening_slider.UsingGlobal());
- ui->fsr_sharpening_slider->setValue(Settings::values.fsr_sharpening_slider.GetValue());
-
- ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
- ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal());
- }
- UpdateBackgroundColorButton(QColor::fromRgb(Settings::values.bg_red.GetValue(),
- Settings::values.bg_green.GetValue(),
- Settings::values.bg_blue.GetValue()));
- UpdateAPILayout();
- PopulateVSyncModeSelection(); //< must happen after UpdateAPILayout
- SetFSRIndicatorText(ui->fsr_sharpening_slider->sliderPosition());
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
- // VSync setting needs to be determined after populating the VSync combobox
- if (Settings::IsConfiguringGlobal()) {
- const auto vsync_mode_setting = Settings::values.vsync_mode.GetValue();
- const auto vsync_mode = VSyncSettingToMode(vsync_mode_setting);
- int index{};
- for (const auto mode : vsync_mode_combobox_enum_map) {
- if (mode == vsync_mode) {
- break;
+ if (setting->Id() == Settings::values.renderer_backend.Id()) {
+ // Add the renderer combobox now so it's at the top
+ api_grid_layout->addWidget(widget);
+ api_combobox = widget->combobox;
+ api_restore_global_button = widget->restore_button;
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(api_restore_global_button, &QAbstractButton::clicked,
+ [this](bool) { UpdateAPILayout(); });
+
+ // Detach API's restore button and place it where we want
+ // Lets us put it on the side, and it will automatically scale if there's a
+ // second combobox (shader_backend, vulkan_device)
+ widget->layout()->removeWidget(api_restore_global_button);
+ api_layout->addWidget(api_restore_global_button);
}
- index++;
- }
- if (static_cast<unsigned long>(index) < vsync_mode_combobox_enum_map.size()) {
- ui->vsync_mode_combobox->setCurrentIndex(index);
+ } else if (setting->Id() == Settings::values.vulkan_device.Id()) {
+ // Keep track of vulkan_device's combobox so we can populate it
+ hold_api.push_back(widget);
+ vulkan_device_combobox = widget->combobox;
+ vulkan_device_widget = widget;
+ } else if (setting->Id() == Settings::values.shader_backend.Id()) {
+ // Keep track of shader_backend's combobox so we can populate it
+ hold_api.push_back(widget);
+ shader_backend_combobox = widget->combobox;
+ shader_backend_widget = widget;
+ } else if (setting->Id() == Settings::values.vsync_mode.Id()) {
+ // Keep track of vsync_mode's combobox so we can populate it
+ vsync_mode_combobox = widget->combobox;
+ hold_graphics.emplace(setting->Id(), widget);
+ } else if (setting->Id() == Settings::values.aspect_ratio.Id()) {
+ // Keep track of the aspect ratio combobox to update other UI tabs that need it
+ aspect_ratio_combobox = widget->combobox;
+ hold_graphics.emplace(setting->Id(), widget);
+ } else if (setting->Id() == Settings::values.resolution_setup.Id()) {
+ // Keep track of the resolution combobox to update other UI tabs that need it
+ resolution_combobox = widget->combobox;
+ hold_graphics.emplace(setting->Id(), widget);
+ } else {
+ hold_graphics.emplace(setting->Id(), widget);
}
}
-}
-void ConfigureGraphics::SetFSRIndicatorText(int percentage) {
- ui->fsr_sharpening_value->setText(
- tr("%1%", "FSR sharpening percentage (e.g. 50%)").arg(100 - (percentage / 2)));
+ for (const auto& [id, widget] : hold_graphics) {
+ graphics_layout.addWidget(widget);
+ }
+
+ for (auto widget : hold_api) {
+ api_grid_layout->addWidget(widget);
+ }
+
+ // Background color is too specific to build into the new system, so we manage it here
+ // (3 settings, all collected into a single widget with a QColor to manage on top)
+ if (Settings::IsConfiguringGlobal()) {
+ apply_funcs.push_back([this](bool powered_on) {
+ Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
+ Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
+ Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
+ });
+ } else {
+ QPushButton* bg_restore_button = ConfigurationShared::Widget::CreateRestoreGlobalButton(
+ Settings::values.bg_red.UsingGlobal(), ui->bg_widget);
+ ui->bg_widget->layout()->addWidget(bg_restore_button);
+
+ QObject::connect(bg_restore_button, &QAbstractButton::clicked,
+ [bg_restore_button, this](bool) {
+ const int r = Settings::values.bg_red.GetValue(true);
+ const int g = Settings::values.bg_green.GetValue(true);
+ const int b = Settings::values.bg_blue.GetValue(true);
+ UpdateBackgroundColorButton(QColor::fromRgb(r, g, b));
+
+ bg_restore_button->setVisible(false);
+ bg_restore_button->setEnabled(false);
+ });
+
+ QObject::connect(ui->bg_button, &QAbstractButton::clicked, [bg_restore_button](bool) {
+ bg_restore_button->setVisible(true);
+ bg_restore_button->setEnabled(true);
+ });
+
+ apply_funcs.push_back([bg_restore_button, this](bool powered_on) {
+ const bool using_global = !bg_restore_button->isEnabled();
+ Settings::values.bg_red.SetGlobal(using_global);
+ Settings::values.bg_green.SetGlobal(using_global);
+ Settings::values.bg_blue.SetGlobal(using_global);
+ if (!using_global) {
+ Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
+ Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
+ Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
+ }
+ });
+ }
}
const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode,
@@ -315,130 +385,48 @@ const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode,
}
}
+int ConfigureGraphics::FindIndex(u32 enumeration, int value) const {
+ for (u32 i = 0; i < combobox_translations.at(enumeration).size(); i++) {
+ if (combobox_translations.at(enumeration)[i].first == static_cast<u32>(value)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
void ConfigureGraphics::ApplyConfiguration() {
- const auto resolution_setup = static_cast<Settings::ResolutionSetup>(
- ui->resolution_combobox->currentIndex() -
- ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
-
- const auto scaling_filter = static_cast<Settings::ScalingFilter>(
- ui->scaling_filter_combobox->currentIndex() -
- ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
-
- const auto anti_aliasing = static_cast<Settings::AntiAliasing>(
- ui->anti_aliasing_combobox->currentIndex() -
- ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
-
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode,
- ui->fullscreen_mode_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio,
- ui->aspect_ratio_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache,
- ui->use_disk_shader_cache, use_disk_shader_cache);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation,
- ui->use_asynchronous_gpu_emulation,
- use_asynchronous_gpu_emulation);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.accelerate_astc, ui->accelerate_astc,
- accelerate_astc);
+ const bool powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(powered_on);
+ }
if (Settings::IsConfiguringGlobal()) {
- // Guard if during game and set to game-specific value
- if (Settings::values.renderer_backend.UsingGlobal()) {
- Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
- }
- if (Settings::values.nvdec_emulation.UsingGlobal()) {
- Settings::values.nvdec_emulation.SetValue(GetCurrentNvdecEmulation());
- }
- if (Settings::values.shader_backend.UsingGlobal()) {
- Settings::values.shader_backend.SetValue(shader_backend);
- }
- if (Settings::values.vulkan_device.UsingGlobal()) {
- Settings::values.vulkan_device.SetValue(vulkan_device);
- }
- if (Settings::values.bg_red.UsingGlobal()) {
- Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
- Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
- Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
- }
- if (Settings::values.resolution_setup.UsingGlobal()) {
- Settings::values.resolution_setup.SetValue(resolution_setup);
- }
- if (Settings::values.scaling_filter.UsingGlobal()) {
- Settings::values.scaling_filter.SetValue(scaling_filter);
- }
- if (Settings::values.anti_aliasing.UsingGlobal()) {
- Settings::values.anti_aliasing.SetValue(anti_aliasing);
- }
- Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value());
-
- const auto mode = vsync_mode_combobox_enum_map[ui->vsync_mode_combobox->currentIndex()];
+ const auto mode = vsync_mode_combobox_enum_map[vsync_mode_combobox->currentIndex()];
const auto vsync_mode = PresentModeToSetting(mode);
Settings::values.vsync_mode.SetValue(vsync_mode);
- } else {
- if (ui->resolution_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.resolution_setup.SetGlobal(true);
- } else {
- Settings::values.resolution_setup.SetGlobal(false);
- Settings::values.resolution_setup.SetValue(resolution_setup);
- }
- if (ui->scaling_filter_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.scaling_filter.SetGlobal(true);
- } else {
- Settings::values.scaling_filter.SetGlobal(false);
- Settings::values.scaling_filter.SetValue(scaling_filter);
- }
- if (ui->anti_aliasing_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.anti_aliasing.SetGlobal(true);
- } else {
- Settings::values.anti_aliasing.SetGlobal(false);
- Settings::values.anti_aliasing.SetValue(anti_aliasing);
- }
- if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.renderer_backend.SetGlobal(true);
- Settings::values.shader_backend.SetGlobal(true);
- Settings::values.vulkan_device.SetGlobal(true);
- } else {
- Settings::values.renderer_backend.SetGlobal(false);
- Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
- switch (GetCurrentGraphicsBackend()) {
- case Settings::RendererBackend::OpenGL:
- case Settings::RendererBackend::Null:
- Settings::values.shader_backend.SetGlobal(false);
- Settings::values.vulkan_device.SetGlobal(true);
- Settings::values.shader_backend.SetValue(shader_backend);
- break;
- case Settings::RendererBackend::Vulkan:
- Settings::values.shader_backend.SetGlobal(true);
- Settings::values.vulkan_device.SetGlobal(false);
- Settings::values.vulkan_device.SetValue(vulkan_device);
- break;
- }
- }
-
- if (ui->nvdec_emulation->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.nvdec_emulation.SetGlobal(true);
- } else {
- Settings::values.nvdec_emulation.SetGlobal(false);
- Settings::values.nvdec_emulation.SetValue(GetCurrentNvdecEmulation());
- }
-
- if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.bg_red.SetGlobal(true);
- Settings::values.bg_green.SetGlobal(true);
- Settings::values.bg_blue.SetGlobal(true);
- } else {
- Settings::values.bg_red.SetGlobal(false);
- Settings::values.bg_green.SetGlobal(false);
- Settings::values.bg_blue.SetGlobal(false);
- Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
- Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
- Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
- }
+ }
- if (ui->fsr_sharpening_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.fsr_sharpening_slider.SetGlobal(true);
- } else {
- Settings::values.fsr_sharpening_slider.SetGlobal(false);
- Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value());
+ Settings::values.vulkan_device.SetGlobal(true);
+ Settings::values.shader_backend.SetGlobal(true);
+ if (Settings::IsConfiguringGlobal() ||
+ (!Settings::IsConfiguringGlobal() && api_restore_global_button->isEnabled())) {
+ auto backend = static_cast<Settings::RendererBackend>(
+ combobox_translations
+ .at(Settings::EnumMetadata<
+ Settings::RendererBackend>::Index())[api_combobox->currentIndex()]
+ .first);
+ switch (backend) {
+ case Settings::RendererBackend::OpenGL:
+ Settings::values.shader_backend.SetGlobal(Settings::IsConfiguringGlobal());
+ Settings::values.shader_backend.SetValue(static_cast<Settings::ShaderBackend>(
+ shader_mapping[shader_backend_combobox->currentIndex()].first));
+ break;
+ case Settings::RendererBackend::Vulkan:
+ Settings::values.vulkan_device.SetGlobal(Settings::IsConfiguringGlobal());
+ Settings::values.vulkan_device.SetValue(vulkan_device_combobox->currentIndex());
+ break;
+ case Settings::RendererBackend::Null:
+ break;
}
}
}
@@ -466,36 +454,26 @@ void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
}
void ConfigureGraphics::UpdateAPILayout() {
- if (!Settings::IsConfiguringGlobal() &&
- ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- vulkan_device = Settings::values.vulkan_device.GetValue(true);
- shader_backend = Settings::values.shader_backend.GetValue(true);
- ui->device_widget->setEnabled(false);
- ui->backend_widget->setEnabled(false);
- } else {
- vulkan_device = Settings::values.vulkan_device.GetValue();
- shader_backend = Settings::values.shader_backend.GetValue();
- ui->device_widget->setEnabled(true);
- ui->backend_widget->setEnabled(true);
- }
-
- switch (GetCurrentGraphicsBackend()) {
- case Settings::RendererBackend::OpenGL:
- ui->backend->setCurrentIndex(static_cast<u32>(shader_backend));
- ui->device_widget->setVisible(false);
- ui->backend_widget->setVisible(true);
- break;
- case Settings::RendererBackend::Vulkan:
- if (static_cast<int>(vulkan_device) < ui->device->count()) {
- ui->device->setCurrentIndex(vulkan_device);
- }
- ui->device_widget->setVisible(true);
- ui->backend_widget->setVisible(false);
- break;
- case Settings::RendererBackend::Null:
- ui->device_widget->setVisible(false);
- ui->backend_widget->setVisible(false);
- break;
+ bool runtime_lock = !system.IsPoweredOn();
+ bool need_global = !(Settings::IsConfiguringGlobal() || api_restore_global_button->isEnabled());
+ vulkan_device = Settings::values.vulkan_device.GetValue(need_global);
+ shader_backend = Settings::values.shader_backend.GetValue(need_global);
+ vulkan_device_widget->setEnabled(!need_global && runtime_lock);
+ shader_backend_widget->setEnabled(!need_global && runtime_lock);
+
+ const auto current_backend = GetCurrentGraphicsBackend();
+ const bool is_opengl = current_backend == Settings::RendererBackend::OpenGL;
+ const bool is_vulkan = current_backend == Settings::RendererBackend::Vulkan;
+
+ vulkan_device_widget->setVisible(is_vulkan);
+ shader_backend_widget->setVisible(is_opengl);
+
+ if (is_opengl) {
+ shader_backend_combobox->setCurrentIndex(
+ FindIndex(Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
+ static_cast<int>(shader_backend)));
+ } else if (is_vulkan && static_cast<int>(vulkan_device) < vulkan_device_combobox->count()) {
+ vulkan_device_combobox->setCurrentIndex(vulkan_device);
}
}
@@ -515,92 +493,19 @@ void ConfigureGraphics::RetrieveVulkanDevices() {
}
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
- if (Settings::IsConfiguringGlobal()) {
- return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
- }
-
- if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.renderer_backend.SetGlobal(true);
- return Settings::values.renderer_backend.GetValue();
- }
- Settings::values.renderer_backend.SetGlobal(false);
- return static_cast<Settings::RendererBackend>(ui->api->currentIndex() -
- ConfigurationShared::USE_GLOBAL_OFFSET);
-}
-
-Settings::NvdecEmulation ConfigureGraphics::GetCurrentNvdecEmulation() const {
- if (Settings::IsConfiguringGlobal()) {
- return static_cast<Settings::NvdecEmulation>(ui->nvdec_emulation->currentIndex());
- }
-
- if (ui->nvdec_emulation->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.nvdec_emulation.SetGlobal(true);
- return Settings::values.nvdec_emulation.GetValue();
- }
- Settings::values.nvdec_emulation.SetGlobal(false);
- return static_cast<Settings::NvdecEmulation>(ui->nvdec_emulation->currentIndex() -
- ConfigurationShared::USE_GLOBAL_OFFSET);
-}
-
-void ConfigureGraphics::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal());
- ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal());
- ui->fullscreen_mode_combobox->setEnabled(Settings::values.fullscreen_mode.UsingGlobal());
- ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
- ui->resolution_combobox->setEnabled(Settings::values.resolution_setup.UsingGlobal());
- ui->scaling_filter_combobox->setEnabled(Settings::values.scaling_filter.UsingGlobal());
- ui->fsr_sharpening_slider->setEnabled(Settings::values.fsr_sharpening_slider.UsingGlobal());
- ui->anti_aliasing_combobox->setEnabled(Settings::values.anti_aliasing.UsingGlobal());
- ui->use_asynchronous_gpu_emulation->setEnabled(
- Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
- ui->nvdec_emulation->setEnabled(Settings::values.nvdec_emulation.UsingGlobal());
- ui->accelerate_astc->setEnabled(Settings::values.accelerate_astc.UsingGlobal());
- ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
- ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
- ui->fsr_slider_layout->setEnabled(Settings::values.fsr_sharpening_slider.UsingGlobal());
-
- return;
+ const auto selected_backend = [&]() {
+ if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) {
+ return Settings::values.renderer_backend.GetValue(true);
+ }
+ return static_cast<Settings::RendererBackend>(
+ combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
+ .at(api_combobox->currentIndex())
+ .first);
+ }();
+
+ if (selected_backend == Settings::RendererBackend::Vulkan &&
+ UISettings::values.has_broken_vulkan) {
+ return Settings::RendererBackend::OpenGL;
}
-
- connect(ui->bg_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) {
- ui->bg_button->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->bg_layout, index == 1);
- });
-
- connect(ui->fsr_sharpening_combobox, qOverload<int>(&QComboBox::activated), this,
- [this](int index) {
- ui->fsr_sharpening_slider->setEnabled(index == 1);
- ui->fsr_sharpening_value->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->fsr_sharpening_layout, index == 1);
- });
-
- ConfigurationShared::SetColoredTristate(
- ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
- ConfigurationShared::SetColoredTristate(ui->accelerate_astc, Settings::values.accelerate_astc,
- accelerate_astc);
- ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
- Settings::values.use_asynchronous_gpu_emulation,
- use_asynchronous_gpu_emulation);
-
- ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label,
- Settings::values.aspect_ratio.GetValue(true));
- ConfigurationShared::SetColoredComboBox(
- ui->fullscreen_mode_combobox, ui->fullscreen_mode_label,
- static_cast<int>(Settings::values.fullscreen_mode.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->resolution_combobox, ui->resolution_label,
- static_cast<int>(Settings::values.resolution_setup.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->scaling_filter_combobox, ui->scaling_filter_label,
- static_cast<int>(Settings::values.scaling_filter.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->anti_aliasing_combobox, ui->anti_aliasing_label,
- static_cast<int>(Settings::values.anti_aliasing.GetValue(true)));
- ConfigurationShared::InsertGlobalItem(
- ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
- ConfigurationShared::InsertGlobalItem(
- ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true)));
-
- ui->vsync_mode_layout->setVisible(false);
+ return selected_backend;
}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index be9310b74..9c24a56db 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -5,6 +5,8 @@
#include <functional>
#include <memory>
+#include <type_traits>
+#include <typeindex>
#include <vector>
#include <QColor>
#include <QString>
@@ -12,10 +14,15 @@
#include <qobjectdefs.h>
#include <vulkan/vulkan_core.h>
#include "common/common_types.h"
+#include "common/settings_enums.h"
+#include "configuration/shared_translation.h"
#include "vk_device_info.h"
+#include "yuzu/configuration/configuration_shared.h"
+class QPushButton;
class QEvent;
class QObject;
+class QComboBox;
namespace Settings {
enum class NvdecEmulation : u32;
@@ -27,31 +34,34 @@ namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureGraphics;
}
-class ConfigureGraphics : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureGraphics : public ConfigurationShared::Tab {
public:
- explicit ConfigureGraphics(const Core::System& system_,
- std::vector<VkDeviceInfo::Record>& records,
- const std::function<void()>& expose_compute_option_,
- QWidget* parent = nullptr);
+ explicit ConfigureGraphics(
+ const Core::System& system_, std::vector<VkDeviceInfo::Record>& records,
+ const std::function<void()>& expose_compute_option,
+ const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>&
+ update_aspect_ratio,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
~ConfigureGraphics() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
+ void Setup(const ConfigurationShared::Builder& builder);
+
void PopulateVSyncModeSelection();
void UpdateBackgroundColorButton(QColor color);
void UpdateAPILayout();
@@ -60,34 +70,43 @@ private:
void RetrieveVulkanDevices();
- void SetFSRIndicatorText(int percentage);
/* Turns a Vulkan present mode into a textual string for a UI
* (and eventually for a human to read) */
const QString TranslateVSyncMode(VkPresentModeKHR mode,
Settings::RendererBackend backend) const;
- void SetupPerGameUI();
-
Settings::RendererBackend GetCurrentGraphicsBackend() const;
- Settings::NvdecEmulation GetCurrentNvdecEmulation() const;
+
+ int FindIndex(u32 enumeration, int value) const;
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
- ConfigurationShared::CheckState use_nvdec_emulation;
- ConfigurationShared::CheckState accelerate_astc;
- ConfigurationShared::CheckState use_disk_shader_cache;
- ConfigurationShared::CheckState use_asynchronous_gpu_emulation;
+ std::vector<std::function<void(bool)>> apply_funcs{};
std::vector<VkDeviceInfo::Record>& records;
std::vector<QString> vulkan_devices;
std::vector<std::vector<VkPresentModeKHR>> device_present_modes;
std::vector<VkPresentModeKHR>
- vsync_mode_combobox_enum_map; //< Keeps track of which present mode corresponds to which
- // selection in the combobox
+ vsync_mode_combobox_enum_map{}; //< Keeps track of which present mode corresponds to which
+ // selection in the combobox
u32 vulkan_device{};
Settings::ShaderBackend shader_backend{};
const std::function<void()>& expose_compute_option;
+ const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)> update_aspect_ratio;
const Core::System& system;
+ const ConfigurationShared::ComboboxTranslationMap& combobox_translations;
+ const std::vector<std::pair<u32, QString>>& shader_mapping;
+
+ QPushButton* api_restore_global_button;
+ QComboBox* vulkan_device_combobox;
+ QComboBox* api_combobox;
+ QComboBox* shader_backend_combobox;
+ QComboBox* vsync_mode_combobox;
+ QWidget* vulkan_device_widget;
+ QWidget* api_widget;
+ QWidget* shader_backend_widget;
+ QComboBox* aspect_ratio_combobox;
+ QComboBox* resolution_combobox;
};
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 39f70e406..d09415d70 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -27,7 +27,7 @@
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="api_widget" native="true">
- <layout class="QGridLayout" name="gridLayout">
+ <layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -40,115 +40,6 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <property name="horizontalSpacing">
- <number>6</number>
- </property>
- <item row="4" column="0">
- <widget class="QWidget" name="backend_widget" native="true">
- <layout class="QHBoxLayout" name="backend_layout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="backend_label">
- <property name="text">
- <string>Shader Backend:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="backend"/>
- </item>
- </layout>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QWidget" name="device_widget" native="true">
- <layout class="QHBoxLayout" name="device_layout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="device_label">
- <property name="text">
- <string>Device:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="device"/>
- </item>
- </layout>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QWidget" name="api_layout_2" native="true">
- <layout class="QHBoxLayout" name="api_layout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="api_label">
- <property name="text">
- <string>API:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="api">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string notr="true">OpenGL</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string notr="true">Vulkan</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>None</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
</layout>
</widget>
</item>
@@ -168,111 +59,8 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <widget class="QCheckBox" name="use_disk_shader_cache">
- <property name="text">
- <string>Use disk pipeline cache</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_asynchronous_gpu_emulation">
- <property name="text">
- <string>Use asynchronous GPU emulation</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="accelerate_astc">
- <property name="text">
- <string>Accelerate ASTC texture decoding</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="vsync_mode_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="vsync_mode_label">
- <property name="text">
- <string>VSync Mode:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="vsync_mode_combobox">
- <property name="toolTip">
- <string>FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
-FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
-Mailbox can have lower latency than FIFO and does not tear but may drop frames.
-Immediate (no synchronization) just presents whatever is available and can exhibit tearing.</string>
- </property>
- <property name="currentText">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="nvdec_emulation_widget" native="true">
- <layout class="QHBoxLayout" name="nvdec_emulation_layout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="nvdec_emulation_label">
- <property name="text">
- <string>NVDEC emulation:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="nvdec_emulation">
- <item>
- <property name="text">
- <string>No Video Output</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>CPU Video Decoding</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GPU Video Decoding (Default)</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="fullscreen_mode_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <widget class="QWidget" name="graphics_widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -285,33 +73,12 @@ Immediate (no synchronization) just presents whatever is available and can exhib
<property name="bottomMargin">
<number>0</number>
</property>
- <item>
- <widget class="QLabel" name="fullscreen_mode_label">
- <property name="text">
- <string>Fullscreen Mode:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="fullscreen_mode_combobox">
- <item>
- <property name="text">
- <string>Borderless Windowed</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Exclusive Fullscreen</string>
- </property>
- </item>
- </widget>
- </item>
</layout>
</widget>
</item>
<item>
- <widget class="QWidget" name="aspect_ratio_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <widget class="QWidget" name="bg_widget" native="true">
+ <layout class="QHBoxLayout" name="bg_layout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -325,452 +92,35 @@ Immediate (no synchronization) just presents whatever is available and can exhib
<number>0</number>
</property>
<item>
- <widget class="QLabel" name="ar_label">
- <property name="text">
- <string>Aspect Ratio:</string>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="aspect_ratio_combobox">
- <item>
- <property name="text">
- <string>Default (16:9)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Force 4:3</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Force 21:9</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Force 16:10</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Stretch to Window</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="resolution_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_5">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="resolution_label">
- <property name="text">
- <string>Resolution:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="resolution_combobox">
- <item>
- <property name="text">
- <string>0.5X (360p/540p) [EXPERIMENTAL]</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>0.75X (540p/810p) [EXPERIMENTAL]</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>1X (720p/1080p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>1.5X (1080p/1620p) [EXPERIMENTAL]</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>2X (1440p/2160p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>3X (2160p/3240p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>4X (2880p/4320p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>5X (3600p/5400p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>6X (4320p/6480p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>7X (5040p/7560p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>8X (5760p/8640p)</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="scaling_filter_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_6">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="scaling_filter_label">
- <property name="text">
- <string>Window Adapting Filter:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="scaling_filter_combobox">
- <item>
- <property name="text">
- <string>Nearest Neighbor</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Bilinear</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Bicubic</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Gaussian</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>ScaleForce</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>AMD FidelityFX™️ Super Resolution</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="anti_aliasing_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="anti_aliasing_label">
- <property name="text">
- <string>Anti-Aliasing Method:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="anti_aliasing_combobox">
- <item>
- <property name="text">
- <string>None</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>FXAA</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>SMAA</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="fsr_sharpening_layout" native="true">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="sizeConstraint">
- <enum>QLayout::SetDefaultConstraint</enum>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <layout class="QHBoxLayout" name="fsr_sharpening_label_group">
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QComboBox" name="fsr_sharpening_combobox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>Use global FSR Sharpness</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Set FSR Sharpness</string>
- </property>
- </item>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="fsr_sharpening_label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>FSR Sharpness:</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="fsr_slider_layout">
- <property name="spacing">
- <number>6</number>
- </property>
- <item>
- <widget class="QSlider" name="fsr_sharpening_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="baseSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximum">
- <number>200</number>
- </property>
- <property name="sliderPosition">
- <number>25</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="invertedAppearance">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="fsr_sharpening_value">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>100%</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="bg_layout" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QComboBox" name="bg_combobox">
- <property name="currentText">
- <string>Use global background color</string>
- </property>
- <property name="currentIndex">
- <number>0</number>
- </property>
- <property name="maxVisibleItems">
- <number>10</number>
- </property>
- <item>
- <property name="text">
- <string>Use global background color</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Set background color:</string>
- </property>
- </item>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="bg_label">
<property name="text">
<string>Background Color:</string>
</property>
</widget>
</item>
<item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
<widget class="QPushButton" name="bg_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
+ <property name="text">
+ <string/>
+ </property>
</widget>
</item>
</layout>
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index c0a044767..4db18673d 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -1,104 +1,68 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <vector>
+#include <QLabel>
+#include <qnamespace.h>
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_graphics_advanced.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics_advanced.h"
+#include "yuzu/configuration/shared_translation.h"
+#include "yuzu/configuration/shared_widget.h"
-ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphicsAdvanced>()}, system{system_} {
+ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(
+ const Core::System& system_, std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphicsAdvanced>()}, system{system_} {
ui->setupUi(this);
- SetupPerGameUI();
+ Setup(builder);
SetConfiguration();
- ui->enable_compute_pipelines_checkbox->setVisible(false);
+ checkbox_enable_compute_pipelines->setVisible(false);
}
ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default;
-void ConfigureGraphicsAdvanced::SetConfiguration() {
- const bool runtime_lock = !system.IsPoweredOn();
- ui->use_reactive_flushing->setEnabled(runtime_lock);
- ui->async_present->setEnabled(runtime_lock);
- ui->renderer_force_max_clock->setEnabled(runtime_lock);
- ui->async_astc->setEnabled(runtime_lock);
- ui->astc_recompression_combobox->setEnabled(runtime_lock);
- ui->use_asynchronous_shaders->setEnabled(runtime_lock);
- ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);
- ui->enable_compute_pipelines_checkbox->setEnabled(runtime_lock);
-
- ui->async_present->setChecked(Settings::values.async_presentation.GetValue());
- ui->renderer_force_max_clock->setChecked(Settings::values.renderer_force_max_clock.GetValue());
- ui->use_reactive_flushing->setChecked(Settings::values.use_reactive_flushing.GetValue());
- ui->async_astc->setChecked(Settings::values.async_astc.GetValue());
- ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
- ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
- ui->use_vulkan_driver_pipeline_cache->setChecked(
- Settings::values.use_vulkan_driver_pipeline_cache.GetValue());
- ui->enable_compute_pipelines_checkbox->setChecked(
- Settings::values.enable_compute_pipelines.GetValue());
- ui->use_video_framerate_checkbox->setChecked(Settings::values.use_video_framerate.GetValue());
- ui->barrier_feedback_loops_checkbox->setChecked(
- Settings::values.barrier_feedback_loops.GetValue());
-
- if (Settings::IsConfiguringGlobal()) {
- ui->gpu_accuracy->setCurrentIndex(
- static_cast<int>(Settings::values.gpu_accuracy.GetValue()));
- ui->anisotropic_filtering_combobox->setCurrentIndex(
- Settings::values.max_anisotropy.GetValue());
- ui->astc_recompression_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.astc_recompression.GetValue()));
- } else {
- ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy);
- ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox,
- &Settings::values.max_anisotropy);
- ConfigurationShared::SetPerGameSetting(ui->astc_recompression_combobox,
- &Settings::values.astc_recompression);
- ConfigurationShared::SetHighlight(ui->label_gpu_accuracy,
- !Settings::values.gpu_accuracy.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->af_label,
- !Settings::values.max_anisotropy.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_astc_recompression,
- !Settings::values.astc_recompression.UsingGlobal());
+void ConfigureGraphicsAdvanced::SetConfiguration() {}
+
+void ConfigureGraphicsAdvanced::Setup(const ConfigurationShared::Builder& builder) {
+ auto& layout = *ui->populate_target->layout();
+ std::map<u32, QWidget*> hold{}; // A map will sort the data for us
+
+ for (auto setting :
+ Settings::values.linkage.by_category[Settings::Category::RendererAdvanced]) {
+ ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
+
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
+
+ hold.emplace(setting->Id(), widget);
+
+ // Keep track of enable_compute_pipelines so we can display it when needed
+ if (setting->Id() == Settings::values.enable_compute_pipelines.Id()) {
+ checkbox_enable_compute_pipelines = widget;
+ }
+ }
+ for (const auto& [id, widget] : hold) {
+ layout.addWidget(widget);
}
}
void ConfigureGraphicsAdvanced::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.gpu_accuracy, ui->gpu_accuracy);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_presentation,
- ui->async_present, async_present);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.renderer_force_max_clock,
- ui->renderer_force_max_clock,
- renderer_force_max_clock);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
- ui->anisotropic_filtering_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_reactive_flushing,
- ui->use_reactive_flushing, use_reactive_flushing);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_astc, ui->async_astc,
- async_astc);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.astc_recompression,
- ui->astc_recompression_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders,
- ui->use_asynchronous_shaders,
- use_asynchronous_shaders);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
- ui->use_fast_gpu_time, use_fast_gpu_time);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vulkan_driver_pipeline_cache,
- ui->use_vulkan_driver_pipeline_cache,
- use_vulkan_driver_pipeline_cache);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines,
- ui->enable_compute_pipelines_checkbox,
- enable_compute_pipelines);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_video_framerate,
- ui->use_video_framerate_checkbox, use_video_framerate);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.barrier_feedback_loops,
- ui->barrier_feedback_loops_checkbox,
- barrier_feedback_loops);
+ const bool is_powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(is_powered_on);
+ }
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -113,71 +77,6 @@ void ConfigureGraphicsAdvanced::RetranslateUI() {
ui->retranslateUi(this);
}
-void ConfigureGraphicsAdvanced::SetupPerGameUI() {
- // Disable if not global (only happens during game)
- if (Settings::IsConfiguringGlobal()) {
- ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());
- ui->async_present->setEnabled(Settings::values.async_presentation.UsingGlobal());
- ui->renderer_force_max_clock->setEnabled(
- Settings::values.renderer_force_max_clock.UsingGlobal());
- ui->use_reactive_flushing->setEnabled(Settings::values.use_reactive_flushing.UsingGlobal());
- ui->async_astc->setEnabled(Settings::values.async_astc.UsingGlobal());
- ui->astc_recompression_combobox->setEnabled(
- Settings::values.astc_recompression.UsingGlobal());
- ui->use_asynchronous_shaders->setEnabled(
- Settings::values.use_asynchronous_shaders.UsingGlobal());
- ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
- ui->use_vulkan_driver_pipeline_cache->setEnabled(
- Settings::values.use_vulkan_driver_pipeline_cache.UsingGlobal());
- ui->anisotropic_filtering_combobox->setEnabled(
- Settings::values.max_anisotropy.UsingGlobal());
- ui->enable_compute_pipelines_checkbox->setEnabled(
- Settings::values.enable_compute_pipelines.UsingGlobal());
- ui->use_video_framerate_checkbox->setEnabled(
- Settings::values.use_video_framerate.UsingGlobal());
- ui->barrier_feedback_loops_checkbox->setEnabled(
- Settings::values.barrier_feedback_loops.UsingGlobal());
-
- return;
- }
-
- ConfigurationShared::SetColoredTristate(ui->async_present, Settings::values.async_presentation,
- async_present);
- ConfigurationShared::SetColoredTristate(ui->renderer_force_max_clock,
- Settings::values.renderer_force_max_clock,
- renderer_force_max_clock);
- ConfigurationShared::SetColoredTristate(
- ui->use_reactive_flushing, Settings::values.use_reactive_flushing, use_reactive_flushing);
- ConfigurationShared::SetColoredTristate(ui->async_astc, Settings::values.async_astc,
- async_astc);
- ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders,
- Settings::values.use_asynchronous_shaders,
- use_asynchronous_shaders);
- ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
- Settings::values.use_fast_gpu_time, use_fast_gpu_time);
- ConfigurationShared::SetColoredTristate(ui->use_vulkan_driver_pipeline_cache,
- Settings::values.use_vulkan_driver_pipeline_cache,
- use_vulkan_driver_pipeline_cache);
- ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox,
- Settings::values.enable_compute_pipelines,
- enable_compute_pipelines);
- ConfigurationShared::SetColoredTristate(ui->use_video_framerate_checkbox,
- Settings::values.use_video_framerate,
- use_video_framerate);
- ConfigurationShared::SetColoredTristate(ui->barrier_feedback_loops_checkbox,
- Settings::values.barrier_feedback_loops,
- barrier_feedback_loops);
- ConfigurationShared::SetColoredComboBox(
- ui->gpu_accuracy, ui->label_gpu_accuracy,
- static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->anisotropic_filtering_combobox, ui->af_label,
- static_cast<int>(Settings::values.max_anisotropy.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->astc_recompression_combobox, ui->label_astc_recompression,
- static_cast<int>(Settings::values.astc_recompression.GetValue(true)));
-}
-
void ConfigureGraphicsAdvanced::ExposeComputeOption() {
- ui->enable_compute_pipelines_checkbox->setVisible(true);
+ checkbox_enable_compute_pipelines->setVisible(true);
}
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 369a7c83e..78b5389c3 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -4,51 +4,44 @@
#pragma once
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureGraphicsAdvanced;
}
-class ConfigureGraphicsAdvanced : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureGraphicsAdvanced : public ConfigurationShared::Tab {
public:
- explicit ConfigureGraphicsAdvanced(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureGraphicsAdvanced(
+ const Core::System& system_, std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
~ConfigureGraphicsAdvanced() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
void ExposeComputeOption();
private:
+ void Setup(const ConfigurationShared::Builder& builder);
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void SetupPerGameUI();
-
std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui;
- ConfigurationShared::CheckState async_present;
- ConfigurationShared::CheckState renderer_force_max_clock;
- ConfigurationShared::CheckState use_vsync;
- ConfigurationShared::CheckState async_astc;
- ConfigurationShared::CheckState use_reactive_flushing;
- ConfigurationShared::CheckState use_asynchronous_shaders;
- ConfigurationShared::CheckState use_fast_gpu_time;
- ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache;
- ConfigurationShared::CheckState enable_compute_pipelines;
- ConfigurationShared::CheckState use_video_framerate;
- ConfigurationShared::CheckState barrier_feedback_loops;
-
const Core::System& system;
+
+ std::vector<std::function<void(bool)>> apply_funcs;
+
+ QWidget* checkbox_enable_compute_pipelines{};
};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index d527a6f38..37a854ca3 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -26,8 +26,8 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
- <widget class="QWidget" name="gpu_accuracy_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <widget class="QWidget" name="populate_target" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -40,233 +40,6 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <item>
- <widget class="QLabel" name="label_gpu_accuracy">
- <property name="text">
- <string>Accuracy Level:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="gpu_accuracy">
- <item>
- <property name="text">
- <string notr="true">Normal</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string notr="true">High</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string notr="true">Extreme(very slow)</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="astc_recompression_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="label_astc_recompression">
- <property name="text">
- <string>ASTC recompression:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="astc_recompression_combobox">
- <item>
- <property name="text">
- <string>Uncompressed (Best quality)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>BC1 (Low quality)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>BC3 (Medium quality)</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="async_present">
- <property name="text">
- <string>Enable asynchronous presentation (Vulkan only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="renderer_force_max_clock">
- <property name="toolTip">
- <string>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</string>
- </property>
- <property name="text">
- <string>Force maximum clocks (Vulkan only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="async_astc">
- <property name="toolTip">
- <string>Enables asynchronous ASTC texture decoding, which may reduce load time stutter. This feature is experimental.</string>
- </property>
- <property name="text">
- <string>Decode ASTC textures asynchronously (Hack)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_reactive_flushing">
- <property name="toolTip">
- <string>Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.</string>
- </property>
- <property name="text">
- <string>Enable Reactive Flushing</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_asynchronous_shaders">
- <property name="toolTip">
- <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string>
- </property>
- <property name="text">
- <string>Use asynchronous shader building (Hack)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_fast_gpu_time">
- <property name="toolTip">
- <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string>
- </property>
- <property name="text">
- <string>Use Fast GPU Time (Hack)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache">
- <property name="toolTip">
- <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string>
- </property>
- <property name="text">
- <string>Use Vulkan pipeline cache</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="enable_compute_pipelines_checkbox">
- <property name="toolTip">
- <string>Enable compute pipelines, required by some games. This setting only exists for Intel proprietary drivers, and may crash if enabled.
-Compute pipelines are always enabled on all other drivers.</string>
- </property>
- <property name="text">
- <string>Enable Compute Pipelines (Intel Vulkan only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_video_framerate_checkbox">
- <property name="toolTip">
- <string>Run the game at normal speed during video playback, even when the framerate is unlocked.</string>
- </property>
- <property name="text">
- <string>Sync to framerate of video playback</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="barrier_feedback_loops_checkbox">
- <property name="toolTip">
- <string>Improves rendering of transparency effects in specific games.</string>
- </property>
- <property name="text">
- <string>Barrier feedback loops</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="af_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_1">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="af_label">
- <property name="text">
- <string>Anisotropic Filtering:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="anisotropic_filtering_combobox">
- <item>
- <property name="text">
- <string>Automatic</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Default</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>2x</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>4x</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>8x</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>16x</string>
- </property>
- </item>
- </widget>
- </item>
</layout>
</widget>
</item>
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 @@
#include <memory>
#include <thread>
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -197,9 +199,11 @@ void ConfigureInput::ApplyConfiguration() {
advanced->ApplyConfiguration();
- const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
- Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
- OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system);
+ const bool pre_docked_mode = Settings::IsDockedMode();
+ const bool docked_mode_selected = ui->radioDocked->isChecked();
+ Settings::values.use_docked_mode.SetValue(
+ docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
+ OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
@@ -267,8 +271,8 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
- ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
- ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
+ ui->radioDocked->setChecked(Settings::IsDockedMode());
+ ui->radioUndocked->setChecked(!Settings::IsDockedMode());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index eb96e6068..b91d6ad4a 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -17,6 +17,9 @@
#include <QTimer>
#include "common/fs/fs_util.h"
+#include "common/settings_enums.h"
+#include "common/settings_input.h"
+#include "configuration/shared_widget.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -24,9 +27,9 @@
#include "core/loader/loader.h"
#include "ui_configure_per_game.h"
#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_audio.h"
#include "yuzu/configuration/configure_cpu.h"
-#include "yuzu/configuration/configure_general.h"
#include "yuzu/configuration/configure_graphics.h"
#include "yuzu/configuration/configure_graphics_advanced.h"
#include "yuzu/configuration/configure_input_per_game.h"
@@ -41,26 +44,28 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_)
: QDialog(parent),
- ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} {
+ ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_},
+ builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
+ tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
- audio_tab = std::make_unique<ConfigureAudio>(system_, this);
- cpu_tab = std::make_unique<ConfigureCpu>(system_, this);
- general_tab = std::make_unique<ConfigureGeneral>(system_, this);
- graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this);
+ audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this);
+ cpu_tab = std::make_unique<ConfigureCpu>(system_, tab_group, *builder, this);
+ graphics_advanced_tab =
+ std::make_unique<ConfigureGraphicsAdvanced>(system_, tab_group, *builder, this);
graphics_tab = std::make_unique<ConfigureGraphics>(
- system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this);
+ system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); },
+ [](Settings::AspectRatio, Settings::ResolutionSetup) {}, tab_group, *builder, this);
input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this);
- system_tab = std::make_unique<ConfigureSystem>(system_, this);
+ system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this);
ui->setupUi(this);
ui->tabWidget->addTab(addons_tab.get(), tr("Add-Ons"));
- ui->tabWidget->addTab(general_tab.get(), tr("General"));
ui->tabWidget->addTab(system_tab.get(), tr("System"));
ui->tabWidget->addTab(cpu_tab.get(), tr("CPU"));
ui->tabWidget->addTab(graphics_tab.get(), tr("Graphics"));
@@ -88,15 +93,18 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
ConfigurePerGame::~ConfigurePerGame() = default;
void ConfigurePerGame::ApplyConfiguration() {
+ for (const auto tab : *tab_group) {
+ tab->ApplyConfiguration();
+ }
addons_tab->ApplyConfiguration();
- general_tab->ApplyConfiguration();
- cpu_tab->ApplyConfiguration();
- system_tab->ApplyConfiguration();
- graphics_tab->ApplyConfiguration();
- graphics_advanced_tab->ApplyConfiguration();
- audio_tab->ApplyConfiguration();
input_tab->ApplyConfiguration();
+ if (Settings::IsDockedMode() && Settings::values.players.GetValue()[0].controller_type ==
+ Settings::ControllerType::Handheld) {
+ Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
+ Settings::values.use_docked_mode.SetGlobal(true);
+ }
+
system.ApplySettings();
Settings::LogSettings();
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index 7ec1ded06..1a727f32c 100644
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -10,9 +10,12 @@
#include <QDialog>
#include <QList>
+#include "configuration/shared_widget.h"
#include "core/file_sys/vfs_types.h"
#include "vk_device_info.h"
#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/shared_translation.h"
namespace Core {
class System;
@@ -25,7 +28,6 @@ class InputSubsystem;
class ConfigurePerGameAddons;
class ConfigureAudio;
class ConfigureCpu;
-class ConfigureGeneral;
class ConfigureGraphics;
class ConfigureGraphicsAdvanced;
class ConfigureInputPerGame;
@@ -73,11 +75,12 @@ private:
std::unique_ptr<Config> game_config;
Core::System& system;
+ std::unique_ptr<ConfigurationShared::Builder> builder;
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> tab_group;
std::unique_ptr<ConfigurePerGameAddons> addons_tab;
std::unique_ptr<ConfigureAudio> audio_tab;
std::unique_ptr<ConfigureCpu> cpu_tab;
- std::unique_ptr<ConfigureGeneral> general_tab;
std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab;
std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureInputPerGame> input_tab;
diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui
index 85c86e107..99ba2fd18 100644
--- a/src/yuzu/configuration/configure_per_game.ui
+++ b/src/yuzu/configuration/configure_per_game.ui
@@ -2,6 +2,14 @@
<ui version="4.0">
<class>ConfigurePerGame</class>
<widget class="QDialog" name="ConfigurePerGame">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>900</width>
+ <height>607</height>
+ </rect>
+ </property>
<property name="minimumSize">
<size>
<width>900</width>
@@ -225,20 +233,31 @@
</layout>
</item>
<item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Some settings are only available when a game is not running.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index f1ae312c6..0c8e5c8b4 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -3,16 +3,23 @@
#include <chrono>
#include <optional>
+#include <vector>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDateTimeEdit>
#include <QFileDialog>
#include <QGraphicsItem>
+#include <QLineEdit>
#include <QMessageBox>
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/time/time_manager.h"
#include "ui_configure_system.h"
+#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_system.h"
+#include "yuzu/configuration/shared_widget.h"
constexpr std::array<u32, 7> LOCALE_BLOCKLIST{
// pzzefezrpnkzeidfej
@@ -37,44 +44,32 @@ static bool IsValidLocale(u32 region_index, u32 language_index) {
return ((LOCALE_BLOCKLIST.at(region_index) >> language_index) & 1) == 0;
}
-ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureSystem>()}, system{system_} {
+ConfigureSystem::ConfigureSystem(Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureSystem>()}, system{system_} {
ui->setupUi(this);
- connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
- ui->rng_seed_edit->setEnabled(state == Qt::Checked);
- if (state != Qt::Checked) {
- ui->rng_seed_edit->setText(QStringLiteral("00000000"));
- }
- });
-
- connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
- ui->custom_rtc_edit->setEnabled(state == Qt::Checked);
- if (state != Qt::Checked) {
- ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
- }
- });
+ Setup(builder);
- const auto locale_check = [this](int index) {
- const auto region_index = ConfigurationShared::GetComboboxIndex(
- Settings::values.region_index.GetValue(true), ui->combo_region);
- const auto language_index = ConfigurationShared::GetComboboxIndex(
- Settings::values.language_index.GetValue(true), ui->combo_language);
+ const auto locale_check = [this]() {
+ const auto region_index = combo_region->currentIndex();
+ const auto language_index = combo_language->currentIndex();
const bool valid_locale = IsValidLocale(region_index, language_index);
ui->label_warn_invalid_locale->setVisible(!valid_locale);
if (!valid_locale) {
ui->label_warn_invalid_locale->setText(
tr("Warning: \"%1\" is not a valid language for region \"%2\"")
- .arg(ui->combo_language->currentText())
- .arg(ui->combo_region->currentText()));
+ .arg(combo_language->currentText())
+ .arg(combo_region->currentText()));
}
};
- connect(ui->combo_language, qOverload<int>(&QComboBox::currentIndexChanged), this,
- locale_check);
- connect(ui->combo_region, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check);
+ connect(combo_language, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check);
+ connect(combo_region, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check);
- SetupPerGameUI();
+ ui->label_warn_invalid_locale->setVisible(false);
+ locale_check();
SetConfiguration();
}
@@ -93,137 +88,71 @@ void ConfigureSystem::RetranslateUI() {
ui->retranslateUi(this);
}
-void ConfigureSystem::SetConfiguration() {
- enabled = !system.IsPoweredOn();
- const auto rng_seed =
- QStringLiteral("%1")
- .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'})
- .toUpper();
- const auto rtc_time = Settings::values.custom_rtc.value_or(QDateTime::currentSecsSinceEpoch());
-
- ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value());
- ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() &&
- Settings::values.rng_seed.UsingGlobal());
- ui->rng_seed_edit->setText(rng_seed);
-
- ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
- ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value());
- ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time));
- ui->device_name_edit->setText(
- QString::fromUtf8(Settings::values.device_name.GetValue().c_str()));
- ui->use_unsafe_extended_memory_layout->setEnabled(enabled);
- ui->use_unsafe_extended_memory_layout->setChecked(
- Settings::values.use_unsafe_extended_memory_layout.GetValue());
-
- if (Settings::IsConfiguringGlobal()) {
- ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
- ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue());
- ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue());
- } else {
- ConfigurationShared::SetPerGameSetting(ui->combo_language,
- &Settings::values.language_index);
- ConfigurationShared::SetPerGameSetting(ui->combo_region, &Settings::values.region_index);
- ConfigurationShared::SetPerGameSetting(ui->combo_time_zone,
- &Settings::values.time_zone_index);
-
- ConfigurationShared::SetHighlight(ui->label_language,
- !Settings::values.language_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_region,
- !Settings::values.region_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_timezone,
- !Settings::values.time_zone_index.UsingGlobal());
- }
-}
+void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) {
+ auto& core_layout = *ui->core_widget->layout();
+ auto& system_layout = *ui->system_widget->layout();
-void ConfigureSystem::ReadSystemSettings() {}
+ std::map<u32, QWidget*> core_hold{};
+ std::map<u32, QWidget*> system_hold{};
-void ConfigureSystem::ApplyConfiguration() {
- // Allow setting custom RTC even if system is powered on,
- // to allow in-game time to be fast forwarded
- if (Settings::IsConfiguringGlobal()) {
- if (ui->custom_rtc_checkbox->isChecked()) {
- Settings::values.custom_rtc = ui->custom_rtc_edit->dateTime().toSecsSinceEpoch();
- if (system.IsPoweredOn()) {
- const s64 posix_time{*Settings::values.custom_rtc};
- system.GetTimeManager().UpdateLocalSystemClockTime(posix_time);
- }
- } else {
- Settings::values.custom_rtc = std::nullopt;
+ std::vector<Settings::BasicSetting*> settings;
+ auto push = [&settings](auto& list) {
+ for (auto setting : list) {
+ settings.push_back(setting);
}
- }
+ };
- Settings::values.device_name = ui->device_name_edit->text().toStdString();
+ push(Settings::values.linkage.by_category[Settings::Category::Core]);
+ push(Settings::values.linkage.by_category[Settings::Category::System]);
- if (!enabled) {
- return;
- }
+ for (auto setting : settings) {
+ if (setting->Id() == Settings::values.use_docked_mode.Id() &&
+ Settings::IsConfiguringGlobal()) {
+ continue;
+ }
+
+ ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, ui->combo_language);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index,
- ui->combo_time_zone);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_unsafe_extended_memory_layout,
- ui->use_unsafe_extended_memory_layout,
- use_unsafe_extended_memory_layout);
-
- if (Settings::IsConfiguringGlobal()) {
- // Guard if during game and set to game-specific value
- if (Settings::values.rng_seed.UsingGlobal()) {
- if (ui->rng_seed_checkbox->isChecked()) {
- Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16));
- } else {
- Settings::values.rng_seed.SetValue(std::nullopt);
- }
+ if (widget == nullptr) {
+ continue;
}
- } else {
- switch (use_rng_seed) {
- case ConfigurationShared::CheckState::On:
- case ConfigurationShared::CheckState::Off:
- Settings::values.rng_seed.SetGlobal(false);
- if (ui->rng_seed_checkbox->isChecked()) {
- Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16));
- } else {
- Settings::values.rng_seed.SetValue(std::nullopt);
- }
- break;
- case ConfigurationShared::CheckState::Global:
- Settings::values.rng_seed.SetGlobal(false);
- Settings::values.rng_seed.SetValue(std::nullopt);
- Settings::values.rng_seed.SetGlobal(true);
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
+
+ if (setting->Id() == Settings::values.region_index.Id()) {
+ // Keep track of the region_index (and langauge_index) combobox to validate the selected
+ // settings
+ combo_region = widget->combobox;
+ } else if (setting->Id() == Settings::values.language_index.Id()) {
+ combo_language = widget->combobox;
+ }
+
+ switch (setting->GetCategory()) {
+ case Settings::Category::Core:
+ core_hold.emplace(setting->Id(), widget);
break;
- case ConfigurationShared::CheckState::Count:
+ case Settings::Category::System:
+ system_hold.emplace(setting->Id(), widget);
break;
+ default:
+ widget->deleteLater();
}
}
+ for (const auto& [label, widget] : core_hold) {
+ core_layout.addWidget(widget);
+ }
+ for (const auto& [id, widget] : system_hold) {
+ system_layout.addWidget(widget);
+ }
}
-void ConfigureSystem::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal());
- ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal());
- ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal());
- ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal());
- ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal());
+void ConfigureSystem::SetConfiguration() {}
- return;
+void ConfigureSystem::ApplyConfiguration() {
+ const bool powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(powered_on);
}
-
- ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language,
- Settings::values.language_index.GetValue(true));
- ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region,
- Settings::values.region_index.GetValue(true));
- ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone,
- Settings::values.time_zone_index.GetValue(true));
-
- ConfigurationShared::SetColoredTristate(
- ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(),
- Settings::values.rng_seed.GetValue().has_value(),
- Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed);
-
- ConfigurationShared::SetColoredTristate(ui->use_unsafe_extended_memory_layout,
- Settings::values.use_unsafe_extended_memory_layout,
- use_unsafe_extended_memory_layout);
-
- ui->custom_rtc_checkbox->setVisible(false);
- ui->custom_rtc_edit->setVisible(false);
}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index ce1a91601..eab99a48a 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -3,45 +3,53 @@
#pragma once
+#include <functional>
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
+class QCheckBox;
+class QLineEdit;
+class QComboBox;
+class QDateTimeEdit;
namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureSystem;
}
-class ConfigureSystem : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureSystem : public ConfigurationShared::Tab {
public:
- explicit ConfigureSystem(Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureSystem(Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder,
+ QWidget* parent = nullptr);
~ConfigureSystem() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void ReadSystemSettings();
+ void Setup(const ConfigurationShared::Builder& builder);
- void SetupPerGameUI();
+ std::vector<std::function<void(bool)>> apply_funcs{};
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;
- ConfigurationShared::CheckState use_rng_seed;
- ConfigurationShared::CheckState use_unsafe_extended_memory_layout;
-
Core::System& system;
+
+ QComboBox* combo_region;
+ QComboBox* combo_language;
};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index e0caecd5e..2a735836e 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>366</width>
+ <width>605</width>
<height>483</height>
</rect>
</property>
@@ -22,470 +22,63 @@
<item>
<widget class="QGroupBox" name="group_system_settings">
<property name="title">
- <string>System Settings</string>
+ <string>System</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="0">
- <widget class="QLabel" name="label_region">
- <property name="text">
- <string>Region:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QComboBox" name="combo_time_zone">
- <item>
- <property name="text">
- <string>Auto</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Default</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>CET</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>CST6CDT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Cuba</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>EET</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Egypt</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Eire</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>EST</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>EST5EDT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GB-Eire</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GMT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GMT+0</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GMT-0</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GMT0</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Greenwich</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Hongkong</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>HST</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Iceland</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Iran</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Israel</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Jamaica</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Japan</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Kwajalein</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Libya</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>MET</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>MST</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>MST7MDT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Navajo</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>NZ</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>NZ-CHAT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Poland</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Portugal</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>PRC</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>PST8PDT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>ROC</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>ROK</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Singapore</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Turkey</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>UCT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Universal</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>UTC</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>W-SU</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>WET</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Zulu</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="combo_region">
- <item>
- <property name="text">
- <string>Japan</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>USA</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Europe</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Australia</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>China</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Korea</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Taiwan</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_timezone">
- <property name="text">
- <string>Time Zone:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="combo_language">
- <property name="toolTip">
- <string>Note: this can be overridden when region setting is auto-select</string>
- </property>
- <item>
- <property name="text">
- <string>Japanese (日本語)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>American English</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>French (français)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>German (Deutsch)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Italian (italiano)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Spanish (español)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Chinese</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Korean (한국어)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Dutch (Nederlands)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Portuguese (português)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Russian (Русский)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Taiwanese</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>British English</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Canadian French</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Latin American Spanish</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Simplified Chinese</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Traditional Chinese (正體中文)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Brazilian Portuguese (português do Brasil)</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="custom_rtc_checkbox">
- <property name="text">
- <string>Custom RTC</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_language">
- <property name="text">
- <string>Language</string>
- </property>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QCheckBox" name="rng_seed_checkbox">
- <property name="text">
- <string>RNG Seed</string>
- </property>
- </widget>
- </item>
- <item row="6" column="0">
- <widget class="QLabel" name="device_name_label">
- <property name="text">
- <string>Device Name</string>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QDateTimeEdit" name="custom_rtc_edit">
- <property name="minimumDate">
- <date>
- <year>1970</year>
- <month>1</month>
- <day>1</day>
- </date>
- </property>
- </widget>
- </item>
- <item row="6" column="1">
- <widget class="QLineEdit" name="device_name_edit">
- <property name="maxLength">
- <number>128</number>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QLineEdit" name="rng_seed_edit">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <family>Lucida Console</family>
- </font>
- </property>
- <property name="inputMask">
- <string notr="true">HHHHHHHH</string>
- </property>
- <property name="maxLength">
- <number>8</number>
- </property>
- </widget>
- </item>
- <item row="7" column="0">
- <widget class="QCheckBox" name="use_unsafe_extended_memory_layout">
- <property name="text">
- <string>Unsafe extended memory layout (8GB DRAM)</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QWidget" name="system_widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_warn_invalid_locale">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Core</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QWidget" name="core_widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
@@ -503,26 +96,6 @@
</property>
</spacer>
</item>
- <item>
- <widget class="QLabel" name="label_warn_invalid_locale">
- <property name="text">
- <string></string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_disable_info">
- <property name="text">
- <string>System settings are available only when game is not running.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
</layout>
</item>
</layout>
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 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "yuzu/configuration/configure_ui.h"
+
#include <array>
+#include <cstdlib>
+#include <set>
+#include <stdexcept>
+#include <string>
#include <utility>
-#include <QFileDialog>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QCoreApplication>
#include <QDirIterator>
+#include <QFileDialog>
+#include <QString>
+#include <QToolButton>
+#include <QVariant>
+
#include "common/common_types.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
+#include "core/frontend/framebuffer_layout.h"
#include "ui_configure_ui.h"
-#include "yuzu/configuration/configure_ui.h"
#include "yuzu/uisettings.h"
namespace {
@@ -54,8 +68,40 @@ QString GetTranslatedRowTextName(size_t index) {
}
} // Anonymous namespace
+static float GetUpFactor(Settings::ResolutionSetup res_setup) {
+ Settings::ResolutionScalingInfo info{};
+ Settings::TranslateResolutionInfo(res_setup, info);
+ return info.up_factor;
+}
+
+static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* parent) {
+ screenshot_height->clear();
+
+ const auto& enumeration =
+ Settings::EnumMetadata<Settings::ResolutionSetup>::Canonicalizations();
+ std::set<u32> resolutions{};
+ for (const auto& [name, value] : enumeration) {
+ const float up_factor = GetUpFactor(value);
+ u32 height_undocked = Layout::ScreenUndocked::Height * up_factor;
+ u32 height_docked = Layout::ScreenDocked::Height * up_factor;
+ resolutions.emplace(height_undocked);
+ resolutions.emplace(height_docked);
+ }
+
+ screenshot_height->addItem(parent->tr("Auto", "Screenshot height option"));
+ for (const auto res : resolutions) {
+ screenshot_height->addItem(QString::fromStdString(std::to_string(res)));
+ }
+}
+
+static u32 ScreenshotDimensionToInt(const QString& height) {
+ return std::strtoul(height.toUtf8(), nullptr, 0);
+}
+
ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()}, system{system_} {
+ : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()},
+ ratio{Settings::values.aspect_ratio.GetValue()},
+ resolution_setting{Settings::values.resolution_setup.GetValue()}, system{system_} {
ui->setupUi(this);
InitializeLanguageComboBox();
@@ -68,6 +114,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
InitializeIconSizeComboBox();
InitializeRowComboBoxes();
+ PopulateResolutionComboBox(ui->screenshot_height, this);
+
SetConfiguration();
// Force game list reload if any of the relevant settings are changed.
@@ -104,6 +152,10 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
ui->screenshot_path_edit->setText(dir);
}
});
+
+ connect(ui->screenshot_height, &QComboBox::currentTextChanged, [this]() { UpdateWidthText(); });
+
+ UpdateWidthText();
}
ConfigureUi::~ConfigureUi() = default;
@@ -123,6 +175,10 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir,
ui->screenshot_path_edit->text().toStdString());
+
+ const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
+ UISettings::values.screenshot_height.SetValue(height);
+
system.ApplySettings();
}
@@ -147,6 +203,13 @@ void ConfigureUi::SetConfiguration() {
UISettings::values.enable_screenshot_save_as.GetValue());
ui->screenshot_path_edit->setText(QString::fromStdString(
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)));
+
+ const auto height = UISettings::values.screenshot_height.GetValue();
+ if (height == 0) {
+ ui->screenshot_height->setCurrentIndex(0);
+ } else {
+ ui->screenshot_height->setCurrentText(QStringLiteral("%1").arg(height));
+ }
}
void ConfigureUi::changeEvent(QEvent* event) {
@@ -317,3 +380,29 @@ void ConfigureUi::OnLanguageChanged(int index) {
emit LanguageChanged(ui->language_combobox->itemData(index).toString());
}
+
+void ConfigureUi::UpdateWidthText() {
+ const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
+ const u32 width = UISettings::CalculateWidth(height, ratio);
+ if (height == 0) {
+ const auto up_factor = GetUpFactor(resolution_setting);
+ const u32 height_docked = Layout::ScreenDocked::Height * up_factor;
+ const u32 width_docked = UISettings::CalculateWidth(height_docked, ratio);
+ const u32 height_undocked = Layout::ScreenUndocked::Height * up_factor;
+ const u32 width_undocked = UISettings::CalculateWidth(height_undocked, ratio);
+ ui->screenshot_width->setText(tr("Auto (%1 x %2, %3 x %4)", "Screenshot width value")
+ .arg(width_undocked)
+ .arg(height_undocked)
+ .arg(width_docked)
+ .arg(height_docked));
+ } else {
+ ui->screenshot_width->setText(QStringLiteral("%1 x").arg(width));
+ }
+}
+
+void ConfigureUi::UpdateScreenshotInfo(Settings::AspectRatio ratio_,
+ Settings::ResolutionSetup resolution_setting_) {
+ ratio = ratio_;
+ resolution_setting = resolution_setting_;
+ UpdateWidthText();
+}
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 @@
#include <memory>
#include <QWidget>
+#include "common/settings_enums.h"
namespace Core {
class System;
@@ -23,6 +24,9 @@ public:
void ApplyConfiguration();
+ void UpdateScreenshotInfo(Settings::AspectRatio ratio,
+ Settings::ResolutionSetup resolution_info);
+
private slots:
void OnLanguageChanged(int index);
@@ -44,7 +48,11 @@ private:
void UpdateFirstRowComboBox(bool init = false);
void UpdateSecondRowComboBox(bool init = false);
+ void UpdateWidthText();
+
std::unique_ptr<Ui::ConfigureUi> ui;
+ Settings::AspectRatio ratio;
+ Settings::ResolutionSetup resolution_setting;
Core::System& system;
};
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 @@
<x>0</x>
<y>0</y>
<width>363</width>
- <height>562</height>
+ <height>603</height>
</rect>
</property>
<property name="windowTitle">
@@ -201,6 +201,41 @@
</item>
</layout>
</item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QLabel" name="screenshot_width">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="screenshot_height">
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Resolution:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</item>
</layout>
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
new file mode 100644
index 000000000..276bdbaba
--- /dev/null
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -0,0 +1,391 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/time_zone.h"
+#include "yuzu/configuration/shared_translation.h"
+
+#include <map>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <QWidget>
+#include "common/settings.h"
+#include "common/settings_enums.h"
+#include "common/settings_setting.h"
+#include "yuzu/uisettings.h"
+
+namespace ConfigurationShared {
+
+std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
+ std::unique_ptr<TranslationMap> translations = std::make_unique<TranslationMap>();
+ const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); };
+
+#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \
+ translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{tr((NAME)), tr((TOOLTIP))}})
+
+ // A setting can be ignored by giving it a blank name
+
+ // Audio
+ INSERT(Settings, sink_id, "Output Engine:", "");
+ INSERT(Settings, audio_output_device_id, "Output Device:", "");
+ INSERT(Settings, audio_input_device_id, "Input Device:", "");
+ INSERT(Settings, audio_muted, "Mute audio when in background", "");
+ INSERT(Settings, volume, "Volume:", "");
+ INSERT(Settings, dump_audio_commands, "", "");
+
+ // Core
+ INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
+ INSERT(Settings, memory_layout_mode, "Memory Layout", "");
+ INSERT(Settings, use_speed_limit, "", "");
+ INSERT(Settings, speed_limit, "Limit Speed Percent", "");
+
+ // Cpu
+ INSERT(Settings, cpu_accuracy, "Accuracy:", "");
+
+ // Cpu Debug
+
+ // Cpu Unsafe
+ INSERT(Settings, cpuopt_unsafe_unfuse_fma,
+ "Unfuse FMA (improve performance on CPUs without FMA)",
+ "This option improves speed by reducing accuracy of fused-multiply-add instructions on "
+ "CPUs without native FMA support.");
+ INSERT(Settings, cpuopt_unsafe_reduce_fp_error, "Faster FRSQRTE and FRECPE",
+ "This option improves the speed of some approximate floating-point functions by using "
+ "less accurate native approximations.");
+ INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr, "Faster ASIMD instructions (32 bits only)",
+ "This option improves the speed of 32 bits ASIMD floating-point functions by running "
+ "with incorrect rounding modes.");
+ INSERT(Settings, cpuopt_unsafe_inaccurate_nan, "Inaccurate NaN handling",
+ "This option improves speed by removing NaN checking. Please note this also reduces "
+ "accuracy of certain floating-point instructions.");
+ INSERT(
+ Settings, cpuopt_unsafe_fastmem_check, "Disable address space checks",
+ "This option improves speed by eliminating a safety check before every memory read/write "
+ "in guest. Disabling it may allow a game to read/write the emulator's memory.");
+ INSERT(Settings, cpuopt_unsafe_ignore_global_monitor, "Ignore global monitor",
+ "This option improves speed by relying only on the semantics of cmpxchg to ensure "
+ "safety of exclusive access instructions. Please note this may result in deadlocks and "
+ "other race conditions.");
+
+ // Renderer
+ INSERT(Settings, renderer_backend, "API:", "");
+ INSERT(Settings, vulkan_device, "Device:", "");
+ INSERT(Settings, shader_backend, "Shader Backend:", "");
+ INSERT(Settings, resolution_setup, "Resolution:", "");
+ INSERT(Settings, scaling_filter, "Window Adapting Filter:", "");
+ INSERT(Settings, fsr_sharpening_slider, "FSR Sharpness:", "");
+ INSERT(Settings, anti_aliasing, "Anti-Aliasing Method:", "");
+ INSERT(Settings, fullscreen_mode, "Fullscreen Mode:", "");
+ INSERT(Settings, aspect_ratio, "Aspect Ratio:", "");
+ INSERT(Settings, use_disk_shader_cache, "Use disk pipeline cache", "");
+ INSERT(Settings, use_asynchronous_gpu_emulation, "Use asynchronous GPU emulation", "");
+ INSERT(Settings, nvdec_emulation, "NVDEC emulation:", "");
+ INSERT(Settings, accelerate_astc, "ASTC Decoding Method:", "");
+ INSERT(Settings, astc_recompression, "ASTC Recompression Method:", "");
+ INSERT(Settings, vsync_mode, "VSync Mode:",
+ "FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
+ "refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from "
+ "a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop "
+ "frames.\nImmediate (no synchronization) just presents whatever is available and can "
+ "exhibit tearing.");
+ INSERT(Settings, bg_red, "", "");
+ INSERT(Settings, bg_green, "", "");
+ INSERT(Settings, bg_blue, "", "");
+
+ // Renderer (Advanced Graphics)
+ INSERT(Settings, async_presentation, "Enable asynchronous presentation (Vulkan only)", "");
+ INSERT(Settings, renderer_force_max_clock, "Force maximum clocks (Vulkan only)",
+ "Runs work in the background while waiting for graphics commands to keep the GPU from "
+ "lowering its clock speed.");
+ INSERT(Settings, max_anisotropy, "Anisotropic Filtering:", "");
+ INSERT(Settings, gpu_accuracy, "Accuracy Level:", "");
+ INSERT(Settings, use_asynchronous_shaders, "Use asynchronous shader building (Hack)",
+ "Enables asynchronous shader compilation, which may reduce shader stutter. This feature "
+ "is experimental.");
+ INSERT(Settings, use_fast_gpu_time, "Use Fast GPU Time (Hack)",
+ "Enables Fast GPU Time. This option will force most games to run at their highest "
+ "native resolution.");
+ INSERT(Settings, use_vulkan_driver_pipeline_cache, "Use Vulkan pipeline cache",
+ "Enables GPU vendor-specific pipeline cache. This option can improve shader loading "
+ "time significantly in cases where the Vulkan driver does not store pipeline cache "
+ "files internally.");
+ INSERT(Settings, enable_compute_pipelines, "Enable Compute Pipelines (Intel Vulkan Only)",
+ "Enable compute pipelines, required by some games.\nThis setting only exists for Intel "
+ "proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled "
+ "on all other drivers.");
+ INSERT(Settings, use_reactive_flushing, "Enable Reactive Flushing",
+ "Uses reactive flushing instead of predictive flushing, allowing more accurate memory "
+ "syncing.");
+ INSERT(Settings, use_video_framerate, "Sync to framerate of video playback",
+ "Run the game at normal speed during video playback, even when the framerate is "
+ "unlocked.");
+ INSERT(Settings, barrier_feedback_loops, "Barrier feedback loops",
+ "Improves rendering of transparency effects in specific games.");
+
+ // Renderer (Debug)
+
+ // System
+ INSERT(Settings, rng_seed, "RNG Seed", "");
+ INSERT(Settings, rng_seed_enabled, "", "");
+ INSERT(Settings, device_name, "Device Name", "");
+ INSERT(Settings, custom_rtc, "Custom RTC", "");
+ INSERT(Settings, custom_rtc_enabled, "", "");
+ INSERT(Settings, language_index,
+ "Language:", "Note: this can be overridden when region setting is auto-select");
+ INSERT(Settings, region_index, "Region:", "");
+ INSERT(Settings, time_zone_index, "Time Zone:", "");
+ INSERT(Settings, sound_index, "Sound Output Mode:", "");
+ INSERT(Settings, use_docked_mode, "Console Mode:", "");
+ INSERT(Settings, current_user, "", "");
+
+ // Controls
+
+ // Data Storage
+
+ // Debugging
+
+ // Debugging Graphics
+
+ // Network
+
+ // Web Service
+
+ // Ui
+
+ // Ui General
+ INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
+ INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
+ INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
+ INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
+ INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
+
+ // Ui Debugging
+
+ // Ui Multiplayer
+
+ // Ui Games list
+
+#undef INSERT
+
+ return translations;
+}
+
+std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
+ std::unique_ptr<ComboboxTranslationMap> translations =
+ std::make_unique<ComboboxTranslationMap>();
+ const auto& tr = [&](const char* text, const char* context = "") {
+ return parent->tr(text, context);
+ };
+
+#define PAIR(ENUM, VALUE, TRANSLATION) \
+ { static_cast<u32>(Settings::ENUM::VALUE), tr(TRANSLATION) }
+#define CTX_PAIR(ENUM, VALUE, TRANSLATION, CONTEXT) \
+ { static_cast<u32>(Settings::ENUM::VALUE), tr(TRANSLATION, CONTEXT) }
+
+ // Intentionally skipping VSyncMode to let the UI fill that one out
+
+ translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(),
+ {
+ PAIR(AstcDecodeMode, Cpu, "CPU"),
+ PAIR(AstcDecodeMode, Gpu, "GPU"),
+ PAIR(AstcDecodeMode, CpuAsynchronous, "CPU Asynchronous"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AstcRecompression>::Index(),
+ {
+ PAIR(AstcRecompression, Uncompressed, "Uncompressed (Best quality)"),
+ PAIR(AstcRecompression, Bc1, "BC1 (Low quality)"),
+ PAIR(AstcRecompression, Bc3, "BC3 (Medium quality)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(),
+ {
+#ifdef HAS_OPENGL
+ PAIR(RendererBackend, OpenGL, "OpenGL"),
+#endif
+ PAIR(RendererBackend, Vulkan, "Vulkan"),
+ PAIR(RendererBackend, Null, "Null"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
+ {
+ PAIR(ShaderBackend, Glsl, "GLSL"),
+ PAIR(ShaderBackend, Glasm, "GLASM (Assembly Shaders, NVIDIA Only)"),
+ PAIR(ShaderBackend, SpirV, "SPIR-V (Experimental, Mesa Only)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(),
+ {
+ PAIR(GpuAccuracy, Normal, "Normal"),
+ PAIR(GpuAccuracy, High, "High"),
+ PAIR(GpuAccuracy, Extreme, "Extreme"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
+ {
+ PAIR(CpuAccuracy, Auto, "Auto"),
+ PAIR(CpuAccuracy, Accurate, "Accurate"),
+ PAIR(CpuAccuracy, Unsafe, "Unsafe"),
+ PAIR(CpuAccuracy, Paranoid, "Paranoid (disables most optimizations)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::FullscreenMode>::Index(),
+ {
+ PAIR(FullscreenMode, Borderless, "Borderless Windowed"),
+ PAIR(FullscreenMode, Exclusive, "Exclusive Fullscreen"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::NvdecEmulation>::Index(),
+ {
+ PAIR(NvdecEmulation, Off, "No Video Output"),
+ PAIR(NvdecEmulation, Cpu, "CPU Video Decoding"),
+ PAIR(NvdecEmulation, Gpu, "GPU Video Decoding (Default)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::ResolutionSetup>::Index(),
+ {
+ PAIR(ResolutionSetup, Res1_2X, "0.5X (360p/540p) [EXPERIMENTAL]"),
+ PAIR(ResolutionSetup, Res3_4X, "0.75X (540p/810p) [EXPERIMENTAL]"),
+ PAIR(ResolutionSetup, Res1X, "1X (720p/1080p)"),
+ PAIR(ResolutionSetup, Res3_2X, "1.5X (1080p/1620p) [EXPERIMENTAL]"),
+ PAIR(ResolutionSetup, Res2X, "2X (1440p/2160p)"),
+ PAIR(ResolutionSetup, Res3X, "3X (2160p/3240p)"),
+ PAIR(ResolutionSetup, Res4X, "4X (2880p/4320p)"),
+ PAIR(ResolutionSetup, Res5X, "5X (3600p/5400p)"),
+ PAIR(ResolutionSetup, Res6X, "6X (4320p/6480p)"),
+ PAIR(ResolutionSetup, Res7X, "7X (5040p/7560p)"),
+ PAIR(ResolutionSetup, Res8X, "8X (5760p/8640p)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::ScalingFilter>::Index(),
+ {
+ PAIR(ScalingFilter, NearestNeighbor, "Nearest Neighbor"),
+ PAIR(ScalingFilter, Bilinear, "Bilinear"),
+ PAIR(ScalingFilter, Bicubic, "Bicubic"),
+ PAIR(ScalingFilter, Gaussian, "Gaussian"),
+ PAIR(ScalingFilter, ScaleForce, "ScaleForce"),
+ PAIR(ScalingFilter, Fsr, "AMD FidelityFX™️ Super Resolution"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AntiAliasing>::Index(),
+ {
+ PAIR(AntiAliasing, None, "None"),
+ PAIR(AntiAliasing, Fxaa, "FXAA"),
+ PAIR(AntiAliasing, Smaa, "SMAA"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AspectRatio>::Index(),
+ {
+ PAIR(AspectRatio, R16_9, "Default (16:9)"),
+ PAIR(AspectRatio, R4_3, "Force 4:3"),
+ PAIR(AspectRatio, R21_9, "Force 21:9"),
+ PAIR(AspectRatio, R16_10, "Force 16:10"),
+ PAIR(AspectRatio, Stretch, "Stretch to Window"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AnisotropyMode>::Index(),
+ {
+ PAIR(AnisotropyMode, Automatic, "Automatic"),
+ PAIR(AnisotropyMode, Default, "Default"),
+ PAIR(AnisotropyMode, X2, "2x"),
+ PAIR(AnisotropyMode, X4, "4x"),
+ PAIR(AnisotropyMode, X8, "8x"),
+ PAIR(AnisotropyMode, X16, "16x"),
+ }});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::Language>::Index(),
+ {
+ PAIR(Language, Japanese, "Japanese (日本語)"),
+ PAIR(Language, EnglishAmerican, "American English"),
+ PAIR(Language, French, "French (français)"),
+ PAIR(Language, German, "German (Deutsch)"),
+ PAIR(Language, Italian, "Italian (italiano)"),
+ PAIR(Language, Spanish, "Spanish (español)"),
+ PAIR(Language, Chinese, "Chinese"),
+ PAIR(Language, Korean, "Korean (한국어)"),
+ PAIR(Language, Dutch, "Dutch (Nederlands)"),
+ PAIR(Language, Portuguese, "Portuguese (português)"),
+ PAIR(Language, Russian, "Russian (Русский)"),
+ PAIR(Language, Taiwanese, "Taiwanese"),
+ PAIR(Language, EnglishBritish, "British English"),
+ PAIR(Language, FrenchCanadian, "Canadian French"),
+ PAIR(Language, SpanishLatin, "Latin American Spanish"),
+ PAIR(Language, ChineseSimplified, "Simplified Chinese"),
+ PAIR(Language, ChineseTraditional, "Traditional Chinese (正體中文)"),
+ PAIR(Language, PortugueseBrazilian, "Brazilian Portuguese (português do Brasil)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::Region>::Index(),
+ {
+ PAIR(Region, Japan, "Japan"),
+ PAIR(Region, Usa, "USA"),
+ PAIR(Region, Europe, "Europe"),
+ PAIR(Region, Australia, "Australia"),
+ PAIR(Region, China, "China"),
+ PAIR(Region, Korea, "Korea"),
+ PAIR(Region, Taiwan, "Taiwan"),
+ }});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::TimeZone>::Index(),
+ {
+ {static_cast<u32>(Settings::TimeZone::Auto),
+ tr("Auto (%1)", "Auto select time zone")
+ .arg(QString::fromStdString(
+ Settings::GetTimeZoneString(Settings::TimeZone::Auto)))},
+ {static_cast<u32>(Settings::TimeZone::Default),
+ tr("Default (%1)", "Default time zone")
+ .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))},
+ PAIR(TimeZone, Cet, "CET"),
+ PAIR(TimeZone, Cst6Cdt, "CST6CDT"),
+ PAIR(TimeZone, Cuba, "Cuba"),
+ PAIR(TimeZone, Eet, "EET"),
+ PAIR(TimeZone, Egypt, "Egypt"),
+ PAIR(TimeZone, Eire, "Eire"),
+ PAIR(TimeZone, Est, "EST"),
+ PAIR(TimeZone, Est5Edt, "EST5EDT"),
+ PAIR(TimeZone, Gb, "GB"),
+ PAIR(TimeZone, GbEire, "GB-Eire"),
+ PAIR(TimeZone, Gmt, "GMT"),
+ PAIR(TimeZone, GmtPlusZero, "GMT+0"),
+ PAIR(TimeZone, GmtMinusZero, "GMT-0"),
+ PAIR(TimeZone, GmtZero, "GMT0"),
+ PAIR(TimeZone, Greenwich, "Greenwich"),
+ PAIR(TimeZone, Hongkong, "Hongkong"),
+ PAIR(TimeZone, Hst, "HST"),
+ PAIR(TimeZone, Iceland, "Iceland"),
+ PAIR(TimeZone, Iran, "Iran"),
+ PAIR(TimeZone, Israel, "Israel"),
+ PAIR(TimeZone, Jamaica, "Jamaica"),
+ PAIR(TimeZone, Japan, "Japan"),
+ PAIR(TimeZone, Kwajalein, "Kwajalein"),
+ PAIR(TimeZone, Libya, "Libya"),
+ PAIR(TimeZone, Met, "MET"),
+ PAIR(TimeZone, Mst, "MST"),
+ PAIR(TimeZone, Mst7Mdt, "MST7MDT"),
+ PAIR(TimeZone, Navajo, "Navajo"),
+ PAIR(TimeZone, Nz, "NZ"),
+ PAIR(TimeZone, NzChat, "NZ-CHAT"),
+ PAIR(TimeZone, Poland, "Poland"),
+ PAIR(TimeZone, Portugal, "Portugal"),
+ PAIR(TimeZone, Prc, "PRC"),
+ PAIR(TimeZone, Pst8Pdt, "PST8PDT"),
+ PAIR(TimeZone, Roc, "ROC"),
+ PAIR(TimeZone, Rok, "ROK"),
+ PAIR(TimeZone, Singapore, "Singapore"),
+ PAIR(TimeZone, Turkey, "Turkey"),
+ PAIR(TimeZone, Uct, "UCT"),
+ PAIR(TimeZone, Universal, "Universal"),
+ PAIR(TimeZone, Utc, "UTC"),
+ PAIR(TimeZone, WSu, "W-SU"),
+ PAIR(TimeZone, Wet, "WET"),
+ PAIR(TimeZone, Zulu, "Zulu"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AudioMode>::Index(),
+ {
+ PAIR(AudioMode, Mono, "Mono"),
+ PAIR(AudioMode, Stereo, "Stereo"),
+ PAIR(AudioMode, Surround, "Surround"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::MemoryLayout>::Index(),
+ {
+ PAIR(MemoryLayout, Memory_4Gb, "4GB DRAM (Default)"),
+ PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"),
+ PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"),
+ }});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
+ {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
+
+#undef PAIR
+#undef CTX_PAIR
+
+ return translations;
+}
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/shared_translation.h b/src/yuzu/configuration/shared_translation.h
new file mode 100644
index 000000000..99a0e808c
--- /dev/null
+++ b/src/yuzu/configuration/shared_translation.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <typeindex>
+#include <utility>
+#include <vector>
+#include <QString>
+#include "common/common_types.h"
+
+class QWidget;
+
+namespace ConfigurationShared {
+using TranslationMap = std::map<u32, std::pair<QString, QString>>;
+using ComboboxTranslations = std::vector<std::pair<u32, QString>>;
+using ComboboxTranslationMap = std::map<u32, ComboboxTranslations>;
+
+std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent);
+
+std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent);
+
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
new file mode 100644
index 000000000..ea8d7add4
--- /dev/null
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -0,0 +1,802 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "yuzu/configuration/shared_widget.h"
+
+#include <functional>
+#include <limits>
+#include <typeindex>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include <QAbstractButton>
+#include <QAbstractSlider>
+#include <QBoxLayout>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDateTime>
+#include <QDateTimeEdit>
+#include <QIcon>
+#include <QLabel>
+#include <QLayout>
+#include <QLineEdit>
+#include <QObject>
+#include <QPushButton>
+#include <QRadioButton>
+#include <QRegularExpression>
+#include <QSizePolicy>
+#include <QSlider>
+#include <QSpinBox>
+#include <QStyle>
+#include <QValidator>
+#include <QVariant>
+#include <QtCore/qglobal.h>
+#include <QtCore/qobjectdefs.h>
+#include <fmt/core.h>
+#include <qglobal.h>
+#include <qnamespace.h>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/settings_common.h"
+#include "yuzu/configuration/shared_translation.h"
+
+namespace ConfigurationShared {
+
+static int restore_button_count = 0;
+
+static std::string RelevantDefault(const Settings::BasicSetting& setting) {
+ return Settings::IsConfiguringGlobal() ? setting.DefaultToString() : setting.ToStringGlobal();
+}
+
+static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) {
+ const auto tr = [parent](const char* text, const char* context) {
+ return parent->tr(text, context);
+ };
+
+ if ((setting.Specialization() & Settings::SpecializationAttributeMask) ==
+ Settings::Specialization::Percentage) {
+ std::string context{fmt::format("{} percentage (e.g. 50%)", setting.GetLabel())};
+ return tr("%", context.c_str());
+ }
+
+ return default_suffix;
+}
+
+QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) {
+ restore_button_count++;
+
+ QStyle* style = parent->style();
+ QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton));
+ QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent);
+ restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count));
+ restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+
+ // Workaround for dark theme causing min-width to be much larger than 0
+ restore_button->setStyleSheet(
+ QStringLiteral("QAbstractButton#%1 { min-width: 0px }").arg(restore_button->objectName()));
+
+ QSizePolicy sp_retain = restore_button->sizePolicy();
+ sp_retain.setRetainSizeWhenHidden(true);
+ restore_button->setSizePolicy(sp_retain);
+
+ restore_button->setEnabled(!using_global);
+ restore_button->setVisible(!using_global);
+
+ return restore_button;
+}
+
+QLabel* Widget::CreateLabel(const QString& text) {
+ QLabel* qt_label = new QLabel(text, this->parent);
+ qt_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ return qt_label;
+}
+
+QWidget* Widget::CreateCheckBox(Settings::BasicSetting* bool_setting, const QString& label,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ checkbox = new QCheckBox(label, this);
+ checkbox->setCheckState(bool_setting->ToString() == "true" ? Qt::CheckState::Checked
+ : Qt::CheckState::Unchecked);
+ checkbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ if (!bool_setting->Save() && !Settings::IsConfiguringGlobal() && runtime_lock) {
+ checkbox->setEnabled(false);
+ }
+
+ serializer = [this]() {
+ return checkbox->checkState() == Qt::CheckState::Checked ? "true" : "false";
+ };
+
+ restore_func = [this, bool_setting]() {
+ checkbox->setCheckState(RelevantDefault(*bool_setting) == "true" ? Qt::Checked
+ : Qt::Unchecked);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(checkbox, &QCheckBox::clicked, [touch]() { touch(); });
+ }
+
+ return checkbox;
+}
+
+QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto type = setting.EnumIndex();
+
+ combobox = new QComboBox(this);
+ combobox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ const ComboboxTranslations* enumeration{nullptr};
+ if (combobox_enumerations.contains(type)) {
+ enumeration = &combobox_enumerations.at(type);
+ for (const auto& [id, name] : *enumeration) {
+ combobox->addItem(name);
+ }
+ } else {
+ return combobox;
+ }
+
+ const auto find_index = [=](u32 value) -> int {
+ for (u32 i = 0; i < enumeration->size(); i++) {
+ if (enumeration->at(i).first == value) {
+ return i;
+ }
+ }
+ return -1;
+ };
+
+ const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
+ combobox->setCurrentIndex(find_index(setting_value));
+
+ serializer = [this, enumeration]() {
+ int current = combobox->currentIndex();
+ return std::to_string(enumeration->at(current).first);
+ };
+
+ restore_func = [this, find_index]() {
+ const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
+ combobox->setCurrentIndex(find_index(global_value));
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(combobox, QOverload<int>::of(&QComboBox::activated),
+ [touch]() { touch(); });
+ }
+
+ return combobox;
+}
+
+QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto type = setting.EnumIndex();
+
+ QWidget* group = new QWidget(this);
+ QHBoxLayout* layout = new QHBoxLayout(group);
+ layout->setContentsMargins(0, 0, 0, 0);
+ group->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ const ComboboxTranslations* enumeration{nullptr};
+ if (combobox_enumerations.contains(type)) {
+ enumeration = &combobox_enumerations.at(type);
+ for (const auto& [id, name] : *enumeration) {
+ QRadioButton* radio_button = new QRadioButton(name, group);
+ layout->addWidget(radio_button);
+ radio_buttons.push_back({id, radio_button});
+ }
+ } else {
+ return group;
+ }
+
+ const auto get_selected = [=]() -> int {
+ for (const auto& [id, button] : radio_buttons) {
+ if (button->isChecked()) {
+ return id;
+ }
+ }
+ return -1;
+ };
+
+ const auto set_index = [=](u32 value) {
+ for (const auto& [id, button] : radio_buttons) {
+ button->setChecked(id == value);
+ }
+ };
+
+ const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
+ set_index(setting_value);
+
+ serializer = [get_selected]() {
+ int current = get_selected();
+ return std::to_string(current);
+ };
+
+ restore_func = [this, set_index]() {
+ const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
+ set_index(global_value);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ for (const auto& [id, button] : radio_buttons) {
+ QObject::connect(button, &QAbstractButton::clicked, [touch]() { touch(); });
+ }
+ }
+
+ return group;
+}
+
+QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch, bool managed) {
+ const QString text = QString::fromStdString(setting.ToString());
+ line_edit = new QLineEdit(this);
+ line_edit->setText(text);
+
+ serializer = [this]() { return line_edit->text().toStdString(); };
+
+ if (!managed) {
+ return line_edit;
+ }
+
+ restore_func = [this]() {
+ line_edit->setText(QString::fromStdString(RelevantDefault(setting)));
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(line_edit, &QLineEdit::textChanged, [touch]() { touch(); });
+ }
+
+ return line_edit;
+}
+
+static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
+ QLabel* feedback, const QString& use_format, QSlider* slider,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func) {
+ const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
+
+ const auto update_feedback = [=](int value) {
+ int present = (reversed ? max_val - value : value) * multiplier + 0.5f;
+ feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
+ };
+
+ QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
+ update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0));
+
+ slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0));
+ slider->setMaximum(max_val);
+ slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0));
+
+ serializer = [slider]() { return std::to_string(slider->value()); };
+ restore_func = [slider, &setting]() {
+ slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0));
+ };
+}
+
+static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
+ QLabel* feedback, const QString& use_format, QSlider* slider,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func) {
+ const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr);
+ const float min_val = std::strtof(setting.MinVal().c_str(), nullptr);
+ const float use_multiplier =
+ multiplier == default_multiplier ? default_float_multiplier : multiplier;
+
+ const auto update_feedback = [=](float value) {
+ int present = (reversed ? max_val - value : value) + 0.5f;
+ feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
+ };
+
+ QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
+ update_feedback(std::strtof(setting.ToString().c_str(), nullptr));
+
+ slider->setMinimum(min_val * use_multiplier);
+ slider->setMaximum(max_val * use_multiplier);
+ slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier);
+
+ serializer = [slider, use_multiplier]() {
+ return std::to_string(slider->value() / use_multiplier);
+ };
+ restore_func = [slider, &setting, use_multiplier]() {
+ slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier);
+ };
+}
+
+QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ if (!setting.Ranged()) {
+ LOG_ERROR(Frontend, "\"{}\" is not a ranged setting, but a slider was requested.",
+ setting.GetLabel());
+ return nullptr;
+ }
+
+ QWidget* container = new QWidget(this);
+ QHBoxLayout* layout = new QHBoxLayout(container);
+
+ slider = new QSlider(Qt::Horizontal, this);
+ QLabel* feedback = new QLabel(this);
+
+ layout->addWidget(slider);
+ layout->addWidget(feedback);
+
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
+
+ const QString use_format = QStringLiteral("%1").append(suffix);
+
+ if (setting.IsIntegral()) {
+ CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
+ restore_func);
+ } else {
+ CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
+ restore_func);
+ }
+
+ slider->setInvertedAppearance(reversed);
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); });
+ }
+
+ return container;
+}
+
+QWidget* Widget::CreateSpinBox(const QString& given_suffix,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0);
+ const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
+ const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0);
+
+ QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
+
+ spinbox = new QSpinBox(this);
+ spinbox->setRange(min_val, max_val);
+ spinbox->setValue(default_val);
+ spinbox->setSuffix(suffix);
+ spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ serializer = [this]() { return std::to_string(spinbox->value()); };
+
+ restore_func = [this]() {
+ auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)};
+ spinbox->setValue(value);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() {
+ if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) {
+ touch();
+ }
+ });
+ }
+
+ return spinbox;
+}
+
+QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr);
+ const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr);
+ const auto default_val = std::strtod(setting.ToString().c_str(), nullptr);
+
+ QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
+
+ double_spinbox = new QDoubleSpinBox(this);
+ double_spinbox->setRange(min_val, max_val);
+ double_spinbox->setValue(default_val);
+ double_spinbox->setSuffix(suffix);
+ double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); };
+
+ restore_func = [this]() {
+ auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)};
+ double_spinbox->setValue(value);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
+ [this, touch]() {
+ if (double_spinbox->value() !=
+ std::strtod(setting.ToStringGlobal().c_str(), nullptr)) {
+ touch();
+ }
+ });
+ }
+
+ return double_spinbox;
+}
+
+QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ auto* data_component = CreateLineEdit(serializer, restore_func, touch, false);
+ if (data_component == nullptr) {
+ return nullptr;
+ }
+
+ auto to_hex = [=](const std::string& input) {
+ return QString::fromStdString(
+ fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0)));
+ };
+
+ QRegularExpressionValidator* regex = new QRegularExpressionValidator(
+ QRegularExpression{QStringLiteral("^[0-9a-fA-F]{0,8}$")}, line_edit);
+
+ const QString default_val = to_hex(setting.ToString());
+
+ line_edit->setText(default_val);
+ line_edit->setMaxLength(8);
+ line_edit->setValidator(regex);
+
+ auto hex_to_dec = [this]() -> std::string {
+ return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16));
+ };
+
+ serializer = [hex_to_dec]() { return hex_to_dec(); };
+
+ restore_func = [this, to_hex]() { line_edit->setText(to_hex(RelevantDefault(setting))); };
+
+ if (!Settings::IsConfiguringGlobal()) {
+
+ QObject::connect(line_edit, &QLineEdit::textChanged, [touch]() { touch(); });
+ }
+
+ return line_edit;
+}
+
+QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const long long current_time = QDateTime::currentSecsSinceEpoch();
+ const s64 the_time =
+ disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0);
+ const auto default_val = QDateTime::fromSecsSinceEpoch(the_time);
+
+ date_time_edit = new QDateTimeEdit(this);
+ date_time_edit->setDateTime(default_val);
+ date_time_edit->setMinimumDateTime(QDateTime::fromSecsSinceEpoch(0));
+ date_time_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ serializer = [this]() { return std::to_string(date_time_edit->dateTime().toSecsSinceEpoch()); };
+
+ auto get_clear_val = [this, restrict, current_time]() {
+ return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() {
+ if (restrict && checkbox->checkState() == Qt::Checked) {
+ return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0);
+ }
+ return current_time;
+ }());
+ };
+
+ restore_func = [this, get_clear_val]() { date_time_edit->setDateTime(get_clear_val()); };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(date_time_edit, &QDateTimeEdit::editingFinished,
+ [this, get_clear_val, touch]() {
+ if (date_time_edit->dateTime() != get_clear_val()) {
+ touch();
+ }
+ });
+ }
+
+ return date_time_edit;
+}
+
+void Widget::SetupComponent(const QString& label, std::function<void()>& load_func, bool managed,
+ RequestType request, float multiplier,
+ Settings::BasicSetting* other_setting, const QString& suffix) {
+ created = true;
+ const auto type = setting.TypeId();
+
+ QLayout* layout = new QHBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ if (other_setting == nullptr) {
+ other_setting = setting.PairedSetting();
+ }
+
+ const bool require_checkbox =
+ other_setting != nullptr && other_setting->TypeId() == typeid(bool);
+
+ if (other_setting != nullptr && other_setting->TypeId() != typeid(bool)) {
+ LOG_WARNING(
+ Frontend,
+ "Extra setting \"{}\" specified but is not bool, refusing to create checkbox for it.",
+ other_setting->GetLabel());
+ }
+
+ std::function<std::string()> checkbox_serializer = []() -> std::string { return {}; };
+ std::function<void()> checkbox_restore_func = []() {};
+
+ std::function<void()> touch = []() {};
+ std::function<std::string()> serializer = []() -> std::string { return {}; };
+ std::function<void()> restore_func = []() {};
+
+ QWidget* data_component{nullptr};
+
+ request = [&]() {
+ if (request != RequestType::Default) {
+ return request;
+ }
+ switch (setting.Specialization() & Settings::SpecializationTypeMask) {
+ case Settings::Specialization::Default:
+ return RequestType::Default;
+ case Settings::Specialization::Time:
+ return RequestType::DateTimeEdit;
+ case Settings::Specialization::Hex:
+ return RequestType::HexEdit;
+ case Settings::Specialization::RuntimeList:
+ managed = false;
+ [[fallthrough]];
+ case Settings::Specialization::List:
+ return RequestType::ComboBox;
+ case Settings::Specialization::Scalar:
+ return RequestType::Slider;
+ case Settings::Specialization::Countable:
+ return RequestType::SpinBox;
+ case Settings::Specialization::Radio:
+ return RequestType::RadioGroup;
+ default:
+ break;
+ }
+ return request;
+ }();
+
+ if (!Settings::IsConfiguringGlobal() && managed) {
+ restore_button = CreateRestoreGlobalButton(setting.UsingGlobal(), this);
+
+ touch = [this]() {
+ LOG_DEBUG(Frontend, "Enabling custom setting for \"{}\"", setting.GetLabel());
+ restore_button->setEnabled(true);
+ restore_button->setVisible(true);
+ };
+ }
+
+ if (require_checkbox) {
+ QWidget* lhs =
+ CreateCheckBox(other_setting, label, checkbox_serializer, checkbox_restore_func, touch);
+ layout->addWidget(lhs);
+ } else if (setting.TypeId() != typeid(bool)) {
+ QLabel* qt_label = CreateLabel(label);
+ layout->addWidget(qt_label);
+ }
+
+ if (setting.TypeId() == typeid(bool)) {
+ data_component = CreateCheckBox(&setting, label, serializer, restore_func, touch);
+ } else if (setting.IsEnum()) {
+ if (request == RequestType::RadioGroup) {
+ data_component = CreateRadioGroup(serializer, restore_func, touch);
+ } else {
+ data_component = CreateCombobox(serializer, restore_func, touch);
+ }
+ } else if (setting.IsIntegral()) {
+ switch (request) {
+ case RequestType::Slider:
+ case RequestType::ReverseSlider:
+ data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
+ serializer, restore_func, touch);
+ break;
+ case RequestType::Default:
+ case RequestType::LineEdit:
+ data_component = CreateLineEdit(serializer, restore_func, touch);
+ break;
+ case RequestType::DateTimeEdit:
+ data_component = CreateDateTimeEdit(other_setting->ToString() != "true", true,
+ serializer, restore_func, touch);
+ break;
+ case RequestType::SpinBox:
+ data_component = CreateSpinBox(suffix, serializer, restore_func, touch);
+ break;
+ case RequestType::HexEdit:
+ data_component = CreateHexEdit(serializer, restore_func, touch);
+ break;
+ case RequestType::ComboBox:
+ data_component = CreateCombobox(serializer, restore_func, touch);
+ break;
+ default:
+ UNIMPLEMENTED();
+ }
+ } else if (setting.IsFloatingPoint()) {
+ switch (request) {
+ case RequestType::Default:
+ case RequestType::SpinBox:
+ data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch);
+ break;
+ case RequestType::Slider:
+ case RequestType::ReverseSlider:
+ data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
+ serializer, restore_func, touch);
+ break;
+ default:
+ UNIMPLEMENTED();
+ }
+ } else if (type == typeid(std::string)) {
+ switch (request) {
+ case RequestType::Default:
+ case RequestType::LineEdit:
+ data_component = CreateLineEdit(serializer, restore_func, touch);
+ break;
+ case RequestType::ComboBox:
+ data_component = CreateCombobox(serializer, restore_func, touch);
+ break;
+ default:
+ UNIMPLEMENTED();
+ }
+ }
+
+ if (data_component == nullptr) {
+ LOG_ERROR(Frontend, "Failed to create widget for \"{}\"", setting.GetLabel());
+ created = false;
+ return;
+ }
+
+ layout->addWidget(data_component);
+
+ if (!managed) {
+ return;
+ }
+
+ if (Settings::IsConfiguringGlobal()) {
+ load_func = [this, serializer, checkbox_serializer, require_checkbox, other_setting]() {
+ if (require_checkbox && other_setting->UsingGlobal()) {
+ other_setting->LoadString(checkbox_serializer());
+ }
+ if (setting.UsingGlobal()) {
+ setting.LoadString(serializer());
+ }
+ };
+ } else {
+ layout->addWidget(restore_button);
+
+ QObject::connect(restore_button, &QAbstractButton::clicked,
+ [this, restore_func, checkbox_restore_func](bool) {
+ LOG_DEBUG(Frontend, "Restore global state for \"{}\"",
+ setting.GetLabel());
+
+ restore_button->setEnabled(false);
+ restore_button->setVisible(false);
+
+ checkbox_restore_func();
+ restore_func();
+ });
+
+ load_func = [this, serializer, require_checkbox, checkbox_serializer, other_setting]() {
+ bool using_global = !restore_button->isEnabled();
+ setting.SetGlobal(using_global);
+ if (!using_global) {
+ setting.LoadString(serializer());
+ }
+ if (require_checkbox) {
+ other_setting->SetGlobal(using_global);
+ if (!using_global) {
+ other_setting->LoadString(checkbox_serializer());
+ }
+ }
+ };
+ }
+
+ if (other_setting != nullptr) {
+ const auto reset = [restore_func, data_component](int state) {
+ data_component->setEnabled(state == Qt::Checked);
+ if (state != Qt::Checked) {
+ restore_func();
+ }
+ };
+ connect(checkbox, &QCheckBox::stateChanged, reset);
+ reset(checkbox->checkState());
+ }
+}
+
+bool Widget::Valid() const {
+ return created;
+}
+
+Widget::~Widget() = default;
+
+Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translations_,
+ const ComboboxTranslationMap& combobox_translations_, QWidget* parent_,
+ bool runtime_lock_, std::vector<std::function<void(bool)>>& apply_funcs_,
+ RequestType request, bool managed, float multiplier,
+ Settings::BasicSetting* other_setting, const QString& suffix)
+ : QWidget(parent_), parent{parent_}, translations{translations_},
+ combobox_enumerations{combobox_translations_}, setting{*setting_}, apply_funcs{apply_funcs_},
+ runtime_lock{runtime_lock_} {
+ if (!Settings::IsConfiguringGlobal() && !setting.Switchable()) {
+ LOG_DEBUG(Frontend, "\"{}\" is not switchable, skipping...", setting.GetLabel());
+ return;
+ }
+
+ const int id = setting.Id();
+
+ const auto [label, tooltip] = [&]() {
+ const auto& setting_label = setting.GetLabel();
+ if (translations.contains(id)) {
+ return std::pair{translations.at(id).first, translations.at(id).second};
+ }
+ LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label);
+ return std::pair{QString::fromStdString(setting_label), QStringLiteral()};
+ }();
+
+ if (label == QStringLiteral()) {
+ LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...",
+ setting.GetLabel());
+ return;
+ }
+
+ std::function<void()> load_func = []() {};
+
+ SetupComponent(label, load_func, managed, request, multiplier, other_setting, suffix);
+
+ if (!created) {
+ LOG_WARNING(Frontend, "No widget was created for \"{}\"", setting.GetLabel());
+ return;
+ }
+
+ apply_funcs.push_back([load_func, setting_](bool powered_on) {
+ if (setting_->RuntimeModfiable() || !powered_on) {
+ load_func();
+ }
+ });
+
+ bool enable = runtime_lock || setting.RuntimeModfiable();
+ if (setting.Switchable() && Settings::IsConfiguringGlobal() && !runtime_lock) {
+ enable &= setting.UsingGlobal();
+ }
+ this->setEnabled(enable);
+
+ this->setToolTip(tooltip);
+}
+
+Builder::Builder(QWidget* parent_, bool runtime_lock_)
+ : translations{InitializeTranslations(parent_)},
+ combobox_translations{ComboboxEnumeration(parent_)}, parent{parent_}, runtime_lock{
+ runtime_lock_} {}
+
+Builder::~Builder() = default;
+
+Widget* Builder::BuildWidget(Settings::BasicSetting* setting,
+ std::vector<std::function<void(bool)>>& apply_funcs,
+ RequestType request, bool managed, float multiplier,
+ Settings::BasicSetting* other_setting, const QString& suffix) const {
+ if (!Settings::IsConfiguringGlobal() && !setting->Switchable()) {
+ return nullptr;
+ }
+
+ if (setting->Specialization() == Settings::Specialization::Paired) {
+ LOG_DEBUG(Frontend, "\"{}\" has specialization Paired: ignoring", setting->GetLabel());
+ return nullptr;
+ }
+
+ return new Widget(setting, *translations, *combobox_translations, parent, runtime_lock,
+ apply_funcs, request, managed, multiplier, other_setting, suffix);
+}
+
+Widget* Builder::BuildWidget(Settings::BasicSetting* setting,
+ std::vector<std::function<void(bool)>>& apply_funcs,
+ Settings::BasicSetting* other_setting, RequestType request,
+ const QString& suffix) const {
+ return BuildWidget(setting, apply_funcs, request, true, 1.0f, other_setting, suffix);
+}
+
+const ComboboxTranslationMap& Builder::ComboboxTranslations() const {
+ return *combobox_translations;
+}
+
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
new file mode 100644
index 000000000..226284cf3
--- /dev/null
+++ b/src/yuzu/configuration/shared_widget.h
@@ -0,0 +1,178 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+#include <QString>
+#include <QStringLiteral>
+#include <QWidget>
+#include <qobjectdefs.h>
+#include "yuzu/configuration/shared_translation.h"
+
+class QCheckBox;
+class QComboBox;
+class QDateTimeEdit;
+class QLabel;
+class QLineEdit;
+class QObject;
+class QPushButton;
+class QSlider;
+class QSpinBox;
+class QDoubleSpinBox;
+class QRadioButton;
+
+namespace Settings {
+class BasicSetting;
+} // namespace Settings
+
+namespace ConfigurationShared {
+
+enum class RequestType {
+ Default,
+ ComboBox,
+ SpinBox,
+ Slider,
+ ReverseSlider,
+ LineEdit,
+ HexEdit,
+ DateTimeEdit,
+ RadioGroup,
+ MaxEnum,
+};
+
+constexpr float default_multiplier{1.f};
+constexpr float default_float_multiplier{100.f};
+static const QString default_suffix = QStringLiteral();
+
+class Widget : public QWidget {
+ Q_OBJECT
+
+public:
+ /**
+ * @param setting The primary Setting to create the Widget for
+ * @param translations Map of translations to display on the left side label/checkbox
+ * @param combobox_translations Map of translations for enumerating combo boxes
+ * @param parent Qt parent
+ * @param runtime_lock Emulated guest powered on state, for use on settings that should be
+ * configured during guest execution
+ * @param apply_funcs_ List to append, functions to run to apply the widget state to the setting
+ * @param request What type of data representation component to create -- not always respected
+ * for the Setting data type
+ * @param managed Set true if the caller will set up component data and handling
+ * @param multiplier Value to multiply the slider feedback label
+ * @param other_setting Second setting to modify, to replace the label with a checkbox
+ * @param suffix Set to specify formats for Slider feedback labels or SpinBox
+ */
+ explicit Widget(Settings::BasicSetting* setting, const TranslationMap& translations,
+ const ComboboxTranslationMap& combobox_translations, QWidget* parent,
+ bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_,
+ RequestType request = RequestType::Default, bool managed = true,
+ float multiplier = default_multiplier,
+ Settings::BasicSetting* other_setting = nullptr,
+ const QString& suffix = default_suffix);
+ virtual ~Widget();
+
+ /**
+ * @returns True if the Widget successfully created the components for the setting
+ */
+ bool Valid() const;
+
+ /**
+ * Creates a button to appear when a setting has been modified. This exists for custom
+ * configurations and wasn't designed to work for the global configuration. It has public access
+ * for settings that need to be unmanaged but can be custom.
+ *
+ * @param using_global The global state of the setting this button is for
+ * @param parent QWidget parent
+ */
+ [[nodiscard]] static QPushButton* CreateRestoreGlobalButton(bool using_global, QWidget* parent);
+
+ // Direct handles to sub components created
+ QPushButton* restore_button{}; ///< Restore button for custom configurations
+ QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit
+ QSpinBox* spinbox{};
+ QDoubleSpinBox* double_spinbox{};
+ QCheckBox* checkbox{};
+ QSlider* slider{};
+ QComboBox* combobox{};
+ QDateTimeEdit* date_time_edit{};
+ std::vector<std::pair<u32, QRadioButton*>> radio_buttons{};
+
+private:
+ void SetupComponent(const QString& label, std::function<void()>& load_func, bool managed,
+ RequestType request, float multiplier,
+ Settings::BasicSetting* other_setting, const QString& suffix);
+
+ QLabel* CreateLabel(const QString& text);
+ QWidget* CreateCheckBox(Settings::BasicSetting* bool_setting, const QString& label,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+
+ QWidget* CreateCombobox(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+ QWidget* CreateRadioGroup(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+ QWidget* CreateLineEdit(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func, const std::function<void()>& touch,
+ bool managed = true);
+ QWidget* CreateHexEdit(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func, const std::function<void()>& touch);
+ QWidget* CreateSlider(bool reversed, float multiplier, const QString& suffix,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func, const std::function<void()>& touch);
+ QWidget* CreateDateTimeEdit(bool disabled, bool restrict,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+ QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer,
+ std::function<void()>& restore_func, const std::function<void()>& touch);
+ QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+
+ QWidget* parent;
+ const TranslationMap& translations;
+ const ComboboxTranslationMap& combobox_enumerations;
+ Settings::BasicSetting& setting;
+ std::vector<std::function<void(bool)>>& apply_funcs;
+
+ bool created{false};
+ bool runtime_lock{false};
+};
+
+class Builder {
+public:
+ explicit Builder(QWidget* parent, bool runtime_lock);
+ ~Builder();
+
+ Widget* BuildWidget(Settings::BasicSetting* setting,
+ std::vector<std::function<void(bool)>>& apply_funcs,
+ RequestType request = RequestType::Default, bool managed = true,
+ float multiplier = default_multiplier,
+ Settings::BasicSetting* other_setting = nullptr,
+ const QString& suffix = default_suffix) const;
+
+ Widget* BuildWidget(Settings::BasicSetting* setting,
+ std::vector<std::function<void(bool)>>& apply_funcs,
+ Settings::BasicSetting* other_setting,
+ RequestType request = RequestType::Default,
+ const QString& suffix = default_suffix) const;
+
+ const ComboboxTranslationMap& ComboboxTranslations() const;
+
+private:
+ std::unique_ptr<TranslationMap> translations;
+ std::unique_ptr<ComboboxTranslationMap> combobox_translations;
+
+ QWidget* parent;
+ const bool runtime_lock;
+};
+
+} // namespace ConfigurationShared
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) {
const int children_count = folder->rowCount();
for (int j = 0; j < children_count; ++j) {
++children_total;
+
const QStandardItem* child = folder->child(j, 0);
+
+ const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong();
+
const QString file_path =
child->data(GameListItemPath::FullPathRole).toString().toLower();
const QString file_title =
child->data(GameListItemPath::TitleRole).toString().toLower();
const QString file_program_id =
- child->data(GameListItemPath::ProgramIdRole).toString().toLower();
+ QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'});
// Only items which filename in combination with its title contains all words
// that are in the searchfield will be visible in the gamelist
@@ -231,7 +235,7 @@ void GameList::OnTextChanged(const QString& new_text) {
file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
file_title;
if (ContainsAllWords(file_name, edit_filter_text) ||
- (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {
+ (file_program_id.count() == 16 && file_program_id.contains(edit_filter_text))) {
tree_view->setRowHidden(j, folder_index, false);
++result_count;
} else {
@@ -553,6 +557,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS"));
QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS"));
QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
+ QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
#ifndef WIN32
@@ -584,10 +589,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
});
connect(start_game, &QAction::triggered, [this, path]() {
- emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal);
+ emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal,
+ AmLaunchType::UserInitiated);
});
connect(start_game_global, &QAction::triggered, [this, path]() {
- emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global);
+ emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global,
+ AmLaunchType::UserInitiated);
});
connect(open_mod_location, &QAction::triggered, [this, program_id, path]() {
emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
@@ -624,6 +631,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() {
emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC);
});
+ connect(verify_integrity, &QAction::triggered,
+ [this, path]() { emit VerifyIntegrityRequested(path); });
connect(copy_tid, &QAction::triggered,
[this, program_id]() { emit CopyTIDRequested(program_id); });
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;
class GameListSearchField;
class GameListDir;
class GMainWindow;
+enum class AmLaunchType;
enum class StartGameType;
namespace FileSys {
@@ -103,7 +104,7 @@ public:
signals:
void BootGame(const QString& game_path, u64 program_id, std::size_t program_index,
- StartGameType type);
+ StartGameType type, AmLaunchType launch_type);
void GameChosen(const QString& game_path, const u64 title_id = 0);
void ShouldCancelWorker();
void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
@@ -113,6 +114,7 @@ signals:
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
+ void VerifyIntegrityRequested(const std::string& game_path);
void CopyTIDRequested(u64 program_id);
void CreateShortcut(u64 program_id, const std::string& game_path,
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,
}
QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
- const std::vector<u8>& icon, Loader::AppLoader& loader,
- u64 program_id, const CompatibilityList& compatibility_list,
+ const std::size_t size, const std::vector<u8>& icon,
+ Loader::AppLoader& loader, u64 program_id,
+ const CompatibilityList& compatibility_list,
const FileSys::PatchManager& patch) {
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -210,7 +211,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
file_type_string, program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
- new GameListItemSize(Common::FS::GetSize(path)),
+ new GameListItemSize(size),
};
const auto patch_versions = GetGameListCachedObject(
@@ -278,8 +279,8 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
GetMetadataFromControlNCA(patch, *control, icon, name);
}
- emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
- compatibility_list, patch),
+ emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
+ program_id, compatibility_list, patch),
parent_dir);
}
}
@@ -354,8 +355,9 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{id, system.GetFileSystemController(),
system.GetContentProvider()};
- emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, id,
- compatibility_list, patch),
+ emit EntryReady(MakeGameListEntry(physical_name, name,
+ Common::FS::GetSize(physical_name), icon,
+ *loader, id, compatibility_list, patch),
parent_dir);
}
} else {
@@ -368,9 +370,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
- emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader,
- program_id, compatibility_list, patch),
- parent_dir);
+ emit EntryReady(
+ MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name),
+ icon, *loader, program_id, compatibility_list, patch),
+ parent_dir);
}
}
} 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 @@
#pragma once
#include <map>
+#include <QKeySequence>
+#include <QString>
+#include <QWidget>
#include "core/hid/hid_types.h"
class QDialog;
-class QKeySequence;
class QSettings;
class QShortcut;
class ControllerShortcut;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 6cd557c29..d32aa9615 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -8,6 +8,8 @@
#include <iostream>
#include <memory>
#include <thread>
+#include "core/loader/nca.h"
+#include "core/tools/renderdoc.h"
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
@@ -24,6 +26,7 @@
#include "applets/qt_software_keyboard.h"
#include "applets/qt_web_browser.h"
#include "common/nvidia_flags.h"
+#include "common/settings_enums.h"
#include "configuration/configure_input.h"
#include "configuration/configure_per_game.h"
#include "configuration/configure_tas.h"
@@ -441,8 +444,13 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
"#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
"here for instructions to fix the issue</a>."));
+#ifdef HAS_OPENGL
Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
+#else
+ Settings::values.renderer_backend = Settings::RendererBackend::Null;
+#endif
+ UpdateAPIText();
renderer_status_button->setDisabled(true);
renderer_status_button->setChecked(false);
} else {
@@ -1095,10 +1103,9 @@ void GMainWindow::InitializeWidgets() {
aa_status_button->setFocusPolicy(Qt::NoFocus);
connect(aa_status_button, &QPushButton::clicked, [&] {
auto aa_mode = Settings::values.anti_aliasing.GetValue();
- if (aa_mode == Settings::AntiAliasing::LastAA) {
+ aa_mode = static_cast<Settings::AntiAliasing>(static_cast<u32>(aa_mode) + 1);
+ if (aa_mode == Settings::AntiAliasing::MaxEnum) {
aa_mode = Settings::AntiAliasing::None;
- } else {
- aa_mode = static_cast<Settings::AntiAliasing>(static_cast<u32>(aa_mode) + 1);
}
Settings::values.anti_aliasing.SetValue(aa_mode);
aa_status_button->setChecked(true);
@@ -1158,9 +1165,9 @@ void GMainWindow::InitializeWidgets() {
[this](const QPoint& menu_location) {
QMenu context_menu;
- for (auto const& docked_mode_pair : Config::use_docked_mode_texts_map) {
- context_menu.addAction(docked_mode_pair.second, [this, docked_mode_pair] {
- if (docked_mode_pair.first != Settings::values.use_docked_mode.GetValue()) {
+ for (auto const& pair : Config::use_docked_mode_texts_map) {
+ context_menu.addAction(pair.second, [this, &pair] {
+ if (pair.first != Settings::values.use_docked_mode.GetValue()) {
OnToggleDockedMode();
}
});
@@ -1183,7 +1190,7 @@ void GMainWindow::InitializeWidgets() {
QMenu context_menu;
for (auto const& gpu_accuracy_pair : Config::gpu_accuracy_texts_map) {
- if (gpu_accuracy_pair.first == Settings::GPUAccuracy::Extreme) {
+ if (gpu_accuracy_pair.first == Settings::GpuAccuracy::Extreme) {
continue;
}
context_menu.addAction(gpu_accuracy_pair.second, [this, gpu_accuracy_pair] {
@@ -1342,6 +1349,11 @@ void GMainWindow::InitializeHotkeys() {
connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
});
+ connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] {
+ if (Settings::values.enable_renderdoc_hotkey) {
+ system->GetRenderdocAPI().ToggleCapture();
+ }
+ });
connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
if (Settings::values.mouse_enabled) {
Settings::values.mouse_panning = false;
@@ -1447,6 +1459,8 @@ void GMainWindow::ConnectWidgetEvents() {
&GMainWindow::OnGameListRemoveInstalledEntry);
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
+ connect(game_list, &GameList::VerifyIntegrityRequested, this,
+ &GMainWindow::OnGameListVerifyIntegrity);
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
@@ -1547,6 +1561,7 @@ void GMainWindow::ConnectMenuEvents() {
// Help
connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
+ connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
connect_menu(ui->action_About, &GMainWindow::OnAbout);
}
@@ -1698,7 +1713,8 @@ void GMainWindow::AllowOSSleep() {
#endif
}
-bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) {
+bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index,
+ AmLaunchType launch_type) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr) {
ShutdownGame();
@@ -1710,6 +1726,10 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
system->SetFilesystem(vfs);
+ if (launch_type == AmLaunchType::UserInitiated) {
+ system->GetUserChannel().clear();
+ }
+
system->SetAppletFrontendSet({
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
(UISettings::values.controller_applet_disabled.GetValue() == true)
@@ -1811,8 +1831,45 @@ bool GMainWindow::SelectAndSetCurrentUser(
return true;
}
+void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
+ // Ensure all NCAs are registered before launching the game
+ const auto file = vfs->OpenFile(filepath, FileSys::Mode::Read);
+ if (!file) {
+ return;
+ }
+
+ auto loader = Loader::GetLoader(*system, file);
+ if (!loader) {
+ return;
+ }
+
+ const auto file_type = loader->GetFileType();
+ if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
+ return;
+ }
+
+ u64 program_id = 0;
+ const auto res2 = loader->ReadProgramId(program_id);
+ if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
+ provider->AddEntry(FileSys::TitleType::Application,
+ FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id,
+ file);
+ } else if (res2 == Loader::ResultStatus::Success &&
+ (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
+ const auto nsp = file_type == Loader::FileType::NSP
+ ? std::make_shared<FileSys::NSP>(file)
+ : FileSys::XCI{file}.GetSecurePartitionNSP();
+ for (const auto& title : nsp->GetNCAs()) {
+ for (const auto& entry : title.second) {
+ provider->AddEntry(entry.first.first, entry.first.second, title.first,
+ entry.second->GetBaseFile());
+ }
+ }
+ }
+}
+
void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index,
- StartGameType type) {
+ StartGameType type, AmLaunchType launch_type) {
LOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list
@@ -1825,6 +1882,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
last_filename_booted = filename;
+ ConfigureFilesystemProvider(filename.toStdString());
const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData());
const auto loader = Loader::GetLoader(*system, v_file, program_id, program_index);
@@ -1855,7 +1913,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
}
}
- if (!LoadROM(filename, program_id, program_index)) {
+ if (!LoadROM(filename, program_id, program_index, launch_type)) {
return;
}
@@ -1972,8 +2030,16 @@ bool GMainWindow::OnShutdownBegin() {
emit EmulationStopping();
+ int shutdown_time = 1000;
+
+ if (system->DebuggerEnabled()) {
+ shutdown_time = 0;
+ } else if (system->GetExitLocked()) {
+ shutdown_time = 5000;
+ }
+
shutdown_timer.setSingleShot(true);
- shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000);
+ shutdown_timer.start(shutdown_time);
connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired);
connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped);
@@ -2229,40 +2295,62 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path));
}
-static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) {
- std::size_t out = 0;
-
- for (const auto& subdir : dir->GetSubdirectories()) {
- out += 1 + CalculateRomFSEntrySize(subdir, full);
- }
-
- return out + (full ? dir->GetFiles().size() : 0);
-}
-
-static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src,
- const FileSys::VirtualDir& dest, std::size_t block_size, bool full) {
+static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& dialog,
+ const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest,
+ bool full) {
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
return false;
if (dialog.wasCanceled())
return false;
+ std::vector<u8> buffer(CopyBufferSize);
+ auto last_timestamp = std::chrono::steady_clock::now();
+
+ const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file,
+ const FileSys::VirtualFile& dest_file) {
+ if (src_file == nullptr || dest_file == nullptr) {
+ return false;
+ }
+ if (!dest_file->Resize(src_file->GetSize())) {
+ return false;
+ }
+
+ for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) {
+ if (dialog.wasCanceled()) {
+ dest_file->Resize(0);
+ return false;
+ }
+
+ using namespace std::literals::chrono_literals;
+ const auto new_timestamp = std::chrono::steady_clock::now();
+
+ if ((new_timestamp - last_timestamp) > 33ms) {
+ last_timestamp = new_timestamp;
+ dialog.setValue(
+ static_cast<int>(std::min(read_size, total_size) * 100 / total_size));
+ QCoreApplication::processEvents();
+ }
+
+ const auto read = src_file->Read(buffer.data(), buffer.size(), i);
+ dest_file->Write(buffer.data(), read, i);
+
+ read_size += read;
+ }
+
+ return true;
+ };
+
if (full) {
for (const auto& file : src->GetFiles()) {
const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
- if (!FileSys::VfsRawCopy(file, out, block_size))
- return false;
- dialog.setValue(dialog.value() + 1);
- if (dialog.wasCanceled())
+ if (!QtRawCopy(file, out))
return false;
}
}
for (const auto& dir : src->GetSubdirectories()) {
const auto out = dest->CreateSubdirectory(dir->GetName());
- if (!RomFSRawCopy(dialog, dir, out, block_size, full))
- return false;
- dialog.setValue(dialog.value() + 1);
- if (dialog.wasCanceled())
+ if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full))
return false;
}
@@ -2535,16 +2623,34 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- FileSys::VirtualFile file;
- if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) {
+ FileSys::VirtualFile packed_update_raw{};
+ loader->ReadUpdateRaw(packed_update_raw);
+
+ const auto& installed = system->GetContentProvider();
+
+ u64 title_id{};
+ u8 raw_type{};
+ if (!SelectRomFSDumpTarget(installed, program_id, &title_id, &raw_type)) {
failed();
return;
}
- const auto& installed = system->GetContentProvider();
- const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
+ const auto type = static_cast<FileSys::ContentRecordType>(raw_type);
+ const auto base_nca = installed.GetEntry(title_id, type);
+ if (!base_nca) {
+ failed();
+ return;
+ }
+
+ const FileSys::NCA update_nca{packed_update_raw, nullptr};
+ if (type != FileSys::ContentRecordType::Program ||
+ update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS ||
+ update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) {
+ packed_update_raw = {};
+ }
- if (!romfs_title_id) {
+ const auto base_romfs = base_nca->GetRomFS();
+ if (!base_romfs) {
failed();
return;
}
@@ -2553,26 +2659,12 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
target == DumpRomFSTarget::Normal
? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)
: Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents";
- const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id);
+ const auto romfs_dir = fmt::format("{:016X}/romfs", title_id);
const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir);
- FileSys::VirtualFile romfs;
-
- if (*romfs_title_id == program_id) {
- const u64 ivfc_offset = loader->ReadRomFSIVFCOffset();
- const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed};
- romfs =
- pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false);
- } else {
- romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
- }
-
- const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
- if (extracted == nullptr) {
- failed();
- return;
- }
+ const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed};
+ auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false);
const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
@@ -2596,11 +2688,16 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
+ const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
+ if (extracted == nullptr) {
+ failed();
+ return;
+ }
+
const auto full = res == selections.constFirst();
- const auto entry_size = CalculateRomFSEntrySize(extracted, full);
- // The minimum required space is the size of the extracted RomFS + 1 GiB
- const auto minimum_free_space = extracted->GetSize() + 0x40000000;
+ // The expected required space is the size of the RomFS + 1 GiB
+ const auto minimum_free_space = romfs->GetSize() + 0x40000000;
if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) {
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
@@ -2611,12 +2708,15 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0,
- static_cast<s32>(entry_size), this);
+ QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, 100, this);
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
- if (RomFSRawCopy(progress, extracted, out, 0x400000, full)) {
+ size_t read_size = 0;
+
+ if (RomFSRawCopy(romfs->GetSize(), read_size, progress, extracted, out, full)) {
progress.close();
QMessageBox::information(this, tr("RomFS Extraction Succeeded!"),
tr("The operation completed successfully."));
@@ -2628,6 +2728,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
}
}
+void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) {
+ const auto NotImplemented = [this] {
+ QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"),
+ tr("File contents were not checked for validity."));
+ };
+ const auto Failed = [this] {
+ QMessageBox::critical(this, tr("Integrity verification failed!"),
+ tr("File contents may be corrupt."));
+ };
+
+ const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
+ if (loader == nullptr) {
+ NotImplemented();
+ return;
+ }
+
+ QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+
+ const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) {
+ if (progress.wasCanceled()) {
+ return false;
+ }
+
+ progress.setValue(static_cast<int>((processed_size * 100) / total_size));
+ return true;
+ };
+
+ const auto status = loader->VerifyIntegrity(QtProgressCallback);
+ if (progress.wasCanceled() ||
+ status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) {
+ NotImplemented();
+ return;
+ }
+
+ if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) {
+ Failed();
+ return;
+ }
+
+ progress.close();
+ QMessageBox::information(this, tr("Integrity verification succeeded!"),
+ tr("The operation completed successfully."));
+}
+
void GMainWindow::OnGameListCopyTID(u64 program_id) {
QClipboard* clipboard = QGuiApplication::clipboard();
clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
@@ -3217,7 +3365,7 @@ void GMainWindow::OnPauseContinueGame() {
}
void GMainWindow::OnStopGame() {
- if (system->GetExitLock() && !ConfirmForceLockedExit()) {
+ if (system->GetExitLocked() && !ConfirmForceLockedExit()) {
return;
}
@@ -3234,7 +3382,8 @@ void GMainWindow::OnLoadComplete() {
void GMainWindow::OnExecuteProgram(std::size_t program_index) {
ShutdownGame();
- BootGame(last_filename_booted, 0, program_index);
+ BootGame(last_filename_booted, 0, program_index, StartGameType::Normal,
+ AmLaunchType::ApplicationInitiated);
}
void GMainWindow::OnExit() {
@@ -3630,7 +3779,7 @@ void GMainWindow::OnTasReset() {
}
void GMainWindow::OnToggleDockedMode() {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
+ const bool is_docked = Settings::IsDockedMode();
auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
@@ -3644,21 +3793,22 @@ void GMainWindow::OnToggleDockedMode() {
controller_dialog->refreshConfiguration();
}
- Settings::values.use_docked_mode.SetValue(!is_docked);
+ Settings::values.use_docked_mode.SetValue(is_docked ? Settings::ConsoleMode::Handheld
+ : Settings::ConsoleMode::Docked);
UpdateDockedButton();
OnDockedModeChanged(is_docked, !is_docked, *system);
}
void GMainWindow::OnToggleGpuAccuracy() {
switch (Settings::values.gpu_accuracy.GetValue()) {
- case Settings::GPUAccuracy::High: {
- Settings::values.gpu_accuracy.SetValue(Settings::GPUAccuracy::Normal);
+ case Settings::GpuAccuracy::High: {
+ Settings::values.gpu_accuracy.SetValue(Settings::GpuAccuracy::Normal);
break;
}
- case Settings::GPUAccuracy::Normal:
- case Settings::GPUAccuracy::Extreme:
+ case Settings::GpuAccuracy::Normal:
+ case Settings::GpuAccuracy::Extreme:
default: {
- Settings::values.gpu_accuracy.SetValue(Settings::GPUAccuracy::High);
+ Settings::values.gpu_accuracy.SetValue(Settings::GpuAccuracy::High);
break;
}
}
@@ -3702,10 +3852,9 @@ void GMainWindow::OnIncreaseVolume() {
void GMainWindow::OnToggleAdaptingFilter() {
auto filter = Settings::values.scaling_filter.GetValue();
- if (filter == Settings::ScalingFilter::LastFilter) {
+ filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1);
+ if (filter == Settings::ScalingFilter::MaxEnum) {
filter = Settings::ScalingFilter::NearestNeighbor;
- } else {
- filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1);
}
Settings::values.scaling_filter.SetValue(filter);
filter_status_button->setChecked(true);
@@ -3714,10 +3863,14 @@ void GMainWindow::OnToggleAdaptingFilter() {
void GMainWindow::OnToggleGraphicsAPI() {
auto api = Settings::values.renderer_backend.GetValue();
- if (api == Settings::RendererBackend::OpenGL) {
+ if (api != Settings::RendererBackend::Vulkan) {
api = Settings::RendererBackend::Vulkan;
} else {
+#ifdef HAS_OPENGL
api = Settings::RendererBackend::OpenGL;
+#else
+ api = Settings::RendererBackend::Null;
+#endif
}
Settings::values.renderer_backend.SetValue(api);
renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan);
@@ -3861,6 +4014,108 @@ void GMainWindow::OnOpenYuzuFolder() {
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir))));
}
+void GMainWindow::OnVerifyInstalledContents() {
+ // Declare sizes.
+ size_t total_size = 0;
+ size_t processed_size = 0;
+
+ // Initialize a progress dialog.
+ QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+
+ // Declare a list of file names which failed to verify.
+ std::vector<std::string> failed;
+
+ // Declare progress callback.
+ auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) {
+ if (progress.wasCanceled()) {
+ return false;
+ }
+ progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size));
+ return true;
+ };
+
+ // Get content registries.
+ auto bis_contents = system->GetFileSystemController().GetSystemNANDContents();
+ auto user_contents = system->GetFileSystemController().GetUserNANDContents();
+
+ std::vector<FileSys::RegisteredCache*> content_providers;
+ if (bis_contents) {
+ content_providers.push_back(bis_contents);
+ }
+ if (user_contents) {
+ content_providers.push_back(user_contents);
+ }
+
+ // Get associated NCA files.
+ std::vector<FileSys::VirtualFile> nca_files;
+
+ // Get all installed IDs.
+ for (auto nca_provider : content_providers) {
+ const auto entries = nca_provider->ListEntriesFilter();
+
+ for (const auto& entry : entries) {
+ auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type);
+ if (!nca_file) {
+ continue;
+ }
+
+ total_size += nca_file->GetSize();
+ nca_files.push_back(std::move(nca_file));
+ }
+ }
+
+ // Using the NCA loader, determine if all NCAs are valid.
+ for (auto& nca_file : nca_files) {
+ Loader::AppLoader_NCA nca_loader(nca_file);
+
+ auto status = nca_loader.VerifyIntegrity(QtProgressCallback);
+ if (progress.wasCanceled()) {
+ break;
+ }
+ if (status != Loader::ResultStatus::Success) {
+ FileSys::NCA nca(nca_file);
+ const auto title_id = nca.GetTitleId();
+ std::string title_name = "unknown";
+
+ const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id),
+ FileSys::ContentRecordType::Control);
+ if (control && control->GetStatus() == Loader::ResultStatus::Success) {
+ const FileSys::PatchManager pm{title_id, system->GetFileSystemController(),
+ *provider};
+ const auto [nacp, logo] = pm.ParseControlNCA(*control);
+ if (nacp) {
+ title_name = nacp->GetApplicationName();
+ }
+ }
+
+ if (title_id > 0) {
+ failed.push_back(
+ fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name));
+ } else {
+ failed.push_back(fmt::format("{} (unknown)", nca_file->GetName()));
+ }
+ }
+
+ processed_size += nca_file->GetSize();
+ }
+
+ progress.close();
+
+ if (failed.size() > 0) {
+ auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n")));
+ QMessageBox::critical(
+ this, tr("Integrity verification failed!"),
+ tr("Verification failed for the following files:\n\n%1").arg(failed_names));
+ } else {
+ QMessageBox::information(this, tr("Integrity verification succeeded!"),
+ tr("The operation completed successfully."));
+ }
+}
+
void GMainWindow::OnAbout() {
AboutDialog aboutDialog(this);
aboutDialog.exec();
@@ -4071,14 +4326,14 @@ void GMainWindow::UpdateGPUAccuracyButton() {
const auto gpu_accuracy = Settings::values.gpu_accuracy.GetValue();
const auto gpu_accuracy_text = Config::gpu_accuracy_texts_map.find(gpu_accuracy)->second;
gpu_accuracy_button->setText(gpu_accuracy_text.toUpper());
- gpu_accuracy_button->setChecked(gpu_accuracy != Settings::GPUAccuracy::Normal);
+ gpu_accuracy_button->setChecked(gpu_accuracy != Settings::GpuAccuracy::Normal);
}
void GMainWindow::UpdateDockedButton() {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
- dock_status_button->setChecked(is_docked);
+ const auto console_mode = Settings::values.use_docked_mode.GetValue();
+ dock_status_button->setChecked(Settings::IsDockedMode());
dock_status_button->setText(
- Config::use_docked_mode_texts_map.find(is_docked)->second.toUpper());
+ Config::use_docked_mode_texts_map.find(console_mode)->second.toUpper());
}
void GMainWindow::UpdateAPIText() {
@@ -4306,28 +4561,41 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
return mii_nca->GetRomFS().get() != nullptr;
}
-std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,
- u64 program_id) {
- const auto dlc_entries =
- installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
- std::vector<FileSys::ContentProviderEntry> dlc_match;
- dlc_match.reserve(dlc_entries.size());
- std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
- [&program_id, &installed](const FileSys::ContentProviderEntry& entry) {
- return FileSys::GetBaseTitleID(entry.title_id) == program_id &&
- installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
- });
-
- std::vector<u64> romfs_tids;
- romfs_tids.push_back(program_id);
- for (const auto& entry : dlc_match) {
- romfs_tids.push_back(entry.title_id);
- }
-
- if (romfs_tids.size() > 1) {
- QStringList list{QStringLiteral("Base")};
- for (std::size_t i = 1; i < romfs_tids.size(); ++i) {
- list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
+bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
+ u64* selected_title_id, u8* selected_content_record_type) {
+ using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>;
+ boost::container::flat_map<u64, ContentInfo> available_title_ids;
+
+ const auto RetrieveEntries = [&](FileSys::TitleType title_type,
+ FileSys::ContentRecordType record_type) {
+ const auto entries = installed.ListEntriesFilter(title_type, record_type);
+ for (const auto& entry : entries) {
+ if (FileSys::GetBaseTitleID(entry.title_id) == program_id &&
+ installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) {
+ available_title_ids[entry.title_id] = {title_type, record_type};
+ }
+ }
+ };
+
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
+ RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
+
+ if (available_title_ids.empty()) {
+ return false;
+ }
+
+ size_t title_index = 0;
+
+ if (available_title_ids.size() > 1) {
+ QStringList list;
+ for (auto& [title_id, content_info] : available_title_ids) {
+ const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
+ if (content_info.first == FileSys::TitleType::Application) {
+ list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id));
+ } else {
+ list.push_back(
+ QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
+ }
}
bool ok;
@@ -4335,13 +4603,16 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv
this, tr("Select RomFS Dump Target"),
tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
if (!ok) {
- return {};
+ return false;
}
- return romfs_tids[list.indexOf(res)];
+ title_index = list.indexOf(res);
}
- return program_id;
+ const auto selected_info = available_title_ids.nth(title_index);
+ *selected_title_id = selected_info->first;
+ *selected_content_record_type = static_cast<u8>(selected_info->second.second);
+ return true;
}
bool GMainWindow::ConfirmClose() {
@@ -4471,6 +4742,8 @@ void GMainWindow::RequestGameExit() {
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
bool has_signalled = false;
+ system->SetExitRequested(true);
+
if (applet_oe != nullptr) {
applet_oe->GetMessageQueue()->RequestExit();
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 {
Global, // Only uses global configuration
};
+enum class AmLaunchType {
+ UserInitiated,
+ ApplicationInitiated,
+};
+
namespace Core {
enum class SystemResultStatus : u32;
class System;
@@ -239,9 +244,11 @@ private:
void PreventOSSleep();
void AllowOSSleep();
- bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index);
+ bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index,
+ AmLaunchType launch_type);
void BootGame(const QString& filename, u64 program_id = 0, std::size_t program_index = 0,
- StartGameType with_config = StartGameType::Normal);
+ StartGameType with_config = StartGameType::Normal,
+ AmLaunchType launch_type = AmLaunchType::UserInitiated);
void ShutdownGame();
void ShowTelemetryCallout();
@@ -313,6 +320,7 @@ private slots:
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
+ void OnGameListVerifyIntegrity(const std::string& game_path);
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
@@ -342,6 +350,7 @@ private slots:
void OnConfigurePerGame();
void OnLoadAmiibo();
void OnOpenYuzuFolder();
+ void OnVerifyInstalledContents();
void OnAbout();
void OnToggleFilterBar();
void OnToggleStatusBar();
@@ -375,7 +384,8 @@ private:
void RemoveAllTransferableShaderCaches(u64 program_id);
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
void RemoveCacheStorage(u64 program_id);
- std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
+ bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
+ u64* selected_title_id, u8* selected_content_record_type);
InstallResult InstallNSPXCI(const QString& filename);
InstallResult InstallNCA(const QString& filename);
void MigrateConfigFiles();
@@ -399,6 +409,7 @@ private:
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
bool CheckDarkMode();
bool CheckSystemArchiveDecryption();
+ void ConfigureFilesystemProvider(const std::string& filepath);
QString GetTasStateDescription() const;
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 @@
<addaction name="action_Configure_Tas"/>
</widget>
<addaction name="action_Rederive"/>
+ <addaction name="action_Verify_installed_contents"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
<addaction name="menuTAS"/>
@@ -214,6 +215,11 @@
<string>&amp;Reinitialize keys...</string>
</property>
</action>
+ <action name="action_Verify_installed_contents">
+ <property name="text">
+ <string>Verify installed contents</string>
+ </property>
+ </action>
<action name="action_About">
<property name="text">
<string>&amp;About yuzu</string>
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
index d71cc23a7..a415a953f 100644
--- a/src/yuzu/multiplayer/direct_connect.cpp
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -34,13 +34,14 @@ DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
connect(watcher, &QFutureWatcher<void>::finished, this, &DirectConnectWindow::OnConnection);
ui->nickname->setValidator(validation.GetNickname());
- ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
+ ui->nickname->setText(
+ QString::fromStdString(UISettings::values.multiplayer_nickname.GetValue()));
if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
// Use yuzu Web Service user name as nickname by default
ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
}
ui->ip->setValidator(validation.GetIP());
- ui->ip->setText(UISettings::values.multiplayer_ip.GetValue());
+ ui->ip->setText(QString::fromStdString(UISettings::values.multiplayer_ip.GetValue()));
ui->port->setValidator(validation.GetPort());
ui->port->setText(QString::number(UISettings::values.multiplayer_port.GetValue()));
@@ -91,8 +92,8 @@ void DirectConnectWindow::Connect() {
}
// Store settings
- UISettings::values.multiplayer_nickname = ui->nickname->text();
- UISettings::values.multiplayer_ip = ui->ip->text();
+ UISettings::values.multiplayer_nickname = ui->nickname->text().toStdString();
+ UISettings::values.multiplayer_ip = ui->ip->text().toStdString();
if (ui->port->isModified() && !ui->port->text().isEmpty()) {
UISettings::values.multiplayer_port = ui->port->text().toInt();
} else {
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
index a8faa5b24..ef364ee43 100644
--- a/src/yuzu/multiplayer/host_room.cpp
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -55,12 +55,14 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host);
// Restore the settings:
- ui->username->setText(UISettings::values.multiplayer_room_nickname.GetValue());
+ ui->username->setText(
+ QString::fromStdString(UISettings::values.multiplayer_room_nickname.GetValue()));
if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
// Use yuzu Web Service user name as nickname by default
ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
}
- ui->room_name->setText(UISettings::values.multiplayer_room_name.GetValue());
+ ui->room_name->setText(
+ QString::fromStdString(UISettings::values.multiplayer_room_name.GetValue()));
ui->port->setText(QString::number(UISettings::values.multiplayer_room_port.GetValue()));
ui->max_player->setValue(UISettings::values.multiplayer_max_player.GetValue());
int index = UISettings::values.multiplayer_host_type.GetValue();
@@ -72,7 +74,8 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
if (index != -1) {
ui->game_list->setCurrentIndex(index);
}
- ui->room_description->setText(UISettings::values.multiplayer_room_description.GetValue());
+ ui->room_description->setText(
+ QString::fromStdString(UISettings::values.multiplayer_room_description.GetValue()));
}
HostRoomWindow::~HostRoomWindow() = default;
@@ -218,8 +221,8 @@ void HostRoomWindow::Host() {
Network::NoPreferredIP, password, token);
// Store settings
- UISettings::values.multiplayer_room_nickname = ui->username->text();
- UISettings::values.multiplayer_room_name = ui->room_name->text();
+ UISettings::values.multiplayer_room_nickname = ui->username->text().toStdString();
+ UISettings::values.multiplayer_room_name = ui->room_name->text().toStdString();
UISettings::values.multiplayer_game_id =
ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
UISettings::values.multiplayer_max_player = ui->max_player->value();
@@ -230,7 +233,8 @@ void HostRoomWindow::Host() {
} else {
UISettings::values.multiplayer_room_port = Network::DefaultRoomPort;
}
- UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
+ UISettings::values.multiplayer_room_description =
+ ui->room_description->toPlainText().toStdString();
ui->host->setEnabled(true);
emit SaveConfig();
close();
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
index 387f6f7c9..603e9ae3d 100644
--- a/src/yuzu/multiplayer/lobby.cpp
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -60,7 +60,8 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
ui->room_list->setContextMenuPolicy(Qt::CustomContextMenu);
ui->nickname->setValidator(validation.GetNickname());
- ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
+ ui->nickname->setText(
+ QString::fromStdString(UISettings::values.multiplayer_nickname.GetValue()));
// Try find the best nickname by default
if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) {
@@ -202,9 +203,9 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
// TODO(jroweboy): disable widgets and display a connecting while we wait
// Save settings
- UISettings::values.multiplayer_nickname = ui->nickname->text();
+ UISettings::values.multiplayer_nickname = ui->nickname->text().toStdString();
UISettings::values.multiplayer_ip =
- proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
+ proxy->data(connection_index, LobbyItemHost::HostIPRole).value<QString>().toStdString();
UISettings::values.multiplayer_port =
proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
emit SaveConfig();
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 2c1b547fb..1c833767b 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -3,6 +3,18 @@
#include "yuzu/uisettings.h"
+#ifndef CANNOT_EXPLICITLY_INSTANTIATE
+namespace Settings {
+template class Setting<bool>;
+template class Setting<std::string>;
+template class Setting<u16, true>;
+template class Setting<u32>;
+template class Setting<u8, true>;
+template class Setting<u8>;
+template class Setting<unsigned long long>;
+} // namespace Settings
+#endif
+
namespace UISettings {
const Themes themes{{
@@ -24,4 +36,20 @@ bool IsDarkTheme() {
Values values = {};
+u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) {
+ switch (ratio) {
+ case Settings::AspectRatio::R4_3:
+ return height * 4 / 3;
+ case Settings::AspectRatio::R21_9:
+ return height * 21 / 9;
+ case Settings::AspectRatio::R16_10:
+ return height * 16 / 10;
+ case Settings::AspectRatio::R16_9:
+ case Settings::AspectRatio::Stretch:
+ // TODO: Move this function wherever appropriate to implement Stretched aspect
+ break;
+ }
+ return height * 16 / 9;
+}
+
} // namespace UISettings
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 20a517d34..8efd63f31 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -13,6 +13,22 @@
#include <QVector>
#include "common/common_types.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
+
+using Settings::Category;
+using Settings::Setting;
+
+#ifndef CANNOT_EXPLICITLY_INSTANTIATE
+namespace Settings {
+extern template class Setting<bool>;
+extern template class Setting<std::string>;
+extern template class Setting<u16, true>;
+extern template class Setting<u32>;
+extern template class Setting<u8, true>;
+extern template class Setting<u8>;
+extern template class Setting<unsigned long long>;
+} // namespace Settings
+#endif
namespace UISettings {
@@ -56,6 +72,8 @@ struct GameDir {
};
struct Values {
+ Settings::Linkage linkage{1000};
+
QByteArray geometry;
QByteArray state;
@@ -64,30 +82,56 @@ struct Values {
QByteArray gamelist_header_state;
QByteArray microprofile_geometry;
- Settings::Setting<bool> microprofile_visible{false, "microProfileDialogVisible"};
-
- Settings::Setting<bool> single_window_mode{true, "singleWindowMode"};
- Settings::Setting<bool> fullscreen{false, "fullscreen"};
- Settings::Setting<bool> display_titlebar{true, "displayTitleBars"};
- Settings::Setting<bool> show_filter_bar{true, "showFilterBar"};
- Settings::Setting<bool> show_status_bar{true, "showStatusBar"};
-
- Settings::Setting<bool> confirm_before_closing{true, "confirmClose"};
- Settings::Setting<bool> first_start{true, "firstStart"};
- Settings::Setting<bool> pause_when_in_background{false, "pauseWhenInBackground"};
- Settings::Setting<bool> mute_when_in_background{false, "muteWhenInBackground"};
- Settings::Setting<bool> hide_mouse{true, "hideInactiveMouse"};
- Settings::Setting<bool> controller_applet_disabled{false, "disableControllerApplet"};
-
+ Setting<bool> microprofile_visible{linkage, false, "microProfileDialogVisible",
+ Category::UiLayout};
+
+ Setting<bool> single_window_mode{linkage, true, "singleWindowMode", Category::Ui};
+ Setting<bool> fullscreen{linkage, false, "fullscreen", Category::Ui};
+ Setting<bool> display_titlebar{linkage, true, "displayTitleBars", Category::Ui};
+ Setting<bool> show_filter_bar{linkage, true, "showFilterBar", Category::Ui};
+ Setting<bool> show_status_bar{linkage, true, "showStatusBar", Category::Ui};
+
+ Setting<bool> confirm_before_closing{
+ linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
+ true, true};
+ Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
+ Setting<bool> pause_when_in_background{linkage,
+ false,
+ "pauseWhenInBackground",
+ Category::UiGeneral,
+ Settings::Specialization::Default,
+ true,
+ true};
+ Setting<bool> mute_when_in_background{
+ linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default,
+ true, true};
+ Setting<bool> hide_mouse{
+ linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
+ true, true};
+ Setting<bool> controller_applet_disabled{linkage, false, "disableControllerApplet",
+ Category::UiGeneral};
// Set when Vulkan is known to crash the application
bool has_broken_vulkan = false;
- Settings::Setting<bool> select_user_on_boot{false, "select_user_on_boot"};
+ Setting<bool> select_user_on_boot{linkage,
+ false,
+ "select_user_on_boot",
+ Category::UiGeneral,
+ Settings::Specialization::Default,
+ true,
+ true};
+ Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Ui};
// Discord RPC
- Settings::Setting<bool> enable_discord_presence{true, "enable_discord_presence"};
+ Setting<bool> enable_discord_presence{linkage, true, "enable_discord_presence", Category::Ui};
- Settings::Setting<bool> enable_screenshot_save_as{true, "enable_screenshot_save_as"};
+ // logging
+ Setting<bool> show_console{linkage, false, "showConsole", Category::Ui};
+
+ // Screenshots
+ Setting<bool> enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as",
+ Category::Screenshots};
+ Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots};
QString roms_path;
QString symbols_path;
@@ -102,51 +146,52 @@ struct Values {
// Shortcut name <Shortcut, context>
std::vector<Shortcut> shortcuts;
- Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"};
+ Setting<u32> callout_flags{linkage, 0, "calloutFlags", Category::Ui};
// multiplayer settings
- Settings::Setting<QString> multiplayer_nickname{{}, "nickname"};
- Settings::Setting<QString> multiplayer_ip{{}, "ip"};
- Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"};
- Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};
- Settings::Setting<QString> multiplayer_room_name{{}, "room_name"};
- Settings::SwitchableSetting<uint, true> multiplayer_max_player{8, 0, 8, "max_player"};
- Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, UINT16_MAX,
- "room_port"};
- Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"};
- Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"};
- Settings::Setting<QString> multiplayer_room_description{{}, "room_description"};
+ Setting<std::string> multiplayer_nickname{linkage, {}, "nickname", Category::Multiplayer};
+ Setting<std::string> multiplayer_ip{linkage, {}, "ip", Category::Multiplayer};
+ Setting<u16, true> multiplayer_port{linkage, 24872, 0,
+ UINT16_MAX, "port", Category::Multiplayer};
+ Setting<std::string> multiplayer_room_nickname{
+ linkage, {}, "room_nickname", Category::Multiplayer};
+ Setting<std::string> multiplayer_room_name{linkage, {}, "room_name", Category::Multiplayer};
+ Setting<u8, true> multiplayer_max_player{linkage, 8, 0, 8, "max_player", Category::Multiplayer};
+ Setting<u16, true> multiplayer_room_port{linkage, 24872, 0,
+ UINT16_MAX, "room_port", Category::Multiplayer};
+ Setting<u8, true> multiplayer_host_type{linkage, 0, 0, 1, "host_type", Category::Multiplayer};
+ Setting<unsigned long long> multiplayer_game_id{linkage, {}, "game_id", Category::Multiplayer};
+ Setting<std::string> multiplayer_room_description{
+ linkage, {}, "room_description", Category::Multiplayer};
std::pair<std::vector<std::string>, std::vector<std::string>> multiplayer_ban_list;
- // logging
- Settings::Setting<bool> show_console{false, "showConsole"};
-
// Game List
- Settings::Setting<bool> show_add_ons{true, "show_add_ons"};
- Settings::Setting<uint32_t> game_icon_size{64, "game_icon_size"};
- Settings::Setting<uint32_t> folder_icon_size{48, "folder_icon_size"};
- Settings::Setting<uint8_t> row_1_text_id{3, "row_1_text_id"};
- Settings::Setting<uint8_t> row_2_text_id{2, "row_2_text_id"};
+ Setting<bool> show_add_ons{linkage, true, "show_add_ons", Category::UiGameList};
+ Setting<u32> game_icon_size{linkage, 64, "game_icon_size", Category::UiGameList};
+ Setting<u32> folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList};
+ Setting<u8> row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList};
+ Setting<u8> row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList};
std::atomic_bool is_game_list_reload_pending{false};
- Settings::Setting<bool> cache_game_list{true, "cache_game_list"};
- Settings::Setting<bool> favorites_expanded{true, "favorites_expanded"};
+ Setting<bool> cache_game_list{linkage, true, "cache_game_list", Category::UiGameList};
+ Setting<bool> favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList};
QVector<u64> favorited_ids;
// Compatibility List
- Settings::Setting<bool> show_compat{false, "show_compat"};
+ Setting<bool> show_compat{linkage, false, "show_compat", Category::UiGameList};
// Size & File Types Column
- Settings::Setting<bool> show_size{true, "show_size"};
- Settings::Setting<bool> show_types{true, "show_types"};
+ Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
+ Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
bool configuration_applied;
bool reset_to_defaults;
bool shortcut_already_warned{false};
- Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"};
};
extern Values values;
+u32 CalculateWidth(u32 height, Settings::AspectRatio ratio);
+
} // namespace UISettings
Q_DECLARE_METATYPE(UISettings::GameDir*);
diff --git a/src/yuzu/vk_device_info.cpp b/src/yuzu/vk_device_info.cpp
index e1a0e6a2a..92f10d315 100644
--- a/src/yuzu/vk_device_info.cpp
+++ b/src/yuzu/vk_device_info.cpp
@@ -3,6 +3,9 @@
#include <utility>
#include <vector>
+
+#include "yuzu/qt_common.h"
+
#include "common/dynamic_library.h"
#include "common/logging/log.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -11,7 +14,6 @@
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "vulkan/vulkan_core.h"
-#include "yuzu/qt_common.h"
#include "yuzu/vk_device_info.h"
class QWindow;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c5bc472ca..0d25ff400 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -98,8 +98,26 @@ void Config::ReadSetting(const std::string& group, Settings::Setting<Type, range
static_cast<long>(setting.GetDefault())));
}
+void Config::ReadCategory(Settings::Category category) {
+ for (const auto setting : Settings::values.linkage.by_category[category]) {
+ const char* category_name = [&]() {
+ if (category == Settings::Category::Controls) {
+ // For compatibility with older configs
+ return "ControlsGeneral";
+ } else {
+ return Settings::TranslateCategory(category);
+ }
+ }();
+ std::string setting_value =
+ sdl2_config->Get(category_name, setting->GetLabel(), setting->DefaultToString());
+ setting->LoadString(setting_value);
+ }
+}
+
void Config::ReadValues() {
// Controls
+ ReadCategory(Settings::Category::Controls);
+
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
auto& player = Settings::values.players.GetValue()[p];
@@ -139,13 +157,6 @@ void Config::ReadValues() {
player.connected = sdl2_config->GetBoolean(group, "connected", false);
}
- ReadSetting("ControlsGeneral", Settings::values.mouse_enabled);
-
- ReadSetting("ControlsGeneral", Settings::values.touch_device);
-
- ReadSetting("ControlsGeneral", Settings::values.keyboard_enabled);
-
- ReadSetting("ControlsGeneral", Settings::values.debug_pad_enabled);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.debug_pad_buttons[i] = sdl2_config->Get(
@@ -166,14 +177,6 @@ void Config::ReadValues() {
Settings::values.debug_pad_analogs[i] = default_param;
}
- ReadSetting("ControlsGeneral", Settings::values.enable_raw_input);
- ReadSetting("ControlsGeneral", Settings::values.enable_joycon_driver);
- ReadSetting("ControlsGeneral", Settings::values.enable_procon_driver);
- ReadSetting("ControlsGeneral", Settings::values.random_amiibo_id);
- ReadSetting("ControlsGeneral", Settings::values.emulate_analog_keyboard);
- ReadSetting("ControlsGeneral", Settings::values.vibration_enabled);
- ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations);
- ReadSetting("ControlsGeneral", Settings::values.motion_enabled);
Settings::values.touchscreen.enabled =
sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
Settings::values.touchscreen.rotation_angle =
@@ -217,10 +220,24 @@ void Config::ReadValues() {
Settings::values.touch_from_button_map_index = std::clamp(
Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
- ReadSetting("ControlsGeneral", Settings::values.udp_input_servers);
+ ReadCategory(Settings::Category::Audio);
+ ReadCategory(Settings::Category::Core);
+ ReadCategory(Settings::Category::Cpu);
+ ReadCategory(Settings::Category::CpuDebug);
+ ReadCategory(Settings::Category::CpuUnsafe);
+ ReadCategory(Settings::Category::Renderer);
+ ReadCategory(Settings::Category::RendererAdvanced);
+ ReadCategory(Settings::Category::RendererDebug);
+ ReadCategory(Settings::Category::System);
+ ReadCategory(Settings::Category::SystemAudio);
+ ReadCategory(Settings::Category::DataStorage);
+ ReadCategory(Settings::Category::Debugging);
+ ReadCategory(Settings::Category::DebuggingGraphics);
+ ReadCategory(Settings::Category::Miscellaneous);
+ ReadCategory(Settings::Category::Network);
+ ReadCategory(Settings::Category::WebService);
// Data Storage
- ReadSetting("Data Storage", Settings::values.use_virtual_sd);
FS::SetYuzuPath(FS::YuzuPath::NANDDir,
sdl2_config->Get("Data Storage", "nand_directory",
FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
@@ -233,130 +250,16 @@ void Config::ReadValues() {
FS::SetYuzuPath(FS::YuzuPath::DumpDir,
sdl2_config->Get("Data Storage", "dump_directory",
FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
- ReadSetting("Data Storage", Settings::values.gamecard_inserted);
- ReadSetting("Data Storage", Settings::values.gamecard_current_game);
- ReadSetting("Data Storage", Settings::values.gamecard_path);
-
- // System
- ReadSetting("System", Settings::values.use_docked_mode);
-
- ReadSetting("System", Settings::values.current_user);
- Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
- Service::Account::MAX_USERS - 1);
-
- const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
- if (rng_seed_enabled) {
- Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0));
- } else {
- Settings::values.rng_seed.SetValue(std::nullopt);
- }
-
- const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false);
- if (custom_rtc_enabled) {
- Settings::values.custom_rtc = sdl2_config->GetInteger("System", "custom_rtc", 0);
- } else {
- Settings::values.custom_rtc = std::nullopt;
- }
-
- ReadSetting("System", Settings::values.language_index);
- ReadSetting("System", Settings::values.region_index);
- ReadSetting("System", Settings::values.time_zone_index);
- ReadSetting("System", Settings::values.sound_index);
-
- // Core
- ReadSetting("Core", Settings::values.use_multi_core);
- ReadSetting("Core", Settings::values.use_unsafe_extended_memory_layout);
-
- // Cpu
- ReadSetting("Cpu", Settings::values.cpu_accuracy);
- ReadSetting("Cpu", Settings::values.cpu_debug_mode);
- ReadSetting("Cpu", Settings::values.cpuopt_page_tables);
- ReadSetting("Cpu", Settings::values.cpuopt_block_linking);
- ReadSetting("Cpu", Settings::values.cpuopt_return_stack_buffer);
- ReadSetting("Cpu", Settings::values.cpuopt_fast_dispatcher);
- ReadSetting("Cpu", Settings::values.cpuopt_context_elimination);
- ReadSetting("Cpu", Settings::values.cpuopt_const_prop);
- ReadSetting("Cpu", Settings::values.cpuopt_misc_ir);
- ReadSetting("Cpu", Settings::values.cpuopt_reduce_misalign_checks);
- ReadSetting("Cpu", Settings::values.cpuopt_fastmem);
- ReadSetting("Cpu", Settings::values.cpuopt_fastmem_exclusives);
- ReadSetting("Cpu", Settings::values.cpuopt_recompile_exclusives);
- ReadSetting("Cpu", Settings::values.cpuopt_ignore_memory_aborts);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_unfuse_fma);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_reduce_fp_error);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_inaccurate_nan);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_fastmem_check);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_global_monitor);
-
- // Renderer
- ReadSetting("Renderer", Settings::values.renderer_backend);
- ReadSetting("Renderer", Settings::values.async_presentation);
- ReadSetting("Renderer", Settings::values.renderer_force_max_clock);
- ReadSetting("Renderer", Settings::values.renderer_debug);
- ReadSetting("Renderer", Settings::values.renderer_shader_feedback);
- ReadSetting("Renderer", Settings::values.enable_nsight_aftermath);
- ReadSetting("Renderer", Settings::values.disable_shader_loop_safety_checks);
- ReadSetting("Renderer", Settings::values.vulkan_device);
-
- ReadSetting("Renderer", Settings::values.resolution_setup);
- ReadSetting("Renderer", Settings::values.scaling_filter);
- ReadSetting("Renderer", Settings::values.fsr_sharpening_slider);
- ReadSetting("Renderer", Settings::values.anti_aliasing);
- ReadSetting("Renderer", Settings::values.fullscreen_mode);
- ReadSetting("Renderer", Settings::values.aspect_ratio);
- ReadSetting("Renderer", Settings::values.max_anisotropy);
- ReadSetting("Renderer", Settings::values.use_speed_limit);
- ReadSetting("Renderer", Settings::values.speed_limit);
- ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
- ReadSetting("Renderer", Settings::values.gpu_accuracy);
- ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
- ReadSetting("Renderer", Settings::values.vsync_mode);
- ReadSetting("Renderer", Settings::values.shader_backend);
- ReadSetting("Renderer", Settings::values.use_reactive_flushing);
- ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
- ReadSetting("Renderer", Settings::values.nvdec_emulation);
- ReadSetting("Renderer", Settings::values.accelerate_astc);
- ReadSetting("Renderer", Settings::values.async_astc);
- ReadSetting("Renderer", Settings::values.astc_recompression);
- ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
- ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache);
-
- ReadSetting("Renderer", Settings::values.bg_red);
- ReadSetting("Renderer", Settings::values.bg_green);
- ReadSetting("Renderer", Settings::values.bg_blue);
-
- // Audio
- ReadSetting("Audio", Settings::values.sink_id);
- ReadSetting("Audio", Settings::values.audio_output_device_id);
- ReadSetting("Audio", Settings::values.volume);
-
- // Miscellaneous
- // log_filter has a different default here than from common
- Settings::values.log_filter =
- sdl2_config->Get("Miscellaneous", Settings::values.log_filter.GetLabel(), "*:Trace");
- ReadSetting("Miscellaneous", Settings::values.use_dev_keys);
// Debugging
Settings::values.record_frame_times =
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
- ReadSetting("Debugging", Settings::values.dump_exefs);
- ReadSetting("Debugging", Settings::values.dump_nso);
- ReadSetting("Debugging", Settings::values.enable_fs_access_log);
- ReadSetting("Debugging", Settings::values.reporting_services);
- ReadSetting("Debugging", Settings::values.quest_flag);
- ReadSetting("Debugging", Settings::values.use_debug_asserts);
- ReadSetting("Debugging", Settings::values.use_auto_stub);
- ReadSetting("Debugging", Settings::values.disable_macro_jit);
- ReadSetting("Debugging", Settings::values.disable_macro_hle);
- ReadSetting("Debugging", Settings::values.use_gdbstub);
- ReadSetting("Debugging", Settings::values.gdbstub_port);
const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
std::stringstream ss(title_list);
std::string line;
while (std::getline(ss, line, '|')) {
- const auto title_id = std::stoul(line, nullptr, 16);
+ const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
std::stringstream inner_ss(disabled_list);
@@ -368,15 +271,6 @@ void Config::ReadValues() {
Settings::values.disabled_addons.insert_or_assign(title_id, out);
}
-
- // Web Service
- ReadSetting("WebService", Settings::values.enable_telemetry);
- ReadSetting("WebService", Settings::values.web_api_url);
- ReadSetting("WebService", Settings::values.yuzu_username);
- ReadSetting("WebService", Settings::values.yuzu_token);
-
- // Network
- ReadSetting("Network", Settings::values.network_interface);
}
void Config::Reload() {
diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h
index 021438b17..512591a39 100644
--- a/src/yuzu_cmd/config.h
+++ b/src/yuzu_cmd/config.h
@@ -34,4 +34,5 @@ private:
*/
template <typename Type, bool ranged>
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
+ void ReadCategory(Settings::Category category);
};
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) {
nickname = match[1];
password = match[2];
address = match[3];
- if (!match[4].str().empty())
- port = std::stoi(match[4]);
+ if (!match[4].str().empty()) {
+ port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0));
+ }
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
if (!std::regex_match(nickname, nickname_re)) {
std::cout
@@ -358,6 +359,7 @@ int main(int argc, char** argv) {
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
+ system.GetUserChannel().clear();
const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath)};