summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt31
-rw-r--r--src/android/.gitignore3
-rw-r--r--src/android/app/build.gradle.kts39
-rw-r--r--src/android/app/src/main/AndroidManifest.xml11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt71
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt4
-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/DriverAdapter.kt117
-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.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt49
-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.kt239
-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.kt285
-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.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt8
-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/DriverManagerFragment.kt186
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt75
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt259
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt109
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt213
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt109
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt138
-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.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt158
-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.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt13
-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/TaskViewModel.kt41
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt15
-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.kt553
-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/DocumentsTree.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt164
-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/GpuDriverHelper.kt228
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt112
-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/views/FixedRatioSurfaceView.kt48
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt2
-rw-r--r--src/android/app/src/main/jni/config.cpp38
-rw-r--r--src/android/app/src/main/jni/config.h24
-rw-r--r--src/android/app/src/main/jni/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.cpp150
-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_build.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_delete.xml9
-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/activity_settings.xml54
-rw-r--r--src/android/app/src/main/res/layout/card_driver_option.xml89
-rw-r--r--src/android/app/src/main/res/layout/card_installable.xml71
-rw-r--r--src/android/app/src/main/res/layout/dialog_progress_bar.xml28
-rw-r--r--src/android/app/src/main/res/layout/fragment_driver_manager.xml48
-rw-r--r--src/android/app/src/main/res/layout/fragment_emulation.xml86
-rw-r--r--src/android/app/src/main/res/layout/fragment_installables.xml31
-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/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.xml35
-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.xml4
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-w600dp/integers.xml6
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml4
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml4
-rw-r--r--src/android/app/src/main/res/values/arrays.xml10
-rw-r--r--src/android/app/src/main/res/values/dimens.xml3
-rw-r--r--src/android/app/src/main/res/values/integers.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml36
-rw-r--r--src/android/build.gradle.kts4
-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.cpp227
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h111
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/command_buffer.h23
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp (renamed from src/audio_core/renderer/adsp/command_list_processor.cpp)29
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/command_list_processor.h (renamed from src/audio_core/renderer/adsp/command_list_processor.h)21
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.cpp107
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.h38
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.cpp269
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.h92
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp111
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h39
-rw-r--r--src/audio_core/adsp/apps/opus/shared_memory.h17
-rw-r--r--src/audio_core/adsp/mailbox.h60
-rw-r--r--src/audio_core/audio_core.cpp4
-rw-r--r--src/audio_core/audio_core.h6
-rw-r--r--src/audio_core/audio_event.cpp1
-rw-r--r--src/audio_core/audio_in_manager.cpp2
-rw-r--r--src/audio_core/audio_in_manager.h4
-rw-r--r--src/audio_core/audio_out_manager.cpp2
-rw-r--r--src/audio_core/audio_out_manager.h3
-rw-r--r--src/audio_core/audio_render_manager.cpp4
-rw-r--r--src/audio_core/audio_render_manager.h4
-rw-r--r--src/audio_core/common/audio_renderer_parameter.h6
-rw-r--r--src/audio_core/opus/decoder.cpp179
-rw-r--r--src/audio_core/opus/decoder.h53
-rw-r--r--src/audio_core/opus/decoder_manager.cpp102
-rw-r--r--src/audio_core/opus/decoder_manager.h38
-rw-r--r--src/audio_core/opus/hardware_opus.cpp241
-rw-r--r--src/audio_core/opus/hardware_opus.h45
-rw-r--r--src/audio_core/opus/parameters.h54
-rw-r--r--src/audio_core/renderer/adsp/adsp.cpp117
-rw-r--r--src/audio_core/renderer/adsp/adsp.h171
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp225
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h204
-rw-r--r--src/audio_core/renderer/adsp/command_buffer.h21
-rw-r--r--src/audio_core/renderer/audio_device.cpp4
-rw-r--r--src/audio_core/renderer/audio_device.h4
-rw-r--r--src/audio_core/renderer/audio_renderer.cpp4
-rw-r--r--src/audio_core/renderer/audio_renderer.h6
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp4
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.h8
-rw-r--r--src/audio_core/renderer/behavior/info_updater.cpp4
-rw-r--r--src/audio_core/renderer/behavior/info_updater.h4
-rw-r--r--src/audio_core/renderer/command/command_buffer.cpp4
-rw-r--r--src/audio_core/renderer/command/command_buffer.h4
-rw-r--r--src/audio_core/renderer/command/command_generator.cpp4
-rw-r--r--src/audio_core/renderer/command/command_generator.h4
-rw-r--r--src/audio_core/renderer/command/command_list_header.h4
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.cpp8
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.h4
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.cpp24
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.h23
-rw-r--r--src/audio_core/renderer/command/data_source/decode.cpp110
-rw-r--r--src/audio_core/renderer/command/data_source/decode.h4
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.cpp28
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.h23
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.cpp28
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.h23
-rw-r--r--src/audio_core/renderer/command/effect/aux_.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/aux_.h13
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.cpp14
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.h13
-rw-r--r--src/audio_core/renderer/command/effect/capture.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/capture.h13
-rw-r--r--src/audio_core/renderer/command/effect/compressor.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/compressor.h13
-rw-r--r--src/audio_core/renderer/command/effect/delay.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/delay.h13
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.h13
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.cpp22
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.h19
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp14
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h13
-rw-r--r--src/audio_core/renderer/command/effect/reverb.cpp12
-rw-r--r--src/audio_core/renderer/command/effect/reverb.h13
-rw-r--r--src/audio_core/renderer/command/icommand.h17
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.cpp14
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.h13
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.cpp14
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.h13
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp14
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.h13
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.cpp14
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.h15
-rw-r--r--src/audio_core/renderer/command/mix/mix.cpp12
-rw-r--r--src/audio_core/renderer/command/mix/mix.h13
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.cpp13
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.h13
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp13
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.h13
-rw-r--r--src/audio_core/renderer/command/mix/volume.cpp12
-rw-r--r--src/audio_core/renderer/command/mix/volume.h13
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.cpp13
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.h13
-rw-r--r--src/audio_core/renderer/command/performance/performance.cpp20
-rw-r--r--src/audio_core/renderer/command/performance/performance.h13
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp14
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h13
-rw-r--r--src/audio_core/renderer/command/resample/resample.cpp4
-rw-r--r--src/audio_core/renderer/command/resample/resample.h4
-rw-r--r--src/audio_core/renderer/command/resample/upsample.cpp12
-rw-r--r--src/audio_core/renderer/command/resample/upsample.h13
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.cpp14
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.h13
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp12
-rw-r--r--src/audio_core/renderer/command/sink/device.h13
-rw-r--r--src/audio_core/renderer/effect/aux_.cpp4
-rw-r--r--src/audio_core/renderer/effect/aux_.h4
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.cpp4
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.h4
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.cpp4
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.h4
-rw-r--r--src/audio_core/renderer/effect/capture.cpp4
-rw-r--r--src/audio_core/renderer/effect/capture.h4
-rw-r--r--src/audio_core/renderer/effect/compressor.cpp4
-rw-r--r--src/audio_core/renderer/effect/compressor.h4
-rw-r--r--src/audio_core/renderer/effect/delay.cpp4
-rw-r--r--src/audio_core/renderer/effect/delay.h4
-rw-r--r--src/audio_core/renderer/effect/effect_context.cpp4
-rw-r--r--src/audio_core/renderer/effect/effect_context.h4
-rw-r--r--src/audio_core/renderer/effect/effect_info_base.h4
-rw-r--r--src/audio_core/renderer/effect/effect_reset.h4
-rw-r--r--src/audio_core/renderer/effect/effect_result_state.h4
-rw-r--r--src/audio_core/renderer/effect/i3dl2.cpp4
-rw-r--r--src/audio_core/renderer/effect/i3dl2.h4
-rw-r--r--src/audio_core/renderer/effect/light_limiter.cpp4
-rw-r--r--src/audio_core/renderer/effect/light_limiter.h4
-rw-r--r--src/audio_core/renderer/effect/reverb.cpp4
-rw-r--r--src/audio_core/renderer/effect/reverb.h4
-rw-r--r--src/audio_core/renderer/memory/address_info.h4
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.cpp4
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.h4
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.cpp4
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.h4
-rw-r--r--src/audio_core/renderer/mix/mix_context.cpp4
-rw-r--r--src/audio_core/renderer/mix/mix_context.h4
-rw-r--r--src/audio_core/renderer/mix/mix_info.cpp4
-rw-r--r--src/audio_core/renderer/mix/mix_info.h4
-rw-r--r--src/audio_core/renderer/nodes/bit_array.h4
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.cpp4
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.h4
-rw-r--r--src/audio_core/renderer/nodes/node_states.cpp4
-rw-r--r--src/audio_core/renderer/nodes/node_states.h4
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.cpp4
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.h4
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.cpp4
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.h4
-rw-r--r--src/audio_core/renderer/performance/performance_detail.h4
-rw-r--r--src/audio_core/renderer/performance/performance_entry.h4
-rw-r--r--src/audio_core/renderer/performance/performance_entry_addresses.h4
-rw-r--r--src/audio_core/renderer/performance/performance_frame_header.h4
-rw-r--r--src/audio_core/renderer/performance/performance_manager.cpp4
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h4
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.cpp4
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.h4
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.cpp4
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.h4
-rw-r--r--src/audio_core/renderer/sink/sink_context.cpp4
-rw-r--r--src/audio_core/renderer/sink/sink_context.h4
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.cpp4
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.h4
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.cpp4
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.h4
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.cpp4
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.h4
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.cpp4
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.h4
-rw-r--r--src/audio_core/renderer/system.cpp54
-rw-r--r--src/audio_core/renderer/system.h16
-rw-r--r--src/audio_core/renderer/system_manager.cpp30
-rw-r--r--src/audio_core/renderer/system_manager.h21
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_info.h4
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.cpp4
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.h4
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_state.h4
-rw-r--r--src/audio_core/renderer/voice/voice_channel_resource.h4
-rw-r--r--src/audio_core/renderer/voice/voice_context.cpp4
-rw-r--r--src/audio_core/renderer/voice/voice_context.h4
-rw-r--r--src/audio_core/renderer/voice/voice_info.cpp4
-rw-r--r--src/audio_core/renderer/voice/voice_info.h4
-rw-r--r--src/audio_core/renderer/voice/voice_state.h4
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp47
-rw-r--r--src/audio_core/sink/cubeb_sink.h7
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp40
-rw-r--r--src/audio_core/sink/sdl2_sink.h7
-rw-r--r--src/audio_core/sink/sink_details.cpp45
-rw-r--r--src/audio_core/sink/sink_stream.cpp4
-rw-r--r--src/common/CMakeLists.txt19
-rw-r--r--src/common/arm64/native_clock.cpp72
-rw-r--r--src/common/arm64/native_clock.h47
-rw-r--r--src/common/bounded_threadsafe_queue.h4
-rw-r--r--src/common/common_funcs.h4
-rw-r--r--src/common/elf.h8
-rw-r--r--src/common/fs/fs.cpp15
-rw-r--r--src/common/fs/fs_paths.h2
-rw-r--r--src/common/fs/path_util.cpp8
-rw-r--r--src/common/fs/path_util.h2
-rw-r--r--src/common/logging/filter.cpp2
-rw-r--r--src/common/logging/types.h2
-rw-r--r--src/common/polyfill_thread.h39
-rw-r--r--src/common/settings.cpp22
-rw-r--r--src/common/settings.h21
-rw-r--r--src/common/settings_common.cpp1
-rw-r--r--src/common/settings_common.h13
-rw-r--r--src/common/settings_enums.h4
-rw-r--r--src/common/settings_setting.h33
-rw-r--r--src/common/string_util.cpp5
-rw-r--r--src/common/string_util.h1
-rw-r--r--src/common/swap.h5
-rw-r--r--src/common/wall_clock.cpp8
-rw-r--r--src/core/CMakeLists.txt39
-rw-r--r--src/core/core.cpp55
-rw-r--r--src/core/core.h20
-rw-r--r--src/core/core_timing.cpp79
-rw-r--r--src/core/core_timing.h12
-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/content_archive.cpp17
-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/partition_filesystem.cpp1
-rw-r--r--src/core/file_sys/patch_manager.cpp4
-rw-r--r--src/core/file_sys/patch_manager.h2
-rw-r--r--src/core/file_sys/program_metadata.cpp8
-rw-r--r--src/core/file_sys/program_metadata.h1
-rw-r--r--src/core/file_sys/registered_cache.cpp65
-rw-r--r--src/core/file_sys/registered_cache.h5
-rw-r--r--src/core/file_sys/submission_package.cpp35
-rw-r--r--src/core/file_sys/submission_package.h1
-rw-r--r--src/core/file_sys/vfs.h2
-rw-r--r--src/core/frontend/applets/controller.cpp4
-rw-r--r--src/core/frontend/framebuffer_layout.cpp3
-rw-r--r--src/core/hid/emulated_controller.cpp11
-rw-r--r--src/core/hid/emulated_controller.h2
-rw-r--r--src/core/hid/hid_core.cpp8
-rw-r--r--src/core/hid/hid_core.h7
-rw-r--r--src/core/hle/kernel/k_capabilities.cpp1
-rw-r--r--src/core/hle/kernel/k_hardware_timer.cpp4
-rw-r--r--src/core/hle/kernel/k_memory_layout.cpp2
-rw-r--r--src/core/hle/kernel/k_page_table.cpp34
-rw-r--r--src/core/hle/kernel/k_page_table.h3
-rw-r--r--src/core/hle/kernel/k_process.cpp30
-rw-r--r--src/core/hle/kernel/k_process.h15
-rw-r--r--src/core/hle/kernel/k_transfer_memory.cpp89
-rw-r--r--src/core/hle/kernel/k_transfer_memory.h14
-rw-r--r--src/core/hle/kernel/kernel.cpp16
-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_transfer_memory.cpp54
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/acc/acc.cpp43
-rw-r--r--src/core/hle/service/acc/acc.h3
-rw-r--r--src/core/hle/service/acc/acc_su.cpp6
-rw-r--r--src/core/hle/service/acc/profile_manager.h3
-rw-r--r--src/core/hle/service/am/am.cpp607
-rw-r--r--src/core/hle/service/am/am.h75
-rw-r--r--src/core/hle/service/am/applet_ae.cpp68
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.h11
-rw-r--r--src/core/hle/service/am/applets/applet_error.cpp5
-rw-r--r--src/core/hle/service/am/applets/applet_general_backend.cpp6
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit.cpp55
-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/applets.cpp16
-rw-r--r--src/core/hle/service/am/applets/applets.h49
-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/audin_u.cpp4
-rw-r--r--src/core/hle/service/audio/audout_u.cpp2
-rw-r--r--src/core/hle/service/audio/audren_u.cpp2
-rw-r--r--src/core/hle/service/audio/audren_u.h2
-rw-r--r--src/core/hle/service/audio/errors.h12
-rw-r--r--src/core/hle/service/audio/hwopus.cpp697
-rw-r--r--src/core/hle/service/audio/hwopus.h21
-rw-r--r--src/core/hle/service/caps/caps.cpp21
-rw-r--r--src/core/hle/service/caps/caps.h81
-rw-r--r--src/core/hle/service/caps/caps_a.cpp239
-rw-r--r--src/core/hle/service/caps/caps_a.h21
-rw-r--r--src/core/hle/service/caps/caps_c.cpp50
-rw-r--r--src/core/hle/service/caps/caps_c.h10
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp386
-rw-r--r--src/core/hle/service/caps/caps_manager.h79
-rw-r--r--src/core/hle/service/caps/caps_result.h35
-rw-r--r--src/core/hle/service/caps/caps_sc.cpp5
-rw-r--r--src/core/hle/service/caps/caps_sc.h6
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp5
-rw-r--r--src/core/hle/service/caps/caps_ss.h6
-rw-r--r--src/core/hle/service/caps/caps_su.cpp9
-rw-r--r--src/core/hle/service/caps/caps_su.h6
-rw-r--r--src/core/hle/service/caps/caps_types.h184
-rw-r--r--src/core/hle/service/caps/caps_u.cpp146
-rw-r--r--src/core/hle/service/caps/caps_u.h12
-rw-r--r--src/core/hle/service/es/es.cpp10
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp11
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp68
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h2
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp69
-rw-r--r--src/core/hle/service/hid/controllers/npad.h22
-rw-r--r--src/core/hle/service/hid/hid.cpp33
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/jit/jit_context.cpp36
-rw-r--r--src/core/hle/service/ldn/ldn.cpp91
-rw-r--r--src/core/hle/service/mii/mii.cpp502
-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.cpp947
-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.cpp40
-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/nifm/nifm.cpp12
-rw-r--r--src/core/hle/service/nifm/nifm.h1
-rw-r--r--src/core/hle/service/ns/iplatform_service_manager.cpp17
-rw-r--r--src/core/hle/service/ns/ns.cpp27
-rw-r--r--src/core/hle/service/ns/ns.h4
-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/nvdrv/devices/nvmap.h14
-rw-r--r--src/core/hle/service/nvnflinger/buffer_item.h2
-rw-r--r--src/core/hle/service/nvnflinger/buffer_slot.h2
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp351
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.h65
-rw-r--r--src/core/hle/service/nvnflinger/graphic_buffer_producer.h2
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp11
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h9
-rw-r--r--src/core/hle/service/nvnflinger/ui/fence.h3
-rw-r--r--src/core/hle/service/nvnflinger/ui/graphic_buffer.h4
-rw-r--r--src/core/hle/service/pctl/pctl_module.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/sockets/bsd.cpp7
-rw-r--r--src/core/hle/service/sockets/bsd.h3
-rw-r--r--src/core/hle/service/sockets/nsd.cpp11
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp12
-rw-r--r--src/core/hle/service/ssl/ssl.cpp10
-rw-r--r--src/core/hle/service/ssl/ssl_backend_openssl.cpp3
-rw-r--r--src/core/hle/service/ssl/ssl_backend_schannel.cpp28
-rw-r--r--src/core/hle/service/vi/vi.cpp131
-rw-r--r--src/core/internal_network/network.cpp89
-rw-r--r--src/core/internal_network/network.h3
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp12
-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.cpp4
-rw-r--r--src/core/loader/loader.h10
-rw-r--r--src/core/loader/nca.cpp76
-rw-r--r--src/core/loader/nca.h2
-rw-r--r--src/core/loader/nro.cpp5
-rw-r--r--src/core/loader/nso.cpp7
-rw-r--r--src/core/loader/nsp.cpp39
-rw-r--r--src/core/loader/nsp.h2
-rw-r--r--src/core/loader/xci.cpp34
-rw-r--r--src/core/loader/xci.h2
-rw-r--r--src/core/memory/cheat_engine.cpp2
-rw-r--r--src/core/reporter.cpp4
-rw-r--r--src/core/telemetry_session.cpp3
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
-rw-r--r--src/dedicated_room/yuzu_room.cpp6
-rw-r--r--src/input_common/CMakeLists.txt2
-rw-r--r--src/input_common/drivers/udp_client.cpp1
-rw-r--r--src/input_common/drivers/virtual_gamepad.h2
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.h2
-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_composite.cpp2
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_image.cpp2
-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/frontend/maxwell/translate/impl/move_special_register.cpp3
-rw-r--r--src/tests/common/ring_buffer.cpp2
-rw-r--r--src/video_core/CMakeLists.txt6
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h70
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h12
-rw-r--r--src/video_core/control/channel_state_cache.h2
-rw-r--r--src/video_core/dma_pusher.cpp34
-rw-r--r--src/video_core/dma_pusher.h7
-rw-r--r--src/video_core/engines/draw_manager.cpp24
-rw-r--r--src/video_core/engines/draw_manager.h3
-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.cpp80
-rw-r--r--src/video_core/engines/maxwell_3d.h3
-rw-r--r--src/video_core/engines/maxwell_dma.cpp17
-rw-r--r--src/video_core/engines/maxwell_dma.h55
-rw-r--r--src/video_core/engines/puller.cpp28
-rw-r--r--src/video_core/fence_manager.h21
-rw-r--r--src/video_core/gpu.cpp4
-rw-r--r--src/video_core/host1x/codecs/codec.cpp13
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt7
-rw-r--r--src/video_core/host_shaders/convert_d32f_to_abgr8.frag14
-rw-r--r--src/video_core/host_shaders/convert_msaa_to_non_msaa.comp7
-rw-r--r--src/video_core/host_shaders/convert_non_msaa_to_msaa.comp7
-rw-r--r--src/video_core/host_shaders/queries_prefix_scan_sum.comp173
-rw-r--r--src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp138
-rw-r--r--src/video_core/host_shaders/resolve_conditional_render.comp20
-rw-r--r--src/video_core/macro/macro.cpp24
-rw-r--r--src/video_core/macro/macro_hle.cpp49
-rw-r--r--src/video_core/query_cache.h13
-rw-r--r--src/video_core/query_cache/bank_base.h105
-rw-r--r--src/video_core/query_cache/query_base.h70
-rw-r--r--src/video_core/query_cache/query_cache.h580
-rw-r--r--src/video_core/query_cache/query_cache_base.h181
-rw-r--r--src/video_core/query_cache/query_stream.h149
-rw-r--r--src/video_core/query_cache/types.h74
-rw-r--r--src/video_core/rasterizer_interface.h13
-rw-r--r--src/video_core/renderer_base.h3
-rw-r--r--src/video_core/renderer_null/null_rasterizer.cpp18
-rw-r--r--src/video_core/renderer_null/null_rasterizer.h7
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp51
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h7
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp14
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h2
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h1
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp13
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h4
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp7
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp18
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h8
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp320
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h50
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp29
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.cpp67
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.h10
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp1596
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h106
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp152
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h12
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp33
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp175
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h9
-rw-r--r--src/video_core/surface.cpp3
-rw-r--r--src/video_core/surface.h4
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp8
-rw-r--r--src/video_core/texture_cache/formatter.h2
-rw-r--r--src/video_core/texture_cache/image_base.h2
-rw-r--r--src/video_core/texture_cache/image_view_base.cpp1
-rw-r--r--src/video_core/texture_cache/texture_cache.h16
-rw-r--r--src/video_core/texture_cache/util.cpp38
-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.cpp79
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h87
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.cpp8
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp29
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h14
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp6
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h48
-rw-r--r--src/web_service/verify_user_jwt.cpp1
-rw-r--r--src/yuzu/CMakeLists.txt4
-rw-r--r--src/yuzu/applets/qt_amiibo_settings.cpp3
-rw-r--r--src/yuzu/applets/qt_controller.cpp53
-rw-r--r--src/yuzu/applets/qt_controller.h6
-rw-r--r--src/yuzu/applets/qt_controller.ui54
-rw-r--r--src/yuzu/bootmanager.cpp4
-rw-r--r--src/yuzu/configuration/config.cpp13
-rw-r--r--src/yuzu/configuration/config.h3
-rw-r--r--src/yuzu/configuration/configure_audio.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui99
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp28
-rw-r--r--src/yuzu/configuration/configure_input.cpp50
-rw-r--r--src/yuzu/configuration/configure_input.h1
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp7
-rw-r--r--src/yuzu/configuration/configure_system.cpp5
-rw-r--r--src/yuzu/configuration/configure_ui.cpp12
-rw-r--r--src/yuzu/configuration/configure_ui.ui7
-rw-r--r--src/yuzu/configuration/shared_translation.cpp16
-rw-r--r--src/yuzu/configuration/shared_widget.cpp234
-rw-r--r--src/yuzu/configuration/shared_widget.h27
-rw-r--r--src/yuzu/game_list.cpp32
-rw-r--r--src/yuzu/game_list.h11
-rw-r--r--src/yuzu/game_list_p.h26
-rw-r--r--src/yuzu/game_list_worker.cpp39
-rw-r--r--src/yuzu/game_list_worker.h11
-rw-r--r--src/yuzu/hotkeys.h4
-rw-r--r--src/yuzu/main.cpp847
-rw-r--r--src/yuzu/main.h52
-rw-r--r--src/yuzu/main.ui49
-rw-r--r--src/yuzu/play_time_manager.cpp179
-rw-r--r--src/yuzu/play_time_manager.h44
-rw-r--r--src/yuzu/uisettings.h16
-rw-r--r--src/yuzu/util/util.cpp102
-rw-r--r--src/yuzu/util/util.h14
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp6
668 files changed, 25130 insertions, 9154 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7bb88c8ea..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
@@ -61,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
@@ -84,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()
@@ -105,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
@@ -114,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)
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 a8db70511..ac43d84b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -27,7 +27,7 @@ android {
namespace = "org.yuzu.yuzu_emu"
compileSdkVersion = "android-34"
- ndkVersion = "25.2.9519653"
+ ndkVersion = "26.1.10909125"
buildFeatures {
viewBinding = true
@@ -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")
}
@@ -184,23 +203,23 @@ ktlint {
}
dependencies {
- implementation("androidx.core:core-ktx:1.10.1")
+ implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("androidx.recyclerview:recyclerview:1.3.0")
+ implementation("androidx.recyclerview:recyclerview:1.3.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
- implementation("androidx.fragment:fragment-ktx:1.6.0")
+ implementation("androidx.fragment:fragment-ktx:1.6.1")
implementation("androidx.documentfile:documentfile:1.0.1")
implementation("com.google.android.material:material:1.9.0")
- implementation("androidx.preference:preference:1.2.0")
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+ implementation("androidx.preference:preference-ktx:1.2.1")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
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")
- implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
- implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
+ implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
+ implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 6184f3eb6..a67351727 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -25,9 +25,9 @@ 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"
android:fullBackupContent="@xml/data_extraction_rules"
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
android:enableOnBackInvokedCallback="true">
@@ -55,7 +55,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 +65,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..115f72710 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
@@ -15,16 +15,10 @@ import androidx.annotation.Keep
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
-import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
-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.FileUtil
+import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
/**
@@ -77,7 +71,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
} else {
- openContentUri(appContext, path, openmode)
+ FileUtil.openContentUri(path, openmode)
}
}
@@ -87,7 +81,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.getFileSize(path)
} else {
- getFileSize(appContext, path)
+ FileUtil.getFileSize(path)
}
}
@@ -97,7 +91,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.exists(path)
} else {
- exists(appContext, path)
+ FileUtil.exists(path)
}
}
@@ -107,7 +101,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.isDirectory(path)
} else {
- isDirectory(appContext, path)
+ FileUtil.isDirectory(path)
}
}
@@ -219,10 +213,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?)
/**
@@ -253,7 +243,12 @@ object NativeLibrary {
external fun setAppDirectory(directory: String)
- external fun installFileToNand(filename: String): Int
+ /**
+ * Installs a nsp or xci file to nand
+ * @param filename String representation of file uri
+ * @param extension Lowercase string representation of file extension without "."
+ */
+ external fun installFileToNand(filename: String, extension: String): Int
external fun initializeGpuDriver(
hookLibDir: String?,
@@ -314,21 +309,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 +393,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 +437,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 +449,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 +474,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.
*/
@@ -517,6 +513,11 @@ object NativeLibrary {
external fun submitInlineKeyboardInput(key_code: Int)
/**
+ * Creates a generic user directory if it doesn't exist already
+ */
+ external fun initializeEmptyUserDirectory()
+
+ /**
* Button type for use in onTouchEvent
*/
object ButtonType {
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..8c053670c 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,8 +46,8 @@ class YuzuApplication : Application() {
super.onCreate()
application = this
documentsTree = DocumentsTree()
- DirectoryInitialization.start(applicationContext)
- GpuDriverHelper.initializeDriverParameters(applicationContext)
+ DirectoryInitialization.start()
+ GpuDriverHelper.initializeDriverParameters()
NativeLibrary.logDeviceInfo()
createNotificationChannels()
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/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
new file mode 100644
index 000000000..0e818cab9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -0,0 +1,117 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
+import org.yuzu.yuzu_emu.model.DriverViewModel
+import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+
+class DriverAdapter(private val driverViewModel: DriverViewModel) :
+ ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
+ AsyncDifferConfig.Builder(DiffCallback()).build()
+ ) {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
+ val binding =
+ CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return DriverViewHolder(binding)
+ }
+
+ override fun getItemCount(): Int = currentList.size
+
+ override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
+ holder.bind(currentList[position])
+
+ private fun onSelectDriver(position: Int) {
+ driverViewModel.setSelectedDriverIndex(position)
+ notifyItemChanged(driverViewModel.previouslySelectedDriver)
+ notifyItemChanged(driverViewModel.selectedDriver)
+ }
+
+ private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
+ if (driverViewModel.selectedDriver > position) {
+ driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
+ }
+ if (GpuDriverHelper.customDriverData == driverData.second) {
+ driverViewModel.setSelectedDriverIndex(0)
+ }
+ driverViewModel.driversToDelete.add(driverData.first)
+ driverViewModel.removeDriver(driverData)
+ notifyItemRemoved(position)
+ notifyItemChanged(driverViewModel.selectedDriver)
+ }
+
+ inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ private lateinit var driverData: Pair<String, GpuDriverMetadata>
+
+ fun bind(driverData: Pair<String, GpuDriverMetadata>) {
+ this.driverData = driverData
+ val driver = driverData.second
+
+ binding.apply {
+ radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
+ root.setOnClickListener {
+ onSelectDriver(bindingAdapterPosition)
+ }
+ buttonDelete.setOnClickListener {
+ onDeleteDriver(driverData, bindingAdapterPosition)
+ }
+
+ // Delay marquee by 3s
+ title.postDelayed(
+ {
+ title.isSelected = true
+ title.ellipsize = TextUtils.TruncateAt.MARQUEE
+ version.isSelected = true
+ version.ellipsize = TextUtils.TruncateAt.MARQUEE
+ description.isSelected = true
+ description.ellipsize = TextUtils.TruncateAt.MARQUEE
+ },
+ 3000
+ )
+ if (driver.name == null) {
+ title.setText(R.string.system_gpu_driver)
+ description.text = ""
+ version.text = ""
+ version.visibility = View.GONE
+ description.visibility = View.GONE
+ buttonDelete.visibility = View.GONE
+ } else {
+ title.text = driver.name
+ version.text = driver.version
+ description.text = driver.description
+ version.visibility = View.VISIBLE
+ description.visibility = View.VISIBLE
+ buttonDelete.visibility = View.VISIBLE
+ }
+ }
+ }
+ }
+
+ private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
+ override fun areItemsTheSame(
+ oldItem: Pair<String, GpuDriverMetadata>,
+ newItem: Pair<String, GpuDriverMetadata>
+ ): Boolean {
+ return oldItem.first == newItem.first
+ }
+
+ override fun areContentsTheSame(
+ oldItem: Pair<String, GpuDriverMetadata>,
+ newItem: Pair<String, GpuDriverMetadata>
+ ): Boolean {
+ return oldItem.second == newItem.second
+ }
+ }
+}
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 9f859b442..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
@@ -10,8 +10,12 @@ 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
@@ -45,8 +49,9 @@ class HomeSettingAdapter(
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)
}
}
@@ -86,7 +91,11 @@ class HomeSettingAdapter(
binding.optionIcon.alpha = 0.5f
}
- option.details.observe(viewLifecycle) { updateOptionDetails(it) }
+ viewLifecycle.lifecycleScope.launch {
+ viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
+ option.details.collect { updateOptionDetails(it) }
+ }
+ }
binding.optionDetail.postDelayed(
{
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
new file mode 100644
index 000000000..e960fbaab
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
+import org.yuzu.yuzu_emu.model.Installable
+
+class InstallableAdapter(private val installables: List<Installable>) :
+ RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() {
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): InstallableAdapter.InstallableViewHolder {
+ val binding =
+ CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return InstallableViewHolder(binding)
+ }
+
+ override fun getItemCount(): Int = installables.size
+
+ override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) =
+ holder.bind(installables[position])
+
+ inner class InstallableViewHolder(val binding: CardInstallableBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ lateinit var installable: Installable
+
+ fun bind(installable: Installable) {
+ this.installable = installable
+
+ binding.title.setText(installable.titleId)
+ binding.description.setText(installable.descriptionId)
+
+ if (installable.install != null) {
+ binding.buttonInstall.visibility = View.VISIBLE
+ binding.buttonInstall.setOnClickListener { installable.install.invoke() }
+ }
+ if (installable.export != null) {
+ binding.buttonExport.visibility = View.VISIBLE
+ binding.buttonExport.setOnClickListener { installable.export.invoke() }
+ }
+ }
+ }
+}
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..c73edd50e 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,40 @@
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 org.yuzu.yuzu_emu.NativeLibrary
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 +46,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 +72,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 +118,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 +149,53 @@ 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")
}
+ NativeLibrary.reloadSettings()
- 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
- ) { view: View, windowInsets: WindowInsetsCompat ->
+ binding.navigationBarShade
+ ) { _: 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
- mlpShade.height = barInsets.bottom
- binding.navigationBarShade.layoutParams = mlpShade
+ // The only situation where we care to have a nav bar shade is when it's at the bottom
+ // of the screen where scrolling list elements can go behind it.
+ val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
+ mlpNavShade.height = barInsets.bottom
+ binding.navigationBarShade.layoutParams = mlpNavShade
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 9711e2c51..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,160 +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 = String.format(
- context.getString(R.string.value_with_units),
- sliderProgress.toString(),
- 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 = String.format(
- context.getString(R.string.value_with_units),
- sliderProgress.toString(),
- item.units
- )
- }
- }
-
- dialog = MaterialAlertDialogBuilder(context)
- .setTitle(item.nameId)
- .setView(sliderBinding.root)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, defaultCancelListener)
- .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 79572fc06..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
@@ -29,7 +29,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
}
binding.textSettingValue.visibility = View.VISIBLE
- val epochTime = setting.value.toLong()
+ val epochTime = setting.value
val instant = Instant.ofEpochMilli(epochTime * 1000)
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
@@ -46,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/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index b42d955aa..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
@@ -35,7 +35,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
}
}
} else if (item is StringSingleChoiceSetting) {
- for (i in item.values!!.indices) {
+ for (i in item.values.indices) {
if (item.values[i] == item.selectedValue) {
binding.textSettingValue.text = item.choices[i]
break
@@ -66,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 a23b5d109..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
@@ -41,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/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index ef34bf5f4..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,10 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
binding.textSettingDescription.text = ""
binding.textSettingDescription.visibility = View.GONE
}
+
+ 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.isChecked = setting.isChecked
setStyle(setting.isEditable, binding)
}
@@ -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/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
new file mode 100644
index 000000000..df21d74b2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -0,0 +1,186 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.result.contract.ActivityResultContracts
+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.lifecycleScope
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.GridLayoutManager
+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.adapters.DriverAdapter
+import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
+import org.yuzu.yuzu_emu.model.DriverViewModel
+import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import java.io.File
+import java.io.IOException
+
+class DriverManagerFragment : Fragment() {
+ private var _binding: FragmentDriverManagerBinding? = null
+ private val binding get() = _binding!!
+
+ private val homeViewModel: HomeViewModel by activityViewModels()
+ private val driverViewModel: DriverViewModel by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentDriverManagerBinding.inflate(inflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ homeViewModel.setNavigationVisibility(visible = false, animated = true)
+ homeViewModel.setStatusBarShadeVisibility(visible = false)
+
+ if (!driverViewModel.isInteractionAllowed) {
+ DriversLoadingDialogFragment().show(
+ childFragmentManager,
+ DriversLoadingDialogFragment.TAG
+ )
+ }
+
+ binding.toolbarDrivers.setNavigationOnClickListener {
+ binding.root.findNavController().popBackStack()
+ }
+
+ binding.buttonInstall.setOnClickListener {
+ getDriver.launch(arrayOf("application/zip"))
+ }
+
+ binding.listDrivers.apply {
+ layoutManager = GridLayoutManager(
+ requireContext(),
+ resources.getInteger(R.integer.grid_columns)
+ )
+ adapter = DriverAdapter(driverViewModel)
+ }
+
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ driverViewModel.driverList.collectLatest {
+ (binding.listDrivers.adapter as DriverAdapter).submitList(it)
+ }
+ }
+ launch {
+ driverViewModel.newDriverInstalled.collect {
+ if (_binding != null && it) {
+ (binding.listDrivers.adapter as DriverAdapter).apply {
+ notifyItemChanged(driverViewModel.previouslySelectedDriver)
+ notifyItemChanged(driverViewModel.selectedDriver)
+ driverViewModel.setNewDriverInstalled(false)
+ }
+ }
+ }
+ }
+ }
+
+ setInsets()
+ }
+
+ // Start installing requested driver
+ override fun onStop() {
+ super.onStop()
+ driverViewModel.onCloseDriverManager()
+ }
+
+ private fun setInsets() =
+ ViewCompat.setOnApplyWindowInsetsListener(
+ 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 mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams
+ mlpAppBar.leftMargin = leftInsets
+ mlpAppBar.rightMargin = rightInsets
+ binding.toolbarDrivers.layoutParams = mlpAppBar
+
+ val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams
+ mlplistDrivers.leftMargin = leftInsets
+ mlplistDrivers.rightMargin = rightInsets
+ binding.listDrivers.layoutParams = mlplistDrivers
+
+ val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
+ val mlpFab =
+ binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
+ mlpFab.leftMargin = leftInsets + fabSpacing
+ mlpFab.rightMargin = rightInsets + fabSpacing
+ mlpFab.bottomMargin = barInsets.bottom + fabSpacing
+ binding.buttonInstall.layoutParams = mlpFab
+
+ binding.listDrivers.updatePadding(
+ bottom = barInsets.bottom +
+ resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
+ )
+
+ windowInsets
+ }
+
+ private val getDriver =
+ registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
+ if (result == null) {
+ return@registerForActivityResult
+ }
+
+ IndeterminateProgressDialogFragment.newInstance(
+ requireActivity(),
+ R.string.installing_driver,
+ false
+ ) {
+ val driverPath =
+ "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
+ val driverFile = File(driverPath)
+
+ // Ignore file exceptions when a user selects an invalid zip
+ try {
+ if (!GpuDriverHelper.copyDriverToInternalStorage(result)) {
+ throw IOException("Driver failed validation!")
+ }
+ } catch (_: IOException) {
+ if (driverFile.exists()) {
+ driverFile.delete()
+ }
+ return@newInstance getString(R.string.select_gpu_driver_error)
+ }
+
+ val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
+ val driverInList =
+ driverViewModel.driverList.value.firstOrNull { it.second == driverData }
+ if (driverInList != null) {
+ return@newInstance getString(R.string.driver_already_installed)
+ } else {
+ driverViewModel.addDriver(Pair(driverPath, driverData))
+ driverViewModel.setNewDriverInstalled(true)
+ }
+ return@newInstance Any()
+ }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
new file mode 100644
index 000000000..f8c34346a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
@@ -0,0 +1,75 @@
+// 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.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 kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
+import org.yuzu.yuzu_emu.model.DriverViewModel
+
+class DriversLoadingDialogFragment : DialogFragment() {
+ private val driverViewModel: DriverViewModel by activityViewModels()
+
+ private lateinit var binding: DialogProgressBarBinding
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ binding = DialogProgressBarBinding.inflate(layoutInflater)
+ binding.progressBar.isIndeterminate = true
+
+ isCancelable = false
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.loading)
+ .setView(binding.root)
+ .create()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = binding.root
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ driverViewModel.areDriversLoading.collect { checkForDismiss() }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ driverViewModel.isDriverReady.collect { checkForDismiss() }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ driverViewModel.isDeletingDrivers.collect { checkForDismiss() }
+ }
+ }
+ }
+ }
+
+ private fun checkForDismiss() {
+ if (driverViewModel.isInteractionAllowed) {
+ dismiss()
+ }
+ }
+
+ companion object {
+ const val TAG = "DriversLoadingDialogFragment"
+ }
+}
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 25b9d4018..598a9d42b 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,30 @@ 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 android.widget.Toast
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 +39,10 @@ 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.collect
+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,10 +51,12 @@ 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.DriverViewModel
+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.*
+import java.lang.NullPointerException
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var preferences: SharedPreferences
@@ -62,11 +67,14 @@ 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 val driverViewModel: DriverViewModel by activityViewModels()
- private lateinit var onReturnFromSettings: ActivityResultLauncher<Intent>
+ private var isInFoldableLayout = false
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -81,11 +89,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 +100,36 @@ 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
+ }
+ }
+
+ try {
+ game = if (args.game != null) {
+ args.game!!
+ } else {
+ intentGame!!
+ }
+ } catch (e: NullPointerException) {
+ Toast.makeText(
+ requireContext(),
+ R.string.no_game_present,
+ Toast.LENGTH_SHORT
+ ).show()
+ requireActivity().finish()
+ return
+ }
+
// 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 +144,21 @@ 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?) {
+ super.onViewCreated(view, savedInstanceState)
+ if (requireActivity().isFinishing) {
+ return
+ }
+
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 +183,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 +198,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 +214,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 +227,118 @@ 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)
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ driverViewModel.isDriverReady.collect {
+ if (it && !emulationState.isRunning) {
+ if (!DirectoryInitialization.areDirectoriesReady) {
+ DirectoryInitialization.start()
+ }
+
+ updateScreenLayout()
+
+ emulationState.run(emulationActivity!!.isActivityRecreated)
+ }
+ }
+ }
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
+ if (_binding == null) {
+ return
+ }
+
+ updateScreenLayout()
if (emulationActivity?.isInPictureInPictureMode == true) {
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
}
if (EmulationMenuSettings.showOverlay) {
- binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
+ binding.surfaceInputOverlay.visibility = View.INVISIBLE
}
} else {
- if (EmulationMenuSettings.showOverlay) {
- binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
+ if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
+ binding.surfaceInputOverlay.visibility = View.VISIBLE
+ } else {
+ binding.surfaceInputOverlay.visibility = View.INVISIBLE
}
if (!isInFoldableLayout) {
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -217,25 +347,11 @@ 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())
- }
-
- updateScreenLayout()
-
- emulationState.run(emulationActivity!!.isActivityRecreated)
- }
-
override fun onPause() {
- if (emulationState.isRunning) {
+ if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
}
super.onPause()
@@ -251,10 +367,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 +384,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) {
@@ -307,16 +417,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
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 +441,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
isInFoldableLayout = true
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
- refreshInputOverlay()
}
}
it.isSeparating
@@ -428,7 +528,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 +552,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 +658,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 +707,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 +764,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 +771,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@Synchronized
fun newSurface(surface: Surface?) {
this.surface = surface
- if (runWhenSurfaceIsValid) {
+ if (this.surface != null) {
runWithValidSurface()
}
}
@@ -702,10 +799,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 +811,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 d5e793491..fd9785075 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
@@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.fragments
import android.Manifest
import android.content.ActivityNotFoundException
-import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
@@ -25,18 +24,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.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -50,6 +49,7 @@ class HomeSettingsFragment : Fragment() {
private lateinit var mainActivity: MainActivity
private val homeViewModel: HomeViewModel by activityViewModels()
+ private val driverViewModel: DriverViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -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,34 +96,39 @@ 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(
HomeSetting(
- R.string.install_gpu_driver,
+ R.string.gpu_driver_manager,
R.string.install_gpu_driver_description,
- R.drawable.ic_exit,
- { driverInstaller() },
+ R.drawable.ic_build,
+ {
+ binding.root.findNavController()
+ .navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
+ },
{ GpuDriverHelper.supportsCustomDriverLoading() },
R.string.custom_driver_not_supported,
- R.string.custom_driver_not_supported_description
+ R.string.custom_driver_not_supported_description,
+ driverViewModel.selectedDriverMetadata
)
)
add(
HomeSetting(
- R.string.install_amiibo_keys,
- R.string.install_amiibo_keys_description,
- R.drawable.ic_nfc,
- { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
- )
- )
- add(
- HomeSetting(
- R.string.install_game_content,
- R.string.install_game_content_description,
- R.drawable.ic_system_update_alt,
- { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
+ R.string.manage_yuzu_data,
+ R.string.manage_yuzu_data_description,
+ R.drawable.ic_install,
+ {
+ binding.root.findNavController()
+ .navigate(R.id.action_homeSettingsFragment_to_installableFragment)
+ }
)
)
add(
@@ -138,35 +149,6 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
- R.string.manage_save_data,
- R.string.import_export_saves_description,
- R.drawable.ic_save,
- {
- ImportExportSavesFragment().show(
- parentFragmentManager,
- ImportExportSavesFragment.TAG
- )
- }
- )
- )
- add(
- HomeSetting(
- R.string.install_prod_keys,
- R.string.install_prod_keys_description,
- R.drawable.ic_unlock,
- { mainActivity.getProdKey.launch(arrayOf("*/*")) }
- )
- )
- add(
- HomeSetting(
- R.string.install_firmware,
- R.string.install_firmware_description,
- R.drawable.ic_firmware,
- { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
- )
- )
- add(
- HomeSetting(
R.string.share_log,
R.string.share_log_description,
R.drawable.ic_log,
@@ -314,31 +296,6 @@ class HomeSettingsFragment : Fragment() {
}
}
- private fun driverInstaller() {
- // Get the driver name for the dialog message.
- var driverName = GpuDriverHelper.customDriverName
- if (driverName == null) {
- driverName = getString(R.string.system_gpu_driver)
- }
-
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(getString(R.string.select_gpu_driver_title))
- .setMessage(driverName)
- .setNegativeButton(android.R.string.cancel, null)
- .setNeutralButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
- GpuDriverHelper.installDefaultDriver(requireContext())
- Toast.makeText(
- requireContext(),
- R.string.select_gpu_driver_use_default,
- Toast.LENGTH_SHORT
- ).show()
- }
- .setPositiveButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
- mainActivity.getDriver.launch(arrayOf("application/zip"))
- }
- .show()
- }
-
private fun shareLog() {
val file = DocumentFile.fromSingleUri(
mainActivity,
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
deleted file mode 100644
index e1495ee8c..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt
+++ /dev/null
@@ -1,213 +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 android.provider.DocumentsContract
-import android.widget.Toast
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AppCompatActivity
-import androidx.documentfile.provider.DocumentFile
-import androidx.fragment.app.DialogFragment
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import java.io.BufferedOutputStream
-import java.io.File
-import java.io.FileOutputStream
-import java.io.FilenameFilter
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
-import java.util.zip.ZipEntry
-import java.util.zip.ZipOutputStream
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.DocumentProvider
-import org.yuzu.yuzu_emu.getPublicFilesDir
-import org.yuzu.yuzu_emu.utils.FileUtil
-
-class ImportExportSavesFragment : DialogFragment() {
- private val context = YuzuApplication.appContext
- private val savesFolder =
- "${context.getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
-
- // Get first subfolder in saves folder (should be the user folder)
- private val savesFolderRoot = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
- private var lastZipCreated: File? = null
-
- private lateinit var startForResultExportSave: ActivityResultLauncher<Intent>
- private lateinit var documentPicker: ActivityResultLauncher<Array<String>>
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val activity = requireActivity() as AppCompatActivity
-
- val activityResultRegistry = requireActivity().activityResultRegistry
- startForResultExportSave = activityResultRegistry.register(
- "startForResultExportSaveKey",
- ActivityResultContracts.StartActivityForResult()
- ) {
- File(context.getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
- }
- documentPicker = activityResultRegistry.register(
- "documentPickerKey",
- ActivityResultContracts.OpenDocument()
- ) {
- it?.let { uri -> importSave(uri, activity) }
- }
- }
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- return if (savesFolderRoot == "") {
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(R.string.manage_save_data)
- .setMessage(R.string.import_export_saves_no_profile)
- .setPositiveButton(android.R.string.ok, null)
- .show()
- } else {
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(R.string.manage_save_data)
- .setMessage(R.string.manage_save_data_description)
- .setNegativeButton(R.string.export_saves) { _, _ ->
- exportSave()
- }
- .setPositiveButton(R.string.import_saves) { _, _ ->
- documentPicker.launch(arrayOf("application/zip"))
- }
- .setNeutralButton(android.R.string.cancel, null)
- .show()
- }
- }
-
- /**
- * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
- * @return true if the zip file is successfully created, false otherwise.
- */
- private fun zipSave(): Boolean {
- try {
- val tempFolder = File(requireContext().getPublicFilesDir().canonicalPath, "temp")
- tempFolder.mkdirs()
- val saveFolder = File(savesFolderRoot)
- val outputZipFile = File(
- tempFolder,
- "yuzu saves - ${
- LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
- }.zip"
- )
- outputZipFile.createNewFile()
- ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
- saveFolder.walkTopDown().forEach { file ->
- val zipFileName =
- file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
- if (zipFileName == "") {
- return@forEach
- }
- val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
- zos.putNextEntry(entry)
- if (file.isFile) {
- file.inputStream().use { fis -> fis.copyTo(zos) }
- }
- }
- }
- lastZipCreated = outputZipFile
- } catch (e: Exception) {
- return false
- }
- return true
- }
-
- /**
- * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
- */
- private fun exportSave() {
- CoroutineScope(Dispatchers.IO).launch {
- val wasZipCreated = zipSave()
- val lastZipFile = lastZipCreated
- if (!wasZipCreated || lastZipFile == null) {
- withContext(Dispatchers.Main) {
- Toast.makeText(context, "Failed to export save", Toast.LENGTH_LONG).show()
- }
- return@launch
- }
-
- withContext(Dispatchers.Main) {
- val file = DocumentFile.fromSingleUri(
- context,
- DocumentsContract.buildDocumentUri(
- DocumentProvider.AUTHORITY,
- "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
- )
- )!!
- val intent = Intent(Intent.ACTION_SEND)
- .setDataAndType(file.uri, "application/zip")
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- .putExtra(Intent.EXTRA_STREAM, file.uri)
- startForResultExportSave.launch(Intent.createChooser(intent, "Share save file"))
- }
- }
- }
-
- /**
- * Imports the save files contained in the zip file, and replaces any existing ones with the new save file.
- * @param zipUri The Uri of the zip file containing the save file(s) to import.
- */
- private fun importSave(zipUri: Uri, activity: AppCompatActivity) {
- val inputZip = context.contentResolver.openInputStream(zipUri)
- // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
- var validZip = false
- val savesFolder = File(savesFolderRoot)
- val cacheSaveDir = File("${context.cacheDir.path}/saves/")
- cacheSaveDir.mkdir()
-
- if (inputZip == null) {
- Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
- .show()
- return
- }
-
- val filterTitleId =
- FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
-
- try {
- CoroutineScope(Dispatchers.IO).launch {
- FileUtil.unzip(inputZip, cacheSaveDir)
- cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
- File(savesFolder, savePath).deleteRecursively()
- File(cacheSaveDir, savePath).copyRecursively(File(savesFolder, savePath), true)
- validZip = true
- }
-
- withContext(Dispatchers.Main) {
- if (!validZip) {
- MessageDialogFragment.newInstance(
- R.string.save_file_invalid_zip_structure,
- R.string.save_file_invalid_zip_structure_description
- ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
- return@withContext
- }
- Toast.makeText(
- context,
- context.getString(R.string.save_file_imported_success),
- Toast.LENGTH_LONG
- ).show()
- }
-
- cacheSaveDir.deleteRecursively()
- }
- } catch (e: Exception) {
- Toast.makeText(context, context.getString(R.string.fatal_error), Toast.LENGTH_LONG)
- .show()
- }
- }
-
- companion object {
- const val TAG = "ImportExportSavesFragment"
- }
-}
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..7e467814d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -5,63 +5,130 @@ package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
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.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
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)
-
- 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
- )
+ .setView(binding.root)
+
+ if (cancellable) {
+ dialog.setNegativeButton(android.R.string.cancel, null)
+ }
+
+ val alertDialog = dialog.create()
+ alertDialog.setCanceledOnTouchOutside(false)
+
+ if (!taskViewModel.isRunning.value) {
+ taskViewModel.runTask()
+ }
+ 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
+ )
+
+ else -> {
+ // Do nothing
+ }
+ }
+ taskViewModel.clear()
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ taskViewModel.cancelled.collect {
+ if (it) {
+ dialog?.setTitle(R.string.cancelling)
+ }
+ }
}
- taskViewModel.clear()
}
}
+ }
- if (taskViewModel.isRunning.value == false) {
- taskViewModel.runTask()
+ // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
+ // Setting the OnClickListener again after the dialog is shown overrides this behavior.
+ override fun onResume() {
+ super.onResume()
+ val alertDialog = dialog as AlertDialog
+ val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
+ negativeButton.setOnClickListener {
+ alertDialog.setTitle(getString(R.string.cancelling))
+ taskViewModel.setCancelled(true)
}
- return dialog
}
companion object {
const val TAG = "IndeterminateProgressDialogFragment"
private const val TITLE = "Title"
+ private const val CANCELLABLE = "Cancellable"
fun newInstance(
- activity: AppCompatActivity,
+ activity: FragmentActivity,
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/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
new file mode 100644
index 000000000..ec116ab62
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
@@ -0,0 +1,138 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+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.navigation.findNavController
+import androidx.recyclerview.widget.GridLayoutManager
+import com.google.android.material.transition.MaterialSharedAxis
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.adapters.InstallableAdapter
+import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
+import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.model.Installable
+import org.yuzu.yuzu_emu.ui.main.MainActivity
+
+class InstallableFragment : Fragment() {
+ private var _binding: FragmentInstallablesBinding? = null
+ private val binding get() = _binding!!
+
+ private val homeViewModel: HomeViewModel by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentInstallablesBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val mainActivity = requireActivity() as MainActivity
+
+ homeViewModel.setNavigationVisibility(visible = false, animated = true)
+ homeViewModel.setStatusBarShadeVisibility(visible = false)
+
+ binding.toolbarInstallables.setNavigationOnClickListener {
+ binding.root.findNavController().popBackStack()
+ }
+
+ val installables = listOf(
+ Installable(
+ R.string.user_data,
+ R.string.user_data_description,
+ install = { mainActivity.importUserData.launch(arrayOf("application/zip")) },
+ export = { mainActivity.exportUserData.launch("export.zip") }
+ ),
+ Installable(
+ R.string.install_game_content,
+ R.string.install_game_content_description,
+ install = { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
+ ),
+ Installable(
+ R.string.install_firmware,
+ R.string.install_firmware_description,
+ install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
+ ),
+ if (mainActivity.savesFolderRoot != "") {
+ Installable(
+ R.string.manage_save_data,
+ R.string.import_export_saves_description,
+ install = { mainActivity.importSaves.launch(arrayOf("application/zip")) },
+ export = { mainActivity.exportSave() }
+ )
+ } else {
+ Installable(
+ R.string.manage_save_data,
+ R.string.import_export_saves_description,
+ install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }
+ )
+ },
+ Installable(
+ R.string.install_prod_keys,
+ R.string.install_prod_keys_description,
+ install = { mainActivity.getProdKey.launch(arrayOf("*/*")) }
+ ),
+ Installable(
+ R.string.install_amiibo_keys,
+ R.string.install_amiibo_keys_description,
+ install = { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
+ )
+ )
+
+ binding.listInstallables.apply {
+ layoutManager = GridLayoutManager(
+ requireContext(),
+ resources.getInteger(R.integer.grid_columns)
+ )
+ adapter = InstallableAdapter(installables)
+ }
+
+ setInsets()
+ }
+
+ private fun setInsets() =
+ ViewCompat.setOnApplyWindowInsetsListener(
+ 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 mlpAppBar = binding.toolbarInstallables.layoutParams as ViewGroup.MarginLayoutParams
+ mlpAppBar.leftMargin = leftInsets
+ mlpAppBar.rightMargin = rightInsets
+ binding.toolbarInstallables.layoutParams = mlpAppBar
+
+ val mlpScrollAbout =
+ binding.listInstallables.layoutParams as ViewGroup.MarginLayoutParams
+ mlpScrollAbout.leftMargin = leftInsets
+ mlpScrollAbout.rightMargin = rightInsets
+ binding.listInstallables.layoutParams = mlpScrollAbout
+
+ binding.listInstallables.updatePadding(bottom = barInsets.bottom)
+
+ windowInsets
+ }
+}
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 d50c421a0..c66bb635a 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
@@ -22,10 +22,14 @@ 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
@@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
)
}
- homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
- if (it) {
- pageForward()
- homeViewModel.setShouldPageForward(false)
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ homeViewModel.shouldPageForward.collect {
+ if (it) {
+ pageForward()
+ homeViewModel.setShouldPageForward(false)
+ }
+ }
}
}
@@ -287,8 +295,10 @@ class SetupFragment : Fragment() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
- outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible)
- outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible)
+ if (_binding != null) {
+ outState.putBoolean(KEY_NEXT_VISIBILITY, binding.buttonNext.isVisible)
+ outState.putBoolean(KEY_BACK_VISIBILITY, binding.buttonBack.isVisible)
+ }
outState.putBooleanArray(KEY_HAS_BEEN_WARNED, hasBeenWarned)
}
@@ -345,11 +355,15 @@ class SetupFragment : Fragment() {
}
fun pageForward() {
- binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
+ if (_binding != null) {
+ binding.viewPager2.currentItem += 1
+ }
}
fun pageBackward() {
- binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1
+ if (_binding != null) {
+ binding.viewPager2.currentItem -= 1
+ }
}
fun setPageWarned(page: Int) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
new file mode 100644
index 000000000..62945ad65
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -0,0 +1,158 @@
+// 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 androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+import java.io.BufferedOutputStream
+import java.io.File
+
+class DriverViewModel : ViewModel() {
+ private val _areDriversLoading = MutableStateFlow(false)
+ val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading
+
+ private val _isDriverReady = MutableStateFlow(true)
+ val isDriverReady: StateFlow<Boolean> get() = _isDriverReady
+
+ private val _isDeletingDrivers = MutableStateFlow(false)
+ val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers
+
+ private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>())
+ val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
+
+ var previouslySelectedDriver = 0
+ var selectedDriver = -1
+
+ private val _selectedDriverMetadata =
+ MutableStateFlow(
+ GpuDriverHelper.customDriverData.name
+ ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ )
+ val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata
+
+ private val _newDriverInstalled = MutableStateFlow(false)
+ val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
+
+ val driversToDelete = mutableListOf<String>()
+
+ val isInteractionAllowed
+ get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value
+
+ init {
+ _areDriversLoading.value = true
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ val drivers = GpuDriverHelper.getDrivers()
+ val currentDriverMetadata = GpuDriverHelper.customDriverData
+ for (i in drivers.indices) {
+ if (drivers[i].second == currentDriverMetadata) {
+ setSelectedDriverIndex(i)
+ break
+ }
+ }
+
+ // If a user had installed a driver before the manager was implemented, this zips
+ // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
+ // be indexed and exported as expected.
+ if (selectedDriver == -1) {
+ val driverToSave =
+ File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
+ driverToSave.createNewFile()
+ FileUtil.zipFromInternalStorage(
+ File(GpuDriverHelper.driverInstallationPath!!),
+ GpuDriverHelper.driverInstallationPath!!,
+ BufferedOutputStream(driverToSave.outputStream())
+ )
+ drivers.add(Pair(driverToSave.path, currentDriverMetadata))
+ setSelectedDriverIndex(drivers.size - 1)
+ }
+
+ _driverList.value = drivers
+ _areDriversLoading.value = false
+ }
+ }
+ }
+
+ fun setSelectedDriverIndex(value: Int) {
+ if (selectedDriver != -1) {
+ previouslySelectedDriver = selectedDriver
+ }
+ selectedDriver = value
+ }
+
+ fun setNewDriverInstalled(value: Boolean) {
+ _newDriverInstalled.value = value
+ }
+
+ fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
+ val driverIndex = _driverList.value.indexOfFirst { it == driverData }
+ if (driverIndex == -1) {
+ setSelectedDriverIndex(_driverList.value.size)
+ _driverList.value.add(driverData)
+ _selectedDriverMetadata.value = driverData.second.name
+ ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ } else {
+ setSelectedDriverIndex(driverIndex)
+ }
+ }
+
+ fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
+ _driverList.value.remove(driverData)
+ }
+
+ fun onCloseDriverManager() {
+ _isDeletingDrivers.value = true
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ driversToDelete.forEach {
+ val driver = File(it)
+ if (driver.exists()) {
+ driver.delete()
+ }
+ }
+ driversToDelete.clear()
+ _isDeletingDrivers.value = false
+ }
+ }
+
+ if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) {
+ return
+ }
+
+ _isDriverReady.value = false
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ if (selectedDriver == 0) {
+ GpuDriverHelper.installDefaultDriver()
+ setDriverReady()
+ return@withContext
+ }
+
+ val driverToInstall = File(driverList.value[selectedDriver].first)
+ if (driverToInstall.exists()) {
+ GpuDriverHelper.installCustomDriver(driverToInstall)
+ } else {
+ GpuDriverHelper.installDefaultDriver()
+ }
+ setDriverReady()
+ }
+ }
+ }
+
+ private fun setDriverReady() {
+ _isDriverReady.value = true
+ _selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name
+ ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ }
+}
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 498c222fa..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,8 +3,8 @@
package org.yuzu.yuzu_emu.model
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
data class HomeSetting(
val titleId: Int,
@@ -14,5 +14,5 @@ data class HomeSetting(
val isEnabled: () -> Boolean = { true },
val disabledTitleId: Int = 0,
val disabledMessageId: Int = 0,
- val details: LiveData<String> = MutableLiveData("")
+ 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 a48ef7a88..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
@@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
import android.net.Uri
import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
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)
- private val _shouldPageForward = MutableLiveData(false)
- val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
+ val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
+ private val _shouldPageForward = MutableStateFlow(false)
- private val _gamesDir = MutableLiveData(
+ val gamesDir: StateFlow<String> get() = _gamesDir
+ private val _gamesDir = MutableStateFlow(
Uri.parse(
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getString(GameHelper.KEY_GAME_PATH, "")
).path ?: ""
)
- val gamesDir: LiveData<String> get() = _gamesDir
var navigatedToSetup = false
- init {
- _navigationVisible.value = Pair(false, 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
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt
new file mode 100644
index 000000000..36a7c97b8
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Installable.kt
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.annotation.StringRes
+
+data class Installable(
+ @StringRes val titleId: Int,
+ @StringRes val descriptionId: Int,
+ val install: (() -> Unit)? = null,
+ val export: (() -> Unit)? = null
+)
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/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index 27ea725a5..16a794dee 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,56 @@
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
}
}
}
+
+enum class TaskState {
+ Completed,
+ Failed,
+ Cancelled
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index c055c2e35..a13faf3c7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -352,7 +352,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
}
private fun addOverlayControls(layout: String) {
- val windowSize = getSafeScreenSize(context)
+ val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) {
overlayButtons.add(
initializeOverlayButton(
@@ -593,7 +593,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
}
private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) {
- val windowSize = getSafeScreenSize(context)
+ val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
val min = windowSize.first
val max = windowSize.second
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
@@ -968,14 +968,17 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
* @return A pair of points, the first being the top left corner of the safe area,
* the second being the bottom right corner of the safe area
*/
- private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
+ private fun getSafeScreenSize(
+ context: Context,
+ screenSize: Pair<Int, Int>
+ ): Pair<Point, Point> {
// Get screen size
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(context as Activity)
- var maxY = windowMetrics.bounds.height().toFloat()
- var maxX = windowMetrics.bounds.width().toFloat()
- var minY = 0
+ var maxX = screenSize.first.toFloat()
+ var maxY = screenSize.second.toFloat()
var minX = 0
+ var minY = 0
// If we have API access, calculate the safe area to draw the overlay
var cutoutLeft = 0
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 aaf3a0ec1..233aa4101 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
@@ -6,6 +6,7 @@ package org.yuzu.yuzu_emu.ui.main
import android.content.Intent
import android.net.Uri
import android.os.Bundle
+import android.provider.DocumentsContract
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.view.WindowManager
@@ -19,51 +20,65 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.documentfile.provider.DocumentFile
+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
import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationBarView
+import kotlinx.coroutines.CoroutineScope
import java.io.File
import java.io.FilenameFilter
-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.DocumentProvider
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.getPublicFilesDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.model.TaskState
+import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
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
+ private val savesFolder
+ get() = "${getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000"
+
+ // Get first subfolder in saves folder (should be the user folder)
+ val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
+ private var lastZipCreated: File? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
- settingsViewModel.settings.loadSettings()
-
ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState)
@@ -109,25 +124,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)
@@ -303,8 +326,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun processKey(result: Uri): Boolean {
if (FileUtil.getExtension(result) != "keys") {
MessageDialogFragment.newInstance(
- R.string.reading_keys_failure,
- R.string.install_prod_keys_failure_extension_description
+ this,
+ titleId = R.string.reading_keys_failure,
+ descriptionId = R.string.install_prod_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return false
}
@@ -316,11 +340,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
if (FileUtil.copyUriToInternalStorage(
- applicationContext,
result,
dstPath,
"prod.keys"
- )
+ ) != null
) {
if (NativeLibrary.reloadKeys()) {
Toast.makeText(
@@ -332,9 +355,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return true
} 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)
return false
}
@@ -367,13 +391,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val task: () -> Any = {
var messageToShow: Any
try {
- FileUtil.unzip(inputZip, cacheFirmwareDir)
+ FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir)
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
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()
@@ -391,7 +416,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.firmware_installing,
- task
+ task = task
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
@@ -403,8 +428,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
}
@@ -416,11 +442,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
if (FileUtil.copyUriToInternalStorage(
- applicationContext,
result,
dstPath,
"key_retail.bin"
- )
+ ) != null
) {
if (NativeLibrary.reloadKeys()) {
Toast.makeText(
@@ -430,171 +455,345 @@ 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)
}
}
}
- val getDriver =
- registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
- if (result == null) {
- return@registerForActivityResult
- }
-
- val takeFlags =
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
- contentResolver.takePersistableUriPermission(
- result,
- takeFlags
- )
-
- val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
- progressBinding.progressBar.isIndeterminate = true
- val installationDialog = MaterialAlertDialogBuilder(this)
- .setTitle(R.string.installing_driver)
- .setView(progressBinding.root)
- .show()
-
- lifecycleScope.launch {
- withContext(Dispatchers.IO) {
- // Ignore file exceptions when a user selects an invalid zip
- try {
- GpuDriverHelper.installCustomDriver(applicationContext, result)
- } catch (_: IOException) {
- }
-
- withContext(Dispatchers.Main) {
- installationDialog.dismiss()
-
- val driverName = GpuDriverHelper.customDriverName
- if (driverName != null) {
- Toast.makeText(
- applicationContext,
- getString(
- R.string.select_gpu_driver_install_success,
- driverName
- ),
- Toast.LENGTH_SHORT
- ).show()
- } else {
- Toast.makeText(
- applicationContext,
- R.string.select_gpu_driver_error,
- Toast.LENGTH_LONG
- ).show()
- }
- }
- }
- }
- }
-
val installGameUpdate = registerForActivityResult(
ActivityResultContracts.OpenMultipleDocuments()
) { documents: List<Uri> ->
if (documents.isNotEmpty()) {
IndeterminateProgressDialogFragment.newInstance(
this@MainActivity,
- R.string.install_game_content
+ R.string.installing_game_content
) {
var installSuccess = 0
var installOverwrite = 0
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
- }
-
- NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
- installOverwrite += 1
- }
-
- NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
- errorBaseGame += 1
- }
-
- NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
- errorExtension += 1
- }
-
- else -> {
- errorOther += 1
- }
+ documents.forEach {
+ when (
+ NativeLibrary.installFileToNand(
+ it.toString(),
+ FileUtil.getExtension(it)
+ )
+ ) {
+ NativeLibrary.InstallFileToNandResult.Success -> {
+ installSuccess += 1
}
- }
- 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)
+
+ NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
+ installOverwrite += 1
+ }
+
+ NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
+ errorBaseGame += 1
+ }
+
+ NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
+ errorExtension += 1
}
- if (installOverwrite > 0) {
- installResult.append(
- getString(
- R.string.install_game_content_success_overwrite,
- installOverwrite
- )
- )
- installResult.append(separator)
+
+ else -> {
+ errorOther += 1
}
- 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)
- }
- 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)
+ }
+ }
+
+ 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 zipResult = FileUtil.zipFromInternalStorage(
+ File(DirectoryInitialization.userDirectory!!),
+ DirectoryInitialization.userDirectory!!,
+ BufferedOutputStream(contentResolver.openOutputStream(result)),
+ taskViewModel.cancelled
+ )
+ return@newInstance when (zipResult) {
+ TaskState.Completed -> getString(R.string.user_data_export_success)
+ TaskState.Failed -> R.string.export_failed
+ TaskState.Cancelled -> R.string.user_data_export_cancelled
+ }
+ }.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
}
}
}
- return@newInstance installSuccess + installOverwrite + errorTotal
+ if (!isYuzuBackup) {
+ return@newInstance MessageDialogFragment.newInstance(
+ this,
+ titleId = R.string.invalid_yuzu_backup,
+ descriptionId = R.string.user_data_import_failed_description
+ )
+ }
+
+ // Clear existing user data
+ File(DirectoryInitialization.userDirectory!!).deleteRecursively()
+
+ // Copy archive to internal storage
+ try {
+ FileUtil.unzipToInternalStorage(
+ BufferedInputStream(contentResolver.openInputStream(result)),
+ File(DirectoryInitialization.userDirectory!!)
+ )
+ } catch (e: Exception) {
+ return@newInstance MessageDialogFragment.newInstance(
+ this,
+ titleId = R.string.import_failed,
+ descriptionId = R.string.user_data_import_failed_description
+ )
+ }
+
+ // Reinitialize relevant data
+ NativeLibrary.initializeEmulation()
+ gamesViewModel.reloadGames(false)
+
+ return@newInstance getString(R.string.user_data_import_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
+
+ /**
+ * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
+ * @return true if the zip file is successfully created, false otherwise.
+ */
+ private fun zipSave(): Boolean {
+ try {
+ val tempFolder = File(getPublicFilesDir().canonicalPath, "temp")
+ tempFolder.mkdirs()
+ val saveFolder = File(savesFolderRoot)
+ val outputZipFile = File(
+ tempFolder,
+ "yuzu saves - ${
+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
+ }.zip"
+ )
+ outputZipFile.createNewFile()
+ val result = FileUtil.zipFromInternalStorage(
+ saveFolder,
+ savesFolderRoot,
+ BufferedOutputStream(FileOutputStream(outputZipFile))
+ )
+ if (result == TaskState.Failed) {
+ return false
+ }
+ lastZipCreated = outputZipFile
+ } catch (e: Exception) {
+ return false
+ }
+ return true
+ }
+
+ /**
+ * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
+ */
+ fun exportSave() {
+ CoroutineScope(Dispatchers.IO).launch {
+ val wasZipCreated = zipSave()
+ val lastZipFile = lastZipCreated
+ if (!wasZipCreated || lastZipFile == null) {
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ this@MainActivity,
+ getString(R.string.export_save_failed),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ return@launch
+ }
+
+ withContext(Dispatchers.Main) {
+ val file = DocumentFile.fromSingleUri(
+ this@MainActivity,
+ DocumentsContract.buildDocumentUri(
+ DocumentProvider.AUTHORITY,
+ "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
+ )
+ )!!
+ val intent = Intent(Intent.ACTION_SEND)
+ .setDataAndType(file.uri, "application/zip")
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .putExtra(Intent.EXTRA_STREAM, file.uri)
+ startForResultExportSave.launch(
+ Intent.createChooser(
+ intent,
+ getString(R.string.share_save_file)
+ )
+ )
+ }
+ }
}
+
+ private val startForResultExportSave =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
+ File(getPublicFilesDir().canonicalPath, "temp").deleteRecursively()
+ }
+
+ val importSaves =
+ registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
+ if (result == null) {
+ return@registerForActivityResult
+ }
+
+ NativeLibrary.initializeEmptyUserDirectory()
+
+ val inputZip = contentResolver.openInputStream(result)
+ // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
+ var validZip = false
+ val savesFolder = File(savesFolderRoot)
+ val cacheSaveDir = File("${applicationContext.cacheDir.path}/saves/")
+ cacheSaveDir.mkdir()
+
+ if (inputZip == null) {
+ Toast.makeText(
+ applicationContext,
+ getString(R.string.fatal_error),
+ Toast.LENGTH_LONG
+ ).show()
+ return@registerForActivityResult
+ }
+
+ val filterTitleId =
+ FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) }
+
+ try {
+ CoroutineScope(Dispatchers.IO).launch {
+ FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
+ cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
+ File(savesFolder, savePath).deleteRecursively()
+ File(cacheSaveDir, savePath).copyRecursively(
+ File(savesFolder, savePath),
+ true
+ )
+ validZip = true
+ }
+
+ withContext(Dispatchers.Main) {
+ if (!validZip) {
+ MessageDialogFragment.newInstance(
+ this@MainActivity,
+ titleId = R.string.save_file_invalid_zip_structure,
+ descriptionId = R.string.save_file_invalid_zip_structure_description
+ ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ return@withContext
+ }
+ Toast.makeText(
+ applicationContext,
+ getString(R.string.save_file_imported_success),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+
+ cacheSaveDir.deleteRecursively()
+ }
+ } catch (e: Exception) {
+ Toast.makeText(
+ applicationContext,
+ getString(R.string.fatal_error),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
}
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/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
index cf226ad94..eafcf9e42 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
@@ -7,7 +7,6 @@ import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import java.io.File
import java.util.*
-import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
class DocumentsTree {
@@ -22,7 +21,7 @@ class DocumentsTree {
fun openContentUri(filepath: String, openMode: String?): Int {
val node = resolvePath(filepath) ?: return -1
- return FileUtil.openContentUri(YuzuApplication.appContext, node.uri.toString(), openMode)
+ return FileUtil.openContentUri(node.uri.toString(), openMode)
}
fun getFileSize(filepath: String): Long {
@@ -30,7 +29,7 @@ class DocumentsTree {
return if (node == null || node.isDirectory) {
0
} else {
- FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
+ FileUtil.getFileSize(node.uri.toString())
}
}
@@ -67,7 +66,7 @@ class DocumentsTree {
* @param parent parent node of this level
*/
private fun structTree(parent: DocumentsNode) {
- val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!)
+ val documents = FileUtil.listFiles(parent.uri!!)
for (document in documents) {
val node = DocumentsNode(document)
node.parent = parent
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index 142af5f26..5ee74a52c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -3,14 +3,13 @@
package org.yuzu.yuzu_emu.utils
-import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
+import kotlinx.coroutines.flow.StateFlow
import java.io.BufferedInputStream
import java.io.File
-import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.URLDecoder
@@ -18,6 +17,11 @@ import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
+import org.yuzu.yuzu_emu.model.TaskState
+import java.io.BufferedOutputStream
+import java.lang.NullPointerException
+import java.nio.charset.StandardCharsets
+import java.util.zip.ZipOutputStream
object FileUtil {
const val PATH_TREE = "tree"
@@ -25,6 +29,8 @@ object FileUtil {
const val APPLICATION_OCTET_STREAM = "application/octet-stream"
const val TEXT_PLAIN = "text/plain"
+ private val context get() = YuzuApplication.appContext
+
/**
* Create a file from directory with filename.
* @param context Application context
@@ -32,11 +38,11 @@ object FileUtil {
* @param filename file display name.
* @return boolean
*/
- fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? {
+ fun createFile(directory: String?, filename: String): DocumentFile? {
var decodedFilename = filename
try {
val directoryUri = Uri.parse(directory)
- val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
+ val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
var mimeType = APPLICATION_OCTET_STREAM
if (decodedFilename.endsWith(".txt")) {
@@ -52,16 +58,15 @@ object FileUtil {
/**
* Create a directory from directory with filename.
- * @param context Application context
* @param directory parent path for directory.
* @param directoryName directory display name.
* @return boolean
*/
- fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? {
+ fun createDir(directory: String?, directoryName: String?): DocumentFile? {
var decodedDirectoryName = directoryName
try {
val directoryUri = Uri.parse(directory)
- val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
+ val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
val isExist = parent.findFile(decodedDirectoryName)
return isExist ?: parent.createDirectory(decodedDirectoryName)
@@ -73,13 +78,12 @@ object FileUtil {
/**
* Open content uri and return file descriptor to JNI.
- * @param context Application context
* @param path Native content uri path
* @param openMode will be one of "r", "r", "rw", "wa", "rwa"
* @return file descriptor
*/
@JvmStatic
- fun openContentUri(context: Context, path: String, openMode: String?): Int {
+ fun openContentUri(path: String, openMode: String?): Int {
try {
val uri = Uri.parse(path)
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
@@ -99,11 +103,10 @@ object FileUtil {
/**
* Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
* This function will be faster than DoucmentFile.listFiles
- * @param context Application context
* @param uri Directory uri.
* @return CheapDocument lists.
*/
- fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> {
+ fun listFiles(uri: Uri): Array<MinimalDocumentFile> {
val resolver = context.contentResolver
val columns = arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -141,7 +144,7 @@ object FileUtil {
* @param path Native content uri path
* @return bool
*/
- fun exists(context: Context, path: String?): Boolean {
+ fun exists(path: String?): Boolean {
var c: Cursor? = null
try {
val mUri = Uri.parse(path)
@@ -161,7 +164,7 @@ object FileUtil {
* @param path content uri path
* @return bool
*/
- fun isDirectory(context: Context, path: String): Boolean {
+ fun isDirectory(path: String): Boolean {
val resolver = context.contentResolver
val columns = arrayOf(
DocumentsContract.Document.COLUMN_MIME_TYPE
@@ -206,10 +209,10 @@ object FileUtil {
return filename
}
- fun getFilesName(context: Context, path: String): Array<String> {
+ fun getFilesName(path: String): Array<String> {
val uri = Uri.parse(path)
val files: MutableList<String> = ArrayList()
- for (file in listFiles(context, uri)) {
+ for (file in listFiles(uri)) {
files.add(file.filename)
}
return files.toTypedArray()
@@ -221,7 +224,7 @@ object FileUtil {
* @return long file size
*/
@JvmStatic
- fun getFileSize(context: Context, path: String): Long {
+ fun getFileSize(path: String): Long {
val resolver = context.contentResolver
val columns = arrayOf(
DocumentsContract.Document.COLUMN_SIZE
@@ -241,71 +244,100 @@ object FileUtil {
return size
}
+ /**
+ * Creates an input stream with a given [Uri] and copies its data to the given path. This will
+ * overwrite any pre-existing files.
+ *
+ * @param sourceUri The [Uri] to copy data from
+ * @param destinationParentPath Destination directory
+ * @param destinationFilename Optionally renames the file once copied
+ */
fun copyUriToInternalStorage(
- context: Context,
- sourceUri: Uri?,
+ sourceUri: Uri,
destinationParentPath: String,
- destinationFilename: String
- ): Boolean {
- var input: InputStream? = null
- var output: FileOutputStream? = null
+ destinationFilename: String = ""
+ ): File? =
try {
- input = context.contentResolver.openInputStream(sourceUri!!)
- output = FileOutputStream("$destinationParentPath/$destinationFilename")
- val buffer = ByteArray(1024)
- var len: Int
- while (input!!.read(buffer).also { len = it } != -1) {
- output.write(buffer, 0, len)
- }
- output.flush()
- return true
- } catch (e: Exception) {
- Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
- } finally {
- if (input != null) {
- try {
- input.close()
- } catch (e: IOException) {
- Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
- }
+ val fileName =
+ if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename"
+ val inputStream = context.contentResolver.openInputStream(sourceUri)!!
+
+ val destinationFile = File("$destinationParentPath$fileName")
+ if (destinationFile.exists()) {
+ destinationFile.delete()
}
- if (output != null) {
- try {
- output.close()
- } catch (e: IOException) {
- Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
- }
+
+ destinationFile.outputStream().use { fos ->
+ inputStream.use { it.copyTo(fos) }
}
+ destinationFile
+ } catch (e: IOException) {
+ null
+ } catch (e: NullPointerException) {
+ null
}
- return false
- }
/**
* Extracts the given zip file into the given directory.
- * @exception IOException if the file was being created outside of the target directory
*/
@Throws(SecurityException::class)
- fun unzip(zipStream: InputStream, destDir: File): Boolean {
- ZipInputStream(BufferedInputStream(zipStream)).use { zis ->
+ fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) {
+ ZipInputStream(zipStream).use { zis ->
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
- val entryName = entry.name
- val entryFile = File(destDir, entryName)
- if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
- throw SecurityException("Entry is outside of the target dir: " + entryFile.name)
+ val newFile = File(destDir, entry.name)
+ val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
+
+ if (!newFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
+ throw SecurityException("Zip file attempted path traversal! ${entry.name}")
+ }
+
+ if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
+ throw IOException("Failed to create directory $destinationDirectory")
}
- if (entry.isDirectory) {
- entryFile.mkdirs()
- } else {
- entryFile.parentFile?.mkdirs()
- entryFile.createNewFile()
- entryFile.outputStream().use { fos -> zis.copyTo(fos) }
+
+ if (!entry.isDirectory) {
+ newFile.outputStream().use { fos -> zis.copyTo(fos) }
}
entry = zis.nextEntry
}
}
+ }
- return true
+ /**
+ * Creates a zip file from a directory within internal storage
+ * @param inputFile File representation of the item that will be zipped
+ * @param rootDir Directory containing the inputFile
+ * @param outputStream Stream where the zip file will be output
+ */
+ fun zipFromInternalStorage(
+ inputFile: File,
+ rootDir: String,
+ outputStream: BufferedOutputStream,
+ cancelled: StateFlow<Boolean>? = null
+ ): TaskState {
+ try {
+ ZipOutputStream(outputStream).use { zos ->
+ inputFile.walkTopDown().forEach { file ->
+ if (cancelled?.value == true) {
+ return TaskState.Cancelled
+ }
+
+ if (!file.isDirectory) {
+ val entryName =
+ file.absolutePath.removePrefix(rootDir).removePrefix("/")
+ val entry = ZipEntry(entryName)
+ zos.putNextEntry(entry)
+ if (file.isFile) {
+ file.inputStream().use { fis -> fis.copyTo(zos) }
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ return TaskState.Failed
+ }
+ return TaskState.Completed
}
fun isRootTreeUri(uri: Uri): Boolean {
@@ -329,4 +361,12 @@ object FileUtil {
return fileName.substring(fileName.lastIndexOf(".") + 1)
.lowercase()
}
+
+ @Throws(IOException::class)
+ fun getStringFromFile(file: File): String =
+ String(file.readBytes(), StandardCharsets.UTF_8)
+
+ @Throws(IOException::class)
+ fun getStringFromInputStream(stream: InputStream): String =
+ String(stream.readBytes(), StandardCharsets.UTF_8)
}
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..9001ca9ab 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(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(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/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
index 1d4695a2a..f6882ce6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -3,64 +3,33 @@
package org.yuzu.yuzu_emu.utils
-import android.content.Context
import android.net.Uri
+import android.os.Build
import java.io.BufferedInputStream
import java.io.File
-import java.io.FileInputStream
-import java.io.FileOutputStream
import java.io.IOException
-import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
+import org.yuzu.yuzu_emu.YuzuApplication
+import java.util.zip.ZipException
+import java.util.zip.ZipFile
object GpuDriverHelper {
private const val META_JSON_FILENAME = "meta.json"
- private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
private var fileRedirectionPath: String? = null
- private var driverInstallationPath: String? = null
+ var driverInstallationPath: String? = null
private var hookLibPath: String? = null
- @Throws(IOException::class)
- private fun unzip(zipFilePath: String, destDir: String) {
- val dir = File(destDir)
-
- // Create output directory if it doesn't exist
- if (!dir.exists()) dir.mkdirs()
-
- // Unpack the files.
- val inputStream = FileInputStream(zipFilePath)
- val zis = ZipInputStream(BufferedInputStream(inputStream))
- val buffer = ByteArray(1024)
- var ze = zis.nextEntry
- while (ze != null) {
- val newFile = File(destDir, ze.name)
- val canonicalPath = newFile.canonicalPath
- if (!canonicalPath.startsWith(destDir + ze.name)) {
- throw SecurityException("Zip file attempted path traversal! " + ze.name)
- }
-
- newFile.parentFile!!.mkdirs()
- val fos = FileOutputStream(newFile)
- var len: Int
- while (zis.read(buffer).also { len = it } > 0) {
- fos.write(buffer, 0, len)
- }
- fos.close()
- zis.closeEntry()
- ze = zis.nextEntry
- }
- zis.closeEntry()
- }
+ val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/"
- fun initializeDriverParameters(context: Context) {
+ fun initializeDriverParameters() {
try {
// Initialize the file redirection directory.
- fileRedirectionPath =
- context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
+ fileRedirectionPath = YuzuApplication.appContext
+ .getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
// Initialize the driver installation directory.
- driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/"
+ driverInstallationPath = YuzuApplication.appContext
+ .filesDir.canonicalPath + "/gpu_driver/"
} catch (e: IOException) {
throw RuntimeException(e)
}
@@ -69,68 +38,169 @@ object GpuDriverHelper {
initializeDirectories()
// Initialize hook libraries directory.
- hookLibPath = context.applicationInfo.nativeLibraryDir + "/"
+ hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
// Initialize GPU driver.
NativeLibrary.initializeGpuDriver(
hookLibPath,
driverInstallationPath,
- customDriverLibraryName,
+ customDriverData.libraryName,
fileRedirectionPath
)
}
- fun installDefaultDriver(context: Context) {
+ fun getDrivers(): MutableList<Pair<String, GpuDriverMetadata>> {
+ val driverZips = File(driverStoragePath).listFiles()
+ val drivers: MutableList<Pair<String, GpuDriverMetadata>> =
+ driverZips
+ ?.mapNotNull {
+ val metadata = getMetadataFromZip(it)
+ metadata.name?.let { _ -> Pair(it.path, metadata) }
+ }
+ ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
+ ?.distinct()
+ ?.toMutableList() ?: mutableListOf()
+
+ // TODO: Get system driver information
+ drivers.add(0, Pair("", GpuDriverMetadata()))
+ return drivers
+ }
+
+ fun installDefaultDriver() {
// Removing the installed driver will result in the backend using the default system driver.
- val driverInstallationDir = File(driverInstallationPath!!)
- deleteRecursive(driverInstallationDir)
- initializeDriverParameters(context)
+ File(driverInstallationPath!!).deleteRecursively()
+ initializeDriverParameters()
+ }
+
+ fun copyDriverToInternalStorage(driverUri: Uri): Boolean {
+ // Ensure we have directories.
+ initializeDirectories()
+
+ // Copy the zip file URI to user data
+ val copiedFile =
+ FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
+
+ // Validate driver
+ val metadata = getMetadataFromZip(copiedFile)
+ if (metadata.name == null) {
+ copiedFile.delete()
+ return false
+ }
+
+ if (metadata.minApi > Build.VERSION.SDK_INT) {
+ copiedFile.delete()
+ return false
+ }
+ return true
}
- fun installCustomDriver(context: Context, driverPathUri: Uri?) {
+ /**
+ * Copies driver zip into user data directory so that it can be exported along with
+ * other user data and also unzipped into the installation directory
+ */
+ fun installCustomDriver(driverUri: Uri): Boolean {
// Revert to system default in the event the specified driver is bad.
- installDefaultDriver(context)
+ installDefaultDriver()
// Ensure we have directories.
initializeDirectories()
- // Copy the zip file URI into our private storage.
- copyUriToInternalStorage(
- context,
- driverPathUri,
- driverInstallationPath!!,
- DRIVER_INTERNAL_FILENAME
- )
+ // Copy the zip file URI to user data
+ val copiedFile =
+ FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
+
+ // Validate driver
+ val metadata = getMetadataFromZip(copiedFile)
+ if (metadata.name == null) {
+ copiedFile.delete()
+ return false
+ }
+
+ if (metadata.minApi > Build.VERSION.SDK_INT) {
+ copiedFile.delete()
+ return false
+ }
// Unzip the driver.
try {
- unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
+ FileUtil.unzipToInternalStorage(
+ BufferedInputStream(copiedFile.inputStream()),
+ File(driverInstallationPath!!)
+ )
} catch (e: SecurityException) {
- return
+ return false
}
// Initialize the driver parameters.
- initializeDriverParameters(context)
+ initializeDriverParameters()
+
+ return true
}
- external fun supportsCustomDriverLoading(): Boolean
+ /**
+ * Unzips driver into installation directory
+ */
+ fun installCustomDriver(driver: File): Boolean {
+ // Revert to system default in the event the specified driver is bad.
+ installDefaultDriver()
- // Parse the custom driver metadata to retrieve the name.
- val customDriverName: String?
- get() {
- val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
- return metadata.name
+ // Ensure we have directories.
+ initializeDirectories()
+
+ // Validate driver
+ val metadata = getMetadataFromZip(driver)
+ if (metadata.name == null) {
+ driver.delete()
+ return false
}
- // Parse the custom driver metadata to retrieve the library name.
- private val customDriverLibraryName: String?
- get() {
- // Parse the custom driver metadata to retrieve the library name.
- val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
- return metadata.libraryName
+ // Unzip the driver to the private installation directory
+ try {
+ FileUtil.unzipToInternalStorage(
+ BufferedInputStream(driver.inputStream()),
+ File(driverInstallationPath!!)
+ )
+ } catch (e: SecurityException) {
+ return false
}
- private fun initializeDirectories() {
+ // Initialize the driver parameters.
+ initializeDriverParameters()
+
+ return true
+ }
+
+ /**
+ * Takes in a zip file and reads the meta.json file for presentation to the UI
+ *
+ * @param driver Zip containing driver and meta.json file
+ * @return A non-null [GpuDriverMetadata] instance that may have null members
+ */
+ fun getMetadataFromZip(driver: File): GpuDriverMetadata {
+ try {
+ ZipFile(driver).use { zf ->
+ val entries = zf.entries()
+ while (entries.hasMoreElements()) {
+ val entry = entries.nextElement()
+ if (!entry.isDirectory && entry.name.lowercase().contains(".json")) {
+ zf.getInputStream(entry).use {
+ return GpuDriverMetadata(it, entry.size)
+ }
+ }
+ }
+ }
+ } catch (_: ZipException) {
+ }
+ return GpuDriverMetadata()
+ }
+
+ external fun supportsCustomDriverLoading(): Boolean
+
+ // Parse the custom driver metadata to retrieve the name.
+ val customDriverData: GpuDriverMetadata
+ get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
+
+ fun initializeDirectories() {
// Ensure the file redirection directory exists.
val fileRedirectionDir = File(fileRedirectionPath!!)
if (!fileRedirectionDir.exists()) {
@@ -141,14 +211,10 @@ object GpuDriverHelper {
if (!driverInstallationDir.exists()) {
driverInstallationDir.mkdirs()
}
- }
-
- private fun deleteRecursive(fileOrDirectory: File) {
- if (fileOrDirectory.isDirectory) {
- for (child in fileOrDirectory.listFiles()!!) {
- deleteRecursive(child)
- }
+ // Ensure the driver storage directory exists
+ val driverStorageDirectory = File(driverStoragePath)
+ if (!driverStorageDirectory.exists()) {
+ driverStorageDirectory.mkdirs()
}
- fileOrDirectory.delete()
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
index a4e64070a..511a4171a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
@@ -4,29 +4,29 @@
package org.yuzu.yuzu_emu.utils
import java.io.IOException
-import java.nio.charset.StandardCharsets
-import java.nio.file.Files
-import java.nio.file.Paths
import org.json.JSONException
import org.json.JSONObject
+import java.io.File
+import java.io.InputStream
-class GpuDriverMetadata(metadataFilePath: String) {
- var name: String? = null
- var description: String? = null
- var author: String? = null
- var vendor: String? = null
- var driverVersion: String? = null
- var minApi = 0
- var libraryName: String? = null
+class GpuDriverMetadata {
+ /**
+ * Tries to get driver metadata information from a meta.json [File]
+ *
+ * @param metadataFile meta.json file provided with a GPU driver
+ */
+ constructor(metadataFile: File) {
+ if (metadataFile.length() > MAX_META_SIZE_BYTES) {
+ return
+ }
- init {
try {
- val json = JSONObject(getStringFromFile(metadataFilePath))
+ val json = JSONObject(FileUtil.getStringFromFile(metadataFile))
name = json.getString("name")
description = json.getString("description")
author = json.getString("author")
vendor = json.getString("vendor")
- driverVersion = json.getString("driverVersion")
+ version = json.getString("driverVersion")
minApi = json.getInt("minApi")
libraryName = json.getString("libraryName")
} catch (e: JSONException) {
@@ -36,12 +36,84 @@ class GpuDriverMetadata(metadataFilePath: String) {
}
}
- companion object {
- @Throws(IOException::class)
- private fun getStringFromFile(filePath: String): String {
- val path = Paths.get(filePath)
- val bytes = Files.readAllBytes(path)
- return String(bytes, StandardCharsets.UTF_8)
+ /**
+ * Tries to get driver metadata information from an input stream that's intended to be
+ * from a zip file
+ *
+ * @param metadataStream ZipEntry input stream
+ * @param size Size of the file in bytes
+ */
+ constructor(metadataStream: InputStream, size: Long) {
+ if (size > MAX_META_SIZE_BYTES) {
+ return
}
+
+ try {
+ val json = JSONObject(FileUtil.getStringFromInputStream(metadataStream))
+ name = json.getString("name")
+ description = json.getString("description")
+ author = json.getString("author")
+ vendor = json.getString("vendor")
+ version = json.getString("driverVersion")
+ minApi = json.getInt("minApi")
+ libraryName = json.getString("libraryName")
+ } catch (e: JSONException) {
+ // JSON is malformed, ignore and treat as unsupported metadata.
+ } catch (e: IOException) {
+ // File is inaccessible, ignore and treat as unsupported metadata.
+ }
+ }
+
+ /**
+ * Creates an empty metadata instance
+ */
+ constructor()
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is GpuDriverMetadata) {
+ return false
+ }
+
+ return other.name == name &&
+ other.description == description &&
+ other.author == author &&
+ other.vendor == vendor &&
+ other.version == version &&
+ other.minApi == minApi &&
+ other.libraryName == libraryName
+ }
+
+ override fun hashCode(): Int {
+ var result = name?.hashCode() ?: 0
+ result = 31 * result + (description?.hashCode() ?: 0)
+ result = 31 * result + (author?.hashCode() ?: 0)
+ result = 31 * result + (vendor?.hashCode() ?: 0)
+ result = 31 * result + (version?.hashCode() ?: 0)
+ result = 31 * result + minApi
+ result = 31 * result + (libraryName?.hashCode() ?: 0)
+ return result
+ }
+
+ override fun toString(): String =
+ """
+ Name - $name
+ Description - $description
+ Author - $author
+ Vendor - $vendor
+ Version - $version
+ Min API - $minApi
+ Library Name - $libraryName
+ """.trimMargin().trimIndent()
+
+ var name: String? = null
+ var description: String? = null
+ var author: String? = null
+ var vendor: String? = null
+ var version: String? = null
+ var minApi = 0
+ var libraryName: String? = null
+
+ companion object {
+ private const val MAX_META_SIZE_BYTES = 500000
}
}
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/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 5e1f10f99..81120ab0f 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -11,22 +11,25 @@
#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,7 +147,9 @@ 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) {
@@ -277,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);
@@ -298,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..598f4e8bf 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -13,6 +13,8 @@
#include <android/api-level.h>
#include <android/native_window_jni.h>
+#include <common/fs/fs.h>
+#include <core/file_sys/savedata_factory.h>
#include <core/loader/nro.h>
#include <jni.h>
@@ -30,6 +32,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"
@@ -101,7 +104,7 @@ public:
m_native_window = native_window;
}
- int InstallFileToNand(std::string filename) {
+ int InstallFileToNand(std::string filename, std::string file_extension) {
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
std::size_t block_size) {
if (src == nullptr || dest == nullptr) {
@@ -133,15 +136,11 @@ public:
m_system.GetFileSystemController().CreateFactories(*m_vfs);
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
- if (filename.ends_with("nsp")) {
+ if (file_extension == "nsp") {
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
if (nsp->IsExtractedType()) {
return InstallError;
}
- } else if (filename.ends_with("xci")) {
- jconst xci =
- std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
- nsp = xci->GetSecurePartitionNSP();
} else {
return ErrorFilenameExtension;
}
@@ -202,12 +201,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;
}
@@ -221,20 +218,53 @@ public:
return;
}
m_window->OnSurfaceChanged(m_native_window);
- 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 +284,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 +324,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 +372,8 @@ public:
m_system.InitializeDebugger();
}
+ OnEmulationStarted();
+
while (true) {
{
[[maybe_unused]] std::unique_lock lock(m_mutex);
@@ -377,7 +418,7 @@ public:
return false;
}
- return !Settings::values.use_docked_mode.GetValue();
+ return !Settings::IsDockedMode();
}
void SetDeviceType([[maybe_unused]] int index, int type) {
@@ -468,6 +509,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 +538,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;
@@ -550,8 +604,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject
}
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
- [[maybe_unused]] jstring j_file) {
- return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
+ jstring j_file,
+ jstring j_file_extension) {
+ return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file),
+ GetJString(env, j_file_extension));
}
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
@@ -613,18 +669,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 +824,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);
@@ -862,4 +878,24 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env
EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
}
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv* env,
+ jobject instance) {
+ const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
+ auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
+ Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
+
+ Service::Account::ProfileManager manager;
+ const auto user_id = manager.GetUser(static_cast<std::size_t>(0));
+ ASSERT(user_id);
+
+ const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
+ EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
+ FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0);
+
+ const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
+ if (!Common::FS::CreateParentDirs(full_path)) {
+ LOG_WARNING(Frontend, "Failed to create full path of the default user's save directory");
+ }
+}
+
} // extern "C"
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_build.xml b/src/android/app/src/main/res/drawable/ic_build.xml
new file mode 100644
index 000000000..91d52f1b8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_build.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="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_delete.xml b/src/android/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 000000000..d26a79711
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_delete.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="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
+</vector>
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/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml
index 14ae83b04..a187665f2 100644
--- a/src/android/app/src/main/res/layout/activity_settings.xml
+++ b/src/android/app/src/main/res/layout/activity_settings.xml
@@ -1,50 +1,34 @@
<?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"
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="1px"
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_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml
new file mode 100644
index 000000000..1dd9a6d7d
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_driver_option.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView 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"
+ style="?attr/materialCardViewOutlinedStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="16dp"
+ android:layout_marginVertical="12dp"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="center"
+ android:padding="16dp">
+
+ <RadioButton
+ android:id="@+id/radio_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:clickable="false"
+ android:checked="false" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_gravity="center_vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="@string/select_gpu_driver_default" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/version"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dp"
+ android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="@string/install_gpu_driver_description" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dp"
+ android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="@string/install_gpu_driver_description" />
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/button_delete"
+ style="@style/Widget.Material3.Button.IconButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/delete"
+ android:tooltipText="@string/delete"
+ app:icon="@drawable/ic_delete"
+ app:iconTint="?attr/colorControlNormal" />
+
+ </LinearLayout>
+
+</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/card_installable.xml b/src/android/app/src/main/res/layout/card_installable.xml
new file mode 100644
index 000000000..f5b0e3741
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_installable.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView 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"
+ style="?attr/materialCardViewOutlinedStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="16dp"
+ android:layout_marginVertical="12dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="16dp"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/user_data"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dp"
+ android:text="@string/user_data_description"
+ android:textAlignment="viewStart" />
+
+ </LinearLayout>
+
+ <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_gravity="center_vertical"
+ android:contentDescription="@string/export"
+ android:tooltipText="@string/export"
+ android:visibility="gone"
+ app:icon="@drawable/ic_export"
+ tools:visibility="visible" />
+
+ <Button
+ android:id="@+id/button_install"
+ style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="12dp"
+ android:contentDescription="@string/string_import"
+ android:tooltipText="@string/string_import"
+ android:visibility="gone"
+ app:icon="@drawable/ic_import"
+ tools:visibility="visible" />
+
+ </LinearLayout>
+
+</com.google.android.material.card.MaterialCardView>
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/fragment_driver_manager.xml b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
new file mode 100644
index 000000000..6cea2d164
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
@@ -0,0 +1,48 @@
+<?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"
+ android:id="@+id/coordinator_licenses"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <androidx.coordinatorlayout.widget.CoordinatorLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_drivers"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ app:liftOnScrollTargetViewId="@id/list_drivers">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_drivers"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:navigationIcon="@drawable/ic_back"
+ app:title="@string/gpu_driver_manager" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list_drivers"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+ </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+ <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+ android:id="@+id/button_install"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:text="@string/install"
+ app:icon="@drawable/ic_add"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
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..750ce094a 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,82 @@
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"
+ android:clickable="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 +117,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"
@@ -79,8 +156,9 @@
android:id="@+id/in_game_menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_gravity="start|bottom"
+ android:layout_gravity="start"
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_installables.xml b/src/android/app/src/main/res/layout/fragment_installables.xml
new file mode 100644
index 000000000..3a4df81a6
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_installables.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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_licenses"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_installables"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_installables"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:title="@string/manage_yuzu_data"
+ app:navigationIcon="@drawable/ic_back" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list_installables"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
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/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..82749359d 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -19,6 +19,12 @@
<action
android:id="@+id/action_homeSettingsFragment_to_earlyAccessFragment"
app:destination="@id/earlyAccessFragment" />
+ <action
+ android:id="@+id/action_homeSettingsFragment_to_installableFragment"
+ app:destination="@id/installableFragment" />
+ <action
+ android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
+ app:destination="@id/driverManagerFragment" />
</fragment>
<fragment
@@ -62,7 +68,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 +78,29 @@
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" />
+ <fragment
+ android:id="@+id/installableFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
+ android:label="InstallableFragment" />
+ <fragment
+ android:id="@+id/driverManagerFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
+ android:label="DriverManagerFragment" />
+
</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 0c1d91264..72a47fbdb 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -79,7 +79,6 @@
<string name="manage_save_data">Speicherdaten verwalten</string>
<string name="manage_save_data_description">Speicherdaten gefunden. Bitte wähle unten eine Option aus.</string>
<string name="import_export_saves_description">Speicherdaten importieren oder exportieren</string>
- <string name="import_export_saves_no_profile">Keine Speicherdaten gefunden. Bitte starte ein Spiel und versuche es erneut.</string>
<string name="save_file_imported_success">Erfolgreich importiert</string>
<string name="save_file_invalid_zip_structure">Ungültige Speicherverzeichnisstruktur</string>
<string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string>
@@ -169,9 +168,7 @@
<string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
<string name="select_gpu_driver_install">Installieren</string>
<string name="select_gpu_driver_default">Standard</string>
- <string name="select_gpu_driver_install_success">%s wurde installiert</string>
<string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
- <string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string>
<string name="system_gpu_driver">System GPU-Treiber</string>
<string name="installing_driver">Treiber wird installiert...</string>
@@ -209,7 +206,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>
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index 357f956d1..e5bdd5889 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -81,7 +81,6 @@
<string name="manage_save_data">Administrar datos de guardado</string>
<string name="manage_save_data_description">Guardar los datos encontrados. Por favor, seleccione una opción de abajo.</string>
<string name="import_export_saves_description">Importar o exportar archivos de guardado</string>
- <string name="import_export_saves_no_profile">No se han encontrado datos de guardado. Por favor, ejecute un juego y vuelva a intentarlo.</string>
<string name="save_file_imported_success">Importado correctamente</string>
<string name="save_file_invalid_zip_structure">Estructura del directorio de guardado no válido</string>
<string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Predeterminado</string>
- <string name="select_gpu_driver_install_success">Instalado %s</string>
<string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
- <string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string>
<string name="system_gpu_driver">Driver GPU del sistema</string>
<string name="installing_driver">Instalando driver...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index dfca1c830..1e02828aa 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -81,7 +81,6 @@
<string name="manage_save_data">Gérer les données de sauvegarde</string>
<string name="manage_save_data_description">Données de sauvegarde trouvées. Veuillez sélectionner une option ci-dessous.</string>
<string name="import_export_saves_description">Importer ou exporter des fichiers de sauvegarde</string>
- <string name="import_export_saves_no_profile">Aucune données de sauvegarde trouvées. Veuillez lancer un jeu et réessayer.</string>
<string name="save_file_imported_success">Importé avec succès</string>
<string name="save_file_invalid_zip_structure">Structure de répertoire de sauvegarde non valide</string>
<string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
<string name="select_gpu_driver_install">Installer</string>
<string name="select_gpu_driver_default">Défaut</string>
- <string name="select_gpu_driver_install_success">%s Installé</string>
<string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
- <string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string>
<string name="system_gpu_driver">Pilote du GPU du système</string>
<string name="installing_driver">Installation du pilote...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index 089d93ed6..09c9345b0 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -81,7 +81,6 @@
<string name="manage_save_data">Gestisci i salvataggi</string>
<string name="manage_save_data_description">Salvataggio non trovato. Seleziona un\'opzione di seguito.</string>
<string name="import_export_saves_description">Importa o esporta i salvataggi</string>
- <string name="import_export_saves_no_profile">Nessun salvataggio trovato. Avvia un gioco e riprova.</string>
<string name="save_file_imported_success">Importato con successo</string>
<string name="save_file_invalid_zip_structure">La struttura della cartella dei salvataggi è invalida</string>
<string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
<string name="select_gpu_driver_install">Installa</string>
<string name="select_gpu_driver_default">Predefinito</string>
- <string name="select_gpu_driver_install_success">Installato%s</string>
<string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
- <string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string>
<string name="system_gpu_driver">Driver GPU del sistema</string>
<string name="installing_driver">Installando i driver...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index 39b590bee..a0ea78bef 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -80,7 +80,6 @@
<string name="manage_save_data">セーブデータを管理</string>
<string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string>
<string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string>
- <string name="import_export_saves_no_profile">セーブデータがありません。ゲームを起動してから再度お試しください。</string>
<string name="save_file_imported_success">インポートが完了しました</string>
<string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string>
<string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string>
@@ -171,9 +170,7 @@
<string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
<string name="select_gpu_driver_install">インストール</string>
<string name="select_gpu_driver_default">デフォルト</string>
- <string name="select_gpu_driver_install_success">%s をインストールしました</string>
<string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
- <string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
<string name="system_gpu_driver">システムのGPUドライバ</string>
<string name="installing_driver">インストール中…</string>
@@ -211,7 +208,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>
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index cbcb2873f..214f95706 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -81,7 +81,6 @@
<string name="manage_save_data">저장 데이터 관리</string>
<string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string>
<string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string>
- <string name="import_export_saves_no_profile">저장 데이터를 찾을 수 없습니다. 게임을 실행한 후 다시 시도하세요.</string>
<string name="save_file_imported_success">가져오기 성공</string>
<string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</string>
<string name="save_file_invalid_zip_structure_description">첫 번째 하위 폴더 이름은 게임의 타이틀 ID여야 합니다.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
<string name="select_gpu_driver_install">설치</string>
<string name="select_gpu_driver_default">기본값</string>
- <string name="select_gpu_driver_install_success">설치된 %s</string>
<string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
- <string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
<string name="system_gpu_driver">시스템 GPU 드라이버</string>
<string name="installing_driver">드라이버 설치 중...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index e48a4be38..5443cef42 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -81,7 +81,6 @@
<string name="manage_save_data">Administrere lagringsdata</string>
<string name="manage_save_data_description">Lagringsdata funnet. Velg et alternativ nedenfor.</string>
<string name="import_export_saves_description">Importer eller eksporter lagringsfiler</string>
- <string name="import_export_saves_no_profile">Ingen lagringsdata funnet. Start et nytt spill og prøv på nytt.</string>
<string name="save_file_imported_success">Vellykket import</string>
<string name="save_file_invalid_zip_structure">Ugyldig struktur for lagringskatalog</string>
<string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string>
<string name="select_gpu_driver_install">Installer</string>
<string name="select_gpu_driver_default">Standard</string>
- <string name="select_gpu_driver_install_success">Installert %s</string>
<string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
- <string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string>
<string name="system_gpu_driver">Systemets GPU-driver</string>
<string name="installing_driver">Installerer driver...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index bc9c0f7f4..899e233d0 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -81,7 +81,6 @@
<string name="manage_save_data">Zarządzaj plikami zapisów gier</string>
<string name="manage_save_data_description">Znaleziono pliki zapisów gier. Wybierz opcję poniżej.</string>
<string name="import_export_saves_description">Importuj lub wyeksportuj pliki zapisów</string>
- <string name="import_export_saves_no_profile">Nie znaleziono plików zapisów. Uruchom grę i spróbuj ponownie.</string>
<string name="save_file_imported_success">Zaimportowano pomyślnie</string>
<string name="save_file_invalid_zip_structure">Niepoprawna struktura folderów</string>
<string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
<string name="select_gpu_driver_install">Zainstaluj</string>
<string name="select_gpu_driver_default">Domyślne</string>
- <string name="select_gpu_driver_install_success">Zainstalowano %s</string>
<string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
- <string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string>
<string name="system_gpu_driver">Systemowy sterownik GPU</string>
<string name="installing_driver">Instalowanie sterownika...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 75fe0edbf..caa095364 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
@@ -81,7 +81,6 @@
<string name="manage_save_data">Gerir dados guardados</string>
<string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string>
<string name="import_export_saves_description">Importa ou exporta dados guardados</string>
- <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string>
<string name="save_file_imported_success">Importado com sucesso</string>
<string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string>
<string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Padrão</string>
- <string name="select_gpu_driver_install_success">Instalado%s</string>
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
- <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
<string name="system_gpu_driver">Driver do GPU padrão</string>
<string name="installing_driver">A instalar o Driver...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index 96b040c66..0a1a47fbb 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
@@ -81,7 +81,6 @@
<string name="manage_save_data">Gerir dados guardados</string>
<string name="manage_save_data_description">Dados não encontrados. Por favor seleciona uma opção abaixo.</string>
<string name="import_export_saves_description">Importa ou exporta dados guardados</string>
- <string name="import_export_saves_no_profile">Dados não encontrados. Por favor lança o jogo e tenta novamente.</string>
<string name="save_file_imported_success">Importado com sucesso</string>
<string name="save_file_invalid_zip_structure">Estrutura de diretório de dados invalida</string>
<string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Padrão</string>
- <string name="select_gpu_driver_install_success">Instalado%s</string>
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
- <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
<string name="system_gpu_driver">Driver do GPU padrão</string>
<string name="installing_driver">A instalar o Driver...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index 8d954f59e..0bef035d6 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -81,7 +81,6 @@
<string name="manage_save_data">Управление данными сохранений</string>
<string name="manage_save_data_description">Найдено данные сохранений. Пожалуйста, выберите вариант ниже.</string>
<string name="import_export_saves_description">Импорт или экспорт файлов сохранения</string>
- <string name="import_export_saves_no_profile">Данные сохранений не найдены. Пожалуйста, запустите игру и повторите попытку.</string>
<string name="save_file_imported_success">Успешно импортировано</string>
<string name="save_file_invalid_zip_structure">Недопустимая структура папки сохранения</string>
<string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
<string name="select_gpu_driver_install">Установить</string>
<string name="select_gpu_driver_default">По умолчанию</string>
- <string name="select_gpu_driver_install_success">Установлено %s</string>
<string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
- <string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
<string name="system_gpu_driver">Системный драйвер ГП</string>
<string name="installing_driver">Установка драйвера...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index 6c028535b..5b789ee98 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -81,7 +81,6 @@
<string name="manage_save_data">Керування даними збережень</string>
<string name="manage_save_data_description">Знайдено дані збережень. Будь ласка, виберіть варіант нижче.</string>
<string name="import_export_saves_description">Імпорт або експорт файлів збереження</string>
- <string name="import_export_saves_no_profile">Дані збережень не знайдено. Будь ласка, запустіть гру та повторіть спробу.</string>
<string name="save_file_imported_success">Успішно імпортовано</string>
<string name="save_file_invalid_zip_structure">Неприпустима структура папки збереження</string>
<string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
<string name="select_gpu_driver_install">Встановити</string>
<string name="select_gpu_driver_default">За замовчуванням</string>
- <string name="select_gpu_driver_install_success">Встановлено %s</string>
<string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
- <string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
<string name="system_gpu_driver">Системний драйвер ГП</string>
<string name="installing_driver">Встановлення драйвера...</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-w600dp/integers.xml b/src/android/app/src/main/res/values-w600dp/integers.xml
new file mode 100644
index 000000000..9975db801
--- /dev/null
+++ b/src/android/app/src/main/res/values-w600dp/integers.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <integer name="grid_columns">2</integer>
+
+</resources>
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index e4ad2ed07..c0e885751 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
@@ -81,7 +81,6 @@
<string name="manage_save_data">管理存档数据</string>
<string name="manage_save_data_description">已找到存档数据,请选择下方的选项。</string>
<string name="import_export_saves_description">导入或导出存档</string>
- <string name="import_export_saves_no_profile">找不到存档数据,请启动游戏并重试。</string>
<string name="save_file_imported_success">已成功导入存档</string>
<string name="save_file_invalid_zip_structure">无效的存档目录</string>
<string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
<string name="select_gpu_driver_install">安装</string>
<string name="select_gpu_driver_default">系统默认</string>
- <string name="select_gpu_driver_install_success">已安装 %s</string>
<string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
- <string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
<string name="system_gpu_driver">系统 GPU 驱动程序</string>
<string name="installing_driver">正在安装驱动程序…</string>
@@ -213,7 +210,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>
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index 0d32f23df..4a21bf893 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
@@ -81,7 +81,6 @@
<string name="manage_save_data">管理儲存資料</string>
<string name="manage_save_data_description">已找到儲存資料,請選取下方的選項。</string>
<string name="import_export_saves_description">匯入或匯出儲存檔案</string>
- <string name="import_export_saves_no_profile">找不到儲存資料,請啟動遊戲並重試。</string>
<string name="save_file_imported_success">已成功匯入</string>
<string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string>
<string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string>
@@ -172,9 +171,7 @@
<string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
<string name="select_gpu_driver_install">安裝</string>
<string name="select_gpu_driver_default">預設</string>
- <string name="select_gpu_driver_install_success">已安裝 %s</string>
<string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
- <string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
<string name="system_gpu_driver">系統 GPU 驅動程式</string>
<string name="installing_driver">正在安裝驅動程式…</string>
@@ -213,7 +210,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>
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..ef855ea6f 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -12,6 +12,9 @@
<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="spacing_bottom_list_fab">72dp</dimen>
+ <dimen name="spacing_fab">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/integers.xml b/src/android/app/src/main/res/values/integers.xml
index 5e39bc7d9..dc527965c 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <integer name="game_title_lines">2</integer>
+ <integer name="grid_columns">1</integer>
<!-- Default SWITCH landscape layout -->
<integer name="SWITCH_BUTTON_A_X">760</integer>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index de1b2909b..9e4854221 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -43,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>
@@ -71,9 +72,11 @@
<string name="invalid_keys_error">Invalid encryption keys</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
+ <string name="gpu_driver_manager">GPU Driver Manager</string>
<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>
@@ -88,7 +91,6 @@
<string name="manage_save_data">Manage save data</string>
<string name="manage_save_data_description">Save data found. Please select an option below.</string>
<string name="import_export_saves_description">Import or export save files</string>
- <string name="import_export_saves_no_profile">No save data found. Please launch a game and retry.</string>
<string name="save_file_imported_success">Imported successfully</string>
<string name="save_file_invalid_zip_structure">Invalid save directory structure</string>
<string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
@@ -99,12 +101,13 @@
<string name="firmware_installing">Installing firmware</string>
<string name="firmware_installed_success">Firmware installed successfully</string>
<string name="firmware_installed_failure">Firmware installation failed</string>
- <string name="firmware_installed_failure_description">Verify that the ZIP contains valid firmware and try again.</string>
+ <string name="firmware_installed_failure_description">Make sure the firmware nca files are at the root of the zip and try again.</string>
<string name="share_log">Share debug logs</string>
<string name="share_log_description">Share yuzu\'s log file to debug issues</string>
<string name="share_log_missing">No log file found</string>
<string name="install_game_content">Install game content</string>
<string name="install_game_content_description">Install game updates or DLC</string>
+ <string name="installing_game_content">Installing content…</string>
<string name="install_game_content_failure">Error installing file(s) to NAND</string>
<string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
<string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
@@ -116,6 +119,10 @@
<string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<string name="custom_driver_not_supported">Custom drivers not supported</string>
<string name="custom_driver_not_supported_description">Custom driver loading isn\'t currently supported for this device.\nCheck this option again in the future to see if support was added!</string>
+ <string name="manage_yuzu_data">Manage yuzu data</string>
+ <string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string>
+ <string name="share_save_file">Share save file</string>
+ <string name="export_save_failed">Failed to export save</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia isn\'t real</string>
@@ -126,6 +133,16 @@
<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="user_data_import_failed_description">Make sure the user data folders are at the root of the zip folder and contain a config file at config/config.ini and try again.</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>
@@ -200,7 +217,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>
@@ -211,15 +230,22 @@
<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="export_failed">Export failed</string>
+ <string name="import_failed">Import failed</string>
+ <string name="cancelling">Cancelling</string>
+ <string name="install">Install</string>
+ <string name="delete">Delete</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
<string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
<string name="select_gpu_driver_install">Install</string>
<string name="select_gpu_driver_default">Default</string>
- <string name="select_gpu_driver_install_success">Installed %s</string>
<string name="select_gpu_driver_use_default">Using default GPU driver</string>
- <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string>
+ <string name="select_gpu_driver_error">Invalid driver selected</string>
+ <string name="driver_already_installed">Driver already installed</string>
<string name="system_gpu_driver">System GPU driver</string>
<string name="installing_driver">Installing driver…</string>
@@ -259,7 +285,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>
@@ -278,6 +303,7 @@
<string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
<string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
<string name="memory_formatted">%1$s %2$s</string>
+ <string name="no_game_present">No bootable game present!</string>
<!-- Region Names -->
<string name="region_japan">Japan</string>
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 80f370c16..51e559321 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -3,8 +3,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id("com.android.application") version "8.0.2" apply false
- id("com.android.library") version "8.0.2" apply false
+ id("com.android.application") version "8.1.2" apply false
+ id("com.android.library") version "8.1.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
}
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..ef301d8b4
--- /dev/null
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
@@ -0,0 +1,227 @@
+// 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);
+ }
+ PostDSPClearCommandBuffer();
+}
+
+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;
+}
+
+void AudioRenderer::PostDSPClearCommandBuffer() noexcept {
+ for (auto& buffer : command_buffers) {
+ buffer.buffer = 0;
+ buffer.size = 0;
+ buffer.reset_buffer = false;
+ }
+}
+
+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..57b89d9fe
--- /dev/null
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
@@ -0,0 +1,111 @@
+// 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();
+
+ void PostDSPClearCommandBuffer() noexcept;
+
+ /// 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 8c7892bcf..6c4e9fdc6 100644
--- a/src/audio_core/common/audio_renderer_parameter.h
+++ b/src/audio_core/common/audio_renderer_parameter.h
@@ -51,10 +51,10 @@ struct AudioRendererSystemContext {
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..a0cc228f7 100644
--- a/src/audio_core/renderer/command/mix/depop_prepare.h
+++ b/src/audio_core/renderer/command/mix/depop_prepare.h
@@ -8,14 +8,15 @@
#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.
+ * Adds the previously output last samples to the depop buffer.
*/
struct DepopPrepareCommand : ICommand {
/**
@@ -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 027bfa517..7c9a4e3ac 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -22,7 +22,7 @@ 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.
Settings::AudioEngine id;
@@ -30,8 +30,8 @@ struct SinkDetails {
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.
@@ -43,7 +43,7 @@ constexpr SinkDetails sink_details[] = {
return std::make_unique<CubebSink>(device_id);
},
&ListCubebSinkDevices,
- &GetCubebLatency,
+ &IsCubebSuitable,
},
#endif
#ifdef HAVE_SDL2
@@ -53,14 +53,17 @@ constexpr SinkDetails sink_details[] = {
return std::make_unique<SDLSink>(device_id);
},
&ListSDLSinkDevices,
- &GetSDLLatency,
+ &IsSDLSuitable,
},
#endif
- 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 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(Settings::AudioEngine sink_id) {
@@ -72,18 +75,22 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
auto iter = find_backend(sink_id);
if (sink_id == Settings::AudioEngine::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(Settings::AudioEngine::Cubeb);
- if (iter->latency() > TargetSampleCount * 3) {
- iter = find_backend(Settings::AudioEngine::Sdl2);
+ // 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;
+ }
}
-#else
- iter = std::begin(sink_details);
-#endif
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);
+ }
}
if (iter == std::end(sink_details)) {
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 6081352a2..d66d04fae 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -204,6 +204,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
// paused and we'll desync, so just play silence.
if (system.IsPaused() || system.IsShuttingDown()) {
if (system.IsShuttingDown()) {
+ {
+ std::scoped_lock lk{release_mutex};
+ queued_buffers.store(0);
+ }
release_cv.notify_one();
}
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index bf97d9ba2..8a1861051 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
@@ -151,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
@@ -184,6 +189,14 @@ if(ARCHITECTURE_x86_64)
target_link_libraries(common PRIVATE xbyak::xbyak)
endif()
+if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
+ target_sources(common
+ PRIVATE
+ arm64/native_clock.cpp
+ arm64/native_clock.h
+ )
+endif()
+
if (MSVC)
target_compile_definitions(common PRIVATE
# The standard library doesn't provide any replacement for codecvt yet
@@ -191,8 +204,6 @@ 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
diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp
new file mode 100644
index 000000000..88fdba527
--- /dev/null
+++ b/src/common/arm64/native_clock.cpp
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/arm64/native_clock.h"
+
+namespace Common::Arm64 {
+
+namespace {
+
+NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
+ return (static_cast<NativeClock::FactorType>(num) << 64) / den;
+}
+
+u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
+ return static_cast<u64>((m * factor) >> 64);
+}
+
+} // namespace
+
+NativeClock::NativeClock() {
+ const u64 host_cntfrq = GetHostCNTFRQ();
+ ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
+ us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
+ ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
+ guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
+ gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
+}
+
+std::chrono::nanoseconds NativeClock::GetTimeNS() const {
+ return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)};
+}
+
+std::chrono::microseconds NativeClock::GetTimeUS() const {
+ return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)};
+}
+
+std::chrono::milliseconds NativeClock::GetTimeMS() const {
+ return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)};
+}
+
+u64 NativeClock::GetCNTPCT() const {
+ return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor);
+}
+
+u64 NativeClock::GetGPUTick() const {
+ return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor);
+}
+
+u64 NativeClock::GetHostTicksNow() const {
+ u64 cntvct_el0 = 0;
+ asm volatile("dsb ish\n\t"
+ "mrs %[cntvct_el0], cntvct_el0\n\t"
+ "dsb ish\n\t"
+ : [cntvct_el0] "=r"(cntvct_el0));
+ return cntvct_el0;
+}
+
+u64 NativeClock::GetHostTicksElapsed() const {
+ return GetHostTicksNow();
+}
+
+bool NativeClock::IsNative() const {
+ return true;
+}
+
+u64 NativeClock::GetHostCNTFRQ() {
+ u64 cntfrq_el0 = 0;
+ asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
+ return cntfrq_el0;
+}
+
+} // namespace Common::Arm64
diff --git a/src/common/arm64/native_clock.h b/src/common/arm64/native_clock.h
new file mode 100644
index 000000000..a28b419f2
--- /dev/null
+++ b/src/common/arm64/native_clock.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/wall_clock.h"
+
+namespace Common::Arm64 {
+
+class NativeClock final : public WallClock {
+public:
+ explicit NativeClock();
+
+ std::chrono::nanoseconds GetTimeNS() const override;
+
+ std::chrono::microseconds GetTimeUS() const override;
+
+ std::chrono::milliseconds GetTimeMS() const override;
+
+ u64 GetCNTPCT() const override;
+
+ u64 GetGPUTick() const override;
+
+ u64 GetHostTicksNow() const override;
+
+ u64 GetHostTicksElapsed() const override;
+
+ bool IsNative() const override;
+
+ static u64 GetHostCNTFRQ();
+
+public:
+ using FactorType = unsigned __int128;
+
+ FactorType GetGuestCNTFRQFactor() const {
+ return guest_cntfrq_factor;
+ }
+
+private:
+ FactorType ns_cntfrq_factor;
+ FactorType us_cntfrq_factor;
+ FactorType ms_cntfrq_factor;
+ FactorType guest_cntfrq_factor;
+ FactorType gputick_cntfrq_factor;
+};
+
+} // namespace Common::Arm64
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/common_funcs.h b/src/common/common_funcs.h
index 0dad9338a..47d028d48 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -39,8 +39,12 @@
#define Crash() exit(1)
#endif
+#define LTO_NOINLINE __attribute__((noinline))
+
#else // _MSC_VER
+#define LTO_NOINLINE
+
// Locale Cross-Compatibility
#define locale_t _locale_t
diff --git a/src/common/elf.h b/src/common/elf.h
index 14a5e9597..0b728dc54 100644
--- a/src/common/elf.h
+++ b/src/common/elf.h
@@ -211,6 +211,11 @@ struct Elf64_Rela {
Elf64_Sxword r_addend; /* Addend */
};
+/* RELR relocation table entry */
+
+using Elf32_Relr = Elf32_Word;
+using Elf64_Relr = Elf64_Xword;
+
/* How to extract and insert information held in the r_info field. */
static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
@@ -328,6 +333,9 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
+constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */
+constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */
+constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */
} // namespace ELF
} // namespace Common
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/fs_paths.h b/src/common/fs/fs_paths.h
index 61bac9eba..441c8af97 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -18,10 +18,12 @@
#define LOAD_DIR "load"
#define LOG_DIR "log"
#define NAND_DIR "nand"
+#define PLAY_TIME_DIR "play_time"
#define SCREENSHOTS_DIR "screenshots"
#define SDMC_DIR "sdmc"
#define SHADER_DIR "shader"
#define TAS_DIR "tas"
+#define ICONS_DIR "icons"
// yuzu-specific files
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index d71cfacc6..0abd81a45 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;
@@ -122,10 +124,12 @@ public:
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
+ GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR);
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
+ GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
}
private:
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index ba28964d0..63801c924 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -20,10 +20,12 @@ enum class YuzuPath {
LoadDir, // Where cheat/mod files are stored.
LogDir, // Where log files are stored.
NANDDir, // Where the emulated NAND is stored.
+ PlayTimeDir, // Where play time data is stored.
ScreenshotsDir, // Where yuzu screenshots are stored.
SDMCDir, // Where the emulated SDMC is stored.
ShaderDir, // Where shaders are stored.
TASDir, // Where TAS scripts are stored.
+ IconsDir, // Where Icons for Windows shortcuts are stored.
};
/**
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/polyfill_thread.h b/src/common/polyfill_thread.h
index b5ef055db..12e59a893 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -15,12 +15,13 @@
#include <condition_variable>
#include <stop_token>
#include <thread>
+#include <utility>
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::forward<Pred>(pred));
}
template <typename Rep, typename Period>
@@ -109,7 +110,7 @@ public:
// Insert the callback.
stop_state_callback ret = ++m_next_callback;
- m_callbacks.emplace(ret, move(f));
+ m_callbacks.emplace(ret, std::move(f));
return ret;
}
@@ -162,7 +163,7 @@ private:
friend class stop_source;
template <typename Callback>
friend class stop_callback;
- stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {}
+ stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
@@ -198,7 +199,7 @@ public:
private:
friend class jthread;
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
- : m_stop_state(move(stop_state)) {}
+ : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
@@ -218,16 +219,16 @@ public:
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) {
if (m_stop_state) {
- m_callback = m_stop_state->insert_callback(move(cb));
+ m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
- : m_stop_state(move(st.m_stop_state)) {
+ : m_stop_state(std::move(st.m_stop_state)) {
if (m_stop_state) {
- m_callback = m_stop_state->insert_callback(move(cb));
+ m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
~stop_callback() {
@@ -260,7 +261,7 @@ public:
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
- m_thread(make_thread(move(f), move(args)...)) {}
+ m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
~jthread() {
if (joinable()) {
@@ -317,9 +318,9 @@ private:
template <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
- return thread(move(f), get_stop_token(), move(args)...);
+ return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
} else {
- return thread(move(f), move(args)...);
+ return thread(std::forward<F>(f), std::forward<Args>(args)...);
}
}
@@ -332,13 +333,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 +358,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 16a58a750..98b43e49c 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <version>
+#include "common/settings_enums.h"
#if __cpp_lib_chrono >= 201907L
#include <chrono>
#include <exception>
@@ -44,6 +45,7 @@ SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true);
+SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true);
@@ -60,6 +62,10 @@ SWITCHABLE(u32, false);
SWITCHABLE(u8, false);
SWITCHABLE(u8, true);
+// Used in UISettings
+// TODO see if we can move this to uisettings.cpp
+SWITCHABLE(ConfirmStop, true);
+
#undef SETTING
#undef SWITCHABLE
#endif
@@ -129,13 +135,17 @@ void LogSettings() {
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
}
+void UpdateGPUAccuracy() {
+ values.current_gpu_accuracy = values.gpu_accuracy.GetValue();
+}
+
bool IsGPULevelExtreme() {
- return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme;
+ return values.current_gpu_accuracy == GpuAccuracy::Extreme;
}
bool IsGPULevelHigh() {
- return values.gpu_accuracy.GetValue() == GpuAccuracy::Extreme ||
- values.gpu_accuracy.GetValue() == GpuAccuracy::High;
+ return values.current_gpu_accuracy == GpuAccuracy::Extreme ||
+ values.current_gpu_accuracy == GpuAccuracy::High;
}
bool IsFastmemEnabled() {
@@ -145,6 +155,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;
@@ -154,6 +168,8 @@ float Volume() {
const char* TranslateCategory(Category category) {
switch (category) {
+ case Category::Android:
+ return "Android";
case Category::Audio:
return "Audio";
case Category::Core:
diff --git a/src/common/settings.h b/src/common/settings.h
index 4407c1e6d..236e33bee 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -67,6 +67,7 @@ SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true);
+SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true);
@@ -83,6 +84,10 @@ SWITCHABLE(u32, false);
SWITCHABLE(u8, false);
SWITCHABLE(u8, true);
+// Used in UISettings
+// TODO see if we can move this to uisettings.h
+SWITCHABLE(ConfirmStop, true);
+
#undef SETTING
#undef SWITCHABLE
#endif
@@ -307,6 +312,7 @@ struct Values {
Specialization::Default,
true,
true};
+ GpuAccuracy current_gpu_accuracy{GpuAccuracy::High};
SwitchableSetting<AnisotropyMode, true> max_anisotropy{
linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16,
"max_anisotropy", Category::RendererAdvanced};
@@ -348,6 +354,10 @@ struct Values {
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};
+ // TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
+ bool renderer_amdvlk_depth_bias_workaround{};
// System
SwitchableSetting<Language, true> language_index{linkage,
@@ -379,7 +389,13 @@ struct Values {
Setting<s32> current_user{linkage, 0, "current_user", Category::System};
- SwitchableSetting<bool> use_docked_mode{linkage, true, "use_docked_mode", 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;
@@ -514,11 +530,14 @@ struct Values {
extern Values values;
+void UpdateGPUAccuracy();
bool IsGPULevelExtreme();
bool IsGPULevelHigh();
bool IsFastmemEnabled();
+bool IsDockedMode();
+
float Volume();
std::string GetTimeZoneString(TimeZone time_zone);
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp
index 137b65d5f..5960b78aa 100644
--- a/src/common/settings_common.cpp
+++ b/src/common/settings_common.cpp
@@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
: 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++;
}
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 2efb329b0..1800ab10d 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -12,6 +12,7 @@
namespace Settings {
enum class Category : u32 {
+ Android,
Audio,
Core,
Cpu,
@@ -56,6 +57,7 @@ enum Specialization : u8 {
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
};
@@ -67,6 +69,7 @@ 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;
};
@@ -222,6 +225,16 @@ public:
*/
[[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
*/
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index e7cb59ea5..11429d7a8 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -133,6 +133,8 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
+ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
+
ENUM(FullscreenMode, Borderless, Exclusive);
ENUM(NvdecEmulation, Off, Cpu, Gpu);
@@ -146,6 +148,8 @@ 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();
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index e10843c73..3175ab07d 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -10,6 +10,7 @@
#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"
@@ -115,8 +116,12 @@ protected:
} 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(static_cast<u64>(value_));
+ return std::to_string(value_);
}
}
@@ -180,13 +185,17 @@ public:
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 if constexpr (std::is_same_v<Type, AudioEngine>) {
- this->SetValue(ToEnum<Type>(input));
+ this->SetValue(ToEnum<AudioEngine>(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());
}
}
@@ -215,11 +224,27 @@ public:
}
}
+ [[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 {
- return this->ToString(minimum);
+ 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 {
- return this->ToString(maximum);
+ 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 {
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index feab1653d..4c7aba3f5 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -135,6 +135,11 @@ std::u16string UTF8ToUTF16(std::string_view input) {
return convert.from_bytes(input.data(), input.data() + input.size());
}
+std::u32string UTF8ToUTF32(std::string_view input) {
+ std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
+ return convert.from_bytes(input.data(), input.data() + input.size());
+}
+
#ifdef _WIN32
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
const auto size =
diff --git a/src/common/string_util.h b/src/common/string_util.h
index c351f1a0c..9da1ca4e9 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -38,6 +38,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
+[[nodiscard]] std::u32string UTF8ToUTF32(std::string_view input);
#ifdef _WIN32
[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
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 71e15ab4c..caca9a123 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -10,6 +10,10 @@
#include "common/x64/rdtsc.h"
#endif
+#if defined(ARCHITECTURE_arm64) && defined(__linux__)
+#include "common/arm64/native_clock.h"
+#endif
+
namespace Common {
class StandardWallClock final : public WallClock {
@@ -53,7 +57,7 @@ private:
};
std::unique_ptr<WallClock> CreateOptimalClock() {
-#ifdef ARCHITECTURE_x86_64
+#if defined(ARCHITECTURE_x86_64)
const auto& caps = GetCPUCaps();
if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
@@ -64,6 +68,8 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
// - Is not more precise than 1 GHz (1ns resolution)
return std::make_unique<StandardWallClock>();
}
+#elif defined(ARCHITECTURE_arm64) && defined(__linux__)
+ return std::make_unique<Arm64::NativeClock>();
#else
return std::make_unique<StandardWallClock>();
#endif
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 012648d69..e4f499135 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -466,14 +466,18 @@ add_library(core STATIC
hle/service/caps/caps_a.h
hle/service/caps/caps_c.cpp
hle/service/caps/caps_c.h
- hle/service/caps/caps_u.cpp
- hle/service/caps/caps_u.h
+ hle/service/caps/caps_manager.cpp
+ hle/service/caps/caps_manager.h
+ hle/service/caps/caps_result.h
hle/service/caps/caps_sc.cpp
hle/service/caps/caps_sc.h
hle/service/caps/caps_ss.cpp
hle/service/caps/caps_ss.h
hle/service/caps/caps_su.cpp
hle/service/caps/caps_su.h
+ hle/service/caps/caps_types.h
+ hle/service/caps/caps_u.cpp
+ hle/service/caps/caps_u.h
hle/service/erpt/erpt.cpp
hle/service/erpt/erpt.h
hle/service/es/es.cpp
@@ -584,13 +588,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
@@ -617,8 +635,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
@@ -684,6 +702,8 @@ add_library(core STATIC
hle/service/nvnflinger/consumer_base.cpp
hle/service/nvnflinger/consumer_base.h
hle/service/nvnflinger/consumer_listener.h
+ hle/service/nvnflinger/fb_share_buffer_manager.cpp
+ hle/service/nvnflinger/fb_share_buffer_manager.h
hle/service/nvnflinger/graphic_buffer_producer.cpp
hle/service/nvnflinger/graphic_buffer_producer.h
hle/service/nvnflinger/hos_binder_driver_server.cpp
@@ -854,6 +874,8 @@ add_library(core STATIC
telemetry_session.h
tools/freezer.cpp
tools/freezer.h
+ tools/renderdoc.cpp
+ tools/renderdoc.h
)
if (MSVC)
@@ -869,6 +891,7 @@ else()
-Werror=conversion
-Wno-sign-conversion
+ -Wno-cast-function-type
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
)
@@ -877,7 +900,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/core.cpp b/src/core/core.cpp
index 2f67e60a9..0ab2e3b76 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -51,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"
@@ -273,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;
@@ -375,6 +381,10 @@ struct System::Impl {
room_member->SendGameInfo(game_info);
}
+ // Workarounds:
+ // Activate this in Super Smash Brothers Ultimate, it only affects AMD cards using AMDVLK
+ Settings::values.renderer_amdvlk_depth_bias_workaround = program_id == 0x1006A800016E000ULL;
+
status = SystemResultStatus::Success;
return status;
}
@@ -398,12 +408,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();
@@ -425,12 +437,16 @@ struct System::Impl {
debugger.reset();
kernel.Shutdown();
memory.Reset();
+ Network::RestartSocketOperations();
if (auto room_member = room_network.GetRoomMember().lock()) {
Network::GameInfo game_info{};
room_member->SendGameInfo(game_info);
}
+ // Workarounds
+ Settings::values.renderer_amdvlk_depth_bias_workaround = false;
+
LOG_DEBUG(Core, "Shutdown OK");
}
@@ -507,7 +523,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{};
@@ -516,6 +533,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;
@@ -559,6 +578,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)} {}
@@ -943,12 +964,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;
}
-bool System::GetExitLock() const {
- return impl->exit_lock;
+void System::SetExitRequested(bool requested) {
+ impl->exit_requested = requested;
+}
+
+bool System::GetExitRequested() const {
+ return impl->exit_requested;
}
void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) {
@@ -1009,6 +1038,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));
}
@@ -1025,6 +1058,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);
}
@@ -1041,6 +1078,10 @@ void System::ApplySettings() {
impl->RefreshTime();
if (IsPoweredOn()) {
+ if (Settings::values.custom_rtc_enabled) {
+ const s64 posix_time{Settings::values.custom_rtc.GetValue()};
+ GetTimeManager().UpdateLocalSystemClockTime(posix_time);
+ }
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/core_timing.cpp b/src/core/core_timing.cpp
index b98a0cb33..e671b270f 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -32,6 +32,7 @@ struct CoreTiming::Event {
std::uintptr_t user_data;
std::weak_ptr<EventType> type;
s64 reschedule_time;
+ heap_t::handle_type handle{};
// Sort by time, unless the times are the same, in which case sort by
// the order added to the queue
@@ -122,9 +123,9 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
- event_queue.emplace_back(
- Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
- std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ auto h{event_queue.emplace(
+ Event{next_time.count(), event_fifo_id++, user_data, event_type, 0})};
+ (*h).handle = h;
}
event.Set();
@@ -138,10 +139,9 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
- event_queue.emplace_back(
- Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
-
- std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, user_data, event_type,
+ resched_time.count()})};
+ (*h).handle = h;
}
event.Set();
@@ -151,15 +151,17 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data, bool wait) {
{
std::scoped_lock lk{basic_lock};
- const auto itr =
- std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
- return e.type.lock().get() == event_type.get() && e.user_data == user_data;
- });
-
- // Removing random items breaks the invariant so we have to re-establish it.
- if (itr != event_queue.end()) {
- event_queue.erase(itr, event_queue.end());
- std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+
+ std::vector<heap_t::handle_type> to_remove;
+ for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) {
+ const Event& e = *itr;
+ if (e.type.lock().get() == event_type.get() && e.user_data == user_data) {
+ to_remove.push_back(itr->handle);
+ }
+ }
+
+ for (auto h : to_remove) {
+ event_queue.erase(h);
}
}
@@ -200,35 +202,45 @@ std::optional<s64> CoreTiming::Advance() {
std::scoped_lock lock{advance_lock, basic_lock};
global_timer = GetGlobalTimeNs().count();
- while (!event_queue.empty() && event_queue.front().time <= global_timer) {
- Event evt = std::move(event_queue.front());
- std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
- event_queue.pop_back();
+ while (!event_queue.empty() && event_queue.top().time <= global_timer) {
+ const Event& evt = event_queue.top();
if (const auto event_type{evt.type.lock()}) {
- basic_lock.unlock();
+ if (evt.reschedule_time == 0) {
+ const auto evt_user_data = evt.user_data;
+ const auto evt_time = evt.time;
+
+ event_queue.pop();
+
+ basic_lock.unlock();
+
+ event_type->callback(
+ evt_user_data, evt_time,
+ std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
+
+ basic_lock.lock();
+ } else {
+ basic_lock.unlock();
- const auto new_schedule_time{event_type->callback(
- evt.user_data, evt.time,
- std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
+ const auto new_schedule_time{event_type->callback(
+ evt.user_data, evt.time,
+ std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
- basic_lock.lock();
+ basic_lock.lock();
- if (evt.reschedule_time != 0) {
const auto next_schedule_time{new_schedule_time.has_value()
? new_schedule_time.value().count()
: evt.reschedule_time};
- // If this event was scheduled into a pause, its time now is going to be way behind.
- // Re-set this event to continue from the end of the pause.
+ // If this event was scheduled into a pause, its time now is going to be way
+ // behind. Re-set this event to continue from the end of the pause.
auto next_time{evt.time + next_schedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + next_schedule_time;
}
- event_queue.emplace_back(
- Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
- std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.user_data,
+ evt.type, next_schedule_time, evt.handle});
}
}
@@ -236,7 +248,7 @@ std::optional<s64> CoreTiming::Advance() {
}
if (!event_queue.empty()) {
- return event_queue.front().time;
+ return event_queue.top().time;
} else {
return std::nullopt;
}
@@ -274,7 +286,8 @@ void CoreTiming::ThreadLoop() {
#endif
}
} else {
- // Queue is empty, wait until another event is scheduled and signals us to continue.
+ // Queue is empty, wait until another event is scheduled and signals us to
+ // continue.
wait_set = true;
event.Wait();
}
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index c20e906fb..26a8b93a7 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -11,7 +11,8 @@
#include <optional>
#include <string>
#include <thread>
-#include <vector>
+
+#include <boost/heap/fibonacci_heap.hpp>
#include "common/common_types.h"
#include "common/thread.h"
@@ -151,11 +152,10 @@ private:
s64 timer_resolution_ns;
#endif
- // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
- // We don't use std::priority_queue because we need to be able to serialize, unserialize and
- // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
- // accommodated by the standard adaptor class.
- std::vector<Event> event_queue;
+ using heap_t =
+ boost::heap::fibonacci_heap<CoreTiming::Event, boost::heap::compare<std::greater<>>>;
+
+ heap_t event_queue;
u64 event_fifo_id = 0;
std::shared_ptr<EventType> ev_lost;
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 e55831f27..82964f0a1 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
+#include <codecvt>
+#include <locale>
#include <numeric>
#include <optional>
#include <thread>
@@ -12,6 +14,7 @@
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/settings.h"
+#include "common/string_util.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/debugger/gdbstub.h"
@@ -68,10 +71,16 @@ static std::string EscapeGDB(std::string_view data) {
}
static std::string EscapeXML(std::string_view data) {
+ std::u32string converted = U"[Encoding error]";
+ try {
+ converted = Common::UTF8ToUTF32(data);
+ } catch (std::range_error&) {
+ }
+
std::string escaped;
escaped.reserve(data.size());
- for (char c : data) {
+ for (char32_t c : converted) {
switch (c) {
case '&':
escaped += "&amp;";
@@ -86,7 +95,11 @@ static std::string EscapeXML(std::string_view data) {
escaped += "&gt;";
break;
default:
- escaped += c;
+ if (c > 0x7f) {
+ escaped += fmt::format("&#{};", static_cast<u32>(c));
+ } else {
+ escaped += static_cast<char>(c);
+ }
break;
}
}
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 44e6852fe..7d2f0abb8 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -22,6 +22,10 @@
namespace FileSys {
+static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
+ return std::max<u8>(key_generation, 1) - 1;
+}
+
NCA::NCA(VirtualFile file_, const NCA* base_nca)
: file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
@@ -41,12 +45,17 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
return;
}
+ // 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;
+ }
+
RightsId rights_id{};
reader->GetRightsId(rights_id.data(), rights_id.size());
if (rights_id != RightsId{}) {
// External decryption key required; provide it here.
- const auto key_generation = std::max<s32>(reader->GetKeyGeneration(), 1) - 1;
-
u128 rights_id_u128;
std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id));
@@ -57,12 +66,12 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
return;
}
- if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) {
+ if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
return;
}
- auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, key_generation);
+ 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);
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/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 a4baddb15..8e475f25a 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -294,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) {
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index adcde7b7d..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(
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index f00479bd3..8e291ff67 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -5,6 +5,7 @@
#include <vector>
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/file_sys/program_metadata.h"
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
@@ -95,6 +96,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
return Loader::ResultStatus::Success;
}
+Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
+ const u64 original_program_id = aci_header.title_id;
+ SCOPE_EXIT({ aci_header.title_id = original_program_id; });
+
+ return this->Load(file);
+}
+
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
// Allow use of cores 0~3 and thread priorities 1~63.
constexpr u32 default_thread_info_capability = 0x30007F7;
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 2e8960b07..9f8e74b13 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -56,6 +56,7 @@ public:
static ProgramMetadata GetDefault();
Loader::ResultStatus Load(VirtualFile file);
+ Loader::ResultStatus Reload(VirtualFile file);
/// Load from parameters instead of NPDM file, used for KIP
void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index a28af3594..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"
@@ -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,
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/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index e1e89ce2d..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]);
}
}
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/file_sys/vfs.h b/src/core/file_sys/vfs.h
index a93e21f67..a7cd1cae3 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -175,7 +175,7 @@ public:
return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset);
}
- // Renames the file to name. Returns whether or not the operation was successsful.
+ // Renames the file to name. Returns whether or not the operation was successful.
virtual bool Rename(std::string_view name) = 0;
// Returns the full path of this file as a string, recursively
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/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 94bd656fe..2af3f06fc 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -542,6 +542,7 @@ void EmulatedController::UnloadInput() {
}
void EmulatedController::EnableConfiguration() {
+ std::scoped_lock lock{connect_mutex, npad_mutex};
is_configuring = true;
tmp_is_connected = is_connected;
tmp_npad_type = npad_type;
@@ -1556,7 +1557,7 @@ void EmulatedController::Connect(bool use_temporary_value) {
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{connect_mutex, mutex};
if (is_configuring) {
tmp_is_connected = true;
return;
@@ -1572,7 +1573,7 @@ void EmulatedController::Connect(bool use_temporary_value) {
void EmulatedController::Disconnect() {
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{connect_mutex, mutex};
if (is_configuring) {
tmp_is_connected = false;
return;
@@ -1586,7 +1587,7 @@ void EmulatedController::Disconnect() {
}
bool EmulatedController::IsConnected(bool get_temporary_value) const {
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{connect_mutex};
if (get_temporary_value && is_configuring) {
return tmp_is_connected;
}
@@ -1599,7 +1600,7 @@ NpadIdType EmulatedController::GetNpadIdType() const {
}
NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{npad_mutex};
if (get_temporary_value && is_configuring) {
return tmp_npad_type;
}
@@ -1609,7 +1610,7 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{mutex, npad_mutex};
if (is_configuring) {
if (tmp_npad_type == npad_type_) {
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 88d77db8d..d4500583e 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -603,6 +603,8 @@ private:
mutable std::mutex mutex;
mutable std::mutex callback_mutex;
+ mutable std::mutex npad_mutex;
+ mutable std::mutex connect_mutex;
std::unordered_map<int, ControllerUpdateCallback> callback_list;
int last_callback_key = 0;
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/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.cpp b/src/core/hle/kernel/k_hardware_timer.cpp
index 4dcd53821..8e2e40307 100644
--- a/src/core/hle/kernel/k_hardware_timer.cpp
+++ b/src/core/hle/kernel/k_hardware_timer.cpp
@@ -35,7 +35,9 @@ void KHardwareTimer::DoTask() {
}
// Disable the timer interrupt while we handle this.
- this->DisableInterrupt();
+ // Not necessary due to core timing already having popped this event to call it.
+ // this->DisableInterrupt();
+ m_wakeup_time = std::numeric_limits<s64>::max();
if (const s64 next_time = this->DoInterruptTaskImpl(GetTick());
0 < next_time && next_time <= m_wakeup_time) {
diff --git a/src/core/hle/kernel/k_memory_layout.cpp b/src/core/hle/kernel/k_memory_layout.cpp
index af40092c0..bec714668 100644
--- a/src/core/hle/kernel/k_memory_layout.cpp
+++ b/src/core/hle/kernel/k_memory_layout.cpp
@@ -61,7 +61,7 @@ bool KMemoryRegionTree::Insert(u64 address, size_t size, u32 type_id, u32 new_at
found->Reset(address, inserted_region_last, old_pair, new_attr, type_id);
this->insert(*found);
} else {
- // If we can't re-use, adjust the old region.
+ // If we can't reuse, adjust the old region.
found->Reset(old_address, address - 1, old_pair, old_attr, old_type);
this->insert(*found);
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 9bfc85b34..0b0cef984 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/literals.h"
#include "common/scope_exit.h"
+#include "common/settings.h"
#include "core/core.h"
#include "core/hle/kernel/k_address_space_info.h"
#include "core/hle/kernel/k_memory_block.h"
@@ -337,11 +338,14 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
}
void KPageTable::Finalize() {
+ auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
+ if (Settings::IsFastmemEnabled()) {
+ m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size);
+ }
+ };
+
// Finalize memory blocks.
- m_memory_block_manager.Finalize(m_memory_block_slab_manager,
- [&](KProcessAddress addr, u64 size) {
- m_memory->UnmapRegion(*m_page_table_impl, addr, size);
- });
+ m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(HostUnmapCallback));
// Release any insecure mapped memory.
if (m_mapped_insecure_memory) {
@@ -2945,6 +2949,23 @@ Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size)
KMemoryAttribute::Locked, nullptr));
}
+Result KPageTable::LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
+ KMemoryPermission perm) {
+ R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState::FlagCanTransfer,
+ KMemoryState::FlagCanTransfer, KMemoryPermission::All,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
+ KMemoryAttribute::None, perm, KMemoryAttribute::Locked));
+}
+
+Result KPageTable::UnlockForTransferMemory(KProcessAddress address, size_t size,
+ const KPageGroup& pg) {
+ R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanTransfer,
+ KMemoryState::FlagCanTransfer, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Locked, std::addressof(pg)));
+}
+
Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) {
R_RETURN(this->LockMemoryAndOpen(
out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
@@ -3384,6 +3405,11 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K
new_attr, KMemoryBlockDisableMergeAttribute::Locked,
KMemoryBlockDisableMergeAttribute::None);
+ // If we have an output page group, open.
+ if (out_pg) {
+ out_pg->Open();
+ }
+
R_SUCCEED();
}
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index b9e8c6042..7da675f27 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -104,6 +104,9 @@ public:
Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state);
Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state);
+ Result LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
+ KMemoryPermission perm);
+ Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup& pg);
Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size);
Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg);
Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index e573e2a57..4a099286b 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -38,7 +38,7 @@ namespace {
*/
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());
@@ -96,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();
@@ -351,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);
@@ -368,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_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp
index 13d34125c..0e2e11743 100644
--- a/src/core/hle/kernel/k_transfer_memory.cpp
+++ b/src/core/hle/kernel/k_transfer_memory.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/scope_exit.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_transfer_memory.h"
@@ -9,28 +10,50 @@
namespace Kernel {
KTransferMemory::KTransferMemory(KernelCore& kernel)
- : KAutoObjectWithSlabHeapAndContainer{kernel} {}
+ : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{kernel} {}
KTransferMemory::~KTransferMemory() = default;
-Result KTransferMemory::Initialize(KProcessAddress address, std::size_t size,
- Svc::MemoryPermission owner_perm) {
+Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size,
+ Svc::MemoryPermission own_perm) {
// Set members.
m_owner = GetCurrentProcessPointer(m_kernel);
- // TODO(bunnei): Lock for transfer memory
+ // Get the owner page table.
+ auto& page_table = m_owner->GetPageTable();
+
+ // Construct the page group, guarding to make sure our state is valid on exit.
+ m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager());
+ auto pg_guard = SCOPE_GUARD({ m_page_group.reset(); });
+
+ // Lock the memory.
+ R_TRY(page_table.LockForTransferMemory(std::addressof(*m_page_group), addr, size,
+ ConvertToKMemoryPermission(own_perm)));
// Set remaining tracking members.
m_owner->Open();
- m_owner_perm = owner_perm;
- m_address = address;
- m_size = size;
+ m_owner_perm = own_perm;
+ m_address = addr;
m_is_initialized = true;
+ m_is_mapped = false;
+ // We succeeded.
+ pg_guard.Cancel();
R_SUCCEED();
}
-void KTransferMemory::Finalize() {}
+void KTransferMemory::Finalize() {
+ // Unlock.
+ if (!m_is_mapped) {
+ const size_t size = m_page_group->GetNumPages() * PageSize;
+ ASSERT(R_SUCCEEDED(
+ m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, *m_page_group)));
+ }
+
+ // Close the page group.
+ m_page_group->Close();
+ m_page_group->Finalize();
+}
void KTransferMemory::PostDestroy(uintptr_t arg) {
KProcess* owner = reinterpret_cast<KProcess*>(arg);
@@ -38,4 +61,54 @@ void KTransferMemory::PostDestroy(uintptr_t arg) {
owner->Close();
}
+Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) {
+ // Validate the size.
+ R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+
+ // Validate the permission.
+ R_UNLESS(m_owner_perm == map_perm, ResultInvalidState);
+
+ // Lock ourselves.
+ KScopedLightLock lk(m_lock);
+
+ // Ensure we're not already mapped.
+ R_UNLESS(!m_is_mapped, ResultInvalidState);
+
+ // Map the memory.
+ const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
+ ? KMemoryState::Transfered
+ : KMemoryState::SharedTransfered;
+ R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup(
+ address, *m_page_group, state, KMemoryPermission::UserReadWrite));
+
+ // Mark ourselves as mapped.
+ m_is_mapped = true;
+
+ R_SUCCEED();
+}
+
+Result KTransferMemory::Unmap(KProcessAddress address, size_t size) {
+ // Validate the size.
+ R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+
+ // Lock ourselves.
+ KScopedLightLock lk(m_lock);
+
+ // Unmap the memory.
+ const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
+ ? KMemoryState::Transfered
+ : KMemoryState::SharedTransfered;
+ R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state));
+
+ // Mark ourselves as unmapped.
+ ASSERT(m_is_mapped);
+ m_is_mapped = false;
+
+ R_SUCCEED();
+}
+
+size_t KTransferMemory::GetSize() const {
+ return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_transfer_memory.h b/src/core/hle/kernel/k_transfer_memory.h
index 54f97ccb4..8a0b08761 100644
--- a/src/core/hle/kernel/k_transfer_memory.h
+++ b/src/core/hle/kernel/k_transfer_memory.h
@@ -3,6 +3,9 @@
#pragma once
+#include <optional>
+
+#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/result.h"
@@ -48,16 +51,19 @@ public:
return m_address;
}
- size_t GetSize() const {
- return m_is_initialized ? m_size : 0;
- }
+ size_t GetSize() const;
+
+ Result Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm);
+ Result Unmap(KProcessAddress address, size_t size);
private:
+ std::optional<KPageGroup> m_page_group{};
KProcess* m_owner{};
KProcessAddress m_address{};
+ KLightLock m_lock;
Svc::MemoryPermission m_owner_perm{};
- size_t m_size{};
bool m_is_initialized{};
+ bool m_is_mapped{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index a1134b7e2..cb025c3d6 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -373,7 +373,7 @@ struct KernelCore::Impl {
static inline thread_local u8 host_thread_id = UINT8_MAX;
/// Sets the host thread ID for the caller.
- u32 SetHostThreadId(std::size_t core_id) {
+ LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
// This should only be called during core init.
ASSERT(host_thread_id == UINT8_MAX);
@@ -384,13 +384,13 @@ struct KernelCore::Impl {
}
/// Gets the host thread ID for the caller
- u32 GetHostThreadId() const {
+ LTO_NOINLINE u32 GetHostThreadId() const {
return host_thread_id;
}
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
- KThread* GetHostDummyThread(KThread* existing_thread) {
- const auto initialize{[](KThread* thread) {
+ LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
+ const auto initialize{[](KThread* thread) LTO_NOINLINE {
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
return thread;
}};
@@ -424,11 +424,11 @@ struct KernelCore::Impl {
static inline thread_local bool is_phantom_mode_for_singlecore{false};
- bool IsPhantomModeForSingleCore() const {
+ LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
return is_phantom_mode_for_singlecore;
}
- void SetIsPhantomModeForSingleCore(bool value) {
+ LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
ASSERT(!is_multicore);
is_phantom_mode_for_singlecore = value;
}
@@ -439,14 +439,14 @@ struct KernelCore::Impl {
static inline thread_local KThread* current_thread{nullptr};
- KThread* GetCurrentEmuThread() {
+ LTO_NOINLINE KThread* GetCurrentEmuThread() {
if (!current_thread) {
current_thread = GetHostDummyThread(nullptr);
}
return current_thread;
}
- void SetCurrentEmuThread(KThread* thread) {
+ LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
current_thread = thread;
}
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_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
index 7d94e7f09..1f97121b3 100644
--- a/src/core/hle/kernel/svc/svc_transfer_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
@@ -71,15 +71,59 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64
}
Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size,
- MemoryPermission owner_perm) {
- UNIMPLEMENTED();
- R_THROW(ResultNotImplemented);
+ MemoryPermission map_perm) {
+ // Validate the address/size.
+ R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
+ R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
+ R_UNLESS(size > 0, ResultInvalidSize);
+ R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
+
+ // Validate the permission.
+ R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidState);
+
+ // Get the transfer memory.
+ KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
+ .GetHandleTable()
+ .GetObject<KTransferMemory>(trmem_handle);
+ R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
+
+ // Verify that the mapping is in range.
+ R_UNLESS(GetCurrentProcess(system.Kernel())
+ .GetPageTable()
+ .CanContain(address, size, KMemoryState::Transfered),
+ ResultInvalidMemoryRegion);
+
+ // Map the transfer memory.
+ R_TRY(trmem->Map(address, size, map_perm));
+
+ // We succeeded.
+ R_SUCCEED();
}
Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address,
uint64_t size) {
- UNIMPLEMENTED();
- R_THROW(ResultNotImplemented);
+ // Validate the address/size.
+ R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
+ R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
+ R_UNLESS(size > 0, ResultInvalidSize);
+ R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
+
+ // Get the transfer memory.
+ KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
+ .GetHandleTable()
+ .GetObject<KTransferMemory>(trmem_handle);
+ R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
+
+ // Verify that the mapping is in range.
+ R_UNLESS(GetCurrentProcess(system.Kernel())
+ .GetPageTable()
+ .CanContain(address, size, KMemoryState::Transfered),
+ ResultInvalidMemoryRegion);
+
+ // Unmap the transfer memory.
+ R_TRY(trmem->Unmap(address, size));
+
+ R_SUCCEED();
}
Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address,
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 92a1439eb..dd0b27f47 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -62,7 +62,7 @@ enum class ErrorModule : u32 {
XCD = 108,
TMP451 = 109,
NIFM = 110,
- Hwopus = 111,
+ HwOpus = 111,
LSM6DS3 = 112,
Bluetooth = 113,
VI = 114,
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index b971401e6..b7d14060c 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -49,7 +49,7 @@ public:
: ServiceFramework{system_, "IManagerForSystemService"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "CheckAvailability"},
+ {0, &IManagerForSystemService::CheckAvailability, "CheckAvailability"},
{1, nullptr, "GetAccountId"},
{2, nullptr, "EnsureIdTokenCacheAsync"},
{3, nullptr, "LoadIdTokenCache"},
@@ -78,6 +78,13 @@ public:
RegisterHandlers(functions);
}
+
+private:
+ void CheckAvailability(HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
};
// 3.0.0+
@@ -837,6 +844,29 @@ void Module::Interface::InitializeApplicationInfoV2(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void Module::Interface::BeginUserRegistration(HLERequestContext& ctx) {
+ const auto user_id = Common::UUID::MakeRandom();
+ profile_manager->CreateNewUser(user_id, "yuzu");
+
+ LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(user_id);
+}
+
+void Module::Interface::CompleteUserRegistration(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ Common::UUID user_id = rp.PopRaw<Common::UUID>();
+
+ LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
+
+ profile_manager->WriteUserSaveFile();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void Module::Interface::GetProfileEditor(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
Common::UUID user_id = rp.PopRaw<Common::UUID>();
@@ -880,6 +910,17 @@ void Module::Interface::StoreSaveDataThumbnailApplication(HLERequestContext& ctx
StoreSaveDataThumbnail(ctx, uuid, tid);
}
+void Module::Interface::GetBaasAccountManagerForSystemService(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto uuid = rp.PopRaw<Common::UUID>();
+
+ LOG_INFO(Service_ACC, "called, uuid=0x{}", uuid.RawString());
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IManagerForSystemService>(system, uuid);
+}
+
void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto uuid = rp.PopRaw<Common::UUID>();
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 6b4735c2f..0395229b4 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -33,10 +33,13 @@ public:
void TrySelectUserWithoutInteraction(HLERequestContext& ctx);
void IsUserAccountSwitchLocked(HLERequestContext& ctx);
void InitializeApplicationInfoV2(HLERequestContext& ctx);
+ void BeginUserRegistration(HLERequestContext& ctx);
+ void CompleteUserRegistration(HLERequestContext& ctx);
void GetProfileEditor(HLERequestContext& ctx);
void ListQualifiedUsers(HLERequestContext& ctx);
void ListOpenContextStoredUsers(HLERequestContext& ctx);
void StoreSaveDataThumbnailApplication(HLERequestContext& ctx);
+ void GetBaasAccountManagerForSystemService(HLERequestContext& ctx);
void StoreSaveDataThumbnailSystem(HLERequestContext& ctx);
private:
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index d9882ecd3..770d13ec5 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -23,7 +23,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
{99, nullptr, "DebugActivateOpenContextRetention"},
{100, nullptr, "GetUserRegistrationNotifier"},
{101, nullptr, "GetUserStateChangeNotifier"},
- {102, nullptr, "GetBaasAccountManagerForSystemService"},
+ {102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"},
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
{104, nullptr, "GetProfileUpdateNotifier"},
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
@@ -40,8 +40,8 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
{152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
{190, nullptr, "GetUserLastOpenedApplication"},
{191, nullptr, "ActivateOpenContextHolder"},
- {200, nullptr, "BeginUserRegistration"},
- {201, nullptr, "CompleteUserRegistration"},
+ {200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"},
+ {201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
{202, nullptr, "CancelUserRegistration"},
{203, nullptr, "DeleteUser"},
{204, nullptr, "SetUserPosition"},
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 993a5a57a..900e32200 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -96,9 +96,10 @@ public:
bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
const UserData& data_new);
+ void WriteUserSaveFile();
+
private:
void ParseUserSaveFile();
- void WriteUserSaveFile();
std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
bool RemoveProfileAtIndex(std::size_t index);
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 8d057b3a8..98765b81a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -6,7 +6,9 @@
#include <cinttypes>
#include <cstring>
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
+#include "core/core_timing.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
@@ -18,6 +20,8 @@
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
+#include "core/hle/service/am/applets/applet_cabinet.h"
+#include "core/hle/service/am/applets/applet_mii_edit_types.h"
#include "core/hle/service/am/applets/applet_profile_select.h"
#include "core/hle/service/am/applets/applet_web_browser.h"
#include "core/hle/service/am/applets/applets.h"
@@ -27,15 +31,17 @@
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/apm/apm_interface.h"
#include "core/hle/service/bcat/backend/backend.h"
-#include "core/hle/service/caps/caps.h"
+#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/ns.h"
+#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/pm/pm.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/vi/vi.h"
+#include "core/hle/service/vi/vi_results.h"
#include "core/memory.h"
namespace Service::AM {
@@ -45,7 +51,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3};
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
enum class LaunchParameterKind : u32 {
- ApplicationSpecific = 1,
+ UserChannel = 1,
AccountPreselectedUser = 2,
};
@@ -188,8 +194,8 @@ IDisplayController::IDisplayController(Core::System& system_)
{4, nullptr, "UpdateCallerAppletCaptureImage"},
{5, nullptr, "GetLastForegroundCaptureImageEx"},
{6, nullptr, "GetLastApplicationCaptureImageEx"},
- {7, nullptr, "GetCallerAppletCaptureImageEx"},
- {8, nullptr, "TakeScreenShotOfOwnLayer"},
+ {7, &IDisplayController::GetCallerAppletCaptureImageEx, "GetCallerAppletCaptureImageEx"},
+ {8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"},
{9, nullptr, "CopyBetweenCaptureBuffers"},
{10, nullptr, "AcquireLastApplicationCaptureBuffer"},
{11, nullptr, "ReleaseLastApplicationCaptureBuffer"},
@@ -204,10 +210,10 @@ IDisplayController::IDisplayController(Core::System& system_)
{21, nullptr, "ClearAppletTransitionBuffer"},
{22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"},
{23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
- {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"},
- {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"},
- {26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"},
- {27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"},
+ {24, &IDisplayController::AcquireLastForegroundCaptureSharedBuffer, "AcquireLastForegroundCaptureSharedBuffer"},
+ {25, &IDisplayController::ReleaseLastForegroundCaptureSharedBuffer, "ReleaseLastForegroundCaptureSharedBuffer"},
+ {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"},
+ {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"},
{28, nullptr, "TakeScreenShotOfOwnLayerEx"},
};
// clang-format on
@@ -217,6 +223,54 @@ IDisplayController::IDisplayController(Core::System& system_)
IDisplayController::~IDisplayController() = default;
+void IDisplayController::GetCallerAppletCaptureImageEx(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push(1u);
+ rb.Push(0);
+}
+
+void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IDisplayController::AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push(1U);
+ rb.Push(0);
+}
+
+void IDisplayController::ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push(1U);
+ rb.Push(0);
+}
+
+void IDisplayController::ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
IDebugFunctions::IDebugFunctions(Core::System& system_)
: ServiceFramework{system_, "IDebugFunctions"} {
// clang-format off
@@ -276,14 +330,14 @@ ISelfController::ISelfController(Core::System& system_, Nvnflinger::Nvnflinger&
{20, nullptr, "SetDesirableKeyboardLayout"},
{21, nullptr, "GetScreenShotProgramId"},
{40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
- {41, nullptr, "IsSystemBufferSharingEnabled"},
- {42, nullptr, "GetSystemSharedLayerHandle"},
- {43, nullptr, "GetSystemSharedBufferHandle"},
+ {41, &ISelfController::IsSystemBufferSharingEnabled, "IsSystemBufferSharingEnabled"},
+ {42, &ISelfController::GetSystemSharedLayerHandle, "GetSystemSharedLayerHandle"},
+ {43, &ISelfController::GetSystemSharedBufferHandle, "GetSystemSharedBufferHandle"},
{44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"},
{45, nullptr, "SetManagedDisplayLayerSeparationMode"},
{46, nullptr, "SetRecordingLayerCompositionEnabled"},
{50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
- {51, nullptr, "ApproveToDisplay"},
+ {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
{61, nullptr, "SetMediaPlaybackState"},
{62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
@@ -340,7 +394,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 +403,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) {
@@ -478,6 +536,50 @@ void ISelfController::CreateManagedDisplayLayer(HLERequestContext& ctx) {
rb.Push(*layer_id);
}
+void ISelfController::IsSystemBufferSharingEnabled(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(this->EnsureBufferSharingEnabled());
+}
+
+void ISelfController::GetSystemSharedLayerHandle(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(this->EnsureBufferSharingEnabled());
+ rb.Push<s64>(system_shared_buffer_id);
+ rb.Push<s64>(system_shared_layer_id);
+}
+
+void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(this->EnsureBufferSharingEnabled());
+ rb.Push<s64>(system_shared_buffer_id);
+}
+
+Result ISelfController::EnsureBufferSharingEnabled() {
+ if (buffer_sharing_enabled) {
+ return ResultSuccess;
+ }
+
+ if (system.GetAppletManager().GetCurrentAppletId() <= Applets::AppletId::Application) {
+ return VI::ResultOperationFailed;
+ }
+
+ const auto display_id = nvnflinger.OpenDisplay("Default");
+ const auto result = nvnflinger.GetSystemBufferManager().Initialize(
+ &system_shared_buffer_id, &system_shared_layer_id, *display_id);
+
+ if (result.IsSuccess()) {
+ buffer_sharing_enabled = true;
+ }
+
+ return result;
+}
+
void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -503,6 +605,13 @@ void ISelfController::SetHandlesRequestToDisplay(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
idle_time_detection_extension = rp.Pop<u32>();
@@ -671,9 +780,70 @@ void AppletMessageQueue::OperationModeChanged() {
on_operation_mode_changed->Signal();
}
+ILockAccessor::ILockAccessor(Core::System& system_)
+ : ServiceFramework{system_, "ILockAccessor"}, service_context{system_, "ILockAccessor"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1, &ILockAccessor::TryLock, "TryLock"},
+ {2, &ILockAccessor::Unlock, "Unlock"},
+ {3, &ILockAccessor::GetEvent, "GetEvent"},
+ {4,&ILockAccessor::IsLocked, "IsLocked"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
+}
+
+ILockAccessor::~ILockAccessor() = default;
+
+void ILockAccessor::TryLock(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto return_handle = rp.Pop<bool>();
+
+ LOG_WARNING(Service_AM, "(STUBBED) called, return_handle={}", return_handle);
+
+ // TODO: When return_handle is true this function should return the lock handle
+
+ is_locked = true;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_locked);
+}
+
+void ILockAccessor::Unlock(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ is_locked = false;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void ILockAccessor::GetEvent(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ lock_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(lock_event->GetReadableEvent());
+}
+
+void ILockAccessor::IsLocked(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_locked);
+}
+
ICommonStateGetter::ICommonStateGetter(Core::System& system_,
std::shared_ptr<AppletMessageQueue> msg_queue_)
- : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} {
+ : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)},
+ service_context{system_, "ICommonStateGetter"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -686,14 +856,14 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{7, nullptr, "GetCradleStatus"},
{8, &ICommonStateGetter::GetBootMode, "GetBootMode"},
{9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
- {10, nullptr, "RequestToAcquireSleepLock"},
+ {10, &ICommonStateGetter::RequestToAcquireSleepLock, "RequestToAcquireSleepLock"},
{11, nullptr, "ReleaseSleepLock"},
{12, nullptr, "ReleaseSleepLockTransiently"},
- {13, nullptr, "GetAcquiredSleepLockEvent"},
+ {13, &ICommonStateGetter::GetAcquiredSleepLockEvent, "GetAcquiredSleepLockEvent"},
{14, nullptr, "GetWakeupCount"},
{20, nullptr, "PushToGeneralChannel"},
{30, nullptr, "GetHomeButtonReaderLockAccessor"},
- {31, nullptr, "GetReaderLockAccessorEx"},
+ {31, &ICommonStateGetter::GetReaderLockAccessorEx, "GetReaderLockAccessorEx"},
{32, nullptr, "GetWriterLockAccessorEx"},
{40, nullptr, "GetCradleFwVersion"},
{50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
@@ -711,7 +881,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{65, nullptr, "GetApplicationIdByContentActionName"},
{66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
{67, nullptr, "CancelCpuBoostMode"},
- {68, nullptr, "GetBuiltInDisplayType"},
+ {68, &ICommonStateGetter::GetBuiltInDisplayType, "GetBuiltInDisplayType"},
{80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
{90, nullptr, "SetPerformanceConfigurationChangedNotification"},
{91, nullptr, "GetCurrentPerformanceConfiguration"},
@@ -719,7 +889,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{110, nullptr, "OpenMyGpuErrorHandler"},
{120, nullptr, "GetAppletLaunchedHistory"},
{200, nullptr, "GetOperationModeSystemInfo"},
- {300, nullptr, "GetSettingsPlatformRegion"},
+ {300, &ICommonStateGetter::GetSettingsPlatformRegion, "GetSettingsPlatformRegion"},
{400, nullptr, "ActivateMigrationService"},
{401, nullptr, "DeactivateMigrationService"},
{500, nullptr, "DisableSleepTillShutdown"},
@@ -731,6 +901,12 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
// clang-format on
RegisterHandlers(functions);
+
+ sleep_lock_event = service_context.CreateEvent("ICommonStateGetter::SleepLockEvent");
+
+ // Configure applets to be in foreground state
+ msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
+ msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
}
ICommonStateGetter::~ICommonStateGetter() = default;
@@ -776,6 +952,36 @@ void ICommonStateGetter::GetCurrentFocusState(HLERequestContext& ctx) {
rb.Push(static_cast<u8>(FocusState::InFocus));
}
+void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ // Sleep lock is acquired immediately.
+ sleep_lock_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void ICommonStateGetter::GetReaderLockAccessorEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto unknown = rp.Pop<u32>();
+
+ LOG_INFO(Service_AM, "called, unknown={}", unknown);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<ILockAccessor>(system);
+}
+
+void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(sleep_lock_event->GetReadableEvent());
+}
+
void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
@@ -833,7 +1039,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 {
@@ -852,6 +1058,14 @@ void ICommonStateGetter::SetCpuBoostMode(HLERequestContext& ctx) {
apm_sys->SetCpuBoostMode(ctx);
}
+void ICommonStateGetter::GetBuiltInDisplayType(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(0);
+}
+
void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto system_button{rp.PopEnum<SystemButtonType>()};
@@ -862,6 +1076,14 @@ void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext&
rb.Push(ResultSuccess);
}
+void ICommonStateGetter::GetSettingsPlatformRegion(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(SysPlatformRegion::Global);
+}
+
void ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(
HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -921,7 +1143,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};
@@ -1319,18 +1541,19 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) {
ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
: ServiceFramework{system_, "ILibraryAppletSelfAccessor"} {
+ // clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "PopInData"},
- {1, nullptr, "PushOutData"},
+ {0, &ILibraryAppletSelfAccessor::PopInData, "PopInData"},
+ {1, &ILibraryAppletSelfAccessor::PushOutData, "PushOutData"},
{2, nullptr, "PopInteractiveInData"},
{3, nullptr, "PushInteractiveOutData"},
{5, nullptr, "GetPopInDataEvent"},
{6, nullptr, "GetPopInteractiveInDataEvent"},
- {10, nullptr, "ExitProcessAndReturn"},
- {11, nullptr, "GetLibraryAppletInfo"},
+ {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"},
+ {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"},
{12, nullptr, "GetMainAppletIdentityInfo"},
{13, nullptr, "CanUseApplicationCore"},
- {14, nullptr, "GetCallerAppletIdentityInfo"},
+ {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"},
{15, nullptr, "GetMainAppletApplicationControlProperty"},
{16, nullptr, "GetMainAppletStorageId"},
{17, nullptr, "GetCallerAppletIdentityInfoStack"},
@@ -1350,16 +1573,225 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
{100, nullptr, "CreateGameMovieTrimmer"},
{101, nullptr, "ReserveResourceForMovieOperation"},
{102, nullptr, "UnreserveResourceForMovieOperation"},
- {110, nullptr, "GetMainAppletAvailableUsers"},
+ {110, &ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers, "GetMainAppletAvailableUsers"},
{120, nullptr, "GetLaunchStorageInfoForDebug"},
{130, nullptr, "GetGpuErrorDetectedSystemEvent"},
{140, nullptr, "SetApplicationMemoryReservation"},
{150, nullptr, "ShouldSetGpuTimeSliceManually"},
};
+ // clang-format on
RegisterHandlers(functions);
+
+ switch (system.GetAppletManager().GetCurrentAppletId()) {
+ case Applets::AppletId::Cabinet:
+ PushInShowCabinetData();
+ break;
+ case Applets::AppletId::MiiEdit:
+ PushInShowMiiEditData();
+ break;
+ case Applets::AppletId::PhotoViewer:
+ PushInShowAlbum();
+ break;
+ default:
+ break;
+ }
}
ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
+void ILibraryAppletSelfAccessor::PopInData(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ if (queue_data.empty()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultNoDataInChannel);
+ return;
+ }
+
+ auto data = queue_data.front();
+ queue_data.pop_front();
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IStorage>(system, std::move(data));
+}
+
+void ILibraryAppletSelfAccessor::PushOutData(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void ILibraryAppletSelfAccessor::ExitProcessAndReturn(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ system.Exit();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
+ struct LibraryAppletInfo {
+ Applets::AppletId applet_id;
+ Applets::LibraryAppletMode library_applet_mode;
+ };
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ const LibraryAppletInfo applet_info{
+ .applet_id = system.GetAppletManager().GetCurrentAppletId(),
+ .library_applet_mode = Applets::LibraryAppletMode::AllForeground,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(applet_info);
+}
+
+void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
+ struct AppletIdentityInfo {
+ Applets::AppletId applet_id;
+ INSERT_PADDING_BYTES(0x4);
+ u64 application_id;
+ };
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ const AppletIdentityInfo applet_info{
+ .applet_id = Applets::AppletId::QLaunch,
+ .application_id = 0x0100000000001000ull,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(applet_info);
+}
+
+void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {
+ const Service::Account::ProfileManager manager{};
+ bool is_empty{true};
+ s32 user_count{-1};
+
+ LOG_INFO(Service_AM, "called");
+
+ if (manager.GetUserCount() > 0) {
+ is_empty = false;
+ user_count = static_cast<s32>(manager.GetUserCount());
+ ctx.WriteBuffer(manager.GetAllUsers());
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_empty);
+ rb.Push(user_count);
+}
+
+void ILibraryAppletSelfAccessor::PushInShowAlbum() {
+ const Applets::CommonArguments arguments{
+ .arguments_version = Applets::CommonArgumentVersion::Version3,
+ .size = Applets::CommonArgumentSize::Version3,
+ .library_version = 1,
+ .theme_color = Applets::ThemeColor::BasicBlack,
+ .play_startup_sound = true,
+ .system_tick = system.CoreTiming().GetClockTicks(),
+ };
+
+ std::vector<u8> argument_data(sizeof(arguments));
+ std::vector<u8> settings_data{2};
+ std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
+ queue_data.emplace_back(std::move(argument_data));
+ queue_data.emplace_back(std::move(settings_data));
+}
+
+void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
+ const Applets::CommonArguments arguments{
+ .arguments_version = Applets::CommonArgumentVersion::Version3,
+ .size = Applets::CommonArgumentSize::Version3,
+ .library_version = static_cast<u32>(Applets::CabinetAppletVersion::Version1),
+ .theme_color = Applets::ThemeColor::BasicBlack,
+ .play_startup_sound = true,
+ .system_tick = system.CoreTiming().GetClockTicks(),
+ };
+
+ const Applets::StartParamForAmiiboSettings amiibo_settings{
+ .param_1 = 0,
+ .applet_mode = system.GetAppletManager().GetCabinetMode(),
+ .flags = Applets::CabinetFlags::None,
+ .amiibo_settings_1 = 0,
+ .device_handle = 0,
+ .tag_info{},
+ .register_info{},
+ .amiibo_settings_3{},
+ };
+
+ std::vector<u8> argument_data(sizeof(arguments));
+ std::vector<u8> settings_data(sizeof(amiibo_settings));
+ std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
+ std::memcpy(settings_data.data(), &amiibo_settings, sizeof(amiibo_settings));
+ queue_data.emplace_back(std::move(argument_data));
+ queue_data.emplace_back(std::move(settings_data));
+}
+
+void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
+ struct MiiEditV3 {
+ Applets::MiiEditAppletInputCommon common;
+ Applets::MiiEditAppletInputV3 input;
+ };
+ static_assert(sizeof(MiiEditV3) == 0x100, "MiiEditV3 has incorrect size.");
+
+ MiiEditV3 mii_arguments{
+ .common =
+ {
+ .version = Applets::MiiEditAppletVersion::Version3,
+ .applet_mode = Applets::MiiEditAppletMode::ShowMiiEdit,
+ },
+ .input{},
+ };
+
+ std::vector<u8> argument_data(sizeof(mii_arguments));
+ std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments));
+
+ queue_data.emplace_back(std::move(argument_data));
+}
+
+IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
+ : ServiceFramework{system_, "IAppletCommonFunctions"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "SetTerminateResult"},
+ {10, nullptr, "ReadThemeStorage"},
+ {11, nullptr, "WriteThemeStorage"},
+ {20, nullptr, "PushToAppletBoundChannel"},
+ {21, nullptr, "TryPopFromAppletBoundChannel"},
+ {40, nullptr, "GetDisplayLogicalResolution"},
+ {42, nullptr, "SetDisplayMagnification"},
+ {50, nullptr, "SetHomeButtonDoubleClickEnabled"},
+ {51, nullptr, "GetHomeButtonDoubleClickEnabled"},
+ {52, nullptr, "IsHomeButtonShortPressedBlocked"},
+ {60, nullptr, "IsVrModeCurtainRequired"},
+ {61, nullptr, "IsSleepRequiredByHighTemperature"},
+ {62, nullptr, "IsSleepRequiredByLowBattery"},
+ {70, &IAppletCommonFunctions::SetCpuBoostRequestPriority, "SetCpuBoostRequestPriority"},
+ {80, nullptr, "SetHandlingCaptureButtonShortPressedMessageEnabledForApplet"},
+ {81, nullptr, "SetHandlingCaptureButtonLongPressedMessageEnabledForApplet"},
+ {90, nullptr, "OpenNamedChannelAsParent"},
+ {91, nullptr, "OpenNamedChannelAsChild"},
+ {100, nullptr, "SetApplicationCoreUsageMode"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IAppletCommonFunctions::~IAppletCommonFunctions() = default;
+
+void IAppletCommonFunctions::SetCpuBoostRequestPriority(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
IApplicationFunctions::IApplicationFunctions(Core::System& system_)
: ServiceFramework{system_, "IApplicationFunctions"}, service_context{system,
@@ -1381,7 +1813,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"},
@@ -1513,27 +1945,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;
@@ -1545,7 +1976,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));
@@ -1553,12 +1983,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) {
@@ -1819,6 +2248,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");
@@ -1850,14 +2291,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);
@@ -1919,9 +2368,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) {
void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
auto message_queue = std::make_shared<AppletMessageQueue>(system);
- // Needed on game boot
- message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
-
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService(
@@ -2027,8 +2473,8 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
: ServiceFramework{system_, "IProcessWindingController"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "GetLaunchReason"},
- {11, nullptr, "OpenCallingLibraryApplet"},
+ {0, &IProcessWindingController::GetLaunchReason, "GetLaunchReason"},
+ {11, &IProcessWindingController::OpenCallingLibraryApplet, "OpenCallingLibraryApplet"},
{21, nullptr, "PushContext"},
{22, nullptr, "PopContext"},
{23, nullptr, "CancelWindingReservation"},
@@ -2042,4 +2488,47 @@ IProcessWindingController::IProcessWindingController(Core::System& system_)
}
IProcessWindingController::~IProcessWindingController() = default;
+
+void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ struct AppletProcessLaunchReason {
+ u8 flag;
+ INSERT_PADDING_BYTES(3);
+ };
+ static_assert(sizeof(AppletProcessLaunchReason) == 0x4,
+ "AppletProcessLaunchReason is an invalid size");
+
+ AppletProcessLaunchReason reason{
+ .flag = 0,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(reason);
+}
+
+void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) {
+ const auto applet_id = system.GetAppletManager().GetCurrentAppletId();
+ const auto applet_mode = Applets::LibraryAppletMode::AllForeground;
+
+ LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id,
+ applet_mode);
+
+ const auto& applet_manager{system.GetAppletManager()};
+ const auto applet = applet_manager.GetApplet(applet_id, applet_mode);
+
+ if (applet == nullptr) {
+ LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
+}
+
} // namespace Service::AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index d68998f04..64b3f3fe2 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -120,6 +120,14 @@ class IDisplayController final : public ServiceFramework<IDisplayController> {
public:
explicit IDisplayController(Core::System& system_);
~IDisplayController() override;
+
+private:
+ void GetCallerAppletCaptureImageEx(HLERequestContext& ctx);
+ void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
+ void AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
+ void ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
+ void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
+ void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
};
class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
@@ -147,9 +155,13 @@ private:
void SetRestartMessageEnabled(HLERequestContext& ctx);
void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx);
void SetAlbumImageOrientation(HLERequestContext& ctx);
+ void IsSystemBufferSharingEnabled(HLERequestContext& ctx);
+ void GetSystemSharedBufferHandle(HLERequestContext& ctx);
+ void GetSystemSharedLayerHandle(HLERequestContext& ctx);
void CreateManagedDisplayLayer(HLERequestContext& ctx);
void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
void SetHandlesRequestToDisplay(HLERequestContext& ctx);
+ void ApproveToDisplay(HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
void ReportUserIsActive(HLERequestContext& ctx);
@@ -161,6 +173,8 @@ private:
void SaveCurrentScreenshot(HLERequestContext& ctx);
void SetRecordVolumeMuted(HLERequestContext& ctx);
+ Result EnsureBufferSharingEnabled();
+
enum class ScreenshotPermission : u32 {
Inherit = 0,
Enable = 1,
@@ -176,10 +190,30 @@ private:
u32 idle_time_detection_extension = 0;
u64 num_fatal_sections_entered = 0;
+ u64 system_shared_buffer_id = 0;
+ u64 system_shared_layer_id = 0;
bool is_auto_sleep_disabled = false;
+ bool buffer_sharing_enabled = false;
ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
};
+class ILockAccessor final : public ServiceFramework<ILockAccessor> {
+public:
+ explicit ILockAccessor(Core::System& system_);
+ ~ILockAccessor() override;
+
+private:
+ void TryLock(HLERequestContext& ctx);
+ void Unlock(HLERequestContext& ctx);
+ void GetEvent(HLERequestContext& ctx);
+ void IsLocked(HLERequestContext& ctx);
+
+ bool is_locked{};
+
+ Kernel::KEvent* lock_event;
+ KernelHelpers::ServiceContext service_context;
+};
+
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
public:
explicit ICommonStateGetter(Core::System& system_,
@@ -212,9 +246,17 @@ private:
CaptureButtonLongPressing,
};
+ enum class SysPlatformRegion : s32 {
+ Global = 1,
+ Terra = 2,
+ };
+
void GetEventHandle(HLERequestContext& ctx);
void ReceiveMessage(HLERequestContext& ctx);
void GetCurrentFocusState(HLERequestContext& ctx);
+ void RequestToAcquireSleepLock(HLERequestContext& ctx);
+ void GetAcquiredSleepLockEvent(HLERequestContext& ctx);
+ void GetReaderLockAccessorEx(HLERequestContext& ctx);
void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx);
void GetOperationMode(HLERequestContext& ctx);
void GetPerformanceMode(HLERequestContext& ctx);
@@ -226,11 +268,15 @@ private:
void EndVrModeEx(HLERequestContext& ctx);
void GetDefaultDisplayResolution(HLERequestContext& ctx);
void SetCpuBoostMode(HLERequestContext& ctx);
+ void GetBuiltInDisplayType(HLERequestContext& ctx);
void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
+ void GetSettingsPlatformRegion(HLERequestContext& ctx);
void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
std::shared_ptr<AppletMessageQueue> msg_queue;
bool vr_mode_state{};
+ Kernel::KEvent* sleep_lock_event;
+ KernelHelpers::ServiceContext service_context;
};
class IStorageImpl {
@@ -294,6 +340,29 @@ class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletS
public:
explicit ILibraryAppletSelfAccessor(Core::System& system_);
~ILibraryAppletSelfAccessor() override;
+
+private:
+ void PopInData(HLERequestContext& ctx);
+ void PushOutData(HLERequestContext& ctx);
+ void GetLibraryAppletInfo(HLERequestContext& ctx);
+ void ExitProcessAndReturn(HLERequestContext& ctx);
+ void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
+ void GetMainAppletAvailableUsers(HLERequestContext& ctx);
+
+ void PushInShowAlbum();
+ void PushInShowCabinetData();
+ void PushInShowMiiEditData();
+
+ std::deque<std::vector<u8>> queue_data;
+};
+
+class IAppletCommonFunctions final : public ServiceFramework<IAppletCommonFunctions> {
+public:
+ explicit IAppletCommonFunctions(Core::System& system_);
+ ~IAppletCommonFunctions() override;
+
+private:
+ void SetCpuBoostRequestPriority(HLERequestContext& ctx);
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@@ -316,6 +385,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);
@@ -339,7 +409,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;
@@ -378,6 +447,10 @@ class IProcessWindingController final : public ServiceFramework<IProcessWindingC
public:
explicit IProcessWindingController(Core::System& system_);
~IProcessWindingController() override;
+
+private:
+ void GetLaunchReason(HLERequestContext& ctx);
+ void OpenCallingLibraryApplet(HLERequestContext& ctx);
};
void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system);
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index ee9d99a54..e30e6478a 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -27,9 +27,9 @@ public:
{10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"},
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
- {21, nullptr, "GetAppletCommonFunctions"},
- {22, nullptr, "GetHomeMenuFunctions"},
- {23, nullptr, "GetGlobalStateController"},
+ {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
+ {22, &ILibraryAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
+ {23, &ILibraryAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
@@ -86,28 +86,52 @@ private:
rb.PushIpcInterface<IProcessWindingController>(system);
}
- void GetDebugFunctions(HLERequestContext& ctx) {
+ void GetLibraryAppletCreator(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDebugFunctions>(system);
+ rb.PushIpcInterface<ILibraryAppletCreator>(system);
}
- void GetLibraryAppletCreator(HLERequestContext& ctx) {
+ void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<ILibraryAppletCreator>(system);
+ rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system);
}
- void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) {
+ void GetAppletCommonFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system);
+ rb.PushIpcInterface<IAppletCommonFunctions>(system);
+ }
+
+ void GetHomeMenuFunctions(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IHomeMenuFunctions>(system);
+ }
+
+ void GetGlobalStateController(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IGlobalStateController>(system);
+ }
+
+ void GetDebugFunctions(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IDebugFunctions>(system);
}
Nvnflinger::Nvnflinger& nvnflinger;
@@ -133,7 +157,7 @@ public:
{20, &ISystemAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
{21, &ISystemAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
{22, &ISystemAppletProxy::GetApplicationCreator, "GetApplicationCreator"},
- {23, nullptr, "GetAppletCommonFunctions"},
+ {23, &ISystemAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
{1000, &ISystemAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
@@ -182,14 +206,6 @@ private:
rb.PushIpcInterface<IDisplayController>(system);
}
- void GetDebugFunctions(HLERequestContext& ctx) {
- LOG_DEBUG(Service_AM, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDebugFunctions>(system);
- }
-
void GetLibraryAppletCreator(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
@@ -222,6 +238,22 @@ private:
rb.PushIpcInterface<IApplicationCreator>(system);
}
+ void GetAppletCommonFunctions(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IAppletCommonFunctions>(system);
+ }
+
+ void GetDebugFunctions(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IDebugFunctions>(system);
+ }
+
Nvnflinger::Nvnflinger& nvnflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
};
diff --git a/src/core/hle/service/am/applets/applet_cabinet.h b/src/core/hle/service/am/applets/applet_cabinet.h
index b56427021..f498796f7 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.h
+++ b/src/core/hle/service/am/applets/applet_cabinet.h
@@ -29,6 +29,15 @@ enum class CabinetAppletVersion : u32 {
Version1 = 0x1,
};
+enum class CabinetFlags : u8 {
+ None = 0,
+ DeviceHandle = 1 << 0,
+ TagInfo = 1 << 1,
+ RegisterInfo = 1 << 2,
+ All = DeviceHandle | TagInfo | RegisterInfo,
+};
+DECLARE_ENUM_FLAG_OPERATORS(CabinetFlags)
+
enum class CabinetResult : u8 {
Cancel = 0,
TagInfo = 1 << 1,
@@ -51,7 +60,7 @@ static_assert(sizeof(AmiiboSettingsStartParam) == 0x30,
struct StartParamForAmiiboSettings {
u8 param_1;
Service::NFP::CabinetMode applet_mode;
- u8 flags;
+ CabinetFlags flags;
u8 amiibo_settings_1;
u64 device_handle;
Service::NFP::TagInfo tag_info;
diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp
index b46ea840c..5d17c353f 100644
--- a/src/core/hle/service/am/applets/applet_error.cpp
+++ b/src/core/hle/service/am/applets/applet_error.cpp
@@ -138,6 +138,10 @@ void Error::Initialize() {
CopyArgumentData(data, args->application_error);
error_code = Result(args->application_error.error_code);
break;
+ case ErrorAppletMode::ShowErrorPctl:
+ CopyArgumentData(data, args->error_record);
+ error_code = Decode64BitError(args->error_record.error_code_64);
+ break;
case ErrorAppletMode::ShowErrorRecord:
CopyArgumentData(data, args->error_record);
error_code = Decode64BitError(args->error_record.error_code_64);
@@ -191,6 +195,7 @@ void Error::Execute() {
frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
break;
}
+ case ErrorAppletMode::ShowErrorPctl:
case ErrorAppletMode::ShowErrorRecord:
reporter.SaveErrorReport(title_id, error_code,
fmt::format("{:016X}", args->error_record.posix_time));
diff --git a/src/core/hle/service/am/applets/applet_general_backend.cpp b/src/core/hle/service/am/applets/applet_general_backend.cpp
index 8b352020e..c0032f652 100644
--- a/src/core/hle/service/am/applets/applet_general_backend.cpp
+++ b/src/core/hle/service/am/applets/applet_general_backend.cpp
@@ -223,9 +223,9 @@ void StubApplet::Initialize() {
const auto data = broker.PeekDataToAppletForDebug();
system.GetReporter().SaveUnimplementedAppletReport(
- static_cast<u32>(id), common_args.arguments_version, common_args.library_version,
- common_args.theme_color, common_args.play_startup_sound, common_args.system_tick,
- data.normal, data.interactive);
+ static_cast<u32>(id), static_cast<u32>(common_args.arguments_version),
+ common_args.library_version, static_cast<u32>(common_args.theme_color),
+ common_args.play_startup_sound, common_args.system_tick, data.normal, data.interactive);
LogCurrentStorage(broker, "Initialize");
}
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..50adc7c02 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, 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, edit_char_info);
break;
}
default:
@@ -110,6 +145,8 @@ void MiiEdit::MiiEditOutput(MiiEditResult result, s32 index) {
.index{index},
};
+ LOG_INFO(Input, "called, result={}, index={}", result, index);
+
std::vector<u8> out_data(sizeof(MiiEditAppletOutput));
std::memcpy(out_data.data(), &applet_output, sizeof(MiiEditAppletOutput));
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/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 10afbc2da..89d5434af 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -199,6 +199,14 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
return frontend;
}
+NFP::CabinetMode AppletManager::GetCabinetMode() const {
+ return cabinet_mode;
+}
+
+AppletId AppletManager::GetCurrentAppletId() const {
+ return current_applet_id;
+}
+
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
if (set.cabinet != nullptr) {
frontend.cabinet = std::move(set.cabinet);
@@ -237,6 +245,14 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
}
}
+void AppletManager::SetCabinetMode(NFP::CabinetMode mode) {
+ cabinet_mode = mode;
+}
+
+void AppletManager::SetCurrentAppletId(AppletId applet_id) {
+ current_applet_id = applet_id;
+}
+
void AppletManager::SetDefaultAppletFrontendSet() {
ClearAll();
SetDefaultAppletsIfMissing();
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index 12f374199..f02bbc450 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -34,6 +34,10 @@ class KEvent;
class KReadableEvent;
} // namespace Kernel
+namespace Service::NFP {
+enum class CabinetMode : u8;
+} // namespace Service::NFP
+
namespace Service::AM {
class IStorage;
@@ -41,6 +45,8 @@ class IStorage;
namespace Applets {
enum class AppletId : u32 {
+ None = 0x00,
+ Application = 0x01,
OverlayDisplay = 0x02,
QLaunch = 0x03,
Starter = 0x04,
@@ -71,6 +77,32 @@ enum class LibraryAppletMode : u32 {
AllForegroundInitiallyHidden = 4,
};
+enum class CommonArgumentVersion : u32 {
+ Version0,
+ Version1,
+ Version2,
+ Version3,
+};
+
+enum class CommonArgumentSize : u32 {
+ Version3 = 0x20,
+};
+
+enum class ThemeColor : u32 {
+ BasicWhite = 0,
+ BasicBlack = 3,
+};
+
+struct CommonArguments {
+ CommonArgumentVersion arguments_version;
+ CommonArgumentSize size;
+ u32 library_version;
+ ThemeColor theme_color;
+ bool play_startup_sound;
+ u64_le system_tick;
+};
+static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
+
class AppletDataBroker final {
public:
explicit AppletDataBroker(Core::System& system_, LibraryAppletMode applet_mode_);
@@ -161,16 +193,6 @@ public:
}
protected:
- struct CommonArguments {
- u32_le arguments_version;
- u32_le size;
- u32_le library_version;
- u32_le theme_color;
- bool play_startup_sound;
- u64_le system_tick;
- };
- static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
-
CommonArguments common_args{};
AppletDataBroker broker;
LibraryAppletMode applet_mode;
@@ -219,8 +241,12 @@ public:
~AppletManager();
const AppletFrontendSet& GetAppletFrontendSet() const;
+ NFP::CabinetMode GetCabinetMode() const;
+ AppletId GetCurrentAppletId() const;
void SetAppletFrontendSet(AppletFrontendSet set);
+ void SetCabinetMode(NFP::CabinetMode mode);
+ void SetCurrentAppletId(AppletId applet_id);
void SetDefaultAppletFrontendSet();
void SetDefaultAppletsIfMissing();
void ClearAll();
@@ -228,6 +254,9 @@ public:
std::shared_ptr<Applet> GetApplet(AppletId id, LibraryAppletMode mode) const;
private:
+ AppletId current_applet_id{};
+ NFP::CabinetMode cabinet_mode{};
+
AppletFrontendSet frontend;
Core::System& system;
};
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/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 b723b65c8..2f09cade5 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -26,7 +26,7 @@
#include "core/hle/service/ipc_helpers.h"
#include "core/memory.h"
-using namespace AudioCore::AudioRenderer;
+using namespace AudioCore::Renderer;
namespace Service::Audio {
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/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 610fe9940..31dd98140 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -4,6 +4,7 @@
#include "core/hle/service/caps/caps.h"
#include "core/hle/service/caps/caps_a.h"
#include "core/hle/service/caps/caps_c.h"
+#include "core/hle/service/caps/caps_manager.h"
#include "core/hle/service/caps/caps_sc.h"
#include "core/hle/service/caps/caps_ss.h"
#include "core/hle/service/caps/caps_su.h"
@@ -15,13 +16,21 @@ namespace Service::Capture {
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
+ auto album_manager = std::make_shared<AlbumManager>(system);
+
+ server_manager->RegisterNamedService(
+ "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
+ server_manager->RegisterNamedService(
+ "caps:c", std::make_shared<IAlbumControlService>(system, album_manager));
+ server_manager->RegisterNamedService(
+ "caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager));
+
+ server_manager->RegisterNamedService("caps:ss", std::make_shared<IScreenShotService>(system));
+ server_manager->RegisterNamedService("caps:sc",
+ std::make_shared<IScreenShotControlService>(system));
+ server_manager->RegisterNamedService("caps:su",
+ std::make_shared<IScreenShotApplicationService>(system));
- server_manager->RegisterNamedService("caps:a", std::make_shared<CAPS_A>(system));
- server_manager->RegisterNamedService("caps:c", std::make_shared<CAPS_C>(system));
- server_manager->RegisterNamedService("caps:u", std::make_shared<CAPS_U>(system));
- server_manager->RegisterNamedService("caps:sc", std::make_shared<CAPS_SC>(system));
- server_manager->RegisterNamedService("caps:ss", std::make_shared<CAPS_SS>(system));
- server_manager->RegisterNamedService("caps:su", std::make_shared<CAPS_SU>(system));
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h
index 15f0ecfaa..58e9725b8 100644
--- a/src/core/hle/service/caps/caps.h
+++ b/src/core/hle/service/caps/caps.h
@@ -3,93 +3,12 @@
#pragma once
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
namespace Core {
class System;
}
-namespace Service::SM {
-class ServiceManager;
-}
-
namespace Service::Capture {
-enum class AlbumImageOrientation {
- Orientation0 = 0,
- Orientation1 = 1,
- Orientation2 = 2,
- Orientation3 = 3,
-};
-
-enum class AlbumReportOption : s32 {
- Disable = 0,
- Enable = 1,
-};
-
-enum class ContentType : u8 {
- Screenshot = 0,
- Movie = 1,
- ExtraMovie = 3,
-};
-
-enum class AlbumStorage : u8 {
- NAND = 0,
- SD = 1,
-};
-
-struct AlbumFileDateTime {
- s16 year{};
- s8 month{};
- s8 day{};
- s8 hour{};
- s8 minute{};
- s8 second{};
- s8 uid{};
-};
-static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
-
-struct AlbumEntry {
- u64 size{};
- u64 application_id{};
- AlbumFileDateTime datetime{};
- AlbumStorage storage{};
- ContentType content{};
- INSERT_PADDING_BYTES(6);
-};
-static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
-
-struct AlbumFileEntry {
- u64 size{}; // Size of the entry
- u64 hash{}; // AES256 with hardcoded key over AlbumEntry
- AlbumFileDateTime datetime{};
- AlbumStorage storage{};
- ContentType content{};
- INSERT_PADDING_BYTES(5);
- u8 unknown{1}; // Set to 1 on official SW
-};
-static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
-
-struct ApplicationAlbumEntry {
- u64 size{}; // Size of the entry
- u64 hash{}; // AES256 with hardcoded key over AlbumEntry
- AlbumFileDateTime datetime{};
- AlbumStorage storage{};
- ContentType content{};
- INSERT_PADDING_BYTES(5);
- u8 unknown{1}; // Set to 1 on official SW
-};
-static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
-
-struct ApplicationAlbumFileEntry {
- ApplicationAlbumEntry entry{};
- AlbumFileDateTime datetime{};
- u64 unknown{};
-};
-static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
- "ApplicationAlbumFileEntry has incorrect size.");
-
void LoopProcess(Core::System& system);
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 44267b284..9925720a3 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -1,40 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/logging/log.h"
#include "core/hle/service/caps/caps_a.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_result.h"
+#include "core/hle/service/caps/caps_types.h"
+#include "core/hle/service/ipc_helpers.h"
namespace Service::Capture {
-class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> {
-public:
- explicit IAlbumAccessorSession(Core::System& system_)
- : ServiceFramework{system_, "IAlbumAccessorSession"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {2001, nullptr, "OpenAlbumMovieReadStream"},
- {2002, nullptr, "CloseAlbumMovieReadStream"},
- {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
- {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
- {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
- {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
- {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
- {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
+IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager)
+ : ServiceFramework{system_, "caps:a"}, manager{album_manager} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAlbumFileCount"},
{1, nullptr, "GetAlbumFileList"},
{2, nullptr, "LoadAlbumFile"},
- {3, nullptr, "DeleteAlbumFile"},
+ {3, &IAlbumAccessorService::DeleteAlbumFile, "DeleteAlbumFile"},
{4, nullptr, "StorageCopyAlbumFile"},
- {5, nullptr, "IsAlbumMounted"},
+ {5, &IAlbumAccessorService::IsAlbumMounted, "IsAlbumMounted"},
{6, nullptr, "GetAlbumUsage"},
{7, nullptr, "GetAlbumFileSize"},
{8, nullptr, "LoadAlbumFileThumbnail"},
@@ -47,18 +33,18 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
{15, nullptr, "GetAlbumUsage3"},
{16, nullptr, "GetAlbumMountResult"},
{17, nullptr, "GetAlbumUsage16"},
- {18, nullptr, "Unknown18"},
+ {18, &IAlbumAccessorService::Unknown18, "Unknown18"},
{19, nullptr, "Unknown19"},
{100, nullptr, "GetAlbumFileCountEx0"},
- {101, nullptr, "GetAlbumFileListEx0"},
+ {101, &IAlbumAccessorService::GetAlbumFileListEx0, "GetAlbumFileListEx0"},
{202, nullptr, "SaveEditedScreenShot"},
{301, nullptr, "GetLastThumbnail"},
{302, nullptr, "GetLastOverlayMovieThumbnail"},
- {401, nullptr, "GetAutoSavingStorage"},
+ {401, &IAlbumAccessorService::GetAutoSavingStorage, "GetAutoSavingStorage"},
{501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"},
{1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"},
- {1002, nullptr, "LoadAlbumScreenShotImageEx1"},
- {1003, nullptr, "LoadAlbumScreenShotThumbnailImageEx1"},
+ {1002, &IAlbumAccessorService::LoadAlbumScreenShotImageEx1, "LoadAlbumScreenShotImageEx1"},
+ {1003, &IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1, "LoadAlbumScreenShotThumbnailImageEx1"},
{8001, nullptr, "ForceAlbumUnmounted"},
{8002, nullptr, "ResetAlbumMountStatus"},
{8011, nullptr, "RefreshAlbumCache"},
@@ -74,6 +60,199 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
RegisterHandlers(functions);
}
-CAPS_A::~CAPS_A() = default;
+IAlbumAccessorService::~IAlbumAccessorService() = default;
+
+void IAlbumAccessorService::DeleteAlbumFile(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
+ file_id.application_id, file_id.storage, file_id.type);
+
+ Result result = manager->DeleteAlbumFile(file_id);
+ result = TranslateResult(result);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<AlbumStorage>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ Result result = manager->IsAlbumMounted(storage);
+ const bool is_mounted = result.IsSuccess();
+ result = TranslateResult(result);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push<u8>(is_mounted);
+}
+
+void IAlbumAccessorService::Unknown18(HLERequestContext& ctx) {
+ struct UnknownBuffer {
+ INSERT_PADDING_BYTES(0x10);
+ };
+ static_assert(sizeof(UnknownBuffer) == 0x10, "UnknownBuffer is an invalid size");
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called");
+
+ std::vector<UnknownBuffer> buffer{};
+
+ if (!buffer.empty()) {
+ ctx.WriteBuffer(buffer);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(buffer.size()));
+}
+
+void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<AlbumStorage>()};
+ const auto flags{rp.Pop<u8>()};
+ const auto album_entry_size{ctx.GetWriteBufferNumElements<AlbumEntry>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
+
+ std::vector<AlbumEntry> entries;
+ Result result = manager->GetAlbumFileList(entries, storage, flags);
+ result = TranslateResult(result);
+
+ entries.resize(std::min(album_entry_size, entries.size()));
+
+ if (!entries.empty()) {
+ ctx.WriteBuffer(entries);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push<u64>(entries.size());
+}
+
+void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
+ LOG_WARNING(Service_Capture, "(STUBBED) called");
+
+ bool is_autosaving{};
+ Result result = manager->GetAutoSavingStorage(is_autosaving);
+ result = TranslateResult(result);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push<u8>(is_autosaving);
+}
+
+void IAlbumAccessorService::LoadAlbumScreenShotImageEx1(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+ const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
+ const auto image_buffer_size{ctx.GetWriteBufferSize(1)};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
+ file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
+
+ std::vector<u8> image;
+ LoadAlbumScreenShotImageOutput image_output;
+ Result result =
+ manager->LoadAlbumScreenShotImage(image_output, image, file_id, decoder_options);
+ result = TranslateResult(result);
+
+ if (image.size() > image_buffer_size) {
+ result = ResultWorkMemoryError;
+ }
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(image_output, 0);
+ ctx.WriteBuffer(image, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+ const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
+ file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
+
+ std::vector<u8> image(ctx.GetWriteBufferSize(1));
+ LoadAlbumScreenShotImageOutput image_output;
+ Result result =
+ manager->LoadAlbumScreenShotThumbnail(image_output, image, file_id, decoder_options);
+ result = TranslateResult(result);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(image_output, 0);
+ ctx.WriteBuffer(image, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+Result IAlbumAccessorService::TranslateResult(Result in_result) {
+ if (in_result.IsSuccess()) {
+ return in_result;
+ }
+
+ if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) {
+ if (in_result.description - 0x514 < 100) {
+ return ResultInvalidFileData;
+ }
+ if (in_result.description - 0x5dc < 100) {
+ return ResultInvalidFileData;
+ }
+
+ if (in_result.description - 0x578 < 100) {
+ if (in_result == ResultFileCountLimit) {
+ return ResultUnknown22;
+ }
+ return ResultUnknown25;
+ }
+
+ if (in_result.raw < ResultUnknown1801.raw) {
+ if (in_result == ResultUnknown1202) {
+ return ResultUnknown810;
+ }
+ if (in_result == ResultUnknown1203) {
+ return ResultUnknown810;
+ }
+ if (in_result == ResultUnknown1701) {
+ return ResultUnknown5;
+ }
+ } else if (in_result.raw < ResultUnknown1803.raw) {
+ if (in_result == ResultUnknown1801) {
+ return ResultUnknown5;
+ }
+ if (in_result == ResultUnknown1802) {
+ return ResultUnknown6;
+ }
+ } else {
+ if (in_result == ResultUnknown1803) {
+ return ResultUnknown7;
+ }
+ if (in_result == ResultUnknown1804) {
+ return ResultOutOfRange;
+ }
+ }
+ return ResultUnknown1024;
+ }
+
+ if (in_result.module == ErrorModule::FS) {
+ if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) ||
+ (((in_result.description - 3000) >> 3) < 0x271)) {
+ // TODO: Translate FS error
+ return in_result;
+ }
+ }
+
+ return in_result;
+}
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index 98a21a5ad..c90cff71e 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -10,11 +10,26 @@ class System;
}
namespace Service::Capture {
+class AlbumManager;
-class CAPS_A final : public ServiceFramework<CAPS_A> {
+class IAlbumAccessorService final : public ServiceFramework<IAlbumAccessorService> {
public:
- explicit CAPS_A(Core::System& system_);
- ~CAPS_A() override;
+ explicit IAlbumAccessorService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager);
+ ~IAlbumAccessorService() override;
+
+private:
+ void DeleteAlbumFile(HLERequestContext& ctx);
+ void IsAlbumMounted(HLERequestContext& ctx);
+ void Unknown18(HLERequestContext& ctx);
+ void GetAlbumFileListEx0(HLERequestContext& ctx);
+ void GetAutoSavingStorage(HLERequestContext& ctx);
+ void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx);
+ void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx);
+
+ Result TranslateResult(Result in_result);
+
+ std::shared_ptr<AlbumManager> manager = nullptr;
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp
index fc77e35cd..1e7fe6474 100644
--- a/src/core/hle/service/caps/caps_c.cpp
+++ b/src/core/hle/service/caps/caps_c.cpp
@@ -3,53 +3,21 @@
#include "common/logging/log.h"
#include "core/hle/service/caps/caps_c.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_result.h"
+#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/ipc_helpers.h"
namespace Service::Capture {
-class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> {
-public:
- explicit IAlbumControlSession(Core::System& system_)
- : ServiceFramework{system_, "IAlbumControlSession"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {2001, nullptr, "OpenAlbumMovieReadStream"},
- {2002, nullptr, "CloseAlbumMovieReadStream"},
- {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
- {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
- {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
- {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
- {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
- {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
- {2401, nullptr, "OpenAlbumMovieWriteStream"},
- {2402, nullptr, "FinishAlbumMovieWriteStream"},
- {2403, nullptr, "CommitAlbumMovieWriteStream"},
- {2404, nullptr, "DiscardAlbumMovieWriteStream"},
- {2405, nullptr, "DiscardAlbumMovieWriteStreamNoDelete"},
- {2406, nullptr, "CommitAlbumMovieWriteStreamEx"},
- {2411, nullptr, "StartAlbumMovieWriteStreamDataSection"},
- {2412, nullptr, "EndAlbumMovieWriteStreamDataSection"},
- {2413, nullptr, "StartAlbumMovieWriteStreamMetaSection"},
- {2414, nullptr, "EndAlbumMovieWriteStreamMetaSection"},
- {2421, nullptr, "ReadDataFromAlbumMovieWriteStream"},
- {2422, nullptr, "WriteDataToAlbumMovieWriteStream"},
- {2424, nullptr, "WriteMetaToAlbumMovieWriteStream"},
- {2431, nullptr, "GetAlbumMovieWriteStreamBrokenReason"},
- {2433, nullptr, "GetAlbumMovieWriteStreamDataSize"},
- {2434, nullptr, "SetAlbumMovieWriteStreamDataSize"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
+IAlbumControlService::IAlbumControlService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager)
+ : ServiceFramework{system_, "caps:c"}, manager{album_manager} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
{2, nullptr, "CaptureRawImageWithTimeout"},
- {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"},
+ {33, &IAlbumControlService::SetShimLibraryVersion, "SetShimLibraryVersion"},
{1001, nullptr, "RequestTakingScreenShot"},
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
{1011, nullptr, "NotifyTakingScreenShotRefused"},
@@ -72,9 +40,9 @@ CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
RegisterHandlers(functions);
}
-CAPS_C::~CAPS_C() = default;
+IAlbumControlService::~IAlbumControlService() = default;
-void CAPS_C::SetShimLibraryVersion(HLERequestContext& ctx) {
+void IAlbumControlService::SetShimLibraryVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto library_version{rp.Pop<u64>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h
index 537b3a2e3..92ba242db 100644
--- a/src/core/hle/service/caps/caps_c.h
+++ b/src/core/hle/service/caps/caps_c.h
@@ -10,14 +10,18 @@ class System;
}
namespace Service::Capture {
+class AlbumManager;
-class CAPS_C final : public ServiceFramework<CAPS_C> {
+class IAlbumControlService final : public ServiceFramework<IAlbumControlService> {
public:
- explicit CAPS_C(Core::System& system_);
- ~CAPS_C() override;
+ explicit IAlbumControlService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager);
+ ~IAlbumControlService() override;
private:
void SetShimLibraryVersion(HLERequestContext& ctx);
+
+ std::shared_ptr<AlbumManager> manager = nullptr;
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
new file mode 100644
index 000000000..2b4e3f076
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -0,0 +1,386 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <sstream>
+#include <stb_image.h>
+#include <stb_image_resize.h>
+
+#include "common/fs/file.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_result.h"
+#include "core/hle/service/time/time_manager.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
+
+namespace Service::Capture {
+
+AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
+
+AlbumManager::~AlbumManager() = default;
+
+Result AlbumManager::DeleteAlbumFile(const AlbumFileId& file_id) {
+ if (file_id.storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ std::filesystem::path path;
+ const auto result = GetFile(path, file_id);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ if (!Common::FS::RemoveFile(path)) {
+ return ResultFileNotFound;
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::IsAlbumMounted(AlbumStorage storage) {
+ if (storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ is_mounted = true;
+
+ if (storage == AlbumStorage::Sd) {
+ FindScreenshots();
+ }
+
+ return is_mounted ? ResultSuccess : ResultIsNotMounted;
+}
+
+Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
+ u8 flags) const {
+ if (storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ for (auto& [file_id, path] : album_files) {
+ if (file_id.storage != storage) {
+ continue;
+ }
+ if (out_entries.size() >= SdAlbumFileLimit) {
+ break;
+ }
+
+ const auto entry_size = Common::FS::GetSize(path);
+ out_entries.push_back({
+ .entry_size = entry_size,
+ .file_id = file_id,
+ });
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
+ ContentType contex_type, s64 start_posix_time,
+ s64 end_posix_time, u64 aruid) const {
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ std::vector<ApplicationAlbumEntry> album_entries;
+ const auto start_date = ConvertToAlbumDateTime(start_posix_time);
+ const auto end_date = ConvertToAlbumDateTime(end_posix_time);
+ const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ for (const auto& album_entry : album_entries) {
+ ApplicationAlbumFileEntry entry{
+ .entry = album_entry,
+ .datetime = album_entry.datetime,
+ .unknown = {},
+ };
+ out_entries.push_back(entry);
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
+ ContentType contex_type, AlbumFileDateTime start_date,
+ AlbumFileDateTime end_date, u64 aruid) const {
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ for (auto& [file_id, path] : album_files) {
+ if (file_id.type != contex_type) {
+ continue;
+ }
+ if (file_id.date > start_date) {
+ continue;
+ }
+ if (file_id.date < end_date) {
+ continue;
+ }
+ if (out_entries.size() >= SdAlbumFileLimit) {
+ break;
+ }
+
+ const auto entry_size = Common::FS::GetSize(path);
+ ApplicationAlbumEntry entry{
+ .size = entry_size,
+ .hash{},
+ .datetime = file_id.date,
+ .storage = file_id.storage,
+ .content = contex_type,
+ .unknown = 1,
+ };
+ out_entries.push_back(entry);
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::GetAutoSavingStorage(bool& out_is_autosaving) const {
+ out_is_autosaving = false;
+ return ResultSuccess;
+}
+
+Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
+ std::vector<u8>& out_image,
+ const AlbumFileId& file_id,
+ const ScreenShotDecodeOption& decoder_options) const {
+ if (file_id.storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ out_image_output = {
+ .width = 1280,
+ .height = 720,
+ .attribute =
+ {
+ .unknown_0{},
+ .orientation = AlbumImageOrientation::None,
+ .unknown_1{},
+ .unknown_2{},
+ },
+ };
+
+ std::filesystem::path path;
+ const auto result = GetFile(path, file_id);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
+
+ return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
+ +static_cast<int>(out_image_output.height), decoder_options.flags);
+}
+
+Result AlbumManager::LoadAlbumScreenShotThumbnail(
+ LoadAlbumScreenShotImageOutput& out_image_output, std::vector<u8>& out_image,
+ const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const {
+ if (file_id.storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ out_image_output = {
+ .width = 320,
+ .height = 180,
+ .attribute =
+ {
+ .unknown_0{},
+ .orientation = AlbumImageOrientation::None,
+ .unknown_1{},
+ .unknown_2{},
+ },
+ };
+
+ std::filesystem::path path;
+ const auto result = GetFile(path, file_id);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
+
+ return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
+ +static_cast<int>(out_image_output.height), decoder_options.flags);
+}
+
+Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
+ const auto file = album_files.find(file_id);
+
+ if (file == album_files.end()) {
+ return ResultFileNotFound;
+ }
+
+ out_path = file->second;
+ return ResultSuccess;
+}
+
+void AlbumManager::FindScreenshots() {
+ is_mounted = false;
+ album_files.clear();
+
+ // TODO: Swap this with a blocking operation.
+ const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir);
+ Common::FS::IterateDirEntries(
+ screenshots_dir,
+ [this](const std::filesystem::path& full_path) {
+ AlbumEntry entry;
+ if (GetAlbumEntry(entry, full_path).IsError()) {
+ return true;
+ }
+ while (album_files.contains(entry.file_id)) {
+ if (++entry.file_id.date.unique_id == 0) {
+ break;
+ }
+ }
+ album_files[entry.file_id] = full_path;
+ return true;
+ },
+ Common::FS::DirEntryFilter::File);
+
+ is_mounted = true;
+}
+
+Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const {
+ std::istringstream line_stream(path.filename().string());
+ std::string date;
+ std::string application;
+ std::string time;
+
+ // Parse filename to obtain entry properties
+ std::getline(line_stream, application, '_');
+ std::getline(line_stream, date, '_');
+ std::getline(line_stream, time, '_');
+
+ std::istringstream date_stream(date);
+ std::istringstream time_stream(time);
+ std::string year;
+ std::string month;
+ std::string day;
+ std::string hour;
+ std::string minute;
+ std::string second;
+
+ std::getline(date_stream, year, '-');
+ std::getline(date_stream, month, '-');
+ std::getline(date_stream, day, '-');
+
+ std::getline(time_stream, hour, '-');
+ std::getline(time_stream, minute, '-');
+ std::getline(time_stream, second, '-');
+
+ try {
+ out_entry = {
+ .entry_size = 1,
+ .file_id{
+ .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
+ .date =
+ {
+ .year = static_cast<s16>(std::stoi(year)),
+ .month = static_cast<s8>(std::stoi(month)),
+ .day = static_cast<s8>(std::stoi(day)),
+ .hour = static_cast<s8>(std::stoi(hour)),
+ .minute = static_cast<s8>(std::stoi(minute)),
+ .second = static_cast<s8>(std::stoi(second)),
+ .unique_id = 0,
+ },
+ .storage = AlbumStorage::Sd,
+ .type = ContentType::Screenshot,
+ .unknown = 1,
+ },
+ };
+ } catch (const std::invalid_argument&) {
+ return ResultUnknown;
+ } catch (const std::out_of_range&) {
+ return ResultUnknown;
+ } catch (const std::exception&) {
+ return ResultUnknown;
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
+ int width, int height, ScreenShotDecoderFlag flag) const {
+ if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
+ return ResultUnknown;
+ }
+
+ const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+
+ std::vector<u8> raw_file(db_file.GetSize());
+ if (db_file.Read(raw_file) != raw_file.size()) {
+ return ResultUnknown;
+ }
+
+ int filter_flag = STBIR_FILTER_DEFAULT;
+ int original_width, original_height, color_channels;
+ const auto dbi_image =
+ stbi_load_from_memory(raw_file.data(), static_cast<int>(raw_file.size()), &original_width,
+ &original_height, &color_channels, STBI_rgb_alpha);
+
+ if (dbi_image == nullptr) {
+ return ResultUnknown;
+ }
+
+ switch (flag) {
+ case ScreenShotDecoderFlag::EnableFancyUpsampling:
+ filter_flag = STBIR_FILTER_TRIANGLE;
+ break;
+ case ScreenShotDecoderFlag::EnableBlockSmoothing:
+ filter_flag = STBIR_FILTER_BOX;
+ break;
+ default:
+ filter_flag = STBIR_FILTER_DEFAULT;
+ break;
+ }
+
+ stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width,
+ height, 0, STBI_rgb_alpha, 3, filter_flag);
+
+ return ResultSuccess;
+}
+
+AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
+ Time::TimeZone::CalendarInfo calendar_date{};
+ const auto& time_zone_manager =
+ system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
+
+ time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
+
+ return {
+ .year = calendar_date.time.year,
+ .month = calendar_date.time.month,
+ .day = calendar_date.time.day,
+ .hour = calendar_date.time.hour,
+ .minute = calendar_date.time.minute,
+ .second = calendar_date.time.second,
+ .unique_id = 0,
+ };
+}
+
+} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h
new file mode 100644
index 000000000..f65eb12c1
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <unordered_map>
+
+#include "common/fs/fs.h"
+#include "core/hle/result.h"
+#include "core/hle/service/caps/caps_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace std {
+// Hash used to create lists from AlbumFileId data
+template <>
+struct hash<Service::Capture::AlbumFileId> {
+ size_t operator()(const Service::Capture::AlbumFileId& pad_id) const noexcept {
+ u64 hash_value = (static_cast<u64>(pad_id.date.year) << 8);
+ hash_value ^= (static_cast<u64>(pad_id.date.month) << 7);
+ hash_value ^= (static_cast<u64>(pad_id.date.day) << 6);
+ hash_value ^= (static_cast<u64>(pad_id.date.hour) << 5);
+ hash_value ^= (static_cast<u64>(pad_id.date.minute) << 4);
+ hash_value ^= (static_cast<u64>(pad_id.date.second) << 3);
+ hash_value ^= (static_cast<u64>(pad_id.date.unique_id) << 2);
+ hash_value ^= (static_cast<u64>(pad_id.storage) << 1);
+ hash_value ^= static_cast<u64>(pad_id.type);
+ return static_cast<size_t>(hash_value);
+ }
+};
+
+} // namespace std
+
+namespace Service::Capture {
+
+class AlbumManager {
+public:
+ explicit AlbumManager(Core::System& system_);
+ ~AlbumManager();
+
+ Result DeleteAlbumFile(const AlbumFileId& file_id);
+ Result IsAlbumMounted(AlbumStorage storage);
+ Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
+ u8 flags) const;
+ Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
+ ContentType contex_type, s64 start_posix_time, s64 end_posix_time,
+ u64 aruid) const;
+ Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
+ ContentType contex_type, AlbumFileDateTime start_date,
+ AlbumFileDateTime end_date, u64 aruid) const;
+ Result GetAutoSavingStorage(bool& out_is_autosaving) const;
+ Result LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
+ std::vector<u8>& out_image, const AlbumFileId& file_id,
+ const ScreenShotDecodeOption& decoder_options) const;
+ Result LoadAlbumScreenShotThumbnail(LoadAlbumScreenShotImageOutput& out_image_output,
+ std::vector<u8>& out_image, const AlbumFileId& file_id,
+ const ScreenShotDecodeOption& decoder_options) const;
+
+private:
+ static constexpr std::size_t NandAlbumFileLimit = 1000;
+ static constexpr std::size_t SdAlbumFileLimit = 10000;
+
+ void FindScreenshots();
+ Result GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const;
+ Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const;
+ Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
+ int height, ScreenShotDecoderFlag flag) const;
+
+ AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
+
+ bool is_mounted{};
+ std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
+
+ Core::System& system;
+};
+
+} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_result.h b/src/core/hle/service/caps/caps_result.h
new file mode 100644
index 000000000..c65e5fb9a
--- /dev/null
+++ b/src/core/hle/service/caps/caps_result.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Capture {
+
+constexpr Result ResultWorkMemoryError(ErrorModule::Capture, 3);
+constexpr Result ResultUnknown5(ErrorModule::Capture, 5);
+constexpr Result ResultUnknown6(ErrorModule::Capture, 6);
+constexpr Result ResultUnknown7(ErrorModule::Capture, 7);
+constexpr Result ResultOutOfRange(ErrorModule::Capture, 8);
+constexpr Result ResulInvalidTimestamp(ErrorModule::Capture, 12);
+constexpr Result ResultInvalidStorage(ErrorModule::Capture, 13);
+constexpr Result ResultInvalidFileContents(ErrorModule::Capture, 14);
+constexpr Result ResultIsNotMounted(ErrorModule::Capture, 21);
+constexpr Result ResultUnknown22(ErrorModule::Capture, 22);
+constexpr Result ResultFileNotFound(ErrorModule::Capture, 23);
+constexpr Result ResultInvalidFileData(ErrorModule::Capture, 24);
+constexpr Result ResultUnknown25(ErrorModule::Capture, 25);
+constexpr Result ResultReadBufferShortage(ErrorModule::Capture, 30);
+constexpr Result ResultUnknown810(ErrorModule::Capture, 810);
+constexpr Result ResultUnknown1024(ErrorModule::Capture, 1024);
+constexpr Result ResultUnknown1202(ErrorModule::Capture, 1202);
+constexpr Result ResultUnknown1203(ErrorModule::Capture, 1203);
+constexpr Result ResultFileCountLimit(ErrorModule::Capture, 1401);
+constexpr Result ResultUnknown1701(ErrorModule::Capture, 1701);
+constexpr Result ResultUnknown1801(ErrorModule::Capture, 1801);
+constexpr Result ResultUnknown1802(ErrorModule::Capture, 1802);
+constexpr Result ResultUnknown1803(ErrorModule::Capture, 1803);
+constexpr Result ResultUnknown1804(ErrorModule::Capture, 1804);
+
+} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp
index 395b13da7..6117cb7c6 100644
--- a/src/core/hle/service/caps/caps_sc.cpp
+++ b/src/core/hle/service/caps/caps_sc.cpp
@@ -5,7 +5,8 @@
namespace Service::Capture {
-CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
+IScreenShotControlService::IScreenShotControlService(Core::System& system_)
+ : ServiceFramework{system_, "caps:sc"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
@@ -34,6 +35,6 @@ CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
RegisterHandlers(functions);
}
-CAPS_SC::~CAPS_SC() = default;
+IScreenShotControlService::~IScreenShotControlService() = default;
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h
index e5600f6d7..d555f4979 100644
--- a/src/core/hle/service/caps/caps_sc.h
+++ b/src/core/hle/service/caps/caps_sc.h
@@ -11,10 +11,10 @@ class System;
namespace Service::Capture {
-class CAPS_SC final : public ServiceFramework<CAPS_SC> {
+class IScreenShotControlService final : public ServiceFramework<IScreenShotControlService> {
public:
- explicit CAPS_SC(Core::System& system_);
- ~CAPS_SC() override;
+ explicit IScreenShotControlService(Core::System& system_);
+ ~IScreenShotControlService() override;
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index 62b9edd41..d0d1b5425 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -5,7 +5,8 @@
namespace Service::Capture {
-CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
+IScreenShotService::IScreenShotService(Core::System& system_)
+ : ServiceFramework{system_, "caps:ss"} {
// clang-format off
static const FunctionInfo functions[] = {
{201, nullptr, "SaveScreenShot"},
@@ -21,6 +22,6 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
RegisterHandlers(functions);
}
-CAPS_SS::~CAPS_SS() = default;
+IScreenShotService::~IScreenShotService() = default;
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h
index 718ade485..381e44fd4 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -11,10 +11,10 @@ class System;
namespace Service::Capture {
-class CAPS_SS final : public ServiceFramework<CAPS_SS> {
+class IScreenShotService final : public ServiceFramework<IScreenShotService> {
public:
- explicit CAPS_SS(Core::System& system_);
- ~CAPS_SS() override;
+ explicit IScreenShotService(Core::System& system_);
+ ~IScreenShotService() override;
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index 3b11cc95c..cad173dc7 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -7,10 +7,11 @@
namespace Service::Capture {
-CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
+IScreenShotApplicationService::IScreenShotApplicationService(Core::System& system_)
+ : ServiceFramework{system_, "caps:su"} {
// clang-format off
static const FunctionInfo functions[] = {
- {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"},
+ {32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
{201, nullptr, "SaveScreenShot"},
{203, nullptr, "SaveScreenShotEx0"},
{205, nullptr, "SaveScreenShotEx1"},
@@ -21,9 +22,9 @@ CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
RegisterHandlers(functions);
}
-CAPS_SU::~CAPS_SU() = default;
+IScreenShotApplicationService::~IScreenShotApplicationService() = default;
-void CAPS_SU::SetShimLibraryVersion(HLERequestContext& ctx) {
+void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto library_version{rp.Pop<u64>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index c6398858d..647e3059d 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -11,10 +11,10 @@ class System;
namespace Service::Capture {
-class CAPS_SU final : public ServiceFramework<CAPS_SU> {
+class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
public:
- explicit CAPS_SU(Core::System& system_);
- ~CAPS_SU() override;
+ explicit IScreenShotApplicationService(Core::System& system_);
+ ~IScreenShotApplicationService() override;
private:
void SetShimLibraryVersion(HLERequestContext& ctx);
diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h
new file mode 100644
index 000000000..7fd357954
--- /dev/null
+++ b/src/core/hle/service/caps/caps_types.h
@@ -0,0 +1,184 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::Capture {
+
+// This is nn::album::ImageOrientation
+enum class AlbumImageOrientation {
+ None,
+ Rotate90,
+ Rotate180,
+ Rotate270,
+};
+
+// This is nn::album::AlbumReportOption
+enum class AlbumReportOption : s32 {
+ Disable,
+ Enable,
+};
+
+enum class ContentType : u8 {
+ Screenshot = 0,
+ Movie = 1,
+ ExtraMovie = 3,
+};
+
+enum class AlbumStorage : u8 {
+ Nand,
+ Sd,
+};
+
+enum class ScreenShotDecoderFlag : u64 {
+ None = 0,
+ EnableFancyUpsampling = 1 << 0,
+ EnableBlockSmoothing = 1 << 1,
+};
+
+// This is nn::capsrv::AlbumFileDateTime
+struct AlbumFileDateTime {
+ s16 year{};
+ s8 month{};
+ s8 day{};
+ s8 hour{};
+ s8 minute{};
+ s8 second{};
+ s8 unique_id{};
+
+ friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
+ friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
+ if (a.year > b.year) {
+ return true;
+ }
+ if (a.month > b.month) {
+ return true;
+ }
+ if (a.day > b.day) {
+ return true;
+ }
+ if (a.hour > b.hour) {
+ return true;
+ }
+ if (a.minute > b.minute) {
+ return true;
+ }
+ return a.second > b.second;
+ };
+ friend constexpr bool operator<(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
+ if (a.year < b.year) {
+ return true;
+ }
+ if (a.month < b.month) {
+ return true;
+ }
+ if (a.day < b.day) {
+ return true;
+ }
+ if (a.hour < b.hour) {
+ return true;
+ }
+ if (a.minute < b.minute) {
+ return true;
+ }
+ return a.second < b.second;
+ };
+};
+static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
+
+// This is nn::album::AlbumEntry
+struct AlbumFileEntry {
+ u64 size{}; // Size of the entry
+ u64 hash{}; // AES256 with hardcoded key over AlbumEntry
+ AlbumFileDateTime datetime{};
+ AlbumStorage storage{};
+ ContentType content{};
+ INSERT_PADDING_BYTES(5);
+ u8 unknown{}; // Set to 1 on official SW
+};
+static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
+
+struct AlbumFileId {
+ u64 application_id{};
+ AlbumFileDateTime date{};
+ AlbumStorage storage{};
+ ContentType type{};
+ INSERT_PADDING_BYTES(0x5);
+ u8 unknown{};
+
+ friend constexpr bool operator==(const AlbumFileId&, const AlbumFileId&) = default;
+};
+static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size");
+
+// This is nn::capsrv::AlbumEntry
+struct AlbumEntry {
+ u64 entry_size{};
+ AlbumFileId file_id{};
+};
+static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
+
+// This is nn::capsrv::ApplicationAlbumEntry
+struct ApplicationAlbumEntry {
+ u64 size{}; // Size of the entry
+ u64 hash{}; // AES256 with hardcoded key over AlbumEntry
+ AlbumFileDateTime datetime{};
+ AlbumStorage storage{};
+ ContentType content{};
+ INSERT_PADDING_BYTES(5);
+ u8 unknown{1}; // Set to 1 on official SW
+};
+static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
+
+// This is nn::capsrv::ApplicationAlbumFileEntry
+struct ApplicationAlbumFileEntry {
+ ApplicationAlbumEntry entry{};
+ AlbumFileDateTime datetime{};
+ u64 unknown{};
+};
+static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
+ "ApplicationAlbumFileEntry has incorrect size.");
+
+struct ApplicationData {
+ std::array<u8, 0x400> data{};
+ u32 data_size{};
+};
+static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size");
+
+struct ScreenShotAttribute {
+ u32 unknown_0{};
+ AlbumImageOrientation orientation{};
+ u32 unknown_1{};
+ u32 unknown_2{};
+ INSERT_PADDING_BYTES(0x30);
+};
+static_assert(sizeof(ScreenShotAttribute) == 0x40, "ScreenShotAttribute is an invalid size");
+
+struct ScreenShotDecodeOption {
+ ScreenShotDecoderFlag flags{};
+ INSERT_PADDING_BYTES(0x18);
+};
+static_assert(sizeof(ScreenShotDecodeOption) == 0x20, "ScreenShotDecodeOption is an invalid size");
+
+struct LoadAlbumScreenShotImageOutput {
+ s64 width{};
+ s64 height{};
+ ScreenShotAttribute attribute{};
+ INSERT_PADDING_BYTES(0x400);
+};
+static_assert(sizeof(LoadAlbumScreenShotImageOutput) == 0x450,
+ "LoadAlbumScreenShotImageOutput is an invalid size");
+
+struct LoadAlbumScreenShotImageOutputForApplication {
+ s64 width{};
+ s64 height{};
+ ScreenShotAttribute attribute{};
+ ApplicationData data{};
+ INSERT_PADDING_BYTES(0xAC);
+};
+static_assert(sizeof(LoadAlbumScreenShotImageOutputForApplication) == 0x500,
+ "LoadAlbumScreenShotImageOutput is an invalid size");
+
+} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index bffe0f8d0..b6b33fb2f 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -2,45 +2,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
-#include "core/hle/service/caps/caps.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/caps/caps_u.h"
#include "core/hle/service/ipc_helpers.h"
namespace Service::Capture {
-class IAlbumAccessorApplicationSession final
- : public ServiceFramework<IAlbumAccessorApplicationSession> {
-public:
- explicit IAlbumAccessorApplicationSession(Core::System& system_)
- : ServiceFramework{system_, "IAlbumAccessorApplicationSession"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {2001, nullptr, "OpenAlbumMovieReadStream"},
- {2002, nullptr, "CloseAlbumMovieReadStream"},
- {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
- {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
- {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
+IAlbumApplicationService::IAlbumApplicationService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager)
+ : ServiceFramework{system_, "caps:u"}, manager{album_manager} {
// clang-format off
static const FunctionInfo functions[] = {
- {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"},
- {102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"},
- {103, nullptr, "DeleteAlbumContentsFileForApplication"},
- {104, nullptr, "GetAlbumContentsFileSizeForApplication"},
+ {32, &IAlbumApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
+ {102, &IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated, "GetAlbumFileList0AafeAruidDeprecated"},
+ {103, nullptr, "DeleteAlbumFileByAruid"},
+ {104, nullptr, "GetAlbumFileSizeByAruid"},
{105, nullptr, "DeleteAlbumFileByAruidForDebug"},
- {110, nullptr, "LoadAlbumContentsFileScreenShotImageForApplication"},
- {120, nullptr, "LoadAlbumContentsFileThumbnailImageForApplication"},
- {130, nullptr, "PrecheckToCreateContentsForApplication"},
+ {110, nullptr, "LoadAlbumScreenShotImageByAruid"},
+ {120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"},
+ {130, nullptr, "PrecheckToCreateContentsByAruid"},
{140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
{141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
- {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
+ {142, &IAlbumApplicationService::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
{143, nullptr, "GetAlbumFileList4AaeUidAruid"},
{144, nullptr, "GetAllAlbumFileList3AaeAruid"},
{60002, nullptr, "OpenAccessorSessionForApplication"},
@@ -50,9 +34,9 @@ CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
RegisterHandlers(functions);
}
-CAPS_U::~CAPS_U() = default;
+IAlbumApplicationService::~IAlbumApplicationService() = default;
-void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
+void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto library_version{rp.Pop<u64>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -64,37 +48,89 @@ void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
-void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) {
- // Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an
- // u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total
- // output entries (which is copied to a s32 by official SW).
+void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto pid{rp.Pop<s32>()};
- const auto content_type{rp.PopEnum<ContentType>()};
- const auto start_posix_time{rp.Pop<s64>()};
- const auto end_posix_time{rp.Pop<s64>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ ContentType content_type;
+ INSERT_PADDING_BYTES(7);
+ s64 start_posix_time;
+ s64 end_posix_time;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
- // TODO: Update this when we implement the album.
- // Currently we do not have a method of accessing album entries, set this to 0 for now.
- constexpr u32 total_entries_1{};
- constexpr u32 total_entries_2{};
+ const auto parameters{rp.PopRaw<Parameters>()};
- LOG_WARNING(
- Service_Capture,
- "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
- "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}",
- pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id,
- total_entries_1, total_entries_2);
+ LOG_WARNING(Service_Capture,
+ "(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, "
+ "applet_resource_user_id={}",
+ parameters.content_type, parameters.start_posix_time, parameters.end_posix_time,
+ parameters.applet_resource_user_id);
+
+ Result result = ResultSuccess;
+
+ if (result.IsSuccess()) {
+ result = manager->IsAlbumMounted(AlbumStorage::Sd);
+ }
+
+ std::vector<ApplicationAlbumFileEntry> entries;
+ if (result.IsSuccess()) {
+ result = manager->GetAlbumFileList(entries, parameters.content_type,
+ parameters.start_posix_time, parameters.end_posix_time,
+ parameters.applet_resource_user_id);
+ }
+
+ if (!entries.empty()) {
+ ctx.WriteBuffer(entries);
+ }
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push(total_entries_1);
- rb.Push(total_entries_2);
+ rb.Push(result);
+ rb.Push<u64>(entries.size());
}
-void CAPS_U::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
- GetAlbumContentsFileListForApplication(ctx);
+void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ ContentType content_type;
+ INSERT_PADDING_BYTES(1);
+ AlbumFileDateTime start_date_time;
+ AlbumFileDateTime end_date_time;
+ INSERT_PADDING_BYTES(6);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_Capture,
+ "(STUBBED) called. content_type={}, start_date={}/{}/{}, "
+ "end_date={}/{}/{}, applet_resource_user_id={}",
+ parameters.content_type, parameters.start_date_time.year,
+ parameters.start_date_time.month, parameters.start_date_time.day,
+ parameters.end_date_time.year, parameters.end_date_time.month,
+ parameters.end_date_time.day, parameters.applet_resource_user_id);
+
+ Result result = ResultSuccess;
+
+ if (result.IsSuccess()) {
+ result = manager->IsAlbumMounted(AlbumStorage::Sd);
+ }
+
+ std::vector<ApplicationAlbumEntry> entries;
+ if (result.IsSuccess()) {
+ result =
+ manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time,
+ parameters.end_date_time, parameters.applet_resource_user_id);
+ }
+
+ if (!entries.empty()) {
+ ctx.WriteBuffer(entries);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push<u64>(entries.size());
}
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h
index e8dd037d7..9458c128e 100644
--- a/src/core/hle/service/caps/caps_u.h
+++ b/src/core/hle/service/caps/caps_u.h
@@ -10,16 +10,20 @@ class System;
}
namespace Service::Capture {
+class AlbumManager;
-class CAPS_U final : public ServiceFramework<CAPS_U> {
+class IAlbumApplicationService final : public ServiceFramework<IAlbumApplicationService> {
public:
- explicit CAPS_U(Core::System& system_);
- ~CAPS_U() override;
+ explicit IAlbumApplicationService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager);
+ ~IAlbumApplicationService() override;
private:
void SetShimLibraryVersion(HLERequestContext& ctx);
- void GetAlbumContentsFileListForApplication(HLERequestContext& ctx);
+ void GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx);
void GetAlbumFileList3AaeAruid(HLERequestContext& ctx);
+
+ std::shared_ptr<AlbumManager> manager = nullptr;
};
} // namespace Service::Capture
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 4c1ea1a5b..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"
@@ -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;
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 6e4d26b1e..126cd6ffd 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -329,6 +329,7 @@ public:
{13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"},
{14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"},
{15, nullptr, "QueryEntry"},
+ {16, &IFileSystem::GetFileSystemAttribute, "GetFileSystemAttribute"},
};
RegisterHandlers(functions);
}
@@ -521,6 +522,46 @@ public:
rb.PushRaw(vfs_timestamp);
}
+ void GetFileSystemAttribute(HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ struct FileSystemAttribute {
+ u8 dir_entry_name_length_max_defined;
+ u8 file_entry_name_length_max_defined;
+ u8 dir_path_name_length_max_defined;
+ u8 file_path_name_length_max_defined;
+ INSERT_PADDING_BYTES_NOINIT(0x5);
+ u8 utf16_dir_entry_name_length_max_defined;
+ u8 utf16_file_entry_name_length_max_defined;
+ u8 utf16_dir_path_name_length_max_defined;
+ u8 utf16_file_path_name_length_max_defined;
+ INSERT_PADDING_BYTES_NOINIT(0x18);
+ s32 dir_entry_name_length_max;
+ s32 file_entry_name_length_max;
+ s32 dir_path_name_length_max;
+ s32 file_path_name_length_max;
+ INSERT_PADDING_WORDS_NOINIT(0x5);
+ s32 utf16_dir_entry_name_length_max;
+ s32 utf16_file_entry_name_length_max;
+ s32 utf16_dir_path_name_length_max;
+ s32 utf16_file_path_name_length_max;
+ INSERT_PADDING_WORDS_NOINIT(0x18);
+ INSERT_PADDING_WORDS_NOINIT(0x1);
+ };
+ static_assert(sizeof(FileSystemAttribute) == 0xc0,
+ "FileSystemAttribute has incorrect size");
+
+ FileSystemAttribute savedata_attribute{};
+ savedata_attribute.dir_entry_name_length_max_defined = true;
+ savedata_attribute.file_entry_name_length_max_defined = true;
+ savedata_attribute.dir_entry_name_length_max = 0x40;
+ savedata_attribute.file_entry_name_length_max = 0x40;
+
+ IPC::ResponseBuilder rb{ctx, 50};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(savedata_attribute);
+ }
+
private:
VfsDirectoryServiceWrapper backend;
SizeGetter size;
@@ -698,7 +739,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{19, nullptr, "FormatSdCardFileSystem"},
{21, nullptr, "DeleteSaveDataFileSystem"},
{22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"},
- {23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"},
+ {23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"},
{24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
{25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
{26, nullptr, "FormatSdCardDryRun"},
@@ -712,7 +753,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
{36, nullptr, "OpenHostFileSystemWithOption"},
{51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"},
- {52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"},
+ {52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"},
{53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"},
{57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"},
{58, nullptr, "ReadSaveDataFileSystemExtraData"},
@@ -814,6 +855,9 @@ FSP_SRV::FSP_SRV(Core::System& system_)
if (Settings::values.enable_fs_access_log) {
access_log_mode = AccessLogMode::SdCard;
}
+
+ // This should be true on creation
+ fsc.SetAutoSaveDataCreation(true);
}
FSP_SRV::~FSP_SRV() = default;
@@ -870,6 +914,21 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
+ [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
+
+ LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
+
+ FileSys::VirtualDir save_data_dir{};
+ fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -916,6 +975,11 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
}
+void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
+ OpenSaveDataFileSystem(ctx);
+}
+
void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
OpenSaveDataFileSystem(ctx);
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 4f3c2f6de..280bc9867 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -39,7 +39,9 @@ private:
void OpenFileSystemWithPatch(HLERequestContext& ctx);
void OpenSdCardFileSystem(HLERequestContext& ctx);
void CreateSaveDataFileSystem(HLERequestContext& ctx);
+ void CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
void OpenSaveDataFileSystem(HLERequestContext& ctx);
+ void OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx);
void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx);
void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx);
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..bc822f19e 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);
@@ -347,6 +346,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
}
SignalStyleSetChangedEvent(npad_id);
WriteEmptyEntry(controller.shared_memory);
+ hid_core.SetLastActiveController(npad_id);
}
void Controller_NPad::OnInit() {
@@ -419,9 +419,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 +476,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 +748,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 +1120,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 +1512,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 +1547,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/hid.cpp b/src/core/hle/service/hid/hid.cpp
index fd466db7b..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"},
@@ -2543,8 +2545,9 @@ public:
class HidSys final : public ServiceFramework<HidSys> {
public:
- explicit HidSys(Core::System& system_)
- : ServiceFramework{system_, "hid:sys"}, service_context{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"},
@@ -2756,9 +2759,12 @@ public:
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);
}
@@ -2768,7 +2774,7 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.PushEnum(Core::HID::NpadIdType::Handheld);
+ rb.PushEnum(system.HIDCore().GetLastActiveController());
}
void GetUniquePadsFromNpad(HLERequestContext& ctx) {
@@ -2821,17 +2827,28 @@ private:
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/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp
index 4ed3f02e2..0090e8568 100644
--- a/src/core/hle/service/jit/jit_context.cpp
+++ b/src/core/hle/service/jit/jit_context.cpp
@@ -156,6 +156,8 @@ public:
bool LoadNRO(std::span<const u8> data) {
local_memory.clear();
+
+ relocbase = local_memory.size();
local_memory.insert(local_memory.end(), data.begin(), data.end());
if (FixupRelocations()) {
@@ -181,8 +183,8 @@ public:
// https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
// https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
- VAddr rela_dyn = 0;
- size_t num_rela = 0;
+ VAddr rela_dyn = 0, relr_dyn = 0;
+ size_t num_rela = 0, num_relr = 0;
while (true) {
const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
dynamic_offset += sizeof(Elf64_Dyn);
@@ -196,6 +198,12 @@ public:
if (dyn.d_tag == ElfDtRelasz) {
num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
}
+ if (dyn.d_tag == ElfDtRelr) {
+ relr_dyn = dyn.d_un.d_ptr;
+ }
+ if (dyn.d_tag == ElfDtRelrsz) {
+ num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
+ }
}
for (size_t i = 0; i < num_rela; i++) {
@@ -207,6 +215,29 @@ public:
callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
}
+ VAddr relr_where = 0;
+ for (size_t i = 0; i < num_relr; i++) {
+ const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
+ const auto incr{[&](VAddr where) {
+ callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
+ }};
+
+ if ((relr & 1) == 0) {
+ // where pointer
+ relr_where = relocbase + relr;
+ incr(relr_where);
+ relr_where += sizeof(Elf64_Addr);
+ } else {
+ // bitmap
+ for (int bit = 1; bit < 64; bit++) {
+ if ((relr & (1ULL << bit)) != 0) {
+ incr(relr_where + i * sizeof(Elf64_Addr));
+ }
+ }
+ relr_where += 63 * sizeof(Elf64_Addr);
+ }
+ }
+
return true;
}
@@ -313,6 +344,7 @@ public:
Core::Memory::Memory& memory;
VAddr top_of_stack;
VAddr heap_pointer;
+ VAddr relocbase;
};
void DynarmicCallbacks64::CallSVC(u32 swi) {
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 9d149a7cd..7927f8264 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -23,19 +23,39 @@ public:
explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "GetStateForMonitor"},
+ {0, &IMonitorService::GetStateForMonitor, "GetStateForMonitor"},
{1, nullptr, "GetNetworkInfoForMonitor"},
{2, nullptr, "GetIpv4AddressForMonitor"},
{3, nullptr, "GetDisconnectReasonForMonitor"},
{4, nullptr, "GetSecurityParameterForMonitor"},
{5, nullptr, "GetNetworkConfigForMonitor"},
- {100, nullptr, "InitializeMonitor"},
+ {100, &IMonitorService::InitializeMonitor, "InitializeMonitor"},
{101, nullptr, "FinalizeMonitor"},
};
// clang-format on
RegisterHandlers(functions);
}
+
+private:
+ void GetStateForMonitor(HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(state);
+ }
+
+ void InitializeMonitor(HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ state = State::Initialized;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ State state{State::None};
};
class LDNM final : public ServiceFramework<LDNM> {
@@ -731,14 +751,81 @@ public:
}
};
+class ISfMonitorService final : public ServiceFramework<ISfMonitorService> {
+public:
+ explicit ISfMonitorService(Core::System& system_)
+ : ServiceFramework{system_, "ISfMonitorService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &ISfMonitorService::Initialize, "Initialize"},
+ {288, &ISfMonitorService::GetGroupInfo, "GetGroupInfo"},
+ {320, nullptr, "GetLinkLevel"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Initialize(HLERequestContext& ctx) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(0);
+ }
+
+ void GetGroupInfo(HLERequestContext& ctx) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ struct GroupInfo {
+ std::array<u8, 0x200> info;
+ };
+
+ GroupInfo group_info{};
+
+ ctx.WriteBuffer(group_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+};
+
+class LP2PM final : public ServiceFramework<LP2PM> {
+public:
+ explicit LP2PM(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &LP2PM::CreateMonitorService, "CreateMonitorService"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void CreateMonitorService(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 reserved_input = rp.Pop<u64>();
+
+ LOG_INFO(Service_LDN, "called, reserved_input={}", reserved_input);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<ISfMonitorService>(system);
+ }
+};
+
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system));
server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system));
server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system));
+
server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system));
server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system));
+ server_manager->RegisterNamedService("lp2p:m", std::make_shared<LP2PM>(system));
+
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index 65c11a2f3..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,119 +32,122 @@ 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 default_miis{manager.GetDefault(source_flag)};
- if (default_miis.size() > 0) {
- ctx.WriteBuffer(SerializeArray(default_miis));
+ if (mii_count != 0) {
+ ctx.WriteBuffer(char_info_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(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(default_miis.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);
+ u32 mii_count{};
+ std::vector<CharInfo> char_info(output_size);
+ const auto result = manager->Get(metadata, char_info, mii_count, source_flag);
- const auto default_miis{manager.GetDefault(source_flag)};
-
- std::vector<CharInfo> values;
- for (const auto& element : default_miis) {
- 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>(default_miis.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);
CharInfo new_char_info{};
- const auto result{manager.UpdateLatest(&new_char_info, info, source_flag)};
- if (result != ResultSuccess) {
+ const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag);
+ if (result.IsFailure()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
@@ -148,12 +155,11 @@ private:
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
- rb.PushRaw<CharInfo>(new_char_info);
+ 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>()};
@@ -162,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) {
@@ -193,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) {
@@ -211,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);
@@ -231,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"},
@@ -298,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..0080b6705
--- /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 46125d473..dcfd6b2e2 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -1,697 +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();
}
-Result MiiManager::UpdateLatest(CharInfo* out_info, 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;
}
-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 45c2be3c8..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;
- Result UpdateLatest(CharInfo* out_info, 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;
- 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..08c6029df
--- /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 (index < MaxNameSize && 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..e90124af4
--- /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() + 3;
+ 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..970c748ca
--- /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 ? 6 : 7};
+ 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..e7a00deb3 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,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
return ResultWrongDeviceState;
}
- Service::Mii::MiiManager manager;
- const auto mii = manager.BuildDefault(0);
auto& settings = tag_data.settings;
if (tag_data.settings.settings.amiibo_initialized == 0) {
@@ -835,8 +838,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(register_info.mii_store_data);
+ tag_data.mii_extension.SetFromStoreData(register_info.mii_store_data);
tag_data.unknown = 0;
tag_data.unknown2 = {};
settings.country_code_id = 0;
@@ -868,17 +871,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();
}
@@ -1453,7 +1458,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 +1472,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/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 21b06d10b..22dc55a6d 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -545,6 +545,16 @@ void IGeneralService::IsAnyInternetRequestAccepted(HLERequestContext& ctx) {
}
}
+void IGeneralService::IsAnyForegroundRequestAccepted(HLERequestContext& ctx) {
+ const bool is_accepted{};
+
+ LOG_WARNING(Service_NIFM, "(STUBBED) called, is_accepted={}", is_accepted);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_accepted);
+}
+
IGeneralService::IGeneralService(Core::System& system_)
: ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
// clang-format off
@@ -569,7 +579,7 @@ IGeneralService::IGeneralService(Core::System& system_)
{19, nullptr, "SetEthernetCommunicationEnabled"},
{20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
{21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
- {22, nullptr, "IsAnyForegroundRequestAccepted"},
+ {22, &IGeneralService::IsAnyForegroundRequestAccepted, "IsAnyForegroundRequestAccepted"},
{23, nullptr, "PutToSleep"},
{24, nullptr, "WakeUp"},
{25, nullptr, "GetSsidListVersion"},
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index ae99c4695..b74b66438 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -35,6 +35,7 @@ private:
void GetInternetConnectionStatus(HLERequestContext& ctx);
void IsEthernetCommunicationEnabled(HLERequestContext& ctx);
void IsAnyInternetRequestAccepted(HLERequestContext& ctx);
+ void IsAnyForegroundRequestAccepted(HLERequestContext& ctx);
Network::RoomNetwork& network;
};
diff --git a/src/core/hle/service/ns/iplatform_service_manager.cpp b/src/core/hle/service/ns/iplatform_service_manager.cpp
index 6c2f5e70b..46268be95 100644
--- a/src/core/hle/service/ns/iplatform_service_manager.cpp
+++ b/src/core/hle/service/ns/iplatform_service_manager.cpp
@@ -144,7 +144,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
{3, &IPlatformServiceManager::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
{4, &IPlatformServiceManager::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
{5, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
- {6, nullptr, "GetSharedFontInOrderOfPriorityForSystem"},
+ {6, &IPlatformServiceManager::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriorityForSystem"},
{100, nullptr, "RequestApplicationFunctionAuthorization"},
{101, nullptr, "RequestApplicationFunctionAuthorizationByProcessId"},
{102, nullptr, "RequestApplicationFunctionAuthorizationByApplicationId"},
@@ -262,8 +262,17 @@ void IPlatformServiceManager::GetSharedMemoryNativeHandle(HLERequestContext& ctx
}
void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext& ctx) {
+ // The maximum number of elements that can be returned is 6. Regardless of the available fonts
+ // or buffer size.
+ constexpr std::size_t MaxElementCount = 6;
IPC::RequestParser rp{ctx};
const u64 language_code{rp.Pop<u64>()}; // TODO(ogniK): Find out what this is used for
+ const std::size_t font_codes_count =
+ std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(0));
+ const std::size_t font_offsets_count =
+ std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(1));
+ const std::size_t font_sizes_count =
+ std::min(MaxElementCount, ctx.GetWriteBufferNumElements<u32>(2));
LOG_DEBUG(Service_NS, "called, language_code={:X}", language_code);
IPC::ResponseBuilder rb{ctx, 4};
@@ -280,9 +289,9 @@ void IPlatformServiceManager::GetSharedFontInOrderOfPriority(HLERequestContext&
}
// Resize buffers if game requests smaller size output
- font_codes.resize(std::min(font_codes.size(), ctx.GetWriteBufferNumElements<u32>(0)));
- font_offsets.resize(std::min(font_offsets.size(), ctx.GetWriteBufferNumElements<u32>(1)));
- font_sizes.resize(std::min(font_sizes.size(), ctx.GetWriteBufferNumElements<u32>(2)));
+ font_codes.resize(std::min(font_codes.size(), font_codes_count));
+ font_offsets.resize(std::min(font_offsets.size(), font_offsets_count));
+ font_sizes.resize(std::min(font_sizes.size(), font_sizes_count));
ctx.WriteBuffer(font_codes, 0);
ctx.WriteBuffer(font_offsets, 1);
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 6e0baf0be..f9e0e272d 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -7,6 +7,7 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/vfs.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/errors.h"
@@ -502,8 +503,8 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
static const FunctionInfo functions[] = {
{11, nullptr, "CalculateApplicationOccupiedSize"},
{43, nullptr, "CheckSdCardMountStatus"},
- {47, nullptr, "GetTotalSpaceSize"},
- {48, nullptr, "GetFreeSpaceSize"},
+ {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"},
+ {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"},
{600, nullptr, "CountApplicationContentMeta"},
{601, nullptr, "ListApplicationContentMetaStatus"},
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
@@ -516,6 +517,28 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
IContentManagementInterface::~IContentManagementInterface() = default;
+void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<FileSys::StorageId>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(system.GetFileSystemController().GetTotalSpaceSize(storage));
+}
+
+void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<FileSys::StorageId>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(system.GetFileSystemController().GetFreeSpaceSize(storage));
+}
+
IDocumentInterface::IDocumentInterface(Core::System& system_)
: ServiceFramework{system_, "IDocumentInterface"} {
// clang-format off
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 175dad780..34d2a45dc 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -48,6 +48,10 @@ class IContentManagementInterface final : public ServiceFramework<IContentManage
public:
explicit IContentManagementInterface(Core::System& system_);
~IContentManagementInterface() override;
+
+private:
+ void GetTotalSpaceSize(HLERequestContext& ctx);
+ void GetFreeSpaceSize(HLERequestContext& ctx);
};
class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
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/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 40c65b430..4c0cc71cd 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -45,13 +45,6 @@ public:
IsSharedMemMapped = 6
};
-private:
- /// Id to use for the next handle that is created.
- u32 next_handle = 0;
-
- /// Id to use for the next object that is created.
- u32 next_id = 0;
-
struct IocCreateParams {
// Input
u32_le size{};
@@ -113,6 +106,13 @@ private:
NvResult IocParam(std::span<const u8> input, std::span<u8> output);
NvResult IocFree(std::span<const u8> input, std::span<u8> output);
+private:
+ /// Id to use for the next handle that is created.
+ u32 next_handle = 0;
+
+ /// Id to use for the next object that is created.
+ u32 next_id = 0;
+
NvCore::Container& container;
NvCore::NvMap& file;
};
diff --git a/src/core/hle/service/nvnflinger/buffer_item.h b/src/core/hle/service/nvnflinger/buffer_item.h
index 7fd808f54..3da8cc3aa 100644
--- a/src/core/hle/service/nvnflinger/buffer_item.h
+++ b/src/core/hle/service/nvnflinger/buffer_item.h
@@ -15,7 +15,7 @@
namespace Service::android {
-class GraphicBuffer;
+struct GraphicBuffer;
class BufferItem final {
public:
diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h
index d25bca049..d8c9dec3b 100644
--- a/src/core/hle/service/nvnflinger/buffer_slot.h
+++ b/src/core/hle/service/nvnflinger/buffer_slot.h
@@ -13,7 +13,7 @@
namespace Service::android {
-class GraphicBuffer;
+struct GraphicBuffer;
enum class BufferState : u32 {
Free = 0,
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
new file mode 100644
index 000000000..469a53244
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
@@ -0,0 +1,351 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <random>
+
+#include "core/core.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_system_resource.h"
+#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
+#include "core/hle/service/nvnflinger/pixel_format.h"
+#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
+#include "core/hle/service/vi/layer/vi_layer.h"
+#include "core/hle/service/vi/vi_results.h"
+
+namespace Service::Nvnflinger {
+
+namespace {
+
+Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
+ std::unique_ptr<Kernel::KPageGroup>* out_page_group,
+ Core::System& system, u32 size) {
+ using Core::Memory::YUZU_PAGESIZE;
+
+ // Allocate memory for the system shared buffer.
+ // FIXME: Because the gmmu can only point to cpu addresses, we need
+ // to map this in the application space to allow it to be used.
+ // FIXME: Add proper smmu emulation.
+ // FIXME: This memory belongs to vi's .data section.
+ auto& kernel = system.Kernel();
+ auto* process = system.ApplicationProcess();
+ auto& page_table = process->GetPageTable();
+
+ // Hold a temporary page group reference while we try to map it.
+ auto pg = std::make_unique<Kernel::KPageGroup>(
+ kernel, std::addressof(kernel.GetSystemSystemResource().GetBlockInfoManager()));
+
+ // Allocate memory from secure pool.
+ R_TRY(kernel.MemoryManager().AllocateAndOpen(
+ pg.get(), size / YUZU_PAGESIZE,
+ Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure,
+ Kernel::KMemoryManager::Direction::FromBack)));
+
+ // Get bounds of where mapping is possible.
+ const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart());
+ const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE;
+ const auto state = Kernel::KMemoryState::Io;
+ const auto perm = Kernel::KMemoryPermission::UserReadWrite;
+ std::mt19937_64 rng{process->GetRandomEntropy(0)};
+
+ // Retry up to 64 times to map into alias code range.
+ Result res = ResultSuccess;
+ int i;
+ for (i = 0; i < 64; i++) {
+ *out_map_address = alias_code_begin + ((rng() % alias_code_size) * YUZU_PAGESIZE);
+ res = page_table.MapPageGroup(*out_map_address, *pg, state, perm);
+ if (R_SUCCEEDED(res)) {
+ break;
+ }
+ }
+
+ // Return failure, if necessary
+ R_UNLESS(i < 64, res);
+
+ // Return the mapped page group.
+ *out_page_group = std::move(pg);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+template <typename T>
+std::span<u8> SerializeIoc(T& params) {
+ return std::span(reinterpret_cast<u8*>(std::addressof(params)), sizeof(T));
+}
+
+Result CreateNvMapHandle(u32* out_nv_map_handle, Nvidia::Devices::nvmap& nvmap, u32 size) {
+ // Create a handle.
+ Nvidia::Devices::nvmap::IocCreateParams create_in_params{
+ .size = size,
+ .handle = 0,
+ };
+ Nvidia::Devices::nvmap::IocCreateParams create_out_params{};
+ R_UNLESS(nvmap.IocCreate(SerializeIoc(create_in_params), SerializeIoc(create_out_params)) ==
+ Nvidia::NvResult::Success,
+ VI::ResultOperationFailed);
+
+ // Assign the output handle.
+ *out_nv_map_handle = create_out_params.handle;
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result FreeNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle) {
+ // Free the handle.
+ Nvidia::Devices::nvmap::IocFreeParams free_in_params{
+ .handle = handle,
+ };
+ Nvidia::Devices::nvmap::IocFreeParams free_out_params{};
+ R_UNLESS(nvmap.IocFree(SerializeIoc(free_in_params), SerializeIoc(free_out_params)) ==
+ Nvidia::NvResult::Success,
+ VI::ResultOperationFailed);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result AllocNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle, Common::ProcessAddress buffer,
+ u32 size) {
+ // Assign the allocated memory to the handle.
+ Nvidia::Devices::nvmap::IocAllocParams alloc_in_params{
+ .handle = handle,
+ .heap_mask = 0,
+ .flags = {},
+ .align = 0,
+ .kind = 0,
+ .address = GetInteger(buffer),
+ };
+ Nvidia::Devices::nvmap::IocAllocParams alloc_out_params{};
+ R_UNLESS(nvmap.IocAlloc(SerializeIoc(alloc_in_params), SerializeIoc(alloc_out_params)) ==
+ Nvidia::NvResult::Success,
+ VI::ResultOperationFailed);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv,
+ Common::ProcessAddress buffer, u32 size) {
+ // Get the nvmap device.
+ auto nvmap_fd = nvdrv.Open("/dev/nvmap");
+ auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd);
+ ASSERT(nvmap != nullptr);
+
+ // Create a handle.
+ R_TRY(CreateNvMapHandle(out_handle, *nvmap, size));
+
+ // Ensure we maintain a clean state on failure.
+ ON_RESULT_FAILURE {
+ ASSERT(R_SUCCEEDED(FreeNvMapHandle(*nvmap, *out_handle)));
+ };
+
+ // Assign the allocated memory to the handle.
+ R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size));
+}
+
+constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888;
+constexpr u32 SharedBufferBlockLinearBpp = 4;
+
+constexpr u32 SharedBufferBlockLinearWidth = 1280;
+constexpr u32 SharedBufferBlockLinearHeight = 768;
+constexpr u32 SharedBufferBlockLinearStride =
+ SharedBufferBlockLinearWidth * SharedBufferBlockLinearBpp;
+constexpr u32 SharedBufferNumSlots = 7;
+
+constexpr u32 SharedBufferWidth = 1280;
+constexpr u32 SharedBufferHeight = 720;
+constexpr u32 SharedBufferAsync = false;
+
+constexpr u32 SharedBufferSlotSize =
+ SharedBufferBlockLinearWidth * SharedBufferBlockLinearHeight * SharedBufferBlockLinearBpp;
+constexpr u32 SharedBufferSize = SharedBufferSlotSize * SharedBufferNumSlots;
+
+constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] {
+ SharedMemoryPoolLayout layout{};
+ layout.num_slots = SharedBufferNumSlots;
+
+ for (u32 i = 0; i < SharedBufferNumSlots; i++) {
+ layout.slots[i].buffer_offset = i * SharedBufferSlotSize;
+ layout.slots[i].size = SharedBufferSlotSize;
+ layout.slots[i].width = SharedBufferWidth;
+ layout.slots[i].height = SharedBufferHeight;
+ }
+
+ return layout;
+}();
+
+void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) {
+ auto buffer = std::make_shared<android::GraphicBuffer>();
+ buffer->width = SharedBufferWidth;
+ buffer->height = SharedBufferHeight;
+ buffer->stride = SharedBufferBlockLinearStride;
+ buffer->format = SharedBufferBlockLinearFormat;
+ buffer->buffer_id = handle;
+ buffer->offset = slot * SharedBufferSlotSize;
+ ASSERT(producer.SetPreallocatedBuffer(slot, buffer) == android::Status::NoError);
+}
+
+} // namespace
+
+FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
+ std::shared_ptr<Nvidia::Module> nvdrv)
+ : m_system(system), m_flinger(flinger), m_nvdrv(std::move(nvdrv)) {}
+
+FbShareBufferManager::~FbShareBufferManager() = default;
+
+Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) {
+ std::scoped_lock lk{m_guard};
+
+ // Ensure we have not already created a buffer.
+ R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed);
+
+ // Allocate memory and space for the shared buffer.
+ Common::ProcessAddress map_address;
+ R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address),
+ std::addressof(m_buffer_page_group), m_system,
+ SharedBufferSize));
+
+ // Create an nvmap handle for the buffer and assign the memory to it.
+ R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, map_address,
+ SharedBufferSize));
+
+ // Record the display id.
+ m_display_id = display_id;
+
+ // Create a layer for the display.
+ m_layer_id = m_flinger.CreateLayer(m_display_id).value();
+
+ // Set up the buffer.
+ m_buffer_id = m_next_buffer_id++;
+
+ // Get the layer.
+ VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id);
+ ASSERT(layer != nullptr);
+
+ // Get the producer and set preallocated buffers.
+ auto& producer = layer->GetBufferQueue();
+ MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle);
+ MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle);
+
+ // Assign outputs.
+ *out_buffer_id = m_buffer_id;
+ *out_layer_id = m_layer_id;
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
+ s32* out_nvmap_handle,
+ SharedMemoryPoolLayout* out_pool_layout,
+ u64 buffer_id,
+ u64 applet_resource_user_id) {
+ std::scoped_lock lk{m_guard};
+
+ R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
+ R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound);
+
+ *out_pool_layout = SharedBufferPoolLayout;
+ *out_buffer_size = SharedBufferSize;
+ *out_nvmap_handle = m_buffer_nvmap_handle;
+
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) {
+ // Ensure the layer id is valid.
+ R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound);
+
+ // Get the layer.
+ VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+
+ // We succeeded.
+ *out_layer = layer;
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence,
+ std::array<s32, 4>& out_slot_indexes,
+ s64* out_target_slot, u64 layer_id) {
+ std::scoped_lock lk{m_guard};
+
+ // Get the layer.
+ VI::Layer* layer;
+ R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
+
+ // Get the producer.
+ auto& producer = layer->GetBufferQueue();
+
+ // Get the next buffer from the producer.
+ s32 slot;
+ R_UNLESS(producer.DequeueBuffer(std::addressof(slot), out_fence, SharedBufferAsync != 0,
+ SharedBufferWidth, SharedBufferHeight,
+ SharedBufferBlockLinearFormat, 0) == android::Status::NoError,
+ VI::ResultOperationFailed);
+
+ // Assign remaining outputs.
+ *out_target_slot = slot;
+ out_slot_indexes = {0, 1, -1, -1};
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
+ Common::Rectangle<s32> crop_region,
+ u32 transform, s32 swap_interval,
+ u64 layer_id, s64 slot) {
+ std::scoped_lock lk{m_guard};
+
+ // Get the layer.
+ VI::Layer* layer;
+ R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
+
+ // Get the producer.
+ auto& producer = layer->GetBufferQueue();
+
+ // Request to queue the buffer.
+ std::shared_ptr<android::GraphicBuffer> buffer;
+ R_UNLESS(producer.RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) ==
+ android::Status::NoError,
+ VI::ResultOperationFailed);
+
+ // Queue the buffer to the producer.
+ android::QueueBufferInput input{};
+ android::QueueBufferOutput output{};
+ input.crop = crop_region;
+ input.fence = fence;
+ input.transform = static_cast<android::NativeWindowTransform>(transform);
+ input.swap_interval = swap_interval;
+ R_UNLESS(producer.QueueBuffer(static_cast<s32>(slot), input, std::addressof(output)) ==
+ android::Status::NoError,
+ VI::ResultOperationFailed);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event,
+ u64 layer_id) {
+ std::scoped_lock lk{m_guard};
+
+ // Get the layer.
+ VI::Layer* layer;
+ R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
+
+ // Get the producer.
+ auto& producer = layer->GetBufferQueue();
+
+ // Set the event.
+ *out_event = std::addressof(producer.GetNativeHandle());
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
new file mode 100644
index 000000000..c809c01b4
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/math_util.h"
+#include "core/hle/service/nvnflinger/nvnflinger.h"
+#include "core/hle/service/nvnflinger/ui/fence.h"
+
+namespace Kernel {
+class KPageGroup;
+}
+
+namespace Service::Nvnflinger {
+
+struct SharedMemorySlot {
+ u64 buffer_offset;
+ u64 size;
+ s32 width;
+ s32 height;
+};
+static_assert(sizeof(SharedMemorySlot) == 0x18, "SharedMemorySlot has wrong size");
+
+struct SharedMemoryPoolLayout {
+ s32 num_slots;
+ std::array<SharedMemorySlot, 0x10> slots;
+};
+static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
+
+class FbShareBufferManager final {
+public:
+ explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
+ std::shared_ptr<Nvidia::Module> nvdrv);
+ ~FbShareBufferManager();
+
+ Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id);
+ Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
+ SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
+ u64 applet_resource_user_id);
+ Result AcquireSharedFrameBuffer(android::Fence* out_fence, std::array<s32, 4>& out_slots,
+ s64* out_target_slot, u64 layer_id);
+ Result PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle<s32> crop_region,
+ u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
+ Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
+
+private:
+ Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
+
+private:
+ u64 m_next_buffer_id = 1;
+ u64 m_display_id = 0;
+ u64 m_buffer_id = 0;
+ u64 m_layer_id = 0;
+ u32 m_buffer_nvmap_handle = 0;
+ SharedMemoryPoolLayout m_pool_layout = {};
+
+ std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
+
+ std::mutex m_guard;
+ Core::System& m_system;
+ Nvnflinger& m_flinger;
+ std::shared_ptr<Nvidia::Module> m_nvdrv;
+};
+
+} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
index 21d7b31f3..5d7cff7d3 100644
--- a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
+++ b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
@@ -19,6 +19,7 @@ class InputParcel;
#pragma pack(push, 1)
struct QueueBufferInput final {
explicit QueueBufferInput(InputParcel& parcel);
+ explicit QueueBufferInput() = default;
void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_,
NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_,
@@ -34,7 +35,6 @@ struct QueueBufferInput final {
*fence_ = fence;
}
-private:
s64 timestamp{};
s32 is_auto_timestamp{};
Common::Rectangle<s32> crop{};
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 21f31f7a0..a07c621d9 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -17,6 +17,7 @@
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
+#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
@@ -331,4 +332,14 @@ s64 Nvnflinger::GetNextTicks() const {
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
}
+FbShareBufferManager& Nvnflinger::GetSystemBufferManager() {
+ const auto lock_guard = Lock();
+
+ if (!system_buffer_manager) {
+ system_buffer_manager = std::make_unique<FbShareBufferManager>(system, *this, nvdrv);
+ }
+
+ return *system_buffer_manager;
+}
+
} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index f478c2bc6..14c783582 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -45,6 +45,9 @@ class BufferQueueProducer;
namespace Service::Nvnflinger {
+class FbShareBufferManager;
+class HosBinderDriverServer;
+
class Nvnflinger final {
public:
explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
@@ -90,12 +93,16 @@ public:
[[nodiscard]] s64 GetNextTicks() const;
+ FbShareBufferManager& GetSystemBufferManager();
+
private:
struct Layer {
std::unique_ptr<android::BufferQueueCore> core;
std::unique_ptr<android::BufferQueueProducer> producer;
};
+ friend class FbShareBufferManager;
+
private:
[[nodiscard]] std::unique_lock<std::mutex> Lock() const {
return std::unique_lock{*guard};
@@ -140,6 +147,8 @@ private:
std::shared_ptr<Core::Timing::EventType> multi_composition_event;
std::shared_ptr<Core::Timing::EventType> single_composition_event;
+ std::unique_ptr<FbShareBufferManager> system_buffer_manager;
+
std::shared_ptr<std::mutex> guard;
Core::System& system;
diff --git a/src/core/hle/service/nvnflinger/ui/fence.h b/src/core/hle/service/nvnflinger/ui/fence.h
index 536e8156d..177aed758 100644
--- a/src/core/hle/service/nvnflinger/ui/fence.h
+++ b/src/core/hle/service/nvnflinger/ui/fence.h
@@ -20,6 +20,9 @@ public:
static constexpr Fence NoFence() {
Fence fence;
fence.fences[0].id = -1;
+ fence.fences[1].id = -1;
+ fence.fences[2].id = -1;
+ fence.fences[3].id = -1;
return fence;
}
diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
index 75d1705a8..3eac5cedd 100644
--- a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
+++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
@@ -12,8 +12,7 @@
namespace Service::android {
-class GraphicBuffer final {
-public:
+struct GraphicBuffer final {
constexpr GraphicBuffer() = default;
constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
@@ -77,7 +76,6 @@ public:
return false;
}
-private:
u32 magic{};
s32 width{};
s32 height{};
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index 5db1703d1..938330dd0 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.cpp
@@ -33,7 +33,7 @@ public:
{1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"},
{1002, nullptr, "ConfirmLaunchApplicationPermission"},
{1003, nullptr, "ConfirmResumeApplicationPermission"},
- {1004, nullptr, "ConfirmSnsPostPermission"},
+ {1004, &IParentalControlService::ConfirmSnsPostPermission, "ConfirmSnsPostPermission"},
{1005, nullptr, "ConfirmSystemSettingsPermission"},
{1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"},
{1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
@@ -236,6 +236,13 @@ private:
states.free_communication = true;
}
+ void ConfirmSnsPostPermission(HLERequestContext& ctx) {
+ LOG_WARNING(Service_PCTL, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(Error::ResultNoFreeCommunication);
+ }
+
void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
const bool is_temporary_unlocked = false;
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/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 bac21752a..491b76d48 100644
--- a/src/core/hle/service/sockets/nsd.cpp
+++ b/src/core/hle/service/sockets/nsd.cpp
@@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 {
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[] = {
@@ -101,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/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index 2cba9e5c9..6c8427b0d 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -139,7 +139,6 @@ private:
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;
Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
@@ -174,11 +173,7 @@ private:
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) {
@@ -208,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;
diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp
index b2dd37cd4..5714e6f3c 100644
--- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp
@@ -167,9 +167,8 @@ public:
}
~SSLConnectionBackendOpenSSL() {
- // these are null-tolerant:
+ // this is null-tolerant:
SSL_free(ssl);
- BIO_free(bio);
}
static void KeyLogCallback(const SSL* ssl, const char* line) {
diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp
index bda12b761..212057cfc 100644
--- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp
+++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp
@@ -31,9 +31,9 @@ CredHandle cred_handle;
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),
@@ -478,7 +477,8 @@ public:
return ResultInternalError;
}
PCCERT_CONTEXT some_cert = nullptr;
- while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) {
+ 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);
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 6bb02393c..b1bfb9898 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -20,9 +20,12 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvnflinger/binder.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/parcel.h"
@@ -131,8 +134,9 @@ private:
class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
public:
- explicit ISystemDisplayService(Core::System& system_)
- : ServiceFramework{system_, "ISystemDisplayService"} {
+ explicit ISystemDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_)
+ : ServiceFramework{system_, "ISystemDisplayService"}, nvnflinger{nvnflinger_} {
+ // clang-format off
static const FunctionInfo functions[] = {
{1200, nullptr, "GetZOrderCountMin"},
{1202, nullptr, "GetZOrderCountMax"},
@@ -170,22 +174,126 @@ public:
{3217, nullptr, "SetDisplayCmuLuma"},
{3218, nullptr, "SetDisplayCrcMode"},
{6013, nullptr, "GetLayerPresentationSubmissionTimestamps"},
- {8225, nullptr, "GetSharedBufferMemoryHandleId"},
- {8250, nullptr, "OpenSharedLayer"},
+ {8225, &ISystemDisplayService::GetSharedBufferMemoryHandleId, "GetSharedBufferMemoryHandleId"},
+ {8250, &ISystemDisplayService::OpenSharedLayer, "OpenSharedLayer"},
{8251, nullptr, "CloseSharedLayer"},
- {8252, nullptr, "ConnectSharedLayer"},
+ {8252, &ISystemDisplayService::ConnectSharedLayer, "ConnectSharedLayer"},
{8253, nullptr, "DisconnectSharedLayer"},
- {8254, nullptr, "AcquireSharedFrameBuffer"},
- {8255, nullptr, "PresentSharedFrameBuffer"},
- {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"},
+ {8254, &ISystemDisplayService::AcquireSharedFrameBuffer, "AcquireSharedFrameBuffer"},
+ {8255, &ISystemDisplayService::PresentSharedFrameBuffer, "PresentSharedFrameBuffer"},
+ {8256, &ISystemDisplayService::GetSharedFrameBufferAcquirableEvent, "GetSharedFrameBufferAcquirableEvent"},
{8257, nullptr, "FillSharedFrameBufferColor"},
{8258, nullptr, "CancelSharedFrameBuffer"},
{9000, nullptr, "GetDp2hdmiController"},
};
+ // clang-format on
RegisterHandlers(functions);
}
private:
+ void GetSharedBufferMemoryHandleId(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 buffer_id = rp.PopRaw<u64>();
+
+ LOG_INFO(Service_VI, "called. buffer_id={:#x}", buffer_id);
+
+ struct OutputParameters {
+ s32 nvmap_handle;
+ u64 size;
+ };
+
+ OutputParameters out{};
+ Nvnflinger::SharedMemoryPoolLayout layout{};
+ const auto result = nvnflinger.GetSystemBufferManager().GetSharedBufferMemoryHandleId(
+ &out.size, &out.nvmap_handle, &layout, buffer_id, 0);
+
+ ctx.WriteBuffer(&layout, sizeof(layout));
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.PushRaw(out);
+ }
+
+ void OpenSharedLayer(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 layer_id = rp.PopRaw<u64>();
+
+ LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void ConnectSharedLayer(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 layer_id = rp.PopRaw<u64>();
+
+ LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void GetSharedFrameBufferAcquirableEvent(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ IPC::RequestParser rp{ctx};
+ const u64 layer_id = rp.PopRaw<u64>();
+
+ Kernel::KReadableEvent* event{};
+ const auto result = nvnflinger.GetSystemBufferManager().GetSharedFrameBufferAcquirableEvent(
+ &event, layer_id);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(result);
+ rb.PushCopyObjects(event);
+ }
+
+ void AcquireSharedFrameBuffer(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ IPC::RequestParser rp{ctx};
+ const u64 layer_id = rp.PopRaw<u64>();
+
+ struct OutputParameters {
+ android::Fence fence;
+ std::array<s32, 4> slots;
+ s64 target_slot;
+ };
+ static_assert(sizeof(OutputParameters) == 0x40, "OutputParameters has wrong size");
+
+ OutputParameters out{};
+ const auto result = nvnflinger.GetSystemBufferManager().AcquireSharedFrameBuffer(
+ &out.fence, out.slots, &out.target_slot, layer_id);
+
+ IPC::ResponseBuilder rb{ctx, 18};
+ rb.Push(result);
+ rb.PushRaw(out);
+ }
+
+ void PresentSharedFrameBuffer(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ struct InputParameters {
+ android::Fence fence;
+ Common::Rectangle<s32> crop_region;
+ u32 window_transform;
+ s32 swap_interval;
+ u64 layer_id;
+ s64 surface_id;
+ };
+ static_assert(sizeof(InputParameters) == 0x50, "InputParameters has wrong size");
+
+ IPC::RequestParser rp{ctx};
+ auto input = rp.PopRaw<InputParameters>();
+
+ const auto result = nvnflinger.GetSystemBufferManager().PresentSharedFrameBuffer(
+ input.fence, input.crop_region, input.window_transform, input.swap_interval,
+ input.layer_id, input.surface_id);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
void SetLayerZ(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 layer_id = rp.Pop<u64>();
@@ -217,7 +325,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 {
@@ -228,6 +336,9 @@ private:
rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games.
rb.Push<u32>(0);
}
+
+private:
+ Nvnflinger::Nvnflinger& nvnflinger;
};
class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
@@ -453,7 +564,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<ISystemDisplayService>(system);
+ rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger);
}
void GetManagerDisplayService(HLERequestContext& ctx) {
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index 5d28300e6..a983f23ea 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -48,15 +48,32 @@ enum class CallType {
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;
@@ -157,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;
@@ -490,6 +540,14 @@ NetworkInstance::~NetworkInstance() {
Finalize();
}
+void CancelPendingSocketOperations() {
+ InterruptSocketOperations();
+}
+
+void RestartSocketOperations() {
+ AcknowledgeInterrupt();
+}
+
std::optional<IPv4Address> GetHostIPv4Address() {
const auto network_interface = Network::GetSelectedNetworkInterface();
if (!network_interface.has_value()) {
@@ -560,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; }));
@@ -627,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) {
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
index c7e20ae34..b7b7d773a 100644
--- a/src/core/internal_network/network.h
+++ b/src/core/internal_network/network.h
@@ -96,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/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index e04ad19db..5c36b71e5 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())) {
@@ -118,7 +118,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
return {ResultStatus::ErrorMissingNPDM, {}};
}
- const ResultStatus result2 = metadata.Load(npdm);
+ const ResultStatus result2 = metadata.Reload(npdm);
if (result2 != ResultStatus::Success) {
return {result2, {}};
}
@@ -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 07c65dc1a..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.",
@@ -175,6 +175,8 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
"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 721eb8e8c..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>
@@ -132,6 +133,8 @@ enum class ResultStatus : u16 {
ErrorBLZDecompressionFailed,
ErrorBadINIHeader,
ErrorINITooManyKIPs,
+ ErrorIntegrityVerificationNotImplemented,
+ ErrorIntegrityVerificationFailed,
};
std::string GetResultStatusString(ResultStatus status);
@@ -170,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
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 09d40e695..4feb6968a 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -3,6 +3,8 @@
#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"
@@ -12,6 +14,7 @@
#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 {
@@ -80,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;
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index cf356ce63..96779e27f 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -39,6 +39,8 @@ 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;
ResultStatus ReadProgramId(u64& out_program_id) 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 f9b2549a3..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,6 +118,42 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
return result;
}
+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;
+}
+
ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
return secondary_loader->ReadRomFS(out_file);
}
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 79df4586a..7ce436c67 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -45,6 +45,8 @@ 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;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 3a76bc788..12d72c380 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -85,6 +85,40 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S
return result;
}
+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;
+}
+
ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
return nca_loader->ReadRomFS(out_file);
}
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index ff05e6f62..b02e136d3 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -45,6 +45,8 @@ 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;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
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 62b3f6636..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"
@@ -275,7 +276,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
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/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 808b21069..77db60e92 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -338,6 +338,7 @@ void UDPClient::StartCommunication(std::size_t client, const std::string& host,
for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
PreSetController(identifier);
+ PreSetMotion(identifier, 0);
}
}
diff --git a/src/input_common/drivers/virtual_gamepad.h b/src/input_common/drivers/virtual_gamepad.h
index dfbc45a28..3a40e3fd3 100644
--- a/src/input_common/drivers/virtual_gamepad.h
+++ b/src/input_common/drivers/virtual_gamepad.h
@@ -67,7 +67,7 @@ public:
* @param player_index the player number that will take this action
* @param delta_timestamp time passed since last reading
* @param gyro_x,gyro_y,gyro_z the gyro sensor readings
- * @param accel_x,accel_y,accel_z the acelerometer reading
+ * @param accel_x,accel_y,accel_z the accelerometer reading
*/
void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
float gyro_z, float accel_x, float accel_y, float accel_z);
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
index 90fcd17f6..b94567f82 100644
--- a/src/input_common/helpers/joycon_protocol/generic_functions.h
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -55,7 +55,7 @@ public:
/**
* Configures the motion sensor with the specified parameters
- * @param gsen gyroscope sensor sensitvity in degrees per second
+ * @param gsen gyroscope sensor sensitivity in degrees per second
* @param gfrec gyroscope sensor frequency in hertz
* @param asen accelerometer sensitivity in G force
* @param afrec accelerometer frequency in hertz
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_composite.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
index d508ee567..4e8ba4ae6 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
@@ -55,7 +55,7 @@ void CompositeInsert(EmitContext& ctx, IR::Inst& inst, Register composite, Objec
"MOV.{} {}.{},{};",
type, ret, composite, type, ret, swizzle, object);
} else {
- // The return value is alised so we can just insert the object, it doesn't matter if it's
+ // The return value is aliased so we can just insert the object, it doesn't matter if it's
// aliased
ctx.Add("MOV.{} {}.{},{};", type, ret, swizzle, object);
}
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
index 3ad668a47..d9872ecc2 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
@@ -558,7 +558,7 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
if (multi_component) {
if (info.num_derivates >= 3) {
const auto offset_vec{ctx.var_alloc.Consume(offset)};
- ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yz, {}.y));", texel, texture,
+ ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yw, {}.y));", texel, texture,
coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec);
return;
}
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/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/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 9b13ccbab..cf9266d54 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -95,6 +95,12 @@ add_library(video_core STATIC
memory_manager.h
precompiled_headers.h
pte_kind.h
+ query_cache/bank_base.h
+ query_cache/query_base.h
+ query_cache/query_cache_base.h
+ query_cache/query_cache.h
+ query_cache/query_stream.h
+ query_cache/types.h
query_cache.h
rasterizer_accelerated.cpp
rasterizer_accelerated.h
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index f0f450edb..9b2698fad 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -272,13 +272,19 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
if (!cpu_addr) {
return {&slot_buffers[NULL_BUFFER_ID], 0};
}
- const BufferId buffer_id = FindBuffer(*cpu_addr, size);
+ return ObtainCPUBuffer(*cpu_addr, size, sync_info, post_op);
+}
+
+template <class P>
+std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
+ VAddr cpu_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) {
+ const BufferId buffer_id = FindBuffer(cpu_addr, size);
Buffer& buffer = slot_buffers[buffer_id];
// synchronize op
switch (sync_info) {
case ObtainBufferSynchronize::FullSynchronize:
- SynchronizeBuffer(buffer, *cpu_addr, size);
+ SynchronizeBuffer(buffer, cpu_addr, size);
break;
default:
break;
@@ -286,18 +292,21 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
switch (post_op) {
case ObtainBufferOperation::MarkAsWritten:
- MarkWrittenBuffer(buffer_id, *cpu_addr, size);
+ 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:
break;
}
- return {&buffer, buffer.Offset(*cpu_addr)};
+ return {&buffer, buffer.Offset(cpu_addr)};
}
template <class P>
@@ -535,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
it++;
}
- boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
+ boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads;
u64 total_size_bytes = 0;
u64 largest_copy = 0;
for (const IntervalSet& intervals : committed_ranges) {
@@ -905,6 +914,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
const u32 offset = buffer.Offset(binding.cpu_addr);
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
+
+ if (is_written) {
+ MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
+ }
+
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
++binding_index;
@@ -922,6 +936,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.cpu_addr, size);
+ const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0;
+ if (is_written) {
+ MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
+ }
+
const u32 offset = buffer.Offset(binding.cpu_addr);
const PixelFormat format = binding.format;
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -953,6 +972,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.cpu_addr, size);
+ MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
+
const u32 offset = buffer.Offset(binding.cpu_addr);
host_bindings.buffers.push_back(&buffer);
host_bindings.offsets.push_back(offset);
@@ -1002,6 +1023,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
const u32 offset = buffer.Offset(binding.cpu_addr);
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
+
+ if (is_written) {
+ MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
+ }
+
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
++binding_index;
@@ -1019,6 +1045,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.cpu_addr, size);
+ const bool is_written =
+ ((channel_state->written_compute_texture_buffers >> index) & 1) != 0;
+ if (is_written) {
+ MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
+ }
+
const u32 offset = buffer.Offset(binding.cpu_addr);
const PixelFormat format = binding.format;
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -1159,6 +1191,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),
@@ -1187,16 +1224,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
template <class P>
void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
- const u32 written_mask = channel_state->written_storage_buffers[stage];
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
// Resolve buffer
Binding& binding = channel_state->storage_buffers[stage][index];
const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
binding.buffer_id = buffer_id;
- // Mark buffer as written if needed
- if (((written_mask >> index) & 1) != 0) {
- MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
- }
});
}
@@ -1205,10 +1237,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
Binding& binding = channel_state->texture_buffers[stage][index];
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
- // Mark buffer as written if needed
- if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
- MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
- }
});
}
@@ -1238,7 +1266,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
.size = size,
.buffer_id = buffer_id,
};
- MarkWrittenBuffer(buffer_id, *cpu_addr, size);
}
template <class P>
@@ -1265,10 +1292,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
// Resolve buffer
Binding& binding = channel_state->compute_storage_buffers[index];
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
- // Mark as written if needed
- if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
- MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
- }
});
}
@@ -1277,18 +1300,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
Binding& binding = channel_state->compute_texture_buffers[index];
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
- // Mark as written if needed
- if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
- MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
- }
});
}
template <class P>
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
- if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
- SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
- }
memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
const IntervalType base_interval{cpu_addr, cpu_addr + size};
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index fdc26688d..eed267361 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -299,6 +299,10 @@ public:
[[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size,
ObtainBufferSynchronize sync_info,
ObtainBufferOperation post_op);
+
+ [[nodiscard]] std::pair<Buffer*, u32> ObtainCPUBuffer(VAddr gpu_addr, u32 size,
+ ObtainBufferSynchronize sync_info,
+ ObtainBufferOperation post_op);
void FlushCachedWrites();
/// Return true when there are uncommitted buffers to be downloaded
@@ -339,6 +343,14 @@ public:
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
+ template <typename Func>
+ void BufferOperations(Func&& func) {
+ do {
+ channel_state->has_deleted_buffers = false;
+ func();
+ } while (channel_state->has_deleted_buffers);
+ }
+
std::recursive_mutex mutex;
Runtime& runtime;
diff --git a/src/video_core/control/channel_state_cache.h b/src/video_core/control/channel_state_cache.h
index 46bc9e322..5574e1fba 100644
--- a/src/video_core/control/channel_state_cache.h
+++ b/src/video_core/control/channel_state_cache.h
@@ -51,7 +51,7 @@ public:
virtual void CreateChannel(Tegra::Control::ChannelState& channel);
/// Bind a channel for execution.
- void BindToChannel(s32 id);
+ virtual void BindToChannel(s32 id);
/// Erase channel's state.
void EraseChannel(s32 id);
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..e46a8fa5c 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);
@@ -159,7 +161,7 @@ private:
u32 method_count; ///< Current method count
u32 length_pending; ///< Large NI command length pending
GPUVAddr dma_get; ///< Currently read segment
- u64 dma_word_offset; ///< Current word ofset from address
+ u64 dma_word_offset; ///< Current word offset from address
bool non_incrementing; ///< Current command's NI flag
bool is_last_call;
};
@@ -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/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp
index f34090791..d77ff455b 100644
--- a/src/video_core/engines/draw_manager.cpp
+++ b/src/video_core/engines/draw_manager.cpp
@@ -48,8 +48,14 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
SetInlineIndexBuffer(regs.inline_index_4x8.index3);
break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
+ DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
+ regs.vertex_array_instance_first.start.Value(),
+ regs.vertex_array_instance_first.count.Value(), false);
+ break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
- LOG_WARNING(HW_GPU, "(STUBBED) called");
+ DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
+ regs.vertex_array_instance_subsequent.start.Value(),
+ regs.vertex_array_instance_subsequent.count.Value(), true);
break;
}
case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
@@ -84,6 +90,22 @@ void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 ve
ProcessDraw(false, num_instances);
}
+void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
+ bool subsequent) {
+ draw_state.topology = topology;
+ draw_state.vertex_buffer.first = vertex_first;
+ draw_state.vertex_buffer.count = vertex_count;
+
+ if (!subsequent) {
+ draw_state.instance_count = 1;
+ }
+
+ draw_state.base_instance = draw_state.instance_count - 1;
+ draw_state.draw_mode = DrawMode::Instance;
+ draw_state.instance_count++;
+ ProcessDraw(false, 1);
+}
+
void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
u32 base_index, u32 base_instance, u32 num_instances) {
const auto& regs{maxwell3d->regs};
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h
index 7c22c49f1..cfc8127fc 100644
--- a/src/video_core/engines/draw_manager.h
+++ b/src/video_core/engines/draw_manager.h
@@ -46,6 +46,7 @@ public:
};
struct IndirectParams {
+ bool is_byte_count;
bool is_indexed;
bool include_count;
GPUVAddr count_start_address;
@@ -65,6 +66,8 @@ public:
void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
u32 base_instance, u32 num_instances);
+ void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
+ bool subsequent);
void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
u32 base_instance, u32 num_instances);
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..32d767d85 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -20,8 +20,6 @@
namespace Tegra::Engines {
-using VideoCore::QueryType;
-
/// First register id that is actually a Macro call.
constexpr u32 MacroRegistersStart = 0xE00;
@@ -257,6 +255,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 +268,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) {
@@ -496,27 +498,21 @@ void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
}
void Maxwell3D::ProcessQueryGet() {
+ VideoCommon::QueryPropertiesFlags flags{};
+ if (regs.report_semaphore.query.short_query == 0) {
+ flags |= VideoCommon::QueryPropertiesFlags::HasTimeout;
+ }
+ const GPUVAddr sequence_address{regs.report_semaphore.Address()};
+ const VideoCommon::QueryType query_type =
+ static_cast<VideoCommon::QueryType>(regs.report_semaphore.query.report.Value());
+ const u32 payload = regs.report_semaphore.payload;
+ const u32 subreport = regs.report_semaphore.query.sub_report;
switch (regs.report_semaphore.query.operation) {
case Regs::ReportSemaphore::Operation::Release:
if (regs.report_semaphore.query.short_query != 0) {
- const GPUVAddr sequence_address{regs.report_semaphore.Address()};
- const u32 payload = regs.report_semaphore.payload;
- std::function<void()> operation([this, sequence_address, payload] {
- memory_manager.Write<u32>(sequence_address, payload);
- });
- rasterizer->SignalFence(std::move(operation));
- } else {
- struct LongQueryResult {
- u64_le value;
- u64_le timestamp;
- };
- const GPUVAddr sequence_address{regs.report_semaphore.Address()};
- const u32 payload = regs.report_semaphore.payload;
- [this, sequence_address, payload] {
- memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
- memory_manager.Write<u64>(sequence_address, payload);
- }();
+ flags |= VideoCommon::QueryPropertiesFlags::IsAFence;
}
+ rasterizer->Query(sequence_address, query_type, flags, payload, subreport);
break;
case Regs::ReportSemaphore::Operation::Acquire:
// TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that
@@ -524,11 +520,7 @@ void Maxwell3D::ProcessQueryGet() {
UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE");
break;
case Regs::ReportSemaphore::Operation::ReportOnly:
- if (const std::optional<u64> result = GetQueryResult()) {
- // If the query returns an empty optional it means it's cached and deferred.
- // In this case we have a non-empty result, so we stamp it immediately.
- StampQueryResult(*result, regs.report_semaphore.query.short_query == 0);
- }
+ rasterizer->Query(sequence_address, query_type, flags, payload, subreport);
break;
case Regs::ReportSemaphore::Operation::Trap:
UNIMPLEMENTED_MSG("Unimplemented query operation TRAP");
@@ -540,6 +532,10 @@ void Maxwell3D::ProcessQueryGet() {
}
void Maxwell3D::ProcessQueryCondition() {
+ if (rasterizer->AccelerateConditionalRendering()) {
+ execute_on = true;
+ return;
+ }
const GPUVAddr condition_address{regs.render_enable.Address()};
switch (regs.render_enable_override) {
case Regs::RenderEnable::Override::AlwaysRender:
@@ -549,10 +545,6 @@ void Maxwell3D::ProcessQueryCondition() {
execute_on = false;
break;
case Regs::RenderEnable::Override::UseRenderEnable: {
- if (rasterizer->AccelerateConditionalRendering()) {
- execute_on = true;
- return;
- }
switch (regs.render_enable.mode) {
case Regs::RenderEnable::Mode::True: {
execute_on = true;
@@ -594,15 +586,9 @@ void Maxwell3D::ProcessQueryCondition() {
}
void Maxwell3D::ProcessCounterReset() {
-#if ANDROID
- if (!Settings::IsGPULevelHigh()) {
- // This is problematic on Android, disable on GPU Normal.
- return;
- }
-#endif
switch (regs.clear_report_value) {
case Regs::ClearReport::ZPassPixelCount:
- rasterizer->ResetCounter(QueryType::SamplesPassed);
+ rasterizer->ResetCounter(VideoCommon::QueryType::ZPassPixelCount64);
break;
default:
LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.clear_report_value);
@@ -616,28 +602,6 @@ void Maxwell3D::ProcessSyncPoint() {
rasterizer->SignalSyncPoint(sync_point);
}
-std::optional<u64> Maxwell3D::GetQueryResult() {
- switch (regs.report_semaphore.query.report) {
- case Regs::ReportSemaphore::Report::Payload:
- return regs.report_semaphore.payload;
- case Regs::ReportSemaphore::Report::ZPassPixelCount64:
-#if ANDROID
- if (!Settings::IsGPULevelHigh()) {
- // This is problematic on Android, disable on GPU Normal.
- return 120;
- }
-#endif
- // Deferred.
- rasterizer->Query(regs.report_semaphore.Address(), QueryType::SamplesPassed,
- system.GPU().GetTicks());
- return std::nullopt;
- default:
- LOG_DEBUG(HW_GPU, "Unimplemented query report type {}",
- regs.report_semaphore.query.report.Value());
- return 1;
- }
-}
-
void Maxwell3D::ProcessCBBind(size_t stage_index) {
// Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader
// stage.
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 6c19354e1..17faacc37 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -3182,9 +3182,6 @@ private:
/// Handles writes to syncing register.
void ProcessSyncPoint();
- /// Returns a query's value or an empty object if the value will be deferred through a cache.
- std::optional<u64> GetQueryResult();
-
void RefreshParametersImpl();
bool IsMethodExecutable(u32 method);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index da8eab7ee..422d4d859 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -109,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));
@@ -361,21 +362,17 @@ void MaxwellDMA::ReleaseSemaphore() {
const auto type = regs.launch_dma.semaphore_type;
const GPUVAddr address = regs.semaphore.address;
const u32 payload = regs.semaphore.payload;
+ VideoCommon::QueryPropertiesFlags flags{VideoCommon::QueryPropertiesFlags::IsAFence};
switch (type) {
case LaunchDMA::SemaphoreType::NONE:
break;
case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: {
- std::function<void()> operation(
- [this, address, payload] { memory_manager.Write<u32>(address, payload); });
- rasterizer->SignalFence(std::move(operation));
+ rasterizer->Query(address, VideoCommon::QueryType::Payload, flags, payload, 0);
break;
}
case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: {
- std::function<void()> operation([this, address, payload] {
- memory_manager.Write<u64>(address + sizeof(u64), system.GPU().GetTicks());
- memory_manager.Write<u64>(address, payload);
- });
- rasterizer->SignalFence(std::move(operation));
+ rasterizer->Query(address, VideoCommon::QueryType::Payload,
+ flags | VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
break;
}
default:
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..8dd34c04a 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);
@@ -77,10 +82,8 @@ void Puller::ProcessSemaphoreTriggerMethod() {
if (op == GpuSemaphoreOperation::WriteLong) {
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
const u32 payload = regs.semaphore_sequence;
- [this, sequence_address, payload] {
- memory_manager.Write<u64>(sequence_address + sizeof(u64), gpu.GetTicks());
- memory_manager.Write<u64>(sequence_address, payload);
- }();
+ rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
+ VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0);
} else {
do {
const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())};
@@ -115,10 +118,8 @@ void Puller::ProcessSemaphoreTriggerMethod() {
void Puller::ProcessSemaphoreRelease() {
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
const u32 payload = regs.semaphore_release;
- std::function<void()> operation([this, sequence_address, payload] {
- memory_manager.Write<u32>(sequence_address, payload);
- });
- rasterizer->SignalFence(std::move(operation));
+ rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload,
+ VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0);
}
void Puller::ProcessSemaphoreAcquire() {
@@ -127,7 +128,6 @@ void Puller::ProcessSemaphoreAcquire() {
while (word != value) {
regs.acquire_active = true;
regs.acquire_value = value;
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
rasterizer->ReleaseFences();
word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
// TODO(kemathe73) figure out how to do the acquire_timeout
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index ab20ff30f..805a89900 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -55,6 +55,9 @@ public:
// Unlike other fences, this one doesn't
void SignalOrdering() {
+ if constexpr (!can_async_check) {
+ TryReleasePendingFences<false>();
+ }
std::scoped_lock lock{buffer_cache.mutex};
buffer_cache.AccumulateFlushes();
}
@@ -104,9 +107,25 @@ public:
SignalFence(std::move(func));
}
- void WaitPendingFences() {
+ void WaitPendingFences([[maybe_unused]] bool force) {
if constexpr (!can_async_check) {
TryReleasePendingFences<true>();
+ } else {
+ if (!force) {
+ return;
+ }
+ std::mutex wait_mutex;
+ std::condition_variable wait_cv;
+ std::atomic<bool> wait_finished{};
+ std::function<void()> func([&] {
+ std::scoped_lock lk(wait_mutex);
+ wait_finished.store(true, std::memory_order_relaxed);
+ wait_cv.notify_all();
+ });
+ SignalFence(std::move(func));
+ std::unique_lock lk(wait_mutex);
+ wait_cv.wait(
+ lk, [&wait_finished] { return wait_finished.load(std::memory_order_relaxed); });
}
}
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index c192e33b2..11549d448 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -102,7 +102,8 @@ struct GPU::Impl {
/// Signal the ending of command list.
void OnCommandListEnd() {
- rasterizer->ReleaseFences();
+ rasterizer->ReleaseFences(false);
+ Settings::UpdateGPUAccuracy();
}
/// Request a host GPU memory flush from the CPU.
@@ -220,6 +221,7 @@ struct GPU::Impl {
/// This can be used to launch any necessary threads and register any necessary
/// core timing events.
void Start() {
+ Settings::UpdateGPUAccuracy();
gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
}
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index 220cce28a..dbcf508e5 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -137,16 +137,6 @@ bool Codec::CreateGpuAvDevice() {
break;
}
if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) {
-#if defined(__unix__)
- // Some linux decoding backends are reported to crash with this config method
- // TODO(ameerj): Properly support this method
- if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0) {
- // skip zero-copy decoders, we don't currently support them
- LOG_DEBUG(Service_NVDRV, "Skipping decoder {} with unsupported capability {}.",
- av_hwdevice_get_type_name(type), config->methods);
- continue;
- }
-#endif
LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
av_codec_ctx->pix_fmt = config->pix_fmt;
return true;
@@ -319,6 +309,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 +325,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/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index c4d459077..8bb429578 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -19,6 +19,7 @@ set(SHADER_FILES
block_linear_unswizzle_2d.comp
block_linear_unswizzle_3d.comp
convert_abgr8_to_d24s8.frag
+ convert_d32f_to_abgr8.frag
convert_d24s8_to_abgr8.frag
convert_depth_to_float.frag
convert_float_to_depth.frag
@@ -41,6 +42,9 @@ set(SHADER_FILES
pitch_unswizzle.comp
present_bicubic.frag
present_gaussian.frag
+ queries_prefix_scan_sum.comp
+ queries_prefix_scan_sum_nosubgroups.comp
+ resolve_conditional_render.comp
smaa_edge_detection.vert
smaa_edge_detection.frag
smaa_blending_weight_calculation.vert
@@ -70,6 +74,7 @@ if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
endif()
set(GLSL_FLAGS "")
+set(SPIR_V_VERSION "spirv1.3")
set(QUIET_FLAG "--quiet")
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
@@ -123,7 +128,7 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES})
OUTPUT
${SPIRV_HEADER_FILE}
COMMAND
- ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE}
+ ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE} --target-env ${SPIR_V_VERSION}
MAIN_DEPENDENCY
${SOURCE_FILE}
)
diff --git a/src/video_core/host_shaders/convert_d32f_to_abgr8.frag b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag
new file mode 100644
index 000000000..04cfef8b5
--- /dev/null
+++ b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#version 450
+
+layout(binding = 0) uniform sampler2D depth_tex;
+
+layout(location = 0) out vec4 color;
+
+void main() {
+ ivec2 coord = ivec2(gl_FragCoord.xy);
+ float depth = textureLod(depth_tex, coord, 0).r;
+ color = vec4(depth, depth, depth, 1.0);
+}
diff --git a/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp b/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
index fc3854d18..66f2ad483 100644
--- a/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
+++ b/src/video_core/host_shaders/convert_msaa_to_non_msaa.comp
@@ -15,11 +15,14 @@ void main() {
// TODO: Specialization constants for num_samples?
const int num_samples = imageSamples(msaa_in);
+ const ivec3 msaa_size = imageSize(msaa_in);
+ const ivec3 out_size = imageSize(output_img);
+ const ivec3 scale = out_size / msaa_size;
for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) {
const vec4 pixel = imageLoad(msaa_in, coords, curr_sample);
- const int single_sample_x = 2 * coords.x + (curr_sample & 1);
- const int single_sample_y = 2 * coords.y + ((curr_sample / 2) & 1);
+ const int single_sample_x = scale.x * coords.x + (curr_sample & 1);
+ const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1);
const ivec3 dest_coords = ivec3(single_sample_x, single_sample_y, coords.z);
if (any(greaterThanEqual(dest_coords, imageSize(output_img)))) {
diff --git a/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp b/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
index dedd962f1..c7ce38efa 100644
--- a/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
+++ b/src/video_core/host_shaders/convert_non_msaa_to_msaa.comp
@@ -15,9 +15,12 @@ void main() {
// TODO: Specialization constants for num_samples?
const int num_samples = imageSamples(output_msaa);
+ const ivec3 msaa_size = imageSize(output_msaa);
+ const ivec3 out_size = imageSize(img_in);
+ const ivec3 scale = out_size / msaa_size;
for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) {
- const int single_sample_x = 2 * coords.x + (curr_sample & 1);
- const int single_sample_y = 2 * coords.y + ((curr_sample / 2) & 1);
+ const int single_sample_x = scale.x * coords.x + (curr_sample & 1);
+ const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1);
const ivec3 single_coords = ivec3(single_sample_x, single_sample_y, coords.z);
if (any(greaterThanEqual(single_coords, imageSize(img_in)))) {
diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum.comp b/src/video_core/host_shaders/queries_prefix_scan_sum.comp
new file mode 100644
index 000000000..6faa8981f
--- /dev/null
+++ b/src/video_core/host_shaders/queries_prefix_scan_sum.comp
@@ -0,0 +1,173 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#version 460 core
+
+#extension GL_KHR_shader_subgroup_basic : require
+#extension GL_KHR_shader_subgroup_shuffle : require
+#extension GL_KHR_shader_subgroup_shuffle_relative : require
+#extension GL_KHR_shader_subgroup_arithmetic : require
+
+#ifdef VULKAN
+
+#define HAS_EXTENDED_TYPES 1
+#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
+#define END_PUSH_CONSTANTS };
+#define UNIFORM(n)
+#define BINDING_INPUT_BUFFER 0
+#define BINDING_OUTPUT_IMAGE 1
+
+#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
+
+#extension GL_NV_gpu_shader5 : enable
+#ifdef GL_NV_gpu_shader5
+#define HAS_EXTENDED_TYPES 1
+#else
+#define HAS_EXTENDED_TYPES 0
+#endif
+#define BEGIN_PUSH_CONSTANTS
+#define END_PUSH_CONSTANTS
+#define UNIFORM(n) layout(location = n) uniform
+#define BINDING_INPUT_BUFFER 0
+#define BINDING_OUTPUT_IMAGE 0
+
+#endif
+
+BEGIN_PUSH_CONSTANTS
+UNIFORM(0) uint min_accumulation_base;
+UNIFORM(1) uint max_accumulation_base;
+UNIFORM(2) uint accumulation_limit;
+UNIFORM(3) uint buffer_offset;
+END_PUSH_CONSTANTS
+
+#define LOCAL_RESULTS 8
+#define QUERIES_PER_INVOC 2048
+
+layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in;
+
+layout(std430, binding = 0) readonly buffer block1 {
+ uvec2 input_data[];
+};
+
+layout(std430, binding = 1) coherent buffer block2 {
+ uvec2 output_data[];
+};
+
+layout(std430, binding = 2) coherent buffer block3 {
+ uvec2 accumulated_data;
+};
+
+shared uvec2 shared_data[128];
+
+// Simple Uint64 add that uses 2 uint variables for GPUs that don't support uint64
+uvec2 AddUint64(uvec2 value_1, uvec2 value_2) {
+ uint carry = 0;
+ uvec2 result;
+ result.x = uaddCarry(value_1.x, value_2.x, carry);
+ result.y = value_1.y + value_2.y + carry;
+ return result;
+}
+
+// do subgroup Prefix Sum using Hillis and Steele's algorithm
+uvec2 subgroupInclusiveAddUint64(uvec2 value) {
+ uvec2 result = value;
+ for (uint i = 1; i < gl_SubgroupSize; i *= 2) {
+ uvec2 other = subgroupShuffleUp(result, i); // get value from subgroup_inv_id - i;
+ if (i <= gl_SubgroupInvocationID) {
+ result = AddUint64(result, other);
+ }
+ }
+ return result;
+}
+
+// Writes down the results to the output buffer and to the accumulation buffer
+void WriteResults(uvec2 results[LOCAL_RESULTS]) {
+ const uint current_id = gl_LocalInvocationID.x;
+ const uvec2 accum = accumulated_data;
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ uvec2 base_data = current_id * LOCAL_RESULTS + i < min_accumulation_base ? accum : uvec2(0, 0);
+ AddUint64(results[i], base_data);
+ }
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ output_data[buffer_offset + current_id * LOCAL_RESULTS + i] = results[i];
+ }
+ uint index = accumulation_limit % LOCAL_RESULTS;
+ uint base_id = accumulation_limit / LOCAL_RESULTS;
+ if (min_accumulation_base >= accumulation_limit + 1) {
+ if (current_id == base_id) {
+ accumulated_data = results[index];
+ }
+ return;
+ }
+ // We have that ugly case in which the accumulation data is reset in the middle somewhere.
+ barrier();
+ groupMemoryBarrier();
+
+ if (current_id == base_id) {
+ uvec2 reset_value = output_data[max_accumulation_base - 1];
+ // Calculate two complement / negate manually
+ reset_value = AddUint64(uvec2(1,0), ~reset_value);
+ accumulated_data = AddUint64(results[index], reset_value);
+ }
+}
+
+void main() {
+ const uint subgroup_inv_id = gl_SubgroupInvocationID;
+ const uint subgroup_id = gl_SubgroupID + gl_WorkGroupID.x * gl_NumSubgroups;
+ const uint last_subgroup_id = subgroupMax(subgroup_inv_id);
+ const uint current_id = gl_LocalInvocationID.x;
+ const uint total_work = accumulation_limit;
+ const uint last_result_id = LOCAL_RESULTS - 1;
+ uvec2 data[LOCAL_RESULTS];
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ data[i] = input_data[buffer_offset + current_id * LOCAL_RESULTS + i];
+ }
+ uvec2 results[LOCAL_RESULTS];
+ results[0] = data[0];
+ for (uint i = 1; i < LOCAL_RESULTS; i++) {
+ results[i] = AddUint64(data[i], results[i - 1]);
+ }
+ // make sure all input data has been loaded
+ subgroupBarrier();
+ subgroupMemoryBarrier();
+
+ // on the last local result, do a subgroup inclusive scan sum
+ results[last_result_id] = subgroupInclusiveAddUint64(results[last_result_id]);
+ // get the last local result from the subgroup behind the current
+ uvec2 result_behind = subgroupShuffleUp(results[last_result_id], 1);
+ if (subgroup_inv_id != 0) {
+ for (uint i = 1; i < LOCAL_RESULTS; i++) {
+ results[i - 1] = AddUint64(results[i - 1], result_behind);
+ }
+ }
+
+ // if we had less queries than our subgroup, just write down the results.
+ if (total_work <= gl_SubgroupSize * LOCAL_RESULTS) { // This condition is constant per dispatch.
+ WriteResults(results);
+ return;
+ }
+
+ // We now have more, so lets write the last result into shared memory.
+ // Only pick the last subgroup.
+ if (subgroup_inv_id == last_subgroup_id) {
+ shared_data[subgroup_id] = results[last_result_id];
+ }
+ // wait until everyone loaded their stuffs
+ barrier();
+ memoryBarrierShared();
+
+ // only if it's not the first subgroup
+ if (subgroup_id != 0) {
+ // get the results from some previous invocation
+ uvec2 tmp = shared_data[subgroup_inv_id];
+ subgroupBarrier();
+ subgroupMemoryBarrierShared();
+ tmp = subgroupInclusiveAddUint64(tmp);
+ // obtain the result that would be equivalent to the previous result
+ uvec2 shuffled_result = subgroupShuffle(tmp, subgroup_id - 1);
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ results[i] = AddUint64(results[i], shuffled_result);
+ }
+ }
+ WriteResults(results);
+} \ No newline at end of file
diff --git a/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp b/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp
new file mode 100644
index 000000000..559a213b9
--- /dev/null
+++ b/src/video_core/host_shaders/queries_prefix_scan_sum_nosubgroups.comp
@@ -0,0 +1,138 @@
+// SPDX-FileCopyrightText: Copyright 2015 Graham Sellers, Richard Wright Jr. and Nicholas Haemel
+// SPDX-License-Identifier: MIT
+
+// Code obtained from OpenGL SuperBible, Seventh Edition by Graham Sellers, Richard Wright Jr. and
+// Nicholas Haemel. Modified to suit needs.
+
+#version 460 core
+
+#ifdef VULKAN
+
+#define HAS_EXTENDED_TYPES 1
+#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
+#define END_PUSH_CONSTANTS };
+#define UNIFORM(n)
+#define BINDING_INPUT_BUFFER 0
+#define BINDING_OUTPUT_IMAGE 1
+
+#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
+
+#extension GL_NV_gpu_shader5 : enable
+#ifdef GL_NV_gpu_shader5
+#define HAS_EXTENDED_TYPES 1
+#else
+#define HAS_EXTENDED_TYPES 0
+#endif
+#define BEGIN_PUSH_CONSTANTS
+#define END_PUSH_CONSTANTS
+#define UNIFORM(n) layout(location = n) uniform
+#define BINDING_INPUT_BUFFER 0
+#define BINDING_OUTPUT_IMAGE 0
+
+#endif
+
+BEGIN_PUSH_CONSTANTS
+UNIFORM(0) uint min_accumulation_base;
+UNIFORM(1) uint max_accumulation_base;
+UNIFORM(2) uint accumulation_limit;
+UNIFORM(3) uint buffer_offset;
+END_PUSH_CONSTANTS
+
+#define LOCAL_RESULTS 4
+#define QUERIES_PER_INVOC 2048
+
+layout(local_size_x = QUERIES_PER_INVOC / LOCAL_RESULTS) in;
+
+layout(std430, binding = 0) readonly buffer block1 {
+ uvec2 input_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
+};
+
+layout(std430, binding = 1) writeonly coherent buffer block2 {
+ uvec2 output_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
+};
+
+layout(std430, binding = 2) coherent buffer block3 {
+ uvec2 accumulated_data;
+};
+
+shared uvec2 shared_data[gl_WorkGroupSize.x * LOCAL_RESULTS];
+
+uvec2 AddUint64(uvec2 value_1, uvec2 value_2) {
+ uint carry = 0;
+ uvec2 result;
+ result.x = uaddCarry(value_1.x, value_2.x, carry);
+ result.y = value_1.y + value_2.y + carry;
+ return result;
+}
+
+void main(void) {
+ uint id = gl_LocalInvocationID.x;
+ uvec2 base_value[LOCAL_RESULTS];
+ const uvec2 accum = accumulated_data;
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ base_value[i] = (buffer_offset + id * LOCAL_RESULTS + i) < min_accumulation_base
+ ? accumulated_data
+ : uvec2(0);
+ }
+ uint work_size = gl_WorkGroupSize.x;
+ uint rd_id;
+ uint wr_id;
+ uint mask;
+ uvec2 inputs[LOCAL_RESULTS];
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ inputs[i] = input_data[buffer_offset + id * LOCAL_RESULTS + i];
+ }
+ // The number of steps is the log base 2 of the
+ // work group size, which should be a power of 2
+ const uint steps = uint(log2(work_size)) + uint(log2(LOCAL_RESULTS));
+ uint step = 0;
+
+ // Each invocation is responsible for the content of
+ // two elements of the output array
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ shared_data[id * LOCAL_RESULTS + i] = inputs[i];
+ }
+ // Synchronize to make sure that everyone has initialized
+ // their elements of shared_data[] with data loaded from
+ // the input arrays
+ barrier();
+ memoryBarrierShared();
+ // For each step...
+ for (step = 0; step < steps; step++) {
+ // Calculate the read and write index in the
+ // shared array
+ mask = (1 << step) - 1;
+ rd_id = ((id >> step) << (step + 1)) + mask;
+ wr_id = rd_id + 1 + (id & mask);
+ // Accumulate the read data into our element
+
+ shared_data[wr_id] = AddUint64(shared_data[rd_id], shared_data[wr_id]);
+ // Synchronize again to make sure that everyone
+ // has caught up with us
+ barrier();
+ memoryBarrierShared();
+ }
+ // Add the accumulation
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ shared_data[id * LOCAL_RESULTS + i] =
+ AddUint64(shared_data[id * LOCAL_RESULTS + i], base_value[i]);
+ }
+ barrier();
+ memoryBarrierShared();
+
+ // Finally write our data back to the output buffer
+ for (uint i = 0; i < LOCAL_RESULTS; i++) {
+ output_data[buffer_offset + id * LOCAL_RESULTS + i] = shared_data[id * LOCAL_RESULTS + i];
+ }
+ if (id == 0) {
+ if (min_accumulation_base >= accumulation_limit + 1) {
+ accumulated_data = shared_data[accumulation_limit];
+ return;
+ }
+ uvec2 reset_value = shared_data[max_accumulation_base - 1];
+ uvec2 final_value = shared_data[accumulation_limit];
+ // Two complements
+ reset_value = AddUint64(uvec2(1, 0), ~reset_value);
+ accumulated_data = AddUint64(final_value, reset_value);
+ }
+} \ No newline at end of file
diff --git a/src/video_core/host_shaders/resolve_conditional_render.comp b/src/video_core/host_shaders/resolve_conditional_render.comp
new file mode 100644
index 000000000..307e77d1a
--- /dev/null
+++ b/src/video_core/host_shaders/resolve_conditional_render.comp
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#version 450
+
+layout(local_size_x = 1) in;
+
+layout(std430, binding = 0) buffer Query {
+ uvec2 initial;
+ uvec2 unknown;
+ uvec2 current;
+};
+
+layout(std430, binding = 1) buffer Result {
+ uint result;
+};
+
+void main() {
+ result = all(equal(initial, current)) ? 1 : 0;
+}
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/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 6272a4652..046c8085e 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -67,6 +67,7 @@ public:
}
auto& params = maxwell3d.draw_manager->GetIndirectParams();
+ params.is_byte_count = false;
params.is_indexed = false;
params.include_count = false;
params.count_start_address = 0;
@@ -161,6 +162,7 @@ public:
0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
}
auto& params = maxwell3d.draw_manager->GetIndirectParams();
+ params.is_byte_count = false;
params.is_indexed = true;
params.include_count = false;
params.count_start_address = 0;
@@ -256,6 +258,7 @@ public:
const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize());
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
auto& params = maxwell3d.draw_manager->GetIndirectParams();
+ params.is_byte_count = false;
params.is_indexed = true;
params.include_count = true;
params.count_start_address = maxwell3d.GetMacroAddress(4);
@@ -319,6 +322,47 @@ private:
}
};
+class HLE_DrawIndirectByteCount final : public HLEMacroImpl {
+public:
+ explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
+
+ void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
+ auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0xFFFFU);
+ if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
+ Fallback(parameters);
+ return;
+ }
+
+ auto& params = maxwell3d.draw_manager->GetIndirectParams();
+ params.is_byte_count = true;
+ params.is_indexed = false;
+ params.include_count = false;
+ params.count_start_address = 0;
+ params.indirect_start_address = maxwell3d.GetMacroAddress(2);
+ params.buffer_size = 4;
+ params.max_draw_counts = 1;
+ params.stride = parameters[1];
+ maxwell3d.regs.draw.begin = parameters[0];
+ maxwell3d.regs.draw_auto_stride = parameters[1];
+ maxwell3d.regs.draw_auto_byte_count = parameters[2];
+
+ maxwell3d.draw_manager->DrawArrayIndirect(topology);
+ }
+
+private:
+ void Fallback(const std::vector<u32>& parameters) {
+ maxwell3d.RefreshParameters();
+
+ maxwell3d.regs.draw.begin = parameters[0];
+ maxwell3d.regs.draw_auto_stride = parameters[1];
+ maxwell3d.regs.draw_auto_byte_count = parameters[2];
+
+ maxwell3d.draw_manager->DrawArray(
+ maxwell3d.regs.draw.topology, 0,
+ maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1);
+ }
+};
+
class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl {
public:
explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
@@ -536,6 +580,11 @@ HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {
[](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> {
return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d__);
}));
+ builders.emplace(0xB5F74EDB717278ECULL,
+ std::function<std::unique_ptr<CachedMacro>(Maxwell3D&)>(
+ [](Maxwell3D& maxwell3d__) -> std::unique_ptr<CachedMacro> {
+ return std::make_unique<HLE_DrawIndirectByteCount>(maxwell3d__);
+ }));
}
HLEMacro::~HLEMacro() = default;
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index 7047e2e63..9fcaeeac7 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -25,6 +25,13 @@
#include "video_core/rasterizer_interface.h"
#include "video_core/texture_cache/slot_vector.h"
+namespace VideoCore {
+enum class QueryType {
+ SamplesPassed,
+};
+constexpr std::size_t NumQueryTypes = 1;
+} // namespace VideoCore
+
namespace VideoCommon {
using AsyncJobId = SlotId;
@@ -98,10 +105,10 @@ private:
};
template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
-class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
+class QueryCacheLegacy : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
public:
- explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
- Core::Memory::Memory& cpu_memory_)
+ explicit QueryCacheLegacy(VideoCore::RasterizerInterface& rasterizer_,
+ Core::Memory::Memory& cpu_memory_)
: rasterizer{rasterizer_},
// Use reinterpret_cast instead of static_cast as workaround for
// UBSan bug (https://github.com/llvm/llvm-project/issues/59060)
diff --git a/src/video_core/query_cache/bank_base.h b/src/video_core/query_cache/bank_base.h
new file mode 100644
index 000000000..44769ea97
--- /dev/null
+++ b/src/video_core/query_cache/bank_base.h
@@ -0,0 +1,105 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <deque>
+#include <utility>
+
+#include "common/common_types.h"
+
+namespace VideoCommon {
+
+class BankBase {
+protected:
+ const size_t base_bank_size{};
+ size_t bank_size{};
+ std::atomic<size_t> references{};
+ size_t current_slot{};
+
+public:
+ explicit BankBase(size_t bank_size_) : base_bank_size{bank_size_}, bank_size(bank_size_) {}
+
+ virtual ~BankBase() = default;
+
+ virtual std::pair<bool, size_t> Reserve() {
+ if (IsClosed()) {
+ return {false, bank_size};
+ }
+ const size_t result = current_slot++;
+ return {true, result};
+ }
+
+ virtual void Reset() {
+ current_slot = 0;
+ references = 0;
+ bank_size = base_bank_size;
+ }
+
+ size_t Size() const {
+ return bank_size;
+ }
+
+ void AddReference(size_t how_many = 1) {
+ references.fetch_add(how_many, std::memory_order_relaxed);
+ }
+
+ void CloseReference(size_t how_many = 1) {
+ if (how_many > references.load(std::memory_order_relaxed)) {
+ UNREACHABLE();
+ }
+ references.fetch_sub(how_many, std::memory_order_relaxed);
+ }
+
+ void Close() {
+ bank_size = current_slot;
+ }
+
+ bool IsClosed() const {
+ return current_slot >= bank_size;
+ }
+
+ bool IsDead() const {
+ return IsClosed() && references == 0;
+ }
+};
+
+template <typename BankType>
+class BankPool {
+private:
+ std::deque<BankType> bank_pool;
+ std::deque<size_t> bank_indices;
+
+public:
+ BankPool() = default;
+ ~BankPool() = default;
+
+ // Reserve a bank from the pool and return its index
+ template <typename Func>
+ size_t ReserveBank(Func&& builder) {
+ if (!bank_indices.empty() && bank_pool[bank_indices.front()].IsDead()) {
+ size_t new_index = bank_indices.front();
+ bank_indices.pop_front();
+ bank_pool[new_index].Reset();
+ bank_indices.push_back(new_index);
+ return new_index;
+ }
+ size_t new_index = bank_pool.size();
+ builder(bank_pool, new_index);
+ bank_indices.push_back(new_index);
+ return new_index;
+ }
+
+ // Get a reference to a bank using its index
+ BankType& GetBank(size_t index) {
+ return bank_pool[index];
+ }
+
+ // Get the total number of banks in the pool
+ size_t BankCount() const {
+ return bank_pool.size();
+ }
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/query_cache/query_base.h b/src/video_core/query_cache/query_base.h
new file mode 100644
index 000000000..1d786b3a7
--- /dev/null
+++ b/src/video_core/query_cache/query_base.h
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace VideoCommon {
+
+enum class QueryFlagBits : u32 {
+ HasTimestamp = 1 << 0, ///< Indicates if this query has a timestamp.
+ IsFinalValueSynced = 1 << 1, ///< Indicates if the query has been synced in the host
+ IsHostSynced = 1 << 2, ///< Indicates if the query has been synced in the host
+ IsGuestSynced = 1 << 3, ///< Indicates if the query has been synced with the guest.
+ IsHostManaged = 1 << 4, ///< Indicates if this query points to a host query
+ IsRewritten = 1 << 5, ///< Indicates if this query was rewritten by another query
+ IsInvalidated = 1 << 6, ///< Indicates the value of th query has been nullified.
+ IsOrphan = 1 << 7, ///< Indicates the query has not been set by a guest query.
+ IsFence = 1 << 8, ///< Indicates the query is a fence.
+};
+DECLARE_ENUM_FLAG_OPERATORS(QueryFlagBits)
+
+class QueryBase {
+public:
+ VAddr guest_address{};
+ QueryFlagBits flags{};
+ u64 value{};
+
+protected:
+ // Default constructor
+ QueryBase() = default;
+
+ // Parameterized constructor
+ QueryBase(VAddr address, QueryFlagBits flags_, u64 value_)
+ : guest_address(address), flags(flags_), value{value_} {}
+};
+
+class GuestQuery : public QueryBase {
+public:
+ // Parameterized constructor
+ GuestQuery(bool isLong, VAddr address, u64 queryValue)
+ : QueryBase(address, QueryFlagBits::IsFinalValueSynced, queryValue) {
+ if (isLong) {
+ flags |= QueryFlagBits::HasTimestamp;
+ }
+ }
+};
+
+class HostQueryBase : public QueryBase {
+public:
+ // Default constructor
+ HostQueryBase() : QueryBase(0, QueryFlagBits::IsHostManaged | QueryFlagBits::IsOrphan, 0) {}
+
+ // Parameterized constructor
+ HostQueryBase(bool has_timestamp, VAddr address)
+ : QueryBase(address, QueryFlagBits::IsHostManaged, 0), start_bank_id{}, size_banks{},
+ start_slot{}, size_slots{} {
+ if (has_timestamp) {
+ flags |= QueryFlagBits::HasTimestamp;
+ }
+ }
+
+ u32 start_bank_id{};
+ u32 size_banks{};
+ size_t start_slot{};
+ size_t size_slots{};
+};
+
+} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h
new file mode 100644
index 000000000..78b42b518
--- /dev/null
+++ b/src/video_core/query_cache/query_cache.h
@@ -0,0 +1,580 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <deque>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+#include <utility>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "common/settings.h"
+#include "core/memory.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+#include "video_core/query_cache/bank_base.h"
+#include "video_core/query_cache/query_base.h"
+#include "video_core/query_cache/query_cache_base.h"
+#include "video_core/query_cache/query_stream.h"
+#include "video_core/query_cache/types.h"
+
+namespace VideoCommon {
+
+using Maxwell = Tegra::Engines::Maxwell3D;
+
+struct SyncValuesStruct {
+ VAddr address;
+ u64 value;
+ u64 size;
+
+ static constexpr bool GeneratesBaseBuffer = true;
+};
+
+template <typename Traits>
+class GuestStreamer : public SimpleStreamer<GuestQuery> {
+public:
+ using RuntimeType = typename Traits::RuntimeType;
+
+ GuestStreamer(size_t id_, RuntimeType& runtime_)
+ : SimpleStreamer<GuestQuery>(id_), runtime{runtime_} {}
+
+ virtual ~GuestStreamer() = default;
+
+ size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
+ std::optional<u32> subreport = std::nullopt) override {
+ auto new_id = BuildQuery(has_timestamp, address, static_cast<u64>(value));
+ pending_sync.push_back(new_id);
+ return new_id;
+ }
+
+ bool HasPendingSync() const override {
+ return !pending_sync.empty();
+ }
+
+ void SyncWrites() override {
+ if (pending_sync.empty()) {
+ return;
+ }
+ std::vector<SyncValuesStruct> sync_values;
+ sync_values.reserve(pending_sync.size());
+ for (size_t pending_id : pending_sync) {
+ auto& query = slot_queries[pending_id];
+ if (True(query.flags & QueryFlagBits::IsRewritten) ||
+ True(query.flags & QueryFlagBits::IsInvalidated)) {
+ continue;
+ }
+ query.flags |= QueryFlagBits::IsHostSynced;
+ sync_values.emplace_back(SyncValuesStruct{
+ .address = query.guest_address,
+ .value = query.value,
+ .size = static_cast<u64>(True(query.flags & QueryFlagBits::HasTimestamp) ? 8 : 4)});
+ }
+ pending_sync.clear();
+ if (sync_values.size() > 0) {
+ runtime.template SyncValues<SyncValuesStruct>(sync_values);
+ }
+ }
+
+private:
+ RuntimeType& runtime;
+ std::deque<size_t> pending_sync;
+};
+
+template <typename Traits>
+class StubStreamer : public GuestStreamer<Traits> {
+public:
+ using RuntimeType = typename Traits::RuntimeType;
+
+ StubStreamer(size_t id_, RuntimeType& runtime_, u32 stub_value_)
+ : GuestStreamer<Traits>(id_, runtime_), stub_value{stub_value_} {}
+
+ ~StubStreamer() override = default;
+
+ size_t WriteCounter(VAddr address, bool has_timestamp, [[maybe_unused]] u32 value,
+ std::optional<u32> subreport = std::nullopt) override {
+ size_t new_id =
+ GuestStreamer<Traits>::WriteCounter(address, has_timestamp, stub_value, subreport);
+ return new_id;
+ }
+
+private:
+ u32 stub_value;
+};
+
+template <typename Traits>
+struct QueryCacheBase<Traits>::QueryCacheBaseImpl {
+ using RuntimeType = typename Traits::RuntimeType;
+
+ QueryCacheBaseImpl(QueryCacheBase<Traits>* owner_, VideoCore::RasterizerInterface& rasterizer_,
+ Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_, Tegra::GPU& gpu_)
+ : owner{owner_}, rasterizer{rasterizer_},
+ cpu_memory{cpu_memory_}, runtime{runtime_}, gpu{gpu_} {
+ streamer_mask = 0;
+ for (size_t i = 0; i < static_cast<size_t>(QueryType::MaxQueryTypes); i++) {
+ streamers[i] = runtime.GetStreamerInterface(static_cast<QueryType>(i));
+ if (streamers[i]) {
+ streamer_mask |= 1ULL << streamers[i]->GetId();
+ }
+ }
+ }
+
+ template <typename Func>
+ void ForEachStreamerIn(u64 mask, Func&& func) {
+ static constexpr bool RETURNS_BOOL =
+ std::is_same_v<std::invoke_result<Func, StreamerInterface*>, bool>;
+ while (mask != 0) {
+ size_t position = std::countr_zero(mask);
+ mask &= ~(1ULL << position);
+ if constexpr (RETURNS_BOOL) {
+ if (func(streamers[position])) {
+ return;
+ }
+ } else {
+ func(streamers[position]);
+ }
+ }
+ }
+
+ template <typename Func>
+ void ForEachStreamer(Func&& func) {
+ ForEachStreamerIn(streamer_mask, func);
+ }
+
+ QueryBase* ObtainQuery(QueryCacheBase<Traits>::QueryLocation location) {
+ size_t which_stream = location.stream_id.Value();
+ auto* streamer = streamers[which_stream];
+ if (!streamer) {
+ return nullptr;
+ }
+ return streamer->GetQuery(location.query_id.Value());
+ }
+
+ QueryCacheBase<Traits>* owner;
+ VideoCore::RasterizerInterface& rasterizer;
+ Core::Memory::Memory& cpu_memory;
+ RuntimeType& runtime;
+ Tegra::GPU& gpu;
+ std::array<StreamerInterface*, static_cast<size_t>(QueryType::MaxQueryTypes)> streamers;
+ u64 streamer_mask;
+ std::mutex flush_guard;
+ std::deque<u64> flushes_pending;
+ std::vector<QueryCacheBase<Traits>::QueryLocation> pending_unregister;
+};
+
+template <typename Traits>
+QueryCacheBase<Traits>::QueryCacheBase(Tegra::GPU& gpu_,
+ VideoCore::RasterizerInterface& rasterizer_,
+ Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_)
+ : cached_queries{} {
+ impl = std::make_unique<QueryCacheBase<Traits>::QueryCacheBaseImpl>(
+ this, rasterizer_, cpu_memory_, runtime_, gpu_);
+}
+
+template <typename Traits>
+QueryCacheBase<Traits>::~QueryCacheBase() = default;
+
+template <typename Traits>
+void QueryCacheBase<Traits>::CounterEnable(QueryType counter_type, bool is_enabled) {
+ size_t index = static_cast<size_t>(counter_type);
+ StreamerInterface* streamer = impl->streamers[index];
+ if (!streamer) [[unlikely]] {
+ UNREACHABLE();
+ return;
+ }
+ if (is_enabled) {
+ streamer->StartCounter();
+ } else {
+ streamer->PauseCounter();
+ }
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::CounterClose(QueryType counter_type) {
+ size_t index = static_cast<size_t>(counter_type);
+ StreamerInterface* streamer = impl->streamers[index];
+ if (!streamer) [[unlikely]] {
+ UNREACHABLE();
+ return;
+ }
+ streamer->CloseCounter();
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::CounterReset(QueryType counter_type) {
+ size_t index = static_cast<size_t>(counter_type);
+ StreamerInterface* streamer = impl->streamers[index];
+ if (!streamer) [[unlikely]] {
+ UNIMPLEMENTED();
+ return;
+ }
+ streamer->ResetCounter();
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::BindToChannel(s32 id) {
+ VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>::BindToChannel(id);
+ impl->runtime.Bind3DEngine(maxwell3d);
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type,
+ QueryPropertiesFlags flags, u32 payload, u32 subreport) {
+ const bool has_timestamp = True(flags & QueryPropertiesFlags::HasTimeout);
+ const bool is_fence = True(flags & QueryPropertiesFlags::IsAFence);
+ size_t streamer_id = static_cast<size_t>(counter_type);
+ auto* streamer = impl->streamers[streamer_id];
+ if (streamer == nullptr) [[unlikely]] {
+ counter_type = QueryType::Payload;
+ payload = 1U;
+ streamer_id = static_cast<size_t>(counter_type);
+ streamer = impl->streamers[streamer_id];
+ }
+ auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(addr);
+ if (!cpu_addr_opt) [[unlikely]] {
+ return;
+ }
+ VAddr cpu_addr = *cpu_addr_opt;
+ const size_t new_query_id = streamer->WriteCounter(cpu_addr, has_timestamp, payload, subreport);
+ auto* query = streamer->GetQuery(new_query_id);
+ if (is_fence) {
+ query->flags |= QueryFlagBits::IsFence;
+ }
+ QueryLocation query_location{};
+ query_location.stream_id.Assign(static_cast<u32>(streamer_id));
+ query_location.query_id.Assign(static_cast<u32>(new_query_id));
+ const auto gen_caching_indexing = [](VAddr cur_addr) {
+ return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS,
+ static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK));
+ };
+ u8* pointer = impl->cpu_memory.GetPointer(cpu_addr);
+ u8* pointer_timestamp = impl->cpu_memory.GetPointer(cpu_addr + 8);
+ bool is_synced = !Settings::IsGPULevelHigh() && is_fence;
+
+ std::function<void()> operation([this, is_synced, streamer, query_base = query, query_location,
+ pointer, pointer_timestamp] {
+ if (True(query_base->flags & QueryFlagBits::IsInvalidated)) {
+ if (!is_synced) [[likely]] {
+ impl->pending_unregister.push_back(query_location);
+ }
+ return;
+ }
+ if (False(query_base->flags & QueryFlagBits::IsFinalValueSynced)) [[unlikely]] {
+ UNREACHABLE();
+ return;
+ }
+ query_base->value += streamer->GetAmmendValue();
+ streamer->SetAccumulationValue(query_base->value);
+ if (True(query_base->flags & QueryFlagBits::HasTimestamp)) {
+ u64 timestamp = impl->gpu.GetTicks();
+ std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
+ std::memcpy(pointer, &query_base->value, sizeof(query_base->value));
+ } else {
+ u32 value = static_cast<u32>(query_base->value);
+ std::memcpy(pointer, &value, sizeof(value));
+ }
+ if (!is_synced) [[likely]] {
+ impl->pending_unregister.push_back(query_location);
+ }
+ });
+ if (is_fence) {
+ impl->rasterizer.SignalFence(std::move(operation));
+ } else {
+ if (!Settings::IsGPULevelHigh() && counter_type == QueryType::Payload) {
+ if (has_timestamp) {
+ u64 timestamp = impl->gpu.GetTicks();
+ u64 value = static_cast<u64>(payload);
+ std::memcpy(pointer_timestamp, &timestamp, sizeof(timestamp));
+ std::memcpy(pointer, &value, sizeof(value));
+ } else {
+ std::memcpy(pointer, &payload, sizeof(payload));
+ }
+ streamer->Free(new_query_id);
+ return;
+ }
+ impl->rasterizer.SyncOperation(std::move(operation));
+ }
+ if (is_synced) {
+ streamer->Free(new_query_id);
+ return;
+ }
+ auto [cont_addr, base] = gen_caching_indexing(cpu_addr);
+ {
+ std::scoped_lock lock(cache_mutex);
+ auto it1 = cached_queries.try_emplace(cont_addr);
+ auto& sub_container = it1.first->second;
+ auto it_current = sub_container.find(base);
+ if (it_current == sub_container.end()) {
+ sub_container.insert_or_assign(base, query_location);
+ return;
+ }
+ auto* old_query = impl->ObtainQuery(it_current->second);
+ old_query->flags |= QueryFlagBits::IsRewritten;
+ sub_container.insert_or_assign(base, query_location);
+ }
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::UnregisterPending() {
+ const auto gen_caching_indexing = [](VAddr cur_addr) {
+ return std::make_pair<u64, u32>(cur_addr >> Core::Memory::YUZU_PAGEBITS,
+ static_cast<u32>(cur_addr & Core::Memory::YUZU_PAGEMASK));
+ };
+ std::scoped_lock lock(cache_mutex);
+ for (QueryLocation loc : impl->pending_unregister) {
+ const auto [streamer_id, query_id] = loc.unpack();
+ auto* streamer = impl->streamers[streamer_id];
+ if (!streamer) [[unlikely]] {
+ continue;
+ }
+ auto* query = streamer->GetQuery(query_id);
+ auto [cont_addr, base] = gen_caching_indexing(query->guest_address);
+ auto it1 = cached_queries.find(cont_addr);
+ if (it1 != cached_queries.end()) {
+ auto it2 = it1->second.find(base);
+ if (it2 != it1->second.end()) {
+ if (it2->second.raw == loc.raw) {
+ it1->second.erase(it2);
+ }
+ }
+ }
+ streamer->Free(query_id);
+ }
+ impl->pending_unregister.clear();
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::NotifyWFI() {
+ bool should_sync = false;
+ impl->ForEachStreamer(
+ [&should_sync](StreamerInterface* streamer) { should_sync |= streamer->HasPendingSync(); });
+ if (!should_sync) {
+ return;
+ }
+
+ impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->PresyncWrites(); });
+ impl->runtime.Barriers(true);
+ impl->ForEachStreamer([](StreamerInterface* streamer) { streamer->SyncWrites(); });
+ impl->runtime.Barriers(false);
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::NotifySegment(bool resume) {
+ if (resume) {
+ impl->runtime.ResumeHostConditionalRendering();
+ } else {
+ CounterClose(VideoCommon::QueryType::ZPassPixelCount64);
+ CounterClose(VideoCommon::QueryType::StreamingByteCount);
+ impl->runtime.PauseHostConditionalRendering();
+ }
+}
+
+template <typename Traits>
+bool QueryCacheBase<Traits>::AccelerateHostConditionalRendering() {
+ bool qc_dirty = false;
+ const auto gen_lookup = [this, &qc_dirty](GPUVAddr address) -> VideoCommon::LookupData {
+ auto cpu_addr_opt = gpu_memory->GpuToCpuAddress(address);
+ if (!cpu_addr_opt) [[unlikely]] {
+ return VideoCommon::LookupData{
+ .address = 0,
+ .found_query = nullptr,
+ };
+ }
+ VAddr cpu_addr = *cpu_addr_opt;
+ std::scoped_lock lock(cache_mutex);
+ auto it1 = cached_queries.find(cpu_addr >> Core::Memory::YUZU_PAGEBITS);
+ if (it1 == cached_queries.end()) {
+ return VideoCommon::LookupData{
+ .address = cpu_addr,
+ .found_query = nullptr,
+ };
+ }
+ auto& sub_container = it1->second;
+ auto it_current = sub_container.find(cpu_addr & Core::Memory::YUZU_PAGEMASK);
+
+ if (it_current == sub_container.end()) {
+ auto it_current_2 = sub_container.find((cpu_addr & Core::Memory::YUZU_PAGEMASK) + 4);
+ if (it_current_2 == sub_container.end()) {
+ return VideoCommon::LookupData{
+ .address = cpu_addr,
+ .found_query = nullptr,
+ };
+ }
+ }
+ auto* query = impl->ObtainQuery(it_current->second);
+ qc_dirty |= True(query->flags & QueryFlagBits::IsHostManaged) &&
+ False(query->flags & QueryFlagBits::IsGuestSynced);
+ return VideoCommon::LookupData{
+ .address = cpu_addr,
+ .found_query = query,
+ };
+ };
+
+ auto& regs = maxwell3d->regs;
+ if (regs.render_enable_override != Maxwell::Regs::RenderEnable::Override::UseRenderEnable) {
+ impl->runtime.EndHostConditionalRendering();
+ return false;
+ }
+ const ComparisonMode mode = static_cast<ComparisonMode>(regs.render_enable.mode);
+ const GPUVAddr address = regs.render_enable.Address();
+ switch (mode) {
+ case ComparisonMode::True:
+ impl->runtime.EndHostConditionalRendering();
+ return false;
+ case ComparisonMode::False:
+ impl->runtime.EndHostConditionalRendering();
+ return false;
+ case ComparisonMode::Conditional: {
+ VideoCommon::LookupData object_1{gen_lookup(address)};
+ return impl->runtime.HostConditionalRenderingCompareValue(object_1, qc_dirty);
+ }
+ case ComparisonMode::IfEqual: {
+ VideoCommon::LookupData object_1{gen_lookup(address)};
+ VideoCommon::LookupData object_2{gen_lookup(address + 16)};
+ return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty,
+ true);
+ }
+ case ComparisonMode::IfNotEqual: {
+ VideoCommon::LookupData object_1{gen_lookup(address)};
+ VideoCommon::LookupData object_2{gen_lookup(address + 16)};
+ return impl->runtime.HostConditionalRenderingCompareValues(object_1, object_2, qc_dirty,
+ false);
+ }
+ default:
+ return false;
+ }
+}
+
+// Async downloads
+template <typename Traits>
+void QueryCacheBase<Traits>::CommitAsyncFlushes() {
+ // Make sure to have the results synced in Host.
+ NotifyWFI();
+
+ u64 mask{};
+ {
+ std::scoped_lock lk(impl->flush_guard);
+ impl->ForEachStreamer([&mask](StreamerInterface* streamer) {
+ bool local_result = streamer->HasUnsyncedQueries();
+ if (local_result) {
+ mask |= 1ULL << streamer->GetId();
+ }
+ });
+ impl->flushes_pending.push_back(mask);
+ }
+ std::function<void()> func([this] { UnregisterPending(); });
+ impl->rasterizer.SyncOperation(std::move(func));
+ if (mask == 0) {
+ return;
+ }
+ u64 ran_mask = ~mask;
+ while (mask) {
+ impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) {
+ u64 dep_mask = streamer->GetDependentMask();
+ if ((dep_mask & ~ran_mask) != 0) {
+ return;
+ }
+ u64 index = streamer->GetId();
+ ran_mask |= (1ULL << index);
+ mask &= ~(1ULL << index);
+ streamer->PushUnsyncedQueries();
+ });
+ }
+}
+
+template <typename Traits>
+bool QueryCacheBase<Traits>::HasUncommittedFlushes() const {
+ bool result = false;
+ impl->ForEachStreamer([&result](StreamerInterface* streamer) {
+ result |= streamer->HasUnsyncedQueries();
+ return result;
+ });
+ return result;
+}
+
+template <typename Traits>
+bool QueryCacheBase<Traits>::ShouldWaitAsyncFlushes() {
+ std::scoped_lock lk(impl->flush_guard);
+ return !impl->flushes_pending.empty() && impl->flushes_pending.front() != 0ULL;
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::PopAsyncFlushes() {
+ u64 mask;
+ {
+ std::scoped_lock lk(impl->flush_guard);
+ mask = impl->flushes_pending.front();
+ impl->flushes_pending.pop_front();
+ }
+ if (mask == 0) {
+ return;
+ }
+ u64 ran_mask = ~mask;
+ while (mask) {
+ impl->ForEachStreamerIn(mask, [&mask, &ran_mask](StreamerInterface* streamer) {
+ u64 dep_mask = streamer->GetDependenceMask();
+ if ((dep_mask & ~ran_mask) != 0) {
+ return;
+ }
+ u64 index = streamer->GetId();
+ ran_mask |= (1ULL << index);
+ mask &= ~(1ULL << index);
+ streamer->PopUnsyncedQueries();
+ });
+ }
+}
+
+// Invalidation
+
+template <typename Traits>
+void QueryCacheBase<Traits>::InvalidateQuery(QueryCacheBase<Traits>::QueryLocation location) {
+ auto* query_base = impl->ObtainQuery(location);
+ if (!query_base) {
+ return;
+ }
+ query_base->flags |= QueryFlagBits::IsInvalidated;
+}
+
+template <typename Traits>
+bool QueryCacheBase<Traits>::IsQueryDirty(QueryCacheBase<Traits>::QueryLocation location) {
+ auto* query_base = impl->ObtainQuery(location);
+ if (!query_base) {
+ return false;
+ }
+ return True(query_base->flags & QueryFlagBits::IsHostManaged) &&
+ False(query_base->flags & QueryFlagBits::IsGuestSynced);
+}
+
+template <typename Traits>
+bool QueryCacheBase<Traits>::SemiFlushQueryDirty(QueryCacheBase<Traits>::QueryLocation location) {
+ auto* query_base = impl->ObtainQuery(location);
+ if (!query_base) {
+ return false;
+ }
+ if (True(query_base->flags & QueryFlagBits::IsFinalValueSynced) &&
+ False(query_base->flags & QueryFlagBits::IsGuestSynced)) {
+ auto* ptr = impl->cpu_memory.GetPointer(query_base->guest_address);
+ if (True(query_base->flags & QueryFlagBits::HasTimestamp)) {
+ std::memcpy(ptr, &query_base->value, sizeof(query_base->value));
+ return false;
+ }
+ u32 value_l = static_cast<u32>(query_base->value);
+ std::memcpy(ptr, &value_l, sizeof(value_l));
+ return false;
+ }
+ return True(query_base->flags & QueryFlagBits::IsHostManaged) &&
+ False(query_base->flags & QueryFlagBits::IsGuestSynced);
+}
+
+template <typename Traits>
+void QueryCacheBase<Traits>::RequestGuestHostSync() {
+ impl->rasterizer.ReleaseFences();
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/query_cache/query_cache_base.h b/src/video_core/query_cache/query_cache_base.h
new file mode 100644
index 000000000..07be421c6
--- /dev/null
+++ b/src/video_core/query_cache/query_cache_base.h
@@ -0,0 +1,181 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <optional>
+#include <span>
+#include <unordered_map>
+#include <utility>
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "core/memory.h"
+#include "video_core/control/channel_state_cache.h"
+#include "video_core/query_cache/query_base.h"
+#include "video_core/query_cache/types.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace VideoCore {
+class RasterizerInterface;
+}
+
+namespace Tegra {
+class GPU;
+}
+
+namespace VideoCommon {
+
+struct LookupData {
+ VAddr address;
+ QueryBase* found_query;
+};
+
+template <typename Traits>
+class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
+ using RuntimeType = typename Traits::RuntimeType;
+
+public:
+ union QueryLocation {
+ BitField<27, 5, u32> stream_id;
+ BitField<0, 27, u32> query_id;
+ u32 raw;
+
+ std::pair<size_t, size_t> unpack() const {
+ return {static_cast<size_t>(stream_id.Value()), static_cast<size_t>(query_id.Value())};
+ }
+ };
+
+ explicit QueryCacheBase(Tegra::GPU& gpu, VideoCore::RasterizerInterface& rasterizer_,
+ Core::Memory::Memory& cpu_memory_, RuntimeType& runtime_);
+
+ ~QueryCacheBase();
+
+ void InvalidateRegion(VAddr addr, std::size_t size) {
+ IterateCache<true>(addr, size,
+ [this](QueryLocation location) { InvalidateQuery(location); });
+ }
+
+ void FlushRegion(VAddr addr, std::size_t size) {
+ bool result = false;
+ IterateCache<false>(addr, size, [this, &result](QueryLocation location) {
+ result |= SemiFlushQueryDirty(location);
+ return result;
+ });
+ if (result) {
+ RequestGuestHostSync();
+ }
+ }
+
+ static u64 BuildMask(std::span<const QueryType> types) {
+ u64 mask = 0;
+ for (auto query_type : types) {
+ mask |= 1ULL << (static_cast<u64>(query_type));
+ }
+ return mask;
+ }
+
+ /// Return true when a CPU region is modified from the GPU
+ [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size) {
+ bool result = false;
+ IterateCache<false>(addr, size, [this, &result](QueryLocation location) {
+ result |= IsQueryDirty(location);
+ return result;
+ });
+ return result;
+ }
+
+ void CounterEnable(QueryType counter_type, bool is_enabled);
+
+ void CounterReset(QueryType counter_type);
+
+ void CounterClose(QueryType counter_type);
+
+ void CounterReport(GPUVAddr addr, QueryType counter_type, QueryPropertiesFlags flags,
+ u32 payload, u32 subreport);
+
+ void NotifyWFI();
+
+ bool AccelerateHostConditionalRendering();
+
+ // Async downloads
+ void CommitAsyncFlushes();
+
+ bool HasUncommittedFlushes() const;
+
+ bool ShouldWaitAsyncFlushes();
+
+ void PopAsyncFlushes();
+
+ void NotifySegment(bool resume);
+
+ void BindToChannel(s32 id) override;
+
+protected:
+ template <bool remove_from_cache, typename Func>
+ void IterateCache(VAddr addr, std::size_t size, Func&& func) {
+ static constexpr bool RETURNS_BOOL =
+ std::is_same_v<std::invoke_result<Func, QueryLocation>, bool>;
+ const u64 addr_begin = addr;
+ const u64 addr_end = addr_begin + size;
+
+ const u64 page_end = addr_end >> Core::Memory::YUZU_PAGEBITS;
+ std::scoped_lock lock(cache_mutex);
+ for (u64 page = addr_begin >> Core::Memory::YUZU_PAGEBITS; page <= page_end; ++page) {
+ const u64 page_start = page << Core::Memory::YUZU_PAGEBITS;
+ const auto in_range = [page_start, addr_begin, addr_end](const u32 query_location) {
+ const u64 cache_begin = page_start + query_location;
+ const u64 cache_end = cache_begin + sizeof(u32);
+ return cache_begin < addr_end && addr_begin < cache_end;
+ };
+ const auto& it = cached_queries.find(page);
+ if (it == std::end(cached_queries)) {
+ continue;
+ }
+ auto& contents = it->second;
+ for (auto& query : contents) {
+ if (!in_range(query.first)) {
+ continue;
+ }
+ if constexpr (RETURNS_BOOL) {
+ if (func(query.second)) {
+ return;
+ }
+ } else {
+ func(query.second);
+ }
+ }
+ if constexpr (remove_from_cache) {
+ const auto in_range2 = [&](const std::pair<u32, QueryLocation>& pair) {
+ return in_range(pair.first);
+ };
+ std::erase_if(contents, in_range2);
+ }
+ }
+ }
+
+ using ContentCache = std::unordered_map<u64, std::unordered_map<u32, QueryLocation>>;
+
+ void InvalidateQuery(QueryLocation location);
+ bool IsQueryDirty(QueryLocation location);
+ bool SemiFlushQueryDirty(QueryLocation location);
+ void RequestGuestHostSync();
+ void UnregisterPending();
+
+ std::unordered_map<u64, std::unordered_map<u32, QueryLocation>> cached_queries;
+ std::mutex cache_mutex;
+
+ struct QueryCacheBaseImpl;
+ friend struct QueryCacheBaseImpl;
+ friend RuntimeType;
+
+ std::unique_ptr<QueryCacheBaseImpl> impl;
+};
+
+} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/query_stream.h b/src/video_core/query_cache/query_stream.h
new file mode 100644
index 000000000..39da6ac07
--- /dev/null
+++ b/src/video_core/query_cache/query_stream.h
@@ -0,0 +1,149 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <deque>
+#include <optional>
+#include <vector>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "video_core/query_cache/bank_base.h"
+#include "video_core/query_cache/query_base.h"
+
+namespace VideoCommon {
+
+class StreamerInterface {
+public:
+ explicit StreamerInterface(size_t id_) : id{id_}, dependence_mask{}, dependent_mask{} {}
+ virtual ~StreamerInterface() = default;
+
+ virtual QueryBase* GetQuery(size_t id) = 0;
+
+ virtual void StartCounter() {
+ /* Do Nothing */
+ }
+
+ virtual void PauseCounter() {
+ /* Do Nothing */
+ }
+
+ virtual void ResetCounter() {
+ /* Do Nothing */
+ }
+
+ virtual void CloseCounter() {
+ /* Do Nothing */
+ }
+
+ virtual bool HasPendingSync() const {
+ return false;
+ }
+
+ virtual void PresyncWrites() {
+ /* Do Nothing */
+ }
+
+ virtual void SyncWrites() {
+ /* Do Nothing */
+ }
+
+ virtual size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
+ std::optional<u32> subreport = std::nullopt) = 0;
+
+ virtual bool HasUnsyncedQueries() const {
+ return false;
+ }
+
+ virtual void PushUnsyncedQueries() {
+ /* Do Nothing */
+ }
+
+ virtual void PopUnsyncedQueries() {
+ /* Do Nothing */
+ }
+
+ virtual void Free(size_t query_id) = 0;
+
+ size_t GetId() const {
+ return id;
+ }
+
+ u64 GetDependenceMask() const {
+ return dependence_mask;
+ }
+
+ u64 GetDependentMask() const {
+ return dependence_mask;
+ }
+
+ u64 GetAmmendValue() const {
+ return ammend_value;
+ }
+
+ void SetAccumulationValue(u64 new_value) {
+ acumulation_value = new_value;
+ }
+
+protected:
+ void MakeDependent(StreamerInterface* depend_on) {
+ dependence_mask |= 1ULL << depend_on->id;
+ depend_on->dependent_mask |= 1ULL << id;
+ }
+
+ const size_t id;
+ u64 dependence_mask;
+ u64 dependent_mask;
+ u64 ammend_value{};
+ u64 acumulation_value{};
+};
+
+template <typename QueryType>
+class SimpleStreamer : public StreamerInterface {
+public:
+ explicit SimpleStreamer(size_t id_) : StreamerInterface{id_} {}
+ virtual ~SimpleStreamer() = default;
+
+protected:
+ virtual QueryType* GetQuery(size_t query_id) override {
+ if (query_id < slot_queries.size()) {
+ return &slot_queries[query_id];
+ }
+ return nullptr;
+ }
+
+ virtual void Free(size_t query_id) override {
+ std::scoped_lock lk(guard);
+ ReleaseQuery(query_id);
+ }
+
+ template <typename... Args, typename = decltype(QueryType(std::declval<Args>()...))>
+ size_t BuildQuery(Args&&... args) {
+ std::scoped_lock lk(guard);
+ if (!old_queries.empty()) {
+ size_t new_id = old_queries.front();
+ old_queries.pop_front();
+ new (&slot_queries[new_id]) QueryType(std::forward<Args>(args)...);
+ return new_id;
+ }
+ size_t new_id = slot_queries.size();
+ slot_queries.emplace_back(std::forward<Args>(args)...);
+ return new_id;
+ }
+
+ void ReleaseQuery(size_t query_id) {
+
+ if (query_id < slot_queries.size()) {
+ old_queries.push_back(query_id);
+ return;
+ }
+ UNREACHABLE();
+ }
+
+ std::mutex guard;
+ std::deque<QueryType> slot_queries;
+ std::deque<size_t> old_queries;
+};
+
+} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/query_cache/types.h b/src/video_core/query_cache/types.h
new file mode 100644
index 000000000..e9226bbfc
--- /dev/null
+++ b/src/video_core/query_cache/types.h
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace VideoCommon {
+
+enum class QueryPropertiesFlags : u32 {
+ HasTimeout = 1 << 0,
+ IsAFence = 1 << 1,
+};
+DECLARE_ENUM_FLAG_OPERATORS(QueryPropertiesFlags)
+
+// This should always be equivalent to maxwell3d Report Semaphore Reports
+enum class QueryType : u32 {
+ Payload = 0, // "None" in docs, but confirmed via hardware to return the payload
+ VerticesGenerated = 1,
+ ZPassPixelCount = 2,
+ PrimitivesGenerated = 3,
+ AlphaBetaClocks = 4,
+ VertexShaderInvocations = 5,
+ StreamingPrimitivesNeededMinusSucceeded = 6,
+ GeometryShaderInvocations = 7,
+ GeometryShaderPrimitivesGenerated = 9,
+ ZCullStats0 = 10,
+ StreamingPrimitivesSucceeded = 11,
+ ZCullStats1 = 12,
+ StreamingPrimitivesNeeded = 13,
+ ZCullStats2 = 14,
+ ClipperInvocations = 15,
+ ZCullStats3 = 16,
+ ClipperPrimitivesGenerated = 17,
+ VtgPrimitivesOut = 18,
+ PixelShaderInvocations = 19,
+ ZPassPixelCount64 = 21,
+ IEEECleanColorTarget = 24,
+ IEEECleanZetaTarget = 25,
+ StreamingByteCount = 26,
+ TessellationInitInvocations = 27,
+ BoundingRectangle = 28,
+ TessellationShaderInvocations = 29,
+ TotalStreamingPrimitivesNeededMinusSucceeded = 30,
+ TessellationShaderPrimitivesGenerated = 31,
+ // max.
+ MaxQueryTypes,
+};
+
+// Comparison modes for Host Conditional Rendering
+enum class ComparisonMode : u32 {
+ False = 0,
+ True = 1,
+ Conditional = 2,
+ IfEqual = 3,
+ IfNotEqual = 4,
+ MaxComparisonMode,
+};
+
+// Reduction ops.
+enum class ReductionOp : u32 {
+ RedAdd = 0,
+ RedMin = 1,
+ RedMax = 2,
+ RedInc = 3,
+ RedDec = 4,
+ RedAnd = 5,
+ RedOr = 6,
+ RedXor = 7,
+ MaxReductionOp,
+};
+
+} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index cb8029a4f..af1469147 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -12,6 +12,7 @@
#include "video_core/cache_types.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/gpu.h"
+#include "video_core/query_cache/types.h"
#include "video_core/rasterizer_download_area.h"
namespace Tegra {
@@ -26,11 +27,6 @@ struct ChannelState;
namespace VideoCore {
-enum class QueryType {
- SamplesPassed,
-};
-constexpr std::size_t NumQueryTypes = 1;
-
enum class LoadCallbackStage {
Prepare,
Build,
@@ -58,10 +54,11 @@ public:
virtual void DispatchCompute() = 0;
/// Resets the counter of a query
- virtual void ResetCounter(QueryType type) = 0;
+ virtual void ResetCounter(VideoCommon::QueryType type) = 0;
/// Records a GPU query and caches it
- virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0;
+ virtual void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
+ VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) = 0;
/// Signal an uniform buffer binding
virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -83,7 +80,7 @@ public:
virtual void SignalReference() = 0;
/// Release all pending fences.
- virtual void ReleaseFences() = 0;
+ virtual void ReleaseFences(bool force = true) = 0;
/// Notify rasterizer that all caches should be flushed to Switch memory
virtual void FlushAll() = 0;
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 3e12a8813..78ea5208b 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -89,9 +89,6 @@ public:
void RequestScreenshot(void* data, std::function<void(bool)> callback,
const Layout::FramebufferLayout& layout);
- /// This is called to notify the rendering backend of a surface change
- virtual void NotifySurfaceChanged() {}
-
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
std::unique_ptr<Core::Frontend::GraphicsContext> context;
diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp
index 92ecf6682..65cd5aa06 100644
--- a/src/video_core/renderer_null/null_rasterizer.cpp
+++ b/src/video_core/renderer_null/null_rasterizer.cpp
@@ -26,16 +26,18 @@ void RasterizerNull::Draw(bool is_indexed, u32 instance_count) {}
void RasterizerNull::DrawTexture() {}
void RasterizerNull::Clear(u32 layer_count) {}
void RasterizerNull::DispatchCompute() {}
-void RasterizerNull::ResetCounter(VideoCore::QueryType type) {}
-void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
- std::optional<u64> timestamp) {
+void RasterizerNull::ResetCounter(VideoCommon::QueryType type) {}
+void RasterizerNull::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
+ VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
if (!gpu_memory) {
return;
}
-
- gpu_memory->Write(gpu_addr, u64{0});
- if (timestamp) {
- gpu_memory->Write(gpu_addr + 8, *timestamp);
+ if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
+ u64 ticks = m_gpu.GetTicks();
+ gpu_memory->Write<u64>(gpu_addr + 8, ticks);
+ gpu_memory->Write<u64>(gpu_addr, static_cast<u64>(payload));
+ } else {
+ gpu_memory->Write<u32>(gpu_addr, payload);
}
}
void RasterizerNull::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -74,7 +76,7 @@ void RasterizerNull::SignalSyncPoint(u32 value) {
syncpoint_manager.IncrementHost(value);
}
void RasterizerNull::SignalReference() {}
-void RasterizerNull::ReleaseFences() {}
+void RasterizerNull::ReleaseFences(bool) {}
void RasterizerNull::FlushAndInvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {}
void RasterizerNull::WaitForIdle() {}
void RasterizerNull::FragmentBarrier() {}
diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h
index 93b9a6971..23001eeb8 100644
--- a/src/video_core/renderer_null/null_rasterizer.h
+++ b/src/video_core/renderer_null/null_rasterizer.h
@@ -42,8 +42,9 @@ public:
void DrawTexture() override;
void Clear(u32 layer_count) override;
void DispatchCompute() override;
- void ResetCounter(VideoCore::QueryType type) override;
- void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
+ void ResetCounter(VideoCommon::QueryType type) override;
+ void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
+ VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
void FlushAll() override;
@@ -63,7 +64,7 @@ public:
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
- void ReleaseFences() override;
+ void ReleaseFences(bool force) override;
void FlushAndInvalidateRegion(
VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void WaitForIdle() override;
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index 99d7347f5..ec142d48e 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -27,7 +27,7 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
} // Anonymous namespace
QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_)
- : QueryCacheBase(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {}
+ : QueryCacheLegacy(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {}
QueryCache::~QueryCache() = default;
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index 872513f22..0721e0b3d 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -26,7 +26,7 @@ class RasterizerOpenGL;
using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
class QueryCache final
- : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
+ : public VideoCommon::QueryCacheLegacy<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_);
~QueryCache();
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 1ba31be88..27e2de1bf 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -380,18 +380,55 @@ 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();
}
-void RasterizerOpenGL::ResetCounter(VideoCore::QueryType type) {
- query_cache.ResetCounter(type);
+void RasterizerOpenGL::ResetCounter(VideoCommon::QueryType type) {
+ if (type == VideoCommon::QueryType::ZPassPixelCount64) {
+ query_cache.ResetCounter(VideoCore::QueryType::SamplesPassed);
+ }
}
-void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
- std::optional<u64> timestamp) {
- query_cache.Query(gpu_addr, type, timestamp);
+void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
+ VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
+ if (type == VideoCommon::QueryType::ZPassPixelCount64) {
+ if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
+ query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, {gpu.GetTicks()});
+ } else {
+ query_cache.Query(gpu_addr, VideoCore::QueryType::SamplesPassed, std::nullopt);
+ }
+ return;
+ }
+ if (type != VideoCommon::QueryType::Payload) {
+ payload = 1u;
+ }
+ std::function<void()> func([this, gpu_addr, flags, memory_manager = gpu_memory, payload]() {
+ if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) {
+ u64 ticks = gpu.GetTicks();
+ memory_manager->Write<u64>(gpu_addr + 8, ticks);
+ memory_manager->Write<u64>(gpu_addr, static_cast<u64>(payload));
+ } else {
+ memory_manager->Write<u32>(gpu_addr, payload);
+ }
+ });
+ if (True(flags & VideoCommon::QueryPropertiesFlags::IsAFence)) {
+ SignalFence(std::move(func));
+ return;
+ }
+ func();
}
void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -562,8 +599,8 @@ void RasterizerOpenGL::SignalReference() {
fence_manager.SignalOrdering();
}
-void RasterizerOpenGL::ReleaseFences() {
- fence_manager.WaitPendingFences();
+void RasterizerOpenGL::ReleaseFences(bool force) {
+ fence_manager.WaitPendingFences(force);
}
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size,
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 8eda2ddba..ceffe1f1e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -86,8 +86,9 @@ public:
void DrawTexture() override;
void Clear(u32 layer_count) override;
void DispatchCompute() override;
- void ResetCounter(VideoCore::QueryType type) override;
- void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
+ void ResetCounter(VideoCommon::QueryType type) override;
+ void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
+ VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
void FlushAll() override;
@@ -107,7 +108,7 @@ public:
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
- void ReleaseFences() override;
+ void ReleaseFences(bool force = true) override;
void FlushAndInvalidateRegion(
VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void WaitForIdle() override;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 9cafd2983..512eef575 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -1048,6 +1048,10 @@ void Image::Scale(bool up_scale) {
}
bool Image::ScaleUp(bool ignore) {
+ const auto& resolution = runtime->resolution;
+ if (!resolution.active) {
+ return false;
+ }
if (True(flags & ImageFlagBits::Rescaled)) {
return false;
}
@@ -1060,9 +1064,6 @@ bool Image::ScaleUp(bool ignore) {
return false;
}
flags |= ImageFlagBits::Rescaled;
- if (!runtime->resolution.active) {
- return false;
- }
has_scaled = true;
if (ignore) {
current_texture = upscaled_backup.handle;
@@ -1073,13 +1074,14 @@ bool Image::ScaleUp(bool ignore) {
}
bool Image::ScaleDown(bool ignore) {
- if (False(flags & ImageFlagBits::Rescaled)) {
+ const auto& resolution = runtime->resolution;
+ if (!resolution.active) {
return false;
}
- flags &= ~ImageFlagBits::Rescaled;
- if (!runtime->resolution.active) {
+ if (False(flags & ImageFlagBits::Rescaled)) {
return false;
}
+ flags &= ~ImageFlagBits::Rescaled;
if (ignore) {
current_texture = texture.handle;
return true;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 3676eaaa9..e71b87e99 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -118,6 +118,8 @@ public:
void InsertUploadMemoryBarrier();
+ void TransitionImageLayout(Image& image) {}
+
FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
bool HasNativeBgr() const noexcept {
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index c7dc7e0a1..5ea9e2378 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -116,6 +116,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
{GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
+ {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8}, // X8_D24_UNORM
{GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index 1032c9d12..f01d2394e 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -9,6 +9,7 @@
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
+#include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
@@ -433,6 +434,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
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)),
+ convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
@@ -557,6 +559,13 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
}
+void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer,
+ ImageView& src_image_view) {
+ ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
+ convert_d32f_to_abgr8_frag);
+ ConvertDepthStencil(*convert_d32f_to_abgr8_pipeline, dst_framebuffer, src_image_view);
+}
+
void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
ImageView& src_image_view) {
ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
@@ -609,6 +618,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
const VkPipelineLayout layout = *clear_color_pipeline_layout;
scheduler.RequestRenderpass(dst_framebuffer);
scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
+ constexpr std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f};
+ cmdbuf.SetBlendConstants(blend_constants.data());
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
BindBlitState(cmdbuf, dst_region);
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
@@ -865,7 +876,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .depthTestEnable = VK_FALSE,
+ .depthTestEnable = key.depth_clear,
.depthWriteEnable = key.depth_clear,
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
.depthBoundsTestEnable = VK_FALSE,
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index dcfe217aa..a032c71fb 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -67,6 +67,8 @@ public:
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
+ void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
+
void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
@@ -128,6 +130,7 @@ private:
vk::ShaderModule convert_depth_to_float_frag;
vk::ShaderModule convert_float_to_depth_frag;
vk::ShaderModule convert_abgr8_to_d24s8_frag;
+ vk::ShaderModule convert_d32f_to_abgr8_frag;
vk::ShaderModule convert_d24s8_to_abgr8_frag;
vk::ShaderModule convert_s8d24_to_abgr8_frag;
vk::Sampler linear_sampler;
@@ -146,6 +149,7 @@ private:
vk::Pipeline convert_d16_to_r16_pipeline;
vk::Pipeline convert_r16_to_d16_pipeline;
vk::Pipeline convert_abgr8_to_d24s8_pipeline;
+ vk::Pipeline convert_d32f_to_abgr8_pipeline;
vk::Pipeline convert_d24s8_to_abgr8_pipeline;
vk::Pipeline convert_s8d24_to_abgr8_pipeline;
};
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 35bf80ea3..a08f2f67f 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -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
@@ -214,8 +214,9 @@ struct FormatTuple {
{VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
// Depth formats
- {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
- {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
+ {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
+ {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
+ {VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM
// Stencil formats
{VK_FORMAT_S8_UINT, Attachable}, // S8_UINT
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 89e98425e..14e257cf7 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -35,8 +35,6 @@ class GPU;
namespace Vulkan {
-using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>;
-
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
VkSurfaceKHR surface);
@@ -58,10 +56,6 @@ public:
return device.GetDriverName();
}
- void NotifySurfaceChanged() override {
- present_manager.NotifySurfaceChanged();
- }
-
private:
void Report() const;
@@ -75,7 +69,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 31928bb94..52fc142d1 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -96,6 +96,7 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
switch (framebuffer.pixel_format) {
case Service::android::PixelFormat::Rgba8888:
+ case Service::android::PixelFormat::Rgbx8888:
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
case Service::android::PixelFormat::Rgb565:
return VK_FORMAT_R5G6B5_UNORM_PACK16;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 60a6ac651..d8148e89a 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -61,6 +61,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
if (device.IsExtTransformFeedbackSupported()) {
flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
}
+ if (device.IsExtConditionalRendering()) {
+ flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
+ }
const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
@@ -529,17 +532,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 54ee030ce..617f92910 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -3,6 +3,7 @@
#include <array>
#include <memory>
+#include <numeric>
#include <optional>
#include <utility>
@@ -11,7 +12,13 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
+#include "common/vector_math.h"
#include "video_core/host_shaders/astc_decoder_comp_spv.h"
+#include "video_core/host_shaders/convert_msaa_to_non_msaa_comp_spv.h"
+#include "video_core/host_shaders/convert_non_msaa_to_msaa_comp_spv.h"
+#include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h"
+#include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h"
+#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
@@ -57,6 +64,30 @@ constexpr std::array<VkDescriptorSetLayoutBinding, 2> INPUT_OUTPUT_DESCRIPTOR_SE
},
}};
+constexpr std::array<VkDescriptorSetLayoutBinding, 3> QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS{{
+ {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+ {
+ .binding = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+ {
+ .binding = 2,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+}};
+
constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 2,
@@ -67,6 +98,16 @@ constexpr DescriptorBankInfo INPUT_OUTPUT_BANK_INFO{
.score = 2,
};
+constexpr DescriptorBankInfo QUERIES_SCAN_BANK_INFO{
+ .uniform_buffers = 0,
+ .storage_buffers = 3,
+ .texture_buffers = 0,
+ .image_buffers = 0,
+ .textures = 0,
+ .images = 0,
+ .score = 3,
+};
+
constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> ASTC_DESCRIPTOR_SET_BINDINGS{{
{
.binding = ASTC_BINDING_INPUT_BUFFER,
@@ -94,6 +135,33 @@ constexpr DescriptorBankInfo ASTC_BANK_INFO{
.score = 2,
};
+constexpr std::array<VkDescriptorSetLayoutBinding, ASTC_NUM_BINDINGS> MSAA_DESCRIPTOR_SET_BINDINGS{{
+ {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+ {
+ .binding = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+}};
+
+constexpr DescriptorBankInfo MSAA_BANK_INFO{
+ .uniform_buffers = 0,
+ .storage_buffers = 0,
+ .texture_buffers = 0,
+ .image_buffers = 0,
+ .textures = 0,
+ .images = 2,
+ .score = 2,
+};
+
constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{
.dstBinding = 0,
.dstArrayElement = 0,
@@ -103,6 +171,24 @@ constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLAT
.stride = sizeof(DescriptorUpdateEntry),
};
+constexpr VkDescriptorUpdateTemplateEntry QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE{
+ .dstBinding = 0,
+ .dstArrayElement = 0,
+ .descriptorCount = 3,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .offset = 0,
+ .stride = sizeof(DescriptorUpdateEntry),
+};
+
+constexpr VkDescriptorUpdateTemplateEntry MSAA_DESCRIPTOR_UPDATE_TEMPLATE{
+ .dstBinding = 0,
+ .dstArrayElement = 0,
+ .descriptorCount = 2,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .offset = 0,
+ .stride = sizeof(DescriptorUpdateEntry),
+};
+
constexpr std::array<VkDescriptorUpdateTemplateEntry, ASTC_NUM_BINDINGS>
ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{
{
@@ -131,13 +217,21 @@ struct AstcPushConstants {
u32 block_height;
u32 block_height_mask;
};
+
+struct QueriesPrefixScanPushConstants {
+ u32 min_accumulation_base;
+ u32 max_accumulation_base;
+ u32 accumulation_limit;
+ u32 buffer_offset;
+};
} // Anonymous namespace
ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
vk::Span<VkDescriptorSetLayoutBinding> bindings,
vk::Span<VkDescriptorUpdateTemplateEntry> templates,
const DescriptorBankInfo& bank_info,
- vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code)
+ vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
+ std::optional<u32> optional_subgroup_size)
: device{device_} {
descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
@@ -170,6 +264,9 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
});
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info);
}
+ if (code.empty()) {
+ return;
+ }
module = device.GetLogical().CreateShaderModule({
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.pNext = nullptr,
@@ -178,13 +275,19 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
.pCode = code.data(),
});
device.SaveShader(code);
+ const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
+ };
+ bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
pipeline = device.GetLogical().CreateComputePipeline({
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stage{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
- .pNext = nullptr,
+ .pNext = use_setup_size ? &subgroup_size_ci : nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = *module,
@@ -302,6 +405,123 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
return {staging.buffer, staging.offset};
}
+ConditionalRenderingResolvePass::ConditionalRenderingResolvePass(
+ const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
+ : ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
+ INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, nullptr,
+ RESOLVE_CONDITIONAL_RENDER_COMP_SPV),
+ scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
+
+void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_buffer,
+ u32 src_offset, bool compare_to_zero) {
+ const size_t compare_size = compare_to_zero ? 8 : 24;
+
+ compute_pass_descriptor_queue.Acquire();
+ compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, compare_size);
+ compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, sizeof(u32));
+ const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
+
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([this, descriptor_data](vk::CommandBuffer cmdbuf) {
+ static constexpr VkMemoryBarrier read_barrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
+ };
+ static constexpr VkMemoryBarrier write_barrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
+ };
+ const VkDescriptorSet set = descriptor_allocator.Commit();
+ device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
+
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
+ cmdbuf.Dispatch(1, 1, 1);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0, write_barrier);
+ });
+}
+
+QueriesPrefixScanPass::QueriesPrefixScanPass(
+ const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
+ : ComputePass(
+ device_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS,
+ QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE, QUERIES_SCAN_BANK_INFO,
+ COMPUTE_PUSH_CONSTANT_RANGE<sizeof(QueriesPrefixScanPushConstants)>,
+ device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT) &&
+ device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) &&
+ device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) &&
+ device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT)
+ ? std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_COMP_SPV)
+ : std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_NOSUBGROUPS_COMP_SPV)),
+ scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
+
+void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer,
+ VkBuffer src_buffer, size_t number_of_sums,
+ size_t min_accumulation_limit, size_t max_accumulation_limit) {
+ size_t current_runs = number_of_sums;
+ size_t offset = 0;
+ while (current_runs != 0) {
+ static constexpr size_t DISPATCH_SIZE = 2048U;
+ size_t runs_to_do = std::min<size_t>(current_runs, DISPATCH_SIZE);
+ current_runs -= runs_to_do;
+ compute_pass_descriptor_queue.Acquire();
+ compute_pass_descriptor_queue.AddBuffer(src_buffer, 0, number_of_sums * sizeof(u64));
+ compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, number_of_sums * sizeof(u64));
+ compute_pass_descriptor_queue.AddBuffer(accumulation_buffer, 0, sizeof(u64));
+ const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
+ size_t used_offset = offset;
+ offset += runs_to_do;
+
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([this, descriptor_data, min_accumulation_limit, max_accumulation_limit,
+ runs_to_do, used_offset](vk::CommandBuffer cmdbuf) {
+ static constexpr VkMemoryBarrier read_barrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
+ };
+ static constexpr VkMemoryBarrier write_barrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
+ VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
+ VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
+ VK_ACCESS_UNIFORM_READ_BIT |
+ VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
+ };
+ const QueriesPrefixScanPushConstants uniforms{
+ .min_accumulation_base = static_cast<u32>(min_accumulation_limit),
+ .max_accumulation_base = static_cast<u32>(max_accumulation_limit),
+ .accumulation_limit = static_cast<u32>(runs_to_do - 1),
+ .buffer_offset = static_cast<u32>(used_offset),
+ };
+ const VkDescriptorSet set = descriptor_allocator.Commit();
+ device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
+
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
+ cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
+ cmdbuf.Dispatch(1, 1, 1);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0,
+ write_barrier);
+ });
+ }
+}
+
ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
@@ -413,4 +633,100 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
scheduler.Finish();
}
+MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
+ DescriptorPool& descriptor_pool_,
+ StagingBufferPool& staging_buffer_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
+ : ComputePass(device_, descriptor_pool_, MSAA_DESCRIPTOR_SET_BINDINGS,
+ MSAA_DESCRIPTOR_UPDATE_TEMPLATE, MSAA_BANK_INFO, {},
+ CONVERT_NON_MSAA_TO_MSAA_COMP_SPV),
+ scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
+ compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {
+ const auto make_msaa_pipeline = [this](size_t i, std::span<const u32> code) {
+ modules[i] = device.GetLogical().CreateShaderModule({
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .codeSize = static_cast<u32>(code.size_bytes()),
+ .pCode = code.data(),
+ });
+ pipelines[i] = device.GetLogical().CreateComputePipeline({
+ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage = VK_SHADER_STAGE_COMPUTE_BIT,
+ .module = *modules[i],
+ .pName = "main",
+ .pSpecializationInfo = nullptr,
+ },
+ .layout = *layout,
+ .basePipelineHandle = nullptr,
+ .basePipelineIndex = 0,
+ });
+ };
+ make_msaa_pipeline(0, CONVERT_NON_MSAA_TO_MSAA_COMP_SPV);
+ make_msaa_pipeline(1, CONVERT_MSAA_TO_NON_MSAA_COMP_SPV);
+}
+
+MSAACopyPass::~MSAACopyPass() = default;
+
+void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image,
+ std::span<const VideoCommon::ImageCopy> copies,
+ bool msaa_to_non_msaa) {
+ const VkPipeline msaa_pipeline = *pipelines[msaa_to_non_msaa ? 1 : 0];
+ scheduler.RequestOutsideRenderPassOperationContext();
+ for (const VideoCommon::ImageCopy& copy : copies) {
+ ASSERT(copy.src_subresource.base_layer == 0);
+ ASSERT(copy.src_subresource.num_layers == 1);
+ ASSERT(copy.dst_subresource.base_layer == 0);
+ ASSERT(copy.dst_subresource.num_layers == 1);
+
+ compute_pass_descriptor_queue.Acquire();
+ compute_pass_descriptor_queue.AddImage(
+ src_image.StorageImageView(copy.src_subresource.base_level));
+ compute_pass_descriptor_queue.AddImage(
+ dst_image.StorageImageView(copy.dst_subresource.base_level));
+ const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
+
+ const Common::Vec3<u32> num_dispatches = {
+ Common::DivCeil(copy.extent.width, 8U),
+ Common::DivCeil(copy.extent.height, 8U),
+ copy.extent.depth,
+ };
+
+ scheduler.Record([this, dst = dst_image.Handle(), msaa_pipeline, num_dispatches,
+ descriptor_data](vk::CommandBuffer cmdbuf) {
+ const VkDescriptorSet set = descriptor_allocator.Commit();
+ device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, msaa_pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
+ cmdbuf.Dispatch(num_dispatches.x, num_dispatches.y, num_dispatches.z);
+ const VkImageMemoryBarrier write_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = dst,
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier);
+ });
+ }
+}
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index dd3927376..7b8f938c1 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -3,6 +3,7 @@
#pragma once
+#include <optional>
#include <span>
#include <utility>
@@ -10,6 +11,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
+#include "video_core/texture_cache/types.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -31,7 +33,8 @@ public:
vk::Span<VkDescriptorSetLayoutBinding> bindings,
vk::Span<VkDescriptorUpdateTemplateEntry> templates,
const DescriptorBankInfo& bank_info,
- vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code);
+ vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
+ std::optional<u32> optional_subgroup_size = std::nullopt);
~ComputePass();
protected:
@@ -82,6 +85,33 @@ private:
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
+class ConditionalRenderingResolvePass final : public ComputePass {
+public:
+ explicit ConditionalRenderingResolvePass(
+ const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
+
+ void Resolve(VkBuffer dst_buffer, VkBuffer src_buffer, u32 src_offset, bool compare_to_zero);
+
+private:
+ Scheduler& scheduler;
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue;
+};
+
+class QueriesPrefixScanPass final : public ComputePass {
+public:
+ explicit QueriesPrefixScanPass(const Device& device_, Scheduler& scheduler_,
+ DescriptorPool& descriptor_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
+
+ void Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer, VkBuffer src_buffer,
+ size_t number_of_sums, size_t min_accumulation_limit, size_t max_accumulation_limit);
+
+private:
+ Scheduler& scheduler;
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue;
+};
+
class ASTCDecoderPass final : public ComputePass {
public:
explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
@@ -101,4 +131,22 @@ private:
MemoryAllocator& memory_allocator;
};
+class MSAACopyPass final : public ComputePass {
+public:
+ explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_,
+ DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
+ ~MSAACopyPass();
+
+ void CopyImage(Image& dst_image, Image& src_image,
+ std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
+
+private:
+ Scheduler& scheduler;
+ StagingBufferPool& staging_buffer_pool;
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue;
+ std::array<vk::ShaderModule, 2> modules;
+ std::array<vk::Pipeline, 2> pipelines;
+};
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h
index 145359d4e..336573574 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.h
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.h
@@ -7,6 +7,7 @@
#include "video_core/fence_manager.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
namespace Core {
@@ -20,7 +21,6 @@ class RasterizerInterface;
namespace Vulkan {
class Device;
-class QueryCache;
class Scheduler;
class InnerFence : public VideoCommon::FenceBase {
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index c1314ca99..a1ec1a100 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -294,10 +294,11 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
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()};
@@ -611,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(hash, key.unique_hashes[index]);
- }
if (!uses_vertex_a || index != 1) {
// Normal path
programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);
@@ -624,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];
}
@@ -664,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;
}
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index d681bd22a..2ef36583b 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -103,8 +103,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
swapchain.GetImageViewFormat())},
use_present_thread{Settings::values.async_presentation.GetValue()},
- image_count{swapchain.GetImageCount()}, last_render_surface{
- render_window_.GetWindowInfo().render_surface} {
+ image_count{swapchain.GetImageCount()} {
auto& dld = device.GetLogical();
cmdpool = dld.CreateCommandPool({
@@ -289,44 +288,36 @@ void PresentManager::PresentThread(std::stop_token token) {
}
}
-void PresentManager::NotifySurfaceChanged() {
-#ifdef ANDROID
- std::scoped_lock lock{recreate_surface_mutex};
- recreate_surface_cv.notify_one();
-#endif
+void PresentManager::RecreateSwapchain(Frame* frame) {
+ swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
+ image_count = swapchain.GetImageCount();
}
void PresentManager::CopyToSwapchain(Frame* frame) {
- MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
-
- const auto recreate_swapchain = [&] {
- swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
- image_count = swapchain.GetImageCount();
- };
-
-#ifdef ANDROID
- std::unique_lock lock{recreate_surface_mutex};
-
- const auto needs_recreation = [&] {
- if (last_render_surface != render_window.GetWindowInfo().render_surface) {
- return true;
- }
- if (swapchain.NeedsRecreation(frame->is_srgb)) {
- return true;
+ bool requires_recreation = false;
+
+ while (true) {
+ try {
+ // Recreate surface and swapchain if needed.
+ if (requires_recreation) {
+ surface = CreateSurface(instance, render_window.GetWindowInfo());
+ RecreateSwapchain(frame);
+ }
+
+ // Draw to swapchain.
+ return CopyToSwapchainImpl(frame);
+ } catch (const vk::Exception& except) {
+ if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) {
+ throw;
+ }
+
+ requires_recreation = true;
}
- return false;
- };
-
- recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
- [&]() { return !needs_recreation(); });
-
- // If the frontend recreated the surface, recreate the renderer surface and swapchain.
- if (last_render_surface != render_window.GetWindowInfo().render_surface) {
- last_render_surface = render_window.GetWindowInfo().render_surface;
- surface = CreateSurface(instance, render_window.GetWindowInfo());
- recreate_swapchain();
}
-#endif
+}
+
+void PresentManager::CopyToSwapchainImpl(Frame* frame) {
+ MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
// If the size or colorspace of the incoming frames has changed, recreate the swapchain
// to account for that.
@@ -334,11 +325,11 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
const bool size_changed =
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
if (srgb_changed || size_changed) {
- recreate_swapchain();
+ RecreateSwapchain(frame);
}
while (swapchain.AcquireNextImage()) {
- recreate_swapchain();
+ RecreateSwapchain(frame);
}
const vk::CommandBuffer cmdbuf{frame->cmdbuf};
@@ -488,4 +479,4 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
swapchain.Present(render_semaphore);
}
-} // namespace Vulkan \ No newline at end of file
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index 83e859416..a3d825fe6 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -54,14 +54,15 @@ public:
/// Waits for the present thread to finish presenting all queued frames.
void WaitPresent();
- /// This is called to notify the rendering backend of a surface change
- void NotifySurfaceChanged();
-
private:
void PresentThread(std::stop_token token);
void CopyToSwapchain(Frame* frame);
+ void CopyToSwapchainImpl(Frame* frame);
+
+ void RecreateSwapchain(Frame* frame);
+
private:
const vk::Instance& instance;
Core::Frontend::EmuWindow& render_window;
@@ -76,16 +77,13 @@ private:
std::queue<Frame*> free_queue;
std::condition_variable_any frame_cv;
std::condition_variable free_cv;
- std::condition_variable recreate_surface_cv;
std::mutex swapchain_mutex;
- std::mutex recreate_surface_mutex;
std::mutex queue_mutex;
std::mutex free_mutex;
std::jthread present_thread;
bool blit_supported;
bool use_present_thread;
std::size_t image_count{};
- void* last_render_surface{};
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 29e0b797b..2edaafa7e 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -1,139 +1,1555 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
-#include <algorithm>
#include <cstddef>
+#include <limits>
+#include <map>
+#include <memory>
+#include <span>
+#include <type_traits>
+#include <unordered_map>
#include <utility>
#include <vector>
+#include "common/bit_util.h"
+#include "common/common_types.h"
+#include "core/memory.h"
+#include "video_core/engines/draw_manager.h"
+#include "video_core/query_cache/query_cache.h"
+#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_resource_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_update_descriptor.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"
namespace Vulkan {
-using VideoCore::QueryType;
+using Tegra::Engines::Maxwell3D;
+using VideoCommon::QueryType;
namespace {
+class SamplesQueryBank : public VideoCommon::BankBase {
+public:
+ static constexpr size_t BANK_SIZE = 256;
+ static constexpr size_t QUERY_SIZE = 8;
+ explicit SamplesQueryBank(const Device& device_, size_t index_)
+ : BankBase(BANK_SIZE), device{device_}, index{index_} {
+ const auto& dev = device.GetLogical();
+ query_pool = dev.CreateQueryPool({
+ .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .queryType = VK_QUERY_TYPE_OCCLUSION,
+ .queryCount = BANK_SIZE,
+ .pipelineStatistics = 0,
+ });
+ Reset();
+ }
-constexpr std::array QUERY_TARGETS = {VK_QUERY_TYPE_OCCLUSION};
+ ~SamplesQueryBank() = default;
-constexpr VkQueryType GetTarget(QueryType type) {
- return QUERY_TARGETS[static_cast<std::size_t>(type)];
-}
+ void Reset() override {
+ ASSERT(references == 0);
+ VideoCommon::BankBase::Reset();
+ const auto& dev = device.GetLogical();
+ dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
+ host_results.fill(0ULL);
+ next_bank = 0;
+ }
+
+ void Sync(size_t start, size_t size) {
+ const auto& dev = device.GetLogical();
+ const VkResult query_result = dev.GetQueryResults(
+ *query_pool, static_cast<u32>(start), static_cast<u32>(size), sizeof(u64) * size,
+ &host_results[start], sizeof(u64), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
+ switch (query_result) {
+ case VK_SUCCESS:
+ return;
+ case VK_ERROR_DEVICE_LOST:
+ device.ReportLoss();
+ [[fallthrough]];
+ default:
+ throw vk::Exception(query_result);
+ }
+ }
+
+ VkQueryPool GetInnerPool() {
+ return *query_pool;
+ }
+
+ size_t GetIndex() const {
+ return index;
+ }
+
+ const std::array<u64, BANK_SIZE>& GetResults() const {
+ return host_results;
+ }
+
+ size_t next_bank;
+
+private:
+ const Device& device;
+ const size_t index;
+ vk::QueryPool query_pool;
+ std::array<u64, BANK_SIZE> host_results;
+};
+
+using BaseStreamer = VideoCommon::SimpleStreamer<VideoCommon::HostQueryBase>;
+
+struct HostSyncValues {
+ VAddr address;
+ size_t size;
+ size_t offset;
+
+ static constexpr bool GeneratesBaseBuffer = false;
+};
+
+class SamplesStreamer : public BaseStreamer {
+public:
+ explicit SamplesStreamer(size_t id_, QueryCacheRuntime& runtime_,
+ VideoCore::RasterizerInterface* rasterizer_, const Device& device_,
+ Scheduler& scheduler_, const MemoryAllocator& memory_allocator_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue,
+ DescriptorPool& descriptor_pool)
+ : BaseStreamer(id_), runtime{runtime_}, rasterizer{rasterizer_}, device{device_},
+ scheduler{scheduler_}, memory_allocator{memory_allocator_} {
+ current_bank = nullptr;
+ current_query = nullptr;
+ ammend_value = 0;
+ acumulation_value = 0;
+ queries_prefix_scan_pass = std::make_unique<QueriesPrefixScanPass>(
+ device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
+
+ const VkBufferCreateInfo buffer_ci = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = 8,
+ .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ };
+ accumulation_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) {
+ cmdbuf.FillBuffer(buffer, 0, 8, 0);
+ });
+ }
+
+ ~SamplesStreamer() = default;
+
+ void StartCounter() override {
+ if (has_started) {
+ return;
+ }
+ ReserveHostQuery();
+ scheduler.Record([query_pool = current_query_pool,
+ query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
+ const bool use_precise = Settings::IsGPULevelHigh();
+ cmdbuf.BeginQuery(query_pool, static_cast<u32>(query_index),
+ use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
+ });
+ has_started = true;
+ }
+
+ void PauseCounter() override {
+ if (!has_started) {
+ return;
+ }
+ scheduler.Record([query_pool = current_query_pool,
+ query_index = current_bank_slot](vk::CommandBuffer cmdbuf) {
+ cmdbuf.EndQuery(query_pool, static_cast<u32>(query_index));
+ });
+ has_started = false;
+ }
+
+ void ResetCounter() override {
+ if (has_started) {
+ PauseCounter();
+ }
+ AbandonCurrentQuery();
+ std::function<void()> func([this, counts = pending_flush_queries.size()] {
+ ammend_value = 0;
+ acumulation_value = 0;
+ });
+ rasterizer->SyncOperation(std::move(func));
+ accumulation_since_last_sync = false;
+ first_accumulation_checkpoint = std::min(first_accumulation_checkpoint, num_slots_used);
+ last_accumulation_checkpoint = std::max(last_accumulation_checkpoint, num_slots_used);
+ }
+
+ void CloseCounter() override {
+ PauseCounter();
+ }
-} // Anonymous namespace
+ bool HasPendingSync() const override {
+ return !pending_sync.empty();
+ }
+
+ void SyncWrites() override {
+ if (sync_values_stash.empty()) {
+ return;
+ }
-QueryPool::QueryPool(const Device& device_, Scheduler& scheduler, QueryType type_)
- : ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {}
+ for (size_t i = 0; i < sync_values_stash.size(); i++) {
+ runtime.template SyncValues<HostSyncValues>(sync_values_stash[i],
+ *buffers[resolve_buffers[i]]);
+ }
+
+ sync_values_stash.clear();
+ }
-QueryPool::~QueryPool() = default;
+ void PresyncWrites() override {
+ if (pending_sync.empty()) {
+ return;
+ }
+ PauseCounter();
+ sync_values_stash.clear();
+ sync_values_stash.emplace_back();
+ std::vector<HostSyncValues>* sync_values = &sync_values_stash.back();
+ sync_values->reserve(num_slots_used);
+ std::unordered_map<size_t, std::pair<size_t, size_t>> offsets;
+ resolve_buffers.clear();
+ size_t resolve_buffer_index = ObtainBuffer<true>(num_slots_used);
+ resolve_buffers.push_back(resolve_buffer_index);
+ size_t base_offset = 0;
-std::pair<VkQueryPool, u32> QueryPool::Commit() {
- std::size_t index;
- do {
- index = CommitResource();
- } while (usage[index]);
- usage[index] = true;
+ ApplyBanksWideOp<true>(pending_sync, [&](SamplesQueryBank* bank, size_t start,
+ size_t amount) {
+ size_t bank_id = bank->GetIndex();
+ auto& resolve_buffer = buffers[resolve_buffer_index];
+ VkQueryPool query_pool = bank->GetInnerPool();
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([start, amount, base_offset, query_pool,
+ buffer = *resolve_buffer](vk::CommandBuffer cmdbuf) {
+ const VkBufferMemoryBarrier copy_query_pool_barrier{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = buffer,
+ .offset = base_offset,
+ .size = amount * SamplesQueryBank::QUERY_SIZE,
+ };
+
+ cmdbuf.CopyQueryPoolResults(
+ query_pool, static_cast<u32>(start), static_cast<u32>(amount), buffer,
+ static_cast<u32>(base_offset), SamplesQueryBank::QUERY_SIZE,
+ VK_QUERY_RESULT_WAIT_BIT | VK_QUERY_RESULT_64_BIT);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, 0, copy_query_pool_barrier);
+ });
+ offsets[bank_id] = {start, base_offset};
+ base_offset += amount * SamplesQueryBank::QUERY_SIZE;
+ });
+
+ // Convert queries
+ bool has_multi_queries = false;
+ for (auto q : pending_sync) {
+ auto* query = GetQuery(q);
+ size_t sync_value_slot = 0;
+ if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) {
+ continue;
+ }
+ if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) {
+ continue;
+ }
+ if (accumulation_since_last_sync || query->size_slots > 1) {
+ if (!has_multi_queries) {
+ has_multi_queries = true;
+ sync_values_stash.emplace_back();
+ }
+ sync_value_slot = 1;
+ }
+ query->flags |= VideoCommon::QueryFlagBits::IsHostSynced;
+ auto loc_data = offsets[query->start_bank_id];
+ sync_values_stash[sync_value_slot].emplace_back(HostSyncValues{
+ .address = query->guest_address,
+ .size = SamplesQueryBank::QUERY_SIZE,
+ .offset =
+ loc_data.second + (query->start_slot - loc_data.first + query->size_slots - 1) *
+ SamplesQueryBank::QUERY_SIZE,
+ });
+ }
+
+ if (has_multi_queries) {
+ size_t intermediary_buffer_index = ObtainBuffer<false>(num_slots_used);
+ resolve_buffers.push_back(intermediary_buffer_index);
+ queries_prefix_scan_pass->Run(*accumulation_buffer, *buffers[intermediary_buffer_index],
+ *buffers[resolve_buffer_index], num_slots_used,
+ std::min(first_accumulation_checkpoint, num_slots_used),
+ last_accumulation_checkpoint);
+
+ } else {
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) {
+ cmdbuf.FillBuffer(buffer, 0, 8, 0);
+ });
+ }
+
+ ReplicateCurrentQueryIfNeeded();
+ std::function<void()> func([this] { ammend_value = acumulation_value; });
+ rasterizer->SyncOperation(std::move(func));
+ AbandonCurrentQuery();
+ num_slots_used = 0;
+ first_accumulation_checkpoint = std::numeric_limits<size_t>::max();
+ last_accumulation_checkpoint = 0;
+ accumulation_since_last_sync = has_multi_queries;
+ pending_sync.clear();
+ }
- return {*pools[index / GROW_STEP], static_cast<u32>(index % GROW_STEP)};
+ size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
+ [[maybe_unused]] std::optional<u32> subreport) override {
+ PauseCounter();
+ auto index = BuildQuery();
+ auto* new_query = GetQuery(index);
+ new_query->guest_address = address;
+ new_query->value = 0;
+ new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
+ if (has_timestamp) {
+ new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
+ }
+ if (!current_query) {
+ new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
+ return index;
+ }
+ new_query->start_bank_id = current_query->start_bank_id;
+ new_query->size_banks = current_query->size_banks;
+ new_query->start_slot = current_query->start_slot;
+ new_query->size_slots = current_query->size_slots;
+ ApplyBankOp(new_query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
+ bank->AddReference(amount);
+ });
+ pending_sync.push_back(index);
+ pending_flush_queries.push_back(index);
+ return index;
+ }
+
+ bool HasUnsyncedQueries() const override {
+ return !pending_flush_queries.empty();
+ }
+
+ void PushUnsyncedQueries() override {
+ PauseCounter();
+ current_bank->Close();
+ {
+ std::scoped_lock lk(flush_guard);
+ pending_flush_sets.emplace_back(std::move(pending_flush_queries));
+ }
+ }
+
+ void PopUnsyncedQueries() override {
+ std::vector<size_t> current_flush_queries;
+ {
+ std::scoped_lock lk(flush_guard);
+ current_flush_queries = std::move(pending_flush_sets.front());
+ pending_flush_sets.pop_front();
+ }
+ ApplyBanksWideOp<false>(
+ current_flush_queries,
+ [](SamplesQueryBank* bank, size_t start, size_t amount) { bank->Sync(start, amount); });
+ for (auto q : current_flush_queries) {
+ auto* query = GetQuery(q);
+ u64 total = 0;
+ ApplyBankOp(query, [&total](SamplesQueryBank* bank, size_t start, size_t amount) {
+ const auto& results = bank->GetResults();
+ for (size_t i = 0; i < amount; i++) {
+ total += results[start + i];
+ }
+ });
+ query->value = total;
+ query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
+ }
+ }
+
+private:
+ template <typename Func>
+ void ApplyBankOp(VideoCommon::HostQueryBase* query, Func&& func) {
+ size_t size_slots = query->size_slots;
+ if (size_slots == 0) {
+ return;
+ }
+ size_t bank_id = query->start_bank_id;
+ size_t banks_set = query->size_banks;
+ size_t start_slot = query->start_slot;
+ for (size_t i = 0; i < banks_set; i++) {
+ auto& the_bank = bank_pool.GetBank(bank_id);
+ size_t amount = std::min(the_bank.Size() - start_slot, size_slots);
+ func(&the_bank, start_slot, amount);
+ bank_id = the_bank.next_bank - 1;
+ start_slot = 0;
+ size_slots -= amount;
+ }
+ }
+
+ template <bool is_ordered, typename Func>
+ void ApplyBanksWideOp(std::vector<size_t>& queries, Func&& func) {
+ std::conditional_t<is_ordered, std::map<size_t, std::pair<size_t, size_t>>,
+ std::unordered_map<size_t, std::pair<size_t, size_t>>>
+ indexer;
+ for (auto q : queries) {
+ auto* query = GetQuery(q);
+ ApplyBankOp(query, [&indexer](SamplesQueryBank* bank, size_t start, size_t amount) {
+ auto id_ = bank->GetIndex();
+ auto pair = indexer.try_emplace(id_, std::numeric_limits<size_t>::max(),
+ std::numeric_limits<size_t>::min());
+ auto& current_pair = pair.first->second;
+ current_pair.first = std::min(current_pair.first, start);
+ current_pair.second = std::max(current_pair.second, amount + start);
+ });
+ }
+ for (auto& cont : indexer) {
+ func(&bank_pool.GetBank(cont.first), cont.second.first,
+ cont.second.second - cont.second.first);
+ }
+ }
+
+ void ReserveBank() {
+ current_bank_id =
+ bank_pool.ReserveBank([this](std::deque<SamplesQueryBank>& queue, size_t index) {
+ queue.emplace_back(device, index);
+ });
+ if (current_bank) {
+ current_bank->next_bank = current_bank_id + 1;
+ }
+ current_bank = &bank_pool.GetBank(current_bank_id);
+ current_query_pool = current_bank->GetInnerPool();
+ }
+
+ size_t ReserveBankSlot() {
+ if (!current_bank || current_bank->IsClosed()) {
+ ReserveBank();
+ }
+ auto [built, index] = current_bank->Reserve();
+ current_bank_slot = index;
+ return index;
+ }
+
+ void ReserveHostQuery() {
+ size_t new_slot = ReserveBankSlot();
+ current_bank->AddReference(1);
+ num_slots_used++;
+ if (current_query) {
+ size_t bank_id = current_query->start_bank_id;
+ size_t banks_set = current_query->size_banks - 1;
+ bool found = bank_id == current_bank_id;
+ while (!found && banks_set > 0) {
+ SamplesQueryBank& some_bank = bank_pool.GetBank(bank_id);
+ bank_id = some_bank.next_bank - 1;
+ found = bank_id == current_bank_id;
+ banks_set--;
+ }
+ if (!found) {
+ current_query->size_banks++;
+ }
+ current_query->size_slots++;
+ } else {
+ current_query_id = BuildQuery();
+ current_query = GetQuery(current_query_id);
+ current_query->start_bank_id = static_cast<u32>(current_bank_id);
+ current_query->size_banks = 1;
+ current_query->start_slot = new_slot;
+ current_query->size_slots = 1;
+ }
+ }
+
+ void Free(size_t query_id) override {
+ std::scoped_lock lk(guard);
+ auto* query = GetQuery(query_id);
+ ApplyBankOp(query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
+ bank->CloseReference(amount);
+ });
+ ReleaseQuery(query_id);
+ }
+
+ void AbandonCurrentQuery() {
+ if (!current_query) {
+ return;
+ }
+ Free(current_query_id);
+ current_query = nullptr;
+ current_query_id = 0;
+ }
+
+ void ReplicateCurrentQueryIfNeeded() {
+ if (pending_sync.empty()) {
+ return;
+ }
+ if (!current_query) {
+ return;
+ }
+ auto index = BuildQuery();
+ auto* new_query = GetQuery(index);
+ new_query->guest_address = 0;
+ new_query->value = 0;
+ new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
+ new_query->start_bank_id = current_query->start_bank_id;
+ new_query->size_banks = current_query->size_banks;
+ new_query->start_slot = current_query->start_slot;
+ new_query->size_slots = current_query->size_slots;
+ ApplyBankOp(new_query, [](SamplesQueryBank* bank, size_t start, size_t amount) {
+ bank->AddReference(amount);
+ });
+ pending_flush_queries.push_back(index);
+ std::function<void()> func([this, index] {
+ auto* query = GetQuery(index);
+ query->value += GetAmmendValue();
+ SetAccumulationValue(query->value);
+ Free(index);
+ });
+ rasterizer->SyncOperation(std::move(func));
+ }
+
+ template <bool is_resolve>
+ size_t ObtainBuffer(size_t num_needed) {
+ const size_t log_2 = std::max<size_t>(11U, Common::Log2Ceil64(num_needed));
+ if constexpr (is_resolve) {
+ if (resolve_table[log_2] != 0) {
+ return resolve_table[log_2] - 1;
+ }
+ } else {
+ if (intermediary_table[log_2] != 0) {
+ return intermediary_table[log_2] - 1;
+ }
+ }
+ const VkBufferCreateInfo buffer_ci = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = SamplesQueryBank::QUERY_SIZE * (1ULL << log_2),
+ .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ };
+ buffers.emplace_back(memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal));
+ if constexpr (is_resolve) {
+ resolve_table[log_2] = buffers.size();
+ } else {
+ intermediary_table[log_2] = buffers.size();
+ }
+ return buffers.size() - 1;
+ }
+
+ QueryCacheRuntime& runtime;
+ VideoCore::RasterizerInterface* rasterizer;
+ const Device& device;
+ Scheduler& scheduler;
+ const MemoryAllocator& memory_allocator;
+ VideoCommon::BankPool<SamplesQueryBank> bank_pool;
+ std::deque<vk::Buffer> buffers;
+ std::array<size_t, 32> resolve_table{};
+ std::array<size_t, 32> intermediary_table{};
+ vk::Buffer accumulation_buffer;
+ std::deque<std::vector<HostSyncValues>> sync_values_stash;
+ std::vector<size_t> resolve_buffers;
+
+ // syncing queue
+ std::vector<size_t> pending_sync;
+
+ // flush levels
+ std::vector<size_t> pending_flush_queries;
+ std::deque<std::vector<size_t>> pending_flush_sets;
+
+ // State Machine
+ size_t current_bank_slot;
+ size_t current_bank_id;
+ SamplesQueryBank* current_bank;
+ VkQueryPool current_query_pool;
+ size_t current_query_id;
+ size_t num_slots_used{};
+ size_t first_accumulation_checkpoint{};
+ size_t last_accumulation_checkpoint{};
+ bool accumulation_since_last_sync{};
+ VideoCommon::HostQueryBase* current_query;
+ bool has_started{};
+ std::mutex flush_guard;
+
+ std::unique_ptr<QueriesPrefixScanPass> queries_prefix_scan_pass;
+};
+
+// Transform feedback queries
+class TFBQueryBank : public VideoCommon::BankBase {
+public:
+ static constexpr size_t BANK_SIZE = 1024;
+ static constexpr size_t QUERY_SIZE = 4;
+ explicit TFBQueryBank(Scheduler& scheduler_, const MemoryAllocator& memory_allocator,
+ size_t index_)
+ : BankBase(BANK_SIZE), scheduler{scheduler_}, index{index_} {
+ const VkBufferCreateInfo buffer_ci = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = QUERY_SIZE * BANK_SIZE,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ };
+ buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
+ }
+
+ ~TFBQueryBank() = default;
+
+ void Reset() override {
+ ASSERT(references == 0);
+ VideoCommon::BankBase::Reset();
+ }
+
+ void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) {
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start,
+ size](vk::CommandBuffer cmdbuf) {
+ std::array<VkBufferCopy, 1> copy{VkBufferCopy{
+ .srcOffset = start * QUERY_SIZE,
+ .dstOffset = extra_offset,
+ .size = size * QUERY_SIZE,
+ }};
+ cmdbuf.CopyBuffer(*buffer, dst_buffer, copy);
+ });
+ }
+
+ size_t GetIndex() const {
+ return index;
+ }
+
+ VkBuffer GetBuffer() const {
+ return *buffer;
+ }
+
+private:
+ Scheduler& scheduler;
+ const size_t index;
+ vk::Buffer buffer;
+};
+
+class PrimitivesSucceededStreamer;
+
+class TFBCounterStreamer : public BaseStreamer {
+public:
+ explicit TFBCounterStreamer(size_t id_, QueryCacheRuntime& runtime_, const Device& device_,
+ Scheduler& scheduler_, const MemoryAllocator& memory_allocator_,
+ StagingBufferPool& staging_pool_)
+ : BaseStreamer(id_), runtime{runtime_}, device{device_}, scheduler{scheduler_},
+ memory_allocator{memory_allocator_}, staging_pool{staging_pool_} {
+ buffers_count = 0;
+ current_bank = nullptr;
+ counter_buffers.fill(VK_NULL_HANDLE);
+ offsets.fill(0);
+ last_queries.fill(0);
+ last_queries_stride.fill(1);
+ const VkBufferCreateInfo buffer_ci = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = TFBQueryBank::QUERY_SIZE * NUM_STREAMS,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+ VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ };
+
+ counters_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
+ for (auto& c : counter_buffers) {
+ c = *counters_buffer;
+ }
+ size_t base_offset = 0;
+ for (auto& o : offsets) {
+ o = base_offset;
+ base_offset += TFBQueryBank::QUERY_SIZE;
+ }
+ }
+
+ ~TFBCounterStreamer() = default;
+
+ void StartCounter() override {
+ FlushBeginTFB();
+ has_started = true;
+ }
+
+ void PauseCounter() override {
+ CloseCounter();
+ }
+
+ void ResetCounter() override {
+ CloseCounter();
+ }
+
+ void CloseCounter() override {
+ if (has_flushed_end_pending) {
+ FlushEndTFB();
+ }
+ runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
+ if (maxwell3d.regs.transform_feedback_enabled == 0) {
+ streams_mask = 0;
+ has_started = false;
+ }
+ });
+ }
+
+ bool HasPendingSync() const override {
+ return !pending_sync.empty();
+ }
+
+ void SyncWrites() override {
+ CloseCounter();
+ std::unordered_map<size_t, std::vector<HostSyncValues>> sync_values_stash;
+ for (auto q : pending_sync) {
+ auto* query = GetQuery(q);
+ if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) {
+ continue;
+ }
+ if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) {
+ continue;
+ }
+ query->flags |= VideoCommon::QueryFlagBits::IsHostSynced;
+ sync_values_stash.try_emplace(query->start_bank_id);
+ sync_values_stash[query->start_bank_id].emplace_back(HostSyncValues{
+ .address = query->guest_address,
+ .size = TFBQueryBank::QUERY_SIZE,
+ .offset = query->start_slot * TFBQueryBank::QUERY_SIZE,
+ });
+ }
+ for (auto& p : sync_values_stash) {
+ auto& bank = bank_pool.GetBank(p.first);
+ runtime.template SyncValues<HostSyncValues>(p.second, bank.GetBuffer());
+ }
+ pending_sync.clear();
+ }
+
+ size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
+ std::optional<u32> subreport_) override {
+ auto index = BuildQuery();
+ auto* new_query = GetQuery(index);
+ new_query->guest_address = address;
+ new_query->value = 0;
+ new_query->flags &= ~VideoCommon::QueryFlagBits::IsOrphan;
+ if (has_timestamp) {
+ new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
+ }
+ if (!subreport_) {
+ new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
+ return index;
+ }
+ const size_t subreport = static_cast<size_t>(*subreport_);
+ last_queries[subreport] = address;
+ if ((streams_mask & (1ULL << subreport)) == 0) {
+ new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
+ return index;
+ }
+ CloseCounter();
+ auto [bank_slot, data_slot] = ProduceCounterBuffer(subreport);
+ new_query->start_bank_id = static_cast<u32>(bank_slot);
+ new_query->size_banks = 1;
+ new_query->start_slot = static_cast<u32>(data_slot);
+ new_query->size_slots = 1;
+ pending_sync.push_back(index);
+ pending_flush_queries.push_back(index);
+ return index;
+ }
+
+ std::optional<std::pair<VAddr, size_t>> GetLastQueryStream(size_t stream) {
+ if (last_queries[stream] != 0) {
+ std::pair<VAddr, size_t> result(last_queries[stream], last_queries_stride[stream]);
+ return result;
+ }
+ return std::nullopt;
+ }
+
+ Maxwell3D::Regs::PrimitiveTopology GetOutputTopology() const {
+ return out_topology;
+ }
+
+ bool HasUnsyncedQueries() const override {
+ return !pending_flush_queries.empty();
+ }
+
+ void PushUnsyncedQueries() override {
+ CloseCounter();
+ auto staging_ref = staging_pool.Request(
+ pending_flush_queries.size() * TFBQueryBank::QUERY_SIZE, MemoryUsage::Download, true);
+ size_t offset_base = staging_ref.offset;
+ for (auto q : pending_flush_queries) {
+ auto* query = GetQuery(q);
+ auto& bank = bank_pool.GetBank(query->start_bank_id);
+ bank.Sync(staging_ref, offset_base, query->start_slot, 1);
+ offset_base += TFBQueryBank::QUERY_SIZE;
+ bank.CloseReference();
+ }
+ static constexpr VkMemoryBarrier WRITE_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+ };
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([](vk::CommandBuffer cmdbuf) {
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
+ });
+
+ std::scoped_lock lk(flush_guard);
+ for (auto& str : free_queue) {
+ staging_pool.FreeDeferred(str);
+ }
+ free_queue.clear();
+ download_buffers.emplace_back(staging_ref);
+ pending_flush_sets.emplace_back(std::move(pending_flush_queries));
+ }
+
+ void PopUnsyncedQueries() override {
+ StagingBufferRef staging_ref;
+ std::vector<size_t> flushed_queries;
+ {
+ std::scoped_lock lk(flush_guard);
+ staging_ref = download_buffers.front();
+ flushed_queries = std::move(pending_flush_sets.front());
+ download_buffers.pop_front();
+ pending_flush_sets.pop_front();
+ }
+
+ size_t offset_base = staging_ref.offset;
+ for (auto q : flushed_queries) {
+ auto* query = GetQuery(q);
+ u32 result = 0;
+ std::memcpy(&result, staging_ref.mapped_span.data() + offset_base, sizeof(u32));
+ query->value = static_cast<u64>(result);
+ query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
+ offset_base += TFBQueryBank::QUERY_SIZE;
+ }
+
+ {
+ std::scoped_lock lk(flush_guard);
+ free_queue.emplace_back(staging_ref);
+ }
+ }
+
+private:
+ void FlushBeginTFB() {
+ if (has_flushed_end_pending) [[unlikely]] {
+ return;
+ }
+ has_flushed_end_pending = true;
+ if (!has_started || buffers_count == 0) {
+ scheduler.Record([](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr);
+ });
+ UpdateBuffers();
+ return;
+ }
+ scheduler.Record([this, total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BeginTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
+ });
+ UpdateBuffers();
+ }
+
+ void FlushEndTFB() {
+ if (!has_flushed_end_pending) [[unlikely]] {
+ UNREACHABLE();
+ return;
+ }
+ has_flushed_end_pending = false;
+
+ if (buffers_count == 0) {
+ scheduler.Record([](vk::CommandBuffer cmdbuf) {
+ cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr);
+ });
+ } else {
+ scheduler.Record([this,
+ total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
+ cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
+ });
+ }
+ }
+
+ void UpdateBuffers() {
+ last_queries.fill(0);
+ last_queries_stride.fill(1);
+ runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
+ buffers_count = 0;
+ out_topology = maxwell3d.draw_manager->GetDrawState().topology;
+ for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
+ const auto& tf = maxwell3d.regs.transform_feedback;
+ if (tf.buffers[i].enable == 0) {
+ continue;
+ }
+ const size_t stream = tf.controls[i].stream;
+ last_queries_stride[stream] = tf.controls[i].stride;
+ streams_mask |= 1ULL << stream;
+ buffers_count = std::max<size_t>(buffers_count, stream + 1);
+ }
+ });
+ }
+
+ std::pair<size_t, size_t> ProduceCounterBuffer(size_t stream) {
+ if (current_bank == nullptr || current_bank->IsClosed()) {
+ current_bank_id =
+ bank_pool.ReserveBank([this](std::deque<TFBQueryBank>& queue, size_t index) {
+ queue.emplace_back(scheduler, memory_allocator, index);
+ });
+ current_bank = &bank_pool.GetBank(current_bank_id);
+ }
+ auto [dont_care, other] = current_bank->Reserve();
+ const size_t slot = other; // workaround to compile bug.
+ current_bank->AddReference();
+
+ static constexpr VkMemoryBarrier READ_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ };
+ static constexpr VkMemoryBarrier WRITE_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
+ };
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([dst_buffer = current_bank->GetBuffer(),
+ src_buffer = counter_buffers[stream], src_offset = offsets[stream],
+ slot](vk::CommandBuffer cmdbuf) {
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
+ std::array<VkBufferCopy, 1> copy{VkBufferCopy{
+ .srcOffset = src_offset,
+ .dstOffset = slot * TFBQueryBank::QUERY_SIZE,
+ .size = TFBQueryBank::QUERY_SIZE,
+ }};
+ cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, WRITE_BARRIER);
+ });
+ return {current_bank_id, slot};
+ }
+
+ friend class PrimitivesSucceededStreamer;
+
+ static constexpr size_t NUM_STREAMS = 4;
+
+ QueryCacheRuntime& runtime;
+ const Device& device;
+ Scheduler& scheduler;
+ const MemoryAllocator& memory_allocator;
+ StagingBufferPool& staging_pool;
+ VideoCommon::BankPool<TFBQueryBank> bank_pool;
+ size_t current_bank_id;
+ TFBQueryBank* current_bank;
+ vk::Buffer counters_buffer;
+
+ // syncing queue
+ std::vector<size_t> pending_sync;
+
+ // flush levels
+ std::vector<size_t> pending_flush_queries;
+ std::deque<StagingBufferRef> download_buffers;
+ std::deque<std::vector<size_t>> pending_flush_sets;
+ std::vector<StagingBufferRef> free_queue;
+ std::mutex flush_guard;
+
+ // state machine
+ bool has_started{};
+ bool has_flushed_end_pending{};
+ size_t buffers_count{};
+ std::array<VkBuffer, NUM_STREAMS> counter_buffers{};
+ std::array<VkDeviceSize, NUM_STREAMS> offsets{};
+ std::array<VAddr, NUM_STREAMS> last_queries;
+ std::array<size_t, NUM_STREAMS> last_queries_stride;
+ Maxwell3D::Regs::PrimitiveTopology out_topology;
+ u64 streams_mask;
+};
+
+class PrimitivesQueryBase : public VideoCommon::QueryBase {
+public:
+ // Default constructor
+ PrimitivesQueryBase()
+ : VideoCommon::QueryBase(0, VideoCommon::QueryFlagBits::IsHostManaged, 0) {}
+
+ // Parameterized constructor
+ PrimitivesQueryBase(bool has_timestamp, VAddr address)
+ : VideoCommon::QueryBase(address, VideoCommon::QueryFlagBits::IsHostManaged, 0) {
+ if (has_timestamp) {
+ flags |= VideoCommon::QueryFlagBits::HasTimestamp;
+ }
+ }
+
+ u64 stride{};
+ VAddr dependant_address{};
+ Maxwell3D::Regs::PrimitiveTopology topology{Maxwell3D::Regs::PrimitiveTopology::Points};
+ size_t dependant_index{};
+ bool dependant_manage{};
+};
+
+class PrimitivesSucceededStreamer : public VideoCommon::SimpleStreamer<PrimitivesQueryBase> {
+public:
+ explicit PrimitivesSucceededStreamer(size_t id_, QueryCacheRuntime& runtime_,
+ TFBCounterStreamer& tfb_streamer_,
+ Core::Memory::Memory& cpu_memory_)
+ : VideoCommon::SimpleStreamer<PrimitivesQueryBase>(id_), runtime{runtime_},
+ tfb_streamer{tfb_streamer_}, cpu_memory{cpu_memory_} {
+ MakeDependent(&tfb_streamer);
+ }
+
+ ~PrimitivesSucceededStreamer() = default;
+
+ size_t WriteCounter(VAddr address, bool has_timestamp, u32 value,
+ std::optional<u32> subreport_) override {
+ auto index = BuildQuery();
+ auto* new_query = GetQuery(index);
+ new_query->guest_address = address;
+ new_query->value = 0;
+ if (has_timestamp) {
+ new_query->flags |= VideoCommon::QueryFlagBits::HasTimestamp;
+ }
+ if (!subreport_) {
+ new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
+ return index;
+ }
+ const size_t subreport = static_cast<size_t>(*subreport_);
+ auto dependant_address_opt = tfb_streamer.GetLastQueryStream(subreport);
+ bool must_manage_dependance = false;
+ new_query->topology = tfb_streamer.GetOutputTopology();
+ if (dependant_address_opt) {
+ auto [dep_address, stride] = *dependant_address_opt;
+ new_query->dependant_address = dep_address;
+ new_query->stride = stride;
+ } else {
+ new_query->dependant_index =
+ tfb_streamer.WriteCounter(address, has_timestamp, value, subreport_);
+ auto* dependant_query = tfb_streamer.GetQuery(new_query->dependant_index);
+ dependant_query->flags |= VideoCommon::QueryFlagBits::IsInvalidated;
+ must_manage_dependance = true;
+ if (True(dependant_query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) {
+ new_query->value = 0;
+ new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
+ if (must_manage_dependance) {
+ tfb_streamer.Free(new_query->dependant_index);
+ }
+ return index;
+ }
+ new_query->stride = 1;
+ runtime.View3DRegs([new_query, subreport](Maxwell3D& maxwell3d) {
+ for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
+ const auto& tf = maxwell3d.regs.transform_feedback;
+ if (tf.buffers[i].enable == 0) {
+ continue;
+ }
+ if (tf.controls[i].stream != subreport) {
+ continue;
+ }
+ new_query->stride = tf.controls[i].stride;
+ break;
+ }
+ });
+ }
+
+ new_query->dependant_manage = must_manage_dependance;
+ pending_flush_queries.push_back(index);
+ return index;
+ }
+
+ bool HasUnsyncedQueries() const override {
+ return !pending_flush_queries.empty();
+ }
+
+ void PushUnsyncedQueries() override {
+ std::scoped_lock lk(flush_guard);
+ pending_flush_sets.emplace_back(std::move(pending_flush_queries));
+ pending_flush_queries.clear();
+ }
+
+ void PopUnsyncedQueries() override {
+ std::vector<size_t> flushed_queries;
+ {
+ std::scoped_lock lk(flush_guard);
+ flushed_queries = std::move(pending_flush_sets.front());
+ pending_flush_sets.pop_front();
+ }
+
+ for (auto q : flushed_queries) {
+ auto* query = GetQuery(q);
+ if (True(query->flags & VideoCommon::QueryFlagBits::IsFinalValueSynced)) {
+ continue;
+ }
+
+ query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
+ u64 num_vertices = 0;
+ if (query->dependant_manage) {
+ auto* dependant_query = tfb_streamer.GetQuery(query->dependant_index);
+ num_vertices = dependant_query->value / query->stride;
+ tfb_streamer.Free(query->dependant_index);
+ } else {
+ u8* pointer = cpu_memory.GetPointer(query->dependant_address);
+ u32 result;
+ std::memcpy(&result, pointer, sizeof(u32));
+ num_vertices = static_cast<u64>(result) / query->stride;
+ }
+ query->value = [&]() -> u64 {
+ switch (query->topology) {
+ case Maxwell3D::Regs::PrimitiveTopology::Points:
+ return num_vertices;
+ case Maxwell3D::Regs::PrimitiveTopology::Lines:
+ return num_vertices / 2;
+ case Maxwell3D::Regs::PrimitiveTopology::LineLoop:
+ return (num_vertices / 2) + 1;
+ case Maxwell3D::Regs::PrimitiveTopology::LineStrip:
+ return num_vertices - 1;
+ case Maxwell3D::Regs::PrimitiveTopology::Patches:
+ case Maxwell3D::Regs::PrimitiveTopology::Triangles:
+ case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
+ return num_vertices / 3;
+ case Maxwell3D::Regs::PrimitiveTopology::TriangleFan:
+ case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip:
+ case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
+ return num_vertices - 2;
+ case Maxwell3D::Regs::PrimitiveTopology::Quads:
+ return num_vertices / 4;
+ case Maxwell3D::Regs::PrimitiveTopology::Polygon:
+ return 1U;
+ default:
+ return num_vertices;
+ }
+ }();
+ }
+ }
+
+private:
+ QueryCacheRuntime& runtime;
+ TFBCounterStreamer& tfb_streamer;
+ Core::Memory::Memory& cpu_memory;
+
+ // syncing queue
+ std::vector<size_t> pending_sync;
+
+ // flush levels
+ std::vector<size_t> pending_flush_queries;
+ std::deque<std::vector<size_t>> pending_flush_sets;
+ std::mutex flush_guard;
+};
+
+} // namespace
+
+struct QueryCacheRuntimeImpl {
+ QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_,
+ Core::Memory::Memory& cpu_memory_, Vulkan::BufferCache& buffer_cache_,
+ const Device& device_, const MemoryAllocator& memory_allocator_,
+ Scheduler& scheduler_, StagingBufferPool& staging_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue,
+ DescriptorPool& descriptor_pool)
+ : rasterizer{rasterizer_}, cpu_memory{cpu_memory_},
+ buffer_cache{buffer_cache_}, device{device_},
+ memory_allocator{memory_allocator_}, scheduler{scheduler_}, staging_pool{staging_pool_},
+ guest_streamer(0, runtime),
+ sample_streamer(static_cast<size_t>(QueryType::ZPassPixelCount64), runtime, rasterizer,
+ device, scheduler, memory_allocator, compute_pass_descriptor_queue,
+ descriptor_pool),
+ tfb_streamer(static_cast<size_t>(QueryType::StreamingByteCount), runtime, device,
+ scheduler, memory_allocator, staging_pool),
+ primitives_succeeded_streamer(
+ static_cast<size_t>(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer,
+ cpu_memory_),
+ primitives_needed_minus_suceeded_streamer(
+ static_cast<size_t>(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u),
+ hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
+
+ hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
+ hcr_setup.pNext = nullptr;
+ hcr_setup.flags = 0;
+
+ conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
+ device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
+
+ const VkBufferCreateInfo buffer_ci = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = sizeof(u32),
+ .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
+ VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ };
+ hcr_resolve_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
+ }
+
+ VideoCore::RasterizerInterface* rasterizer;
+ Core::Memory::Memory& cpu_memory;
+ Vulkan::BufferCache& buffer_cache;
+
+ const Device& device;
+ const MemoryAllocator& memory_allocator;
+ Scheduler& scheduler;
+ StagingBufferPool& staging_pool;
+
+ // Streamers
+ VideoCommon::GuestStreamer<QueryCacheParams> guest_streamer;
+ SamplesStreamer sample_streamer;
+ TFBCounterStreamer tfb_streamer;
+ PrimitivesSucceededStreamer primitives_succeeded_streamer;
+ VideoCommon::StubStreamer<QueryCacheParams> primitives_needed_minus_suceeded_streamer;
+
+ std::vector<std::pair<VAddr, VAddr>> little_cache;
+ std::vector<std::pair<VkBuffer, VkDeviceSize>> buffers_to_upload_to;
+ std::vector<size_t> redirect_cache;
+ std::vector<std::vector<VkBufferCopy>> copies_setup;
+
+ // Host conditional rendering data
+ std::unique_ptr<ConditionalRenderingResolvePass> conditional_resolve_pass;
+ vk::Buffer hcr_resolve_buffer;
+ VkConditionalRenderingBeginInfoEXT hcr_setup;
+ VkBuffer hcr_buffer;
+ size_t hcr_offset;
+ bool hcr_is_set;
+ bool is_hcr_running;
+
+ // maxwell3d
+ Maxwell3D* maxwell3d;
+};
+
+QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
+ Core::Memory::Memory& cpu_memory_,
+ Vulkan::BufferCache& buffer_cache_, const Device& device_,
+ const MemoryAllocator& memory_allocator_,
+ Scheduler& scheduler_, StagingBufferPool& staging_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue,
+ DescriptorPool& descriptor_pool) {
+ impl = std::make_unique<QueryCacheRuntimeImpl>(
+ *this, rasterizer, cpu_memory_, buffer_cache_, device_, memory_allocator_, scheduler_,
+ staging_pool_, compute_pass_descriptor_queue, descriptor_pool);
}
-void QueryPool::Allocate(std::size_t begin, std::size_t end) {
- usage.resize(end);
+void QueryCacheRuntime::Bind3DEngine(Maxwell3D* maxwell3d) {
+ impl->maxwell3d = maxwell3d;
+}
- pools.push_back(device.GetLogical().CreateQueryPool({
- .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .queryType = GetTarget(type),
- .queryCount = static_cast<u32>(end - begin),
- .pipelineStatistics = 0,
- }));
+template <typename Func>
+void QueryCacheRuntime::View3DRegs(Func&& func) {
+ if (impl->maxwell3d) {
+ func(*impl->maxwell3d);
+ }
+}
+
+void QueryCacheRuntime::EndHostConditionalRendering() {
+ PauseHostConditionalRendering();
+ impl->hcr_is_set = false;
+ impl->is_hcr_running = false;
+ impl->hcr_buffer = nullptr;
+ impl->hcr_offset = 0;
+}
+
+void QueryCacheRuntime::PauseHostConditionalRendering() {
+ if (!impl->hcr_is_set) {
+ return;
+ }
+ if (impl->is_hcr_running) {
+ impl->scheduler.Record(
+ [](vk::CommandBuffer cmdbuf) { cmdbuf.EndConditionalRenderingEXT(); });
+ }
+ impl->is_hcr_running = false;
}
-void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
- const auto it =
- std::find_if(pools.begin(), pools.end(), [query_pool = query.first](vk::QueryPool& pool) {
- return query_pool == *pool;
+void QueryCacheRuntime::ResumeHostConditionalRendering() {
+ if (!impl->hcr_is_set) {
+ return;
+ }
+ if (!impl->is_hcr_running) {
+ impl->scheduler.Record([hcr_setup = impl->hcr_setup](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BeginConditionalRenderingEXT(hcr_setup);
});
+ }
+ impl->is_hcr_running = true;
+}
- if (it != std::end(pools)) {
- const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it);
- usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false;
+void QueryCacheRuntime::HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object,
+ bool is_equal) {
+ {
+ std::scoped_lock lk(impl->buffer_cache.mutex);
+ static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
+ const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
+ const auto [buffer, offset] =
+ impl->buffer_cache.ObtainCPUBuffer(object.address, 8, sync_info, post_op);
+ impl->hcr_buffer = buffer->Handle();
+ impl->hcr_offset = offset;
+ }
+ if (impl->hcr_is_set) {
+ if (impl->hcr_setup.buffer == impl->hcr_buffer &&
+ impl->hcr_setup.offset == impl->hcr_offset) {
+ ResumeHostConditionalRendering();
+ return;
+ }
+ PauseHostConditionalRendering();
}
+ impl->hcr_setup.buffer = impl->hcr_buffer;
+ impl->hcr_setup.offset = impl->hcr_offset;
+ impl->hcr_setup.flags = is_equal ? VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT : 0;
+ impl->hcr_is_set = true;
+ impl->is_hcr_running = false;
+ ResumeHostConditionalRendering();
}
-QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_,
- Core::Memory::Memory& cpu_memory_, const Device& device_,
- Scheduler& scheduler_)
- : QueryCacheBase{rasterizer_, cpu_memory_}, device{device_}, scheduler{scheduler_},
- query_pools{
- QueryPool{device_, scheduler_, QueryType::SamplesPassed},
- } {}
-
-QueryCache::~QueryCache() {
- // TODO(Rodrigo): This is a hack to destroy all HostCounter instances before the base class
- // destructor is called. The query cache should be redesigned to have a proper ownership model
- // instead of using shared pointers.
- for (size_t query_type = 0; query_type < VideoCore::NumQueryTypes; ++query_type) {
- auto& stream = Stream(static_cast<QueryType>(query_type));
- stream.Update(false);
- stream.Reset();
+void QueryCacheRuntime::HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal) {
+ VkBuffer to_resolve;
+ u32 to_resolve_offset;
+ {
+ std::scoped_lock lk(impl->buffer_cache.mutex);
+ static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::NoSynchronize;
+ const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
+ const auto [buffer, offset] =
+ impl->buffer_cache.ObtainCPUBuffer(address, 24, sync_info, post_op);
+ to_resolve = buffer->Handle();
+ to_resolve_offset = static_cast<u32>(offset);
}
+ if (impl->is_hcr_running) {
+ PauseHostConditionalRendering();
+ }
+ impl->conditional_resolve_pass->Resolve(*impl->hcr_resolve_buffer, to_resolve,
+ to_resolve_offset, false);
+ impl->hcr_setup.buffer = *impl->hcr_resolve_buffer;
+ impl->hcr_setup.offset = 0;
+ impl->hcr_setup.flags = is_equal ? 0 : VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT;
+ impl->hcr_is_set = true;
+ impl->is_hcr_running = false;
+ ResumeHostConditionalRendering();
}
-std::pair<VkQueryPool, u32> QueryCache::AllocateQuery(QueryType type) {
- return query_pools[static_cast<std::size_t>(type)].Commit();
+bool QueryCacheRuntime::HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1,
+ [[maybe_unused]] bool qc_dirty) {
+ if (!impl->device.IsExtConditionalRendering()) {
+ return false;
+ }
+ HostConditionalRenderingCompareValueImpl(object_1, false);
+ return true;
}
-void QueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) {
- query_pools[static_cast<std::size_t>(type)].Reserve(query);
+bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
+ VideoCommon::LookupData object_2,
+ bool qc_dirty, bool equal_check) {
+ if (!impl->device.IsExtConditionalRendering()) {
+ return false;
+ }
+
+ const auto check_in_bc = [&](VAddr address) {
+ return impl->buffer_cache.IsRegionGpuModified(address, 8);
+ };
+ const auto check_value = [&](VAddr address) {
+ u8* ptr = impl->cpu_memory.GetPointer(address);
+ u64 value{};
+ std::memcpy(&value, ptr, sizeof(value));
+ return value == 0;
+ };
+ std::array<VideoCommon::LookupData*, 2> objects{&object_1, &object_2};
+ std::array<bool, 2> is_in_bc{};
+ std::array<bool, 2> is_in_qc{};
+ std::array<bool, 2> is_in_ac{};
+ std::array<bool, 2> is_null{};
+ {
+ std::scoped_lock lk(impl->buffer_cache.mutex);
+ for (size_t i = 0; i < 2; i++) {
+ is_in_qc[i] = objects[i]->found_query != nullptr;
+ is_in_bc[i] = !is_in_qc[i] && check_in_bc(objects[i]->address);
+ is_in_ac[i] = is_in_qc[i] || is_in_bc[i];
+ }
+ }
+
+ if (!is_in_ac[0] && !is_in_ac[1]) {
+ EndHostConditionalRendering();
+ return false;
+ }
+
+ if (!qc_dirty && !is_in_bc[0] && !is_in_bc[1]) {
+ EndHostConditionalRendering();
+ return false;
+ }
+
+ const bool is_gpu_high = Settings::IsGPULevelHigh();
+ if (!is_gpu_high && impl->device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
+ return true;
+ }
+
+ for (size_t i = 0; i < 2; i++) {
+ is_null[i] = !is_in_ac[i] && check_value(objects[i]->address);
+ }
+
+ for (size_t i = 0; i < 2; i++) {
+ if (is_null[i]) {
+ size_t j = (i + 1) % 2;
+ HostConditionalRenderingCompareValueImpl(*objects[j], equal_check);
+ return true;
+ }
+ }
+
+ if (!is_gpu_high) {
+ return true;
+ }
+
+ if (!is_in_bc[0] && !is_in_bc[1]) {
+ // Both queries are in query cache, it's best to just flush.
+ return true;
+ }
+ HostConditionalRenderingCompareBCImpl(object_1.address, equal_check);
+ return true;
}
-HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
- QueryType type_)
- : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_},
- query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} {
- const vk::Device* logical = &cache.GetDevice().GetLogical();
- cache.GetScheduler().Record([logical, query_ = query](vk::CommandBuffer cmdbuf) {
- const bool use_precise = Settings::IsGPULevelHigh();
- logical->ResetQueryPool(query_.first, query_.second, 1);
- cmdbuf.BeginQuery(query_.first, query_.second,
- use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
- });
+QueryCacheRuntime::~QueryCacheRuntime() = default;
+
+VideoCommon::StreamerInterface* QueryCacheRuntime::GetStreamerInterface(QueryType query_type) {
+ switch (query_type) {
+ case QueryType::Payload:
+ return &impl->guest_streamer;
+ case QueryType::ZPassPixelCount64:
+ return &impl->sample_streamer;
+ case QueryType::StreamingByteCount:
+ return &impl->tfb_streamer;
+ case QueryType::StreamingPrimitivesNeeded:
+ case QueryType::VtgPrimitivesOut:
+ case QueryType::StreamingPrimitivesSucceeded:
+ return &impl->primitives_succeeded_streamer;
+ case QueryType::StreamingPrimitivesNeededMinusSucceeded:
+ return &impl->primitives_needed_minus_suceeded_streamer;
+ default:
+ return nullptr;
+ }
}
-HostCounter::~HostCounter() {
- cache.Reserve(type, query);
+void QueryCacheRuntime::Barriers(bool is_prebarrier) {
+ static constexpr VkMemoryBarrier READ_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
+ };
+ static constexpr VkMemoryBarrier WRITE_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+ };
+ if (is_prebarrier) {
+ impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
+ });
+ } else {
+ impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
+ });
+ }
}
-void HostCounter::EndQuery() {
- cache.GetScheduler().Record([query_ = query](vk::CommandBuffer cmdbuf) {
- cmdbuf.EndQuery(query_.first, query_.second);
+template <typename SyncValuesType>
+void QueryCacheRuntime::SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer) {
+ if (values.size() == 0) {
+ return;
+ }
+ impl->redirect_cache.clear();
+ impl->little_cache.clear();
+ size_t total_size = 0;
+ for (auto& sync_val : values) {
+ total_size += sync_val.size;
+ bool found = false;
+ VAddr base = Common::AlignDown(sync_val.address, Core::Memory::YUZU_PAGESIZE);
+ VAddr base_end = base + Core::Memory::YUZU_PAGESIZE;
+ for (size_t i = 0; i < impl->little_cache.size(); i++) {
+ const auto set_found = [&] {
+ impl->redirect_cache.push_back(i);
+ found = true;
+ };
+ auto& loc = impl->little_cache[i];
+ if (base < loc.second && loc.first < base_end) {
+ set_found();
+ break;
+ }
+ if (loc.first == base_end) {
+ loc.first = base;
+ set_found();
+ break;
+ }
+ if (loc.second == base) {
+ loc.second = base_end;
+ set_found();
+ break;
+ }
+ }
+ if (!found) {
+ impl->redirect_cache.push_back(impl->little_cache.size());
+ impl->little_cache.emplace_back(base, base_end);
+ }
+ }
+
+ // Vulkan part.
+ std::scoped_lock lk(impl->buffer_cache.mutex);
+ impl->buffer_cache.BufferOperations([&] {
+ impl->buffers_to_upload_to.clear();
+ for (auto& pair : impl->little_cache) {
+ static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
+ const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
+ const auto [buffer, offset] = impl->buffer_cache.ObtainCPUBuffer(
+ pair.first, static_cast<u32>(pair.second - pair.first), sync_info, post_op);
+ impl->buffers_to_upload_to.emplace_back(buffer->Handle(), offset);
+ }
});
-}
-u64 HostCounter::BlockingQuery(bool async) const {
- if (!async) {
- cache.GetScheduler().Wait(tick);
- }
- u64 data;
- const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults(
- query.first, query.second, 1, sizeof(data), &data, sizeof(data),
- VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
-
- switch (query_result) {
- case VK_SUCCESS:
- return data;
- case VK_ERROR_DEVICE_LOST:
- cache.GetDevice().ReportLoss();
- [[fallthrough]];
- default:
- throw vk::Exception(query_result);
+ VkBuffer src_buffer;
+ [[maybe_unused]] StagingBufferRef ref;
+ impl->copies_setup.clear();
+ impl->copies_setup.resize(impl->little_cache.size());
+ if constexpr (SyncValuesType::GeneratesBaseBuffer) {
+ ref = impl->staging_pool.Request(total_size, MemoryUsage::Upload);
+ size_t current_offset = ref.offset;
+ size_t accumulated_size = 0;
+ for (size_t i = 0; i < values.size(); i++) {
+ size_t which_copy = impl->redirect_cache[i];
+ impl->copies_setup[which_copy].emplace_back(VkBufferCopy{
+ .srcOffset = current_offset + accumulated_size,
+ .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address -
+ impl->little_cache[which_copy].first,
+ .size = values[i].size,
+ });
+ std::memcpy(ref.mapped_span.data() + accumulated_size, &values[i].value,
+ values[i].size);
+ accumulated_size += values[i].size;
+ }
+ src_buffer = ref.buffer;
+ } else {
+ for (size_t i = 0; i < values.size(); i++) {
+ size_t which_copy = impl->redirect_cache[i];
+ impl->copies_setup[which_copy].emplace_back(VkBufferCopy{
+ .srcOffset = values[i].offset,
+ .dstOffset = impl->buffers_to_upload_to[which_copy].second + values[i].address -
+ impl->little_cache[which_copy].first,
+ .size = values[i].size,
+ });
+ }
+ src_buffer = base_src_buffer;
}
+
+ impl->scheduler.RequestOutsideRenderPassOperationContext();
+ impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to),
+ vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) {
+ size_t size = dst_buffers.size();
+ for (size_t i = 0; i < size; i++) {
+ cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]);
+ }
+ });
}
} // namespace Vulkan
+
+namespace VideoCommon {
+
+template class QueryCacheBase<Vulkan::QueryCacheParams>;
+
+} // namespace VideoCommon
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index c1b9552eb..e9a1ea169 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -1,101 +1,75 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
-#include <cstddef>
#include <memory>
-#include <utility>
-#include <vector>
-#include "common/common_types.h"
-#include "video_core/query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_pool.h"
-#include "video_core/vulkan_common/vulkan_wrapper.h"
+#include "video_core/query_cache/query_cache_base.h"
+#include "video_core/renderer_vulkan/vk_buffer_cache.h"
namespace VideoCore {
class RasterizerInterface;
}
+namespace VideoCommon {
+class StreamerInterface;
+}
+
namespace Vulkan {
-class CachedQuery;
class Device;
-class HostCounter;
-class QueryCache;
class Scheduler;
+class StagingBufferPool;
-using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
+struct QueryCacheRuntimeImpl;
-class QueryPool final : public ResourcePool {
+class QueryCacheRuntime {
public:
- explicit QueryPool(const Device& device, Scheduler& scheduler, VideoCore::QueryType type);
- ~QueryPool() override;
+ explicit QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer,
+ Core::Memory::Memory& cpu_memory_,
+ Vulkan::BufferCache& buffer_cache_, const Device& device_,
+ const MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
+ StagingBufferPool& staging_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue,
+ DescriptorPool& descriptor_pool);
+ ~QueryCacheRuntime();
- std::pair<VkQueryPool, u32> Commit();
+ template <typename SyncValuesType>
+ void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
- void Reserve(std::pair<VkQueryPool, u32> query);
+ void Barriers(bool is_prebarrier);
-protected:
- void Allocate(std::size_t begin, std::size_t end) override;
+ void EndHostConditionalRendering();
-private:
- static constexpr std::size_t GROW_STEP = 512;
+ void PauseHostConditionalRendering();
- const Device& device;
- const VideoCore::QueryType type;
+ void ResumeHostConditionalRendering();
- std::vector<vk::QueryPool> pools;
- std::vector<bool> usage;
-};
+ bool HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1, bool qc_dirty);
-class QueryCache final
- : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
-public:
- explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_,
- Core::Memory::Memory& cpu_memory_, const Device& device_,
- Scheduler& scheduler_);
- ~QueryCache();
-
- std::pair<VkQueryPool, u32> AllocateQuery(VideoCore::QueryType type);
+ bool HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
+ VideoCommon::LookupData object_2, bool qc_dirty,
+ bool equal_check);
- void Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query);
+ VideoCommon::StreamerInterface* GetStreamerInterface(VideoCommon::QueryType query_type);
- const Device& GetDevice() const noexcept {
- return device;
- }
+ void Bind3DEngine(Tegra::Engines::Maxwell3D* maxwell3d);
- Scheduler& GetScheduler() const noexcept {
- return scheduler;
- }
+ template <typename Func>
+ void View3DRegs(Func&& func);
private:
- const Device& device;
- Scheduler& scheduler;
- std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
+ void HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object, bool is_equal);
+ void HostConditionalRenderingCompareBCImpl(VAddr address, bool is_equal);
+ friend struct QueryCacheRuntimeImpl;
+ std::unique_ptr<QueryCacheRuntimeImpl> impl;
};
-class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> {
-public:
- explicit HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
- VideoCore::QueryType type_);
- ~HostCounter();
-
- void EndQuery();
-
-private:
- u64 BlockingQuery(bool async = false) const override;
-
- QueryCache& cache;
- const VideoCore::QueryType type;
- const std::pair<VkQueryPool, u32> query;
- const u64 tick;
+struct QueryCacheParams {
+ using RuntimeType = typename Vulkan::QueryCacheRuntime;
};
-class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> {
-public:
- explicit CachedQuery(QueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_)
- : CachedQueryBase{cpu_addr_, host_ptr_} {}
-};
+using QueryCache = VideoCommon::QueryCacheBase<QueryCacheParams>;
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 3e04b7583..61d03daae 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -24,6 +24,7 @@
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
+#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -170,9 +171,11 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool,
guest_descriptor_queue, compute_pass_descriptor_queue, descriptor_pool),
buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
+ query_cache_runtime(this, cpu_memory_, buffer_cache, device, memory_allocator, scheduler,
+ staging_pool, compute_pass_descriptor_queue, descriptor_pool),
+ query_cache(gpu, *this, cpu_memory_, query_cache_runtime),
pipeline_cache(*this, device, scheduler, descriptor_pool, guest_descriptor_queue,
render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()),
- query_cache{*this, cpu_memory_, device, scheduler},
accelerate_dma(buffer_cache, texture_cache, scheduler),
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
wfi_event(device.GetLogical().CreateEvent()) {
@@ -189,14 +192,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
FlushWork();
gpu_memory->FlushCaching();
-#if ANDROID
- if (Settings::IsGPULevelHigh()) {
- // This is problematic on Android, disable on GPU Normal.
- query_cache.UpdateCounters();
- }
-#else
- query_cache.UpdateCounters();
-#endif
+ query_cache.NotifySegment(true);
GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()};
if (!pipeline) {
@@ -207,13 +203,12 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
- BeginTransformFeedback();
-
UpdateDynamicStates();
+ HandleTransformFeedback();
+ query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
+ maxwell3d->regs.zpass_pixel_count_enable);
draw_func();
-
- EndTransformFeedback();
}
void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
@@ -241,6 +236,14 @@ void RasterizerVulkan::DrawIndirect() {
const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer();
const auto& buffer = indirect_buffer.first;
const auto& offset = indirect_buffer.second;
+ if (params.is_byte_count) {
+ scheduler.Record([buffer_obj = buffer->Handle(), offset,
+ stride = params.stride](vk::CommandBuffer cmdbuf) {
+ cmdbuf.DrawIndirectByteCountEXT(1, 0, buffer_obj, offset, 0,
+ static_cast<u32>(stride));
+ });
+ return;
+ }
if (params.include_count) {
const auto count = buffer_cache.GetDrawIndirectCount();
const auto& draw_buffer = count.first;
@@ -280,20 +283,15 @@ void RasterizerVulkan::DrawTexture() {
SCOPE_EXIT({ gpu.TickWork(); });
FlushWork();
-#if ANDROID
- if (Settings::IsGPULevelHigh()) {
- // This is problematic on Android, disable on GPU Normal.
- query_cache.UpdateCounters();
- }
-#else
- query_cache.UpdateCounters();
-#endif
+ query_cache.NotifySegment(true);
texture_cache.SynchronizeGraphicsDescriptors();
texture_cache.UpdateRenderTargets(false);
UpdateDynamicStates();
+ query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
+ maxwell3d->regs.zpass_pixel_count_enable);
const auto& draw_texture_state = maxwell3d->draw_manager->GetDrawTextureState();
const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler);
const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture);
@@ -316,14 +314,9 @@ void RasterizerVulkan::Clear(u32 layer_count) {
FlushWork();
gpu_memory->FlushCaching();
-#if ANDROID
- if (Settings::IsGPULevelHigh()) {
- // This is problematic on Android, disable on GPU Normal.
- query_cache.UpdateCounters();
- }
-#else
- query_cache.UpdateCounters();
-#endif
+ query_cache.NotifySegment(true);
+ query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64,
+ maxwell3d->regs.zpass_pixel_count_enable);
auto& regs = maxwell3d->regs;
const bool use_color = regs.clear_surface.R || regs.clear_surface.G || regs.clear_surface.B ||
@@ -429,7 +422,8 @@ void RasterizerVulkan::Clear(u32 layer_count) {
return;
}
- if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) {
+ if (use_stencil && framebuffer->HasAspectStencilBit() && 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),
@@ -463,18 +457,32 @@ 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]); });
}
-void RasterizerVulkan::ResetCounter(VideoCore::QueryType type) {
- query_cache.ResetCounter(type);
+void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) {
+ query_cache.CounterReset(type);
}
-void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
- std::optional<u64> timestamp) {
- query_cache.Query(gpu_addr, type, timestamp);
+void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
+ VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) {
+ query_cache.CounterReport(gpu_addr, type, flags, payload, subreport);
}
void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
@@ -655,8 +663,8 @@ void RasterizerVulkan::SignalReference() {
fence_manager.SignalReference();
}
-void RasterizerVulkan::ReleaseFences() {
- fence_manager.WaitPendingFences();
+void RasterizerVulkan::ReleaseFences(bool force) {
+ fence_manager.WaitPendingFences(force);
}
void RasterizerVulkan::FlushAndInvalidateRegion(VAddr addr, u64 size,
@@ -680,6 +688,8 @@ void RasterizerVulkan::WaitForIdle() {
flags |= VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT;
}
+ query_cache.NotifyWFI();
+
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([event = *wfi_event, flags](vk::CommandBuffer cmdbuf) {
cmdbuf.SetEvent(event, flags);
@@ -723,19 +733,7 @@ void RasterizerVulkan::TickFrame() {
bool RasterizerVulkan::AccelerateConditionalRendering() {
gpu_memory->FlushCaching();
- if (Settings::IsGPULevelHigh()) {
- // TODO(Blinkhawk): Reimplement Host conditional rendering.
- return false;
- }
- // Medium / Low Hack: stub any checks on queries written into the buffer cache.
- const GPUVAddr condition_address{maxwell3d->regs.render_enable.Address()};
- Maxwell::ReportSemaphore::Compare cmp;
- if (gpu_memory->IsMemoryDirty(condition_address, sizeof(cmp),
- VideoCommon::CacheType::BufferCache |
- VideoCommon::CacheType::QueryCache)) {
- return true;
- }
- return false;
+ return query_cache.AccelerateHostConditionalRendering();
}
bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
@@ -781,6 +779,7 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
if (!image_view) {
return false;
}
+ query_cache.NotifySegment(false);
screen_info.image = image_view->ImageHandle();
screen_info.image_view = image_view->Handle(Shader::TextureType::Color2D);
screen_info.width = image_view->size.width;
@@ -919,31 +918,18 @@ void RasterizerVulkan::UpdateDynamicStates() {
}
}
-void RasterizerVulkan::BeginTransformFeedback() {
+void RasterizerVulkan::HandleTransformFeedback() {
const auto& regs = maxwell3d->regs;
- if (regs.transform_feedback_enabled == 0) {
- return;
- }
if (!device.IsExtTransformFeedbackSupported()) {
LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported");
return;
}
- UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
- regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
- scheduler.Record(
- [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); });
-}
-
-void RasterizerVulkan::EndTransformFeedback() {
- const auto& regs = maxwell3d->regs;
- if (regs.transform_feedback_enabled == 0) {
- return;
+ query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount,
+ regs.transform_feedback_enabled);
+ if (regs.transform_feedback_enabled != 0) {
+ UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
+ regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
}
- if (!device.IsExtTransformFeedbackSupported()) {
- return;
- }
- scheduler.Record(
- [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
}
void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) {
@@ -1042,15 +1028,37 @@ void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs) {
regs.zeta.format == Tegra::DepthFormat::X8Z24_UNORM ||
regs.zeta.format == Tegra::DepthFormat::S8Z24_UNORM ||
regs.zeta.format == Tegra::DepthFormat::V8Z24_UNORM;
- if (is_d24 && !device.SupportsD24DepthBuffer()) {
+ bool force_unorm = ([&] {
+ if (!is_d24 || device.SupportsD24DepthBuffer()) {
+ return false;
+ }
+ if (device.IsExtDepthBiasControlSupported()) {
+ return true;
+ }
+ if (!Settings::values.renderer_amdvlk_depth_bias_workaround) {
+ return false;
+ }
// the base formulas can be obtained from here:
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
const double rescale_factor =
static_cast<double>(1ULL << (32 - 24)) / (static_cast<double>(0x1.ep+127));
units = static_cast<float>(static_cast<double>(units) * rescale_factor);
- }
+ return false;
+ })();
scheduler.Record([constant = units, clamp = regs.depth_bias_clamp,
- factor = regs.slope_scale_depth_bias](vk::CommandBuffer cmdbuf) {
+ factor = regs.slope_scale_depth_bias, force_unorm,
+ precise = device.HasExactDepthBiasControl()](vk::CommandBuffer cmdbuf) {
+ if (force_unorm) {
+ VkDepthBiasRepresentationInfoEXT info{
+ .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_REPRESENTATION_INFO_EXT,
+ .pNext = nullptr,
+ .depthBiasRepresentation =
+ VK_DEPTH_BIAS_REPRESENTATION_LEAST_REPRESENTABLE_VALUE_FORCE_UNORM_EXT,
+ .depthBiasExact = precise ? VK_TRUE : VK_FALSE,
+ };
+ cmdbuf.SetDepthBias(constant, clamp, factor, &info);
+ return;
+ }
cmdbuf.SetDepthBias(constant, clamp, factor);
});
}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index b31982485..ad069556c 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -84,8 +84,9 @@ public:
void DrawTexture() override;
void Clear(u32 layer_count) override;
void DispatchCompute() override;
- void ResetCounter(VideoCore::QueryType type) override;
- void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
+ void ResetCounter(VideoCommon::QueryType type) override;
+ void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type,
+ VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override;
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
void DisableGraphicsUniformBuffer(size_t stage, u32 index) override;
void FlushAll() override;
@@ -106,7 +107,7 @@ public:
void SyncOperation(std::function<void()>&& func) override;
void SignalSyncPoint(u32 value) override;
void SignalReference() override;
- void ReleaseFences() override;
+ void ReleaseFences(bool force = true) override;
void FlushAndInvalidateRegion(
VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void WaitForIdle() override;
@@ -146,9 +147,7 @@ private:
void UpdateDynamicStates();
- void BeginTransformFeedback();
-
- void EndTransformFeedback();
+ void HandleTransformFeedback();
void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -195,8 +194,9 @@ private:
TextureCache texture_cache;
BufferCacheRuntime buffer_cache_runtime;
BufferCache buffer_cache;
- PipelineCache pipeline_cache;
+ QueryCacheRuntime query_cache_runtime;
QueryCache query_cache;
+ PipelineCache pipeline_cache;
AccelerateDMA accelerate_dma;
FenceManager fence_manager;
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 89fd31b4f..3be7837f4 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -243,10 +243,10 @@ void Scheduler::AllocateNewContext() {
#if ANDROID
if (Settings::IsGPULevelHigh()) {
// This is problematic on Android, disable on GPU Normal.
- query_cache->UpdateCounters();
+ query_cache->NotifySegment(true);
}
#else
- query_cache->UpdateCounters();
+ query_cache->NotifySegment(true);
#endif
}
}
@@ -261,11 +261,12 @@ void Scheduler::EndPendingOperations() {
#if ANDROID
if (Settings::IsGPULevelHigh()) {
// This is problematic on Android, disable on GPU Normal.
- query_cache->DisableStreams();
+ // query_cache->DisableStreams();
}
#else
- query_cache->DisableStreams();
+ // query_cache->DisableStreams();
#endif
+ query_cache->NotifySegment(false);
EndRenderPass();
}
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 475c682eb..da03803aa 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -17,6 +17,11 @@
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
+namespace VideoCommon {
+template <typename Trait>
+class QueryCacheBase;
+}
+
namespace Vulkan {
class CommandPool;
@@ -24,7 +29,8 @@ class Device;
class Framebuffer;
class GraphicsPipeline;
class StateTracker;
-class QueryCache;
+
+struct QueryCacheParams;
/// The scheduler abstracts command buffer and fence management with an interface that's able to do
/// OpenGL-like operations on Vulkan command buffers.
@@ -63,7 +69,7 @@ public:
void InvalidateState();
/// Assigns the query cache.
- void SetQueryCache(QueryCache& query_cache_) {
+ void SetQueryCache(VideoCommon::QueryCacheBase<QueryCacheParams>& query_cache_) {
query_cache = &query_cache_;
}
@@ -219,7 +225,7 @@ private:
std::unique_ptr<MasterSemaphore> master_semaphore;
std::unique_ptr<CommandPool> command_pool;
- QueryCache* query_cache = nullptr;
+ VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr;
vk::CommandBuffer current_cmdbuf;
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index ce92f66ab..b278614e6 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -24,25 +24,38 @@ using namespace Common::Literals;
// Maximum potential alignment of a Vulkan buffer
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
-// Maximum size to put elements in the stream buffer
-constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
// Stream buffer size in bytes
-constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB;
-constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
+constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
-size_t Region(size_t iterator) noexcept {
- return iterator / REGION_SIZE;
+size_t GetStreamBufferSize(const Device& device) {
+ VkDeviceSize size{0};
+ if (device.HasDebuggingToolAttached()) {
+ ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) {
+ size = std::max(size, heap.size);
+ });
+ // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
+ // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
+ // as the heap will be much larger.
+ if (size <= 256_MiB) {
+ size = size * 40 / 100;
+ }
+ } else {
+ size = MAX_STREAM_BUFFER_SIZE;
+ }
+ return std::min(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
}
} // Anonymous namespace
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_)
- : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} {
+ : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
+ stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
+ StagingBufferPool::NUM_SYNCS} {
VkBufferCreateInfo stream_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .size = STREAM_BUFFER_SIZE,
+ .size = stream_buffer_size,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -63,7 +76,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
StagingBufferPool::~StagingBufferPool() = default;
StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
- if (!deferred && usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) {
+ if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
return GetStreamBuffer(size);
}
return GetStagingBuffer(size, usage, deferred);
@@ -101,7 +114,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
used_iterator = iterator;
free_iterator = std::max(free_iterator, iterator + size);
- if (iterator + size >= STREAM_BUFFER_SIZE) {
+ if (iterator + size >= stream_buffer_size) {
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
current_tick);
used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index 5f69f08b1..d3deb9072 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -90,6 +90,9 @@ private:
void ReleaseCache(MemoryUsage usage);
void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
+ size_t Region(size_t iter) const noexcept {
+ return iter / region_size;
+ }
const Device& device;
MemoryAllocator& memory_allocator;
@@ -97,6 +100,8 @@ private:
vk::Buffer stream_buffer;
std::span<u8> stream_pointer;
+ VkDeviceSize stream_buffer_size;
+ VkDeviceSize region_size;
size_t iterator = 0;
size_t used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index b3e17c332..93773a69f 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -120,19 +120,9 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
return usage;
}
-/// Returns the preferred format for a VkImage
-[[nodiscard]] PixelFormat StorageFormat(PixelFormat format) {
- switch (format) {
- case PixelFormat::A8B8G8R8_SRGB:
- return PixelFormat::A8B8G8R8_UNORM;
- default:
- return format;
- }
-}
-
[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
- const PixelFormat format = StorageFormat(info.format);
- const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format);
+ const auto format_info =
+ MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format);
VkImageCreateFlags flags{};
if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) {
@@ -157,7 +147,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
.arrayLayers = static_cast<u32>(info.resources.layers),
.samples = ConvertSampleCount(info.num_samples),
.tiling = VK_IMAGE_TILING_OPTIMAL,
- .usage = ImageUsageFlags(format_info, format),
+ .usage = ImageUsageFlags(format_info, info.format),
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
@@ -186,6 +176,36 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
return allocator.CreateImage(image_ci);
}
+[[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image,
+ VkFormat format) {
+ static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .usage = VK_IMAGE_USAGE_STORAGE_BIT,
+ };
+ return device.CreateImageView(VkImageViewCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = &storage_image_view_usage_create_info,
+ .flags = 0,
+ .image = image,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
+ .format = format,
+ .components{
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = level,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ });
+}
+
[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
switch (VideoCore::Surface::GetFormatType(format)) {
case VideoCore::Surface::SurfaceType::ColorTexture:
@@ -218,6 +238,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
case PixelFormat::D16_UNORM:
case PixelFormat::D32_FLOAT:
+ case PixelFormat::X8_D24_UNORM:
return VK_IMAGE_ASPECT_DEPTH_BIT;
case PixelFormat::S8_UINT:
return VK_IMAGE_ASPECT_STENCIL_BIT;
@@ -600,7 +621,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);
@@ -616,6 +637,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;
}
@@ -822,6 +848,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue, memory_allocator);
}
+ if (device.IsStorageImageMultisampleSupported()) {
+ msaa_copy_pass = std::make_unique<MSAACopyPass>(
+ device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue);
+ }
if (!device.IsKhrImageFormatListSupported()) {
return;
}
@@ -1044,15 +1074,27 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
dst_region, src_region, filter, operation);
return;
}
+ ASSERT(src.format == dst.format);
if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
- if (!device.IsBlitDepthStencilSupported()) {
+ const auto format = src.format;
+ const auto can_blit_depth_stencil = [this, format] {
+ switch (format) {
+ case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
+ case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
+ return device.IsBlitDepth24Stencil8Supported();
+ case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
+ return device.IsBlitDepth32Stencil8Supported();
+ default:
+ UNREACHABLE();
+ }
+ }();
+ if (!can_blit_depth_stencil) {
UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(),
dst_region, src_region, filter, operation);
return;
}
}
- ASSERT(src.format == dst.format);
ASSERT(!(is_dst_msaa && !is_src_msaa));
ASSERT(operation == Fermi2D::Operation::SrcCopy);
@@ -1159,6 +1201,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
}
+ if (src_view.format == PixelFormat::D32_FLOAT) {
+ return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
+ }
break;
case PixelFormat::R32_FLOAT:
if (src_view.format == PixelFormat::D32_FLOAT) {
@@ -1278,7 +1323,11 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src,
std::span<const VideoCommon::ImageCopy> copies) {
- UNIMPLEMENTED_MSG("Copying images with different samples is not implemented in Vulkan.");
+ const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1;
+ if (msaa_copy_pass) {
+ return msaa_copy_pass->CopyImage(dst, src, copies, msaa_to_non_msaa);
+ }
+ UNIMPLEMENTED_MSG("Copying images with different samples is not supported.");
}
u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
@@ -1326,39 +1375,15 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
if (runtime->device.HasDebuggingToolAttached()) {
original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
}
- static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
- .pNext = nullptr,
- .usage = VK_IMAGE_USAGE_STORAGE_BIT,
- };
current_image = *original_image;
+ storage_image_views.resize(info.resources.levels);
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() &&
Settings::values.astc_recompression.GetValue() ==
Settings::AstcRecompression::Uncompressed) {
const auto& device = runtime->device.GetLogical();
- storage_image_views.reserve(info.resources.levels);
for (s32 level = 0; level < info.resources.levels; ++level) {
- storage_image_views.push_back(device.CreateImageView(VkImageViewCreateInfo{
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
- .pNext = &storage_image_view_usage_create_info,
- .flags = 0,
- .image = *original_image,
- .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
- .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
- .components{
- .r = VK_COMPONENT_SWIZZLE_IDENTITY,
- .g = VK_COMPONENT_SWIZZLE_IDENTITY,
- .b = VK_COMPONENT_SWIZZLE_IDENTITY,
- .a = VK_COMPONENT_SWIZZLE_IDENTITY,
- },
- .subresourceRange{
- .aspectMask = aspect_mask,
- .baseMipLevel = static_cast<u32>(level),
- .levelCount = 1,
- .baseArrayLayer = 0,
- .layerCount = VK_REMAINING_ARRAY_LAYERS,
- },
- }));
+ storage_image_views[level] =
+ MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32);
}
}
}
@@ -1489,20 +1514,31 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
DownloadMemory(buffers, offsets, copies);
}
+VkImageView Image::StorageImageView(s32 level) noexcept {
+ auto& view = storage_image_views[level];
+ if (!view) {
+ const auto format_info =
+ MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
+ view =
+ MakeStorageView(runtime->device.GetLogical(), level, current_image, format_info.format);
+ }
+ return *view;
+}
+
bool Image::IsRescaled() const noexcept {
return True(flags & ImageFlagBits::Rescaled);
}
bool Image::ScaleUp(bool ignore) {
+ const auto& resolution = runtime->resolution;
+ if (!resolution.active) {
+ return false;
+ }
if (True(flags & ImageFlagBits::Rescaled)) {
return false;
}
ASSERT(info.type != ImageType::Linear);
flags |= ImageFlagBits::Rescaled;
- const auto& resolution = runtime->resolution;
- if (!resolution.active) {
- return false;
- }
has_scaled = true;
if (!scaled_image) {
const bool is_2d = info.type == ImageType::e2D;
@@ -1531,15 +1567,15 @@ bool Image::ScaleUp(bool ignore) {
}
bool Image::ScaleDown(bool ignore) {
+ const auto& resolution = runtime->resolution;
+ if (!resolution.active) {
+ return false;
+ }
if (False(flags & ImageFlagBits::Rescaled)) {
return false;
}
ASSERT(info.type != ImageType::Linear);
flags &= ~ImageFlagBits::Rescaled;
- const auto& resolution = runtime->resolution;
- if (!resolution.active) {
- return false;
- }
current_image = *original_image;
if (ignore) {
return true;
@@ -1626,8 +1662,8 @@ bool Image::NeedsScaleHelper() const {
return true;
}
static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal;
- const PixelFormat format = StorageFormat(info.format);
- const auto vk_format = MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, format).format;
+ const auto vk_format =
+ MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, info.format).format;
const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
const bool needs_blit_helper = !device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT);
return needs_blit_helper;
@@ -1649,7 +1685,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);
}
@@ -1976,4 +2013,32 @@ void TextureCacheRuntime::AccelerateImageUpload(
ASSERT(false);
}
+void TextureCacheRuntime::TransitionImageLayout(Image& image) {
+ if (!image.ExchangeInitialization()) {
+ VkImageMemoryBarrier barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_NONE,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image.Handle(),
+ .subresourceRange{
+ .aspectMask = image.AspectMask(),
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ };
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) {
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
+ });
+ }
+}
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 565ce19a9..7a0807709 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -92,6 +92,8 @@ public:
void InsertUploadMemoryBarrier() {}
+ void TransitionImageLayout(Image& image);
+
bool HasBrokenTextureViewFormats() const noexcept {
// No known Vulkan driver has broken image views
return false;
@@ -117,6 +119,7 @@ public:
BlitImageHelper& blit_image_helper;
RenderPassCache& render_pass_cache;
std::optional<ASTCDecoderPass> astc_decoder_pass;
+ std::unique_ptr<MSAACopyPass> msaa_copy_pass;
const Settings::ResolutionScalingInfo& resolution;
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
@@ -161,15 +164,13 @@ public:
return aspect_mask;
}
- [[nodiscard]] VkImageView StorageImageView(s32 level) const noexcept {
- return *storage_image_views[level];
- }
-
/// Returns true when the image is already initialized and mark it as initialized
[[nodiscard]] bool ExchangeInitialization() noexcept {
return std::exchange(initialized, true);
}
+ VkImageView StorageImageView(s32 level) noexcept;
+
bool IsRescaled() const noexcept;
bool ScaleUp(bool ignore = false);
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index e16cd5e73..5b3c7aa5a 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -85,6 +85,8 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
return PixelFormat::S8_UINT;
case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
return PixelFormat::D32_FLOAT_S8_UINT;
+ case Tegra::DepthFormat::X8Z24_UNORM:
+ return PixelFormat::X8_D24_UNORM;
default:
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
return PixelFormat::S8_UINT_D24_UNORM;
@@ -202,6 +204,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
switch (format) {
case Service::android::PixelFormat::Rgba8888:
+ case Service::android::PixelFormat::Rgbx8888:
return PixelFormat::A8B8G8R8_UNORM;
case Service::android::PixelFormat::Rgb565:
return PixelFormat::R5G6B5_UNORM;
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 9b9c4d9bc..a5e8e2f62 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -115,6 +115,7 @@ enum class PixelFormat {
// Depth formats
D32_FLOAT = MaxColorFormat,
D16_UNORM,
+ X8_D24_UNORM,
MaxDepthFormat,
@@ -251,6 +252,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
1, // E5B9G9R9_FLOAT
1, // D32_FLOAT
1, // D16_UNORM
+ 1, // X8_D24_UNORM
1, // S8_UINT
1, // D24_UNORM_S8_UINT
1, // S8_UINT_D24_UNORM
@@ -360,6 +362,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
1, // E5B9G9R9_FLOAT
1, // D32_FLOAT
1, // D16_UNORM
+ 1, // X8_D24_UNORM
1, // S8_UINT
1, // D24_UNORM_S8_UINT
1, // S8_UINT_D24_UNORM
@@ -469,6 +472,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
32, // E5B9G9R9_FLOAT
32, // D32_FLOAT
16, // D16_UNORM
+ 32, // X8_D24_UNORM
8, // S8_UINT
32, // D24_UNORM_S8_UINT
32, // S8_UINT_D24_UNORM
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index 11ced6c38..8c774f512 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -138,8 +138,16 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::E5B9G9R9_FLOAT;
case Hash(TextureFormat::Z32, FLOAT):
return PixelFormat::D32_FLOAT;
+ case Hash(TextureFormat::Z32, FLOAT, UINT, UINT, UINT, LINEAR):
+ 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::X8Z24, UNORM):
+ return PixelFormat::X8_D24_UNORM;
+ case Hash(TextureFormat::X8Z24, UNORM, UINT, UINT, UINT, LINEAR):
+ return PixelFormat::X8_D24_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/formatter.h b/src/video_core/texture_cache/formatter.h
index 9ee57a076..cabbfcb2d 100644
--- a/src/video_core/texture_cache/formatter.h
+++ b/src/video_core/texture_cache/formatter.h
@@ -211,6 +211,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
return "D32_FLOAT";
case PixelFormat::D16_UNORM:
return "D16_UNORM";
+ case PixelFormat::X8_D24_UNORM:
+ return "X8_D24_UNORM";
case PixelFormat::S8_UINT:
return "S8_UINT";
case PixelFormat::D24_UNORM_S8_UINT:
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
index 55d49d017..0587d7b72 100644
--- a/src/video_core/texture_cache/image_base.h
+++ b/src/video_core/texture_cache/image_base.h
@@ -41,7 +41,7 @@ enum class ImageFlagBits : u32 {
IsRescalable = 1 << 15,
AsynchronousDecode = 1 << 16,
- IsDecoding = 1 << 17, ///< Is currently being decoded asynchornously.
+ IsDecoding = 1 << 17, ///< Is currently being decoded asynchronously.
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
index 0c5f4450d..18b9250f9 100644
--- a/src/video_core/texture_cache/image_view_base.cpp
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -85,6 +85,7 @@ bool ImageViewBase::SupportsAnisotropy() const noexcept {
// Depth formats
case PixelFormat::D32_FLOAT:
case PixelFormat::D16_UNORM:
+ case PixelFormat::X8_D24_UNORM:
// Stencil formats
case PixelFormat::S8_UINT:
// DepthStencil formats
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 4457b366f..d575c57ca 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;
}
@@ -1003,6 +1016,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
+ runtime.TransitionImageLayout(image);
return;
}
if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index a83f5d41c..15596c925 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -68,6 +68,7 @@ struct LevelInfo {
Extent2D tile_size;
u32 bpp_log2;
u32 tile_width_spacing;
+ u32 num_levels;
};
[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) {
@@ -118,11 +119,11 @@ template <u32 GOB_EXTENT>
}
[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size,
- u32 level) {
+ u32 level, u32 num_levels) {
return {
.width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level),
.height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level),
- .depth = level == 0
+ .depth = level == 0 && num_levels == 1
? block_size.depth
: AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level),
};
@@ -166,7 +167,7 @@ template <u32 GOB_EXTENT>
}
[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) {
- if (level == 0) {
+ if (level == 0 && info.num_levels == 1) {
return Extent3D{
.width = info.block.width,
.height = info.block.height,
@@ -257,7 +258,7 @@ template <u32 GOB_EXTENT>
}
[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block,
- u32 tile_width_spacing) {
+ u32 tile_width_spacing, u32 num_levels) {
const u32 bytes_per_block = BytesPerBlock(format);
return {
.size =
@@ -270,16 +271,18 @@ template <u32 GOB_EXTENT>
.tile_size = DefaultBlockSize(format),
.bpp_log2 = BytesPerBlockLog2(bytes_per_block),
.tile_width_spacing = tile_width_spacing,
+ .num_levels = num_levels,
};
}
[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) {
- return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing);
+ return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing,
+ info.resources.levels);
}
[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block,
u32 tile_width_spacing, u32 level) {
- const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing);
+ const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing, level);
u32 offset = 0;
for (u32 current_level = 0; current_level < level; ++current_level) {
offset += CalculateLevelSize(info, current_level);
@@ -466,7 +469,7 @@ template <u32 GOB_EXTENT>
};
const u32 bpp_log2 = BytesPerBlockLog2(info.format);
const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing);
- const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0);
+ const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0, info.resources.levels);
return Extent3D{
.width = Common::AlignUpLog2(num_tiles.width, alignment),
.height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height),
@@ -533,7 +536,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
UNIMPLEMENTED_IF(copy.image_extent != level_size);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+ const Extent3D block =
+ AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
size_t host_offset = copy.buffer_offset;
@@ -698,7 +702,7 @@ u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level) {
const Extent2D tile_size = DefaultBlockSize(info.format);
const Extent3D level_size = AdjustMipSize(info.size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level);
+ const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level, info.resources.levels);
const u32 bpp_log2 = BytesPerBlockLog2(info.format);
return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing);
}
@@ -887,7 +891,8 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
.image_extent = level_size,
};
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+ const Extent3D block =
+ AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
size_t guest_layer_offset = 0;
@@ -1041,7 +1046,7 @@ Extent3D MipBlockSize(const ImageInfo& info, u32 level) {
const Extent2D tile_size = DefaultBlockSize(info.format);
const Extent3D level_size = AdjustMipSize(info.size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- return AdjustMipBlockSize(num_tiles, level_info.block, level);
+ return AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
}
boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) {
@@ -1063,7 +1068,8 @@ boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const I
for (s32 level = 0; level < num_levels; ++level) {
const Extent3D level_size = AdjustMipSize(size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+ const Extent3D block =
+ AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
params[level] = SwizzleParameters{
.num_tiles = num_tiles,
.block = block,
@@ -1195,7 +1201,7 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
return std::nullopt;
}
} else {
- // Format comaptibility is not relaxed, ensure we are creating a view on a compatible format
+ // Format compatibility is not relaxed, ensure we are creating a view on a compatible format
if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) {
return std::nullopt;
}
@@ -1292,11 +1298,11 @@ u32 MapSizeBytes(const ImageBase& image) {
}
}
-static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) ==
+static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) ==
0x7f8000);
-static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x40000);
+static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x40000);
-static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0}, 0) == 0x40000);
+static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x40000);
static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) ==
0x2afc00);
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 617417040..876cec2e8 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -76,12 +76,20 @@ constexpr std::array VK_FORMAT_R32G32B32_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 {
- AmpereOrNewer,
+ KeplerOrOlder,
+ Maxwell,
+ Pascal,
+ Volta,
Turing,
- VoltaOrOlder,
+ AmpereOrNewer,
};
template <typename T>
@@ -110,6 +118,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
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;
}
@@ -193,6 +203,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
VK_FORMAT_BC7_UNORM_BLOCK,
VK_FORMAT_D16_UNORM,
VK_FORMAT_D16_UNORM_S8_UINT,
+ VK_FORMAT_X8_D24_UNORM_PACK32,
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_D32_SFLOAT_S8_UINT,
@@ -238,6 +249,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,
@@ -313,13 +325,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
physical.GetProperties2(physical_properties);
if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
// Only Ampere and newer support this feature
+ // TODO: Find a way to differentiate Ampere and Ada
return NvidiaArchitecture::AmpereOrNewer;
}
- }
- if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
return NvidiaArchitecture::Turing;
}
- return NvidiaArchitecture::VoltaOrOlder;
+
+ if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) {
+ VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{};
+ advanced_blending_props.sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT;
+ VkPhysicalDeviceProperties2 physical_properties{};
+ physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+ physical_properties.pNext = &advanced_blending_props;
+ physical.GetProperties2(physical_properties);
+ if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) {
+ return NvidiaArchitecture::Maxwell;
+ }
+
+ if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
+ VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{};
+ conservative_raster_props.sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
+ physical_properties.pNext = &conservative_raster_props;
+ physical.GetProperties2(physical_properties);
+ if (conservative_raster_props.degenerateLinesRasterized) {
+ return NvidiaArchitecture::Volta;
+ }
+ return NvidiaArchitecture::Pascal;
+ }
+ }
+
+ return NvidiaArchitecture::KeplerOrOlder;
}
std::vector<const char*> ExtensionListForVulkan(
@@ -420,7 +457,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
first_next = &diagnostics_nv;
}
- is_blit_depth_stencil_supported = TestDepthStencilBlits();
+ is_blit_depth24_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D24_UNORM_S8_UINT);
+ is_blit_depth32_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D32_SFLOAT_S8_UINT);
is_optimal_astc_supported = ComputeIsOptimalAstcSupported();
is_warp_potentially_bigger = !extensions.subgroup_size_control ||
properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize;
@@ -495,19 +533,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (is_nvidia) {
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
- switch (arch) {
- case NvidiaArchitecture::AmpereOrNewer:
+ if (arch >= NvidiaArchitecture::AmpereOrNewer) {
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
features.shader_float16_int8.shaderFloat16 = false;
- break;
- case NvidiaArchitecture::Turing:
- break;
- case NvidiaArchitecture::VoltaOrOlder:
+ } else if (arch <= NvidiaArchitecture::Volta) {
if (nv_major_version < 527) {
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
- break;
}
if (nv_major_version >= 510) {
LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
@@ -652,7 +685,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
+ } else if (extensions.push_descriptor && is_nvidia) {
+ const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
+ if (arch <= NvidiaArchitecture::Pascal) {
+ LOG_WARNING(Render_Vulkan,
+ "Pascal and older architectures have broken VK_KHR_push_descriptor");
+ RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
+ }
}
+
if (is_mvk) {
LOG_WARNING(Render_Vulkan,
"MVK driver breaks when using more than 16 vertex attributes/bindings");
@@ -774,14 +815,13 @@ bool Device::ComputeIsOptimalAstcSupported() const {
return true;
}
-bool Device::TestDepthStencilBlits() const {
+bool Device::TestDepthStencilBlits(VkFormat format) const {
static constexpr VkFormatFeatureFlags required_features =
VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
const auto test_features = [](VkFormatProperties props) {
return (props.optimalTilingFeatures & required_features) == required_features;
};
- return test_features(format_properties.at(VK_FORMAT_D32_SFLOAT_S8_UINT)) &&
- test_features(format_properties.at(VK_FORMAT_D24_UNORM_S8_UINT));
+ return test_features(format_properties.at(format));
}
bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
@@ -1051,6 +1091,13 @@ void Device::RemoveUnsuitableExtensions() {
RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
+ // VK_EXT_depth_bias_control
+ extensions.depth_bias_control =
+ features.depth_bias_control.depthBiasControl &&
+ features.depth_bias_control.leastRepresentableValueForceUnormRepresentation;
+ RemoveExtensionFeatureIfUnsuitable(extensions.depth_bias_control, features.depth_bias_control,
+ VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME);
+
// VK_EXT_depth_clip_control
extensions.depth_clip_control = features.depth_clip_control.depthClipControl;
RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control,
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 488fdd313..282a2925d 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -41,10 +41,12 @@ VK_DEFINE_HANDLE(VmaAllocator)
// Define all features which may be used by the implementation and require an extension here.
#define FOR_EACH_VK_FEATURE_EXT(FEATURE) \
FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \
+ FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \
FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \
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, \
@@ -60,6 +62,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
// Define miscellaneous extensions which may be used by the implementation here.
#define FOR_EACH_VK_EXTENSION(EXTENSION) \
+ EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \
EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \
EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \
EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \
@@ -92,11 +95,14 @@ VK_DEFINE_HANDLE(VmaAllocator)
// Define extensions where the absence of the extension may result in a degraded experience.
#define FOR_EACH_VK_RECOMMENDED_EXTENSION(EXTENSION_NAME) \
+ EXTENSION_NAME(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME) \
+ EXTENSION_NAME(VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME) \
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) \
@@ -143,7 +149,11 @@ VK_DEFINE_HANDLE(VmaAllocator)
// Define features where the absence of the feature may result in a degraded experience.
#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \
FEATURE_NAME(custom_border_color, customBorderColors) \
+ FEATURE_NAME(depth_bias_control, depthBiasControl) \
+ FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \
+ FEATURE_NAME(depth_bias_control, depthBiasExact) \
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) \
@@ -304,7 +314,7 @@ public:
return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
}
- /// Returns true if the device suppors float64 natively.
+ /// Returns true if the device supports float64 natively.
bool IsFloat64Supported() const {
return features.features.shaderFloat64;
}
@@ -319,6 +329,11 @@ public:
return features.shader_float16_int8.shaderInt8;
}
+ /// Returns true if the device supports binding multisample images as storage images.
+ bool IsStorageImageMultisampleSupported() const {
+ return features.features.shaderStorageImageMultisample;
+ }
+
/// Returns true if the device warp size can potentially be bigger than guest's warp size.
bool IsWarpSizePotentiallyBiggerThanGuest() const {
return is_warp_potentially_bigger;
@@ -359,9 +374,14 @@ public:
return features.features.depthBounds;
}
- /// Returns true when blitting from and to depth stencil images is supported.
- bool IsBlitDepthStencilSupported() const {
- return is_blit_depth_stencil_supported;
+ /// Returns true when blitting from and to D24S8 images is supported.
+ bool IsBlitDepth24Stencil8Supported() const {
+ return is_blit_depth24_stencil8_supported;
+ }
+
+ /// Returns true when blitting from and to D32S8 images is supported.
+ bool IsBlitDepth32Stencil8Supported() const {
+ return is_blit_depth32_stencil8_supported;
}
/// Returns true if the device supports VK_NV_viewport_swizzle.
@@ -449,6 +469,11 @@ public:
return extensions.depth_clip_control;
}
+ /// Returns true if the device supports VK_EXT_depth_bias_control.
+ bool IsExtDepthBiasControlSupported() const {
+ return extensions.depth_bias_control;
+ }
+
/// Returns true if the device supports VK_EXT_shader_viewport_index_layer.
bool IsExtShaderViewportIndexLayerSupported() const {
return extensions.shader_viewport_index_layer;
@@ -488,6 +513,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;
@@ -528,6 +558,10 @@ public:
return extensions.shader_atomic_int64;
}
+ bool IsExtConditionalRendering() const {
+ return extensions.conditional_rendering;
+ }
+
bool HasTimelineSemaphore() const;
/// Returns the minimum supported version of SPIR-V.
@@ -600,6 +634,10 @@ public:
return features.robustness2.nullDescriptor;
}
+ bool HasExactDepthBiasControl() const {
+ return features.depth_bias_control.depthBiasExact;
+ }
+
u32 GetMaxVertexInputAttributes() const {
return properties.properties.limits.maxVertexInputAttributes;
}
@@ -666,7 +704,7 @@ private:
bool ComputeIsOptimalAstcSupported() const;
/// Returns true if the device natively supports blitting depth stencil images.
- bool TestDepthStencilBlits() const;
+ bool TestDepthStencilBlits(VkFormat format) const;
private:
VkInstance instance; ///< Vulkan instance.
@@ -730,25 +768,26 @@ private:
VkPhysicalDeviceProperties2 properties2{};
// Misc features
- bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats.
- bool is_blit_depth_stencil_supported{}; ///< Support for blitting from and to depth stencil.
- bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest.
- bool is_integrated{}; ///< Is GPU an iGPU.
- bool is_virtual{}; ///< Is GPU a virtual GPU.
- bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device.
- bool has_broken_compute{}; ///< Compute shaders can cause crashes
- bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit
- bool has_renderdoc{}; ///< Has RenderDoc attached
- bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
- bool supports_d24_depth{}; ///< Supports D24 depth buffers.
- bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
- bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
- bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
- bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
- bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
- bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
- u64 device_access_memory{}; ///< Total size of device local memory in bytes.
- u32 sets_per_pool{}; ///< Sets per Description Pool
+ bool is_optimal_astc_supported{}; ///< Support for all guest ASTC formats.
+ bool is_blit_depth24_stencil8_supported{}; ///< Support for blitting from and to D24S8.
+ bool is_blit_depth32_stencil8_supported{}; ///< Support for blitting from and to D32S8.
+ bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest.
+ bool is_integrated{}; ///< Is GPU an iGPU.
+ bool is_virtual{}; ///< Is GPU a virtual GPU.
+ bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device.
+ bool has_broken_compute{}; ///< Compute shaders can cause crashes
+ bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit
+ bool has_renderdoc{}; ///< Has RenderDoc attached
+ bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
+ bool supports_d24_depth{}; ///< Supports D24 depth buffers.
+ bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
+ bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
+ bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
+ bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
+ bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
+ bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
+ u64 device_access_memory{}; ///< Total size of device local memory in bytes.
+ u32 sets_per_pool{}; ///< Sets per Description Pool
// Telemetry parameters
std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions.
diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp
index bc16145be..180657a75 100644
--- a/src/video_core/vulkan_common/vulkan_instance.cpp
+++ b/src/video_core/vulkan_common/vulkan_instance.cpp
@@ -76,11 +76,9 @@ namespace {
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
}
#endif
- 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);
+ 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_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 3ef381a38..8dd1667f3 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -9,6 +9,7 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/literals.h"
#include "common/logging/log.h"
#include "common/polyfill_ranges.h"
#include "video_core/vulkan_common/vma.h"
@@ -65,12 +66,12 @@ struct Range {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
- return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
+ return VMA_ALLOCATION_CREATE_MAPPED_BIT |
+ VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
- return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
+ return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal:
- return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
- VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
+ return {};
}
return {};
}
@@ -212,7 +213,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
: device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
buffer_image_granularity{
- device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {}
+ device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
+ // GPUs not supporting rebar may only have a region with less than 256MB host visible/device
+ // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
+ // the heap running out of memory. With RenderDoc attached and only a small host/device region,
+ // only allow the stream buffer in this memory heap.
+ if (device.HasDebuggingToolAttached()) {
+ using namespace Common::Literals;
+ ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
+ if (heap.size <= 256_MiB) {
+ valid_memory_types &= ~(1u << index);
+ }
+ });
+ }
+}
MemoryAllocator::~MemoryAllocator() = default;
@@ -239,12 +253,11 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
const VmaAllocationCreateInfo alloc_ci = {
- .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT |
- MemoryUsageVmaFlags(usage),
+ .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
- .memoryTypeBits = 0,
+ .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index f449bc8d0..38a182bcb 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -7,6 +7,7 @@
#include <span>
#include <vector>
#include "common/common_types.h"
+#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
VK_DEFINE_HANDLE(VmaAllocator)
@@ -26,6 +27,18 @@ enum class MemoryUsage {
Stream, ///< Requests device local host visible buffer, falling back host memory.
};
+template <typename F>
+void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) {
+ auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
+ for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
+ auto& memory_type = memory_props.memoryTypes[i];
+ if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
+ (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
+ f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
+ }
+ }
+}
+
/// Ownership handle of a memory commitment.
/// Points to a subregion of a memory allocation.
class MemoryCommit {
@@ -124,6 +137,7 @@ private:
std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
// and optimal images
+ u32 valid_memory_types{~0u};
};
} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 78e5a248f..2f3254a97 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -75,6 +75,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkBeginCommandBuffer);
X(vkBindBufferMemory);
X(vkBindImageMemory);
+ X(vkCmdBeginConditionalRenderingEXT);
X(vkCmdBeginQuery);
X(vkCmdBeginRenderPass);
X(vkCmdBeginTransformFeedbackEXT);
@@ -91,13 +92,17 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCmdCopyBufferToImage);
X(vkCmdCopyImage);
X(vkCmdCopyImageToBuffer);
+ X(vkCmdCopyQueryPoolResults);
X(vkCmdDispatch);
+ X(vkCmdDispatchIndirect);
X(vkCmdDraw);
X(vkCmdDrawIndexed);
X(vkCmdDrawIndirect);
X(vkCmdDrawIndexedIndirect);
X(vkCmdDrawIndirectCount);
X(vkCmdDrawIndexedIndirectCount);
+ X(vkCmdDrawIndirectByteCountEXT);
+ X(vkCmdEndConditionalRenderingEXT);
X(vkCmdEndQuery);
X(vkCmdEndRenderPass);
X(vkCmdEndTransformFeedbackEXT);
@@ -108,6 +113,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkCmdPushDescriptorSetWithTemplateKHR);
X(vkCmdSetBlendConstants);
X(vkCmdSetDepthBias);
+ X(vkCmdSetDepthBias2EXT);
X(vkCmdSetDepthBounds);
X(vkCmdSetEvent);
X(vkCmdSetScissor);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index c226a2a29..0487cd3b6 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -117,6 +117,9 @@ public:
virtual ~Exception() = default;
const char* what() const noexcept override;
+ VkResult GetResult() const noexcept {
+ return result;
+ }
private:
VkResult result;
@@ -185,6 +188,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkBeginCommandBuffer vkBeginCommandBuffer{};
PFN_vkBindBufferMemory vkBindBufferMemory{};
PFN_vkBindImageMemory vkBindImageMemory{};
+ PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT{};
PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{};
PFN_vkCmdBeginQuery vkCmdBeginQuery{};
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{};
@@ -202,13 +206,17 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{};
PFN_vkCmdCopyImage vkCmdCopyImage{};
PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{};
+ PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults{};
PFN_vkCmdDispatch vkCmdDispatch{};
+ PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect{};
PFN_vkCmdDraw vkCmdDraw{};
PFN_vkCmdDrawIndexed vkCmdDrawIndexed{};
PFN_vkCmdDrawIndirect vkCmdDrawIndirect{};
PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect{};
PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{};
PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{};
+ PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT{};
+ PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{};
PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{};
PFN_vkCmdEndQuery vkCmdEndQuery{};
PFN_vkCmdEndRenderPass vkCmdEndRenderPass{};
@@ -221,6 +229,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{};
PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{};
PFN_vkCmdSetDepthBias vkCmdSetDepthBias{};
+ PFN_vkCmdSetDepthBias2EXT vkCmdSetDepthBias2EXT{};
PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{};
PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{};
PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{};
@@ -1181,6 +1190,13 @@ public:
count_offset, draw_count, stride);
}
+ void DrawIndirectByteCountEXT(u32 instance_count, u32 first_instance, VkBuffer counter_buffer,
+ VkDeviceSize counter_buffer_offset, u32 counter_offset,
+ u32 stride) {
+ dld->vkCmdDrawIndirectByteCountEXT(handle, instance_count, first_instance, counter_buffer,
+ counter_buffer_offset, counter_offset, stride);
+ }
+
void ClearAttachments(Span<VkClearAttachment> attachments,
Span<VkClearRect> rects) const noexcept {
dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
@@ -1209,6 +1225,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,
@@ -1265,6 +1285,13 @@ public:
regions.data());
}
+ void CopyQueryPoolResults(VkQueryPool query_pool, u32 first_query, u32 query_count,
+ VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize stride,
+ VkQueryResultFlags flags) const noexcept {
+ dld->vkCmdCopyQueryPoolResults(handle, query_pool, first_query, query_count, dst_buffer,
+ dst_offset, stride, flags);
+ }
+
void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size,
u32 data) const noexcept {
dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data);
@@ -1310,6 +1337,18 @@ public:
dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor);
}
+ void SetDepthBias(float constant_factor, float clamp, float slope_factor,
+ VkDepthBiasRepresentationInfoEXT* extra) const noexcept {
+ VkDepthBiasInfoEXT info{
+ .sType = VK_STRUCTURE_TYPE_DEPTH_BIAS_INFO_EXT,
+ .pNext = extra,
+ .depthBiasConstantFactor = constant_factor,
+ .depthBiasClamp = clamp,
+ .depthBiasSlopeFactor = slope_factor,
+ };
+ dld->vkCmdSetDepthBias2EXT(handle, &info);
+ }
+
void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept {
dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds);
}
@@ -1443,6 +1482,15 @@ public:
counter_buffers, counter_buffer_offsets);
}
+ void BeginConditionalRenderingEXT(
+ const VkConditionalRenderingBeginInfoEXT& info) const noexcept {
+ dld->vkCmdBeginConditionalRenderingEXT(handle, &info);
+ }
+
+ void EndConditionalRenderingEXT() const noexcept {
+ dld->vkCmdEndConditionalRenderingEXT(handle);
+ }
+
void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept {
const VkDebugUtilsLabelEXT label_info{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
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 8f86a1553..34208ed74 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -195,6 +195,8 @@ add_executable(yuzu
multiplayer/state.cpp
multiplayer/state.h
multiplayer/validation.h
+ play_time_manager.cpp
+ play_time_manager.h
precompiled_headers.h
qt_common.cpp
qt_common.h
@@ -382,7 +384,7 @@ if (USE_DISCORD_PRESENCE)
discord_impl.cpp
discord_impl.h
)
- target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib)
+ target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif()
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..ca0e14fad 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"
@@ -21,6 +23,7 @@
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
#include "yuzu/main.h"
+#include "yuzu/util/controller_navigation.h"
namespace {
@@ -130,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
};
+ ui->labelError->setVisible(false);
+
// Setup/load everything prior to setting up connections.
// This avoids unintentionally changing the states of elements while loading them in.
SetSupportedControllers();
@@ -141,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
LoadConfiguration();
+ controller_navigation = new ControllerNavigation(system.HIDCore(), this);
+
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
SetExplainText(i);
UpdateControllerIcon(i);
@@ -149,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
if (checked) {
+ // Hide eventual error message about number of controllers
+ ui->labelError->setVisible(false);
for (std::size_t index = 0; index <= i; ++index) {
connected_controller_checkboxes[index]->setChecked(checked);
}
@@ -197,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
&QtControllerSelectorDialog::ApplyConfiguration);
+ connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+ [this](Qt::Key key) {
+ QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+ QCoreApplication::postEvent(this, event);
+ });
+
// Enhancement: Check if the parameters have already been met before disconnecting controllers.
// If all the parameters are met AND only allows a single player,
// stop the constructor here as we do not need to continue.
@@ -215,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
}
QtControllerSelectorDialog::~QtControllerSelectorDialog() {
+ controller_navigation->UnloadController();
system.HIDCore().DisableAllControllerConfiguration();
}
@@ -226,9 +242,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());
@@ -287,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
dialog.exec();
}
+void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
+ const auto num_connected_players = static_cast<int>(
+ std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
+ [](const QGroupBox* player) { return player->isChecked(); }));
+
+ const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
+ // Display error message when trying to validate using "Enter" and "OK" button is disabled
+ ui->labelError->setVisible(true);
+ return;
+ } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
+ // Remove a player if possible
+ connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
+ return;
+ } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
+ // Add a player, if possible
+ ui->labelError->setVisible(false);
+ connected_controller_checkboxes[num_connected_players]->setChecked(true);
+ return;
+ }
+ QDialog::keyPressEvent(evt);
+}
+
bool QtControllerSelectorDialog::CheckIfParametersMet() {
// Here, we check and validate the current configuration against all applicable parameters.
const auto num_connected_players = static_cast<int>(
@@ -616,8 +659,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/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 2fdc35857..7f0673d06 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -34,6 +34,8 @@ class HIDCore;
enum class NpadStyleIndex : u8;
} // namespace Core::HID
+class ControllerNavigation;
+
class QtControllerSelectorDialog final : public QDialog {
Q_OBJECT
@@ -46,6 +48,8 @@ public:
int exec() override;
+ void keyPressEvent(QKeyEvent* evt) override;
+
private:
// Applies the current configuration.
void ApplyConfiguration();
@@ -110,6 +114,8 @@ private:
Core::System& system;
+ ControllerNavigation* controller_navigation = nullptr;
+
// This is true if and only if all parameters are met. Otherwise, this is false.
// This determines whether the "OK" button can be clicked to exit the applet.
bool parameters_met{false};
diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui
index 729e921ee..6f7cb3c13 100644
--- a/src/yuzu/applets/qt_controller.ui
+++ b/src/yuzu/applets/qt_controller.ui
@@ -2624,13 +2624,53 @@
</spacer>
</item>
<item alignment="Qt::AlignBottom">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
+ <widget class="QWidget" name="closeButtons" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_46">
+ <property name="spacing">
+ <number>7</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="QLabel" name="labelError">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QLabel { color : red; }</string>
+ </property>
+ <property name="text">
+ <string>Not enough controllers</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
</layout>
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 407988b8f..2afa72140 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -928,8 +928,8 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
const Layout::FramebufferLayout layout{[]() {
u32 height = UISettings::values.screenshot_height.GetValue();
if (height == 0) {
- height = Settings::values.use_docked_mode.GetValue() ? Layout::ScreenDocked::Height
- : Layout::ScreenUndocked::Height;
+ height = Settings::IsDockedMode() ? Layout::ScreenDocked::Height
+ : Layout::ScreenUndocked::Height;
height *= Settings::values.resolution_info.up_factor;
}
const u32 width =
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index b22c83303..d5157c502 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -9,6 +9,7 @@
#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"
@@ -85,9 +86,9 @@ 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 = {
@@ -127,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
@@ -376,7 +377,7 @@ void Config::ReadControlValues() {
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);
}
if (IsCustomConfig()) {
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 0ac74c8e7..727feebfb 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -9,6 +9,7 @@
#include <QMetaType>
#include <QVariant>
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "yuzu/uisettings.h"
class QSettings;
@@ -51,7 +52,7 @@ 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::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;
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 9ccfb2435..81dd51ad3 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -42,6 +42,9 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
for (auto* setting : Settings::values.linkage.by_category[category]) {
settings.push_back(setting);
}
+ for (auto* setting : UISettings::values.linkage.by_category[category]) {
+ settings.push_back(setting);
+ }
};
push(Settings::Category::Audio);
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 97c7d9022..66b8b7459 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -18,8 +18,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>829</width>
- <height>758</height>
+ <width>842</width>
+ <height>741</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_1">
@@ -260,7 +260,7 @@
<string>Graphics</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
- <item row="3" column="0">
+ <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>
@@ -270,33 +270,53 @@
</property>
</widget>
</item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="dump_shaders">
+ <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 will dump all the original assembler shaders from the disk shader cache or game as found</string>
+ <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
</property>
<property name="text">
- <string>Dump Game Shaders</string>
+ <string>Disable Macro HLE</string>
</property>
</widget>
</item>
<item row="7" column="0">
- <widget class="QCheckBox" name="disable_macro_hle">
+ <widget class="QCheckBox" name="dump_macros">
<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>
+ <string>When checked, it will dump all the macro programs of the GPU</string>
</property>
<property name="text">
- <string>Disable Macro HLE</string>
+ <string>Dump Maxwell Macros</string>
</property>
</widget>
</item>
- <item row="5" column="0">
+ <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>
@@ -309,6 +329,22 @@
</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">
@@ -322,55 +358,26 @@
</property>
</widget>
</item>
- <item row="6" column="0">
- <widget class="QCheckBox" name="dump_macros">
+ <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 macro programs of the GPU</string>
+ <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 Maxwell Macros</string>
+ <string>Dump Game Shaders</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="2" column="0">
- <widget class="QCheckBox" name="enable_nsight_aftermath">
- <property name="toolTip">
- <string>When checked, it enables Nsight Aftermath crash dumps</string>
- </property>
+ <widget class="QCheckBox" name="enable_renderdoc_hotkey">
<property name="text">
- <string>Enable Nsight Aftermath</string>
+ <string>Enable Renderdoc Hotkey</string>
</property>
</widget>
</item>
- <item row="8" 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>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 8622dc184..fd6bebf0f 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -193,14 +193,10 @@ void ConfigureGraphics::PopulateVSyncModeSelection() {
: vsync_mode_combobox_enum_map[current_index];
int index{};
const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device
- if (device == -1) {
- // Invalid device
- return;
- }
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;
vsync_mode_combobox->clear();
vsync_mode_combobox_enum_map.clear();
@@ -497,11 +493,19 @@ void ConfigureGraphics::RetrieveVulkanDevices() {
}
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
- if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) {
- return Settings::values.renderer_backend.GetValue(true);
+ 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;
}
- return static_cast<Settings::RendererBackend>(
- combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
- .at(api_combobox->currentIndex())
- .first);
+ return selected_backend;
}
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 7fce85bca..5a48e388b 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"
@@ -113,17 +115,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
for (std::size_t i = 0; i < player_tabs.size(); ++i) {
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
player_tabs[i]->layout()->addWidget(player_controllers[i]);
- connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) {
+ connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) {
// Ensures that the controllers are always connected in sequential order
- if (is_connected) {
- for (std::size_t index = 0; index <= i; ++index) {
- player_connected[index]->setChecked(is_connected);
- }
- } else {
- for (std::size_t index = i; index < player_tabs.size(); ++index) {
- player_connected[index]->setChecked(is_connected);
- }
- }
+ this->propagateMouseClickOnPlayers(i, checked, true);
});
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
&ConfigureInput::UpdateAllInputDevices);
@@ -181,6 +175,30 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
LoadConfiguration();
}
+void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) {
+ // Origin has already been toggled
+ if (!origin) {
+ player_connected[player_index]->setChecked(checked);
+ }
+
+ if (checked) {
+ // Check all previous buttons when checked
+ if (player_index > 0) {
+ propagateMouseClickOnPlayers(player_index - 1, checked, false);
+ }
+ } else {
+ // Unchecked all following buttons when unchecked
+ if (player_index < player_tabs.size() - 1) {
+ // Reconnect current player if it was the last one checked
+ // (player number was reduced by more than one)
+ if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) {
+ player_connected[player_index]->setCheckState(Qt::Checked);
+ }
+ propagateMouseClickOnPlayers(player_index + 1, checked, false);
+ }
+ }
+}
+
QList<QWidget*> ConfigureInput::GetSubTabs() const {
return {
ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
@@ -197,9 +215,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 +287,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_input.h b/src/yuzu/configuration/configure_input.h
index c89189c36..abb7f7089 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -56,6 +56,7 @@ private:
void UpdateDockedState(bool is_handheld);
void UpdateAllInputDevices();
void UpdateAllInputProfiles(std::size_t player_index);
+ void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
/// Load configuration settings.
void LoadConfiguration();
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index 4f9e8db08..b91d6ad4a 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -18,6 +18,7 @@
#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"
@@ -98,6 +99,12 @@ void ConfigurePerGame::ApplyConfiguration() {
addons_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_system.cpp b/src/yuzu/configuration/configure_system.cpp
index c4833f4e7..0c8e5c8b4 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -106,6 +106,11 @@ void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) {
push(Settings::values.linkage.by_category[Settings::Category::System]);
for (auto setting : settings) {
+ if (setting->Id() == Settings::values.use_docked_mode.Id() &&
+ Settings::IsConfiguringGlobal()) {
+ continue;
+ }
+
ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
if (widget == nullptr) {
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 34ab01617..82f3b6e78 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -4,6 +4,7 @@
#include "yuzu/configuration/configure_ui.h"
#include <array>
+#include <cstdlib>
#include <set>
#include <stdexcept>
#include <string>
@@ -94,11 +95,7 @@ static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* pa
}
static u32 ScreenshotDimensionToInt(const QString& height) {
- try {
- return std::stoi(height.toStdString());
- } catch (std::invalid_argument&) {
- return 0;
- }
+ return std::strtoul(height.toUtf8(), nullptr, 0);
}
ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
@@ -126,6 +123,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
+ connect(ui->show_play_time, &QCheckBox::stateChanged, this,
+ &ConfigureUi::RequestGameListUpdate);
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureUi::RequestGameListUpdate);
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -170,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.show_compat = ui->show_compat->isChecked();
UISettings::values.show_size = ui->show_size->isChecked();
UISettings::values.show_types = ui->show_types->isChecked();
+ UISettings::values.show_play_time = ui->show_play_time->isChecked();
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -182,6 +182,7 @@ void ConfigureUi::ApplyConfiguration() {
const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
UISettings::values.screenshot_height.SetValue(height);
+ RequestGameListUpdate();
system.ApplySettings();
}
@@ -197,6 +198,7 @@ void ConfigureUi::SetConfiguration() {
ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
ui->show_size->setChecked(UISettings::values.show_size.GetValue());
ui->show_types->setChecked(UISettings::values.show_types.GetValue());
+ ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
ui->game_icon_size_combobox->setCurrentIndex(
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
ui->folder_icon_size_combobox->setCurrentIndex(
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index cb66ef104..b8e648381 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -105,6 +105,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="show_play_time">
+ <property name="text">
+ <string>Show Play Time Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
<item>
<widget class="QLabel" name="game_icon_size_label">
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 335810788..3fe448f27 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -29,9 +29,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
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, audio_muted, "Mute audio", "");
INSERT(Settings, volume, "Volume:", "");
INSERT(Settings, dump_audio_commands, "", "");
+ INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
// Core
INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
@@ -135,7 +136,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
INSERT(Settings, region_index, "Region:", "");
INSERT(Settings, time_zone_index, "Time Zone:", "");
INSERT(Settings, sound_index, "Sound Output Mode:", "");
- INSERT(Settings, use_docked_mode, "", "");
+ INSERT(Settings, use_docked_mode, "Console Mode:", "");
INSERT(Settings, current_user, "", "");
// Controls
@@ -156,6 +157,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
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, confirm_before_stopping, "Confirm before stopping emulation", "");
INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
@@ -379,6 +381,16 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
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")}});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
+ {
+ PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"),
+ PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"),
+ PAIR(ConfirmStop, Ask_Never, "Never ask"),
+ }});
#undef PAIR
#undef CTX_PAIR
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index bdb38c8ea..ea8d7add4 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -23,6 +23,7 @@
#include <QLineEdit>
#include <QObject>
#include <QPushButton>
+#include <QRadioButton>
#include <QRegularExpression>
#include <QSizePolicy>
#include <QSlider>
@@ -62,7 +63,7 @@ static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) {
return tr("%", context.c_str());
}
- return QStringLiteral("");
+ return default_suffix;
}
QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) {
@@ -70,7 +71,7 @@ QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* paren
QStyle* style = parent->style();
QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton));
- QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(""), parent);
+ 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);
@@ -150,7 +151,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
return -1;
};
- const u32 setting_value = std::stoi(setting.ToString());
+ const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
combobox->setCurrentIndex(find_index(setting_value));
serializer = [this, enumeration]() {
@@ -159,7 +160,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
};
restore_func = [this, find_index]() {
- const u32 global_value = std::stoi(RelevantDefault(setting));
+ const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
combobox->setCurrentIndex(find_index(global_value));
};
@@ -171,6 +172,65 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
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) {
@@ -195,6 +255,59 @@ QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
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,
@@ -218,27 +331,20 @@ QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& gi
layout->setContentsMargins(0, 0, 0, 0);
- int max_val = std::stoi(setting.MaxVal());
-
- QString suffix =
- given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
+ QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
const QString use_format = QStringLiteral("%1").append(suffix);
- QObject::connect(slider, &QAbstractSlider::valueChanged, [=](int value) {
- int present = (reversed ? max_val - value : value) * multiplier + 0.5f;
- feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
- });
-
- slider->setMinimum(std::stoi(setting.MinVal()));
- slider->setMaximum(max_val);
- slider->setValue(std::stoi(setting.ToString()));
+ 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);
- serializer = [this]() { return std::to_string(slider->value()); };
- restore_func = [this]() { slider->setValue(std::stoi(RelevantDefault(setting))); };
-
if (!Settings::IsConfiguringGlobal()) {
QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); });
}
@@ -250,14 +356,11 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
std::function<std::string()>& serializer,
std::function<void()>& restore_func,
const std::function<void()>& touch) {
- const int min_val =
- setting.Ranged() ? std::stoi(setting.MinVal()) : std::numeric_limits<int>::min();
- const int max_val =
- setting.Ranged() ? std::stoi(setting.MaxVal()) : std::numeric_limits<int>::max();
- const int default_val = std::stoi(setting.ToString());
+ 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 == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
+ QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
spinbox = new QSpinBox(this);
spinbox->setRange(min_val, max_val);
@@ -268,13 +371,13 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
serializer = [this]() { return std::to_string(spinbox->value()); };
restore_func = [this]() {
- auto value{std::stol(RelevantDefault(setting))};
+ 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::stoi(setting.ToStringGlobal())) {
+ if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) {
touch();
}
});
@@ -283,6 +386,42 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
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) {
@@ -292,7 +431,8 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
}
auto to_hex = [=](const std::string& input) {
- return QString::fromStdString(fmt::format("{:08x}", std::stoul(input)));
+ return QString::fromStdString(
+ fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0)));
};
QRegularExpressionValidator* regex = new QRegularExpressionValidator(
@@ -305,7 +445,7 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
line_edit->setValidator(regex);
auto hex_to_dec = [this]() -> std::string {
- return std::to_string(std::stoul(line_edit->text().toStdString(), nullptr, 16));
+ return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16));
};
serializer = [hex_to_dec]() { return hex_to_dec(); };
@@ -325,7 +465,8 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
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::stoll(setting.ToString());
+ 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);
@@ -338,7 +479,7 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
auto get_clear_val = [this, restrict, current_time]() {
return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() {
if (restrict && checkbox->checkState() == Qt::Checked) {
- return std::stoll(RelevantDefault(setting));
+ return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0);
}
return current_time;
}());
@@ -410,6 +551,8 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
return RequestType::Slider;
case Settings::Specialization::Countable:
return RequestType::SpinBox;
+ case Settings::Specialization::Radio:
+ return RequestType::RadioGroup;
default:
break;
}
@@ -438,9 +581,12 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
if (setting.TypeId() == typeid(bool)) {
data_component = CreateCheckBox(&setting, label, serializer, restore_func, touch);
} else if (setting.IsEnum()) {
- data_component = CreateCombobox(serializer, restore_func, touch);
- } else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) ||
- type == typeid(s64) || type == typeid(u8)) {
+ 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:
@@ -467,6 +613,20 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
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:
@@ -571,10 +731,10 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati
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("")};
+ return std::pair{QString::fromStdString(setting_label), QStringLiteral()};
}();
- if (label == QStringLiteral("")) {
+ if (label == QStringLiteral()) {
LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...",
setting.GetLabel());
return;
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
index e64693bab..226284cf3 100644
--- a/src/yuzu/configuration/shared_widget.h
+++ b/src/yuzu/configuration/shared_widget.h
@@ -22,6 +22,8 @@ class QObject;
class QPushButton;
class QSlider;
class QSpinBox;
+class QDoubleSpinBox;
+class QRadioButton;
namespace Settings {
class BasicSetting;
@@ -38,9 +40,14 @@ enum class RequestType {
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
@@ -64,8 +71,9 @@ public:
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 = 1.0f, Settings::BasicSetting* other_setting = nullptr,
- const QString& suffix = QStringLiteral(""));
+ float multiplier = default_multiplier,
+ Settings::BasicSetting* other_setting = nullptr,
+ const QString& suffix = default_suffix);
virtual ~Widget();
/**
@@ -87,10 +95,12 @@ public:
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,
@@ -106,6 +116,9 @@ private:
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);
@@ -120,6 +133,9 @@ private:
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;
@@ -139,14 +155,15 @@ public:
Widget* BuildWidget(Settings::BasicSetting* setting,
std::vector<std::function<void(bool)>>& apply_funcs,
RequestType request = RequestType::Default, bool managed = true,
- float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr,
- const QString& suffix = QStringLiteral("")) const;
+ 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 = QStringLiteral("")) const;
+ const QString& suffix = default_suffix) const;
const ComboboxTranslationMap& ComboboxTranslations() const;
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index b5a02700d..2bb1a0239 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() {
}
GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
- Core::System& system_, GMainWindow* parent)
- : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} {
+ PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
+ GMainWindow* parent)
+ : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
+ play_time_manager{play_time_manager_}, system{system_} {
watcher = new QFileSystemWatcher(this);
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
@@ -340,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
+ tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
item_model->setSortRole(GameListItemPath::SortRole);
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -548,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
+ QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data"));
QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
@@ -557,11 +561,12 @@ 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
QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
+#ifndef WIN32
QAction* create_applications_menu_shortcut =
shortcut_menu->addAction(tr("Add to Applications Menu"));
#endif
@@ -588,10 +593,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);
@@ -619,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
});
+ connect(remove_play_time_data, &QAction::triggered,
+ [this, program_id]() { emit RemovePlayTimeRequested(program_id); });
connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
});
@@ -628,15 +637,17 @@ 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]() {
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
});
-#ifndef WIN32
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
});
+#ifndef WIN32
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
});
@@ -785,6 +796,7 @@ void GameList::RetranslateUI() {
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
+ item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
}
void GameListSearchField::changeEvent(QEvent* event) {
@@ -812,15 +824,17 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
+ tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
+
+ // Before deleting rows, cancel the worker so that it is not using them
+ emit ShouldCancelWorker();
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
search_field->clear();
- emit ShouldCancelWorker();
-
GameListWorker* worker =
- new GameListWorker(vfs, provider, game_dirs, compatibility_list, system);
+ new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 6c2f75e53..712570cea 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -18,6 +18,7 @@
#include "core/core.h"
#include "uisettings.h"
#include "yuzu/compatibility_list.h"
+#include "yuzu/play_time_manager.h"
namespace Core {
class System;
@@ -28,6 +29,7 @@ class GameListWorker;
class GameListSearchField;
class GameListDir;
class GMainWindow;
+enum class AmLaunchType;
enum class StartGameType;
namespace FileSys {
@@ -74,11 +76,13 @@ public:
COLUMN_ADD_ONS,
COLUMN_FILE_TYPE,
COLUMN_SIZE,
+ COLUMN_PLAY_TIME,
COLUMN_COUNT, // Number of columns
};
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
- FileSys::ManualContentProvider* provider_, Core::System& system_,
+ FileSys::ManualContentProvider* provider_,
+ PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
GMainWindow* parent = nullptr);
~GameList() override;
@@ -103,7 +107,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,
@@ -112,7 +116,9 @@ signals:
void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
+ void RemovePlayTimeRequested(u64 program_id);
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);
@@ -166,6 +172,7 @@ private:
friend class GameListSearchField;
+ const PlayTime::PlayTimeManager& play_time_manager;
Core::System& system;
};
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 1800f090f..86a0c41d9 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -18,6 +18,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "yuzu/play_time_manager.h"
#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
@@ -221,6 +222,31 @@ public:
}
};
+/**
+ * GameListItem for Play Time values.
+ * This object stores the play time of a game in seconds, and its readable
+ * representation in minutes/hours
+ */
+class GameListItemPlayTime : public GameListItem {
+public:
+ static constexpr int PlayTimeRole = SortRole;
+
+ GameListItemPlayTime() = default;
+ explicit GameListItemPlayTime(const qulonglong time_seconds) {
+ setData(time_seconds, PlayTimeRole);
+ }
+
+ void setData(const QVariant& value, int role) override {
+ qulonglong time_seconds = value.toULongLong();
+ GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
+ GameListItem::setData(value, PlayTimeRole);
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong();
+ }
+};
+
class GameListDir : public GameListItem {
public:
static constexpr int GameDirRole = Qt::UserRole + 2;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 9404365b4..077ced12b 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -191,8 +191,10 @@ 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 PlayTime::PlayTimeManager& play_time_manager,
const FileSys::PatchManager& patch) {
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -210,7 +212,8 @@ 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),
+ new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
};
const auto patch_versions = GetGameListCachedObject(
@@ -226,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
FileSys::ManualContentProvider* provider_,
QVector<UISettings::GameDir>& game_dirs_,
- const CompatibilityList& compatibility_list_, Core::System& system_)
+ const CompatibilityList& compatibility_list_,
+ const PlayTime::PlayTimeManager& play_time_manager_,
+ Core::System& system_)
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
- compatibility_list{compatibility_list_}, system{system_} {}
+ compatibility_list{compatibility_list_},
+ play_time_manager{play_time_manager_}, system{system_} {}
GameListWorker::~GameListWorker() = default;
@@ -278,8 +284,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, play_time_manager, patch),
parent_dir);
}
}
@@ -287,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
GameListDir* parent_dir) {
const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
- if (stop_processing) {
+ if (stop_requested) {
// Breaks the callback loop.
return false;
}
@@ -354,8 +360,10 @@ 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,
+ play_time_manager, patch),
parent_dir);
}
} else {
@@ -368,8 +376,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),
+ emit EntryReady(MakeGameListEntry(physical_name, name,
+ Common::FS::GetSize(physical_name), icon,
+ *loader, program_id, compatibility_list,
+ play_time_manager, patch),
parent_dir);
}
}
@@ -389,7 +399,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
}
void GameListWorker::run() {
- stop_processing = false;
provider->ClearAllEntries();
for (UISettings::GameDir& game_dir : game_dirs) {
@@ -417,9 +426,11 @@ void GameListWorker::run() {
}
emit Finished(watch_list);
+ processing_completed.Set();
}
void GameListWorker::Cancel() {
this->disconnect();
- stop_processing = true;
+ stop_requested.store(true);
+ processing_completed.Wait();
}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 24a4e92c3..54dc05e30 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -12,7 +12,9 @@
#include <QRunnable>
#include <QString>
+#include "common/thread.h"
#include "yuzu/compatibility_list.h"
+#include "yuzu/play_time_manager.h"
namespace Core {
class System;
@@ -36,7 +38,9 @@ public:
explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
FileSys::ManualContentProvider* provider_,
QVector<UISettings::GameDir>& game_dirs_,
- const CompatibilityList& compatibility_list_, Core::System& system_);
+ const CompatibilityList& compatibility_list_,
+ const PlayTime::PlayTimeManager& play_time_manager_,
+ Core::System& system_);
~GameListWorker() override;
/// Starts the processing of directory tree information.
@@ -76,9 +80,12 @@ private:
FileSys::ManualContentProvider* provider;
QVector<UISettings::GameDir>& game_dirs;
const CompatibilityList& compatibility_list;
+ const PlayTime::PlayTimeManager& play_time_manager;
QStringList watch_list;
- std::atomic_bool stop_processing;
+
+ Common::Event processing_completed;
+ std::atomic_bool stop_requested = false;
Core::System& system;
};
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 a9d035f3d..1431cf2fe 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
@@ -16,6 +18,8 @@
#include <sys/socket.h>
#endif
+#include <boost/container/flat_set.hpp>
+
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/qt_amiibo_settings.h"
#include "applets/qt_controller.h"
@@ -63,6 +67,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#define QT_NO_OPENGL
#include <QClipboard>
#include <QDesktopServices>
+#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
@@ -72,6 +77,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QPushButton>
#include <QScreen>
#include <QShortcut>
+#include <QStandardPaths>
#include <QStatusBar>
#include <QString>
#include <QSysInfo>
@@ -94,6 +100,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#ifdef _WIN32
+#include <shlobj.h>
#include "common/windows/timer_resolution.h"
#endif
#ifdef ARCHITECTURE_x86_64
@@ -146,6 +153,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/install_dialog.h"
#include "yuzu/loading_screen.h"
#include "yuzu/main.h"
+#include "yuzu/play_time_manager.h"
#include "yuzu/startup_checks.h"
#include "yuzu/uisettings.h"
#include "yuzu/util/clickable_label.h"
@@ -203,7 +211,7 @@ void GMainWindow::ShowTelemetryCallout() {
tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
"data is collected</a> to help improve yuzu. "
"<br/><br/>Would you like to share your usage data with us?");
- if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) {
+ if (!question(this, tr("Telemetry"), telemetry_message)) {
Settings::values.enable_telemetry = false;
system->ApplySettings();
}
@@ -334,6 +342,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
discord_rpc->Update();
+ play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
+
system->GetRoomNetwork().Init();
RegisterMetaTypes();
@@ -442,8 +452,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 {
@@ -977,7 +992,7 @@ void GMainWindow::InitializeWidgets() {
render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
render_window->hide();
- game_list = new GameList(vfs, provider.get(), *system, this);
+ game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
ui->horizontalLayout->addWidget(game_list);
game_list_placeholder = new GameListPlaceholder(this);
@@ -1158,9 +1173,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();
}
});
@@ -1342,6 +1357,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;
@@ -1433,6 +1453,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
Settings::values.audio_muted = false;
auto_muted = false;
}
+ UpdateVolumeUI();
}
}
@@ -1446,7 +1467,11 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
&GMainWindow::OnGameListRemoveInstalledEntry);
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
+ connect(game_list, &GameList::RemovePlayTimeRequested, this,
+ &GMainWindow::OnGameListRemovePlayTimeData);
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);
@@ -1537,6 +1562,16 @@ void GMainWindow::ConnectMenuEvents() {
// Tools
connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
ReinitializeKeyBehavior::Warning));
+ connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
+ connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
+ connect_menu(ui->action_Load_Cabinet_Eraser,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartGameDataEraser); });
+ connect_menu(ui->action_Load_Cabinet_Restorer,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartRestorer); });
+ connect_menu(ui->action_Load_Cabinet_Formatter,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); });
+ connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
// TAS
@@ -1547,11 +1582,13 @@ 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);
}
void GMainWindow::UpdateMenuState() {
const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning();
+ const bool is_firmware_available = CheckFirmwarePresence();
const std::array running_actions{
ui->action_Stop,
@@ -1562,10 +1599,23 @@ void GMainWindow::UpdateMenuState() {
ui->action_Pause,
};
+ const std::array applet_actions{
+ ui->action_Load_Album,
+ ui->action_Load_Cabinet_Nickname_Owner,
+ ui->action_Load_Cabinet_Eraser,
+ ui->action_Load_Cabinet_Restorer,
+ ui->action_Load_Cabinet_Formatter,
+ ui->action_Load_Mii_Edit,
+ };
+
for (QAction* action : running_actions) {
action->setEnabled(emulation_running);
}
+ for (QAction* action : applet_actions) {
+ action->setEnabled(is_firmware_available && !emulation_running);
+ }
+
ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused);
if (emulation_running && is_paused) {
@@ -1698,7 +1748,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 +1761,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 +1866,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 +1917,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 +1948,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 +2065,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);
@@ -2034,6 +2135,8 @@ void GMainWindow::OnEmulationStopped() {
OnTasStateChanged();
render_window->FinalizeCamera();
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::None);
+
// Enable all controllers
system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
@@ -2229,40 +2332,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;
}
@@ -2295,9 +2420,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
}
}();
- if (QMessageBox::question(this, tr("Remove Entry"), entry_question,
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No) != QMessageBox::Yes) {
+ if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No)) {
return;
}
@@ -2396,8 +2520,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
}
}();
- if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No) != QMessageBox::Yes) {
+ if (!GMainWindow::question(this, tr("Remove File"), question,
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
return;
}
@@ -2420,6 +2544,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
}
}
+void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
+ if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+
+ play_time_manager->ResetProgramPlayTime(program_id);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+}
+
void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
const auto target_file_name = [target] {
switch (target) {
@@ -2535,50 +2670,48 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- FileSys::VirtualFile base_romfs;
- if (loader->ReadRomFS(base_romfs) != Loader::ResultStatus::Success) {
- failed();
- return;
- }
+ FileSys::VirtualFile packed_update_raw{};
+ loader->ReadUpdateRaw(packed_update_raw);
const auto& installed = system->GetContentProvider();
- const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
- if (!romfs_title_id) {
+ u64 title_id{};
+ u8 raw_type{};
+ if (!SelectRomFSDumpTarget(installed, program_id, &title_id, &raw_type)) {
failed();
return;
}
- const auto type = *romfs_title_id == program_id ? FileSys::ContentRecordType::Program
- : FileSys::ContentRecordType::Data;
- const auto base_nca = installed.GetEntry(*romfs_title_id, type);
+ 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 = {};
+ }
+
+ const auto base_romfs = base_nca->GetRomFS();
+ if (!base_romfs) {
+ failed();
+ return;
+ }
+
const auto dump_dir =
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 FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed};
- romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, nullptr, false);
- } else {
- romfs = installed.GetEntry(*romfs_title_id, type)->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);
@@ -2602,11 +2735,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!"),
@@ -2617,12 +2755,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."));
@@ -2634,6 +2775,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)));
@@ -2657,7 +2846,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
const QStringList args = QApplication::arguments();
std::filesystem::path yuzu_command = args[0].toStdString();
-#if defined(__linux__) || defined(__FreeBSD__)
// If relative path, make it an absolute path
if (yuzu_command.c_str()[0] == '.') {
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2680,44 +2868,52 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
-#endif // __linux__ || __FreeBSD__
std::filesystem::path target_directory{};
- // Determine target directory for shortcut
-#if defined(__linux__) || defined(__FreeBSD__)
- const char* home = std::getenv("HOME");
- const std::filesystem::path home_path = (home == nullptr ? "~" : home);
- const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
- if (target == GameListShortcutTarget::Desktop) {
- target_directory = home_path / "Desktop";
- if (!Common::FS::IsDir(target_directory)) {
- QMessageBox::critical(
- this, tr("Create Shortcut"),
- tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
- .arg(QString::fromStdString(target_directory)),
- QMessageBox::StandardButton::Ok);
- return;
- }
- } else if (target == GameListShortcutTarget::Applications) {
- target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
- "applications";
- if (!Common::FS::CreateDirs(target_directory)) {
- QMessageBox::critical(this, tr("Create Shortcut"),
- tr("Cannot create shortcut in applications menu. Path \"%1\" "
- "does not exist and cannot be created.")
- .arg(QString::fromStdString(target_directory)),
- QMessageBox::StandardButton::Ok);
- return;
+ switch (target) {
+ case GameListShortcutTarget::Desktop: {
+ const QString desktop_path =
+ QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
+ target_directory = desktop_path.toUtf8().toStdString();
+ break;
+ }
+ case GameListShortcutTarget::Applications: {
+ const QString applications_path =
+ QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
+ if (applications_path.isEmpty()) {
+ const char* home = std::getenv("HOME");
+ if (home != nullptr) {
+ target_directory = std::filesystem::path(home) / ".local/share/applications";
+ }
+ } else {
+ target_directory = applications_path.toUtf8().toStdString();
}
+ break;
+ }
+ default:
+ return;
+ }
+
+ const QDir dir(QString::fromStdString(target_directory.generic_string()));
+ if (!dir.exists()) {
+ QMessageBox::critical(this, tr("Create Shortcut"),
+ tr("Cannot create shortcut. Path \"%1\" does not exist.")
+ .arg(QString::fromStdString(target_directory.generic_string())),
+ QMessageBox::StandardButton::Ok);
+ return;
}
-#endif
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
// Determine full paths for icon and shortcut
#if defined(__linux__) || defined(__FreeBSD__)
+ const char* home = std::getenv("HOME");
+ const std::filesystem::path home_path = (home == nullptr ? "~" : home);
+ const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
+
std::filesystem::path system_icons_path =
- (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) /
+ (xdg_data_home == nullptr ? home_path / ".local/share/"
+ : std::filesystem::path(xdg_data_home)) /
"icons/hicolor/256x256";
if (!Common::FS::CreateDirs(system_icons_path)) {
QMessageBox::critical(
@@ -2733,9 +2929,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
const std::filesystem::path shortcut_path =
target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
: fmt::format("yuzu-{:016X}.desktop", program_id));
+#elif defined(WIN32)
+ std::filesystem::path icons_path =
+ Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
+ std::filesystem::path icon_path =
+ icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
+ : fmt::format("yuzu-{:016X}.ico", program_id)));
#else
- const std::filesystem::path icon_path{};
- const std::filesystem::path shortcut_path{};
+ std::string icon_extension;
#endif
// Get title from game file
@@ -2760,29 +2961,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
}
- QImage icon_jpeg =
+ QImage icon_data =
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
#if defined(__linux__) || defined(__FreeBSD__)
// Convert and write the icon as a PNG
- if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
+ if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
} else {
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
}
+#elif defined(WIN32)
+ if (!SaveIconToFile(icon_path.string(), icon_data)) {
+ LOG_ERROR(Frontend, "Could not write icon to file");
+ return;
+ }
#endif // __linux__
-#if defined(__linux__) || defined(__FreeBSD__)
+#ifdef _WIN32
+ // Replace characters that are illegal in Windows filenames by a dash
+ const std::string illegal_chars = "<>:\"/\\|?*";
+ for (char c : illegal_chars) {
+ std::replace(title.begin(), title.end(), c, '_');
+ }
+ const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
+#endif
+
const std::string comment =
tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "Switch;Nintendo;";
-#else
- const std::string comment{};
- const std::string arguments{};
- const std::string categories{};
- const std::string keywords{};
-#endif
+
if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
yuzu_command.string(), arguments, categories, keywords)) {
QMessageBox::critical(this, tr("Create Shortcut"),
@@ -2968,10 +3177,9 @@ void GMainWindow::OnMenuInstallToNAND() {
QFuture<InstallResult> future;
InstallResult result;
- if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) ||
- file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
+ if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
- future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); });
+ future = QtConcurrent::run([this, &file] { return InstallNSP(file); });
while (!future.isFinished()) {
QCoreApplication::processEvents();
@@ -3030,7 +3238,7 @@ void GMainWindow::OnMenuInstallToNAND() {
ui->action_Install_File_NAND->setEnabled(true);
}
-InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
+InstallResult GMainWindow::InstallNSP(const QString& filename) {
const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
const FileSys::VirtualFile& dest, std::size_t block_size) {
if (src == nullptr || dest == nullptr) {
@@ -3064,9 +3272,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
return InstallResult::Failure;
}
} else {
- const auto xci = std::make_shared<FileSys::XCI>(
- vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
- nsp = xci->GetSecurePartitionNSP();
+ return InstallResult::Failure;
}
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
@@ -3192,6 +3398,9 @@ void GMainWindow::OnStartGame() {
UpdateMenuState();
OnTasStateChanged();
+ play_time_manager->SetProgramId(system->GetApplicationProcessProgramID());
+ play_time_manager->Start();
+
discord_rpc->Update();
}
@@ -3199,14 +3408,18 @@ void GMainWindow::OnRestartGame() {
if (!system->IsPoweredOn()) {
return;
}
- // Make a copy since ShutdownGame edits game_path
- const auto current_game = QString(current_game_path);
- ShutdownGame();
- BootGame(current_game);
+
+ if (ConfirmShutdownGame()) {
+ // Make a copy since ShutdownGame edits game_path
+ const auto current_game = QString(current_game_path);
+ ShutdownGame();
+ BootGame(current_game);
+ }
}
void GMainWindow::OnPauseGame() {
emu_thread->SetRunning(false);
+ play_time_manager->Stop();
UpdateMenuState();
AllowOSSleep();
}
@@ -3223,15 +3436,39 @@ void GMainWindow::OnPauseContinueGame() {
}
void GMainWindow::OnStopGame() {
- if (system->GetExitLock() && !ConfirmForceLockedExit()) {
- return;
+ if (ConfirmShutdownGame()) {
+ play_time_manager->Stop();
+ // Update game list to show new play time
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+ if (OnShutdownBegin()) {
+ OnShutdownBeginDialog();
+ } else {
+ OnEmulationStopped();
+ }
}
+}
- if (OnShutdownBegin()) {
- OnShutdownBeginDialog();
+bool GMainWindow::ConfirmShutdownGame() {
+ if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) {
+ if (system->GetExitLocked()) {
+ if (!ConfirmForceLockedExit()) {
+ return false;
+ }
+ } else {
+ if (!ConfirmChangeGame()) {
+ return false;
+ }
+ }
} else {
- OnEmulationStopped();
+ if (UISettings::values.confirm_before_stopping.GetValue() ==
+ ConfirmStop::Ask_Based_On_Game &&
+ system->GetExitLocked()) {
+ if (!ConfirmForceLockedExit()) {
+ return false;
+ }
+ }
}
+ return true;
}
void GMainWindow::OnLoadComplete() {
@@ -3240,7 +3477,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() {
@@ -3610,22 +3848,11 @@ void GMainWindow::OnTasRecord() {
const bool is_recording = input_subsystem->GetTas()->Record();
if (!is_recording) {
is_tas_recording_dialog_active = true;
- ControllerNavigation* controller_navigation =
- new ControllerNavigation(system->HIDCore(), this);
- // Use QMessageBox instead of question so we can link controller navigation
- QMessageBox* box_dialog = new QMessageBox();
- box_dialog->setWindowTitle(tr("TAS Recording"));
- box_dialog->setText(tr("Overwrite file of player 1?"));
- box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
- box_dialog->setDefaultButton(QMessageBox::Yes);
- connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
- [box_dialog](Qt::Key key) {
- QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
- QCoreApplication::postEvent(box_dialog, event);
- });
- int res = box_dialog->exec();
- controller_navigation->UnloadController();
- input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
+
+ bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
+
+ input_subsystem->GetTas()->SaveRecording(answer);
is_tas_recording_dialog_active = false;
}
OnTasStateChanged();
@@ -3636,7 +3863,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);
@@ -3650,7 +3877,8 @@ 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);
}
@@ -3719,10 +3947,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);
@@ -3794,6 +4026,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
shortcut_stream.close();
return true;
+#elif defined(WIN32)
+ IShellLinkW* shell_link;
+ auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
+ (void**)&shell_link);
+ if (FAILED(hres)) {
+ return false;
+ }
+ shell_link->SetPath(
+ Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
+ shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
+ shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
+ shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
+
+ IPersistFile* persist_file;
+ hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
+ if (FAILED(hres)) {
+ return false;
+ }
+
+ hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
+ if (FAILED(hres)) {
+ return false;
+ }
+
+ persist_file->Release();
+ shell_link->Release();
+
+ return true;
#endif
return false;
}
@@ -3833,6 +4093,29 @@ void GMainWindow::OnLoadAmiibo() {
LoadAmiibo(filename);
}
+bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
+ QMessageBox::StandardButtons buttons,
+ QMessageBox::StandardButton defaultButton) {
+
+ QMessageBox* box_dialog = new QMessageBox(parent);
+ box_dialog->setWindowTitle(title);
+ box_dialog->setText(text);
+ box_dialog->setStandardButtons(buttons);
+ box_dialog->setDefaultButton(defaultButton);
+
+ ControllerNavigation* controller_navigation =
+ new ControllerNavigation(system->HIDCore(), box_dialog);
+ connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+ [box_dialog](Qt::Key key) {
+ QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+ QCoreApplication::postEvent(box_dialog, event);
+ });
+ int res = box_dialog->exec();
+
+ controller_navigation->UnloadController();
+ return res == QMessageBox::Yes;
+}
+
void GMainWindow::LoadAmiibo(const QString& filename) {
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
const QString title = tr("Error loading Amiibo data");
@@ -3866,6 +4149,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();
@@ -3884,6 +4269,76 @@ void GMainWindow::OnToggleStatusBar() {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
}
+void GMainWindow::OnAlbum() {
+ constexpr u64 AlbumId = 0x010000000000100Dull;
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Album applet."));
+ return;
+ }
+
+ auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program);
+ if (!album_nca) {
+ QMessageBox::warning(this, tr("Album Applet"),
+ tr("Album applet is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
+
+ const auto filename = QString::fromStdString(album_nca->GetFullPath());
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
+}
+
+void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
+ constexpr u64 CabinetId = 0x0100000000001002ull;
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Cabinet applet."));
+ return;
+ }
+
+ auto cabinet_nca = bis_system->GetEntry(CabinetId, FileSys::ContentRecordType::Program);
+ if (!cabinet_nca) {
+ QMessageBox::warning(this, tr("Cabinet Applet"),
+ tr("Cabinet applet is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Cabinet);
+ system->GetAppletManager().SetCabinetMode(mode);
+
+ const auto filename = QString::fromStdString(cabinet_nca->GetFullPath());
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
+}
+
+void GMainWindow::OnMiiEdit() {
+ constexpr u64 MiiEditId = 0x0100000000001009ull;
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Mii editor."));
+ return;
+ }
+
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ if (!mii_applet_nca) {
+ QMessageBox::warning(this, tr("Mii Edit Applet"),
+ tr("Mii editor is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::MiiEdit);
+
+ const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
+}
+
void GMainWindow::OnCaptureScreenshot() {
if (emu_thread == nullptr || !emu_thread->IsRunning()) {
return;
@@ -4080,10 +4535,10 @@ void GMainWindow::UpdateGPUAccuracyButton() {
}
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() {
@@ -4290,6 +4745,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
if (behavior == ReinitializeKeyBehavior::Warning) {
game_list->PopulateAsync(UISettings::values.game_dirs);
}
+
+ UpdateMenuState();
}
bool GMainWindow::CheckSystemArchiveDecryption() {
@@ -4311,28 +4768,63 @@ 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::CheckFirmwarePresence() {
+ constexpr u64 MiiEditId = 0x0100000000001009ull;
+
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ return false;
+ }
+
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ if (!mii_applet_nca) {
+ return false;
+ }
+
+ return true;
+}
+
+bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
+ u64* selected_title_id, u8* selected_content_record_type) {
+ using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
+ boost::container::flat_set<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.insert({entry.title_id, title_type, record_type});
+ }
+ }
+ };
+
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::HtmlDocument);
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::LegalInformation);
+ 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, title_type, record_type] : available_title_ids) {
+ const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
+ if (record_type == FileSys::ContentRecordType::Program) {
+ list.push_back(QStringLiteral("Program [%1]").arg(hex_title_id));
+ } else if (record_type == FileSys::ContentRecordType::HtmlDocument) {
+ list.push_back(QStringLiteral("HTML document [%1]").arg(hex_title_id));
+ } else if (record_type == FileSys::ContentRecordType::LegalInformation) {
+ list.push_back(QStringLiteral("Legal information [%1]").arg(hex_title_id));
+ } else {
+ list.push_back(
+ QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
+ }
}
bool ok;
@@ -4340,13 +4832,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& [title_id, title_type, record_type] = *available_title_ids.nth(title_index);
+ *selected_title_id = title_id;
+ *selected_content_record_type = static_cast<u8>(record_type);
+ return true;
}
bool GMainWindow::ConfirmClose() {
@@ -4354,8 +4849,7 @@ bool GMainWindow::ConfirmClose() {
return true;
}
const auto text = tr("Are you sure you want to close yuzu?");
- const auto answer = QMessageBox::question(this, tr("yuzu"), text);
- return answer != QMessageBox::No;
+ return question(this, tr("yuzu"), text);
}
void GMainWindow::closeEvent(QCloseEvent* event) {
@@ -4448,11 +4942,11 @@ bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
- const auto answer = QMessageBox::question(
+ // Use custom question to link controller navigation
+ return question(
this, tr("yuzu"),
tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- return answer != QMessageBox::No;
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
}
bool GMainWindow::ConfirmForceLockedExit() {
@@ -4462,8 +4956,7 @@ bool GMainWindow::ConfirmForceLockedExit() {
const auto text = tr("The currently running application has requested yuzu to not exit.\n\n"
"Would you like to bypass this and exit anyway?");
- const auto answer = QMessageBox::question(this, tr("yuzu"), text);
- return answer != QMessageBox::No;
+ return question(this, tr("yuzu"), text);
}
void GMainWindow::RequestGameExit() {
@@ -4476,6 +4969,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..270a40c5f 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -7,6 +7,7 @@
#include <optional>
#include <QMainWindow>
+#include <QMessageBox>
#include <QTimer>
#include <QTranslator>
@@ -15,6 +16,7 @@
#include "input_common/drivers/tas_input.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
+#include "yuzu/util/controller_navigation.h"
#ifdef __unix__
#include <QVariant>
@@ -58,6 +60,11 @@ enum class StartGameType {
Global, // Only uses global configuration
};
+enum class AmLaunchType {
+ UserInitiated,
+ ApplicationInitiated,
+};
+
namespace Core {
enum class SystemResultStatus : u32;
class System;
@@ -76,6 +83,10 @@ namespace DiscordRPC {
class DiscordInterface;
}
+namespace PlayTime {
+class PlayTimeManager;
+}
+
namespace FileSys {
class ContentProvider;
class ManualContentProvider;
@@ -97,6 +108,10 @@ namespace Service::NFC {
class NfcDevice;
} // namespace Service::NFC
+namespace Service::NFP {
+enum class CabinetMode : u8;
+} // namespace Service::NFP
+
namespace Ui {
class MainWindow;
}
@@ -239,9 +254,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();
@@ -312,7 +329,9 @@ private slots:
void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
+ void OnGameListRemovePlayTimeData(u64 program_id);
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 +361,7 @@ private slots:
void OnConfigurePerGame();
void OnLoadAmiibo();
void OnOpenYuzuFolder();
+ void OnVerifyInstalledContents();
void OnAbout();
void OnToggleFilterBar();
void OnToggleStatusBar();
@@ -356,6 +376,9 @@ private slots:
void ResetWindowSize720();
void ResetWindowSize900();
void ResetWindowSize1080();
+ void OnAlbum();
+ void OnCabinet(Service::NFP::CabinetMode mode);
+ void OnMiiEdit();
void OnCaptureScreenshot();
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
void OnLanguageChanged(const QString& locale);
@@ -374,9 +397,11 @@ private:
void RemoveVulkanDriverPipelineCache(u64 program_id);
void RemoveAllTransferableShaderCaches(u64 program_id);
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
+ void RemovePlayTimeData(u64 program_id);
void RemoveCacheStorage(u64 program_id);
- std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
- InstallResult InstallNSPXCI(const QString& filename);
+ bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
+ u64* selected_title_id, u8* selected_content_record_type);
+ InstallResult InstallNSP(const QString& filename);
InstallResult InstallNCA(const QString& filename);
void MigrateConfigFiles();
void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
@@ -399,6 +424,13 @@ private:
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
bool CheckDarkMode();
bool CheckSystemArchiveDecryption();
+ bool CheckFirmwarePresence();
+ void ConfigureFilesystemProvider(const std::string& filepath);
+ /**
+ * Open (or not) the right confirm dialog based on current setting and game exit lock
+ * @returns true if the player confirmed or the settings do no require it
+ */
+ bool ConfirmShutdownGame();
QString GetTasStateDescription() const;
bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
@@ -406,10 +438,22 @@ private:
const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords);
+ /**
+ * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
+ * The only difference is that it returns a boolean.
+ *
+ * @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button.
+ */
+ bool question(QWidget* parent, const QString& title, const QString& text,
+ QMessageBox::StandardButtons buttons =
+ QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
+ QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
+
std::unique_ptr<Ui::MainWindow> ui;
std::unique_ptr<Core::System> system;
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
+ std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
MultiplayerState* multiplayer_state = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 013ba0ceb..88684ffb5 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -137,6 +137,15 @@
<property name="title">
<string>&amp;Tools</string>
</property>
+ <widget class="QMenu" name="menu_cabinet_applet">
+ <property name="title">
+ <string>&amp;Amiibo</string>
+ </property>
+ <addaction name="action_Load_Cabinet_Nickname_Owner"/>
+ <addaction name="action_Load_Cabinet_Eraser"/>
+ <addaction name="action_Load_Cabinet_Restorer"/>
+ <addaction name="action_Load_Cabinet_Formatter"/>
+ </widget>
<widget class="QMenu" name="menuTAS">
<property name="title">
<string>&amp;TAS</string>
@@ -148,6 +157,11 @@
<addaction name="action_Configure_Tas"/>
</widget>
<addaction name="action_Rederive"/>
+ <addaction name="action_Verify_installed_contents"/>
+ <addaction name="separator"/>
+ <addaction name="menu_cabinet_applet"/>
+ <addaction name="action_Load_Album"/>
+ <addaction name="action_Load_Mii_Edit"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
<addaction name="menuTAS"/>
@@ -214,6 +228,11 @@
<string>&amp;Reinitialize keys...</string>
</property>
</action>
+ <action name="action_Verify_installed_contents">
+ <property name="text">
+ <string>&amp;Verify Installed Contents</string>
+ </property>
+ </action>
<action name="action_About">
<property name="text">
<string>&amp;About yuzu</string>
@@ -362,6 +381,36 @@
<string>&amp;Capture Screenshot</string>
</property>
</action>
+ <action name="action_Load_Album">
+ <property name="text">
+ <string>Open &amp;Album</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Nickname_Owner">
+ <property name="text">
+ <string>&amp;Set Nickname and Owner</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Eraser">
+ <property name="text">
+ <string>&amp;Delete Game Data</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Restorer">
+ <property name="text">
+ <string>&amp;Restore Amiibo</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Formatter">
+ <property name="text">
+ <string>&amp;Format Amiibo</string>
+ </property>
+ </action>
+ <action name="action_Load_Mii_Edit">
+ <property name="text">
+ <string>Open &amp;Mii Editor</string>
+ </property>
+ </action>
<action name="action_Configure_Tas">
<property name="text">
<string>&amp;Configure TAS...</string>
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp
new file mode 100644
index 000000000..155c36b7d
--- /dev/null
+++ b/src/yuzu/play_time_manager.cpp
@@ -0,0 +1,179 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/thread.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "yuzu/play_time_manager.h"
+
+namespace PlayTime {
+
+namespace {
+
+struct PlayTimeElement {
+ ProgramId program_id;
+ PlayTime play_time;
+};
+
+std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
+ const Service::Account::ProfileManager manager;
+ const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
+ if (!uuid.has_value()) {
+ return std::nullopt;
+ }
+ return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
+ uuid->RawString().append(".bin");
+}
+
+[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
+ const auto filename = GetCurrentUserPlayTimePath();
+
+ if (!filename.has_value()) {
+ LOG_ERROR(Frontend, "Failed to get current user path");
+ return false;
+ }
+
+ out_play_time_db.clear();
+
+ if (Common::FS::Exists(filename.value())) {
+ Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+ if (!file.IsOpen()) {
+ LOG_ERROR(Frontend, "Failed to open play time file: {}",
+ Common::FS::PathToUTF8String(filename.value()));
+ return false;
+ }
+
+ const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement);
+ std::vector<PlayTimeElement> elements(num_elements);
+
+ if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) {
+ return false;
+ }
+
+ for (const auto& [program_id, play_time] : elements) {
+ if (program_id != 0) {
+ out_play_time_db[program_id] = play_time;
+ }
+ }
+ }
+
+ return true;
+}
+
+[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
+ const auto filename = GetCurrentUserPlayTimePath();
+
+ if (!filename.has_value()) {
+ LOG_ERROR(Frontend, "Failed to get current user path");
+ return false;
+ }
+
+ Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
+ Common::FS::FileType::BinaryFile};
+ if (!file.IsOpen()) {
+ LOG_ERROR(Frontend, "Failed to open play time file: {}",
+ Common::FS::PathToUTF8String(filename.value()));
+ return false;
+ }
+
+ std::vector<PlayTimeElement> elements;
+ elements.reserve(play_time_db.size());
+
+ for (auto& [program_id, play_time] : play_time_db) {
+ if (program_id != 0) {
+ elements.push_back(PlayTimeElement{program_id, play_time});
+ }
+ }
+
+ return file.WriteSpan<PlayTimeElement>(elements) == elements.size();
+}
+
+} // namespace
+
+PlayTimeManager::PlayTimeManager() {
+ if (!ReadPlayTimeFile(database)) {
+ LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
+ }
+}
+
+PlayTimeManager::~PlayTimeManager() {
+ Save();
+}
+
+void PlayTimeManager::SetProgramId(u64 program_id) {
+ running_program_id = program_id;
+}
+
+void PlayTimeManager::Start() {
+ play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); });
+}
+
+void PlayTimeManager::Stop() {
+ play_time_thread = {};
+}
+
+void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("PlayTimeReport");
+
+ using namespace std::literals::chrono_literals;
+ using std::chrono::seconds;
+ using std::chrono::steady_clock;
+
+ auto timestamp = steady_clock::now();
+
+ const auto GetDuration = [&]() -> u64 {
+ const auto last_timestamp = std::exchange(timestamp, steady_clock::now());
+ const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp);
+ return static_cast<u64>(duration.count());
+ };
+
+ while (!stop_token.stop_requested()) {
+ Common::StoppableTimedWait(stop_token, 30s);
+
+ database[running_program_id] += GetDuration();
+ Save();
+ }
+}
+
+void PlayTimeManager::Save() {
+ if (!WritePlayTimeFile(database)) {
+ LOG_ERROR(Frontend, "Failed to update play time database!");
+ }
+}
+
+u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
+ auto it = database.find(program_id);
+ if (it != database.end()) {
+ return it->second;
+ } else {
+ return 0;
+ }
+}
+
+void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
+ database.erase(program_id);
+ Save();
+}
+
+QString ReadablePlayTime(qulonglong time_seconds) {
+ if (time_seconds == 0) {
+ return {};
+ }
+ const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
+ const auto time_hours = static_cast<double>(time_seconds) / 3600;
+ const bool is_minutes = time_minutes < 60;
+ const char* unit = is_minutes ? "m" : "h";
+ const auto value = is_minutes ? time_minutes : time_hours;
+
+ return QStringLiteral("%L1 %2")
+ .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
+ .arg(QString::fromUtf8(unit));
+}
+
+} // namespace PlayTime
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h
new file mode 100644
index 000000000..5f96f3447
--- /dev/null
+++ b/src/yuzu/play_time_manager.h
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QString>
+
+#include <map>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/polyfill_thread.h"
+
+namespace PlayTime {
+
+using ProgramId = u64;
+using PlayTime = u64;
+using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
+
+class PlayTimeManager {
+public:
+ explicit PlayTimeManager();
+ ~PlayTimeManager();
+
+ YUZU_NON_COPYABLE(PlayTimeManager);
+ YUZU_NON_MOVEABLE(PlayTimeManager);
+
+ u64 GetPlayTime(u64 program_id) const;
+ void ResetProgramPlayTime(u64 program_id);
+ void SetProgramId(u64 program_id);
+ void Start();
+ void Stop();
+
+private:
+ PlayTimeDatabase database;
+ u64 running_program_id;
+ std::jthread play_time_thread;
+ void AutoTimestamp(std::stop_token stop_token);
+ void Save();
+};
+
+QString ReadablePlayTime(qulonglong time_seconds);
+
+} // namespace PlayTime
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 8efd63f31..b62ff620c 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -16,7 +16,9 @@
#include "common/settings_enums.h"
using Settings::Category;
+using Settings::ConfirmStop;
using Settings::Setting;
+using Settings::SwitchableSetting;
#ifndef CANNOT_EXPLICITLY_INSTANTIATE
namespace Settings {
@@ -94,6 +96,15 @@ struct Values {
Setting<bool> confirm_before_closing{
linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
true, true};
+
+ SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
+ ConfirmStop::Ask_Always,
+ "confirmStop",
+ Category::UiGeneral,
+ Settings::Specialization::Default,
+ true,
+ true};
+
Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
Setting<bool> pause_when_in_background{linkage,
false,
@@ -103,7 +114,7 @@ struct Values {
true,
true};
Setting<bool> mute_when_in_background{
- linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default,
+ linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
true, true};
Setting<bool> hide_mouse{
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
@@ -183,6 +194,9 @@ struct Values {
Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
+ // Play time
+ Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
+
bool configuration_applied;
bool reset_to_defaults;
bool shortcut_already_warned{false};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..f2854c8ec 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -5,6 +5,10 @@
#include <cmath>
#include <QPainter>
#include "yuzu/util/util.h"
+#ifdef _WIN32
+#include <windows.h>
+#include "common/fs/file.h"
+#endif
QFont GetMonospaceFont() {
QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,101 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
return circle_pixmap;
}
+
+bool SaveIconToFile(const std::string_view path, const QImage& image) {
+#if defined(WIN32)
+#pragma pack(push, 2)
+ struct IconDir {
+ WORD id_reserved;
+ WORD id_type;
+ WORD id_count;
+ };
+
+ struct IconDirEntry {
+ BYTE width;
+ BYTE height;
+ BYTE color_count;
+ BYTE reserved;
+ WORD planes;
+ WORD bit_count;
+ DWORD bytes_in_res;
+ DWORD image_offset;
+ };
+#pragma pack(pop)
+
+ const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
+ constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
+ constexpr int bytes_per_pixel = 4;
+
+ const IconDir icon_dir{
+ .id_reserved = 0,
+ .id_type = 1,
+ .id_count = static_cast<WORD>(scale_sizes.size()),
+ };
+
+ Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
+ Common::FS::FileType::BinaryFile);
+ if (!icon_file.IsOpen()) {
+ return false;
+ }
+
+ if (!icon_file.Write(icon_dir)) {
+ return false;
+ }
+
+ std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
+ for (std::size_t i = 0; i < scale_sizes.size(); i++) {
+ const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
+ const IconDirEntry icon_entry{
+ .width = static_cast<BYTE>(scale_sizes[i]),
+ .height = static_cast<BYTE>(scale_sizes[i]),
+ .color_count = 0,
+ .reserved = 0,
+ .planes = 1,
+ .bit_count = bytes_per_pixel * 8,
+ .bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
+ .image_offset = static_cast<DWORD>(image_offset),
+ };
+ image_offset += icon_entry.bytes_in_res;
+ if (!icon_file.Write(icon_entry)) {
+ return false;
+ }
+ }
+
+ for (std::size_t i = 0; i < scale_sizes.size(); i++) {
+ const QImage scaled_image = source_image.scaled(
+ scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ const BITMAPINFOHEADER info_header{
+ .biSize = sizeof(BITMAPINFOHEADER),
+ .biWidth = scaled_image.width(),
+ .biHeight = scaled_image.height() * 2,
+ .biPlanes = 1,
+ .biBitCount = bytes_per_pixel * 8,
+ .biCompression = BI_RGB,
+ .biSizeImage{},
+ .biXPelsPerMeter{},
+ .biYPelsPerMeter{},
+ .biClrUsed{},
+ .biClrImportant{},
+ };
+
+ if (!icon_file.Write(info_header)) {
+ return false;
+ }
+
+ for (int y = 0; y < scaled_image.height(); y++) {
+ const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
+ std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
+ std::memcpy(line_data.data(), line, line_data.size());
+ if (!icon_file.Write(line_data)) {
+ return false;
+ }
+ }
+ }
+ icon_file.Close();
+
+ return true;
+#else
+ return false;
+#endif
+}
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 39dd2d895..09c14ce3f 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -7,14 +7,22 @@
#include <QString>
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
-QFont GetMonospaceFont();
+[[nodiscard]] QFont GetMonospaceFont();
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
-QString ReadableByteSize(qulonglong size);
+[[nodiscard]] QString ReadableByteSize(qulonglong size);
/**
* Creates a circle pixmap from a specified color
* @param color The color the pixmap shall have
* @return QPixmap circle pixmap
*/
-QPixmap CreateCirclePixmapFromColor(const QColor& color);
+[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
+
+/**
+ * Saves a windows icon to a file
+ * @param path The icons path
+ * @param image The image to save
+ * @return bool If the operation succeeded
+ */
+[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c42d98709..0d25ff400 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -259,7 +259,7 @@ void Config::ReadValues() {
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);
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)};