summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt7
-rw-r--r--src/android/app/build.gradle.kts5
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt105
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt24
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt90
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt72
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt76
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt54
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt34
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt42
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt113
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt41
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt70
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt72
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt128
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt103
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt55
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt107
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt129
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt70
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt56
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt20
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt93
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt45
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt34
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt44
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt14
-rw-r--r--src/android/app/src/main/jni/android_config.cpp120
-rw-r--r--src/android/app/src/main/jni/android_config.h41
-rw-r--r--src/android/app/src/main/jni/android_settings.cpp (renamed from src/android/app/src/main/jni/uisettings.cpp)2
-rw-r--r--src/android/app/src/main/jni/android_settings.h (renamed from src/android/app/src/main/jni/uisettings.h)10
-rw-r--r--src/android/app/src/main/jni/config.cpp330
-rw-r--r--src/android/app/src/main/jni/config.h47
-rw-r--r--src/android/app/src/main/jni/default_ini.h511
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.cpp8
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.h4
-rw-r--r--src/android/app/src/main/jni/game_metadata.cpp112
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp16
-rw-r--r--src/android/app/src/main/jni/id_cache.h2
-rw-r--r--src/android/app/src/main/jni/native.cpp844
-rw-r--r--src/android/app/src/main/jni/native.h85
-rw-r--r--src/android/app/src/main/jni/native_config.cpp75
-rw-r--r--src/android/app/src/main/jni/native_log.cpp31
-rw-r--r--src/android/app/src/main/res/drawable/ic_album.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_applet.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_audio.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_code.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_edit.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_graphics.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_mii.xml18
-rw-r--r--src/android/app/src/main/res/drawable/ic_refresh.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_restore.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_system_settings.xml9
-rw-r--r--src/android/app/src/main/res/layout-w600dp/fragment_about.xml233
-rw-r--r--src/android/app/src/main/res/layout/card_applet_option.xml57
-rw-r--r--src/android/app/src/main/res/layout/card_folder.xml70
-rw-r--r--src/android/app/src/main/res/layout/card_game.xml45
-rw-r--r--src/android/app/src/main/res/layout/card_home_option.xml7
-rw-r--r--src/android/app/src/main/res/layout/dialog_add_folder.xml45
-rw-r--r--src/android/app/src/main/res/layout/dialog_folder_properties.xml30
-rw-r--r--src/android/app/src/main/res/layout/dialog_list.xml15
-rw-r--r--src/android/app/src/main/res/layout/dialog_list_item.xml30
-rw-r--r--src/android/app/src/main/res/layout/fragment_about.xml10
-rw-r--r--src/android/app/src/main/res/layout/fragment_applet_launcher.xml31
-rw-r--r--src/android/app/src/main/res/layout/fragment_emulation.xml11
-rw-r--r--src/android/app/src/main/res/layout/fragment_folders.xml48
-rw-r--r--src/android/app/src/main/res/layout/fragment_home_settings.xml9
-rw-r--r--src/android/app/src/main/res/layout/fragment_search.xml1
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting.xml72
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting_switch.xml8
-rw-r--r--src/android/app/src/main/res/layout/list_item_settings_header.xml3
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml22
-rw-r--r--src/android/app/src/main/res/resources.properties1
-rw-r--r--src/android/app/src/main/res/values-ar/strings.xml385
-rw-r--r--src/android/app/src/main/res/values-ckb/strings.xml336
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml119
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml180
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml180
-rw-r--r--src/android/app/src/main/res/values-he/strings.xml367
-rw-r--r--src/android/app/src/main/res/values-hu/strings.xml402
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml192
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml218
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml273
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml113
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml81
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml210
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml192
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml159
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml90
-rw-r--r--src/android/app/src/main/res/values-vi/strings.xml340
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml138
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml137
-rw-r--r--src/android/app/src/main/res/values/arrays.xml20
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml39
-rw-r--r--src/android/app/src/main/res/xml/locales_config.xml17
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.cpp214
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.cpp4
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp222
-rw-r--r--src/audio_core/opus/decoder.cpp358
-rw-r--r--src/audio_core/opus/decoder.h106
-rw-r--r--src/audio_core/opus/decoder_manager.cpp204
-rw-r--r--src/audio_core/opus/decoder_manager.h76
-rw-r--r--src/audio_core/opus/hardware_opus.cpp482
-rw-r--r--src/audio_core/opus/hardware_opus.h90
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp2
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp2
-rw-r--r--src/audio_core/sink/sink_stream.cpp12
-rw-r--r--src/audio_core/sink/sink_stream.h6
-rw-r--r--src/common/CMakeLists.txt19
-rw-r--r--src/common/arm64/native_clock.cpp21
-rw-r--r--src/common/free_region_manager.h55
-rw-r--r--src/common/fs/fs_android.cpp33
-rw-r--r--src/common/fs/fs_android.h15
-rw-r--r--src/common/fs/fs_paths.h1
-rw-r--r--src/common/fs/path_util.cpp11
-rw-r--r--src/common/fs/path_util.h1
-rw-r--r--src/common/host_memory.cpp191
-rw-r--r--src/common/host_memory.h15
-rw-r--r--src/common/linux/gamemode.cpp40
-rw-r--r--src/common/linux/gamemode.h24
-rw-r--r--src/common/nvidia_flags.cpp1
-rw-r--r--src/common/page_table.cpp30
-rw-r--r--src/common/page_table.h17
-rw-r--r--src/common/settings.cpp25
-rw-r--r--src/common/settings.h61
-rw-r--r--src/common/settings_common.h2
-rw-r--r--src/common/settings_enums.h2
-rw-r--r--src/common/settings_input.cpp9
-rw-r--r--src/common/settings_input.h7
-rw-r--r--src/common/signal_chain.cpp42
-rw-r--r--src/common/signal_chain.h19
-rw-r--r--src/common/string_util.cpp12
-rw-r--r--src/common/wall_clock.cpp4
-rw-r--r--src/core/CMakeLists.txt42
-rw-r--r--src/core/arm/arm_interface.cpp24
-rw-r--r--src/core/arm/arm_interface.h3
-rw-r--r--src/core/arm/nce/arm_nce.cpp400
-rw-r--r--src/core/arm/nce/arm_nce.h108
-rw-r--r--src/core/arm/nce/arm_nce.s222
-rw-r--r--src/core/arm/nce/arm_nce_asm_definitions.h29
-rw-r--r--src/core/arm/nce/guest_context.h50
-rw-r--r--src/core/arm/nce/instructions.h147
-rw-r--r--src/core/arm/nce/patcher.cpp474
-rw-r--r--src/core/arm/nce/patcher.h98
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core_timing.cpp2
-rw-r--r--src/core/core_timing.h2
-rw-r--r--src/core/cpu_manager.cpp2
-rw-r--r--src/core/debugger/debugger.cpp24
-rw-r--r--src/core/debugger/gdbstub.cpp275
-rw-r--r--src/core/device_memory.cpp3
-rw-r--r--src/core/file_sys/program_metadata.cpp10
-rw-r--r--src/core/file_sys/program_metadata.h15
-rw-r--r--src/core/file_sys/romfs.cpp44
-rw-r--r--src/core/file_sys/romfs.h9
-rw-r--r--src/core/frontend/emu_window.h10
-rw-r--r--src/core/hid/emulated_console.h8
-rw-r--r--src/core/hid/emulated_controller.cpp144
-rw-r--r--src/core/hid/emulated_controller.h4
-rw-r--r--src/core/hid/hid_core.cpp5
-rw-r--r--src/core/hid/hid_types.h110
-rw-r--r--src/core/hid/input_interpreter.cpp11
-rw-r--r--src/core/hid/input_interpreter.h4
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp68
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.h18
-rw-r--r--src/core/hle/kernel/code_set.h14
-rw-r--r--src/core/hle/kernel/k_address_space_info.cpp4
-rw-r--r--src/core/hle/kernel/k_capabilities.cpp39
-rw-r--r--src/core/hle/kernel/k_capabilities.h21
-rw-r--r--src/core/hle/kernel/k_condition_variable.cpp22
-rw-r--r--src/core/hle/kernel/k_condition_variable.h9
-rw-r--r--src/core/hle/kernel/k_device_address_space.cpp4
-rw-r--r--src/core/hle/kernel/k_device_address_space.h10
-rw-r--r--src/core/hle/kernel/k_interrupt_manager.cpp2
-rw-r--r--src/core/hle/kernel/k_memory_layout.h8
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp121
-rw-r--r--src/core/hle/kernel/k_memory_manager.h12
-rw-r--r--src/core/hle/kernel/k_page_table.cpp3519
-rw-r--r--src/core/hle/kernel/k_page_table.h542
-rw-r--r--src/core/hle/kernel/k_page_table_base.cpp5739
-rw-r--r--src/core/hle/kernel/k_page_table_base.h760
-rw-r--r--src/core/hle/kernel/k_process.cpp1453
-rw-r--r--src/core/hle/kernel/k_process.h734
-rw-r--r--src/core/hle/kernel/k_process_page_table.h481
-rw-r--r--src/core/hle/kernel/k_scheduler.cpp4
-rw-r--r--src/core/hle/kernel/k_server_session.cpp2
-rw-r--r--src/core/hle/kernel/k_system_resource.cpp87
-rw-r--r--src/core/hle/kernel/k_thread.cpp28
-rw-r--r--src/core/hle/kernel/k_thread.h17
-rw-r--r--src/core/hle/kernel/k_thread_local_page.cpp4
-rw-r--r--src/core/hle/kernel/kernel.cpp53
-rw-r--r--src/core/hle/kernel/kernel.h3
-rw-r--r--src/core/hle/kernel/physical_core.cpp14
-rw-r--r--src/core/hle/kernel/process_capability.cpp389
-rw-r--r--src/core/hle/kernel/process_capability.h266
-rw-r--r--src/core/hle/kernel/svc.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_info.cpp28
-rw-r--r--src/core/hle/kernel/svc/svc_lock.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_memory.cpp6
-rw-r--r--src/core/hle/kernel/svc/svc_physical_memory.cpp13
-rw-r--r--src/core/hle/kernel/svc/svc_process_memory.cpp3
-rw-r--r--src/core/hle/kernel/svc/svc_query_memory.cpp8
-rw-r--r--src/core/hle/kernel/svc/svc_synchronization.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_thread.cpp7
-rw-r--r--src/core/hle/kernel/svc_generator.py2
-rw-r--r--src/core/hle/kernel/svc_types.h46
-rw-r--r--src/core/hle/result.h31
-rw-r--r--src/core/hle/service/acc/acc.cpp56
-rw-r--r--src/core/hle/service/am/am.cpp191
-rw-r--r--src/core/hle/service/am/am.h6
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.cpp9
-rw-r--r--src/core/hle/service/am/applets/applet_controller.h2
-rw-r--r--src/core/hle/service/am/applets/applet_web_browser.cpp3
-rw-r--r--src/core/hle/service/am/applets/applets.h24
-rw-r--r--src/core/hle/service/btm/btm.cpp56
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp16
-rw-r--r--src/core/hle/service/caps/caps_manager.h9
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp10
-rw-r--r--src/core/hle/service/caps/caps_su.cpp42
-rw-r--r--src/core/hle/service/caps/caps_su.h9
-rw-r--r--src/core/hle/service/friend/friend.cpp13
-rw-r--r--src/core/hle/service/hid/controllers/console_six_axis.cpp42
-rw-r--r--src/core/hle/service/hid/controllers/console_six_axis.h43
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.cpp9
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.h4
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp10
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h6
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp54
-rw-r--r--src/core/hle/service/hid/controllers/gesture.h6
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp10
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.h6
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp11
-rw-r--r--src/core/hle/service/hid/controllers/mouse.h6
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp588
-rw-r--r--src/core/hle/service/hid/controllers/npad.h205
-rw-r--r--src/core/hle/service/hid/controllers/palma.cpp90
-rw-r--r--src/core/hle/service/hid/controllers/palma.h8
-rw-r--r--src/core/hle/service/hid/controllers/seven_six_axis.cpp (renamed from src/core/hle/service/hid/controllers/console_sixaxis.cpp)38
-rw-r--r--src/core/hle/service/hid/controllers/seven_six_axis.h (renamed from src/core/hle/service/hid/controllers/console_sixaxis.h)31
-rw-r--r--src/core/hle/service/hid/controllers/six_axis.cpp413
-rw-r--r--src/core/hle/service/hid/controllers/six_axis.h111
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp23
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h10
-rw-r--r--src/core/hle/service/hid/controllers/xpad.cpp11
-rw-r--r--src/core/hle/service/hid/controllers/xpad.h6
-rw-r--r--src/core/hle/service/hid/hid.cpp2857
-rw-r--r--src/core/hle/service/hid/hid.h212
-rw-r--r--src/core/hle/service/hid/hid_debug_server.cpp159
-rw-r--r--src/core/hle/service/hid/hid_debug_server.h26
-rw-r--r--src/core/hle/service/hid/hid_firmware_settings.cpp99
-rw-r--r--src/core/hle/service/hid/hid_firmware_settings.h54
-rw-r--r--src/core/hle/service/hid/hid_server.cpp2387
-rw-r--r--src/core/hle/service/hid/hid_server.h150
-rw-r--r--src/core/hle/service/hid/hid_system_server.cpp539
-rw-r--r--src/core/hle/service/hid/hid_system_server.h63
-rw-r--r--src/core/hle/service/hid/hid_util.h146
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.cpp5
-rw-r--r--src/core/hle/service/hid/irs.cpp7
-rw-r--r--src/core/hle/service/hid/irs.h5
-rw-r--r--src/core/hle/service/hid/irsensor/clustering_processor.cpp16
-rw-r--r--src/core/hle/service/hid/irsensor/clustering_processor.h9
-rw-r--r--src/core/hle/service/hid/irsensor/image_transfer_processor.cpp2
-rw-r--r--src/core/hle/service/hid/irsensor/moment_processor.cpp123
-rw-r--r--src/core/hle/service/hid/irsensor/moment_processor.h34
-rw-r--r--src/core/hle/service/hid/resource_manager.cpp241
-rw-r--r--src/core/hle/service/hid/resource_manager.h111
-rw-r--r--src/core/hle/service/hid/ring_lifo.h6
-rw-r--r--src/core/hle/service/kernel_helpers.cpp6
-rw-r--r--src/core/hle/service/ldn/ldn.cpp10
-rw-r--r--src/core/hle/service/ldr/ldr.cpp45
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp2
-rw-r--r--src/core/hle/service/nfc/common/device.cpp68
-rw-r--r--src/core/hle/service/nfc/common/device.h1
-rw-r--r--src/core/hle/service/nfc/common/device_manager.cpp3
-rw-r--r--src/core/hle/service/nvdrv/devices/ioctl_serialization.h159
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp82
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h20
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp42
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h12
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp115
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h29
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp117
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h35
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp15
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp87
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h14
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp7
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.cpp13
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp47
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h12
-rw-r--r--src/core/hle/service/nvnflinger/buffer_item.h2
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp27
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_consumer.h9
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_core.cpp18
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_core.h3
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp24
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.h3
-rw-r--r--src/core/hle/service/nvnflinger/buffer_slot.h2
-rw-r--r--src/core/hle/service/nvnflinger/buffer_transform_flags.h2
-rw-r--r--src/core/hle/service/nvnflinger/consumer_base.cpp20
-rw-r--r--src/core/hle/service/nvnflinger/consumer_base.h2
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp29
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp33
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h5
-rw-r--r--src/core/hle/service/nvnflinger/status.h2
-rw-r--r--src/core/hle/service/nvnflinger/ui/graphic_buffer.cpp34
-rw-r--r--src/core/hle/service/nvnflinger/ui/graphic_buffer.h25
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp6
-rw-r--r--src/core/hle/service/pm/pm.cpp2
-rw-r--r--src/core/hle/service/set/set_sys.cpp100
-rw-r--r--src/core/hle/service/set/set_sys.h36
-rw-r--r--src/core/hle/service/sockets/bsd.cpp77
-rw-r--r--src/core/hle/service/sockets/bsd.h2
-rw-r--r--src/core/hle/service/time/clock_types.h5
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp2
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp63
-rw-r--r--src/core/loader/kip.cpp3
-rw-r--r--src/core/loader/nro.cpp63
-rw-r--r--src/core/loader/nro.h2
-rw-r--r--src/core/loader/nso.cpp67
-rw-r--r--src/core/loader/nso.h7
-rw-r--r--src/core/memory.cpp102
-rw-r--r--src/core/memory.h18
-rw-r--r--src/core/memory/cheat_engine.cpp12
-rw-r--r--src/core/reporter.cpp2
-rw-r--r--src/frontend_common/CMakeLists.txt10
-rw-r--r--src/frontend_common/config.cpp1010
-rw-r--r--src/frontend_common/config.h211
-rw-r--r--src/input_common/drivers/gc_adapter.cpp8
-rw-r--r--src/input_common/drivers/joycon.cpp8
-rw-r--r--src/input_common/drivers/sdl_driver.cpp20
-rw-r--r--src/input_common/drivers/sdl_driver.h2
-rw-r--r--src/input_common/drivers/udp_client.cpp8
-rw-r--r--src/input_common/helpers/joycon_driver.cpp16
-rw-r--r--src/input_common/helpers/joycon_driver.h5
-rw-r--r--src/shader_recompiler/CMakeLists.txt1
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_image.cpp6
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp9
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_image.cpp6
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp5
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv.cpp2
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp4
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp56
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_instructions.h2
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp4
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.cpp4
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.h2
-rw-r--r--src/shader_recompiler/frontend/ir/modifiers.h2
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp29
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.cpp3
-rw-r--r--src/shader_recompiler/host_translate_info.h1
-rw-r--r--src/shader_recompiler/ir_opt/constant_propagation_pass.cpp10
-rw-r--r--src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp13
-rw-r--r--src/shader_recompiler/ir_opt/passes.h3
-rw-r--r--src/shader_recompiler/ir_opt/vendor_workaround_pass.cpp79
-rw-r--r--src/shader_recompiler/profile.h3
-rw-r--r--src/tests/common/host_memory.cpp71
-rw-r--r--src/video_core/CMakeLists.txt5
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h135
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h4
-rw-r--r--src/video_core/buffer_cache/usage_tracker.h79
-rw-r--r--src/video_core/engines/fermi_2d.cpp4
-rw-r--r--src/video_core/engines/maxwell_3d.cpp2
-rw-r--r--src/video_core/fence_manager.h5
-rw-r--r--src/video_core/host1x/codecs/codec.cpp329
-rw-r--r--src/video_core/host1x/codecs/codec.h39
-rw-r--r--src/video_core/host1x/codecs/h264.cpp4
-rw-r--r--src/video_core/host1x/codecs/h264.h1
-rw-r--r--src/video_core/host1x/ffmpeg/ffmpeg.cpp419
-rw-r--r--src/video_core/host1x/ffmpeg/ffmpeg.h213
-rw-r--r--src/video_core/host1x/nvdec.cpp2
-rw-r--r--src/video_core/host1x/nvdec.h2
-rw-r--r--src/video_core/host1x/vic.cpp62
-rw-r--r--src/video_core/host1x/vic.h4
-rw-r--r--src/video_core/query_cache/query_cache.h2
-rw-r--r--src/video_core/renderer_null/null_rasterizer.cpp13
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.h1
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp135
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp40
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h23
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr.cpp24
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp29
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp28
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp13
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp29
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp28
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h21
-rw-r--r--src/video_core/renderer_vulkan/vk_smaa.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h1
-rw-r--r--src/video_core/texture_cache/slot_vector.h4
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp80
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h11
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h4
-rw-r--r--src/yuzu/CMakeLists.txt67
-rw-r--r--src/yuzu/applets/qt_controller.cpp53
-rw-r--r--src/yuzu/applets/qt_controller.h4
-rw-r--r--src/yuzu/bootmanager.cpp56
-rw-r--r--src/yuzu/bootmanager.h6
-rw-r--r--src/yuzu/breakpad.cpp77
-rw-r--r--src/yuzu/breakpad.h10
-rw-r--r--src/yuzu/configuration/config.cpp1306
-rw-r--r--src/yuzu/configuration/config.h179
-rw-r--r--src/yuzu/configuration/configure_audio.cpp10
-rw-r--r--src/yuzu/configuration/configure_camera.cpp2
-rw-r--r--src/yuzu/configuration/configure_camera.h2
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp12
-rw-r--r--src/yuzu/configuration/configure_cpu.h1
-rw-r--r--src/yuzu/configuration/configure_cpu.ui30
-rw-r--r--src/yuzu/configuration/configure_debug.cpp21
-rw-r--r--src/yuzu/configuration/configure_debug.ui93
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp1
-rw-r--r--src/yuzu/configuration/configure_general.cpp43
-rw-r--r--src/yuzu/configuration/configure_general.ui27
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp36
-rw-r--r--src/yuzu/configuration/configure_input.cpp61
-rw-r--r--src/yuzu/configuration/configure_input.h9
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp8
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h8
-rw-r--r--src/yuzu/configuration/configure_input_per_game.cpp6
-rw-r--r--src/yuzu/configuration/configure_input_per_game.h5
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp47
-rw-r--r--src/yuzu/configuration/configure_input_player.h7
-rw-r--r--src/yuzu/configuration/configure_input_player.ui389
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp45
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp7
-rw-r--r--src/yuzu/configuration/configure_per_game.h7
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp1
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp6
-rw-r--r--src/yuzu/configuration/configure_ringcon.cpp4
-rw-r--r--src/yuzu/configuration/configure_ringcon.h2
-rw-r--r--src/yuzu/configuration/configure_system.cpp1
-rw-r--r--src/yuzu/configuration/configure_system.ui2
-rw-r--r--src/yuzu/configuration/configure_tas.h2
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.cpp2
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.h2
-rw-r--r--src/yuzu/configuration/configure_ui.cpp7
-rw-r--r--src/yuzu/configuration/input_profiles.cpp10
-rw-r--r--src/yuzu/configuration/input_profiles.h4
-rw-r--r--src/yuzu/configuration/qt_config.cpp549
-rw-r--r--src/yuzu/configuration/qt_config.h55
-rw-r--r--src/yuzu/configuration/shared_translation.cpp523
-rw-r--r--src/yuzu/configuration/shared_translation.h43
-rw-r--r--src/yuzu/configuration/shared_widget.cpp4
-rw-r--r--src/yuzu/debugger/wait_tree.cpp8
-rw-r--r--src/yuzu/game_list.cpp39
-rw-r--r--src/yuzu/game_list.h10
-rw-r--r--src/yuzu/game_list_p.h8
-rw-r--r--src/yuzu/game_list_worker.cpp118
-rw-r--r--src/yuzu/game_list_worker.h35
-rw-r--r--src/yuzu/hotkeys.cpp28
-rw-r--r--src/yuzu/hotkeys.h16
-rw-r--r--src/yuzu/main.cpp784
-rw-r--r--src/yuzu/main.h60
-rw-r--r--src/yuzu/main.ui22
-rw-r--r--src/yuzu/mini_dump.cpp202
-rw-r--r--src/yuzu/mini_dump.h19
-rw-r--r--src/yuzu/uisettings.cpp65
-rw-r--r--src/yuzu/uisettings.h93
-rw-r--r--src/yuzu/util/util.cpp15
-rw-r--r--src/yuzu/util/util.h3
-rw-r--r--src/yuzu/vk_device_info.cpp1
-rw-r--r--src/yuzu_cmd/CMakeLists.txt9
-rw-r--r--src/yuzu_cmd/config.cpp279
-rw-r--r--src/yuzu_cmd/config.h38
-rw-r--r--src/yuzu_cmd/default_ini.h553
-rw-r--r--src/yuzu_cmd/sdl_config.cpp257
-rw-r--r--src/yuzu_cmd/sdl_config.h49
-rw-r--r--src/yuzu_cmd/yuzu.cpp17
511 files changed, 31237 insertions, 18760 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d7f68618c..e04d2418b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,7 +21,7 @@ if (MSVC)
# Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
add_definitions(-DWIN32_LEAN_AND_MEAN)
- # Ensure that projects build with Unicode support.
+ # Ensure that projects are built with Unicode support.
add_definitions(-DUNICODE -D_UNICODE)
# /W4 - Level 4 warnings
@@ -54,11 +54,11 @@ if (MSVC)
/GT
# Modules
- /experimental:module- # Disable module support explicitly due to conflicts with precompiled headers
+ /experimental:module- # Explicitly disable module support due to conflicts with precompiled headers.
# External headers diagnostics
/external:anglebrackets # Treats all headers included by #include <header>, where the header file is enclosed in angle brackets (< >), as external headers
- /external:W0 # Sets the default warning level to 0 for external headers, effectively turning off warnings for external headers
+ /external:W0 # Sets the default warning level to 0 for external headers, effectively disabling warnings for them.
# Warnings
/W4
@@ -187,6 +187,7 @@ add_subdirectory(audio_core)
add_subdirectory(video_core)
add_subdirectory(network)
add_subdirectory(input_common)
+add_subdirectory(frontend_common)
add_subdirectory(shader_recompiler)
if (YUZU_ROOM)
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index ac43d84b7..5721327e7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -47,6 +47,10 @@ android {
jniLibs.useLegacyPackaging = true
}
+ androidResources {
+ generateLocaleConfig = true
+ }
+
defaultConfig {
// TODO If this is ever modified, change application_id in strings.xml
applicationId = "org.yuzu.yuzu_emu"
@@ -215,7 +219,6 @@ dependencies {
implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1")
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.7.4")
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index a67351727..f10131b24 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -26,7 +26,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:supportsRtl="true"
android:isGame="true"
android:appCategory="game"
- android:localeConfig="@xml/locales_config"
android:banner="@drawable/tv_banner"
android:fullBackupContent="@xml/data_extraction_rules"
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
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 115f72710..e0f01127c 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
@@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu
import android.app.Dialog
import android.content.DialogInterface
+import android.net.Uri
import android.os.Bundle
import android.text.Html
import android.text.method.LinkMovementMethod
@@ -16,7 +17,7 @@ import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import org.yuzu.yuzu_emu.activities.EmulationActivity
-import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
+import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
@@ -68,7 +69,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun openContentUri(path: String?, openmode: String?): Int {
- return if (isNativePath(path!!)) {
+ return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
} else {
FileUtil.openContentUri(path, openmode)
@@ -78,7 +79,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun getSize(path: String?): Long {
- return if (isNativePath(path!!)) {
+ return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.getFileSize(path)
} else {
FileUtil.getFileSize(path)
@@ -88,23 +89,41 @@ object NativeLibrary {
@Keep
@JvmStatic
fun exists(path: String?): Boolean {
- return if (isNativePath(path!!)) {
+ return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.exists(path)
} else {
- FileUtil.exists(path)
+ FileUtil.exists(path, suppressLog = true)
}
}
@Keep
@JvmStatic
fun isDirectory(path: String?): Boolean {
- return if (isNativePath(path!!)) {
+ return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.isDirectory(path)
} else {
FileUtil.isDirectory(path)
}
}
+ @Keep
+ @JvmStatic
+ fun getParentDirectory(path: String): String =
+ if (DocumentsTree.isNativePath(path)) {
+ YuzuApplication.documentsTree!!.getParentDirectory(path)
+ } else {
+ path
+ }
+
+ @Keep
+ @JvmStatic
+ fun getFilename(path: String): String =
+ if (DocumentsTree.isNativePath(path)) {
+ YuzuApplication.documentsTree!!.getFilename(path)
+ } else {
+ FileUtil.getFilename(Uri.parse(path))
+ }
+
/**
* Returns true if pro controller isn't available and handheld is
*/
@@ -211,36 +230,8 @@ object NativeLibrary {
*/
external fun onTouchReleased(finger_id: Int)
- external fun reloadSettings()
-
external fun initGameIni(gameID: String?)
- /**
- * Gets the embedded icon within the given ROM.
- *
- * @param filename the file path to the ROM.
- * @return a byte array containing the JPEG data for the icon.
- */
- external fun getIcon(filename: String): ByteArray
-
- /**
- * Gets the embedded title of the given ISO/ROM.
- *
- * @param filename The file path to the ISO/ROM.
- * @return the embedded title of the ISO/ROM.
- */
- external fun getTitle(filename: String): String
-
- external fun getDescription(filename: String): String
-
- external fun getGameId(filename: String): String
-
- external fun getRegions(filename: String): String
-
- external fun getCompany(filename: String): String
-
- external fun isHomebrew(filename: String): Boolean
-
external fun setAppDirectory(directory: String)
/**
@@ -259,7 +250,7 @@ object NativeLibrary {
external fun reloadKeys(): Boolean
- external fun initializeEmulation()
+ external fun initializeSystem(reload: Boolean)
external fun defaultCPUCore(): Int
@@ -294,11 +285,6 @@ object NativeLibrary {
external fun stopEmulation()
/**
- * Resets the in-memory ROM metadata cache.
- */
- external fun resetRomMetadata()
-
- /**
* Returns true if emulation is running (or is paused).
*/
external fun isRunning(): Boolean
@@ -314,6 +300,11 @@ object NativeLibrary {
external fun getPerfStats(): DoubleArray
/**
+ * Returns the current CPU backend.
+ */
+ external fun getCpuBackend(): String
+
+ /**
* Notifies the core emulation that the orientation has changed.
*/
external fun notifyOrientationChange(layout_option: Int, rotation: Int)
@@ -474,12 +465,12 @@ object NativeLibrary {
}
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
- Log.verbose("[NativeLibrary] Registering EmulationActivity.")
+ Log.debug("[NativeLibrary] Registering EmulationActivity.")
sEmulationActivity = WeakReference(emulationActivity)
}
fun clearEmulationActivity() {
- Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
+ Log.debug("[NativeLibrary] Unregistering EmulationActivity.")
sEmulationActivity.clear()
}
@@ -518,6 +509,36 @@ object NativeLibrary {
external fun initializeEmptyUserDirectory()
/**
+ * Gets the launch path for a given applet. It is the caller's responsibility to also
+ * set the system's current applet ID before trying to launch the nca given by this function.
+ *
+ * @param id The applet entry ID
+ * @return The applet's launch path
+ */
+ external fun getAppletLaunchPath(id: Long): String
+
+ /**
+ * Sets the system's current applet ID before launching.
+ *
+ * @param appletId One of the ids in the Service::AM::Applets::AppletId enum
+ */
+ external fun setCurrentAppletId(appletId: Int)
+
+ /**
+ * Sets the cabinet mode for launching the cabinet applet.
+ *
+ * @param cabinetMode One of the modes that corresponds to the enum in Service::NFP::CabinetMode
+ */
+ external fun setCabinetMode(cabinetMode: Int)
+
+ /**
+ * Checks whether NAND contents are available and valid.
+ *
+ * @return 'true' if firmware is available
+ */
+ external fun isFirmwareAvailable(): Boolean
+
+ /**
* 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 8c053670c..d114bd53d 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
@@ -11,6 +11,7 @@ import java.io.File
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.Log
fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
@@ -49,6 +50,7 @@ class YuzuApplication : Application() {
DirectoryInitialization.start()
GpuDriverHelper.initializeDriverParameters()
NativeLibrary.logDeviceInfo()
+ Log.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 e96a2059b..f41d7bdbf 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
@@ -45,9 +45,9 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
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
import org.yuzu.yuzu_emu.utils.InputHandler
+import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.MemoryUtil
import org.yuzu.yuzu_emu.utils.NfcReader
import org.yuzu.yuzu_emu.utils.ThemeHelper
@@ -57,17 +57,16 @@ import kotlin.math.roundToInt
class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding
- private var controllerMappingHelper: ControllerMappingHelper? = null
-
var isActivityRecreated = false
private lateinit var nfcReader: NfcReader
- private lateinit var inputHandler: InputHandler
private val gyro = FloatArray(3)
private val accel = FloatArray(3)
private var motionTimestamp: Long = 0
private var flipMotionOrientation: Boolean = false
+ private var controllerIds = InputHandler.getGameControllerIds()
+
private val actionPause = "ACTION_EMULATOR_PAUSE"
private val actionPlay = "ACTION_EMULATOR_PLAY"
private val actionMute = "ACTION_EMULATOR_MUTE"
@@ -82,6 +81,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
override fun onCreate(savedInstanceState: Bundle?) {
+ Log.gameLaunched = true
ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState)
@@ -95,8 +95,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
isActivityRecreated = savedInstanceState != null
- controllerMappingHelper = ControllerMappingHelper()
-
// Set these options now so that the SurfaceView the game renders into is the right size.
enableFullscreenImmersive()
@@ -105,12 +103,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader = NfcReader(this)
nfcReader.initialize()
- inputHandler = InputHandler()
- inputHandler.initialize()
+ InputHandler.initialize()
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
- if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.Gb)) {
+ if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
Toast.makeText(
this,
getString(
@@ -162,6 +159,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onResume()
nfcReader.startScanning()
startMotionSensorListener()
+ InputHandler.updateControllerIds()
buildPictureInPictureParams()
}
@@ -195,7 +193,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return super.dispatchKeyEvent(event)
}
- return inputHandler.dispatchKeyEvent(event)
+ return InputHandler.dispatchKeyEvent(event)
}
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
@@ -210,7 +208,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return true
}
- return inputHandler.dispatchGenericMotionEvent(event)
+ return InputHandler.dispatchGenericMotionEvent(event)
}
override fun onSensorChanged(event: SensorEvent) {
@@ -375,8 +373,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val isEmulationActive = emulationViewModel.emulationStarted.value &&
+ !emulationViewModel.isEmulationStopping.value
pictureInPictureParamsBuilder.setAutoEnterEnabled(
- BooleanSetting.PICTURE_IN_PICTURE.boolean
+ BooleanSetting.PICTURE_IN_PICTURE.boolean && isEmulationActive
)
}
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
new file mode 100644
index 000000000..a21a705c1
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
@@ -0,0 +1,90 @@
+// 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 android.widget.Toast
+import androidx.core.content.res.ResourcesCompat
+import androidx.fragment.app.FragmentActivity
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.RecyclerView
+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.databinding.CardAppletOptionBinding
+import org.yuzu.yuzu_emu.model.Applet
+import org.yuzu.yuzu_emu.model.AppletInfo
+import org.yuzu.yuzu_emu.model.Game
+
+class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
+ RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(),
+ View.OnClickListener {
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): AppletAdapter.AppletViewHolder {
+ CardAppletOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .apply { root.setOnClickListener(this@AppletAdapter) }
+ .also { return AppletViewHolder(it) }
+ }
+
+ override fun onBindViewHolder(holder: AppletViewHolder, position: Int) =
+ holder.bind(applets[position])
+
+ override fun getItemCount(): Int = applets.size
+
+ override fun onClick(view: View) {
+ val applet = (view.tag as AppletViewHolder).applet
+ val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
+ if (appletPath.isEmpty()) {
+ Toast.makeText(
+ YuzuApplication.appContext,
+ R.string.applets_error_applet,
+ Toast.LENGTH_SHORT
+ ).show()
+ return
+ }
+
+ if (applet.appletInfo == AppletInfo.Cabinet) {
+ view.findNavController()
+ .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
+ return
+ }
+
+ NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
+ val appletGame = Game(
+ title = YuzuApplication.appContext.getString(applet.titleId),
+ path = appletPath
+ )
+ val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
+ view.findNavController().navigate(action)
+ }
+
+ inner class AppletViewHolder(val binding: CardAppletOptionBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ lateinit var applet: Applet
+
+ init {
+ itemView.tag = this
+ }
+
+ fun bind(applet: Applet) {
+ this.applet = applet
+
+ binding.title.setText(applet.titleId)
+ binding.description.setText(applet.descriptionId)
+ binding.icon.setImageDrawable(
+ ResourcesCompat.getDrawable(
+ binding.icon.context.resources,
+ applet.iconId,
+ binding.icon.context.theme
+ )
+ )
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
new file mode 100644
index 000000000..e7b7c0f2f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
@@ -0,0 +1,72 @@
+// 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.core.content.res.ResourcesCompat
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.RecyclerView
+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.databinding.DialogListItemBinding
+import org.yuzu.yuzu_emu.model.CabinetMode
+import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
+import org.yuzu.yuzu_emu.model.AppletInfo
+import org.yuzu.yuzu_emu.model.Game
+
+class CabinetLauncherDialogAdapter(val fragment: Fragment) :
+ RecyclerView.Adapter<CabinetModeViewHolder>(),
+ View.OnClickListener {
+ private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
+ DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
+ .also { return CabinetModeViewHolder(it) }
+ }
+
+ override fun getItemCount(): Int = cabinetModes.size
+
+ override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) =
+ holder.bind(cabinetModes[position])
+
+ override fun onClick(view: View) {
+ val mode = (view.tag as CabinetModeViewHolder).cabinetMode
+ val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
+ NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
+ NativeLibrary.setCabinetMode(mode.id)
+ val appletGame = Game(
+ title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
+ path = appletPath
+ )
+ val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
+ fragment.findNavController().navigate(action)
+ }
+
+ inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ lateinit var cabinetMode: CabinetMode
+
+ init {
+ itemView.tag = this
+ }
+
+ fun bind(cabinetMode: CabinetMode) {
+ this.cabinetMode = cabinetMode
+ binding.icon.setImageDrawable(
+ ResourcesCompat.getDrawable(
+ binding.icon.context.resources,
+ cabinetMode.iconId,
+ binding.icon.context.theme
+ )
+ )
+ binding.title.setText(cabinetMode.titleId)
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
new file mode 100644
index 000000000..ab657a7b9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.net.Uri
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentActivity
+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.databinding.CardFolderBinding
+import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
+import org.yuzu.yuzu_emu.model.GameDir
+import org.yuzu.yuzu_emu.model.GamesViewModel
+
+class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
+ ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
+ AsyncDifferConfig.Builder(DiffCallback()).build()
+ ) {
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): FolderAdapter.FolderViewHolder {
+ CardFolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return FolderViewHolder(it) }
+ }
+
+ override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
+ holder.bind(currentList[position])
+
+ inner class FolderViewHolder(val binding: CardFolderBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ private lateinit var gameDir: GameDir
+
+ fun bind(gameDir: GameDir) {
+ this.gameDir = gameDir
+
+ binding.apply {
+ path.text = Uri.parse(gameDir.uriString).path
+ path.postDelayed(
+ {
+ path.isSelected = true
+ path.ellipsize = TextUtils.TruncateAt.MARQUEE
+ },
+ 3000
+ )
+
+ buttonEdit.setOnClickListener {
+ GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
+ .show(
+ activity.supportFragmentManager,
+ GameFolderPropertiesDialogFragment.TAG
+ )
+ }
+
+ buttonDelete.setOnClickListener {
+ gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
+ }
+ }
+ }
+ }
+
+ private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
+ override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
+ return oldItem == newItem
+ }
+ }
+}
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 f9f88a1d2..2ef638559 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
@@ -22,12 +22,16 @@ 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 kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -92,28 +96,34 @@ class GameAdapter(private val activity: AppCompatActivity) :
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)
+ activity.lifecycleScope.launch {
+ withContext(Dispatchers.IO) {
+ val layerDrawable = ResourcesCompat.getDrawable(
+ YuzuApplication.appContext.resources,
+ R.drawable.shortcut,
+ null
+ ) as LayerDrawable
+ layerDrawable.setDrawableByLayerId(
+ R.id.shortcut_foreground,
+ GameIconUtils.getGameIcon(activity, holder.game)
+ .toDrawable(YuzuApplication.appContext.resources)
)
- )
- .setIntent(openIntent)
- .build()
- ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
+ 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)
@@ -147,7 +157,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
- return oldItem.gameId == newItem.gameId
+ return oldItem.programId == newItem.programId
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
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 151362124..ef10b209f 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
@@ -10,6 +10,7 @@ enum class IntSetting(
override val category: Settings.Category,
override val androidDefault: Int? = null
) : AbstractIntSetting {
+ CPU_BACKEND("cpu_backend", Settings.Category.Cpu),
CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
REGION_INDEX("region_index", Settings.Category.System),
LANGUAGE_INDEX("language_index", Settings.Category.System),
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 08e2a973d..e3cd66185 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
@@ -3,33 +3,9 @@
package org.yuzu.yuzu_emu.features.settings.model
-import android.text.TextUtils
-import android.widget.Toast
import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
object Settings {
- private val context get() = YuzuApplication.appContext
-
- fun saveSettings(gameId: String = "") {
- if (TextUtils.isEmpty(gameId)) {
- Toast.makeText(
- context,
- context.getString(R.string.ini_saved),
- Toast.LENGTH_SHORT
- ).show()
- SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
- } else {
- // TODO: Save custom game settings
- Toast.makeText(
- context,
- context.getString(R.string.gameid_saved, gameId),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
-
enum class Category {
Android,
Audio,
@@ -82,7 +58,6 @@ object Settings {
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),
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 522cc49df..425160024 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
@@ -3,10 +3,13 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.DrawableRes
+
class RunnableSetting(
titleId: Int,
descriptionId: Int,
val isRuntimeRunnable: Boolean,
+ @DrawableRes val iconId: Int = 0,
val runnable: () -> Unit
) : 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 b3b3fc209..e198b18a0 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
@@ -73,12 +73,21 @@ abstract class SettingsItem(
R.string.frame_limit_slider,
R.string.frame_limit_slider_description,
1,
- 200,
+ 400,
"%"
)
)
put(
SingleChoiceSetting(
+ IntSetting.CPU_BACKEND,
+ R.string.cpu_backend,
+ 0,
+ R.array.cpuBackendArm64Names,
+ R.array.cpuBackendArm64Values
+ )
+ )
+ put(
+ SingleChoiceSetting(
IntSetting.CPU_ACCURACY,
R.string.cpu_accuracy,
0,
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 b343e527e..94953b18a 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,11 +3,14 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.Settings
class SubmenuSetting(
- titleId: Int,
- descriptionId: Int,
+ @StringRes titleId: Int,
+ @StringRes descriptionId: Int,
+ @DrawableRes val iconId: Int,
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/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index c73edd50e..64bfc6dd0 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
@@ -19,13 +19,13 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
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.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
import org.yuzu.yuzu_emu.model.SettingsViewModel
@@ -54,10 +54,6 @@ class SettingsActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
- if (savedInstanceState != null) {
- settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
- }
-
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
@@ -128,12 +124,6 @@ class SettingsActivity : AppCompatActivity() {
}
}
- override fun onSaveInstanceState(outState: Bundle) {
- // Critical: If super method is not called, rotations will be busted.
- super.onSaveInstanceState(outState)
- outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
- }
-
override fun onStart() {
super.onStart()
// TODO: Load custom settings contextually
@@ -142,16 +132,10 @@ class SettingsActivity : AppCompatActivity() {
}
}
- /**
- * If this is called, the user has left the settings screen (potentially through the
- * home button) and will expect their changes to be persisted. So we kick off an
- * IntentService which will do so on a background thread.
- */
override fun onStop() {
super.onStop()
- if (isFinishing && settingsViewModel.shouldSave) {
- Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
- Settings.saveSettings()
+ CoroutineScope(Dispatchers.IO).launch {
+ NativeConfig.saveSettings()
}
}
@@ -161,15 +145,13 @@ class SettingsActivity : AppCompatActivity() {
}
fun onSettingsReset() {
- // Prevents saving to a non-existent settings file
- settingsViewModel.shouldSave = false
-
// Delete settings file because the user may have changed values that do not exist in the UI
+ NativeConfig.unloadConfig()
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
- NativeLibrary.reloadSettings()
+ NativeConfig.initializeConfig()
Toast.makeText(
applicationContext,
@@ -194,8 +176,4 @@ class SettingsActivity : AppCompatActivity() {
windowInsets
}
}
-
- 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/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index a7a029fc1..af2c1e582 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
@@ -105,7 +105,6 @@ class SettingsAdapter(
fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
item.checked = checked
settingsViewModel.setShouldReloadSettingsList(true)
- settingsViewModel.shouldSave = true
}
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
@@ -161,7 +160,6 @@ class SettingsAdapter(
epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60
if (item.value != epochTime) {
- settingsViewModel.shouldSave = true
notifyItemChanged(position)
item.value = epochTime
}
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 70d8ec14b..769baf744 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
@@ -20,7 +20,6 @@ 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
@@ -68,15 +67,9 @@ class SettingsFragment : Fragment() {
)
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
- val dividerDecoration = MaterialDividerItemDecoration(
- requireContext(),
- LinearLayoutManager.VERTICAL
- )
- dividerDecoration.isLastItemDecorated = false
binding.listSettings.apply {
adapter = settingsAdapter
layoutManager = LinearLayoutManager(requireContext())
- addItemDecoration(dividerDecoration)
}
binding.toolbarSettings.setNavigationOnClickListener {
@@ -94,17 +87,6 @@ class SettingsFragment : Fragment() {
}
}
}
- 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)
- }
- }
- }
}
if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
@@ -112,8 +94,6 @@ class SettingsFragment : Fragment() {
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
@@ -129,11 +109,6 @@ class SettingsFragment : Fragment() {
setInsets()
}
- override fun onResume() {
- super.onResume()
- settingsViewModel.setIsUsingSearch(false)
- }
-
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
@@ -144,10 +119,9 @@ class SettingsFragment : Fragment() {
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
+ mlpSettingsList.leftMargin = leftInsets
+ mlpSettingsList.rightMargin = rightInsets
binding.listSettings.layoutParams = mlpSettingsList
binding.listSettings.updatePadding(
bottom = barInsets.bottom
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 766414a6c..7425728c6 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,7 +3,6 @@
package org.yuzu.yuzu_emu.features.settings.ui
-import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.widget.Toast
@@ -32,8 +31,6 @@ class SettingsFragmentPresenter(
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- private val context: Context get() = YuzuApplication.appContext
-
// Extension for populating settings list based on paired settings
fun ArrayList<SettingsItem>.add(key: String) {
val item = SettingsItem.settingsItems[key]!!
@@ -53,7 +50,6 @@ class SettingsFragmentPresenter(
val sl = ArrayList<SettingsItem>()
when (menuTag) {
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)
@@ -75,30 +71,53 @@ class SettingsFragmentPresenter(
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
- 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)
- }
+ SubmenuSetting(
+ R.string.preferences_system,
+ R.string.preferences_system_description,
+ R.drawable.ic_system_settings,
+ Settings.MenuTag.SECTION_SYSTEM
+ )
+ )
+ add(
+ SubmenuSetting(
+ R.string.preferences_graphics,
+ R.string.preferences_graphics_description,
+ R.drawable.ic_graphics,
+ Settings.MenuTag.SECTION_RENDERER
+ )
+ )
+ add(
+ SubmenuSetting(
+ R.string.preferences_audio,
+ R.string.preferences_audio_description,
+ R.drawable.ic_audio,
+ Settings.MenuTag.SECTION_AUDIO
+ )
+ )
+ add(
+ SubmenuSetting(
+ R.string.preferences_debug,
+ R.string.preferences_debug_description,
+ R.drawable.ic_code,
+ Settings.MenuTag.SECTION_DEBUG
+ )
+ )
+ add(
+ RunnableSetting(
+ R.string.reset_to_default,
+ R.string.reset_to_default_description,
+ false,
+ R.drawable.ic_restore
+ ) { settingsViewModel.setShouldShowResetSettingsDialog(true) }
)
}
}
- private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
+ private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
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>) {
- sl.apply {
add(BooleanSetting.USE_DOCKED_MODE.key)
add(IntSetting.REGION_INDEX.key)
add(IntSetting.LANGUAGE_INDEX.key)
@@ -116,6 +135,7 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_ANTI_ALIASING.key)
add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
add(IntSetting.RENDERER_ASPECT_RATIO.key)
+ add(BooleanSetting.PICTURE_IN_PICTURE.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
@@ -249,6 +269,8 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_DEBUG.key)
add(HeaderSetting(R.string.cpu))
+ add(IntSetting.CPU_BACKEND.key)
+ add(IntSetting.CPU_ACCURACY.key)
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/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
index 83a2e94f1..036195624 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
+import androidx.core.content.res.ResourcesCompat
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting
@@ -16,6 +17,19 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
override fun bind(item: SettingsItem) {
setting = item as RunnableSetting
+ if (item.iconId != 0) {
+ binding.icon.visibility = View.VISIBLE
+ binding.icon.setImageDrawable(
+ ResourcesCompat.getDrawable(
+ binding.icon.resources,
+ item.iconId,
+ binding.icon.context.theme
+ )
+ )
+ } else {
+ binding.icon.visibility = View.GONE
+ }
+
binding.textSettingName.setText(item.nameId)
if (item.descriptionId != 0) {
binding.textSettingDescription.setText(item.descriptionId)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
index 1cf581a9d..8100c65dd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
+import androidx.core.content.res.ResourcesCompat
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting
@@ -15,6 +16,19 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
override fun bind(item: SettingsItem) {
this.item = item as SubmenuSetting
+ if (item.iconId != 0) {
+ binding.icon.visibility = View.VISIBLE
+ binding.icon.setImageDrawable(
+ ResourcesCompat.getDrawable(
+ binding.icon.resources,
+ item.iconId,
+ binding.icon.context.theme
+ )
+ )
+ } else {
+ binding.icon.visibility = View.GONE
+ }
+
binding.textSettingName.setText(item.nameId)
if (item.descriptionId != 0) {
binding.textSettingDescription.setText(item.descriptionId)
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 2b04d666a..3ae5b4653 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,15 +3,8 @@
package org.yuzu.yuzu_emu.features.settings.utils
-import android.widget.Toast
import java.io.*
-import org.ini4j.Wini
-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.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.
@@ -19,41 +12,6 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
object SettingsFile {
const val FILE_NAME_CONFIG = "config"
- /**
- * 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.
- */
- fun saveFile(fileName: String) {
- val ini = getSettingsFile(fileName)
- try {
- 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)
- }
- }
- }
- wini.store()
- } catch (e: IOException) {
- Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
- val context = YuzuApplication.appContext
- Toast.makeText(
- context,
- context.getString(R.string.error_saving, fileName, e.message),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
-
fun getSettingsFile(fileName: String): File =
File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
index 2ff827c6b..a1620fbb7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
@@ -114,10 +114,10 @@ class AboutFragment : Fragment() {
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
- val mlpAppBar = binding.appbarAbout.layoutParams as MarginLayoutParams
- mlpAppBar.leftMargin = leftInsets
- mlpAppBar.rightMargin = rightInsets
- binding.appbarAbout.layoutParams = mlpAppBar
+ val mlpToolbar = binding.toolbarAbout.layoutParams as MarginLayoutParams
+ mlpToolbar.leftMargin = leftInsets
+ mlpToolbar.rightMargin = rightInsets
+ binding.toolbarAbout.layoutParams = mlpToolbar
val mlpScrollAbout = binding.scrollAbout.layoutParams as MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
new file mode 100644
index 000000000..dec2b7cf1
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
@@ -0,0 +1,53 @@
+// 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.net.Uri
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogAddFolderBinding
+import org.yuzu.yuzu_emu.model.GameDir
+import org.yuzu.yuzu_emu.model.GamesViewModel
+
+class AddGameFolderDialogFragment : DialogFragment() {
+ private val gamesViewModel: GamesViewModel by activityViewModels()
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val binding = DialogAddFolderBinding.inflate(layoutInflater)
+ val folderUriString = requireArguments().getString(FOLDER_URI_STRING)
+ if (folderUriString == null) {
+ dismiss()
+ }
+ binding.path.text = Uri.parse(folderUriString).path
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.add_game_folder)
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+ val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked)
+ gamesViewModel.addFolder(newGameDir)
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .setView(binding.root)
+ .show()
+ }
+
+ companion object {
+ const val TAG = "AddGameFolderDialogFragment"
+
+ private const val FOLDER_URI_STRING = "FolderUriString"
+
+ fun newInstance(folderUriString: String): AddGameFolderDialogFragment {
+ val args = Bundle()
+ args.putString(FOLDER_URI_STRING, folderUriString)
+ val fragment = AddGameFolderDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt
new file mode 100644
index 000000000..1f66b440d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AppletLauncherFragment.kt
@@ -0,0 +1,113 @@
+// 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.AppletAdapter
+import org.yuzu.yuzu_emu.databinding.FragmentAppletLauncherBinding
+import org.yuzu.yuzu_emu.model.Applet
+import org.yuzu.yuzu_emu.model.AppletInfo
+import org.yuzu.yuzu_emu.model.HomeViewModel
+
+class AppletLauncherFragment : Fragment() {
+ private var _binding: FragmentAppletLauncherBinding? = 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 = FragmentAppletLauncherBinding.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)
+
+ binding.toolbarApplets.setNavigationOnClickListener {
+ binding.root.findNavController().popBackStack()
+ }
+
+ val applets = listOf(
+ Applet(
+ R.string.album_applet,
+ R.string.album_applet_description,
+ R.drawable.ic_album,
+ AppletInfo.PhotoViewer
+ ),
+ Applet(
+ R.string.cabinet_applet,
+ R.string.cabinet_applet_description,
+ R.drawable.ic_nfc,
+ AppletInfo.Cabinet
+ ),
+ Applet(
+ R.string.mii_edit_applet,
+ R.string.mii_edit_applet_description,
+ R.drawable.ic_mii,
+ AppletInfo.MiiEdit
+ )
+ )
+
+ binding.listApplets.apply {
+ layoutManager = GridLayoutManager(
+ requireContext(),
+ resources.getInteger(R.integer.grid_columns)
+ )
+ adapter = AppletAdapter(requireActivity(), applets)
+ }
+
+ 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.toolbarApplets.layoutParams as ViewGroup.MarginLayoutParams
+ mlpAppBar.leftMargin = leftInsets
+ mlpAppBar.rightMargin = rightInsets
+ binding.toolbarApplets.layoutParams = mlpAppBar
+
+ val mlpListApplets =
+ binding.listApplets.layoutParams as ViewGroup.MarginLayoutParams
+ mlpListApplets.leftMargin = leftInsets
+ mlpListApplets.rightMargin = rightInsets
+ binding.listApplets.layoutParams = mlpListApplets
+
+ binding.listApplets.updatePadding(bottom = barInsets.bottom)
+
+ windowInsets
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt
new file mode 100644
index 000000000..5933677fd
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CabinetLauncherDialogFragment.kt
@@ -0,0 +1,41 @@
+// 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.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter
+import org.yuzu.yuzu_emu.databinding.DialogListBinding
+
+class CabinetLauncherDialogFragment : DialogFragment() {
+ private lateinit var binding: DialogListBinding
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ binding = DialogListBinding.inflate(layoutInflater)
+ binding.dialogList.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ adapter = CabinetLauncherDialogAdapter(this@CabinetLauncherDialogFragment)
+ }
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.cabinet_launcher)
+ .setView(binding.root)
+ .create()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return binding.root
+ }
+}
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 598a9d42b..734c1d5ca 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
@@ -10,11 +10,11 @@ import android.content.DialogInterface
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.os.SystemClock
import android.view.*
import android.widget.TextView
import android.widget.Toast
@@ -25,6 +25,7 @@ import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.drawerlayout.widget.DrawerLayout
+import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
@@ -153,9 +154,34 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
binding.surfaceEmulation.holder.addCallback(this)
- binding.showFpsText.setTextColor(Color.YELLOW)
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
+ binding.drawerLayout.addDrawerListener(object : DrawerListener {
+ override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
+ binding.surfaceInputOverlay.dispatchTouchEvent(
+ MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis() + 100,
+ MotionEvent.ACTION_UP,
+ 0f,
+ 0f,
+ 0
+ )
+ )
+ }
+
+ override fun onDrawerOpened(drawerView: View) {
+ // No op
+ }
+
+ override fun onDrawerClosed(drawerView: View) {
+ // No op
+ }
+
+ override fun onDrawerStateChanged(newState: Int) {
+ // No op
+ }
+ })
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
game.title
@@ -284,6 +310,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
ViewUtils.showView(binding.surfaceInputOverlay)
ViewUtils.hideView(binding.loadingIndicator)
+ emulationState.updateSurface()
+
// Setup overlay
updateShowFpsOverlay()
}
@@ -384,12 +412,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val FRAMETIME = 2
val SPEED = 3
perfStatsUpdater = {
- if (emulationViewModel.emulationStarted.value == true) {
+ if (emulationViewModel.emulationStarted.value) {
val perfStats = NativeLibrary.getPerfStats()
- if (perfStats[FPS] > 0 && _binding != null) {
- binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
+ val cpuBackend = NativeLibrary.getCpuBackend()
+ if (_binding != null) {
+ binding.showFpsText.text =
+ String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend)
}
- perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
+ perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
}
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
@@ -434,7 +464,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
// Restrict emulation and overlays to the top of the screen
binding.emulationContainer.layoutParams.height = it.bounds.top
- binding.overlayContainer.layoutParams.height = it.bounds.top
// Restrict input and menu drawer to the bottom of the screen
binding.inputContainer.layoutParams.height = it.bounds.bottom
binding.inGameMenu.layoutParams.height = it.bounds.bottom
@@ -448,7 +477,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (!isFolding) {
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
- binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
isInFoldableLayout = false
updateOrientation()
@@ -456,7 +484,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
binding.emulationContainer.requestLayout()
binding.inputContainer.requestLayout()
- binding.overlayContainer.requestLayout()
binding.inGameMenu.requestLayout()
}
@@ -682,24 +709,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
v.setPadding(left, cutInsets.top, right, 0)
-
- // Ensure FPS text doesn't get cut off by rounded display corners
- val sidePadding = resources.getDimensionPixelSize(R.dimen.spacing_xtralarge)
- if (cutInsets.left == 0) {
- binding.showFpsText.setPadding(
- sidePadding,
- cutInsets.top,
- cutInsets.right,
- cutInsets.bottom
- )
- } else {
- binding.showFpsText.setPadding(
- cutInsets.left,
- cutInsets.top,
- cutInsets.right,
- cutInsets.bottom
- )
- }
windowInsets
}
}
@@ -777,6 +786,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
@Synchronized
+ fun updateSurface() {
+ if (surface != null) {
+ NativeLibrary.surfaceChanged(surface)
+ }
+ }
+
+ @Synchronized
fun clearSurface() {
if (surface == null) {
Log.warning("[EmulationFragment] clearSurface called, but surface already null.")
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
new file mode 100644
index 000000000..b6c2e4635
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
@@ -0,0 +1,72 @@
+// 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 androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
+import org.yuzu.yuzu_emu.model.GameDir
+import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
+
+class GameFolderPropertiesDialogFragment : DialogFragment() {
+ private val gamesViewModel: GamesViewModel by activityViewModels()
+
+ private var deepScan = false
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
+ val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
+
+ // Restore checkbox state
+ binding.deepScanSwitch.isChecked =
+ savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan
+
+ // Ensure that we can get the checkbox state even if the view is destroyed
+ deepScan = binding.deepScanSwitch.isChecked
+ binding.deepScanSwitch.setOnClickListener {
+ deepScan = binding.deepScanSwitch.isChecked
+ }
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setView(binding.root)
+ .setTitle(R.string.game_folder_properties)
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+ val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
+ if (folderIndex != -1) {
+ gamesViewModel.folders.value[folderIndex].deepScan =
+ binding.deepScanSwitch.isChecked
+ gamesViewModel.updateGameDirs()
+ }
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putBoolean(DEEP_SCAN, deepScan)
+ }
+
+ companion object {
+ const val TAG = "GameFolderPropertiesDialogFragment"
+
+ private const val GAME_DIR = "GameDir"
+
+ private const val DEEP_SCAN = "DeepScan"
+
+ fun newInstance(gameDir: GameDir): GameFolderPropertiesDialogFragment {
+ val args = Bundle()
+ args.putParcelable(GAME_DIR, gameDir)
+ val fragment = GameFolderPropertiesDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
new file mode 100644
index 000000000..341a37fdb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
@@ -0,0 +1,128 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.content.Intent
+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.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.GridLayoutManager
+import com.google.android.material.transition.MaterialSharedAxis
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.adapters.FolderAdapter
+import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
+import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.ui.main.MainActivity
+
+class GameFoldersFragment : Fragment() {
+ private var _binding: FragmentFoldersBinding? = null
+ private val binding get() = _binding!!
+
+ private val homeViewModel: HomeViewModel by activityViewModels()
+ private val gamesViewModel: GamesViewModel 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)
+
+ gamesViewModel.onOpenGameFoldersFragment()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentFoldersBinding.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)
+
+ binding.toolbarFolders.setNavigationOnClickListener {
+ binding.root.findNavController().popBackStack()
+ }
+
+ binding.listFolders.apply {
+ layoutManager = GridLayoutManager(
+ requireContext(),
+ resources.getInteger(R.integer.grid_columns)
+ )
+ adapter = FolderAdapter(requireActivity(), gamesViewModel)
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ gamesViewModel.folders.collect {
+ (binding.listFolders.adapter as FolderAdapter).submitList(it)
+ }
+ }
+ }
+
+ val mainActivity = requireActivity() as MainActivity
+ binding.buttonAdd.setOnClickListener {
+ mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
+ }
+
+ setInsets()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ gamesViewModel.onCloseGameFoldersFragment()
+ }
+
+ 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 mlpToolbar = binding.toolbarFolders.layoutParams as ViewGroup.MarginLayoutParams
+ mlpToolbar.leftMargin = leftInsets
+ mlpToolbar.rightMargin = rightInsets
+ binding.toolbarFolders.layoutParams = mlpToolbar
+
+ val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
+ val mlpFab =
+ binding.buttonAdd.layoutParams as ViewGroup.MarginLayoutParams
+ mlpFab.leftMargin = leftInsets + fabSpacing
+ mlpFab.rightMargin = rightInsets + fabSpacing
+ mlpFab.bottomMargin = barInsets.bottom + fabSpacing
+ binding.buttonAdd.layoutParams = mlpFab
+
+ val mlpListFolders = binding.listFolders.layoutParams as ViewGroup.MarginLayoutParams
+ mlpListFolders.leftMargin = leftInsets
+ mlpListFolders.rightMargin = rightInsets
+ binding.listFolders.layoutParams = mlpListFolders
+
+ binding.listFolders.updatePadding(
+ bottom = barInsets.bottom +
+ resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
+ )
+
+ windowInsets
+ }
+}
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 fd9785075..3addc2e63 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
@@ -26,10 +26,11 @@ 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 androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.HomeNavigationDirections
+import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
@@ -41,6 +42,7 @@ import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.Log
class HomeSettingsFragment : Fragment() {
private var _binding: FragmentHomeSettingsBinding? = null
@@ -85,28 +87,6 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
- R.string.open_user_folder,
- R.string.open_user_folder_description,
- R.drawable.ic_folder_open,
- { openFileManager() }
- )
- )
- add(
- HomeSetting(
- R.string.preferences_theme,
- R.string.theme_and_color_description,
- R.drawable.ic_palette,
- {
- val action = HomeNavigationDirections.actionGlobalSettingsActivity(
- null,
- Settings.MenuTag.SECTION_THEME
- )
- binding.root.findNavController().navigate(action)
- }
- )
- )
- add(
- HomeSetting(
R.string.gpu_driver_manager,
R.string.install_gpu_driver_description,
R.drawable.ic_build,
@@ -122,6 +102,20 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
+ R.string.applets,
+ R.string.applets_description,
+ R.drawable.ic_applet,
+ {
+ binding.root.findNavController()
+ .navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment)
+ },
+ { NativeLibrary.isFirmwareAvailable() },
+ R.string.applets_error_firmware,
+ R.string.applets_error_description
+ )
+ )
+ add(
+ HomeSetting(
R.string.manage_yuzu_data,
R.string.manage_yuzu_data_description,
R.drawable.ic_install,
@@ -133,18 +127,13 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
- R.string.select_games_folder,
+ R.string.manage_game_folders,
R.string.select_games_folder_description,
R.drawable.ic_add,
{
- mainActivity.getGamesDirectory.launch(
- Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
- )
- },
- { true },
- 0,
- 0,
- homeViewModel.gamesDir
+ binding.root.findNavController()
+ .navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment)
+ }
)
)
add(
@@ -157,6 +146,28 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
+ R.string.open_user_folder,
+ R.string.open_user_folder_description,
+ R.drawable.ic_folder_open,
+ { openFileManager() }
+ )
+ )
+ add(
+ HomeSetting(
+ R.string.preferences_theme,
+ R.string.theme_and_color_description,
+ R.drawable.ic_palette,
+ {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ null,
+ Settings.MenuTag.SECTION_THEME
+ )
+ binding.root.findNavController().navigate(action)
+ }
+ )
+ )
+ add(
+ HomeSetting(
R.string.about,
R.string.about_description,
R.drawable.ic_info_outline,
@@ -186,7 +197,8 @@ class HomeSettingsFragment : Fragment() {
}
binding.homeSettingsList.apply {
- layoutManager = LinearLayoutManager(requireContext())
+ layoutManager =
+ GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns))
adapter = HomeSettingAdapter(
requireActivity() as AppCompatActivity,
viewLifecycleOwner,
@@ -296,19 +308,32 @@ class HomeSettingsFragment : Fragment() {
}
}
+ // Share the current log if we just returned from a game but share the old log
+ // if we just started the app and the old log exists.
private fun shareLog() {
- val file = DocumentFile.fromSingleUri(
+ val currentLog = DocumentFile.fromSingleUri(
mainActivity,
DocumentsContract.buildDocumentUri(
DocumentProvider.AUTHORITY,
"${DocumentProvider.ROOT_ID}/log/yuzu_log.txt"
)
)!!
- if (file.exists()) {
- val intent = Intent(Intent.ACTION_SEND)
- .setDataAndType(file.uri, FileUtil.TEXT_PLAIN)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- .putExtra(Intent.EXTRA_STREAM, file.uri)
+ val oldLog = DocumentFile.fromSingleUri(
+ mainActivity,
+ DocumentsContract.buildDocumentUri(
+ DocumentProvider.AUTHORITY,
+ "${DocumentProvider.ROOT_ID}/log/yuzu_log.txt.old.txt"
+ )
+ )!!
+
+ val intent = Intent(Intent.ACTION_SEND)
+ .setDataAndType(currentLog.uri, FileUtil.TEXT_PLAIN)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ if (!Log.gameLaunched && oldLog.exists()) {
+ intent.putExtra(Intent.EXTRA_STREAM, oldLog.uri)
+ startActivity(Intent.createChooser(intent, getText(R.string.share_log)))
+ } else if (currentLog.exists()) {
+ intent.putExtra(Intent.EXTRA_STREAM, currentLog.uri)
startActivity(Intent.createChooser(intent, getText(R.string.share_log)))
} else {
Toast.makeText(
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
index ec116ab62..6940fc757 100644
--- 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
@@ -21,6 +21,8 @@ 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
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
class InstallableFragment : Fragment() {
private var _binding: FragmentInstallablesBinding? = null
@@ -78,7 +80,15 @@ class InstallableFragment : Fragment() {
R.string.manage_save_data,
R.string.import_export_saves_description,
install = { mainActivity.importSaves.launch(arrayOf("application/zip")) },
- export = { mainActivity.exportSave() }
+ export = {
+ mainActivity.exportSaves.launch(
+ "yuzu saves - ${
+ LocalDateTime.now().format(
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
+ )
+ }.zip"
+ )
+ }
)
} else {
Installable(
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 541b22f47..a6183d19e 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
@@ -8,6 +8,7 @@ import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
+import android.text.Html
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
@@ -32,7 +33,9 @@ class MessageDialogFragment : DialogFragment() {
if (titleId != 0) dialog.setTitle(titleId)
if (titleString.isNotEmpty()) dialog.setTitle(titleString)
- if (descriptionId != 0) dialog.setMessage(descriptionId)
+ if (descriptionId != 0) {
+ dialog.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
+ }
if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString)
if (helpLinkId != 0) {
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
index d18ec6974..b88d2c038 100644
--- 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
@@ -52,7 +52,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
settingsViewModel.clickedItem!!.setting.reset()
settingsViewModel.setAdapterItemChanged(position)
- settingsViewModel.shouldSave = true
}
.setNegativeButton(android.R.string.cancel, null)
.create()
@@ -137,24 +136,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
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
}
}
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
index 9d0594c6e..f95d545bf 100644
--- 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
@@ -40,8 +40,10 @@ class SettingsSearchFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
- returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
override fun onCreateView(
@@ -55,7 +57,6 @@ class SettingsSearchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- settingsViewModel.setIsUsingSearch(true)
if (savedInstanceState != null) {
binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
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 c66bb635a..c4277735d 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
@@ -42,7 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
-import org.yuzu.yuzu_emu.utils.GameHelper
+import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupFragment : Fragment() {
@@ -184,11 +184,7 @@ class SetupFragment : Fragment() {
R.string.add_games_warning_description,
R.string.add_games_warning_help,
{
- val preferences =
- PreferenceManager.getDefaultSharedPreferences(
- YuzuApplication.appContext
- )
- if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
+ if (NativeConfig.getGameDirs().isNotEmpty()) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt
new file mode 100644
index 000000000..8677674a3
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Applet.kt
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.R
+
+data class Applet(
+ @StringRes val titleId: Int,
+ @StringRes val descriptionId: Int,
+ @DrawableRes val iconId: Int,
+ val appletInfo: AppletInfo,
+ val cabinetMode: CabinetMode = CabinetMode.None
+)
+
+// Combination of Common::AM::Applets::AppletId enum and the entry id
+enum class AppletInfo(val appletId: Int, val entryId: Long = 0) {
+ None(0x00),
+ Application(0x01),
+ OverlayDisplay(0x02),
+ QLaunch(0x03),
+ Starter(0x04),
+ Auth(0x0A),
+ Cabinet(0x0B, 0x0100000000001002),
+ Controller(0x0C),
+ DataErase(0x0D),
+ Error(0x0E),
+ NetConnect(0x0F),
+ ProfileSelect(0x10),
+ SoftwareKeyboard(0x11),
+ MiiEdit(0x12, 0x0100000000001009),
+ Web(0x13),
+ Shop(0x14),
+ PhotoViewer(0x015, 0x010000000000100D),
+ Settings(0x16),
+ OfflineWeb(0x17),
+ LoginShare(0x18),
+ WebAuth(0x19),
+ MyPage(0x1A)
+}
+
+// Matches enum in Service::NFP::CabinetMode with extra metadata
+enum class CabinetMode(
+ val id: Int,
+ @StringRes val titleId: Int = 0,
+ @DrawableRes val iconId: Int = 0
+) {
+ None(-1),
+ StartNicknameAndOwnerSettings(0, R.string.cabinet_nickname_and_owner, R.drawable.ic_edit),
+ StartGameDataEraser(1, R.string.cabinet_game_data_eraser, R.drawable.ic_refresh),
+ StartRestorer(2, R.string.cabinet_restorer, R.drawable.ic_restore),
+ StartFormatter(3, R.string.cabinet_formatter, R.drawable.ic_clear)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index 6527c64ab..2fa3ab31b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -11,16 +11,15 @@ import kotlinx.serialization.Serializable
@Parcelize
@Serializable
class Game(
- val title: String,
- val description: String,
- val regions: String,
+ val title: String = "",
val path: String,
- val gameId: String,
- val company: String,
- val isHomebrew: Boolean
+ val programId: String = "",
+ val developer: String = "",
+ val version: String = "",
+ val isHomebrew: Boolean = false
) : Parcelable {
- val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
- val keyLastPlayedTime get() = "${gameId}_LastPlayed"
+ val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime"
+ val keyLastPlayedTime get() = "${path}_LastPlayed"
override fun equals(other: Any?): Boolean {
if (other !is Game) {
@@ -32,11 +31,9 @@ class Game(
override fun hashCode(): Int {
var result = title.hashCode()
- result = 31 * result + description.hashCode()
- result = 31 * result + regions.hashCode()
result = 31 * result + path.hashCode()
- result = 31 * result + gameId.hashCode()
- result = 31 * result + company.hashCode()
+ result = 31 * result + programId.hashCode()
+ result = 31 * result + developer.hashCode()
result = 31 * result + isHomebrew.hashCode()
return result
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt
new file mode 100644
index 000000000..274bc1c7b
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.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 android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class GameDir(
+ val uriString: String,
+ var deepScan: Boolean
+) : Parcelable
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 6e09fa81d..752d98c10 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
@@ -12,17 +12,17 @@ import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
+import org.yuzu.yuzu_emu.utils.GameMetadata
+import org.yuzu.yuzu_emu.utils.NativeConfig
-@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() {
val games: StateFlow<List<Game>> get() = _games
private val _games = MutableStateFlow(emptyList<Game>())
@@ -42,6 +42,9 @@ class GamesViewModel : ViewModel() {
val searchFocused: StateFlow<Boolean> get() = _searchFocused
private val _searchFocused = MutableStateFlow(false)
+ private val _folders = MutableStateFlow(mutableListOf<GameDir>())
+ val folders = _folders.asStateFlow()
+
init {
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
@@ -49,26 +52,35 @@ class GamesViewModel : ViewModel() {
// Retrieve list of cached games
val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getStringSet(GameHelper.KEY_GAMES, emptySet())
- if (storedGames!!.isNotEmpty()) {
- val deserializedGames = mutableSetOf<Game>()
- storedGames.forEach {
- val game: Game
- try {
- game = Json.decodeFromString(it)
- } catch (e: MissingFieldException) {
- return@forEach
- }
- val gameExists =
- DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
- ?.exists()
- if (gameExists == true) {
- deserializedGames.add(game)
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ getGameDirs()
+ if (storedGames!!.isNotEmpty()) {
+ val deserializedGames = mutableSetOf<Game>()
+ storedGames.forEach {
+ val game: Game
+ try {
+ game = Json.decodeFromString(it)
+ } catch (e: Exception) {
+ // We don't care about any errors related to parsing the game cache
+ return@forEach
+ }
+
+ val gameExists =
+ DocumentFile.fromSingleUri(
+ YuzuApplication.appContext,
+ Uri.parse(game.path)
+ )?.exists()
+ if (gameExists == true) {
+ deserializedGames.add(game)
+ }
+ }
+ setGames(deserializedGames.toList())
}
+ reloadGames(false)
}
- setGames(deserializedGames.toList())
}
- reloadGames(false)
}
fun setGames(games: List<Game>) {
@@ -98,7 +110,7 @@ class GamesViewModel : ViewModel() {
_searchFocused.value = searchFocused
}
- fun reloadGames(directoryChanged: Boolean) {
+ fun reloadGames(directoriesChanged: Boolean) {
if (isReloading.value) {
return
}
@@ -106,14 +118,65 @@ class GamesViewModel : ViewModel() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
- NativeLibrary.resetRomMetadata()
+ GameMetadata.resetMetadata()
setGames(GameHelper.getGames())
_isReloading.value = false
- if (directoryChanged) {
+ if (directoriesChanged) {
setShouldSwapData(true)
}
}
}
}
+
+ fun addFolder(gameDir: GameDir) =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ NativeConfig.addGameDir(gameDir)
+ getGameDirs()
+ }
+ }
+
+ fun removeFolder(gameDir: GameDir) =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ val gameDirs = _folders.value.toMutableList()
+ val removedDirIndex = gameDirs.indexOf(gameDir)
+ if (removedDirIndex != -1) {
+ gameDirs.removeAt(removedDirIndex)
+ NativeConfig.setGameDirs(gameDirs.toTypedArray())
+ getGameDirs()
+ }
+ }
+ }
+
+ fun updateGameDirs() =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ NativeConfig.setGameDirs(_folders.value.toTypedArray())
+ getGameDirs()
+ }
+ }
+
+ fun onOpenGameFoldersFragment() =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ getGameDirs()
+ }
+ }
+
+ fun onCloseGameFoldersFragment() =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ getGameDirs(true)
+ }
+ }
+
+ private fun getGameDirs(reloadList: Boolean = false) {
+ val gameDirs = NativeConfig.getGameDirs()
+ _folders.value = gameDirs.toMutableList()
+ if (reloadList) {
+ reloadGames(true)
+ }
+ }
}
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 756f76721..251b5a667 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -3,15 +3,9 @@
package org.yuzu.yuzu_emu.model
-import android.net.Uri
-import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.preference.PreferenceManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() {
val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
@@ -23,14 +17,6 @@ class HomeViewModel : ViewModel() {
val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
private val _shouldPageForward = MutableStateFlow(false)
- val gamesDir: StateFlow<String> get() = _gamesDir
- private val _gamesDir = MutableStateFlow(
- Uri.parse(
- PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- .getString(GameHelper.KEY_GAME_PATH, "")
- ).path ?: ""
- )
-
var navigatedToSetup = false
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
@@ -50,9 +36,4 @@ class HomeViewModel : ViewModel() {
fun setShouldPageForward(pageForward: Boolean) {
_shouldPageForward.value = pageForward
}
-
- fun setGamesDir(activity: FragmentActivity, dir: String) {
- ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
- _gamesDir.value = dir
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index 53fa7a8de..ccc981e95 100644
--- 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
@@ -13,8 +13,6 @@ 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
@@ -29,9 +27,6 @@ class SettingsViewModel : ViewModel() {
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)
@@ -57,10 +52,6 @@ class SettingsViewModel : ViewModel() {
_shouldReloadSettingsList.value = value
}
- fun setIsUsingSearch(value: Boolean) {
- _isUsingSearch.value = value
- }
-
fun setSliderTextValue(value: Float, units: String) {
_sliderProgress.value = value.toInt()
_sliderTextValue.value = String.format(
@@ -80,6 +71,5 @@ class SettingsViewModel : ViewModel() {
fun clear() {
game = null
- shouldSave = 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 233aa4101..16323a316 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,7 +6,6 @@ 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
@@ -20,7 +19,6 @@ 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
@@ -41,8 +39,8 @@ 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.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.getPublicFilesDir
@@ -53,9 +51,6 @@ 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
@@ -73,7 +68,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// 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()
@@ -259,6 +253,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
super.onResume()
}
+ override fun onStop() {
+ super.onStop()
+ CoroutineScope(Dispatchers.IO).launch {
+ NativeConfig.saveSettings()
+ }
+ }
+
override fun onDestroy() {
EmulationActivity.stopForegroundService(this)
super.onDestroy()
@@ -300,20 +301,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
- // When a new directory is picked, we currently will reset the existing games
- // database. This effectively means that only one game directory is supported.
- PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
- .putString(GameHelper.KEY_GAME_PATH, result.toString())
- .apply()
-
- Toast.makeText(
- applicationContext,
- R.string.games_dir_selected,
- Toast.LENGTH_LONG
- ).show()
+ val uriString = result.toString()
+ val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
+ if (folder != null) {
+ Toast.makeText(
+ applicationContext,
+ R.string.folder_already_added,
+ Toast.LENGTH_SHORT
+ ).show()
+ return
+ }
- gamesViewModel.reloadGames(true)
- homeViewModel.setGamesDir(this, result.path!!)
+ AddGameFolderDialogFragment.newInstance(uriString)
+ .show(supportFragmentManager, AddGameFolderDialogFragment.TAG)
}
val getProdKey =
@@ -403,6 +403,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} else {
firmwarePath.deleteRecursively()
cacheFirmwareDir.copyRecursively(firmwarePath, true)
+ NativeLibrary.initializeSystem(true)
getString(R.string.save_file_imported_success)
}
} catch (e: Exception) {
@@ -631,6 +632,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
// Clear existing user data
+ NativeConfig.unloadConfig()
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
// Copy archive to internal storage
@@ -648,7 +650,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
// Reinitialize relevant data
- NativeLibrary.initializeEmulation()
+ NativeLibrary.initializeSystem(true)
+ NativeConfig.initializeConfig()
gamesViewModel.reloadGames(false)
return@newInstance getString(R.string.user_data_import_success)
@@ -656,74 +659,30 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
/**
- * 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
- }
+ val exportSaves = registerForActivityResult(
+ ActivityResultContracts.CreateDocument("application/zip")
+ ) { result ->
+ if (result == null) {
+ return@registerForActivityResult
+ }
- 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)
- )
- )
+ IndeterminateProgressDialogFragment.newInstance(
+ this,
+ R.string.save_files_exporting,
+ false
+ ) {
+ val zipResult = FileUtil.zipFromInternalStorage(
+ File(savesFolderRoot),
+ savesFolderRoot,
+ BufferedOutputStream(contentResolver.openOutputStream(result))
+ )
+ return@newInstance when (zipResult) {
+ TaskState.Completed -> getString(R.string.export_success)
+ TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
- }
+ }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
private val startForResultExportSave =
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt
deleted file mode 100644
index eeefcdf20..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.utils
-
-import android.view.InputDevice
-import android.view.KeyEvent
-import android.view.MotionEvent
-
-/**
- * Some controllers have incorrect mappings. This class has special-case fixes for them.
- */
-class ControllerMappingHelper {
- /**
- * Some controllers report extra button presses that can be ignored.
- */
- fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
- return if (isDualShock4(inputDevice)) {
- // The two analog triggers generate analog motion events as well as a keycode.
- // We always prefer to use the analog values, so throw away the button press
- keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
- } else {
- false
- }
- }
-
- /**
- * Scale an axis to be zero-centered with a proper range.
- */
- fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float {
- if (isDualShock4(inputDevice)) {
- // Android doesn't have correct mappings for this controller's triggers. It reports them
- // as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
- // Scale them to properly zero-centered with a range of [0.0, 1.0].
- if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
- return (value + 1) / 2.0f
- }
- } else if (isXboxOneWireless(inputDevice)) {
- // Same as the DualShock 4, the mappings are missing.
- if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
- return (value + 1) / 2.0f
- }
- if (axis == MotionEvent.AXIS_GENERIC_1) {
- // This axis is stuck at ~.5. Ignore it.
- return 0.0f
- }
- } else if (isMogaPro2Hid(inputDevice)) {
- // This controller has a broken axis that reports a constant value. Ignore it.
- if (axis == MotionEvent.AXIS_GENERIC_1) {
- return 0.0f
- }
- }
- return value
- }
-
- // Sony DualShock 4 controller
- private fun isDualShock4(inputDevice: InputDevice): Boolean {
- return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc
- }
-
- // Microsoft Xbox One controller
- private fun isXboxOneWireless(inputDevice: InputDevice): Boolean {
- return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0
- }
-
- // Moga Pro 2 HID
- private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean {
- return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271
- }
-}
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 3c9f6bad0..21270fc84 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
@@ -15,7 +15,8 @@ object DirectoryInitialization {
fun start() {
if (!areDirectoriesReady) {
initializeInternalStorage()
- NativeLibrary.initializeEmulation()
+ NativeLibrary.initializeSystem(false)
+ NativeConfig.initializeConfig()
areDirectoriesReady = true
}
}
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 eafcf9e42..738275297 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
@@ -42,6 +42,23 @@ class DocumentsTree {
return node != null && node.isDirectory
}
+ fun getParentDirectory(filepath: String): String {
+ val node = resolvePath(filepath)!!
+ val parentNode = node.parent
+ if (parentNode != null && parentNode.isDirectory) {
+ return parentNode.uri!!.toString()
+ }
+ return node.uri!!.toString()
+ }
+
+ fun getFilename(filepath: String): String {
+ val node = resolvePath(filepath)
+ if (node != null) {
+ return node.name!!
+ }
+ return filepath
+ }
+
private fun resolvePath(filepath: String): DocumentsNode? {
val tokens = StringTokenizer(filepath, File.separator, false)
var iterator = root
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 5ee74a52c..bbe7bfa92 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
@@ -144,7 +144,7 @@ object FileUtil {
* @param path Native content uri path
* @return bool
*/
- fun exists(path: String?): Boolean {
+ fun exists(path: String?, suppressLog: Boolean = false): Boolean {
var c: Cursor? = null
try {
val mUri = Uri.parse(path)
@@ -152,7 +152,9 @@ object FileUtil {
c = context.contentResolver.query(mUri, columns, null, null, null)
return c!!.count > 0
} catch (e: Exception) {
- Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
+ if (!suppressLog) {
+ Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
+ }
} finally {
closeQuietly(c)
}
@@ -362,6 +364,27 @@ object FileUtil {
.lowercase()
}
+ fun isTreeUriValid(uri: Uri): Boolean {
+ val resolver = context.contentResolver
+ val columns = arrayOf(
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+ DocumentsContract.Document.COLUMN_MIME_TYPE
+ )
+ return try {
+ val docId: String = if (isRootTreeUri(uri)) {
+ DocumentsContract.getTreeDocumentId(uri)
+ } else {
+ DocumentsContract.getDocumentId(uri)
+ }
+ val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId)
+ resolver.query(childrenUri, columns, null, null, null)
+ true
+ } catch (_: Exception) {
+ false
+ }
+ }
+
@Throws(IOException::class)
fun getStringFromFile(file: File): String =
String(file.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 9001ca9ab..55010dc59 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,10 +11,11 @@ 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.GameDir
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
object GameHelper {
- const val KEY_GAME_PATH = "game_path"
+ private const val KEY_OLD_GAME_PATH = "game_path"
const val KEY_GAMES = "Games"
private lateinit var preferences: SharedPreferences
@@ -22,15 +23,43 @@ object GameHelper {
fun getGames(): List<Game> {
val games = mutableListOf<Game>()
val context = YuzuApplication.appContext
- val gamesDir =
- PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_GAME_PATH, "")
- val gamesUri = Uri.parse(gamesDir)
preferences = PreferenceManager.getDefaultSharedPreferences(context)
+ val gameDirs = mutableListOf<GameDir>()
+ val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: ""
+ if (oldGamesDir.isNotEmpty()) {
+ gameDirs.add(GameDir(oldGamesDir, true))
+ preferences.edit().remove(KEY_OLD_GAME_PATH).apply()
+ }
+ gameDirs.addAll(NativeConfig.getGameDirs())
+
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
- addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
+ val badDirs = mutableListOf<Int>()
+ gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
+ val gameDirUri = Uri.parse(gameDir.uriString)
+ val isValid = FileUtil.isTreeUriValid(gameDirUri)
+ if (isValid) {
+ addGamesRecursive(
+ games,
+ FileUtil.listFiles(gameDirUri),
+ if (gameDir.deepScan) 3 else 1
+ )
+ } else {
+ badDirs.add(index)
+ }
+ }
+
+ // Remove all game dirs with insufficient permissions from config
+ if (badDirs.isNotEmpty()) {
+ var offset = 0
+ badDirs.forEach {
+ gameDirs.removeAt(it - offset)
+ offset++
+ }
+ }
+ NativeConfig.setGameDirs(gameDirs.toTypedArray())
// Cache list of games found on disk
val serializedGames = mutableSetOf<String>()
@@ -71,27 +100,26 @@ object GameHelper {
fun getGame(uri: Uri, addedToLibrary: Boolean): Game {
val filePath = uri.toString()
- var name = NativeLibrary.getTitle(filePath)
+ var name = GameMetadata.getTitle(filePath)
// If the game's title field is empty, use the filename.
if (name.isEmpty()) {
name = FileUtil.getFilename(uri)
}
- var gameId = NativeLibrary.getGameId(filePath)
+ var programId = GameMetadata.getProgramId(filePath)
// If the game's ID field is empty, use the filename without extension.
- if (gameId.isEmpty()) {
- gameId = name.substring(0, name.lastIndexOf("."))
+ if (programId.isEmpty()) {
+ programId = name.substring(0, name.lastIndexOf("."))
}
val newGame = Game(
name,
- NativeLibrary.getDescription(filePath).replace("\n", " "),
- NativeLibrary.getRegions(filePath),
filePath,
- gameId,
- NativeLibrary.getCompany(filePath),
- NativeLibrary.isHomebrew(filePath)
+ programId,
+ GameMetadata.getDeveloper(filePath),
+ GameMetadata.getVersion(filePath),
+ GameMetadata.getIsHomebrew(filePath)
)
if (addedToLibrary) {
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
index 9fe99fab1..2e9b0beb8 100644
--- 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
@@ -8,9 +8,9 @@ import android.graphics.BitmapFactory
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
+import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.decode.DataSource
-import coil.executeBlocking
import coil.fetch.DrawableResult
import coil.fetch.FetchResult
import coil.fetch.Fetcher
@@ -18,7 +18,6 @@ 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
@@ -36,7 +35,7 @@ class GameIconFetcher(
}
private fun decodeGameIcon(uri: String): Bitmap? {
- val data = NativeLibrary.getIcon(uri)
+ val data = GameMetadata.getIcon(uri)
return BitmapFactory.decodeByteArray(
data,
0,
@@ -77,12 +76,13 @@ object GameIconUtils {
imageLoader.enqueue(request)
}
- fun getGameIcon(game: Game): Bitmap {
+ suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap {
val request = ImageRequest.Builder(YuzuApplication.appContext)
.data(game)
+ .lifecycle(lifecycleOwner)
.error(R.drawable.default_icon)
.build()
- return imageLoader.executeBlocking(request)
+ return imageLoader.execute(request)
.drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt
new file mode 100644
index 000000000..0f3542ac6
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+object GameMetadata {
+ external fun getTitle(path: String): String
+
+ external fun getProgramId(path: String): String
+
+ external fun getDeveloper(path: String): String
+
+ external fun getVersion(path: String): String
+
+ external fun getIcon(path: String): ByteArray
+
+ external fun getIsHomebrew(path: String): Boolean
+
+ external fun resetMetadata()
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
index e963dfbc1..e63382e1d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt
@@ -3,23 +3,32 @@
package org.yuzu.yuzu_emu.utils
+import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import kotlin.math.sqrt
import org.yuzu.yuzu_emu.NativeLibrary
-class InputHandler {
+object InputHandler {
+ private var controllerIds = getGameControllerIds()
+
fun initialize() {
// Connect first controller
NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
}
+ fun updateControllerIds() {
+ controllerIds = getGameControllerIds()
+ }
+
fun dispatchKeyEvent(event: KeyEvent): Boolean {
val button: Int = when (event.device.vendorId) {
0x045E -> getInputXboxButtonKey(event.keyCode)
0x054C -> getInputDS5ButtonKey(event.keyCode)
0x057E -> getInputJoyconButtonKey(event.keyCode)
0x1532 -> getInputRazerButtonKey(event.keyCode)
+ 0x3537 -> getInputRedmagicButtonKey(event.keyCode)
+ 0x358A -> getInputBackboneLabsButtonKey(event.keyCode)
else -> getInputGenericButtonKey(event.keyCode)
}
@@ -35,7 +44,7 @@ class InputHandler {
}
return NativeLibrary.onGamePadButtonEvent(
- getPlayerNumber(event.device.controllerNumber),
+ getPlayerNumber(event.device.controllerNumber, event.deviceId),
button,
action
)
@@ -58,9 +67,14 @@ class InputHandler {
return true
}
- private fun getPlayerNumber(index: Int): Int {
+ private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int {
+ var deviceIndex = index
+ if (deviceId != -1) {
+ deviceIndex = controllerIds[deviceId] ?: 0
+ }
+
// TODO: Joycons are handled as different controllers. Find a way to merge them.
- return when (index) {
+ return when (deviceIndex) {
2 -> NativeLibrary.Player2Device
3 -> NativeLibrary.Player3Device
4 -> NativeLibrary.Player4Device
@@ -215,6 +229,42 @@ class InputHandler {
}
}
+ private fun getInputRedmagicButtonKey(key: Int): Int {
+ return when (key) {
+ KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
+ KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
+ KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
+ KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
+ KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
+ KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
+ KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
+ KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
+ KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
+ KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
+ KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
+ KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
+ else -> -1
+ }
+ }
+
+ private fun getInputBackboneLabsButtonKey(key: Int): Int {
+ return when (key) {
+ KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
+ KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
+ KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
+ KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
+ KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
+ KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
+ KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
+ KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
+ KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
+ KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
+ KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
+ KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
+ else -> -1
+ }
+ }
+
private fun getInputGenericButtonKey(key: Int): Int {
return when (key) {
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
@@ -238,7 +288,7 @@ class InputHandler {
}
private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
- val playerNumber = getPlayerNumber(event.device.controllerNumber)
+ val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
when (axis) {
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -297,7 +347,7 @@ class InputHandler {
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
// Joycon support is half dead. Right joystick doesn't work
- val playerNumber = getPlayerNumber(event.device.controllerNumber)
+ val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
when (axis) {
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -325,7 +375,7 @@ class InputHandler {
}
private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
- val playerNumber = getPlayerNumber(event.device.controllerNumber)
+ val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
when (axis) {
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
@@ -362,4 +412,33 @@ class InputHandler {
)
}
}
+
+ fun getGameControllerIds(): Map<Int, Int> {
+ val gameControllerDeviceIds = mutableMapOf<Int, Int>()
+ val deviceIds = InputDevice.getDeviceIds()
+ var controllerSlot = 1
+ deviceIds.forEach { deviceId ->
+ InputDevice.getDevice(deviceId)?.apply {
+ // Don't over-assign controllers
+ if (controllerSlot >= 8) {
+ return gameControllerDeviceIds
+ }
+
+ // Verify that the device has gamepad buttons, control sticks, or both.
+ if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
+ sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
+ ) {
+ // This device is a game controller. Store its device ID.
+ if (deviceId and id and vendorId and productId != 0) {
+ // Additionally filter out devices that have no ID
+ gameControllerDeviceIds
+ .takeIf { !it.contains(deviceId) }
+ ?.put(deviceId, controllerSlot)
+ controllerSlot++
+ }
+ }
+ }
+ }
+ return gameControllerDeviceIds
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt
index a193e82a4..aebe84b0f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/Log.kt
@@ -3,38 +3,29 @@
package org.yuzu.yuzu_emu.utils
-import android.util.Log
-import org.yuzu.yuzu_emu.BuildConfig
-
-/**
- * Contains methods that call through to [android.util.Log], but
- * with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log
- * levels in release builds.
- */
+import android.os.Build
+
object Log {
- private const val TAG = "Yuzu Frontend"
+ // Tracks whether we should share the old log or the current log
+ var gameLaunched = false
- fun verbose(message: String) {
- if (BuildConfig.DEBUG) {
- Log.v(TAG, message)
- }
- }
+ external fun debug(message: String)
- fun debug(message: String) {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, message)
- }
- }
+ external fun warning(message: String)
- fun info(message: String) {
- Log.i(TAG, message)
- }
+ external fun info(message: String)
- fun warning(message: String) {
- Log.w(TAG, message)
- }
+ external fun error(message: String)
- fun error(message: String) {
- Log.e(TAG, message)
+ external fun critical(message: String)
+
+ fun logDeviceInfo() {
+ info("Device Manufacturer - ${Build.MANUFACTURER}")
+ info("Device Model - ${Build.MODEL}")
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
+ info("SoC Manufacturer - ${Build.SOC_MANUFACTURER}")
+ info("SoC Model - ${Build.SOC_MODEL}")
+ }
+ info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
index aa4a5539a..9076a86c4 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
@@ -27,7 +27,7 @@ object MemoryUtil {
const val Pb = Tb * 1024
const val Eb = Pb * 1024
- private fun bytesToSizeUnit(size: Float): String =
+ private fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String =
when {
size < Kb -> {
context.getString(
@@ -39,63 +39,59 @@ object MemoryUtil {
size < Mb -> {
context.getString(
R.string.memory_formatted,
- (size / Kb).hundredths,
+ if (roundUp) ceil(size / Kb) else (size / Kb).hundredths,
context.getString(R.string.memory_kilobyte)
)
}
size < Gb -> {
context.getString(
R.string.memory_formatted,
- (size / Mb).hundredths,
+ if (roundUp) ceil(size / Mb) else (size / Mb).hundredths,
context.getString(R.string.memory_megabyte)
)
}
size < Tb -> {
context.getString(
R.string.memory_formatted,
- (size / Gb).hundredths,
+ if (roundUp) ceil(size / Gb) else (size / Gb).hundredths,
context.getString(R.string.memory_gigabyte)
)
}
size < Pb -> {
context.getString(
R.string.memory_formatted,
- (size / Tb).hundredths,
+ if (roundUp) ceil(size / Tb) else (size / Tb).hundredths,
context.getString(R.string.memory_terabyte)
)
}
size < Eb -> {
context.getString(
R.string.memory_formatted,
- (size / Pb).hundredths,
+ if (roundUp) ceil(size / Pb) else (size / Pb).hundredths,
context.getString(R.string.memory_petabyte)
)
}
else -> {
context.getString(
R.string.memory_formatted,
- (size / Eb).hundredths,
+ if (roundUp) ceil(size / Eb) else (size / Eb).hundredths,
context.getString(R.string.memory_exabyte)
)
}
}
- // Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
- // the potential error created by memInfo.totalMem
- private val totalMemory: Float
+ val totalMemory: Float
get() {
val memInfo = ActivityManager.MemoryInfo()
with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
getMemoryInfo(memInfo)
}
- return ceil(
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- memInfo.advertisedMem.toFloat()
- } else {
- memInfo.totalMem.toFloat()
- }
- )
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ memInfo.advertisedMem.toFloat()
+ } else {
+ memInfo.totalMem.toFloat()
+ }
}
fun isLessThan(minimum: Int, size: Float): Boolean =
@@ -109,5 +105,7 @@ object MemoryUtil {
else -> totalMemory < Kb && totalMemory < minimum
}
- fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory)
+ // Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
+ // the potential error created by memInfo.totalMem
+ fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory, true)
}
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
index 9425f8b99..f4e1bb13f 100644
--- 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
@@ -3,7 +3,33 @@
package org.yuzu.yuzu_emu.utils
+import org.yuzu.yuzu_emu.model.GameDir
+
object NativeConfig {
+ /**
+ * Creates a Config object and opens the emulation config.
+ */
+ @Synchronized
+ external fun initializeConfig()
+
+ /**
+ * Destroys the stored config object. This automatically saves the existing config.
+ */
+ @Synchronized
+ external fun unloadConfig()
+
+ /**
+ * Reads values saved to the config file and saves them.
+ */
+ @Synchronized
+ external fun reloadSettings()
+
+ /**
+ * Saves settings values in memory to disk.
+ */
+ @Synchronized
+ external fun saveSettings()
+
external fun getBoolean(key: String, getDefault: Boolean): Boolean
external fun setBoolean(key: String, value: Boolean)
@@ -30,4 +56,22 @@ object NativeConfig {
external fun getConfigHeader(category: Int): String
external fun getPairedSettingKey(key: String): String
+
+ /**
+ * Gets every [GameDir] in AndroidSettings::values.game_dirs
+ */
+ @Synchronized
+ external fun getGameDirs(): Array<GameDir>
+
+ /**
+ * Clears the AndroidSettings::values.game_dirs array and replaces them with the provided array
+ */
+ @Synchronized
+ external fun setGameDirs(dirs: Array<GameDir>)
+
+ /**
+ * Adds a single [GameDir] to the AndroidSettings::values.game_dirs array
+ */
+ @Synchronized
+ external fun addGameDir(dir: GameDir)
}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e15d1480b..2acc93da8 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -6,22 +6,24 @@ add_library(yuzu-android SHARED
android_common/android_common.h
applets/software_keyboard.cpp
applets/software_keyboard.h
- config.cpp
- config.h
- default_ini.h
emu_window/emu_window.cpp
emu_window/emu_window.h
id_cache.cpp
id_cache.h
native.cpp
+ native.h
native_config.cpp
- uisettings.cpp
+ android_settings.cpp
+ game_metadata.cpp
+ native_log.cpp
+ android_config.cpp
+ android_config.h
)
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
-target_link_libraries(yuzu-android PRIVATE audio_core common core input_common)
-target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad inih jnigraphics log)
+target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common)
+target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log)
if (ARCHITECTURE_arm64)
target_link_libraries(yuzu-android PRIVATE adrenotools)
endif()
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
new file mode 100644
index 000000000..767d8ea83
--- /dev/null
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "android_config.h"
+#include "android_settings.h"
+#include "common/settings_setting.h"
+
+AndroidConfig::AndroidConfig(const std::string& config_name, ConfigType config_type)
+ : Config(config_type) {
+ Initialize(config_name);
+ if (config_type != ConfigType::InputProfile) {
+ ReadAndroidValues();
+ SaveAndroidValues();
+ }
+}
+
+AndroidConfig::~AndroidConfig() {
+ if (global) {
+ AndroidConfig::SaveAllValues();
+ }
+}
+
+void AndroidConfig::ReloadAllValues() {
+ Reload();
+ ReadAndroidValues();
+ SaveAndroidValues();
+}
+
+void AndroidConfig::SaveAllValues() {
+ Save();
+ SaveAndroidValues();
+}
+
+void AndroidConfig::ReadAndroidValues() {
+ if (global) {
+ ReadAndroidUIValues();
+ ReadUIValues();
+ }
+}
+
+void AndroidConfig::ReadAndroidUIValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Android));
+
+ ReadCategory(Settings::Category::Android);
+
+ EndGroup();
+}
+
+void AndroidConfig::ReadUIValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
+
+ ReadPathValues();
+
+ EndGroup();
+}
+
+void AndroidConfig::ReadPathValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
+
+ const int gamedirs_size = BeginArray(std::string("gamedirs"));
+ for (int i = 0; i < gamedirs_size; ++i) {
+ SetArrayIndex(i);
+ AndroidSettings::GameDir game_dir;
+ game_dir.path = ReadStringSetting(std::string("path"));
+ game_dir.deep_scan =
+ ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false));
+ AndroidSettings::values.game_dirs.push_back(game_dir);
+ }
+ EndArray();
+
+ EndGroup();
+}
+
+void AndroidConfig::SaveAndroidValues() {
+ if (global) {
+ SaveAndroidUIValues();
+ SaveUIValues();
+ }
+
+ WriteToIni();
+}
+
+void AndroidConfig::SaveAndroidUIValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Android));
+
+ WriteCategory(Settings::Category::Android);
+
+ EndGroup();
+}
+
+void AndroidConfig::SaveUIValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
+
+ SavePathValues();
+
+ EndGroup();
+}
+
+void AndroidConfig::SavePathValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
+
+ BeginArray(std::string("gamedirs"));
+ for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
+ SetArrayIndex(i);
+ const auto& game_dir = AndroidSettings::values.game_dirs[i];
+ WriteSetting(std::string("path"), game_dir.path);
+ WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false));
+ }
+ EndArray();
+
+ EndGroup();
+}
+
+std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
+ auto& map = Settings::values.linkage.by_category;
+ if (map.contains(category)) {
+ return Settings::values.linkage.by_category[category];
+ }
+ return AndroidSettings::values.linkage.by_category[category];
+}
diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h
new file mode 100644
index 000000000..f490be016
--- /dev/null
+++ b/src/android/app/src/main/jni/android_config.h
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "frontend_common/config.h"
+
+class AndroidConfig final : public Config {
+public:
+ explicit AndroidConfig(const std::string& config_name = "config",
+ ConfigType config_type = ConfigType::GlobalConfig);
+ ~AndroidConfig() override;
+
+ void ReloadAllValues() override;
+ void SaveAllValues() override;
+
+protected:
+ void ReadAndroidValues();
+ void ReadAndroidUIValues();
+ void ReadHidbusValues() override {}
+ void ReadDebugControlValues() override {}
+ void ReadPathValues() override;
+ void ReadShortcutValues() override {}
+ void ReadUIValues() override;
+ void ReadUIGamelistValues() override {}
+ void ReadUILayoutValues() override {}
+ void ReadMultiplayerValues() override {}
+
+ void SaveAndroidValues();
+ void SaveAndroidUIValues();
+ void SaveHidbusValues() override {}
+ void SaveDebugControlValues() override {}
+ void SavePathValues() override;
+ void SaveShortcutValues() override {}
+ void SaveUIValues() override;
+ void SaveUIGamelistValues() override {}
+ void SaveUILayoutValues() override {}
+ void SaveMultiplayerValues() override {}
+
+ std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
+};
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/android_settings.cpp
index f2f0bad50..16023a6b0 100644
--- a/src/android/app/src/main/jni/uisettings.cpp
+++ b/src/android/app/src/main/jni/android_settings.cpp
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "uisettings.h"
+#include "android_settings.h"
namespace AndroidSettings {
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/android_settings.h
index 494654af7..fc0523206 100644
--- a/src/android/app/src/main/jni/uisettings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -9,11 +9,19 @@
namespace AndroidSettings {
+struct GameDir {
+ std::string path;
+ bool deep_scan = false;
+};
+
struct Values {
Settings::Linkage linkage;
+ // Path settings
+ std::vector<GameDir> game_dirs;
+
// Android
- Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
+ Settings::Setting<bool> picture_in_picture{linkage, false, "picture_in_picture",
Settings::Category::Android};
Settings::Setting<s32> screen_layout{linkage,
5,
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
deleted file mode 100644
index 81120ab0f..000000000
--- a/src/android/app/src/main/jni/config.cpp
+++ /dev/null
@@ -1,330 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <memory>
-#include <optional>
-#include <sstream>
-
-#include <INIReader.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/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(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) {
- LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
- config_loc_str);
-
- void(FS::CreateParentDir(config_loc));
- void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents));
-
- config = std::make_unique<INIReader>(config_loc_str);
-
- return LoadINI(default_contents, false);
- }
- LOG_ERROR(Config, "Failed.");
- return false;
- }
- LOG_INFO(Config, "Successfully loaded {}", config_loc_str);
- return true;
-}
-
-template <>
-void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
- std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault());
- if (setting_value.empty()) {
- setting_value = setting.GetDefault();
- }
- setting = std::move(setting_value);
-}
-
-template <>
-void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
- setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
-}
-
-template <typename Type, bool ranged>
-void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
- setting = static_cast<Type>(
- config->GetInteger(group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
-}
-
-void Config::ReadValues() {
- ReadSetting("ControlsGeneral", Settings::values.mouse_enabled);
- ReadSetting("ControlsGeneral", Settings::values.touch_device);
- ReadSetting("ControlsGeneral", Settings::values.keyboard_enabled);
- ReadSetting("ControlsGeneral", Settings::values.debug_pad_enabled);
- ReadSetting("ControlsGeneral", Settings::values.vibration_enabled);
- ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations);
- ReadSetting("ControlsGeneral", Settings::values.motion_enabled);
- Settings::values.touchscreen.enabled =
- config->GetBoolean("ControlsGeneral", "touch_enabled", true);
- Settings::values.touchscreen.rotation_angle =
- config->GetInteger("ControlsGeneral", "touch_angle", 0);
- Settings::values.touchscreen.diameter_x =
- config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
- Settings::values.touchscreen.diameter_y =
- config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
-
- int num_touch_from_button_maps =
- config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
- if (num_touch_from_button_maps > 0) {
- for (int i = 0; i < num_touch_from_button_maps; ++i) {
- Settings::TouchFromButtonMap map;
- map.name = config->Get("ControlsGeneral",
- std::string("touch_from_button_maps_") + std::to_string(i) +
- std::string("_name"),
- "default");
- const int num_touch_maps = config->GetInteger(
- "ControlsGeneral",
- std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"),
- 0);
- map.buttons.reserve(num_touch_maps);
-
- for (int j = 0; j < num_touch_maps; ++j) {
- std::string touch_mapping =
- config->Get("ControlsGeneral",
- std::string("touch_from_button_maps_") + std::to_string(i) +
- std::string("_bind_") + std::to_string(j),
- "");
- map.buttons.emplace_back(std::move(touch_mapping));
- }
-
- Settings::values.touch_from_button_maps.emplace_back(std::move(map));
- }
- } else {
- Settings::values.touch_from_button_maps.emplace_back(
- Settings::TouchFromButtonMap{"default", {}});
- num_touch_from_button_maps = 1;
- }
- Settings::values.touch_from_button_map_index = std::clamp(
- Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
-
- ReadSetting("ControlsGeneral", Settings::values.udp_input_servers);
-
- // Data Storage
- ReadSetting("Data Storage", Settings::values.use_virtual_sd);
- FS::SetYuzuPath(FS::YuzuPath::NANDDir,
- config->Get("Data Storage", "nand_directory",
- FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
- FS::SetYuzuPath(FS::YuzuPath::SDMCDir,
- config->Get("Data Storage", "sdmc_directory",
- FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
- FS::SetYuzuPath(FS::YuzuPath::LoadDir,
- config->Get("Data Storage", "load_directory",
- FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
- FS::SetYuzuPath(FS::YuzuPath::DumpDir,
- config->Get("Data Storage", "dump_directory",
- FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
- ReadSetting("Data Storage", Settings::values.gamecard_inserted);
- ReadSetting("Data Storage", Settings::values.gamecard_current_game);
- ReadSetting("Data Storage", Settings::values.gamecard_path);
-
- // System
- ReadSetting("System", Settings::values.current_user);
- Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
- Service::Account::MAX_USERS - 1);
-
- // Disable docked mode by default on Android
- Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false)
- ? Settings::ConsoleMode::Docked
- : Settings::ConsoleMode::Handheld);
-
- const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
- if (rng_seed_enabled) {
- Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0));
- } else {
- Settings::values.rng_seed.SetValue(0);
- }
- Settings::values.rng_seed_enabled.SetValue(rng_seed_enabled);
-
- const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false);
- if (custom_rtc_enabled) {
- Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0);
- } else {
- Settings::values.custom_rtc = 0;
- }
- Settings::values.custom_rtc_enabled = custom_rtc_enabled;
-
- ReadSetting("System", Settings::values.language_index);
- ReadSetting("System", Settings::values.region_index);
- ReadSetting("System", Settings::values.time_zone_index);
- ReadSetting("System", Settings::values.sound_index);
-
- // Core
- ReadSetting("Core", Settings::values.use_multi_core);
- ReadSetting("Core", Settings::values.memory_layout_mode);
-
- // Cpu
- ReadSetting("Cpu", Settings::values.cpu_accuracy);
- ReadSetting("Cpu", Settings::values.cpu_debug_mode);
- ReadSetting("Cpu", Settings::values.cpuopt_page_tables);
- ReadSetting("Cpu", Settings::values.cpuopt_block_linking);
- ReadSetting("Cpu", Settings::values.cpuopt_return_stack_buffer);
- ReadSetting("Cpu", Settings::values.cpuopt_fast_dispatcher);
- ReadSetting("Cpu", Settings::values.cpuopt_context_elimination);
- ReadSetting("Cpu", Settings::values.cpuopt_const_prop);
- ReadSetting("Cpu", Settings::values.cpuopt_misc_ir);
- ReadSetting("Cpu", Settings::values.cpuopt_reduce_misalign_checks);
- ReadSetting("Cpu", Settings::values.cpuopt_fastmem);
- ReadSetting("Cpu", Settings::values.cpuopt_fastmem_exclusives);
- ReadSetting("Cpu", Settings::values.cpuopt_recompile_exclusives);
- ReadSetting("Cpu", Settings::values.cpuopt_ignore_memory_aborts);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_unfuse_fma);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_reduce_fp_error);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_inaccurate_nan);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_fastmem_check);
- ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_global_monitor);
-
- // Renderer
- ReadSetting("Renderer", Settings::values.renderer_backend);
- ReadSetting("Renderer", Settings::values.renderer_debug);
- ReadSetting("Renderer", Settings::values.renderer_shader_feedback);
- ReadSetting("Renderer", Settings::values.enable_nsight_aftermath);
- ReadSetting("Renderer", Settings::values.disable_shader_loop_safety_checks);
- ReadSetting("Renderer", Settings::values.vulkan_device);
-
- ReadSetting("Renderer", Settings::values.resolution_setup);
- ReadSetting("Renderer", Settings::values.scaling_filter);
- ReadSetting("Renderer", Settings::values.fsr_sharpening_slider);
- ReadSetting("Renderer", Settings::values.anti_aliasing);
- ReadSetting("Renderer", Settings::values.fullscreen_mode);
- ReadSetting("Renderer", Settings::values.aspect_ratio);
- ReadSetting("Renderer", Settings::values.max_anisotropy);
- ReadSetting("Renderer", Settings::values.use_speed_limit);
- ReadSetting("Renderer", Settings::values.speed_limit);
- ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
- ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
- ReadSetting("Renderer", Settings::values.vsync_mode);
- ReadSetting("Renderer", Settings::values.shader_backend);
- ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
- ReadSetting("Renderer", Settings::values.nvdec_emulation);
- ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
- ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache);
-
- ReadSetting("Renderer", Settings::values.bg_red);
- ReadSetting("Renderer", Settings::values.bg_green);
- ReadSetting("Renderer", Settings::values.bg_blue);
-
- // Use GPU accuracy normal by default on Android
- Settings::values.gpu_accuracy = static_cast<Settings::GpuAccuracy>(config->GetInteger(
- "Renderer", "gpu_accuracy", static_cast<u32>(Settings::GpuAccuracy::Normal)));
-
- // Use GPU default anisotropic filtering on Android
- Settings::values.max_anisotropy =
- static_cast<Settings::AnisotropyMode>(config->GetInteger("Renderer", "max_anisotropy", 1));
-
- // Disable ASTC compute by default on Android
- Settings::values.accelerate_astc.SetValue(
- config->GetBoolean("Renderer", "accelerate_astc", false) ? Settings::AstcDecodeMode::Gpu
- : Settings::AstcDecodeMode::Cpu);
-
- // Enable asynchronous presentation by default on Android
- Settings::values.async_presentation =
- config->GetBoolean("Renderer", "async_presentation", true);
-
- // Disable force_max_clock by default on Android
- Settings::values.renderer_force_max_clock =
- config->GetBoolean("Renderer", "force_max_clock", false);
-
- // Disable use_reactive_flushing by default on Android
- Settings::values.use_reactive_flushing =
- config->GetBoolean("Renderer", "use_reactive_flushing", false);
-
- // Audio
- ReadSetting("Audio", Settings::values.sink_id);
- ReadSetting("Audio", Settings::values.audio_output_device_id);
- ReadSetting("Audio", Settings::values.volume);
-
- // Miscellaneous
- // log_filter has a different default here than from common
- Settings::values.log_filter = "*:Info";
- ReadSetting("Miscellaneous", Settings::values.use_dev_keys);
-
- // Debugging
- Settings::values.record_frame_times =
- config->GetBoolean("Debugging", "record_frame_times", false);
- ReadSetting("Debugging", Settings::values.dump_exefs);
- ReadSetting("Debugging", Settings::values.dump_nso);
- ReadSetting("Debugging", Settings::values.enable_fs_access_log);
- ReadSetting("Debugging", Settings::values.reporting_services);
- ReadSetting("Debugging", Settings::values.quest_flag);
- ReadSetting("Debugging", Settings::values.use_debug_asserts);
- ReadSetting("Debugging", Settings::values.use_auto_stub);
- ReadSetting("Debugging", Settings::values.disable_macro_jit);
- ReadSetting("Debugging", Settings::values.disable_macro_hle);
- ReadSetting("Debugging", Settings::values.use_gdbstub);
- ReadSetting("Debugging", Settings::values.gdbstub_port);
-
- const auto title_list = config->Get("AddOns", "title_ids", "");
- std::stringstream ss(title_list);
- std::string line;
- while (std::getline(ss, line, '|')) {
- 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);
- std::string inner_line;
- std::vector<std::string> out;
- while (std::getline(inner_ss, inner_line, '|')) {
- out.push_back(inner_line);
- }
-
- Settings::values.disabled_addons.insert_or_assign(title_id, out);
- }
-
- // Web Service
- ReadSetting("WebService", Settings::values.enable_telemetry);
- ReadSetting("WebService", Settings::values.web_api_url);
- ReadSetting("WebService", Settings::values.yuzu_username);
- ReadSetting("WebService", Settings::values.yuzu_token);
-
- // Network
- ReadSetting("Network", Settings::values.network_interface);
-
- // Android
- ReadSetting("Android", AndroidSettings::values.picture_in_picture);
- ReadSetting("Android", AndroidSettings::values.screen_layout);
-}
-
-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
deleted file mode 100644
index e1e8f47ed..000000000
--- a/src/android/app/src/main/jni/config.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <filesystem>
-#include <memory>
-#include <optional>
-#include <string>
-
-#include "common/settings.h"
-
-class INIReader;
-
-class Config {
- bool LoadINI(const std::string& default_contents = "", bool retry = true);
-
-public:
- enum class ConfigType {
- GlobalConfig,
- PerGameConfig,
- InputProfile,
- };
-
- explicit Config(const std::string& config_name = "config",
- ConfigType config_type = ConfigType::GlobalConfig);
- ~Config();
-
- void Initialize(const std::string& config_name);
-
-private:
- /**
- * 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/default_ini.h b/src/android/app/src/main/jni/default_ini.h
deleted file mode 100644
index d81422a74..000000000
--- a/src/android/app/src/main/jni/default_ini.h
+++ /dev/null
@@ -1,511 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-namespace DefaultINI {
-
-const char* android_config_file = R"(
-
-[ControlsP0]
-# The input devices and parameters for each Switch native input
-# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ...
-# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
-# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
-
-# Indicates if this player should be connected at boot
-connected=
-
-# for button input, the following devices are available:
-# - "keyboard" (default) for keyboard input. Required parameters:
-# - "code": the code of the key to bind
-# - "sdl" for joystick input using SDL. Required parameters:
-# - "guid": SDL identification GUID of the joystick
-# - "port": the index of the joystick to bind
-# - "button"(optional): the index of the button to bind
-# - "hat"(optional): the index of the hat to bind as direction buttons
-# - "axis"(optional): the index of the axis to bind
-# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
-# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
-# triggered if the axis value crosses
-# - "direction"(only used for axis): "+" means the button is triggered when the axis value
-# is greater than the threshold; "-" means the button is triggered when the axis value
-# is smaller than the threshold
-button_a=
-button_b=
-button_x=
-button_y=
-button_lstick=
-button_rstick=
-button_l=
-button_r=
-button_zl=
-button_zr=
-button_plus=
-button_minus=
-button_dleft=
-button_dup=
-button_dright=
-button_ddown=
-button_lstick_left=
-button_lstick_up=
-button_lstick_right=
-button_lstick_down=
-button_sl=
-button_sr=
-button_home=
-button_screenshot=
-
-# for analog input, the following devices are available:
-# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
-# - "up", "down", "left", "right": sub-devices for each direction.
-# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
-# - "modifier": sub-devices as a modifier.
-# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
-# Must be in range of 0.0-1.0. Defaults to 0.5
-# - "sdl" for joystick input using SDL. Required parameters:
-# - "guid": SDL identification GUID of the joystick
-# - "port": the index of the joystick to bind
-# - "axis_x": the index of the axis to bind as x-axis (default to 0)
-# - "axis_y": the index of the axis to bind as y-axis (default to 1)
-lstick=
-rstick=
-
-# for motion input, the following devices are available:
-# - "keyboard" (default) for emulating random motion input from buttons. Required parameters:
-# - "code": the code of the key to bind
-# - "sdl" for motion input using SDL. Required parameters:
-# - "guid": SDL identification GUID of the joystick
-# - "port": the index of the joystick to bind
-# - "motion": the index of the motion sensor to bind
-# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters:
-# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001"
-# - "port": the port of the cemu hook server
-# - "pad": the index of the joystick
-# - "motion": the index of the motion sensor of the joystick to bind
-motionleft=
-motionright=
-
-[ControlsGeneral]
-# To use the debug_pad, prepend `debug_pad_` before each button setting above.
-# i.e. debug_pad_button_a=
-
-# Enable debug pad inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-debug_pad_enabled =
-
-# Whether to enable or disable vibration
-# 0: Disabled, 1 (default): Enabled
-vibration_enabled=
-
-# Whether to enable or disable accurate vibrations
-# 0 (default): Disabled, 1: Enabled
-enable_accurate_vibrations=
-
-# Enables controller motion inputs
-# 0: Disabled, 1 (default): Enabled
-motion_enabled =
-
-# Defines the udp device's touch screen coordinate system for cemuhookudp devices
-# - "min_x", "min_y", "max_x", "max_y"
-touch_device=
-
-# for mapping buttons to touch inputs.
-#touch_from_button_map=1
-#touch_from_button_maps_0_name=default
-#touch_from_button_maps_0_count=2
-#touch_from_button_maps_0_bind_0=foo
-#touch_from_button_maps_0_bind_1=bar
-# etc.
-
-# List of Cemuhook UDP servers, delimited by ','.
-# Default: 127.0.0.1:26760
-# Example: 127.0.0.1:26760,123.4.5.67:26761
-udp_input_servers =
-
-# Enable controlling an axis via a mouse input.
-# 0 (default): Off, 1: On
-mouse_panning =
-
-# Set mouse sensitivity.
-# Default: 1.0
-mouse_panning_sensitivity =
-
-# Emulate an analog control stick from keyboard inputs.
-# 0 (default): Disabled, 1: Enabled
-emulate_analog_keyboard =
-
-# Enable mouse inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-mouse_enabled =
-
-# Enable keyboard inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-keyboard_enabled =
-
-[Core]
-# Whether to use multi-core for CPU emulation
-# 0: Disabled, 1 (default): Enabled
-use_multi_core =
-
-# Enable unsafe extended guest system memory layout (8GB DRAM)
-# 0 (default): Disabled, 1: Enabled
-use_unsafe_extended_memory_layout =
-
-[Cpu]
-# Adjusts various optimizations.
-# Auto-select mode enables choice unsafe optimizations.
-# Accurate enables only safe optimizations.
-# Unsafe allows any unsafe optimizations.
-# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations
-cpu_accuracy =
-
-# Allow disabling safe optimizations.
-# 0 (default): Disabled, 1: Enabled
-cpu_debug_mode =
-
-# Enable inline page tables optimization (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_page_tables =
-
-# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_block_linking =
-
-# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_return_stack_buffer =
-
-# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fast_dispatcher =
-
-# Enable context elimination CPU Optimization (reduce host memory use for guest context)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_context_elimination =
-
-# Enable constant propagation CPU optimization (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_const_prop =
-
-# Enable miscellaneous CPU optimizations (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_misc_ir =
-
-# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_reduce_misalign_checks =
-
-# Enable Host MMU Emulation (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fastmem =
-
-# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fastmem_exclusives =
-
-# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_recompile_exclusives =
-
-# Enable optimization to ignore invalid memory accesses (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_ignore_memory_aborts =
-
-# Enable unfuse FMA (improve performance on CPUs without FMA)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_unfuse_fma =
-
-# Enable faster FRSQRTE and FRECPE
-# Only enabled if cpu_accuracy is set to Unsafe.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_reduce_fp_error =
-
-# Enable faster ASIMD instructions (32 bits only)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_ignore_standard_fpcr =
-
-# Enable inaccurate NaN handling
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_inaccurate_nan =
-
-# Disable address space checks (64 bits only)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_fastmem_check =
-
-# Enable faster exclusive instructions
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_ignore_global_monitor =
-
-[Renderer]
-# Which backend API to use.
-# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null
-backend =
-
-# Whether to enable asynchronous presentation (Vulkan only)
-# 0: Off, 1 (default): On
-async_presentation =
-
-# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).
-# 0 (default): Disabled, 1: Enabled
-force_max_clock =
-
-# Enable graphics API debugging mode.
-# 0 (default): Disabled, 1: Enabled
-debug =
-
-# Enable shader feedback.
-# 0 (default): Disabled, 1: Enabled
-renderer_shader_feedback =
-
-# Enable Nsight Aftermath crash dumps
-# 0 (default): Disabled, 1: Enabled
-nsight_aftermath =
-
-# Disable shader loop safety checks, executing the shader without loop logic changes
-# 0 (default): Disabled, 1: Enabled
-disable_shader_loop_safety_checks =
-
-# Which Vulkan physical device to use (defaults to 0)
-vulkan_device =
-
-# 0: 0.5x (360p/540p) [EXPERIMENTAL]
-# 1: 0.75x (540p/810p) [EXPERIMENTAL]
-# 2 (default): 1x (720p/1080p)
-# 3: 2x (1440p/2160p)
-# 4: 3x (2160p/3240p)
-# 5: 4x (2880p/4320p)
-# 6: 5x (3600p/5400p)
-# 7: 6x (4320p/6480p)
-resolution_setup =
-
-# Pixel filter to use when up- or down-sampling rendered frames.
-# 0: Nearest Neighbor
-# 1 (default): Bilinear
-# 2: Bicubic
-# 3: Gaussian
-# 4: ScaleForce
-# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only]
-scaling_filter =
-
-# Anti-Aliasing (AA)
-# 0 (default): None, 1: FXAA
-anti_aliasing =
-
-# Whether to use fullscreen or borderless window mode
-# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen
-fullscreen_mode =
-
-# Aspect ratio
-# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window
-aspect_ratio =
-
-# Anisotropic filtering
-# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
-max_anisotropy =
-
-# Whether to enable VSync or not.
-# OpenGL: Values other than 0 enable VSync
-# Vulkan: FIFO is selected if the requested mode is not supported by the driver.
-# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
-# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
-# Mailbox can have lower latency than FIFO and does not tear but may drop frames.
-# Immediate (no synchronization) just presents whatever is available and can exhibit tearing.
-# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed
-use_vsync =
-
-# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is
-# not available and GLASM is selected, GLSL will be used.
-# 0: GLSL, 1 (default): GLASM, 2: SPIR-V
-shader_backend =
-
-# Whether to allow asynchronous shader building.
-# 0 (default): Off, 1: On
-use_asynchronous_shaders =
-
-# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
-# 0 (default): Off, 1: On
-use_reactive_flushing =
-
-# NVDEC emulation.
-# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
-nvdec_emulation =
-
-# Accelerate ASTC texture decoding.
-# 0 (default): Off, 1: On
-accelerate_astc =
-
-# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value
-# 0: Off, 1: On (default)
-use_speed_limit =
-
-# Limits the speed of the game to run no faster than this value as a percentage of target speed
-# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
-speed_limit =
-
-# Whether to use disk based shader cache
-# 0: Off, 1 (default): On
-use_disk_shader_cache =
-
-# Which gpu accuracy level to use
-# 0 (default): Normal, 1: High, 2: Extreme (Very slow)
-gpu_accuracy =
-
-# Whether to use asynchronous GPU emulation
-# 0 : Off (slow), 1 (default): On (fast)
-use_asynchronous_gpu_emulation =
-
-# Inform the guest that GPU operations completed more quickly than they did.
-# 0: Off, 1 (default): On
-use_fast_gpu_time =
-
-# Force unmodified buffers to be flushed, which can cost performance.
-# 0: Off (default), 1: On
-use_pessimistic_flushes =
-
-# Whether to use garbage collection or not for GPU caches.
-# 0 (default): Off, 1: On
-use_caches_gc =
-
-# The clear color for the renderer. What shows up on the sides of the bottom screen.
-# Must be in range of 0-255. Defaults to 0 for all.
-bg_red =
-bg_blue =
-bg_green =
-
-[Audio]
-# Which audio output engine to use.
-# auto (default): Auto-select
-# cubeb: Cubeb audio engine (if available)
-# sdl2: SDL2 audio engine (if available)
-# null: No audio output
-output_engine =
-
-# Which audio device to use.
-# auto (default): Auto-select
-output_device =
-
-# Output volume.
-# 100 (default): 100%, 0; mute
-volume =
-
-[Data Storage]
-# Whether to create a virtual SD card.
-# 1: Yes, 0 (default): No
-use_virtual_sd =
-
-# Whether or not to enable gamecard emulation
-# 1: Yes, 0 (default): No
-gamecard_inserted =
-
-# Whether or not the gamecard should be emulated as the current game
-# If 'gamecard_inserted' is 0 this setting is irrelevant
-# 1: Yes, 0 (default): No
-gamecard_current_game =
-
-# Path to an XCI file to use as the gamecard
-# If 'gamecard_inserted' is 0 this setting is irrelevant
-# If 'gamecard_current_game' is 1 this setting is irrelevant
-gamecard_path =
-
-[System]
-# Whether the system is docked
-# 1 (default): Yes, 0: No
-use_docked_mode =
-
-# Sets the seed for the RNG generator built into the switch
-# rng_seed will be ignored and randomly generated if rng_seed_enabled is false
-rng_seed_enabled =
-rng_seed =
-
-# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
-# This will auto-increment, with the time set being the time the game is started
-# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
-custom_rtc_enabled =
-custom_rtc =
-
-# Sets the systems language index
-# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese,
-# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French,
-# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese
-language_index =
-
-# The system region that yuzu will use during emulation
-# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
-region_index =
-
-# The system time zone that yuzu will use during emulation
-# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone
-time_zone_index =
-
-# Sets the sound output mode.
-# 0: Mono, 1 (default): Stereo, 2: Surround
-sound_index =
-
-[Miscellaneous]
-# A filter which removes logs below a certain logging level.
-# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
-log_filter = *:Trace
-
-# Use developer keys
-# 0 (default): Disabled, 1: Enabled
-use_dev_keys =
-
-[Debugging]
-# Record frame time data, can be found in the log directory. Boolean value
-record_frame_times =
-# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
-dump_exefs=false
-# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
-dump_nso=false
-# Determines whether or not yuzu will save the filesystem access log.
-enable_fs_access_log=false
-# Enables verbose reporting services
-reporting_services =
-# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode
-# false: Retail/Normal Mode (default), true: Kiosk Mode
-quest_flag =
-# Determines whether debug asserts should be enabled, which will throw an exception on asserts.
-# false: Disabled (default), true: Enabled
-use_debug_asserts =
-# Determines whether unimplemented HLE service calls should be automatically stubbed.
-# false: Disabled (default), true: Enabled
-use_auto_stub =
-# Enables/Disables the macro JIT compiler
-disable_macro_jit=false
-# Determines whether to enable the GDB stub and wait for the debugger to attach before running.
-# false: Disabled (default), true: Enabled
-use_gdbstub=false
-# The port to use for the GDB server, if it is enabled.
-gdbstub_port=6543
-
-[WebService]
-# Whether or not to enable telemetry
-# 0: No, 1 (default): Yes
-enable_telemetry =
-# URL for Web API
-web_api_url = https://api.yuzu-emu.org
-# Username and token for yuzu Web Service
-# See https://profile.yuzu-emu.org/ for more info
-yuzu_username =
-yuzu_token =
-
-[Network]
-# Name of the network interface device to use with yuzu LAN play.
-# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo'
-# e.g. On Windows: 'Ethernet', 'Wi-Fi'
-network_interface =
-
-[AddOns]
-# Used to disable add-ons
-# List of title IDs of games that will have add-ons disabled (separated by '|'):
-title_ids =
-# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
-# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
-)";
-} // namespace DefaultINI
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 a7e414b81..c4f631924 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
@@ -9,6 +9,7 @@
#include "input_common/drivers/virtual_gamepad.h"
#include "input_common/main.h"
#include "jni/emu_window/emu_window.h"
+#include "jni/native.h"
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
m_window_width = ANativeWindow_getWidth(surface);
@@ -57,6 +58,13 @@ void EmuWindow_Android::OnRemoveNfcTag() {
m_input_subsystem->GetVirtualAmiibo()->CloseAmiibo();
}
+void EmuWindow_Android::OnFrameDisplayed() {
+ if (!m_first_frame) {
+ EmulationSession::GetInstance().OnEmulationStarted();
+ m_first_frame = true;
+ }
+}
+
EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem,
ANativeWindow* surface,
std::shared_ptr<Common::DynamicLibrary> driver_library)
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h
index b38087f73..a34a0e479 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.h
+++ b/src/android/app/src/main/jni/emu_window/emu_window.h
@@ -45,7 +45,7 @@ public:
float gyro_z, float accel_x, float accel_y, float accel_z);
void OnReadNfcTag(std::span<u8> data);
void OnRemoveNfcTag();
- void OnFrameDisplayed() override {}
+ void OnFrameDisplayed() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
return {std::make_unique<GraphicsContext_Android>(m_driver_library)};
@@ -61,4 +61,6 @@ private:
float m_window_height{};
std::shared_ptr<Common::DynamicLibrary> m_driver_library;
+
+ bool m_first_frame = false;
};
diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp
new file mode 100644
index 000000000..24d9df702
--- /dev/null
+++ b/src/android/app/src/main/jni/game_metadata.cpp
@@ -0,0 +1,112 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <core/core.h>
+#include <core/file_sys/patch_manager.h>
+#include <core/loader/nro.h>
+#include <jni.h>
+#include "core/loader/loader.h"
+#include "jni/android_common/android_common.h"
+#include "native.h"
+
+struct RomMetadata {
+ std::string title;
+ u64 programId;
+ std::string developer;
+ std::string version;
+ std::vector<u8> icon;
+ bool isHomebrew;
+};
+
+std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
+
+RomMetadata CacheRomMetadata(const std::string& path) {
+ const auto file =
+ Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
+ auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
+
+ RomMetadata entry;
+ loader->ReadTitle(entry.title);
+ loader->ReadProgramId(entry.programId);
+ loader->ReadIcon(entry.icon);
+
+ const FileSys::PatchManager pm{
+ entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
+ EmulationSession::GetInstance().System().GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+
+ if (control.first != nullptr) {
+ entry.developer = control.first->GetDeveloperName();
+ entry.version = control.first->GetVersionString();
+ } else {
+ FileSys::NACP nacp;
+ if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
+ entry.developer = nacp.GetDeveloperName();
+ } else {
+ entry.developer = "";
+ }
+
+ entry.version = "1.0.0";
+ }
+
+ if (loader->GetFileType() == Loader::FileType::NRO) {
+ auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
+ entry.isHomebrew = loader_nro->IsHomebrew();
+ } else {
+ entry.isHomebrew = false;
+ }
+
+ m_rom_metadata_cache[path] = entry;
+
+ return entry;
+}
+
+RomMetadata GetRomMetadata(const std::string& path) {
+ if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
+ return search->second;
+ }
+
+ return CacheRomMetadata(path);
+}
+
+extern "C" {
+
+jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return ToJString(env, GetRomMetadata(GetJString(env, jpath)).title);
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return ToJString(env, std::to_string(GetRomMetadata(GetJString(env, jpath)).programId));
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return ToJString(env, GetRomMetadata(GetJString(env, jpath)).developer);
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return ToJString(env, GetRomMetadata(GetJString(env, jpath)).version);
+}
+
+jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ auto icon_data = GetRomMetadata(GetJString(env, jpath)).icon;
+ jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
+ env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
+ reinterpret_cast<jbyte*>(icon_data.data()));
+ return icon;
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return static_cast<jboolean>(GetRomMetadata(GetJString(env, jpath)).isHomebrew);
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
+ return m_rom_metadata_cache.clear();
+}
+
+} // extern "C"
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index 960abf95a..a56ed5662 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -13,6 +13,8 @@ static JavaVM* s_java_vm;
static jclass s_native_library_class;
static jclass s_disk_cache_progress_class;
static jclass s_load_callback_stage_class;
+static jclass s_game_dir_class;
+static jmethodID s_game_dir_constructor;
static jmethodID s_exit_emulation_activity;
static jmethodID s_disk_cache_load_progress;
static jmethodID s_on_emulation_started;
@@ -53,6 +55,14 @@ jclass GetDiskCacheLoadCallbackStageClass() {
return s_load_callback_stage_class;
}
+jclass GetGameDirClass() {
+ return s_game_dir_class;
+}
+
+jmethodID GetGameDirConstructor() {
+ return s_game_dir_constructor;
+}
+
jmethodID GetExitEmulationActivity() {
return s_exit_emulation_activity;
}
@@ -90,6 +100,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
"org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
+ const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir");
+ s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class));
+ s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>", "(Ljava/lang/String;Z)V");
+ env->DeleteLocalRef(game_dir_class);
+
// Initialize methods
s_exit_emulation_activity =
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
@@ -120,6 +135,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
env->DeleteGlobalRef(s_native_library_class);
env->DeleteGlobalRef(s_disk_cache_progress_class);
env->DeleteGlobalRef(s_load_callback_stage_class);
+ env->DeleteGlobalRef(s_game_dir_class);
// UnInitialize applets
SoftwareKeyboard::CleanupJNI(env);
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
index b76158928..855649efa 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -13,6 +13,8 @@ JNIEnv* GetEnvForThread();
jclass GetNativeLibraryClass();
jclass GetDiskCacheProgressClass();
jclass GetDiskCacheLoadCallbackStageClass();
+jclass GetGameDirClass();
+jmethodID GetGameDirConstructor();
jmethodID GetExitEmulationActivity();
jmethodID GetDiskCacheLoadProgress();
jmethodID GetOnEmulationStarted();
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 598f4e8bf..ed5ce6f8a 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -33,7 +33,6 @@
#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"
#include "core/file_sys/vfs_real.h"
@@ -48,520 +47,421 @@
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
-#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
-#include "core/perf_stats.h"
+#include "frontend_common/config.h"
#include "jni/android_common/android_common.h"
-#include "jni/applets/software_keyboard.h"
-#include "jni/config.h"
-#include "jni/emu_window/emu_window.h"
#include "jni/id_cache.h"
-#include "video_core/rasterizer_interface.h"
+#include "jni/native.h"
#include "video_core/renderer_base.h"
#define jconst [[maybe_unused]] const auto
#define jauto [[maybe_unused]] auto
-namespace {
+static EmulationSession s_instance;
-class EmulationSession final {
-public:
- EmulationSession() {
- m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
- }
-
- ~EmulationSession() = default;
-
- static EmulationSession& GetInstance() {
- return s_instance;
- }
-
- const Core::System& System() const {
- return m_system;
- }
+EmulationSession::EmulationSession() {
+ m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
+}
- Core::System& System() {
- return m_system;
- }
+EmulationSession& EmulationSession::GetInstance() {
+ return s_instance;
+}
- const EmuWindow_Android& Window() const {
- return *m_window;
- }
+const Core::System& EmulationSession::System() const {
+ return m_system;
+}
- EmuWindow_Android& Window() {
- return *m_window;
- }
+Core::System& EmulationSession::System() {
+ return m_system;
+}
- ANativeWindow* NativeWindow() const {
- return m_native_window;
- }
+const EmuWindow_Android& EmulationSession::Window() const {
+ return *m_window;
+}
- void SetNativeWindow(ANativeWindow* native_window) {
- m_native_window = native_window;
- }
+EmuWindow_Android& EmulationSession::Window() {
+ return *m_window;
+}
- 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) {
- return false;
- }
- if (!dest->Resize(src->GetSize())) {
- return false;
- }
+ANativeWindow* EmulationSession::NativeWindow() const {
+ return m_native_window;
+}
- using namespace Common::Literals;
- [[maybe_unused]] std::vector<u8> buffer(1_MiB);
+void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
+ m_native_window = native_window;
+}
- for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
- jconst read = src->Read(buffer.data(), buffer.size(), i);
- dest->Write(buffer.data(), read, i);
- }
- return true;
- };
-
- enum InstallResult {
- Success = 0,
- SuccessFileOverwritten = 1,
- InstallError = 2,
- ErrorBaseGame = 3,
- ErrorFilenameExtension = 4,
- };
-
- m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
- m_system.GetFileSystemController().CreateFactories(*m_vfs);
-
- [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
- if (file_extension == "nsp") {
- nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
- if (nsp->IsExtractedType()) {
- return InstallError;
- }
- } else {
- return ErrorFilenameExtension;
+int EmulationSession::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) {
+ return false;
}
-
- if (!nsp) {
- return InstallError;
+ if (!dest->Resize(src->GetSize())) {
+ return false;
}
- if (nsp->GetStatus() != Loader::ResultStatus::Success) {
- return InstallError;
- }
+ using namespace Common::Literals;
+ [[maybe_unused]] std::vector<u8> buffer(1_MiB);
- jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
- *nsp, true, copy_func);
-
- switch (res) {
- case FileSys::InstallResult::Success:
- return Success;
- case FileSys::InstallResult::OverwriteExisting:
- return SuccessFileOverwritten;
- case FileSys::InstallResult::ErrorBaseInstall:
- return ErrorBaseGame;
- default:
- return InstallError;
+ for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
+ jconst read = src->Read(buffer.data(), buffer.size(), i);
+ dest->Write(buffer.data(), read, i);
}
- }
+ return true;
+ };
- void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
- const std::string& custom_driver_name,
- const std::string& file_redirect_dir) {
-#ifdef ARCHITECTURE_arm64
- void* handle{};
- const char* file_redirect_dir_{};
- int featureFlags{};
-
- // Enable driver file redirection when renderer debugging is enabled.
- if (Settings::values.renderer_debug && file_redirect_dir.size()) {
- featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
- file_redirect_dir_ = file_redirect_dir.c_str();
- }
+ enum InstallResult {
+ Success = 0,
+ SuccessFileOverwritten = 1,
+ InstallError = 2,
+ ErrorBaseGame = 3,
+ ErrorFilenameExtension = 4,
+ };
- // Try to load a custom driver.
- if (custom_driver_name.size()) {
- handle = adrenotools_open_libvulkan(
- RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
- custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
- }
+ m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
+ m_system.GetFileSystemController().CreateFactories(*m_vfs);
- // Try to load the system driver.
- if (!handle) {
- handle =
- adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
- nullptr, nullptr, file_redirect_dir_, nullptr);
+ [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
+ if (file_extension == "nsp") {
+ nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
+ if (nsp->IsExtractedType()) {
+ return InstallError;
}
-
- m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle);
-#endif
+ } else {
+ return ErrorFilenameExtension;
}
- bool IsRunning() const {
- return m_is_running;
+ if (!nsp) {
+ return InstallError;
}
- bool IsPaused() const {
- return m_is_running && m_is_paused;
+ if (nsp->GetStatus() != Loader::ResultStatus::Success) {
+ return InstallError;
}
- const Core::PerfStatsResults& PerfStats() const {
- std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
- return m_perf_stats;
- }
+ jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true,
+ copy_func);
- void SurfaceChanged() {
- if (!IsRunning()) {
- return;
- }
- m_window->OnSurfaceChanged(m_native_window);
+ switch (res) {
+ case FileSys::InstallResult::Success:
+ return Success;
+ case FileSys::InstallResult::OverwriteExisting:
+ return SuccessFileOverwritten;
+ case FileSys::InstallResult::ErrorBaseInstall:
+ return ErrorBaseGame;
+ default:
+ return InstallError;
}
+}
- 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;
- }
+void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
+ const std::string& custom_driver_dir,
+ const std::string& custom_driver_name,
+ const std::string& file_redirect_dir) {
+#ifdef ARCHITECTURE_arm64
+ void* handle{};
+ const char* file_redirect_dir_{};
+ int featureFlags{};
- 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());
- }
- }
- }
+ // Enable driver file redirection when renderer debugging is enabled.
+ if (Settings::values.renderer_debug && file_redirect_dir.size()) {
+ featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
+ file_redirect_dir_ = file_redirect_dir.c_str();
}
- Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
- std::scoped_lock lock(m_mutex);
-
- // 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>();
- m_software_keyboard = android_keyboard.get();
- m_system.SetShuttingDown(false);
- m_system.ApplySettings();
- Settings::LogSettings();
- m_system.HIDCore().ReloadInputDevices();
- m_system.SetAppletFrontendSet({
- nullptr, // Amiibo Settings
- nullptr, // Controller Selector
- nullptr, // Error Display
- nullptr, // Mii Editor
- nullptr, // Parental Controls
- nullptr, // Photo Viewer
- nullptr, // Profile Selector
- 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>();
-
- // Load the ROM.
- m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
- if (m_load_result != Core::SystemResultStatus::Success) {
- return m_load_result;
- }
-
- // Complete initialization.
- m_system.GPU().Start();
- m_system.GetCpuManager().OnGpuReady();
- m_system.RegisterExitCallback([&] { HaltEmulation(); });
+ // Try to load a custom driver.
+ if (custom_driver_name.size()) {
+ handle = adrenotools_open_libvulkan(
+ RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
+ custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
+ }
- return Core::SystemResultStatus::Success;
+ // Try to load the system driver.
+ if (!handle) {
+ handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
+ nullptr, nullptr, file_redirect_dir_, nullptr);
}
- void ShutdownEmulation() {
- std::scoped_lock lock(m_mutex);
+ m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle);
+#endif
+}
- m_is_running = false;
+bool EmulationSession::IsRunning() const {
+ return m_is_running;
+}
- // Unload user input.
- m_system.HIDCore().UnloadInputDevices();
+bool EmulationSession::IsPaused() const {
+ return m_is_running && m_is_paused;
+}
- // Shutdown the main emulated process
- if (m_load_result == Core::SystemResultStatus::Success) {
- m_system.DetachDebugger();
- m_system.ShutdownMainProcess();
- m_detached_tasks.WaitForAllTasks();
- m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
- m_window.reset();
- OnEmulationStopped(Core::SystemResultStatus::Success);
- return;
- }
+const Core::PerfStatsResults& EmulationSession::PerfStats() {
+ m_perf_stats = m_system.GetAndResetPerfStats();
+ return m_perf_stats;
+}
- // Tear down the render window.
- m_window.reset();
+void EmulationSession::SurfaceChanged() {
+ if (!IsRunning()) {
+ return;
}
+ m_window->OnSurfaceChanged(m_native_window);
+}
- void PauseEmulation() {
- std::scoped_lock lock(m_mutex);
- m_system.Pause();
- m_is_paused = true;
+void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) {
+ const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
+ if (!file) {
+ return;
}
- void UnPauseEmulation() {
- std::scoped_lock lock(m_mutex);
- m_system.Run();
- m_is_paused = false;
+ auto loader = Loader::GetLoader(m_system, file);
+ if (!loader) {
+ return;
}
- void HaltEmulation() {
- std::scoped_lock lock(m_mutex);
- m_is_running = false;
- m_cv.notify_one();
+ const auto file_type = loader->GetFileType();
+ if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
+ return;
}
- void RunEmulation() {
- {
- std::scoped_lock lock(m_mutex);
- m_is_running = true;
- }
-
- // Load the disk shader cache.
- if (Settings::values.use_disk_shader_cache.GetValue()) {
- LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
- m_system.Renderer().ReadRasterizer()->LoadDiskResources(
- m_system.GetApplicationProcessProgramID(), std::stop_token{},
- LoadDiskCacheProgress);
- LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
+ 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());
+ }
}
+ }
+}
- void(m_system.Run());
-
- if (m_system.DebuggerEnabled()) {
- m_system.InitializeDebugger();
- }
+void EmulationSession::InitializeSystem(bool reload) {
+ if (!reload) {
+ // Initialize logging system
+ Common::Log::Initialize();
+ Common::Log::SetColorConsoleBackendEnabled(true);
+ Common::Log::Start();
+ }
+
+ // Initialize filesystem.
+ m_system.SetFilesystem(m_vfs);
+ m_system.GetUserChannel().clear();
+ 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);
+}
+
+Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) {
+ std::scoped_lock lock(m_mutex);
+
+ // Create the render window.
+ m_window =
+ std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library);
+
+ // Initialize system.
+ jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
+ m_software_keyboard = android_keyboard.get();
+ m_system.SetShuttingDown(false);
+ m_system.ApplySettings();
+ Settings::LogSettings();
+ m_system.HIDCore().ReloadInputDevices();
+ m_system.SetAppletFrontendSet({
+ nullptr, // Amiibo Settings
+ nullptr, // Controller Selector
+ nullptr, // Error Display
+ nullptr, // Mii Editor
+ nullptr, // Parental Controls
+ nullptr, // Photo Viewer
+ nullptr, // Profile Selector
+ std::move(android_keyboard), // Software Keyboard
+ nullptr, // Web Browser
+ });
+
+ // Initialize filesystem.
+ ConfigureFilesystemProvider(filepath);
+
+ // Initialize account manager
+ m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
+
+ // Load the ROM.
+ m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
+ if (m_load_result != Core::SystemResultStatus::Success) {
+ return m_load_result;
+ }
+
+ // Complete initialization.
+ m_system.GPU().Start();
+ m_system.GetCpuManager().OnGpuReady();
+ m_system.RegisterExitCallback([&] { HaltEmulation(); });
- OnEmulationStarted();
+ return Core::SystemResultStatus::Success;
+}
- while (true) {
- {
- [[maybe_unused]] std::unique_lock lock(m_mutex);
- if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
- [&]() { return !m_is_running; })) {
- // Emulation halted.
- break;
- }
- }
- {
- // Refresh performance stats.
- std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
- m_perf_stats = m_system.GetAndResetPerfStats();
- }
- }
- }
+void EmulationSession::ShutdownEmulation() {
+ std::scoped_lock lock(m_mutex);
- std::string GetRomTitle(const std::string& path) {
- return GetRomMetadata(path).title;
- }
+ m_is_running = false;
- std::vector<u8> GetRomIcon(const std::string& path) {
- return GetRomMetadata(path).icon;
- }
+ // Unload user input.
+ m_system.HIDCore().UnloadInputDevices();
- bool GetIsHomebrew(const std::string& path) {
- return GetRomMetadata(path).isHomebrew;
+ // Shutdown the main emulated process
+ if (m_load_result == Core::SystemResultStatus::Success) {
+ m_system.DetachDebugger();
+ m_system.ShutdownMainProcess();
+ m_detached_tasks.WaitForAllTasks();
+ m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
+ m_window.reset();
+ OnEmulationStopped(Core::SystemResultStatus::Success);
+ return;
}
- void ResetRomMetadata() {
- m_rom_metadata_cache.clear();
- }
+ // Tear down the render window.
+ m_window.reset();
+}
- bool IsHandheldOnly() {
- jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
+void EmulationSession::PauseEmulation() {
+ std::scoped_lock lock(m_mutex);
+ m_system.Pause();
+ m_is_paused = true;
+}
- if (npad_style_set.fullkey == 1) {
- return false;
- }
+void EmulationSession::UnPauseEmulation() {
+ std::scoped_lock lock(m_mutex);
+ m_system.Run();
+ m_is_paused = false;
+}
- if (npad_style_set.handheld == 0) {
- return false;
- }
+void EmulationSession::HaltEmulation() {
+ std::scoped_lock lock(m_mutex);
+ m_is_running = false;
+ m_cv.notify_one();
+}
- return !Settings::IsDockedMode();
+void EmulationSession::RunEmulation() {
+ {
+ std::scoped_lock lock(m_mutex);
+ m_is_running = true;
}
- void SetDeviceType([[maybe_unused]] int index, int type) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
- controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
+ // Load the disk shader cache.
+ if (Settings::values.use_disk_shader_cache.GetValue()) {
+ LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
+ m_system.Renderer().ReadRasterizer()->LoadDiskResources(
+ m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress);
+ LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
}
- void OnGamepadConnectEvent([[maybe_unused]] int index) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+ void(m_system.Run());
- // Ensure that player1 is configured correctly and handheld disconnected
- if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
- jauto handheld =
- m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
-
- if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
- handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
- controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
- handheld->Disconnect();
- }
- }
-
- // Ensure that handheld is configured correctly and player 1 disconnected
- if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
- jauto player1 =
- m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+ if (m_system.DebuggerEnabled()) {
+ m_system.InitializeDebugger();
+ }
- if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
- player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
- controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
- player1->Disconnect();
+ while (true) {
+ {
+ [[maybe_unused]] std::unique_lock lock(m_mutex);
+ if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
+ [&]() { return !m_is_running; })) {
+ // Emulation halted.
+ break;
}
}
-
- if (!controller->IsConnected()) {
- controller->Connect();
- }
}
+}
+
+bool EmulationSession::IsHandheldOnly() {
+ jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
- void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
- controller->Disconnect();
+ if (npad_style_set.fullkey == 1) {
+ return false;
}
- SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() {
- return m_software_keyboard;
+ if (npad_style_set.handheld == 0) {
+ return false;
}
-private:
- struct RomMetadata {
- std::string title;
- std::vector<u8> icon;
- bool isHomebrew;
- };
+ return !Settings::IsDockedMode();
+}
- RomMetadata GetRomMetadata(const std::string& path) {
- if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
- return search->second;
- }
+void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) {
+ jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+ controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
+}
- return CacheRomMetadata(path);
- }
+void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
+ jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
- RomMetadata CacheRomMetadata(const std::string& path) {
- jconst file = Core::GetGameFileFromPath(m_vfs, path);
- jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
+ // Ensure that player1 is configured correctly and handheld disconnected
+ if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
+ jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
- RomMetadata entry;
- loader->ReadTitle(entry.title);
- loader->ReadIcon(entry.icon);
- if (loader->GetFileType() == Loader::FileType::NRO) {
- jauto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
- entry.isHomebrew = loader_nro->IsHomebrew();
- } else {
- entry.isHomebrew = false;
+ if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
+ handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+ controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+ handheld->Disconnect();
}
-
- m_rom_metadata_cache[path] = entry;
-
- return entry;
}
-private:
- static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
- JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
- IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
- static_cast<jint>(progress), static_cast<jint>(max));
- }
+ // Ensure that handheld is configured correctly and player 1 disconnected
+ if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
+ jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
- static void OnEmulationStarted() {
- JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
- IDCache::GetOnEmulationStarted());
+ if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
+ player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
+ controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
+ player1->Disconnect();
+ }
}
- static void OnEmulationStopped(Core::SystemResultStatus result) {
- JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
- IDCache::GetOnEmulationStopped(), static_cast<jint>(result));
+ if (!controller->IsConnected()) {
+ controller->Connect();
}
+}
-private:
- static EmulationSession s_instance;
-
- // Frontend management
- std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
-
- // Window management
- std::unique_ptr<EmuWindow_Android> m_window;
- ANativeWindow* m_native_window{};
-
- // Core emulation
- Core::System m_system;
- InputCommon::InputSubsystem m_input_subsystem;
- Common::DetachedTasks m_detached_tasks;
- Core::PerfStatsResults m_perf_stats{};
- std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
- Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
- 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;
+void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) {
+ jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+ controller->Disconnect();
+}
- // GPU driver parameters
- std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
+SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
+ return m_software_keyboard;
+}
- // Synchronization
- std::condition_variable_any m_cv;
- mutable std::mutex m_perf_stats_mutex;
- mutable std::mutex m_mutex;
-};
+void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress,
+ int max) {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
+ IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
+ static_cast<jint>(progress), static_cast<jint>(max));
+}
-/*static*/ EmulationSession EmulationSession::s_instance;
+void EmulationSession::OnEmulationStarted() {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStarted());
+}
-} // Anonymous namespace
+void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStopped(),
+ static_cast<jint>(result));
+}
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
- Common::Log::Initialize();
- Common::Log::SetColorConsoleBackendEnabled(true);
- Common::Log::Start();
-
MicroProfileOnThreadCreate("EmuThread");
SCOPE_EXIT({ MicroProfileShutdown(); });
@@ -657,10 +557,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass cla
EmulationSession::GetInstance().HaltEmulation();
}
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
- EmulationSession::GetInstance().ResetRomMetadata();
-}
-
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
}
@@ -766,51 +662,13 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
}
}
-jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
- jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
- env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
- reinterpret_cast<jbyte*>(icon_data.data()));
- return icon;
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
- return env->NewStringUTF(title.c_str());
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return j_filename;
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return j_filename;
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return env->NewStringUTF("");
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return env->NewStringUTF("");
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
- // Create the default config.ini.
- Config{};
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
+ jboolean reload) {
// Initialize the emulated system.
- EmulationSession::GetInstance().System().Initialize();
+ if (!reload) {
+ EmulationSession::GetInstance().System().Initialize();
+ }
+ EmulationSession::GetInstance().InitializeSystem(reload);
}
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) {
@@ -820,17 +678,6 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass cl
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
- Config{};
-}
-
-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);
-
- env->ReleaseStringUTFChars(j_game_id, game_id.data());
-}
-
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
jdoubleArray j_stats = env->NewDoubleArray(4);
@@ -847,6 +694,14 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jcl
return j_stats;
}
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass clazz) {
+ if (Settings::IsNceEnabled()) {
+ return ToJString(env, "NCE");
+ }
+
+ return ToJString(env, "JIT");
+}
+
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env,
jclass clazz,
jstring j_path) {}
@@ -898,4 +753,49 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
}
}
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletLaunchPath(JNIEnv* env, jclass clazz,
+ jlong jid) {
+ auto bis_system =
+ EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ return ToJString(env, "");
+ }
+
+ auto applet_nca =
+ bis_system->GetEntry(static_cast<u64>(jid), FileSys::ContentRecordType::Program);
+ if (!applet_nca) {
+ return ToJString(env, "");
+ }
+
+ return ToJString(env, applet_nca->GetFullPath());
+}
+
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCurrentAppletId(JNIEnv* env, jclass clazz,
+ jint jappletId) {
+ EmulationSession::GetInstance().System().GetAppletManager().SetCurrentAppletId(
+ static_cast<Service::AM::Applets::AppletId>(jappletId));
+}
+
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_setCabinetMode(JNIEnv* env, jclass clazz,
+ jint jcabinetMode) {
+ EmulationSession::GetInstance().System().GetAppletManager().SetCabinetMode(
+ static_cast<Service::NFP::CabinetMode>(jcabinetMode));
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, jclass clazz) {
+ auto bis_system =
+ EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ return false;
+ }
+
+ // Query an applet to see if it's available
+ auto applet_nca =
+ bis_system->GetEntry(0x010000000000100Dull, FileSys::ContentRecordType::Program);
+ if (!applet_nca) {
+ return false;
+ }
+ return true;
+}
+
} // extern "C"
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
new file mode 100644
index 000000000..78ef96802
--- /dev/null
+++ b/src/android/app/src/main/jni/native.h
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <android/native_window_jni.h>
+#include "common/detached_tasks.h"
+#include "core/core.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/perf_stats.h"
+#include "jni/applets/software_keyboard.h"
+#include "jni/emu_window/emu_window.h"
+#include "video_core/rasterizer_interface.h"
+
+#pragma once
+
+class EmulationSession final {
+public:
+ explicit EmulationSession();
+ ~EmulationSession() = default;
+
+ static EmulationSession& GetInstance();
+ const Core::System& System() const;
+ Core::System& System();
+
+ const EmuWindow_Android& Window() const;
+ EmuWindow_Android& Window();
+ ANativeWindow* NativeWindow() const;
+ void SetNativeWindow(ANativeWindow* native_window);
+ void SurfaceChanged();
+
+ int InstallFileToNand(std::string filename, std::string file_extension);
+ void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
+ const std::string& custom_driver_name,
+ const std::string& file_redirect_dir);
+
+ bool IsRunning() const;
+ bool IsPaused() const;
+ void PauseEmulation();
+ void UnPauseEmulation();
+ void HaltEmulation();
+ void RunEmulation();
+ void ShutdownEmulation();
+
+ const Core::PerfStatsResults& PerfStats();
+ void ConfigureFilesystemProvider(const std::string& filepath);
+ void InitializeSystem(bool reload);
+ Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
+
+ bool IsHandheldOnly();
+ void SetDeviceType([[maybe_unused]] int index, int type);
+ void OnGamepadConnectEvent([[maybe_unused]] int index);
+ void OnGamepadDisconnectEvent([[maybe_unused]] int index);
+ SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
+
+ static void OnEmulationStarted();
+
+private:
+ static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
+ static void OnEmulationStopped(Core::SystemResultStatus result);
+
+private:
+ // Window management
+ std::unique_ptr<EmuWindow_Android> m_window;
+ ANativeWindow* m_native_window{};
+
+ // Core emulation
+ Core::System m_system;
+ InputCommon::InputSubsystem m_input_subsystem;
+ Common::DetachedTasks m_detached_tasks;
+ Core::PerfStatsResults m_perf_stats{};
+ std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
+ Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
+ 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;
+
+ // Synchronization
+ std::condition_variable_any m_cv;
+ mutable std::mutex m_mutex;
+};
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 8a704960c..763b2164c 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -5,11 +5,15 @@
#include <jni.h>
+#include "android_config.h"
+#include "android_settings.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "frontend_common/config.h"
#include "jni/android_common/android_common.h"
-#include "jni/config.h"
-#include "uisettings.h"
+#include "jni/id_cache.h"
+
+std::unique_ptr<AndroidConfig> config;
template <typename T>
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
@@ -28,6 +32,22 @@ Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
extern "C" {
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeConfig(JNIEnv* env, jobject obj) {
+ config = std::make_unique<AndroidConfig>();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadConfig(JNIEnv* env, jobject obj) {
+ config.reset();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadSettings(JNIEnv* env, jobject obj) {
+ config->AndroidConfig::ReloadAllValues();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveSettings(JNIEnv* env, jobject obj) {
+ config->AndroidConfig::SaveAllValues();
+}
+
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
jstring jkey, jboolean getDefault) {
auto setting = getSetting<bool>(env, jkey);
@@ -234,4 +254,55 @@ jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* e
return ToJString(env, setting->PairedSetting()->GetLabel());
}
+jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getGameDirs(JNIEnv* env, jobject obj) {
+ jclass gameDirClass = IDCache::GetGameDirClass();
+ jmethodID gameDirConstructor = IDCache::GetGameDirConstructor();
+ jobjectArray jgameDirArray =
+ env->NewObjectArray(AndroidSettings::values.game_dirs.size(), gameDirClass, nullptr);
+ for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
+ jobject jgameDir =
+ env->NewObject(gameDirClass, gameDirConstructor,
+ ToJString(env, AndroidSettings::values.game_dirs[i].path),
+ static_cast<jboolean>(AndroidSettings::values.game_dirs[i].deep_scan));
+ env->SetObjectArrayElement(jgameDirArray, i, jgameDir);
+ }
+ return jgameDirArray;
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setGameDirs(JNIEnv* env, jobject obj,
+ jobjectArray gameDirs) {
+ AndroidSettings::values.game_dirs.clear();
+ int size = env->GetArrayLength(gameDirs);
+
+ if (size == 0) {
+ return;
+ }
+
+ jobject dir = env->GetObjectArrayElement(gameDirs, 0);
+ jclass gameDirClass = IDCache::GetGameDirClass();
+ jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;");
+ jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z");
+ for (int i = 0; i < size; ++i) {
+ dir = env->GetObjectArrayElement(gameDirs, i);
+ jstring juriString = static_cast<jstring>(env->GetObjectField(dir, uriStringField));
+ jboolean jdeepScanBoolean = env->GetBooleanField(dir, deepScanBooleanField);
+ std::string uriString = GetJString(env, juriString);
+ AndroidSettings::values.game_dirs.push_back(
+ AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)});
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject obj,
+ jobject gameDir) {
+ jclass gameDirClass = IDCache::GetGameDirClass();
+ jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;");
+ jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z");
+
+ jstring juriString = static_cast<jstring>(env->GetObjectField(gameDir, uriStringField));
+ jboolean jdeepScanBoolean = env->GetBooleanField(gameDir, deepScanBooleanField);
+ std::string uriString = GetJString(env, juriString);
+ AndroidSettings::values.game_dirs.push_back(
+ AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)});
+}
+
} // extern "C"
diff --git a/src/android/app/src/main/jni/native_log.cpp b/src/android/app/src/main/jni/native_log.cpp
new file mode 100644
index 000000000..33d691dc8
--- /dev/null
+++ b/src/android/app/src/main/jni/native_log.cpp
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <common/logging/log.h>
+#include <jni.h>
+
+#include "android_common/android_common.h"
+
+extern "C" {
+
+void Java_org_yuzu_yuzu_1emu_utils_Log_debug(JNIEnv* env, jobject obj, jstring jmessage) {
+ LOG_DEBUG(Frontend, "{}", GetJString(env, jmessage));
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_Log_warning(JNIEnv* env, jobject obj, jstring jmessage) {
+ LOG_WARNING(Frontend, "{}", GetJString(env, jmessage));
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_Log_info(JNIEnv* env, jobject obj, jstring jmessage) {
+ LOG_INFO(Frontend, "{}", GetJString(env, jmessage));
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_Log_error(JNIEnv* env, jobject obj, jstring jmessage) {
+ LOG_ERROR(Frontend, "{}", GetJString(env, jmessage));
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_Log_critical(JNIEnv* env, jobject obj, jstring jmessage) {
+ LOG_CRITICAL(Frontend, "{}", GetJString(env, jmessage));
+}
+
+} // extern "C"
diff --git a/src/android/app/src/main/res/drawable/ic_album.xml b/src/android/app/src/main/res/drawable/ic_album.xml
new file mode 100644
index 000000000..f2b63813f
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_album.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="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_applet.xml b/src/android/app/src/main/res/drawable/ic_applet.xml
new file mode 100644
index 000000000..b154e6f56
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_applet.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="M17,16l-4,-4V8.82C14.16,8.4 15,7.3 15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6c0,1.3 0.84,2.4 2,2.82V12l-4,4H3v5h5v-3.05l4,-4.2 4,4.2V21h5v-5h-4z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_audio.xml b/src/android/app/src/main/res/drawable/ic_audio.xml
new file mode 100644
index 000000000..e306c3b0c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_audio.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_code.xml b/src/android/app/src/main/res/drawable/ic_code.xml
new file mode 100644
index 000000000..26f83b39b
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_code.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M320,720 L80,480l240,-240 57,57 -184,184 183,183 -56,56ZM640,720 L583,663 767,479 584,296 640,240 880,480 640,720Z"/>
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_edit.xml b/src/android/app/src/main/res/drawable/ic_edit.xml
new file mode 100644
index 000000000..ac22ce8a5
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_edit.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="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_graphics.xml b/src/android/app/src/main/res/drawable/ic_graphics.xml
new file mode 100644
index 000000000..2fdb5a4d6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_graphics.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M160,840q-33,0 -56.5,-23.5T80,760v-560q0,-33 23.5,-56.5T160,120h560q33,0 56.5,23.5T800,200v80h80v80h-80v80h80v80h-80v80h80v80h-80v80q0,33 -23.5,56.5T720,840L160,840ZM160,760h560v-560L160,200v560ZM240,680h200v-160L240,520v160ZM480,400h160v-120L480,280v120ZM240,480h200v-200L240,280v200ZM480,680h160v-240L480,440v240ZM160,200v560,-560Z"/>
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_mii.xml b/src/android/app/src/main/res/drawable/ic_mii.xml
new file mode 100644
index 000000000..1271ec401
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_mii.xml
@@ -0,0 +1,18 @@
+<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,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0" />
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M20.77,8.58l-0.92,2.01c0.09,0.46 0.15,0.93 0.15,1.41 0,4.41 -3.59,8 -8,8s-8,-3.59 -8,-8c0,-0.05 0.01,-0.1 0,-0.14 2.6,-0.98 4.69,-2.99 5.74,-5.55C11.58,8.56 14.37,10 17.5,10c0.45,0 0.89,-0.04 1.33,-0.1l-0.6,-1.32 -0.88,-1.93 -1.93,-0.88 -2.79,-1.27 2.79,-1.27 0.71,-0.32C14.87,2.33 13.47,2 12,2 6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10c0,-1.47 -0.33,-2.87 -0.9,-4.13l-0.33,0.71z" />
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M15,13m-1.25,0a1.25,1.25 0,1 1,2.5 0a1.25,1.25 0,1 1,-2.5 0" />
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M20.6,5.6L19.5,8l-1.1,-2.4L16,4.5l2.4,-1.1L19.5,1l1.1,2.4L23,4.5z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_refresh.xml b/src/android/app/src/main/res/drawable/ic_refresh.xml
new file mode 100644
index 000000000..d0d87ecc2
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_refresh.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="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_restore.xml b/src/android/app/src/main/res/drawable/ic_restore.xml
new file mode 100644
index 000000000..d6d9d4017
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_restore.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="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_system_settings.xml b/src/android/app/src/main/res/drawable/ic_system_settings.xml
new file mode 100644
index 000000000..7701a2bab
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_system_settings.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M320,960q-17,0 -28.5,-11.5T280,920q0,-17 11.5,-28.5T320,880q17,0 28.5,11.5T360,920q0,17 -11.5,28.5T320,960ZM480,960q-17,0 -28.5,-11.5T440,920q0,-17 11.5,-28.5T480,880q17,0 28.5,11.5T520,920q0,17 -11.5,28.5T480,960ZM640,960q-17,0 -28.5,-11.5T600,920q0,-17 11.5,-28.5T640,880q17,0 28.5,11.5T680,920q0,17 -11.5,28.5T640,960ZM320,800q-33,0 -56.5,-23.5T240,720v-640q0,-33 23.5,-56.5T320,0h320q33,0 56.5,23.5T720,80v640q0,33 -23.5,56.5T640,800L320,800ZM320,720h320v-40L320,680v40ZM320,600h320v-400L320,200v400ZM320,120h320v-40L320,80v40ZM320,120v-40,40ZM320,720v-40,40Z"/>
+</vector>
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_about.xml b/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
new file mode 100644
index 000000000..a26ffbc73
--- /dev/null
+++ b/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
@@ -0,0 +1,233 @@
+<?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"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/coordinator_about"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_about"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_about"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:navigationIcon="@drawable/ic_back"
+ app:title="@string/about" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.core.widget.NestedScrollView
+ android:id="@+id/scroll_about"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fadeScrollbars="false"
+ android:scrollbars="vertical"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+ <LinearLayout
+ android:id="@+id/content_about"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/image_logo"
+ android:layout_width="200dp"
+ android:layout_height="200dp"
+ android:layout_gravity="center_horizontal"
+ android:padding="20dp"
+ android:src="@drawable/ic_yuzu_title" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:text="@string/about"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:text="@string/about_app_description"
+ android:textAlignment="viewStart" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:id="@+id/button_contributors"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:text="@string/contributors"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:text="@string/contributors_description"
+ android:textAlignment="viewStart" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:id="@+id/button_licenses"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:text="@string/licenses"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:text="@string/licenses_description"
+ android:textAlignment="viewStart" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:id="@+id/button_build_hash"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:text="@string/build"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_build_hash"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:textAlignment="viewStart"
+ tools:text="abc123" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="40dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="16dp"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/button_discord"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ app:icon="@drawable/ic_discord"
+ app:iconGravity="textEnd"
+ app:iconSize="24dp"
+ app:iconTint="?attr/colorOnSurface" />
+
+ <Button
+ android:id="@+id/button_website"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ app:icon="@drawable/ic_website"
+ app:iconGravity="textEnd"
+ app:iconSize="24dp"
+ app:iconTint="?attr/colorOnSurface" />
+
+ <Button
+ android:id="@+id/button_github"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ app:icon="@drawable/ic_github"
+ app:iconGravity="textEnd"
+ app:iconSize="24dp"
+ app:iconTint="?attr/colorOnSurface" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </androidx.core.widget.NestedScrollView>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/card_applet_option.xml b/src/android/app/src/main/res/layout/card_applet_option.xml
new file mode 100644
index 000000000..19fbec9f1
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_applet_option.xml
@@ -0,0 +1,57 @@
+<?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="24dp">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginEnd="20dp"
+ android:layout_gravity="center_vertical"
+ app:tint="?attr/colorOnSurface" />
+
+ <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:textAlignment="viewStart"
+ tools:text="@string/applets" />
+
+ <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:textAlignment="viewStart"
+ tools:text="@string/applets_description" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/card_folder.xml b/src/android/app/src/main/res/layout/card_folder.xml
new file mode 100644
index 000000000..4e0c04b6b
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_folder.xml
@@ -0,0 +1,70 @@
+<?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:focusable="true">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="16dp"
+ android:layout_gravity="center_vertical"
+ android:animateLayoutChanges="true">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/path"
+ style="@style/TextAppearance.Material3.BodyLarge"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/button_layout"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="@string/select_gpu_driver_default" />
+
+ <LinearLayout
+ android:id="@+id/button_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <Button
+ android:id="@+id/button_edit"
+ style="@style/Widget.Material3.Button.IconButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/delete"
+ android:tooltipText="@string/edit"
+ app:icon="@drawable/ic_edit"
+ app:iconTint="?attr/colorControlNormal" />
+
+ <Button
+ android:id="@+id/button_delete"
+ style="@style/Widget.Material3.Button.IconButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/delete"
+ android:tooltipText="@string/delete"
+ app:icon="@drawable/ic_delete"
+ app:iconTint="?attr/colorControlNormal" />
+
+ </LinearLayout>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml
index 1f5de219b..6340171ec 100644
--- a/src/android/app/src/main/res/layout/card_game.xml
+++ b/src/android/app/src/main/res/layout/card_game.xml
@@ -1,63 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
- style="?attr/materialCardViewElevatedStyle"
android:id="@+id/card_game"
+ style="?attr/materialCardViewElevatedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_gravity="center"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:clipToPadding="false"
android:focusable="true"
android:transitionName="card_game"
- android:layout_gravity="center"
- app:cardElevation="0dp"
- app:cardCornerRadius="12dp">
+ app:cardCornerRadius="4dp"
+ app:cardElevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dp">
- <com.google.android.material.card.MaterialCardView
- style="?attr/materialCardViewElevatedStyle"
- android:id="@+id/card_game_art"
+ <com.google.android.material.imageview.ShapeableImageView
+ android:id="@+id/image_game_screen"
android:layout_width="150dp"
android:layout_height="150dp"
- app:cardCornerRadius="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent">
-
- <ImageView
- android:id="@+id/image_game_screen"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:src="@drawable/default_icon" />
-
- </com.google.android.material.card.MaterialCardView>
+ app:layout_constraintTop_toTopOf="parent"
+ app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.ExtraSmall"
+ tools:src="@drawable/default_icon" />
<com.google.android.material.textview.MaterialTextView
- style="@style/TextAppearance.Material3.TitleMedium"
android:id="@+id/text_game_title"
+ style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- android:textAlignment="center"
- android:textSize="14sp"
- android:singleLine="true"
- android:marqueeRepeatLimit="marquee_forever"
android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
- app:layout_constraintEnd_toEndOf="@+id/card_game_art"
- app:layout_constraintStart_toStartOf="@+id/card_game_art"
- app:layout_constraintTop_toBottomOf="@+id/card_game_art"
+ android:singleLine="true"
+ android:textAlignment="center"
+ android:textSize="14sp"
+ app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
+ app:layout_constraintStart_toStartOf="@+id/image_game_screen"
+ app:layout_constraintTop_toBottomOf="@+id/image_game_screen"
tools:text="The Legend of Zelda: Skyward Sword" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml
index f9f1d89fb..cb667c928 100644
--- a/src/android/app/src/main/res/layout/card_home_option.xml
+++ b/src/android/app/src/main/res/layout/card_home_option.xml
@@ -6,8 +6,8 @@
android:id="@+id/option_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginVertical="12dp"
- android:layout_marginHorizontal="16dp"
+ android:layout_marginBottom="24dp"
+ android:layout_marginHorizontal="12dp"
android:background="?attr/selectableItemBackground"
android:backgroundTint="?attr/colorSurfaceVariant"
android:clickable="true"
@@ -16,7 +16,8 @@
<LinearLayout
android:id="@+id/option_layout"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/option_icon"
diff --git a/src/android/app/src/main/res/layout/dialog_add_folder.xml b/src/android/app/src/main/res/layout/dialog_add_folder.xml
new file mode 100644
index 000000000..01f95e868
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_add_folder.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ android:orientation="vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/path"
+ style="@style/TextAppearance.Material3.BodyLarge"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_gravity="center_vertical|start"
+ android:layout_weight="1"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="folder/folder/folder/folder" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="8dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_weight="1"
+ android:text="@string/deep_scan"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.checkbox.MaterialCheckBox
+ android:id="@+id/deep_scan_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_folder_properties.xml b/src/android/app/src/main/res/layout/dialog_folder_properties.xml
new file mode 100644
index 000000000..248d048cb
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_folder_properties.xml
@@ -0,0 +1,30 @@
+<?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="wrap_content"
+ android:padding="24dp"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/deep_scan_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_weight="1"
+ android:text="@string/deep_scan"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.checkbox.MaterialCheckBox
+ android:id="@+id/deep_scan_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_list.xml b/src/android/app/src/main/res/layout/dialog_list.xml
new file mode 100644
index 000000000..7de2b2c3a
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_list.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/dialog_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:fadeScrollbars="false"
+ android:paddingVertical="12dp"
+ android:scrollbars="vertical" />
+
+</androidx.appcompat.widget.LinearLayoutCompat>
diff --git a/src/android/app/src/main/res/layout/dialog_list_item.xml b/src/android/app/src/main/res/layout/dialog_list_item.xml
new file mode 100644
index 000000000..39f3558ff
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_list_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:orientation="horizontal"
+ android:paddingHorizontal="24dp"
+ android:paddingVertical="16dp">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_gravity="center"
+ tools:src="@drawable/ic_nfc" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_gravity="center_vertical|start"
+ android:textAlignment="viewStart"
+ tools:text="List option" />
+
+</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_about.xml b/src/android/app/src/main/res/layout/fragment_about.xml
index 3e1d98451..a24f5230e 100644
--- a/src/android/app/src/main/res/layout/fragment_about.xml
+++ b/src/android/app/src/main/res/layout/fragment_about.xml
@@ -38,17 +38,17 @@
<ImageView
android:id="@+id/image_logo"
- android:layout_width="250dp"
- android:layout_height="250dp"
- android:layout_marginTop="20dp"
+ android:layout_width="150dp"
+ android:layout_height="150dp"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="28dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_yuzu_title" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="20dp"
- android:layout_marginTop="28dp" />
+ android:layout_marginHorizontal="20dp" />
<LinearLayout
android:layout_width="match_parent"
diff --git a/src/android/app/src/main/res/layout/fragment_applet_launcher.xml b/src/android/app/src/main/res/layout/fragment_applet_launcher.xml
new file mode 100644
index 000000000..fe8fae40f
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_applet_launcher.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_applets"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_applets"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_applets"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:navigationIcon="@drawable/ic_back"
+ app:title="@string/applets" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list_applets"
+ 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_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml
index 750ce094a..5252adf54 100644
--- a/src/android/app/src/main/res/layout/fragment_emulation.xml
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -134,18 +134,21 @@
<FrameLayout
android:id="@+id/overlay_container"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
- <TextView
+ <com.google.android.material.textview.MaterialTextView
android:id="@+id/show_fps_text"
+ style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:clickable="false"
android:focusable="false"
- android:shadowColor="@android:color/black"
+ android:paddingHorizontal="20dp"
android:textColor="@android:color/white"
- android:textSize="12sp"
+ android:shadowColor="@android:color/black"
+ android:shadowRadius="3"
tools:ignore="RtlHardcoded" />
</FrameLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_folders.xml b/src/android/app/src/main/res/layout/fragment_folders.xml
new file mode 100644
index 000000000..74f2f3754
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_folders.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_folders"
+ 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_folders"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ app:liftOnScrollTargetViewId="@id/list_folders">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_folders"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:navigationIcon="@drawable/ic_back"
+ app:title="@string/game_folders" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list_folders"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+ </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
+ android:id="@+id/button_add"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:contentDescription="@string/add_games"
+ app:srcCompat="@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_home_settings.xml b/src/android/app/src/main/res/layout/fragment_home_settings.xml
index 1cb421dcb..d84093ba3 100644
--- a/src/android/app/src/main/res/layout/fragment_home_settings.xml
+++ b/src/android/app/src/main/res/layout/fragment_home_settings.xml
@@ -14,13 +14,14 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:background="?attr/colorSurface">
+ android:background="?attr/colorSurface"
+ android:paddingHorizontal="8dp">
<ImageView
android:id="@+id/logo_image"
- android:layout_width="128dp"
- android:layout_height="128dp"
- android:layout_margin="64dp"
+ android:layout_width="96dp"
+ android:layout_height="96dp"
+ android:layout_marginVertical="32dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_yuzu_full" />
diff --git a/src/android/app/src/main/res/layout/fragment_search.xml b/src/android/app/src/main/res/layout/fragment_search.xml
index b8d54d947..efdfd7047 100644
--- a/src/android/app/src/main/res/layout/fragment_search.xml
+++ b/src/android/app/src/main/res/layout/fragment_search.xml
@@ -127,6 +127,7 @@
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingVertical="4dp"
+ app:checkedChip="@id/chip_recently_played"
app:chipSpacingHorizontal="12dp"
app:singleLine="true"
app:singleSelection="true">
diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml
index f1037a740..544280e75 100644
--- a/src/android/app/src/main/res/layout/list_item_setting.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting.xml
@@ -10,41 +10,59 @@
android:focusable="true"
android:gravity="center_vertical"
android:minHeight="72dp"
- android:padding="@dimen/spacing_large">
+ android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:orientation="horizontal">
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_setting_name"
- style="@style/TextAppearance.Material3.HeadlineMedium"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="viewStart"
- android:textSize="16sp"
- app:lineHeight="22dp"
- tools:text="Setting Name" />
-
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_setting_description"
- style="@style/TextAppearance.Material3.BodySmall"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacing_small"
- android:textAlignment="viewStart"
- tools:text="@string/app_disclaimer" />
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="24dp"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone"
+ app:tint="?attr/colorOnSurface" />
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_setting_value"
- style="@style/TextAppearance.Material3.LabelMedium"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacing_small"
- android:textAlignment="viewStart"
- android:textStyle="bold"
- tools:text="1x" />
+ android:orientation="vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_name"
+ style="@style/TextAppearance.Material3.HeadlineMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="17sp"
+ app:lineHeight="22dp"
+ tools:text="Setting Name" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_description"
+ style="@style/TextAppearance.Material3.BodySmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ tools:text="@string/app_disclaimer" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_value"
+ style="@style/TextAppearance.Material3.LabelMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ android:textStyle="bold"
+ android:textSize="13sp"
+ tools:text="1x" />
+
+ </LinearLayout>
</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_setting_switch.xml b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
index a5767adee..a8f5aff78 100644
--- a/src/android/app/src/main/res/layout/list_item_setting_switch.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
@@ -8,9 +8,7 @@
android:clickable="true"
android:focusable="true"
android:minHeight="72dp"
- android:paddingVertical="@dimen/spacing_large"
- android:paddingStart="@dimen/spacing_large"
- android:paddingEnd="24dp">
+ android:padding="16dp">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_widget"
@@ -24,7 +22,7 @@
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
- android:layout_marginEnd="@dimen/spacing_large"
+ android:layout_marginEnd="24dp"
android:layout_toStartOf="@+id/switch_widget"
android:gravity="center_vertical"
android:orientation="vertical">
@@ -35,7 +33,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
- android:textSize="16sp"
+ android:textSize="17sp"
app:lineHeight="28dp"
tools:text="@string/frame_limit_enable" />
diff --git a/src/android/app/src/main/res/layout/list_item_settings_header.xml b/src/android/app/src/main/res/layout/list_item_settings_header.xml
index cf85bc0da..21276b19e 100644
--- a/src/android/app/src/main/res/layout/list_item_settings_header.xml
+++ b/src/android/app/src/main/res/layout/list_item_settings_header.xml
@@ -7,7 +7,8 @@
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingHorizontal="@dimen/spacing_large"
- android:paddingVertical="16dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
android:textAlignment="viewStart"
android:textColor="?attr/colorPrimary"
android:textStyle="bold"
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 82749359d..cf70b4bc4 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -25,6 +25,12 @@
<action
android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
app:destination="@id/driverManagerFragment" />
+ <action
+ android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment"
+ app:destination="@id/appletLauncherFragment" />
+ <action
+ android:id="@+id/action_homeSettingsFragment_to_gameFoldersFragment"
+ app:destination="@id/gameFoldersFragment" />
</fragment>
<fragment
@@ -102,5 +108,21 @@
android:id="@+id/driverManagerFragment"
android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
android:label="DriverManagerFragment" />
+ <fragment
+ android:id="@+id/appletLauncherFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
+ android:label="AppletLauncherFragment" >
+ <action
+ android:id="@+id/action_appletLauncherFragment_to_cabinetLauncherDialogFragment"
+ app:destination="@id/cabinetLauncherDialogFragment" />
+ </fragment>
+ <dialog
+ android:id="@+id/cabinetLauncherDialogFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment"
+ android:label="CabinetLauncherDialogFragment" />
+ <fragment
+ android:id="@+id/gameFoldersFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.GameFoldersFragment"
+ android:label="GameFoldersFragment" />
</navigation>
diff --git a/src/android/app/src/main/res/resources.properties b/src/android/app/src/main/res/resources.properties
new file mode 100644
index 000000000..467b3efec
--- /dev/null
+++ b/src/android/app/src/main/res/resources.properties
@@ -0,0 +1 @@
+unqualifiedResLocale=en-US
diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml
new file mode 100644
index 000000000..07dffffe8
--- /dev/null
+++ b/src/android/app/src/main/res/values-ar/strings.xml
@@ -0,0 +1,385 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
+
+ <string name="emulation_notification_channel_name">المحاكي نشط</string>
+ <string name="emulation_notification_channel_description">اظهار اشعار دائم عندما يكون المحاكي نشطاً</string>
+ <string name="emulation_notification_running">يوزو يعمل</string>
+ <string name="notice_notification_channel_name">الإشعارات والأخطاء</string>
+ <string name="notice_notification_channel_description">اظهار اشعار عند حصول اي مشكلة.</string>
+ <string name="notification_permission_not_granted">لم يتم منح إذن الإشعار</string>
+
+ <!-- Setup strings -->
+ <string name="welcome">مرحبًا</string>
+ <string name="welcome_description">والانتقال إلى المحاكاة <b>يوزو</b> تعرف على كيفية إعداد.</string>
+ <string name="get_started">لنبدأ</string>
+ <string name="keys">المفاتيح</string>
+ <string name="keys_description">اختر ملف &lt;b>prod.keys&lt;/b> من الزر ادناه</string>
+ <string name="select_keys">إختيار المفاتيح</string>
+ <string name="games">الألعاب</string>
+ <string name="games_description">اختر مجلد &lt;b>العابك&lt;/b> من الزر ادناه.</string>
+ <string name="done">إنهاء</string>
+ <string name="done_description">كل شيء جاهز./n استمتع بألعابك!</string>
+ <string name="text_continue">استمر</string>
+ <string name="next">التالي</string>
+ <string name="back">عودة</string>
+ <string name="add_games">إضافة ألعاب</string>
+ <string name="add_games_description">إختار مجلد ألعابك</string>
+ <string name="step_complete">مكتمل</string>
+
+ <!-- Home strings -->
+ <string name="home_games">الألعاب</string>
+ <string name="home_search">البحث</string>
+ <string name="home_settings">الإعدادات</string>
+ <string name="empty_gamelist">لم يتم العثور على ملفات او لم يتم تحديد مسار العاب.</string>
+ <string name="search_and_filter_games">بحث وتصفية الألعاب</string>
+ <string name="select_games_folder">تحديد مجلد الألعاب</string>
+ <string name="select_games_folder_description">يسمح لـ يوزو بملء قائمة الألعاب</string>
+ <string name="add_games_warning">تخطُ اختيار مجلد الالعاب؟</string>
+ <string name="add_games_warning_description">لن يتم عرض الألعاب في قائمة الألعاب إذا لم يتم تحديد مجلد</string>
+ <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
+ <string name="home_search_games">البحث عن ألعاب</string>
+ <string name="search_settings">إعدادات البحث</string>
+ <string name="games_dir_selected">تم تحديد مجلد الألعاب</string>
+ <string name="install_prod_keys">تثبيت prod.keys</string>
+ <string name="install_prod_keys_description">مطلوب لفك تشفير ألعاب البيع بالتجزئة</string>
+ <string name="install_prod_keys_warning">تخطي إضافة المفاتيح؟</string>
+ <string name="install_prod_keys_warning_description">مطلوب مفاتيح صالحة لمحاكاة ألعاب البيع بالتجزئة. ستعمل تطبيقات البيرة المنزلية فقط إذا تابعت</string>
+ <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
+ <string name="notifications">التنبيهات</string>
+ <string name="notifications_description">امنح إذن الإشعار باستخدام الزر أدناه</string>
+ <string name="give_permission">منح الإذن</string>
+ <string name="notification_warning">تخطي منح إذن الإشعارات؟</string>
+ <string name="notification_warning_description">لن يتمكن يوزو من إشعارك بالمعلومات المهمة</string>
+ <string name="permission_denied">تم رفض الإذن</string>
+ <string name="permission_denied_description">لقد رفضت هذا الإذن عدة مرات ويتعين عليك الآن منحه يدويًا في إعدادات النظام</string>
+ <string name="about">حول</string>
+ <string name="about_description">بناء الإصدار، والاعتمادات، وأكثر من ذلك</string>
+ <string name="warning_help">مساعدة</string>
+ <string name="warning_skip">تخطي</string>
+ <string name="warning_cancel">إلغاء</string>
+ <string name="install_amiibo_keys">تثبيت مفاتيح أميبو</string>
+ <string name="install_amiibo_keys_description">مطلوب لاستخدام أميبو في اللعبة</string>
+ <string name="invalid_keys_file">تم تحديد ملف مفاتيح غير صالح</string>
+ <string name="install_keys_success">تم تثبيت المفاتيح بنجاح</string>
+ <string name="reading_keys_failure">خطأ في قراءة مفاتيح التشفير</string>
+ <string name="invalid_keys_error">مفاتيح التشفير غير صالحة</string>
+ <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
+ <string name="install_keys_failure_description">الملف المحدد غير صحيح أو تالف. يرجى إعادة المفاتيح الخاصة بك</string>
+ <string name="install_gpu_driver">GPU تثبيت برنامج تشغيل</string>
+ <string name="install_gpu_driver_description">قم بتثبيت برامج تشغيل بديلة للحصول على أداء أو دقة أفضل</string>
+ <string name="advanced_settings">إعدادات متقدمة</string>
+ <string name="advanced_settings_game">إعدادات متقدمة: %1$s</string>
+ <string name="settings_description">تكوين إعدادات المحاكي</string>
+ <string name="search_recently_played">لعبت مؤخرا</string>
+ <string name="search_recently_added">أضيف مؤخرا</string>
+ <string name="search_retail">بيع بالتجزئة</string>
+ <string name="search_homebrew">البيرة المنزلية</string>
+ <string name="open_user_folder">فتح مجلد يوزو</string>
+ <string name="open_user_folder_description">إدارة ملفات يوزو الداخلية</string>
+ <string name="theme_and_color_description">تعديل مظهر التطبيق</string>
+ <string name="no_file_manager">لم يتم العثور على مدير الملفات</string>
+ <string name="notification_no_directory_link">لا يمكن فتح مجلد يوزو</string>
+ <string name="notification_no_directory_link_description">الرجاء تحديد موقع مجلد المستخدم باستخدام اللوحة الجانبية لمدير الملفات يدويًا</string>
+ <string name="manage_save_data">إدارة حفظ البيانات</string>
+ <string name="manage_save_data_description">حفظ البيانات التي تم العثور عليها. يرجى اختيار أحد الخيارات التالية</string>
+ <string name="import_export_saves_description">استيراد أو تصدير ملفات الحفظ</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>
+ <string name="import_saves">استيراد</string>
+ <string name="export_saves">تصدير</string>
+ <string name="install_firmware">تثبيت البرامج الثابتة</string>
+ <string name="firmware_installing">تثبيت البرامج الثابتة</string>
+ <string name="firmware_installed_success">تم تثبيت البرامج الثابتة بنجاح</string>
+ <string name="firmware_installed_failure">فشل تثبيت البرامج الثابتة</string>
+ <string name="share_log">مشاركة سجلات التصحيح</string>
+ <string name="share_log_description">مشاركة ملف سجل يوزو لتصحيح المشكلات</string>
+ <string name="share_log_missing">لم يتم العثور على ملف السجل</string>
+ <string name="install_game_content">تثبيت محتوى اللعبة</string>
+ <string name="install_game_content_description">DLC قم بتثبيت تحديثات اللعبة أو</string>
+ <string name="installing_game_content">جارٍ تثبيت المحتوى</string>
+ <string name="install_game_content_failure_base">لا يُسمح بتثبيت الألعاب الأساسية لتجنب التعارضات المحتملة.</string>
+ <string name="install_game_content_success_install">%1$d تم التثبيت بنجاح</string>
+ <string name="install_game_content_success_overwrite">%1$d تمت الكتابة فوقه بنجاح</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">برامج التشغيل المخصصة غير مدعومة</string>
+ <string name="custom_driver_not_supported_description">تحميل برنامج التشغيل المخصص غير معتمد حاليًا لهذا الجهاز.\nحدد هذا الخيار مرة أخرى في المستقبل لمعرفة ما إذا تمت إضافة الدعم!</string>
+ <string name="manage_yuzu_data">إدارة بيانات يوزو</string>
+ <string name="manage_yuzu_data_description">استيراد/تصدير البرامج الثابتة والمفاتيح وبيانات المستخدم والمزيد!</string>
+ <string name="share_save_file">مشاركة ملف الحفظ</string>
+ <string name="export_save_failed">فشل تصدير الحفظ</string>
+
+ <string name="copied_to_clipboard">نسخ إلى الحافظة</string>
+ <string name="about_app_description">محاكي سويتش مفتوح المصدر</string>
+ <string name="contributors">المساهمين</string>
+ <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">المشاريع التي تجعل تطبيق يوزو لنظام أندرويد ممكنًا</string>
+ <string name="build">البناء</string>
+ <string name="user_data">بيانات المستخدم</string>
+ <string name="exporting_user_data">جارٍ تصدير بيانات المستخدم</string>
+ <string name="importing_user_data">جارٍ استيراد بيانات المستخدم</string>
+ <string name="import_user_data">استيراد بيانات المستخدم</string>
+ <string name="invalid_yuzu_backup">نسخة احتياطية يوزو غير صالحة</string>
+ <string name="user_data_export_success">تم تصدير بيانات المستخدم بنجاح</string>
+ <string name="user_data_import_success">تم استيراد بيانات المستخدم بنجاح</string>
+ <string name="user_data_export_cancelled">تم إلغاء التصدير</string>
+ <string name="support_link">https://discord.gg/u77vRWY</string>
+ <string name="website_link">https://yuzu-emu.org/</string>
+ <string name="github_link">https://github.com/yuzu-emu</string>
+
+ <!-- Early access upgrade strings -->
+ <string name="early_access">الوصول المبكر</string>
+ <string name="get_early_access">احصل على الوصول المبكر</string>
+ <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
+ <string name="get_early_access_description">الميزات المتطورة، والوصول المبكر إلى التحديثات، وأكثر من ذلك</string>
+ <string name="early_access_benefits">مزايا الوصول المبكر</string>
+ <string name="cutting_edge_features">ميزات متطورة</string>
+ <string name="early_access_updates">الوصول المبكر إلى التحديثات</string>
+ <string name="no_manual_installation">لا يوجد التثبيت اليدوي</string>
+ <string name="prioritized_support">الدعم ذو الأولوية</string>
+ <string name="helping_game_preservation">المساعدة في الحفاظ على اللعبة</string>
+ <string name="our_eternal_gratitude">امتناننا الأبدي</string>
+ <string name="are_you_interested">هل انت مهتم؟</string>
+
+ <!-- General settings strings -->
+ <string name="frame_limit_enable">الحد من السرعة</string>
+ <string name="frame_limit_enable_description">يحد من سرعة المحاكاة بنسبة محددة من السرعة العادية</string>
+ <string name="frame_limit_slider">الحد من السرعة في المئة</string>
+ <string name="frame_limit_slider_description">يحدد النسبة المئوية للحد من سرعة المحاكاة. 100% هي السرعة الطبيعية. ستؤدي القيم الأعلى أو الأدنى إلى زيادة أو تقليل حد السرعة.</string>
+ <string name="cpu_accuracy">دقة وحدة المعالجة المركزية</string>
+ <string name="value_with_units">%1$s%2$s</string>
+
+ <!-- System settings strings -->
+ <string name="use_docked_mode">وضع الإرساء</string>
+ <string name="use_docked_mode_description">زيادة الدقة، وانخفاض الأداء. يتم استخدام الوضع المحمول عند تعطيله، مما يؤدي إلى خفض الدقة وزيادة الأداء.</string>
+ <string name="emulated_region">المنطقة التي تمت محاكاتها</string>
+ <string name="emulated_language">لغة المحاكاه</string>
+ <string name="select_rtc_date">حدد التاريخ و الساعة في الوقت الحقيقي</string>
+ <string name="select_rtc_time">حدد وقت الساعة في الوقت الفعلي</string>
+ <string name="use_custom_rtc">ساعة مخصصة في الوقت الحقيقي</string>
+ <string name="use_custom_rtc_description">يسمح لك بتعيين ساعة مخصصة في الوقت الفعلي منفصلة عن وقت النظام الحالي لديك</string>
+ <string name="set_custom_rtc">تعيين ساعة مخصصة في الوقت الحقيقي</string>
+
+ <!-- Graphics settings strings -->
+ <string name="renderer_accuracy">مستوى الدقة</string>
+ <string name="renderer_resolution">(Handheld/Docked) الدقة</string>
+ <string name="renderer_vsync">VSync وضع</string>
+ <string name="renderer_screen_layout">الاتجاه</string>
+ <string name="renderer_aspect_ratio">تناسب الابعاد</string>
+ <string name="renderer_anti_aliasing">طريقة مكافحة التعرج</string>
+ <string name="renderer_asynchronous_shaders">استخدم تظليل غير متزامن</string>
+ <string name="renderer_asynchronous_shaders_description">يجمع التظليل بشكل غير متزامن، مما يقلل من التأتأة ولكنه قد يؤدي إلى حدوث بعض الأخطاء.</string>
+ <string name="renderer_reactive_flushing">استخدم التنظيف التفاعلي</string>
+ <string name="renderer_reactive_flushing_description">تحسين دقة العرض في بعض الألعاب على حساب الأداء</string>
+ <string name="use_disk_shader_cache_description">يقلل من التأتأة عن طريق تخزين وتحميل التظليلات التي تم إنشاؤها محليًا.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">وحدة المعالج المركزية</string>
+ <string name="cpu_debug_mode">تصحيح أخطاء وحدة المعالجة المركزية</string>
+ <string name="cpu_debug_mode_description">يضع وحدة المعالجة المركزية في وضع التصحيح البطيء.</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">تصحيح الأخطاء الرسومية</string>
+ <string name="renderer_debug_description">يضبط واجهة برمجة تطبيقات الرسومات على وضع تصحيح الأخطاء البطيء.</string>
+ <string name="fastmem">Fastmem</string>
+
+ <!-- Audio settings strings -->
+ <string name="audio_output_engine">محرك الإخراج</string>
+ <string name="audio_volume">حجم</string>
+ <string name="audio_volume_description">يحدد حجم إخراج الصوت</string>
+
+ <!-- Miscellaneous -->
+ <string name="slider_default">افتراضي</string>
+ <string name="ini_saved">الإعدادات المحفوظة</string>
+ <string name="gameid_saved">الإعدادات المحفوظة لـ %1$s</string>
+ <string name="unimplemented_menu">القائمة غير المنفذة</string>
+ <string name="loading">جاري تحميل</string>
+ <string name="shutting_down">إيقاف تشغيل</string>
+ <string name="reset_setting_confirmation">هل تريد إعادة تعيين هذا الإعداد مرة أخرى إلى قيمته الافتراضية؟</string>
+ <string name="reset_to_default">إعادة تعيين إلى الافتراضي</string>
+ <string name="reset_all_settings">إعادة تعيين جميع الإعدادات؟</string>
+ <string name="reset_all_settings_description">سيتم إعادة تعيين كافة الإعدادات المتقدمة إلى تكوينها الافتراضي. هذا لا يمكن التراجع عنها.</string>
+ <string name="settings_reset">إعادة تعيين الأعدادات</string>
+ <string name="close">إغلاق</string>
+ <string name="learn_more">معرفة المزيد</string>
+ <string name="auto">تلقائي</string>
+ <string name="submit">إرسال</string>
+ <string name="string_null">قيمه خاليه</string>
+ <string name="string_import">استيراد</string>
+ <string name="export">تصدير</string>
+ <string name="export_failed">فشل التصدير</string>
+ <string name="import_failed">فشل الاستيراد</string>
+ <string name="cancelling">إلغاء</string>
+
+ <!-- GPU driver installation -->
+ <string name="select_gpu_driver">GPU حدد برنامج تشغيل</string>
+ <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_use_default">يستخدم تعريف معالج الرسوميات الافتراضي</string>
+ <string name="select_gpu_driver_error">تم تحديد برنامج تشغيل غير صالح ، باستخدام النظام الافتراضي</string>
+ <string name="system_gpu_driver">تعريف معالج الرسوميات الخاص بالنظام</string>
+ <string name="installing_driver">جارٍ تثبيت برنامج التشغيل…</string>
+
+ <!-- Preferences Screen -->
+ <string name="preferences_settings">إعدادات</string>
+ <string name="preferences_general">عام</string>
+ <string name="preferences_system">النظام</string>
+ <string name="preferences_graphics">الرسوميات</string>
+ <string name="preferences_audio">الصوت</string>
+ <string name="preferences_theme">السمة واللون</string>
+ <string name="preferences_debug">تصحيح الأخطاء</string>
+
+ <!-- ROM loading errors -->
+ <string name="loader_error_encrypted">الخاص بك ROM تم تشفير</string>
+ <string name="loader_error_video_core">حدث خطأ أثناء تهيئة مركز الفيديو</string>
+ <string name="loader_error_invalid_format">ROM غير قادر على تحميل</string>
+ <string name="loader_error_file_not_found">غير موجود ROM ملف</string>
+
+ <!-- Emulation Menu -->
+ <string name="emulation_exit">الخروج من المحاكاة</string>
+ <string name="emulation_done">منجز</string>
+ <string name="emulation_fps_counter">عداد إطار/ثانية</string>
+ <string name="emulation_toggle_controls">تبديل عناصر التحكم</string>
+ <string name="emulation_rel_stick_center">مركز العصا النسبي</string>
+ <string name="emulation_dpad_slide">مزلاق أزرار الاتجاهات</string>
+ <string name="emulation_haptics">الاهتزازات الديناميكية</string>
+ <string name="emulation_show_overlay">عرض التراكب</string>
+ <string name="emulation_toggle_all">تبديل الكل</string>
+ <string name="emulation_control_adjust">ضبط التراكب</string>
+ <string name="emulation_control_scale">حجم</string>
+ <string name="emulation_control_opacity">العتامه</string>
+ <string name="emulation_touch_overlay_reset">إعادة تعيين التراكب</string>
+ <string name="emulation_touch_overlay_edit">تحرير التراكب</string>
+ <string name="emulation_pause">إيقاف المحاكاة مؤقتًا</string>
+ <string name="emulation_unpause">إلغاء الإيقاف المؤقت للمضاهاة</string>
+ <string name="emulation_input_overlay">خيارات التراكب</string>
+
+ <string name="load_settings">جارٍ تحميل الإعدادات</string>
+
+ <!-- Software keyboard -->
+ <string name="software_keyboard">لوحة المفاتيح البرمجية</string>
+
+ <!-- Errors and warnings -->
+ <string name="abort_button">إلغاء</string>
+ <string name="continue_button">استمر</string>
+ <string name="system_archive_not_found">لم يتم العثور على أرشيف النظام</string>
+ <string name="system_archive_general">أرشيف النظام</string>
+ <string name="save_load_error">خطأ في الحفظ/التحميل</string>
+ <string name="fatal_error">خطا فادح</string>
+ <string name="performance_warning">سيؤدي إيقاف تشغيل هذا الإعداد إلى تقليل أداء المحاكاة بشكل ملحوظ! للحصول على أفضل تجربة، يوصى بترك هذا الإعداد ممكنًا.</string>
+ <string name="memory_formatted">%1$s %2$s</string>
+ <string name="no_game_present">لا توجد لعبة قابلة للتمهيد</string>
+
+ <!-- Region Names -->
+ <string name="region_japan">اليابان</string>
+ <string name="region_usa">الولايات المتحدة الأمريكية</string>
+ <string name="region_europe">أوروبا</string>
+ <string name="region_australia">أستراليا</string>
+ <string name="region_china">الصين</string>
+ <string name="region_korea">كوريا</string>
+ <string name="region_taiwan">تايوان</string>
+
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
+
+ <!-- Renderer APIs -->
+ <string name="renderer_vulkan">Vulkan</string>
+ <string name="renderer_none">لاشيء</string>
+
+ <!-- Renderer Accuracy -->
+ <string name="renderer_accuracy_normal">عادي</string>
+ <string name="renderer_accuracy_high">عالي</string>
+ <string name="renderer_accuracy_extreme">Extreme (بطيء)</string>
+
+ <!-- Resolutions -->
+ <string name="resolution_half">0.5X (360p/540p)</string>
+ <string name="resolution_three_quarter">0.75X (540p/810p)</string>
+ <string name="resolution_one">1X (720p/1080p)</string>
+ <string name="resolution_two">2X (1440p/2160p) (بطيء)</string>
+ <string name="resolution_three">3X (2160p/3240p) (بطيء)</string>
+ <string name="resolution_four">4X (2880p/4320p) (بطيء)</string>
+
+ <!-- Renderer VSync -->
+ <string name="renderer_vsync_immediate">Immediate (Off)</string>
+ <string name="renderer_vsync_mailbox">Mailbox</string>
+ <string name="renderer_vsync_fifo">FIFO (On)</string>
+ <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
+
+ <!-- Scaling Filters -->
+ <string name="scaling_filter_nearest_neighbor">Nearest Neighbor</string>
+ <string name="scaling_filter_bilinear">Bilinear</string>
+ <string name="scaling_filter_bicubic">Bicubic</string>
+ <string name="scaling_filter_gaussian">Gaussian</string>
+ <string name="scaling_filter_scale_force">ScaleForce</string>
+ <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
+
+ <!-- Anti-Aliasing -->
+ <string name="anti_aliasing_none">لا شيء</string>
+ <string name="anti_aliasing_fxaa">FXAA</string>
+ <string name="anti_aliasing_smaa">SMAA</string>
+
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">افقي</string>
+ <string name="screen_layout_portrait">عمودي</string>
+ <string name="screen_layout_auto">تلقائي</string>
+
+ <!-- Aspect Ratios -->
+ <string name="ratio_default">(16:9) افتراضي</string>
+ <string name="ratio_force_four_three">4:3 فرض</string>
+ <string name="ratio_force_twenty_one_nine">21:9 فرض</string>
+ <string name="ratio_force_sixteen_ten">16:10 فرض</string>
+ <string name="ratio_stretch">تمتد إلى النافذة</string>
+
+ <!-- CPU Accuracy -->
+ <string name="cpu_accuracy_accurate">دقه</string>
+ <string name="cpu_accuracy_unsafe">غير آمن</string>
+ <string name="cpu_accuracy_paranoid">Paranoid (Slow)</string>
+
+ <!-- Gamepad Buttons -->
+ <string name="gamepad_d_pad">أزرار الاتجاهات</string>
+ <string name="gamepad_left_stick">العصا اليسرى</string>
+ <string name="gamepad_right_stick">العصا اليمنى</string>
+ <string name="gamepad_home">شاشة الإستقبال</string>
+ <string name="gamepad_screenshot">لقطة شاشة</string>
+
+ <!-- Disk shader cache -->
+ <string name="preparing_shaders">تحضير التظليل</string>
+ <string name="building_shaders">بناء التظليل</string>
+
+ <!-- Theme options -->
+ <string name="change_app_theme">تغيير سمة التطبيق</string>
+ <string name="theme_default">افتراضي</string>
+ <string name="theme_material_you">Material You</string>
+
+ <!-- Theme Modes -->
+ <string name="change_theme_mode">تغيير وضع السمة</string>
+ <string name="theme_mode_follow_system">اتبع النظام</string>
+ <string name="theme_mode_light">فاتح</string>
+ <string name="theme_mode_dark">غامق</string>
+
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
+ <!-- Black backgrounds theme -->
+ <string name="use_black_backgrounds">خلفيات سوداء</string>
+ <string name="use_black_backgrounds_description">عند استخدام المظهر الداكن، قم بتطبيق خلفيات سوداء.</string>
+
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">صورة داخل صورة</string>
+ <string name="picture_in_picture_description">تصغير النافذة عند وضعها في الخلفية</string>
+ <string name="pause">توقف</string>
+ <string name="play">تشغيل</string>
+ <string name="mute">كتم</string>
+ <string name="unmute">إلغاء الكتم</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">التراخيص</string>
+ <string name="license_fidelityfx_fsr_description">AMD ترقية عالية الجودة من</string>
+ </resources>
diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml
new file mode 100644
index 000000000..d2e5fee19
--- /dev/null
+++ b/src/android/app/src/main/res/values-ckb/strings.xml
@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
+
+ <string name="app_disclaimer">ئەم نەرمەکاڵایە یارییەکانی کۆنسۆلی نینتێندۆ سویچ کارپێدەکات. هیچ ناونیشانێکی یاری و کلیلی تێدا نییە..&lt;br /&gt;&lt;br /&gt;پێش ئەوەی دەست پێ بکەیت، تکایە شوێنی فایلی <![CDATA[<b> prod.keys </b>]]> دیاریبکە لە نێو کۆگای ئامێرەکەت.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">زیاتر فێربە</a>]]></string>
+ <string name="emulation_notification_channel_name">ئیمولەیشن کارایە</string>
+ <string name="emulation_notification_channel_description">ئاگادارکردنەوەیەکی بەردەوام نیشان دەدات کاتێک ئیمولەیشن کاردەکات.</string>
+ <string name="emulation_notification_running">یوزو کاردەکات</string>
+ <string name="notice_notification_channel_name">ئاگاداری و هەڵەکان</string>
+ <string name="notice_notification_channel_description">ئاگادارکردنەوەکان پیشان دەدات کاتێک شتێک بە هەڵەدا دەچێت.</string>
+ <string name="notification_permission_not_granted">مۆڵەتی ئاگادارکردنەوە نەدراوە!</string>
+
+ <!-- Setup strings -->
+ <string name="welcome">بەخێربێیت!</string>
+ <string name="welcome_description">فێربە چۆن &lt;b>yuzu&lt;/b> ڕێکبخەیت و بچییە ناو ئیمولەیشن.</string>
+ <string name="get_started">دەست پێبکە</string>
+ <string name="keys">کلیلەکان</string>
+ <string name="keys_description">فایلی &lt;b>prod.keys&lt;/b> هەڵبژێرە بە دوگمەی خوارەوە.</string>
+ <string name="select_keys">کلیلەکان هەڵبژێرە</string>
+ <string name="games">یاریەکان</string>
+ <string name="games_description">فۆڵدەری &lt;b>Games&lt;/b> هەڵبژێرە بە دوگمەی خوارەوە.</string>
+ <string name="done">تەواو</string>
+ <string name="done_description">تۆ تەواو ئامادەیت.\nچێژ لە یارییەکانت وەربگرە!</string>
+ <string name="text_continue">بەردەوام بوون</string>
+ <string name="next">دواتر</string>
+ <string name="back">گەڕانەوە</string>
+ <string name="add_games">زیادکردنی یاری</string>
+ <string name="add_games_description">فۆڵدەری یارییەکانت هەڵبژێرە</string>
+ <!-- Home strings -->
+ <string name="home_games">یاریەکان</string>
+ <string name="home_search">گەڕان</string>
+ <string name="home_settings">ڕێکخستنەکان</string>
+ <string name="empty_gamelist">تا ئێستا هیچ فایلێک نەدۆزراوەتەوە یان هیچ ناونیشانێکی یاری هەڵنەبژێردراوە.</string>
+ <string name="search_and_filter_games">گەڕان و فلتەرکردنی یارییەکان</string>
+ <string name="select_games_folder">فۆڵدەری یارییەکان هەڵبژێرە</string>
+ <string name="select_games_folder_description">ڕێگە بە یوزو دەدات بۆ پڕکردنەوەی لیستی یارییەکان</string>
+ <string name="add_games_warning">هەڵبژاردنی فۆڵدەری یارییەکان تێپەڕدەکەیت؟</string>
+ <string name="add_games_warning_description">یارییەکان لە لیستی یارییەکاندا پیشان نادرێن ئەگەر فۆڵدەرێک هەڵنەبژێردرێت.</string>
+ <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
+ <string name="home_search_games">گەڕان بەدوای یارییەکاندا</string>
+ <string name="games_dir_selected">ناونیشانی یارییەکان هەڵبژێردرا</string>
+ <string name="install_prod_keys">دابمەزرێنە prod.keys</string>
+ <string name="install_prod_keys_description">پێویستە بۆ کۆدکردنەوەى یارییە تاکەکەسییەکان</string>
+ <string name="install_prod_keys_warning">زیادکردنی کلیلەکان تێپەڕدەکەیت؟</string>
+ <string name="install_prod_keys_warning_description">کلیلی دروست پێویستە بۆ وەرگرتنی یارییەکانی تاکەکەسی. تەنها ئەپەکانی homebrew کاردەکەن ئەگەر بەردەوام بیت.</string>
+ <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
+ <string name="notifications">ئاگادارکردنەوەکان</string>
+ <string name="notifications_description">بە دوگمەی خوارەوە مۆڵەتی ئاگادارکردنەوەکە بدە.</string>
+ <string name="give_permission">مۆڵەت بدە</string>
+ <string name="notification_warning">پێدانی مۆڵەتی ئاگادارکردنەوە تێپەڕدەکەیت؟</string>
+ <string name="notification_warning_description">یوزو ناتوانێت لە زانیاری گرنگ ئاگادارت بکاتەوە.</string>
+ <string name="permission_denied">مۆڵەت پێدان ڕەتکرایەوە</string>
+ <string name="permission_denied_description">زۆر جار ئەم مۆڵەتەت ڕەتکردۆتەوە و ئێستا دەبێت بە دەستی ڕێگەپێدان بکەیت لە ڕێکخستنەکانی سیستەمدا.</string>
+ <string name="about">دەربارە</string>
+ <string name="about_description">وەشانی دروستکردن، بیتبێن و زۆر شتیتر</string>
+ <string name="warning_help">یارمەتی</string>
+ <string name="warning_skip">پەڕاندن</string>
+ <string name="warning_cancel">ڕەتکردنەوە</string>
+ <string name="install_amiibo_keys">دامەزراندنی کلیلی Amiibo</string>
+ <string name="install_amiibo_keys_description">پێویستە بۆ بەکارهێنانی Amiibo لە یاریدا</string>
+ <string name="invalid_keys_file">فایلی کلیلێکی نادروست هەڵبژێردرا</string>
+ <string name="install_keys_success">کلیلەکان بە سەرکەوتوویی دامەزران</string>
+ <string name="reading_keys_failure">هەڵە لە خوێندنەوەی کۆدکردنی کلیل</string>
+ <string name="install_prod_keys_failure_extension_description">دڵنیابەوە کە فایلی کلیلەکانت درێژکراوەی .keys ی هەیە و دووبارە هەوڵبدەرەوە.</string>
+ <string name="install_amiibo_keys_failure_extension_description">دڵنیابە کە فایلی کلیلەکانت درێژکراوەی .bin ی هەیە و دووبارە هەوڵبدەرەوە.</string>
+ <string name="invalid_keys_error">کلیلی کۆدکردنی نادروستە</string>
+ <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
+ <string name="install_keys_failure_description">فایلە هەڵبژێردراوەکە هەڵەیە یان تێکچووە. تکایە دووبارە کلیلەکانت دەربێنەوە.</string>
+ <string name="install_gpu_driver">دامەزراندنی وەگەڕخەری GPU</string>
+ <string name="install_gpu_driver_description">دامەزراندنی وەگەڕخەری بەدیل بۆ ئەوەی بە ئەگەرێکی زۆرەوە کارایی باشتر یان وردبینی هەبێت</string>
+ <string name="advanced_settings">ڕێکخستنە پێشکەوتووەکان</string>
+ <string name="settings_description">سازدانی ڕێکخستنەکانی ئیمولەیتەر</string>
+ <string name="search_recently_played">بەم دواییە یاری کردووە</string>
+ <string name="search_recently_added">بەم دواییە زیادکرا</string>
+ <string name="search_retail">بەتاک</string>
+ <string name="search_homebrew">هۆم بریو</string>
+ <string name="open_user_folder">کردنەوەی فۆڵدەری یوزو</string>
+ <string name="open_user_folder_description">بەڕێوەبردنی فایلە ناوخۆییەکانی یوزو</string>
+ <string name="theme_and_color_description">دەستکاری کردنی شێوازی ئەپەکە</string>
+ <string name="no_file_manager">هیچ فایل بەڕێوەبەرێک نەدۆزرایەوە</string>
+ <string name="notification_no_directory_link">نەتوانرا ناونیشانی یوزو بکرێتەوە</string>
+ <string name="notification_no_directory_link_description">تکایە شوێنی فۆڵدەری بەکارهێنەر لەگەڵ پانێڵی لایەنی فایل بەڕێوەبارەکان بە دەست بدۆزەرەوە.</string>
+ <string name="manage_save_data">بەڕێوەبردنی داتای پاشەکەوتکراو</string>
+ <string name="manage_save_data_description">داتای پاشەکەوتکراو دۆزراوە. تکایە لە خوارەوە بژاردەیەک هەڵبژێرە.</string>
+ <string name="import_export_saves_description">هاوردەکردن یان هەناردەکردنی فایلی پاشەکەوتکراو</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>
+ <string name="import_saves">هاوردەکردن</string>
+ <string name="export_saves">هەناردەکردن</string>
+ <string name="install_firmware">دامەزراندنی پتەوواڵا</string>
+ <string name="install_firmware_description">پتەوواڵا دەبێت لە ئەرشیفی زیپدا بێت و پێویستە بۆ بووتکردنی هەندێک یاری</string>
+ <string name="firmware_installing">دامەزرانی پتەوواڵا</string>
+ <string name="firmware_installed_success">پتەوواڵا بە سەرکەوتوویی دامەزرا</string>
+ <string name="firmware_installed_failure">دامەزراندنی پتەوواڵا شکستی هێنا</string>
+ <string name="share_log">هاوبەشی پێکردنی لۆگەکانی چاککردنەوە</string>
+ <string name="share_log_description">فایلە لۆگەکەی یوزو هاوبەش بکە بۆ چاککردنی کێشەکان</string>
+ <string name="share_log_missing">هیچ فایلێکی لۆگ نەدۆزراوە</string>
+ <string name="install_game_content">دامەزراندنی ناوەڕۆکی یاری</string>
+ <string name="install_game_content_description">دامەزراندنی نوێکاری یارییەکان یان DLC</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <!-- About screen strings -->
+ <string name="gaia_is_not_real">گایا ڕاستەقینە نییە</string>
+ <string name="copied_to_clipboard">کۆپی کرا بۆ تەختەی نووسین</string>
+ <string name="about_app_description">ئیمۆلیتەرێکی سەرچاوە-کراوەی سویچ</string>
+ <string name="contributors">بەشداربووان</string>
+ <string name="contributors_description">دروستکراوە لەگەڵ \u2764 لەلایەن تیمەکەی یوزو</string>
+ <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">ئەو پڕۆژانەی کە یوزوی بۆ ئەندرۆید ڕەخساند</string>
+ <string name="build">بونیات</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>
+
+ <!-- Early access upgrade strings -->
+ <string name="early_access">بەزوویی دەسپێگەشتن</string>
+ <string name="get_early_access">بەدەستهێنانی بەزوویی دەسپێگەشتن</string>
+ <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
+ <string name="get_early_access_description">تایبەتمەندییە پێشکەوتووەکان، بەزوویی دەستگەیشتن بە نوێکارییەکان و زۆر شتی تر</string>
+ <string name="early_access_benefits">سوودەکانی بەزوویی دەسپێگەشتن</string>
+ <string name="cutting_edge_features">تایبەتمەندییە پێشکەوتووەکان</string>
+ <string name="early_access_updates">زوو دەستگەیشتن بە نوێکارییەکان</string>
+ <string name="no_manual_installation">چیتر دامەزراندنی دەستی نییە</string>
+ <string name="prioritized_support">پشتگیری لە پێشینە</string>
+ <string name="helping_game_preservation">یارمەتیدانی پاراستنی یارییەکان</string>
+ <string name="our_eternal_gratitude">سوپاس و پێزانینی هەمیشەییمان</string>
+ <string name="are_you_interested">ئایا تۆ خوازیاریت؟</string>
+
+ <!-- General settings strings -->
+ <string name="frame_limit_enable">سنووردارکردنی خێرایی</string>
+ <string name="frame_limit_enable_description">خێرایی ئیمولەیشن سنووردار دەکات بۆ ڕێژەیەکی دیاریکراو لە خێرایی ئاسایی.</string>
+ <string name="frame_limit_slider">سنووردارکردنی لەسەدای خێرایی</string>
+ <string name="frame_limit_slider_description">ڕێژەی سەدی دیاری دەکات بۆ سنووردارکردنی خێرایی ئیمولەیشن. 100% خێرایی ئاساییە. بەهایی بەرزتر یان نزمتر دەبێتە هۆی زیاد یان کەمکردنەوەی سنووری خێرایی.</string>
+ <string name="cpu_accuracy">وردی CPU</string>
+ <!-- System settings strings -->
+ <string name="use_docked_mode">دۆخی دۆککراو</string>
+ <string name="use_docked_mode_description">ڕوونی زیاد دەکات، کارایی کەم دەکاتەوە. دۆخی دەستی بەکاردێت کاتێک لەکاردەخرێت، ئەمەش ڕوونی دادەبەزێنێت و کارایی زیاد دەکات.</string>
+ <string name="emulated_region">ناوچەی ئیمولەیشن</string>
+ <string name="emulated_language">زمانی ئیمولەیتەر</string>
+ <string name="select_rtc_date">هەڵبژاردنی بەرواری RTC</string>
+ <string name="select_rtc_time">هەڵبژاردنی کاتی RTC</string>
+ <string name="use_custom_rtc">RTCی تایبەتمەند</string>
+ <string name="use_custom_rtc_description">ڕێگەت پێدەدات کاتژمێرێکی کاتی ڕاستەقینەی تایبەتمەند دابنێیت کە جیاوازە لە کاتی ئێستای سیستەمەکەت.</string>
+ <string name="set_custom_rtc">دانانی RTCی تایبەتمەند</string>
+
+ <!-- Graphics settings strings -->
+ <string name="renderer_accuracy">ئاستی وردبینی</string>
+ <string name="renderer_resolution">ڕوونی (دۆخی دەستی/دۆخی دۆک)</string>
+ <string name="renderer_vsync">دۆخی VSync</string>
+ <string name="renderer_aspect_ratio">ڕێژەی ڕووبەری شاشە</string>
+ <string name="renderer_scaling_filter">فلتەری گونجاندنی پەنجەرە</string>
+ <string name="renderer_anti_aliasing">شێوازی دژە-خاوڕۆیی</string>
+ <string name="renderer_force_max_clock">ناچاریکردن بۆ زۆرترین کاتژمێر (تەنها ئەدرینۆ)</string>
+ <string name="renderer_force_max_clock_description">GPU ناچار دەکات بە زۆرترین کاتژمێر کاربکات (هێشتا سنووردارکردنی گەرمی جێبەجێ دەکرێت).</string>
+ <string name="renderer_asynchronous_shaders">بەکارهێنانی سێبەری ناهاوسەنگ</string>
+ <string name="renderer_asynchronous_shaders_description">سێبەرەکان بە شێوەیەکی ناهاوسەنگ کۆدەکاتەوە، پچڕپچڕی کەمدەکاتەوە بەڵام لەوانەیە گلێچ دروستکا.</string>
+ <string name="renderer_reactive_flushing">بەکارهێنانی بەرپێچدەرەوە</string>
+ <string name="renderer_reactive_flushing_description">وردی ڕێندەرکردن لە هەندێک یاریدا باشتر دەکات لەسەر تێچووی کارایی.</string>
+ <string name="use_disk_shader_cache">بیرگەخێرای سێبەری دیسک</string>
+ <string name="use_disk_shader_cache_description">پچڕپچڕی کەمدەکاتەوە بە هەڵگرتن و بارکردنی سێبەری دروستکراو لە ناوخۆدا.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="renderer_api">API گرافیک</string>
+ <string name="renderer_debug">چاککردنەوەی گرافیک</string>
+ <string name="renderer_debug_description">API ی گرافیکەکان ڕێکدەخات بۆ دۆخی چاککردنی خاو.</string>
+ <string name="audio_volume">قەبارەی دەنگی</string>
+ <string name="audio_volume_description">دیاریکردنی قەبارەی دەنگی دەرچووی بیستۆک و بزوێنەری دەنگی دەرەکی.</string>
+
+ <!-- Miscellaneous -->
+ <string name="slider_default">بنەڕەت</string>
+ <string name="ini_saved">ڕێکخستنە پاشەکەوتکراوەکان</string>
+ <string name="gameid_saved">ڕێکخستنە پاشەکەوتکراوەکان بۆ %1$s</string>
+ <string name="error_saving">هەڵە لە پاشەکەوتکردن %1$s.ini: %2$s</string>
+ <string name="loading">بارکردن...</string>
+ <string name="reset_setting_confirmation">ئایا دەتەوێت ئەم ڕێکخستنە بگەڕێنیتەوە بۆ بەهای بنەڕەتی خۆی؟</string>
+ <string name="reset_to_default">دوبارە ڕێکخستنەوەی بۆ بنەڕەت</string>
+ <string name="reset_all_settings">هەموو ڕێکخستنەکان دوبارە ڕێک دەخاتەوە؟</string>
+ <string name="reset_all_settings_description">هەموو ڕێکخستنە پێشکەوتووەکان دەگەڕێنەوە بۆ ڕێکخستنی بنەڕەتی خۆیان. پاشگەز بوونەوەی نییه.</string>
+ <string name="settings_reset">دوبارە ڕێککردنەوەی ڕێکخستنەکان</string>
+ <string name="close">داخستن</string>
+ <string name="learn_more">زیاتر فێربە</string>
+ <string name="auto">خودکار</string>
+ <string name="submit">پێشکەشکردن</string>
+ <string name="string_import">هاوردەکردن</string>
+ <string name="export">هەناردەکردن</string>
+ <!-- GPU driver installation -->
+ <string name="select_gpu_driver">هەڵبژاردنی وەگەڕخەری GPU</string>
+ <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_use_default">بەکارهێنانی وەگەڕخەری GPU ی بنەڕەت</string>
+ <string name="select_gpu_driver_error">وەگەڕخەری نادروست هەڵبژێردرا، بە بەکارهێنانی بنەڕەتی سیستەم!</string>
+ <string name="system_gpu_driver">وەگەڕخەری GPU ی سیستەم</string>
+ <string name="installing_driver">دامەزراندنی وەگەڕخەر...</string>
+
+ <!-- Preferences Screen -->
+ <string name="preferences_settings">ڕێکخستنەکان</string>
+ <string name="preferences_general">گشتی</string>
+ <string name="preferences_system">سیستەم</string>
+ <string name="preferences_graphics">گرافیک</string>
+ <string name="preferences_audio">دەنگ</string>
+ <string name="preferences_theme">ڕەنگ و ڕووکار</string>
+ <string name="preferences_debug">چاککردنەوە</string>
+
+ <!-- ROM loading errors -->
+ <string name="loader_error_encrypted">ڕۆمەکەت کۆدکراوە</string>
+ <string name="loader_error_encrypted_keys_description"><![CDATA[تکایە دڵنیابەوە لەدامەزراوی <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> فایلەکەت بۆ ئەوەی بتوانرێت یارییەکان کۆد بکرێنەوە.]]></string>
+ <string name="loader_error_video_core">هەڵەیەک لە دەستپێکردنی ناوەکی ڤیدیۆکەدا ڕوویدا</string>
+ <string name="loader_error_video_core_description">ئەمەش بەزۆری بەهۆی وەگەڕخەرێکی ناتەبای GPU ەوەیە. دامەزراندنی وەگەڕخەری GPU ی تایبەتمەندکراو لەوانەیە ئەم کێشەیە چارەسەر بکات.</string>
+ <string name="loader_error_invalid_format">ناتوانرێت ڕۆم باربکرێت</string>
+ <string name="loader_error_file_not_found">فایلی ڕۆم بوونی نییە</string>
+
+ <!-- Emulation Menu -->
+ <string name="emulation_exit">دەرچوون لە ئیمولەیشن</string>
+ <string name="emulation_done">تەواو</string>
+ <string name="emulation_fps_counter">FPS ژمێر</string>
+ <string name="emulation_toggle_controls">گۆڕینی کۆنتڕۆڵ</string>
+ <string name="emulation_rel_stick_center">ناوەندی گێڕ بەنزیکەیی</string>
+ <string name="emulation_dpad_slide">خلیسکانی 4 دوگمەکە</string>
+ <string name="emulation_haptics">لەرینەوەی پەنجەلێدان</string>
+ <string name="emulation_show_overlay">نیشاندانی داپۆشەر</string>
+ <string name="emulation_toggle_all">گۆڕینی سەرجەم</string>
+ <string name="emulation_control_adjust">ڕێکخستنی داپۆشەر</string>
+ <string name="emulation_control_scale">پێوەر</string>
+ <string name="emulation_control_opacity">ڕوونی</string>
+ <string name="emulation_touch_overlay_reset">دووبارە ڕێکخستنەوەی داپۆشەر</string>
+ <string name="emulation_touch_overlay_edit">دەستکاریکردنی داپۆشەر</string>
+ <string name="emulation_pause">وەستاندنی ئیمولەیشن</string>
+ <string name="emulation_unpause">لادانی وەستاندنی ئیمولەیشن</string>
+ <string name="emulation_input_overlay">هەڵبژاردەکانی داپۆشەر</string>
+
+ <string name="load_settings">بارکردنی ڕێکخستنەکان...</string>
+
+ <!-- Software keyboard -->
+ <string name="software_keyboard">کیبۆردی نەرمەکاڵا</string>
+
+ <!-- Errors and warnings -->
+ <string name="abort_button">دەربارە</string>
+ <string name="continue_button">بەردەوام بوون</string>
+ <string name="system_archive_not_found">ئەرشیفی سیستەم نەدۆزراوە</string>
+ <string name="system_archive_not_found_message">%s دیار نییە. تکایە ئەرشیفی سیستەمەکەت فڕێ بدە.\nبەردەوامی ئیمولەیشن لەوانەیە ببێتە هۆی تێکچوون و فڕێدانەدەرەوە.</string>
+ <string name="system_archive_general">ئەرشیفێکی سیستەم</string>
+ <string name="save_load_error">هەڵەی پاشەکەوتکردن/بارکردن</string>
+ <string name="fatal_error">هەڵەی کوشندە</string>
+ <string name="fatal_error_message">هەڵەیەکی کوشندە ڕوویدا. بۆ وردەکارییەکان لۆگەکە بپشکنە.\nبەردەوامی ئیمولەیشن لەوانەیە ببێتە هۆی تێکچوون و فڕێدانەدەرەوە.</string>
+ <string name="performance_warning">کوژاندنەوەی ئەم ڕێکخستنە دەبێتە هۆی کەمکردنەوەی کارایی ئیمولەیشن! بۆ باشترین ئەزموون، باشترە ئەم ڕێکخستنە چالاک بهێڵیتەوە.</string>
+ <!-- Region Names -->
+ <string name="region_japan">ژاپۆن</string>
+ <string name="region_usa">ئەمریکا</string>
+ <string name="region_europe">ئەورووپا</string>
+ <string name="region_australia">ئوسترالیا</string>
+ <string name="region_china">چین</string>
+ <string name="region_korea">کۆریا</string>
+ <string name="region_taiwan">تایوان</string>
+
+ <string name="memory_gigabyte">GB</string>
+ <!-- Renderer APIs -->
+ <string name="renderer_vulkan">ڤوڵکان</string>
+ <string name="renderer_none">هیچ</string>
+
+ <!-- Renderer Accuracy -->
+ <string name="renderer_accuracy_normal">ئاسایی</string>
+ <string name="renderer_accuracy_high">بەرز</string>
+ <string name="renderer_accuracy_extreme">ئەوپەڕ (خاو)</string>
+
+ <!-- Resolutions -->
+ <string name="resolution_half">0.5X (360p/540p)</string>
+ <string name="resolution_three_quarter">0.75X (540p/810p)</string>
+ <string name="resolution_one">1X (720p/1080p)</string>
+ <string name="resolution_two">2X (1440p/2160p) (خاو)</string>
+ <string name="resolution_three">3X (2160p/3240p) (خاو)</string>
+ <string name="resolution_four">4X (2880p/4320p) (خاو)</string>
+
+ <!-- Renderer VSync -->
+ <string name="renderer_vsync_immediate">دەستبەجێ (کوژاوە)</string>
+ <string name="renderer_vsync_mailbox">سندوقی پۆستە</string>
+ <string name="renderer_vsync_fifo">FIFO (پێکراو)</string>
+ <string name="renderer_vsync_fifo_relaxed">FIFO ئارام</string>
+
+ <!-- Scaling Filters -->
+ <string name="scaling_filter_nearest_neighbor">نزیکترین دراوسێ</string>
+ <string name="scaling_filter_bilinear">دوو هێڵی</string>
+ <string name="scaling_filter_bicubic">دووخشتەکی</string>
+ <string name="scaling_filter_gaussian">گاوسی</string>
+ <string name="scaling_filter_scale_force">پێوەرهێز</string>
+ <string name="scaling_filter_fsr">AMD FidelityFX™ سوپەر ووردبینی</string>
+
+ <!-- Anti-Aliasing -->
+ <string name="anti_aliasing_none">هیچ</string>
+ <string name="anti_aliasing_fxaa">FXAA</string>
+ <string name="anti_aliasing_smaa">SMAA</string>
+
+ <string name="screen_layout_auto">خودکار</string>
+
+ <!-- Aspect Ratios -->
+ <string name="ratio_default">بنەڕەت (16:9)</string>
+ <string name="ratio_force_four_three">ڕووبەری 4:3</string>
+ <string name="ratio_force_twenty_one_nine">ڕووبەری 21:9</string>
+ <string name="ratio_force_sixteen_ten">ڕووبەری 16:10</string>
+ <string name="ratio_stretch">کشانی پڕ بەشاشە</string>
+
+ <!-- CPU Accuracy -->
+ <string name="cpu_accuracy_accurate">وورد</string>
+ <string name="cpu_accuracy_unsafe">ناسەقامگیر</string>
+ <string name="cpu_accuracy_paranoid">بەگومان (خاو)</string>
+
+ <!-- Gamepad Buttons -->
+ <string name="gamepad_d_pad">4 دوگمەکە</string>
+ <string name="gamepad_left_stick">گێڕی چەپ</string>
+ <string name="gamepad_right_stick">گێڕی ڕاست</string>
+ <string name="gamepad_home">ماڵەوە</string>
+ <string name="gamepad_screenshot">وێنەگرتنی شاشە</string>
+
+ <!-- Disk shader cache -->
+ <string name="preparing_shaders">ئامادەکردنی سێبەرەکان</string>
+ <string name="building_shaders">دروستکردنی سێبەرەکان</string>
+
+ <!-- Theme options -->
+ <string name="change_app_theme">گۆڕینی ڕووکاری ئەپەکە</string>
+ <string name="theme_default">بنەڕەت</string>
+ <string name="theme_material_you">کەرەستەی تۆ</string>
+
+ <!-- Theme Modes -->
+ <string name="change_theme_mode">گۆڕینی دۆخی ڕووکار</string>
+ <string name="theme_mode_follow_system">پەیڕەوی کردنی سیستەم</string>
+ <string name="theme_mode_light">ڕوناکی</string>
+ <string name="theme_mode_dark">تاریک</string>
+
+ <!-- Black backgrounds theme -->
+ <string name="use_black_backgrounds">پاشبنەمای ڕەش</string>
+ <string name="use_black_backgrounds_description">لە کاتی بەکارهێنانی ڕووکاری تاریکدا، پاشبنەمای ڕەش دادەنێ.</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">مۆڵەتەکان</string>
+ <string name="license_fidelityfx_fsr_description">بەرزکردنەوەی کوالێتی بەرز لە کۆمپانیای AMD</string>
+ </resources>
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 72a47fbdb..9c6590b5e 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">Diese Software kann Spiele für die Nintendo Switch abspielen. Keine Spiele oder Spielekeys sind enthalten.&lt;br /&gt;&lt;br /&gt;Bevor du beginnst, bitte halte deine <![CDATA[<b> prod.keys </b>]]> auf deinem Gerät bereit. .&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Mehr Infos</a>]]></string>
<string name="emulation_notification_channel_name">Emulation ist aktiv</string>
@@ -25,6 +25,7 @@
<string name="back">Zurück</string>
<string name="add_games">Spiele hinzufügen</string>
<string name="add_games_description">Spieleverzeichnis auswählen</string>
+ <string name="step_complete">Fertig!</string>
<!-- Home strings -->
<string name="home_games">Spiele</string>
@@ -38,6 +39,7 @@
<string name="add_games_warning_description">Spiele werden in der Spieleliste nicht angezeigt, wenn kein Ordner ausgewählt ist.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Spiele suchen</string>
+ <string name="search_settings">Einstellungen suchen</string>
<string name="games_dir_selected">Spieleverzeichnis ausgewählt</string>
<string name="install_prod_keys">prod.keys installieren</string>
<string name="install_prod_keys_description">Zum Entschlüsseln von Spielen benötigt</string>
@@ -60,8 +62,11 @@
<string name="invalid_keys_file">Ungültige Schlüsseldatei ausgewählt</string>
<string name="install_keys_success">Schlüssel erfolgreich installiert</string>
<string name="reading_keys_failure">Fehler beim Lesen der Schlüssel</string>
+ <string name="install_prod_keys_failure_extension_description">Überprüfen Sie, ob Ihre Schlüsseldatei die Erweiterung \".keys\" hat, und versuchen Sie es erneut.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Überprüfen Sie, ob Ihre Schlüsseldatei die Erweiterung \".bin\" hat, und versuchen Sie es erneut.</string>
<string name="invalid_keys_error">Ungültige Schlüssel</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
+ <string name="install_keys_failure_description">Die ausgewählte Datei ist falsch oder beschädigt. Bitte kopieren Sie Ihre Schlüssel erneut.</string>
<string name="install_gpu_driver">GPU-Treiber installieren</string>
<string name="install_gpu_driver_description">Alternative Treiber für eventuell bessere Leistung oder Genauigkeit installieren</string>
<string name="advanced_settings">Erweiterte Einstellungen</string>
@@ -84,7 +89,17 @@
<string name="save_file_invalid_zip_structure_description">Der erste Unterordnername muss die Titel-ID des Spiels sein.</string>
<string name="import_saves">Importieren</string>
<string name="export_saves">Exportieren</string>
-
+ <string name="install_firmware">Firmware installieren</string>
+ <string name="install_firmware_description">Die Firmware muss in einem ZIP-Archiv vorliegen und wird zum Booten einiger Spiele benötigt</string>
+ <string name="firmware_installing">Firmware wird installiert</string>
+ <string name="firmware_installed_success">Die Firmware wurde erfolgreich installiert!</string>
+ <string name="firmware_installed_failure">Bei der Firmware installation ist etwas fehlgeschlagen.</string>
+ <string name="share_log">Debug-Logs teilen</string>
+ <string name="share_log_description">Debug-Logs an yuzu zur Untersuchung absenden</string>
+ <string name="share_log_missing">Keine Log-Datei gefunden</string>
+ <string name="install_game_content">Spiel installieren</string>
+ <string name="install_game_content_description">Spiel Update oder DLC installieren</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia ist nicht real</string>
<string name="copied_to_clipboard">In die Zwischenablage kopiert</string>
@@ -92,7 +107,10 @@
<string name="contributors">Beitragende</string>
<string name="contributors_description">Gemacht mit \u2764 vom yuzu Team</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Projekte, die yuzu für Android möglich machen </string>
<string name="build">Build</string>
+ <string name="user_data">Nutzerdaten</string>
+ <string name="user_data_export_cancelled">Export abgebrochen</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>
@@ -107,45 +125,39 @@
<string name="early_access_updates">Früherer Zugriff auf Updates</string>
<string name="no_manual_installation">Keine manuelle Installation</string>
<string name="prioritized_support">Priorisierte Unterstützung</string>
+ <string name="helping_game_preservation">Beitrag zur Erhaltung der Spiele</string>
<string name="our_eternal_gratitude">Unsere ewige Dankbarkeit</string>
<string name="are_you_interested">Bist du interessiert?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Geschwindigkeitsbegrenzung aktivieren</string>
- <string name="frame_limit_enable_description">Wenn aktiviert, wird die Emulationsgeschwindigkeit auf einen Prozentsatz der normalen Geschwindigkeit begrenzt.</string>
+ <string name="frame_limit_enable">Limitierte Geschwindigkeit</string>
+ <string name="frame_limit_enable_description">Limitiert die Geschwindigkeit auf einen von dir festgelegten Prozentsatz.</string>
<string name="frame_limit_slider">Geschwindkeitsbegrenzung in Prozent</string>
- <string name="frame_limit_slider_description">Legt den Prozentsatz der Bergrenzung der Emulationsgeschwindigkeit fest. Mit dem Standardwert von 100% wird die Emulation auf die normale Geschwindigkeit begrenzt. Höhere oder niedrigere Werte erhöhen oder verringern die Geschwindigkeitsbegrenzung.</string>
+ <string name="frame_limit_slider_description">Gibt die prozentuale Geschwindigkeit der Emulation an. 100% sind normal. Werte darüber oder drunter werden die Geschwindigkeit entsprechend verändern.</string>
<string name="cpu_accuracy">CPU-Genauigkeit</string>
-
<!-- System settings strings -->
- <string name="use_docked_mode">Dock-Modus</string>
- <string name="use_docked_mode_description">Emuliert im Dock-Modus, was die Auflösung verbessert, aber die Leistung senkt.</string>
+ <string name="use_docked_mode">Gedockter Modus</string>
+ <string name="use_docked_mode_description">Der Docked Modus erhöht die Auflösung, verringert die aber die Leistung. Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die Leistung.</string>
<string name="emulated_region">Emulierte Region</string>
<string name="emulated_language">Emulierte Sprache</string>
<string name="select_rtc_date">RTC-Datum auswählen</string>
<string name="select_rtc_time">RTC-Zeit auswählen</string>
- <string name="use_custom_rtc">Benutzerdefinierte RTC aktivieren</string>
- <string name="use_custom_rtc_description">Mit dieser Einstellung kann eine benutzerdefinierte Echtzeituhr unabhängig von der aktuellen Systemzeit verwendet werden.</string>
- <string name="set_custom_rtc">Benutzerdefinierte RTC einstellen</string>
-
+ <string name="use_custom_rtc">Benutzerdefinierte Echtzeituhr</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Genauigkeitsstufe</string>
- <string name="renderer_resolution">Auflösung</string>
<string name="renderer_vsync">VSync-Modus</string>
+ <string name="renderer_screen_layout">Orientierung</string>
<string name="renderer_aspect_ratio">Seitenverhältnis</string>
<string name="renderer_scaling_filter">Fensteranpassungsfilter</string>
- <string name="renderer_anti_aliasing">Kantenglättungs-Methode</string>
<string name="renderer_force_max_clock">Maximale Taktfrequenz erzwingen (nur Adreno)</string>
<string name="renderer_force_max_clock_description">Erzwingt den Betrieb der GPU mit der maximal möglichen Taktfrequenz (Temperaturbeschränkungen werden weiterhin angewendet).</string>
<string name="renderer_asynchronous_shaders">Asynchrone Shader nutzen</string>
- <string name="renderer_asynchronous_shaders_description">Kompiliert Shader asynchron, was Ruckler reduziert, aber zu Glitches führen kann.</string>
- <string name="renderer_debug">Grafik-Debugging aktivieren</string>
- <string name="renderer_debug_description">Wenn aktiviert, schaltet die Grafik-API in einen langsameren Debugging-Modus.</string>
- <string name="use_disk_shader_cache">Nutze Festplatten-Shader-Cache</string>
- <string name="use_disk_shader_cache_description">Ruckeln wird durch das Speichern und Laden von generierten Shadern auf der Festplatte reduziert.</string>
-
- <!-- Audio settings strings -->
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">CPU Debugging</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Graphik-Debugging</string>
<string name="audio_volume">Lautstärke</string>
<string name="audio_volume_description">Legt die Lautstärke der Audioausgabe fest.</string>
@@ -154,14 +166,22 @@
<string name="ini_saved">Einstellungen gespeichert</string>
<string name="gameid_saved">Einstellungen für %1$s gespeichert</string>
<string name="error_saving">Fehler beim Speichern von %1$s.ini: %2$s</string>
+ <string name="unimplemented_menu">Unimplementiertes Menü</string>
<string name="loading">Lädt...</string>
<string name="reset_setting_confirmation">Möchtest du diese Einstellung auf den Standardwert zurücksetzen?</string>
<string name="reset_to_default">Auf Standard zurücksetzen</string>
<string name="reset_all_settings">Alle Einstellungen zurücksetzen?</string>
- <string name="reset_all_settings_description">Alle erweiterten Einstellungen werden auf ihren Standardwert zurückgesetzt. Dies kann nicht rückgängig gemacht werden.</string>
<string name="settings_reset">Einstellungen zurückgesetzt</string>
<string name="close">Schließen</string>
<string name="learn_more">Mehr erfahren</string>
+ <string name="auto">Auto</string>
+ <string name="submit">Absenden</string>
+ <string name="string_null">Null</string>
+ <string name="string_import">Importieren</string>
+ <string name="export">Exportieren</string>
+ <string name="export_failed">Export fehlgeschlagen</string>
+ <string name="import_failed">Import fehlgeschlagen</string>
+ <string name="cancelling">Abbrechen</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">GPU-Treiber auswählen</string>
@@ -169,6 +189,7 @@
<string name="select_gpu_driver_install">Installieren</string>
<string name="select_gpu_driver_default">Standard</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>
@@ -179,6 +200,7 @@
<string name="preferences_graphics">Grafik</string>
<string name="preferences_audio">Audio</string>
<string name="preferences_theme">Theme und Farbe</string>
+ <string name="preferences_debug">Debug</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Das ROM ist verschlüsselt</string>
@@ -192,22 +214,15 @@
<string name="emulation_exit">Emulation beenden</string>
<string name="emulation_done">Fertig</string>
<string name="emulation_fps_counter">FPS Zähler</string>
- <string name="emulation_toggle_controls">Steuerung umschalten</string>
- <string name="emulation_rel_stick_center">Relative Stick-Mitte</string>
- <string name="emulation_dpad_slide">DPad Slide</string>
- <string name="emulation_haptics">Haptik</string>
- <string name="emulation_show_overlay">Overlay anzeigen</string>
<string name="emulation_toggle_all">Alle umschalten</string>
<string name="emulation_control_adjust">Overlay anpassen</string>
<string name="emulation_control_scale">Größe</string>
<string name="emulation_control_opacity">Transparenz</string>
<string name="emulation_touch_overlay_reset">Overlay zurücksetzen</string>
<string name="emulation_touch_overlay_edit">Overlay bearbeiten</string>
- <string name="emulation_pause">Emulation pausieren</string>
- <string name="emulation_unpause">Emulation fortsetzen</string>
<string name="emulation_input_overlay">Overlay-Optionen</string>
- <string name="load_settings">Lädt Einstellungen...</string>
+ <string name="load_settings">Lade Einstellungen...</string>
<!-- Software keyboard -->
<string name="software_keyboard">Software-Tastatur</string>
@@ -221,7 +236,7 @@
<string name="fatal_error">Schwerwiegender Fehler</string>
<string name="fatal_error_message">Ein schwerwiegender Fehler ist aufgetreten. Einzelheiten wurden im Log protokolliert.\nDas Fortsetzen der Emulation kann zu Abstürzen und Bugs führen.</string>
<string name="performance_warning">Das Deaktivieren dieser Einstellung führt zu erheblichen Leistungsverlusten! Für ein optimales Erlebnis wird empfohlen, sie aktiviert zu lassen.</string>
-
+ <string name="memory_formatted">%1$s %2$s</string>
<!-- Region Names -->
<string name="region_japan">Japan</string>
<string name="region_usa">USA</string>
@@ -231,6 +246,15 @@
<string name="region_korea">Korea</string>
<string name="region_taiwan">Taiwan</string>
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
+
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
<string name="renderer_none">Keiner</string>
@@ -267,12 +291,15 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <string name="screen_layout_portrait">Portrait</string>
+ <string name="screen_layout_auto">Auto</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Standard (16:9)</string>
<string name="ratio_force_four_three">4:3 erzwingen</string>
<string name="ratio_force_twenty_one_nine">21:9 erzwingen</string>
<string name="ratio_force_sixteen_ten">Erzwinge 16:10</string>
- <string name="ratio_stretch">Auf Fenster anpassen</string>
+ <string name="ratio_stretch">Auf Bildschirmgröße anpsassen</string>
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Akkurat</string>
@@ -280,9 +307,9 @@
<string name="cpu_accuracy_paranoid">Paranoid (Langsam)</string>
<!-- Gamepad Buttons -->
- <string name="gamepad_d_pad">Steuerkreuz</string>
- <string name="gamepad_left_stick">Linker Analogstick</string>
- <string name="gamepad_right_stick">Rechter Analogstick</string>
+ <string name="gamepad_d_pad">D-Pad</string>
+ <string name="gamepad_left_stick">Linker Stick</string>
+ <string name="gamepad_right_stick">Rechter Stick</string>
<string name="gamepad_home">Home</string>
<string name="gamepad_screenshot">Screenshot</string>
@@ -291,18 +318,30 @@
<string name="building_shaders">Shader werden erstellt</string>
<!-- Theme options -->
- <string name="change_app_theme">App-Theme ändern</string>
+ <string name="change_app_theme">App-Thema ändern</string>
<string name="theme_default">Standard</string>
<string name="theme_material_you">Material You</string>
<!-- Theme Modes -->
- <string name="change_theme_mode">Theme-Modus ändern</string>
+ <string name="change_theme_mode">Themen-Modus ändern</string>
<string name="theme_mode_follow_system">System folgen</string>
<string name="theme_mode_light">Hell</string>
<string name="theme_mode_dark">Dunkel</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Schwarze Hintergünde verwenden</string>
+ <string name="use_black_backgrounds">Schwarze Hintergründe</string>
<string name="use_black_backgrounds_description">Bei Verwendung des dunklen Themes, schwarze Hintergründe verwenden.</string>
-</resources>
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">Bild im Bild</string>
+ <string name="pause">Pause</string>
+ <string name="mute">Stummschalten</string>
+ <string name="unmute">Ton aktivieren</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Lizenzen</string>
+ <string name="license_fidelityfx_fsr_description">Hochwertiges Upscaling von AMD</string>
+ </resources>
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 e5bdd5889..103ac6e65 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
- <string name="app_disclaimer">Este software ejecuta juegos para la videoconsola Nintendo Switch. Los videojuegos o keys no vienen incluidos.&lt;br /&gt;&lt;br /&gt;Antes de empezar, por favor, localice el archivo <![CDATA[<b> prod.keys </b>]]>en el almacenamiento de su dispositivo..&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Saber más</a>]]></string>
+ <string name="app_disclaimer">Este software ejecuta juegos para la videoconsola Nintendo Switch. Los videojuegos o claves no vienen incluidos.&lt;br /&gt;&lt;br /&gt;Antes de empezar, por favor, localice el archivo <![CDATA[<b> prod.keys </b>]]>en el almacenamiento de su dispositivo..&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Saber más</a>]]></string>
<string name="emulation_notification_channel_name">Emulación activa</string>
<string name="emulation_notification_channel_description">Muestra una notificación persistente cuando la emulación está activa.</string>
<string name="emulation_notification_running">yuzu esta ejecutándose</string>
@@ -25,6 +25,7 @@
<string name="back">Atrás</string>
<string name="add_games">Añadir Juegos</string>
<string name="add_games_description">Selecciona la carpeta de juegos</string>
+ <string name="step_complete">¡Completado!</string>
<!-- Home strings -->
<string name="home_games">Juegos</string>
@@ -37,7 +38,8 @@
<string name="add_games_warning">¿Omitir la selección de la carpeta de juegos?</string>
<string name="add_games_warning_description">No se mostrará ningún juego si no se ha seleccionado una carpeta de juegos.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
- <string name="home_search_games">Buscar Juegos</string>
+ <string name="home_search_games">Buscar juegos</string>
+ <string name="search_settings">Buscar configuración</string>
<string name="games_dir_selected">Directorio de juegos seleccionado</string>
<string name="install_prod_keys">Instalar prod.keys</string>
<string name="install_prod_keys_description">Requerido para descifrar juegos</string>
@@ -58,15 +60,18 @@
<string name="warning_cancel">Cancelar</string>
<string name="install_amiibo_keys">Instalar clave de Amiiboo</string>
<string name="install_amiibo_keys_description">Necesario para usar Amiibo en el juego</string>
- <string name="invalid_keys_file">Archivo de claves inválido seleccionado</string>
+ <string name="invalid_keys_file">Archivo de claves seleccionado inválido</string>
<string name="install_keys_success">Claves instaladas correctamente</string>
<string name="reading_keys_failure">Error al leer las claves de cifrado</string>
+ <string name="install_prod_keys_failure_extension_description">Compruebe que el archivo de claves tenga una extensión .keys y pruebe otra vez.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Compruebe que el archivo de claves tenga una extensión .bin y pruebe otra vez.</string>
<string name="invalid_keys_error">Claves de cifrado no válidas</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">El archivo seleccionado es incorrecto o está corrupto. Vuelva a redumpear sus claves.</string>
<string name="install_gpu_driver">Instalar driver de GPU</string>
<string name="install_gpu_driver_description">Instale drivers alternativos para obtener un rendimiento o una precisión potencialmente mejores</string>
<string name="advanced_settings">Opciones avanzadas</string>
+ <string name="advanced_settings_game">Configuración avanzada: %1$s</string>
<string name="settings_description">Configurar las opciones del emulador</string>
<string name="search_recently_played">Jugado recientemente</string>
<string name="search_recently_added">Añadido recientemente</string>
@@ -86,6 +91,33 @@
<string name="save_file_invalid_zip_structure_description">El nombre de la primera subcarpeta debe ser el Title ID del juego.</string>
<string name="import_saves">Importar</string>
<string name="export_saves">Exportar</string>
+ <string name="install_firmware">Instalar firmware</string>
+ <string name="install_firmware_description">El firmware debe estar en un archivo ZIP y es necesario para ejecutar algunos juegos</string>
+ <string name="firmware_installing">Instalando firmware</string>
+ <string name="firmware_installed_success">Firmware instalado con éxito</string>
+ <string name="firmware_installed_failure">Falló la instalación de firmware</string>
+ <string name="firmware_installed_failure_description">Asegúrese de que los archivos nca del firmware estén en la raíz del zip e inténtelo de nuevo.</string>
+ <string name="share_log">Compartir registros de depuración</string>
+ <string name="share_log_description">Comparta el archivo de registro de yuzu para depurar problemas</string>
+ <string name="share_log_missing">No se encontró ningún archivo de registro</string>
+ <string name="install_game_content">Instalar contenido de juego</string>
+ <string name="install_game_content_description">Instalar actualizaciones o DLC</string>
+ <string name="installing_game_content">Instalando contenido...</string>
+ <string name="install_game_content_failure">Error instalando archivo(s) a la NAND</string>
+ <string name="install_game_content_failure_description">Asegúrese de que el/los contenido(s) son válidos y que el archivo prod.keys esté instalado.</string>
+ <string name="install_game_content_failure_base">La instalación de los juegos base no está permitida para así evitar posibles conflictos.</string>
+ <string name="install_game_content_failure_file_extension">Sólo hay soporte para el contenido en NSP y XCI. Asegúrese de que el/los contenido(s) son válidos.</string>
+ <string name="install_game_content_failed_count">%1$d error(es) de instalación</string>
+ <string name="install_game_content_success">Contenido(s) de juego instalado/s con éxito</string>
+ <string name="install_game_content_success_install">%1$d instalado con éxito</string>
+ <string name="install_game_content_success_overwrite">%1$d sobreescrito con éxito</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">Drivers personalizados no soportados</string>
+ <string name="custom_driver_not_supported_description">En estos momentos, la carga de drivers personalizados no está disponible para este dispositivo..\n¡Comprueba esta opción en el futuro para ver si ya está añadido el soporte a ese dispositivo!</string>
+ <string name="manage_yuzu_data">Administrar datos de yuzu</string>
+ <string name="manage_yuzu_data_description">Importa/exporta el firmware, las keys, los datos de usuario, ¡y más!</string>
+ <string name="share_save_file">Compartir archivo de guardado</string>
+ <string name="export_save_failed">La exportación del guardado falló</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia no es real</string>
@@ -94,7 +126,18 @@
<string name="contributors">Contribuidores</string>
<string name="contributors_description">Hecho con \u2764 del equipo yuzu</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Proyectos que hacen que yuzu para Android sea una realidad</string>
<string name="build">Versión</string>
+ <string name="user_data">Datos de usuario</string>
+ <string name="user_data_description">Importa/exporta todos los datos de usuario.\n\nCuando se importen los datos de usuario, ¡los demás datos de usuario existentes serán borrados!</string>
+ <string name="exporting_user_data">Exportando datos de usuario...</string>
+ <string name="importing_user_data">Importando datos de usuario...</string>
+ <string name="import_user_data">Importar datos de usuario</string>
+ <string name="invalid_yuzu_backup">Backup de válido</string>
+ <string name="user_data_export_success">Datos de usuario exportados con éxito</string>
+ <string name="user_data_import_success">Datos de usuario importados con éxito</string>
+ <string name="user_data_export_cancelled">Exportación cancelada</string>
+ <string name="user_data_import_failed_description">Asegúrese de que las carpetas de datos de usuario estén en la raíz de la carpeta del zip y contengan un archivo config en config/config.ini e inténtelo de nuevo.</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>
@@ -114,41 +157,53 @@
<string name="are_you_interested">¿Estás interesado?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Activar limite de velocidad</string>
- <string name="frame_limit_enable_description">Cuando está habilitado, la velocidad de emulación se limitará a un porcentaje específico de la velocidad normal.</string>
+ <string name="frame_limit_enable">Limitar velocidad</string>
+ <string name="frame_limit_enable_description">Limita la velocidad de emulación a un porcentaje específico de la velocidad normal.</string>
<string name="frame_limit_slider">Limitar porcentaje de velocidad</string>
- <string name="frame_limit_slider_description">Especifica el porcentaje para limitar la velocidad de emulación. Con el valor predeterminado del 100 %, la emulación se limitará a la velocidad normal. Valores más altos o más bajos aumentarán o disminuirán el límite de velocidad.</string>
+ <string name="frame_limit_slider_description">Especifica el porcentaje para limitar la velocidad de emulación. 100% es la velocidad normal. Valores más altos o bajos incrementarán o disminuirán el límite de velocidad.</string>
<string name="cpu_accuracy">Precisión de CPU</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
- <string name="use_docked_mode">Modo sobremesa</string>
- <string name="use_docked_mode_description">Emula en modo sobremesa, lo que aumenta la resolución perjudicando el rendimiento.</string>
+ <string name="use_docked_mode">Modo Sobremesa</string>
+ <string name="use_docked_mode_description">Incrementa la resolución al coste de reducir el rendimiento. El Modo Portátil es usado cuando está desactivado, reduciendo la resolución y mejorando así el rendimiento.</string>
<string name="emulated_region">Región emulada</string>
<string name="emulated_language">Idioma emulado</string>
- <string name="select_rtc_date">Seleccionar Fecha RTC</string>
- <string name="select_rtc_time">Seleccionar Tiempo RTC</string>
- <string name="use_custom_rtc">Habilitar RTC Personalizado</string>
- <string name="use_custom_rtc_description">Esta configuración le permite configurar un reloj de tiempo real personalizado diferente a la hora actual de su sistema</string>
- <string name="set_custom_rtc">Establecer RTC Personalizado</string>
+ <string name="select_rtc_date">Seleccionar fecha RTC</string>
+ <string name="select_rtc_time">Seleccionar tiempo RTC</string>
+ <string name="use_custom_rtc">RTC personalizado</string>
+ <string name="use_custom_rtc_description">Te permite tener un reloj personalizado en tiempo real diferente del tiempo del propio sistema.</string>
+ <string name="set_custom_rtc">Configurar RTC personalizado</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Nivel de precisión</string>
- <string name="renderer_resolution">Resolución</string>
+ <string name="renderer_resolution">Resolución (Portátil/Sobremesa)</string>
<string name="renderer_vsync">Modo VSync</string>
+ <string name="renderer_screen_layout">Orientación</string>
<string name="renderer_aspect_ratio">Relación de aspecto</string>
<string name="renderer_scaling_filter">Filtro de adaptación de ventana</string>
- <string name="renderer_anti_aliasing">Metodo Anti Aliasing</string>
+ <string name="renderer_anti_aliasing">Método anti-aliasing</string>
<string name="renderer_force_max_clock">Forzar velocidad al máximo (solo Adreno)</string>
<string name="renderer_force_max_clock_description">Fuerza a la GPU a ejecutarse a la velocidad máxima de reloj posible (se seguirán aplicando restricciones térmicas).</string>
<string name="renderer_asynchronous_shaders">Usar shaders asíncronos</string>
- <string name="renderer_asynchronous_shaders_description">Compila shaders de forma asincrónica, lo que reducirá los parones pero puede introducir fallos.</string>
- <string name="renderer_debug">Habilitar la depuración de gráficos</string>
- <string name="renderer_debug_description">Cuando esté marcado, la API de gráficos entra en un modo de depuración más lento.</string>
- <string name="use_disk_shader_cache">Usar caché de shaders en disco</string>
- <string name="use_disk_shader_cache_description">Reduzca los parones almacenando y cargando shaders generados en el disco.</string>
+ <string name="renderer_asynchronous_shaders_description">Compila shaders de manera asíncrona, reduciendo los parones, pero puede introducir fallos.</string>
+ <string name="renderer_reactive_flushing">Usar limpieza reactiva</string>
+ <string name="renderer_reactive_flushing_description">Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento.</string>
+ <string name="use_disk_shader_cache">Caché de shaders en disco</string>
+ <string name="use_disk_shader_cache_description">Reduce los parones almacenando y cargando shaders generados.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">Depuración de CPU</string>
+ <string name="cpu_debug_mode_description">Pone la CPU en un modo de depuración lento.</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Depuración de gráficos</string>
+ <string name="renderer_debug_description">Configura la API gráfica a un modo de depuración lento.</string>
+ <string name="fastmem">Fastmem</string>
<!-- Audio settings strings -->
+ <string name="audio_output_engine">Motor de salida</string>
<string name="audio_volume">Volumen</string>
<string name="audio_volume_description">Especifica el volumen de la salida de audio.</string>
@@ -157,14 +212,24 @@
<string name="ini_saved">Configuración guardada</string>
<string name="gameid_saved">Configuración guardada para %1$s</string>
<string name="error_saving">Error guardando %1$s.ini: %2$s</string>
+ <string name="unimplemented_menu">Menú sin implementar</string>
<string name="loading">Cargando...</string>
+ <string name="shutting_down">Saliendo...</string>
<string name="reset_setting_confirmation">¿Desea restablecer esta configuración a su valor predeterminado?</string>
<string name="reset_to_default">Restablecer a predeterminado</string>
<string name="reset_all_settings">¿Restablecer todas las configuraciones?</string>
- <string name="reset_all_settings_description">Todas las configuraciones avanzadas se restablecerán a su configuración predeterminada. Esto no se puede deshacer.</string>
+ <string name="reset_all_settings_description">Todas las opciones avanzadas se restablecerán a su configuración predeterminada. Esta acción no se puede deshacer.</string>
<string name="settings_reset">Reiniciar la configuracion</string>
<string name="close">Cerrar</string>
- <string name="learn_more">Más información</string>
+ <string name="learn_more">Saber más</string>
+ <string name="auto">Auto</string>
+ <string name="submit">Enviar</string>
+ <string name="string_null">Null</string>
+ <string name="string_import">Importar</string>
+ <string name="export">Exportar</string>
+ <string name="export_failed">La exportación falló</string>
+ <string name="import_failed">La importación falló</string>
+ <string name="cancelling">Cancelando</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Seleccionar driver GPU</string>
@@ -172,6 +237,7 @@
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Predeterminado</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>
@@ -182,10 +248,11 @@
<string name="preferences_graphics">Gráficos</string>
<string name="preferences_audio">Audio</string>
<string name="preferences_theme">Tema y color</string>
+ <string name="preferences_debug">Depuración</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Su ROM está encriptada</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor, siga las guías para redumpear <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">cartuchos de juegos</a> o <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">titulos instalados</a>.]]></string>
+ <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor, siga las guías para redumpear<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards\">cartuchos de juegos</a> o <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop\">títulos instalados</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Por favor, compruebe que su archivo <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> está instalado, para que los juegos sean descifrados.]]></string>
<string name="loader_error_video_core">Ocurrió un error al inicializar el núcleo de video, posiblemente debido a una incompatibilidad con el driver seleccionado</string>
<string name="loader_error_video_core_description">Esto suele deberse a un driver de GPU incompatible. La instalación de un controlador de GPU personalizado puede resolver este problema.</string>
@@ -196,25 +263,25 @@
<string name="emulation_exit">Salir de la emulación</string>
<string name="emulation_done">Hecho</string>
<string name="emulation_fps_counter">Contador de FPS</string>
- <string name="emulation_toggle_controls">Alternar Controles</string>
- <string name="emulation_rel_stick_center">Centro Relativo del Stick</string>
- <string name="emulation_dpad_slide">Deslizamiento de la Cruceta</string>
- <string name="emulation_haptics">Hápticos</string>
- <string name="emulation_show_overlay">Mostrar pantalla</string>
- <string name="emulation_toggle_all">Alternar Todo</string>
- <string name="emulation_control_adjust">Ajustar pantalla</string>
+ <string name="emulation_toggle_controls">Alternar controles</string>
+ <string name="emulation_rel_stick_center">Centro relativo del stick</string>
+ <string name="emulation_dpad_slide">Deslizamiento de la cruceta</string>
+ <string name="emulation_haptics">Toques hápticos</string>
+ <string name="emulation_show_overlay">Mostrar overlay</string>
+ <string name="emulation_toggle_all">Alternar todo</string>
+ <string name="emulation_control_adjust">Ajustar overlay</string>
<string name="emulation_control_scale">Escala</string>
<string name="emulation_control_opacity">Opacidad</string>
- <string name="emulation_touch_overlay_reset">Reiniciar pantalla</string>
- <string name="emulation_touch_overlay_edit">Editar pantalla</string>
- <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_touch_overlay_reset">Reiniciar overlay</string>
+ <string name="emulation_touch_overlay_edit">Editar overlay</string>
+ <string name="emulation_pause">Pausar emulación</string>
+ <string name="emulation_unpause">Despausar emulación</string>
+ <string name="emulation_input_overlay">Opciones de overlay</string>
<string name="load_settings">Cargando configuración...</string>
<!-- Software keyboard -->
- <string name="software_keyboard">Software del teclado</string>
+ <string name="software_keyboard">Teclado de software</string>
<!-- Errors and warnings -->
<string name="abort_button">Abortar</string>
@@ -226,6 +293,9 @@
<string name="fatal_error">Error fatal</string>
<string name="fatal_error_message">Ocurrió un error fatal. Consulte el registro para obtener más detalles.\nContinuar con la emulación puede provocar bloqueos y errores.</string>
<string name="performance_warning">¡Desactivar esta configuración reducirá significativamente el rendimiento de la emulación! Para obtener la mejor experiencia, se recomienda dejar esta configuración habilitada.</string>
+ <string name="device_memory_inadequate">RAM de dispositivo: %1$s\nRecomendado: %2$s</string>
+ <string name="memory_formatted">%1$s %2$s</string>
+ <string name="no_game_present">¡No hay ningún juego ejecutable presente!</string>
<!-- Region Names -->
<string name="region_japan">Japón</string>
@@ -236,7 +306,14 @@
<string name="region_korea">Corea</string>
<string name="region_taiwan">Taiwán</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
@@ -274,6 +351,11 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">Paisaje</string>
+ <string name="screen_layout_portrait">Retrato</string>
+ <string name="screen_layout_auto">Auto</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Predeterminado (16:9)</string>
<string name="ratio_force_four_three">Forzar 4:3</string>
@@ -298,7 +380,7 @@
<string name="building_shaders">Construyendo shaders</string>
<!-- Theme options -->
- <string name="change_app_theme">Cambiar Tema</string>
+ <string name="change_app_theme">Cambiar tema</string>
<string name="theme_default">Predeterminado</string>
<string name="theme_material_you">Material You</string>
@@ -308,8 +390,22 @@
<string name="theme_mode_light">Claro</string>
<string name="theme_mode_dark">Oscuro</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Usar Fondos Negros</string>
+ <string name="use_black_backgrounds">Fondos oscuros</string>
<string name="use_black_backgrounds_description">Cuando utilice el modo oscuro, aplique fondos negros.</string>
-</resources>
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">Picture in Picture</string>
+ <string name="picture_in_picture_description">Minimizar ventana cuando esté en segundo plano</string>
+ <string name="pause">Pausar</string>
+ <string name="play">Jugar</string>
+ <string name="mute">Mutear</string>
+ <string name="unmute">Desmutear</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Licencias</string>
+ <string name="license_fidelityfx_fsr_description">Upscaling de alta calidad de AMD</string>
+ </resources>
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 1e02828aa..5a827c50b 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">Ce logiciel exécutera des jeux pour la console de jeu Nintendo Switch. Aucun jeux ou clés n\'est inclus.&lt;br /&gt;&lt;br /&gt;Avant de commencer, veuillez localiser votre fichier <![CDATA[<b> prod.keys </b>]]> sur le stockage de votre appareil.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">En savoir plus</a>]]></string>
<string name="emulation_notification_channel_name">L\'émulation est active</string>
@@ -19,12 +19,13 @@
<string name="games">Jeux</string>
<string name="games_description">Sélectionnez votre dossier &lt;b>de Jeux&lt;/b> avec le bouton ci-dessous.</string>
<string name="done">Terminé</string>
- <string name="done_description">Vous êtes prêt.\nProfitez de vos jeux !</string>
+ <string name="done_description">Vous êtes prêt.\nProfitez de vos jeux !</string>
<string name="text_continue">Continuer</string>
<string name="next">Suivant</string>
<string name="back">Retour</string>
<string name="add_games">Ajouter des jeux</string>
- <string name="add_games_description">Sélectionner votre dossier de jeux</string>
+ <string name="add_games_description">Sélectionner le dossier des jeux</string>
+ <string name="step_complete">Terminé !</string>
<!-- Home strings -->
<string name="home_games">Jeux</string>
@@ -32,12 +33,13 @@
<string name="home_settings">Paramètres</string>
<string name="empty_gamelist">Aucun fichier n\'a été trouvé ou aucun répertoire de jeu n\'a encore été sélectionné.</string>
<string name="search_and_filter_games">Rechercher et filtrer les jeux</string>
- <string name="select_games_folder">Sélectionner le dossier de jeux</string>
+ <string name="select_games_folder">Sélectionner le dossier des jeux</string>
<string name="select_games_folder_description">Permet à yuzu de remplir la liste des jeux</string>
- <string name="add_games_warning">Ne pas sélectionner le dossier des jeux ?</string>
+ <string name="add_games_warning">Ne pas sélectionner le dossier des jeux ?</string>
<string name="add_games_warning_description">Les jeux ne seront pas affichés dans la liste des jeux si aucun dossier n\'est sélectionné.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Rechercher des jeux</string>
+ <string name="search_settings">Rechercher un paramètre</string>
<string name="games_dir_selected">Répertoire de jeux sélectionné</string>
<string name="install_prod_keys">Installer prod.keys</string>
<string name="install_prod_keys_description">Nécessaire pour décrypter les jeux commerciaux.</string>
@@ -46,7 +48,7 @@
<string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
<string name="notifications">Notifications</string>
<string name="notifications_description">Accordez l\'autorisation de notification avec le bouton ci-dessous.</string>
- <string name="give_permission">Donner la permission</string>
+ <string name="give_permission">Accorder la permission</string>
<string name="notification_warning">Ne pas accorder la permission de notification ?</string>
<string name="notification_warning_description">yuzu ne pourra pas vous communiquer d\'informations importantes.</string>
<string name="permission_denied">Permission refusée</string>
@@ -61,12 +63,15 @@
<string name="invalid_keys_file">Fichier de clés sélectionné invalide</string>
<string name="install_keys_success">Clés installées avec succès</string>
<string name="reading_keys_failure">Erreur lors de la lecture des clés de chiffrement</string>
+ <string name="install_prod_keys_failure_extension_description">Vérifiez que votre fichier de clés a une extension .keys et réessayez.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Vérifiez que votre fichier de clés a une extension .bin et réessayez.</string>
<string name="invalid_keys_error">Clés de chiffrement invalides</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">Le fichier sélectionné est incorrect ou corrompu. Veuillez dumper à nouveau vos clés.</string>
<string name="install_gpu_driver">Installer le pilote du GPU</string>
- <string name="install_gpu_driver_description">Installez des pilotes alternatifs pour des performances ou une précision potentiellement meilleures</string>
+ <string name="install_gpu_driver_description">Installer des pilotes alternatifs pour des performances ou une précision potentiellement meilleures</string>
<string name="advanced_settings">Paramètres avancés</string>
+ <string name="advanced_settings_game">Paramètres avancés : %1$s</string>
<string name="settings_description">Configurer les paramètres de l\'émulateur</string>
<string name="search_recently_played">Joué récemment</string>
<string name="search_recently_added">Ajouté récemment</string>
@@ -86,6 +91,33 @@
<string name="save_file_invalid_zip_structure_description">Le nom du premier sous-dossier doit être l\'identifiant du titre du jeu.</string>
<string name="import_saves">Importer</string>
<string name="export_saves">Exporter</string>
+ <string name="install_firmware">Installer le firmware</string>
+ <string name="install_firmware_description">Le firmware doit être dans une archive ZIP et est nécessaire pour démarrer certains jeux.</string>
+ <string name="firmware_installing">Installation du firmware</string>
+ <string name="firmware_installed_success">Firmware installé avec succès</string>
+ <string name="firmware_installed_failure">L\'installation du firmware a échoué</string>
+ <string name="firmware_installed_failure_description">Assurez-vous que les fichiers NCA du firmware se trouvent à la racine du fichier ZIP, puis réessayez.</string>
+ <string name="share_log">Partager les logs de débogage</string>
+ <string name="share_log_description">Partagez le fichier de log de yuzu pour déboguer les problèmes.</string>
+ <string name="share_log_missing">Aucun fichier de log trouvé</string>
+ <string name="install_game_content">Installer le contenu du jeu</string>
+ <string name="install_game_content_description">Installer une mise à jour ou un DLC</string>
+ <string name="installing_game_content">Installation du contenu en cours...</string>
+ <string name="install_game_content_failure">Erreur lors de l\'installation du fichier dans la NAND</string>
+ <string name="install_game_content_failure_description">Veuillez vous assurer que le contenu est valide et que le fichier prod.keys est installé.</string>
+ <string name="install_game_content_failure_base">L\'installation de jeux de base n\'est pas autorisée afin d\'éviter d\'éventuels conflits.</string>
+ <string name="install_game_content_failure_file_extension">Seuls les contenus NSP et XCI sont pris en charge. Veuillez vérifier que le contenu du jeu est valide.</string>
+ <string name="install_game_content_failed_count">%1$d erreur(s) d\'installation</string>
+ <string name="install_game_content_success">Contenu du jeu installé avec succès</string>
+ <string name="install_game_content_success_install">%1$d installé avec succès</string>
+ <string name="install_game_content_success_overwrite">%1$d écrasé avec succès</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">Pilotes personnalisés non supporté</string>
+ <string name="custom_driver_not_supported_description">Le chargement des pilotes personnalisés ne sont pas actuellement pris en charge pour ce périphérique. Vérifiez à nouveau cette option à l\'avenir pour voir si la prise en charge a été ajoutée !</string>
+ <string name="manage_yuzu_data">Gérer les données de yuzu</string>
+ <string name="manage_yuzu_data_description">Importer/exporter le firmware, les clés, les données utilisateur, et bien plus encore !</string>
+ <string name="share_save_file">Partager le fichier de sauvegarde</string>
+ <string name="export_save_failed">Échec de l\'exportation de la sauvegarde</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia n\'est pas réel</string>
@@ -94,7 +126,18 @@
<string name="contributors">Contributeurs</string>
<string name="contributors_description">Fait avec \u2764 de l\'équipe yuzu</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Des projets qui rendent possible yuzu pour Android</string>
<string name="build">Build</string>
+ <string name="user_data">Données utilisateur</string>
+ <string name="user_data_description">Importer/exporter toutes les données de l\'application.\n\nLors de l\'importation des données utilisateur, toutes les données utilisateur existantes seront supprimées !</string>
+ <string name="exporting_user_data">Exportation des données utilisateur...</string>
+ <string name="importing_user_data">Importation des données utilisateur...</string>
+ <string name="import_user_data">Importer des données utilisateur</string>
+ <string name="invalid_yuzu_backup">Backup yuzu invalide</string>
+ <string name="user_data_export_success">Les données utilisateur ont été exportés avec succès</string>
+ <string name="user_data_import_success">Les données utilisateur ont été importées avec succès</string>
+ <string name="user_data_export_cancelled">Exportation annulée</string>
+ <string name="user_data_import_failed_description">Assurez-vous que les dossiers de données utilisateur se trouvent à la racine du dossier ZIP et contiennent un fichier de configuration à config/config.ini, puis réessayez.</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>
@@ -114,64 +157,87 @@
<string name="are_you_interested">Es tu intéressé ?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Activer la vitesse limite</string>
- <string name="frame_limit_enable_description">Lorsqu\'elle est activée, la vitesse d\'émulation sera limitée à un pourcentage spécifié de la vitesse normale.</string>
+ <string name="frame_limit_enable">Limitation de vitesse</string>
+ <string name="frame_limit_enable_description">Limiter la vitesse d\'émulation à un pourcentage spécifié de la vitesse normale</string>
<string name="frame_limit_slider">Limite en pourcentage de vitesse</string>
- <string name="frame_limit_slider_description">Spécifie le pourcentage pour limiter la vitesse d\'émulation. Avec la valeur par défaut de 100%, l\'émulation sera limitée à la vitesse normale. Des valeurs supérieures ou inférieures augmenteront ou diminueront la limite de vitesse.</string>
+ <string name="frame_limit_slider_description">Spécifier le pourcentage pour limiter la vitesse d\'émulation. 100% correspond à la vitesse normale. Des valeurs plus élevées ou plus basses augmenteront ou diminueront la limite de vitesse.</string>
<string name="cpu_accuracy">Précision du CPU</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
<string name="use_docked_mode">Mode TV</string>
- <string name="use_docked_mode_description">Émuler en mode TV augmente la résolution au détriment des performances.</string>
+ <string name="use_docked_mode_description">Augmenter la résolution, ce qui diminue les performances. Le mode portable est utilisé lorsque la fonction est désactivée, ce qui réduit la résolution et améliore les performances.</string>
<string name="emulated_region">Région émulée</string>
<string name="emulated_language">Langue émulée</string>
<string name="select_rtc_date">Sélectionner la date RTC</string>
<string name="select_rtc_time">Sélectionner l\'heure RTC</string>
- <string name="use_custom_rtc">Activer l\'horloge RTC personnalisée</string>
- <string name="use_custom_rtc_description">Ce paramètre vous permet de définir une horloge en temps réel personnalisée distincte de l\'heure actuelle de votre système.</string>
+ <string name="use_custom_rtc">RTC personnalisé</string>
+ <string name="use_custom_rtc_description">Vous permet de définir une horloge en temps réel personnalisée distincte de l\'heure actuelle de votre système.</string>
<string name="set_custom_rtc">Définir l\'horloge RTC personnalisée</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Niveau de précision</string>
- <string name="renderer_resolution">Résolution</string>
+ <string name="renderer_resolution">Résolution (Mode Portable/Mode TV)</string>
<string name="renderer_vsync">Mode VSync</string>
+ <string name="renderer_screen_layout">Orientation</string>
<string name="renderer_aspect_ratio">Format</string>
<string name="renderer_scaling_filter">Filtre de fenêtre adaptatif</string>
- <string name="renderer_anti_aliasing">Méthode d\'anticrénelage :</string>
- <string name="renderer_force_max_clock">Forcer la fréquence d\'horloge maximale (Adreno uniquement)</string>
- <string name="renderer_force_max_clock_description">Force le GPU à fonctionner au maximum d\'horloges possibles (les contraintes thermiques seront toujours appliquées).</string>
+ <string name="renderer_anti_aliasing">Méthode d\'anticrénelage</string>
+ <string name="renderer_force_max_clock">Forcer les fréquences maximales (Adreno uniquement)</string>
+ <string name="renderer_force_max_clock_description">Forcer le GPU à fonctionner à ses fréquences maximales possibles (les contraintes thermiques seront toujours appliquées).</string>
<string name="renderer_asynchronous_shaders">Utiliser les shaders asynchrones</string>
- <string name="renderer_asynchronous_shaders_description">Compile les shaders de manière asynchrone, ce qui réduira les saccades mais peut entraîner des problèmes visuels.</string>
- <string name="renderer_debug">Activer le débogage des graphismes</string>
- <string name="renderer_debug_description">Lorsque cette case est cochée, l\'API graphique entre dans un mode de débogage plus lent.</string>
- <string name="use_disk_shader_cache">Utiliser les shader cache de disque</string>
- <string name="use_disk_shader_cache_description">Réduire les saccades en stockant et en chargeant les shaders générés sur le disque.</string>
+ <string name="renderer_asynchronous_shaders_description">Compile les shaders de manière asynchrone, réduisant les saccades mais pouvant entraîner des problèmes visuels.</string>
+ <string name="renderer_reactive_flushing">Utiliser le vidage réactif</string>
+ <string name="renderer_reactive_flushing_description">Améliore la précision du rendu dans certains jeux au détriment des performances.</string>
+ <string name="use_disk_shader_cache">Utiliser les shader cache</string>
+ <string name="use_disk_shader_cache_description">Réduire les saccades en stockant et en chargeant localement les shaders générés</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">Débogage du CPU</string>
+ <string name="cpu_debug_mode_description">Place le CPU en mode lent de débogage.</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Débogage des graphismes</string>
+ <string name="renderer_debug_description">Définit l\'API graphique en mode de débogage lent.</string>
+ <string name="fastmem">Fastmem</string>
<!-- Audio settings strings -->
+ <string name="audio_output_engine">Moteur de sortie</string>
<string name="audio_volume">Volume</string>
- <string name="audio_volume_description">Spécifie le volume de la sortie audio.</string>
+ <string name="audio_volume_description">Spécifier le volume de la sortie audio.</string>
<!-- Miscellaneous -->
- <string name="slider_default">Défaut</string>
+ <string name="slider_default">Par défaut</string>
<string name="ini_saved">Paramètres enregistrés</string>
<string name="gameid_saved">Paramètres enregistrés pour %1$s</string>
<string name="error_saving">Erreur lors de l\'enregistrement de %1$s.ini: %2$s</string>
+ <string name="unimplemented_menu">Menu non implémenté</string>
<string name="loading">Chargement...</string>
- <string name="reset_setting_confirmation">Voulez-vous réinitialiser ce paramètre à sa valeur par défaut ?</string>
+ <string name="shutting_down">Extinction en cours...</string>
+ <string name="reset_setting_confirmation">Voulez-vous réinitialiser ce paramètre à sa valeur par défaut ?</string>
<string name="reset_to_default">Réinitialiser par défaut</string>
<string name="reset_all_settings">Réinitialiser tous les réglages ?</string>
<string name="reset_all_settings_description">Tous les paramètres avancés seront réinitialisés à leur configuration par défaut. Ça ne peut pas être annulé.</string>
<string name="settings_reset">Paramètres réinitialisés</string>
<string name="close">Fermer</string>
- <string name="learn_more">Plus d\'informations</string>
+ <string name="learn_more">En savoir plus</string>
+ <string name="auto">Auto</string>
+ <string name="submit">Soumettre</string>
+ <string name="string_null">Nul</string>
+ <string name="string_import">Importer</string>
+ <string name="export">Exporter</string>
+ <string name="export_failed">L\'exportation a échoué</string>
+ <string name="import_failed">L\'importation a échoué</string>
+ <string name="cancelling">Annulation</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Sélectionner le pilote du GPU</string>
<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_use_default">Utilisation du pilote de GPU par défaut</string>
+ <string name="select_gpu_driver_default">Par défaut</string>
+ <string name="select_gpu_driver_use_default">Utilisation du pilote du 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>
@@ -182,13 +248,14 @@
<string name="preferences_graphics">Vidéo</string>
<string name="preferences_audio">Audio</string>
<string name="preferences_theme">Thème et couleur</string>
+ <string name="preferences_debug">Débogage</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Votre ROM est cryptée</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Veuillez suivre les guides pour redumper vos <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">cartouches de jeu</a> ou <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">titres installés</a>.]]></string>
+ <string name="loader_error_encrypted_roms_description"><![CDATA[Veuillez suivre les guides pour refaire un dump de vos <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards\">cartouches de jeu</a> ou de vos <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop\">titres installés</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Veuillez vous assurer que votre fichier <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> est installé pour que les jeux puissent être déchiffrés.]]></string>
<string name="loader_error_video_core">Une erreur s\'est produite lors de l\'initialisation du noyau vidéo</string>
- <string name="loader_error_video_core_description">Cela est généralement dû à un pilote du GPU incompatible. L\'installation d\'un pilote du GPU personnalisé peut résoudre ce problème.</string>
+ <string name="loader_error_video_core_description">Cela est généralement dû à un pilote GPU incompatible. L\'installation d\'un pilote GPU personnalisé peut résoudre ce problème.</string>
<string name="loader_error_invalid_format">Impossible de charger la ROM</string>
<string name="loader_error_file_not_found">Le fichier ROM n\'existe pas</string>
@@ -198,8 +265,8 @@
<string name="emulation_fps_counter">Compteur FPS</string>
<string name="emulation_toggle_controls">Activer/Désactiver les contrôles</string>
<string name="emulation_rel_stick_center">Centre du stick relatif</string>
- <string name="emulation_dpad_slide">Glissement du DPad</string>
- <string name="emulation_haptics">Haptique</string>
+ <string name="emulation_dpad_slide">Glissement du D-pad</string>
+ <string name="emulation_haptics">Toucher haptique</string>
<string name="emulation_show_overlay">Afficher l\'overlay</string>
<string name="emulation_toggle_all">Tout basculer</string>
<string name="emulation_control_adjust">Ajuster l\'overlay</string>
@@ -225,7 +292,10 @@
<string name="save_load_error">Erreur de sauvegarde/chargement</string>
<string name="fatal_error">Erreur fatale</string>
<string name="fatal_error_message">Une erreur fatale s\'est produite. Consultez les logs pour plus de détails.\nContinuer l\'émulation peut entraîner des plantages et des bogues.</string>
- <string name="performance_warning">La désactivation de ce paramètre réduira considérablement les performances d\'émulation ! Pour une expérience optimale, il est recommandé de laisser ce paramètre activé.</string>
+ <string name="performance_warning">La désactivation de ce paramètre réduira considérablement les performances d\'émulation ! Pour une expérience optimale, il est recommandé de laisser ce paramètre activé.</string>
+ <string name="device_memory_inadequate">Mémoire RAM de l\'appareil : %1$s\nRecommandé : %2$s</string>
+ <string name="memory_formatted">%1$s %2$s</string>
+ <string name="no_game_present">Aucun jeu démarreable présent !</string>
<!-- Region Names -->
<string name="region_japan">Japon</string>
@@ -236,7 +306,14 @@
<string name="region_korea">Corée</string>
<string name="region_taiwan">Taïwan</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Octet</string>
+ <string name="memory_kilobyte">Ko</string>
+ <string name="memory_megabyte">Mo</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">To</string>
+ <string name="memory_petabyte">Po</string>
+ <string name="memory_exabyte">Eo</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
@@ -274,6 +351,11 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">Paysage</string>
+ <string name="screen_layout_portrait">Portrait</string>
+ <string name="screen_layout_auto">Auto</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Par défaut (16:9)</string>
<string name="ratio_force_four_three">Forcer le 4:3</string>
@@ -288,8 +370,8 @@
<!-- Gamepad Buttons -->
<string name="gamepad_d_pad">Pavé directionnel</string>
- <string name="gamepad_left_stick">Stick Gauche</string>
- <string name="gamepad_right_stick">Stick Droit</string>
+ <string name="gamepad_left_stick">Stick gauche</string>
+ <string name="gamepad_right_stick">Stick droit</string>
<string name="gamepad_home">Home</string>
<string name="gamepad_screenshot">Capture d\'écran</string>
@@ -299,7 +381,7 @@
<!-- Theme options -->
<string name="change_app_theme">Changer le thème de l\'application</string>
- <string name="theme_default">Défaut</string>
+ <string name="theme_default">Par défaut</string>
<string name="theme_material_you">Material You</string>
<!-- Theme Modes -->
@@ -308,8 +390,22 @@
<string name="theme_mode_light">Lumineux</string>
<string name="theme_mode_dark">Sombre</string>
- <!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Utiliser des arrière-plans noirs</string>
- <string name="use_black_backgrounds_description">Lorsque vous utilisez le thème sombre, appliquer des arrière-plans noirs.</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
-</resources>
+ <!-- Black backgrounds theme -->
+ <string name="use_black_backgrounds">Arrière-plan noir</string>
+ <string name="use_black_backgrounds_description">Lorsque vous utilisez le thème sombre, appliquer un arrière-plan noir.</string>
+
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">Lecteur réduit</string>
+ <string name="picture_in_picture_description">Réduire la fenêtre lorsqu\'elle est placée en arrière-plan</string>
+ <string name="pause">Pause</string>
+ <string name="play">Jouer</string>
+ <string name="mute">Couper le son</string>
+ <string name="unmute">Remettre le son</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Licences</string>
+ <string name="license_fidelityfx_fsr_description">Mise à l\'échelle de haute qualité par AMD.</string>
+ </resources>
diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml
new file mode 100644
index 000000000..0af78a57c
--- /dev/null
+++ b/src/android/app/src/main/res/values-he/strings.xml
@@ -0,0 +1,367 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
+
+ <string name="app_disclaimer">התוכנה תריץ משחקים לקונסולת ה Nintendo Switch. אף משחק או קבצים בעלי זכויות יוצרים נכללים.&lt;br /&gt;&lt;br /&gt; לפני שאת/ה מתחיל בבקשה מצא את קובץ <![CDATA[<b>prod.keys</b>]]> על המכשיר.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">קרא עוד</a>]]></string>
+ <string name="emulation_notification_channel_name">אמולציה פעילה</string>
+ <string name="emulation_notification_channel_description">מציג התראה מתמשכת כאשר האמולציה פועלת.</string>
+ <string name="emulation_notification_running">yuzu רץ</string>
+ <string name="notice_notification_channel_name">התראות ותקלות</string>
+ <string name="notice_notification_channel_description">מציג התראות כאשר משהו הולך לא כשורה.</string>
+ <string name="notification_permission_not_granted">הרשאות התראות לא ניתנה!</string>
+
+ <!-- Setup strings -->
+ <string name="welcome">ברוכים הבאים!</string>
+ <string name="welcome_description">למד איך להפעיל &lt;b>yuzu&lt;/b> וקפוץ ישר לאמולציה.</string>
+ <string name="get_started">כדי להתחיל</string>
+ <string name="keys">מפתחות</string>
+ <string name="keys_description">בחר את קובץ ה &lt;b>prod.keys&lt;/b> שלך עם הכפתור למטה.</string>
+ <string name="select_keys">בחר מפתחות</string>
+ <string name="games">משחקים</string>
+ <string name="games_description">בחר את התיקיית ה &lt;b>Games&lt;/b> שלך עם הכפתור למטה.</string>
+ <string name="done">סיום</string>
+ <string name="done_description">את/ה מוכן. \nתהנה/י מהמשחקים שלך </string>
+ <string name="text_continue">המשך</string>
+ <string name="next">הבא</string>
+ <string name="back">אחורה</string>
+ <string name="add_games">הוסף משחקים</string>
+ <string name="add_games_description">בחר/י את תיקיית המשחקים שלך</string>
+ <string name="step_complete">הושלם!</string>
+
+ <!-- Home strings -->
+ <string name="home_games">משחקים</string>
+ <string name="home_search">חפש</string>
+ <string name="home_settings">הגדרות</string>
+ <string name="empty_gamelist">לא נמצאו קבצים או לנבחרה ספריית קבצים בינתיים.</string>
+ <string name="search_and_filter_games">חפש וסנן משחקים</string>
+ <string name="select_games_folder">בחר תיקיית משחקים</string>
+ <string name="select_games_folder_description">אפשר ל yuzu לאכלס את רשימת המשחקים</string>
+ <string name="add_games_warning">לדלג על בחירת תיקיית המשחקים?</string>
+ <string name="add_games_warning_description">משחקים לא יוצגו ברשימת המשחקים אם לנבחרה תיקיית משחקים.</string>
+ <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
+ <string name="home_search_games">חפש משחקים</string>
+ <string name="search_settings">חפש בהגדרות</string>
+ <string name="games_dir_selected">ספריית משחקים נבחרה</string>
+ <string name="install_prod_keys">התקן prod.keys</string>
+ <string name="install_prod_keys_description">הכרחי בכדי לפענח משחקים</string>
+ <string name="install_prod_keys_warning">לדלג על הוספת מפתחות?</string>
+ <string name="install_prod_keys_warning_description">מפתחות חוקיים הכרחיים כדי לשחק במשחקים. רק אפליקציות פירטיות יפעלו אם תמשיך.</string>
+ <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
+ <string name="notifications">התראות</string>
+ <string name="notifications_description">תן גישה להתראות עם הכפתור למטה.</string>
+ <string name="give_permission">תן הרשאה</string>
+ <string name="notification_warning">דלג על מתן הרשאה להתראות?</string>
+ <string name="notification_warning_description">yuzu לא יוכל להתריע לך על מידע חשוב.</string>
+ <string name="permission_denied">הרשאה נדחתה</string>
+ <string name="permission_denied_description">את/ה דיחת את ההרשאה יותר מדי פעמים ועכשיו את/ה צריך/ה לתת גישה באופן ידני בהגדרות.</string>
+ <string name="about">אודות</string>
+ <string name="about_description">מספר גירסה, קרדיטים ועוד</string>
+ <string name="warning_help">עזרה</string>
+ <string name="warning_skip">דלג</string>
+ <string name="warning_cancel">ביטול</string>
+ <string name="install_amiibo_keys">התקן מפתחות Amiibo</string>
+ <string name="install_amiibo_keys_description">נחוץ כדי להשתמש ב Amiibo במשחק</string>
+ <string name="invalid_keys_file">קובץ מפתחות לא חוקי נבחר</string>
+ <string name="install_keys_success">מפתחות הותקנו בהצלחה</string>
+ <string name="reading_keys_failure">שגיאה בקריאת מפתחות ההצפנה</string>
+ <string name="install_prod_keys_failure_extension_description">ודא שלקובץ המפתחות שלך יש סיומת של key. ונסה/י שוב.</string>
+ <string name="install_amiibo_keys_failure_extension_description">ודא/י שלקובץ המפתחות שלך יש סיומת של bin. ונסה/י שוב.</string>
+ <string name="invalid_keys_error">מפתחות הצפנה לא חוקיים</string>
+ <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
+ <string name="install_keys_failure_description">קבוץ שנבחר מושחת או לא נכון. בבקשה הוצא מחדש את המפתחות שלך.</string>
+ <string name="install_gpu_driver">התקן דרייבר למעבד הגרפי</string>
+ <string name="install_gpu_driver_description">התקן דרייברים אחרים בשביל סיכוי לביצועים או דיוק גבוההים יותר</string>
+ <string name="advanced_settings">הגדרות מתקדמות</string>
+ <string name="advanced_settings_game">הגדרות מתקדמות: %1$s</string>
+ <string name="settings_description">הדר את הגדרות האמולטור</string>
+ <string name="search_recently_played">שוחק לאחרונה</string>
+ <string name="search_recently_added">הוסף לאחרונה</string>
+ <string name="search_retail">קמעונאי</string>
+ <string name="search_homebrew">Homebrew</string>
+ <string name="open_user_folder">פתח את תיקיית yuzu </string>
+ <string name="open_user_folder_description">נה ל את הקבצים הפנימיין של yuzu</string>
+ <string name="theme_and_color_description">ערוך את נראות האפליקציה</string>
+ <string name="no_file_manager">לא נמצא מנהל קבצים</string>
+ <string name="notification_no_directory_link">לא יכול לפתוח את ספריית yuzu</string>
+ <string name="notification_no_directory_link_description">בבקשה מקם את תיקיית המשתמש בפנל הצידי של מנהל הקבצים באופן ידני.</string>
+ <string name="manage_save_data">נהל מידע שמור</string>
+ <string name="manage_save_data_description">מידע שמור לא נמצא. בבקשה בחר/י אופציה מלמטה</string>
+ <string name="import_export_saves_description">יבא או יצא קבצי שמירה</string>
+ <string name="save_file_imported_success">יובא בהצלחה</string>
+ <string name="save_file_invalid_zip_structure">מבנה ספריית השמירות לא חוקי</string>
+ <string name="save_file_invalid_zip_structure_description">התת תיקייה הראשונה חייב להיות ה title ID של המשחק</string>
+ <string name="import_saves">ייבוא</string>
+ <string name="export_saves">ייצוא</string>
+ <string name="install_firmware">התקן firmware</string>
+ <string name="install_firmware_description">ה frimware חייב להיות בקובץ zip והוא הכרחי להפעלת חלק מהמשחקים</string>
+ <string name="firmware_installing">מתקין frimware</string>
+ <string name="firmware_installed_success">ה frimware הותקן בהצלחה</string>
+ <string name="firmware_installed_failure">התקנת ה frimware נכשלה</string>
+ <string name="firmware_installed_failure_description">ודא שקבצי ה firmware nca נמצאים בשורש ה zip ונסה שוב.</string>
+ <string name="share_log">שתף את יומני הרישום של מיפוי הבאגים</string>
+ <string name="share_log_description">שתף את קובץ יומני הרישום של yuzu בכדי לתקן בעיות</string>
+ <string name="share_log_missing">לא נמצא קובץ יומן רישום</string>
+ <string name="install_game_content">התקן תוכן משחק</string>
+ <string name="install_game_content_description">התקן עדכוני משחק או DLC</string>
+ <string name="installing_game_content">מתקין תוכן...</string>
+ <string name="install_game_content_failure">תקלה בהתקנת הקובץ (או קבצים) ל NAND</string>
+ <string name="install_game_content_failure_description">בבקשה ודא שהתוכן (או תכנים) חוקיים ושקובץ ה prod.keys מותקן.</string>
+ <string name="install_game_content_failure_base">התקנת משחק בסיס נדחת בכדי להימנע מקונפליקטים אפשריים.</string>
+ <string name="install_game_content_failure_file_extension">רק קבצי NSP ו XCI נתמכים. בבקשה ודא שתוכן (או תכנים) המשחק חוקי.</string>
+ <string name="install_game_content_failed_count">%1$dבעיה (בעיות) התקנה</string>
+ <string name="install_game_content_success">תוכן (או תכני) המשחק הותקנו בהצלחה</string>
+ <string name="install_game_content_success_install">%1$d הותקן בהצלחה</string>
+ <string name="install_game_content_success_overwrite">%1$d נדרס/נכתב מעל בהצלחה</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">דרייברים מותאמים אישית לא נתמכים</string>
+ <string name="custom_driver_not_supported_description">הטענת דרייבים מותאמים אישית לא נתמך כרגע על מכשיר זה. \nבבקשה בדוק אופציה זו בעתיד בכדי לראות אם נוספה תמיכה!</string>
+ <string name="manage_yuzu_data">נהל את המידע של yuzu</string>
+ <string name="manage_yuzu_data_description">יבא/יצא firmware, keys, מידע של משתמש ועוד!</string>
+ <string name="share_save_file">שתף קובץ שמירה</string>
+ <string name="export_save_failed">נכשל בייצוא שמירה</string>
+
+ <!-- About screen strings -->
+ <string name="gaia_is_not_real">Gaia לא אמיתית</string>
+ <string name="copied_to_clipboard">הועתק ללוח</string>
+ <string name="about_app_description">אמולטור Switch עם קוד פתוח</string>
+ <string name="contributors">תורמים</string>
+ <string name="contributors_description">נוצר עם \u2764 מקבוצת yuzu</string>
+ <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">פרוייקטים שהופכים את yuzu ל Android אפשרי</string>
+ <string name="build">גרסה</string>
+ <string name="user_data">נתוני משתמש</string>
+ <string name="user_data_description">יבא/יצא את כל נתוני האפליקציה.\n\nכאשר מייבאים את נתוני המשתמש, כל נתוני המשתמש הקיימים ימחקו!</string>
+ <string name="exporting_user_data">מייצא נתוני משתמש...</string>
+ <string name="importing_user_data">מייבא נתוני משתמש...</string>
+ <string name="import_user_data">יבא נתוני משתמש</string>
+ <string name="invalid_yuzu_backup">גיבוי yuzu לא חוקי</string>
+ <string name="user_data_export_success">נתוני משתמש יוצאו בהצלחה</string>
+ <string name="user_data_import_success">נתוני משתמש יובאו בהצלחה</string>
+ <string name="user_data_export_cancelled">ייצוא בוטל</string>
+ <string name="user_data_import_failed_description">ודא שנתוני המשתמש נמצאים בשורש קובץ ה zip ושהוא מכיל קובץ סידור ב config/config.ini ונסה שוב.</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>
+
+ <!-- Early access upgrade strings -->
+ <string name="early_access">גישה מוקדמת</string>
+ <string name="get_early_access">קבל גישה מוקדמת</string>
+ <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
+ <string name="get_early_access_description">תכונות חותכות קצה, גישה מוקדמת לעדכונים, ועוד</string>
+ <string name="early_access_benefits">יתרונות של גישה מקודמת</string>
+ <string name="cutting_edge_features">תכונות חותכות קצה</string>
+ <string name="early_access_updates">גישה מוקדמת לעדכונים</string>
+ <string name="no_manual_installation">ללא התקנה ידנית</string>
+ <string name="prioritized_support">תמיכה בעדיפות</string>
+ <string name="helping_game_preservation">עוזר בשמירת משחקים</string>
+ <string name="our_eternal_gratitude">התודה האינסופית שלנו</string>
+ <string name="are_you_interested">אתה מעוניין?</string>
+
+ <!-- General settings strings -->
+ <string name="frame_limit_enable">הגבל מהירות</string>
+ <string name="frame_limit_enable_description">מגביל את מהירות האמולציה לאחוז מהירות המבוקש מהמהירות הרגילה.</string>
+ <string name="frame_limit_slider">הגבל את אחוז המהירות</string>
+ <string name="frame_limit_slider_description">מדייק את אחוז מהירות האמולציה. 100% זה מהירות רגילה. ערכים גדולים או קטנים יאיצו או יאטו את מהירות האמולציה.</string>
+ <string name="cpu_accuracy">דיוק המעבד</string>
+ <string name="value_with_units">%1$s%2$s</string>
+
+ <!-- System settings strings -->
+ <string name="use_docked_mode">מצב עגינה</string>
+ <string name="use_docked_mode_description">מעלה את הרזולוציה, פוגע בביצועים. משתמש במצב נייד כאשר מנוטרל, מפחית את הרזולוציה ומעלה את הביצועים.</string>
+ <string name="emulated_region">אזור אמולציה</string>
+ <string name="emulated_language">שפת אמולציה</string>
+ <string name="select_rtc_date">בחר תאריך RTC</string>
+ <string name="select_rtc_time">בחר זמן RTC</string>
+ <string name="use_custom_rtc">RTC מותאם אישית</string>
+ <string name="use_custom_rtc_description">מאפשר לך לקבוע שעון זמן אמת נפרד משעון המערכת שלך.</string>
+ <string name="set_custom_rtc">קבע RTC מותאם אישית</string>
+
+ <!-- Graphics settings strings -->
+ <string name="renderer_accuracy">רמת דיוק</string>
+ <string name="renderer_resolution">רזולוציה (מעוגן/נייד)</string>
+ <string name="renderer_vsync">מצב VSync</string>
+ <string name="renderer_screen_layout">כיוון</string>
+ <string name="renderer_aspect_ratio">יחס רוחב גובה</string>
+ <string name="renderer_scaling_filter">פילטר מתאם חלון</string>
+ <string name="renderer_anti_aliasing">שיטת Anti-aliasing</string>
+ <string name="renderer_force_max_clock">החזק מהירות שעון מקסימלית (רק ל Adreno)</string>
+ <string name="renderer_force_max_clock_description">מכריח לדחוף את מהירויות המעבד הגרפי למקסימום (הגבלות חום ימשיכו לתפקד).</string>
+ <string name="renderer_reactive_flushing_description">משפר את הדיוק של האמולציה במשחקים מסויימים במחיר של ביצועים.</string>
+ <!-- Debug settings strings -->
+ <string name="cpu">מעבד</string>
+ <string name="cpu_debug_mode_description">מכניס את המעבד למצב דיבאג איטי</string>
+ <string name="gpu">מעבד גרפי</string>
+ <!-- Audio settings strings -->
+ <string name="audio_output_engine">מנוע פלט</string>
+ <string name="audio_volume">עוצמת שמע</string>
+ <!-- Miscellaneous -->
+ <string name="slider_default">ברירת מחדל</string>
+ <string name="ini_saved">הגדרות שמורות</string>
+ <string name="gameid_saved">הגדרות שמורות עבור %1$s</string>
+ <string name="error_saving">תקלה בשמירת %1$s.ini: %2$s</string>
+ <string name="loading">טוען...</string>
+ <string name="shutting_down">כיבוי...</string>
+ <string name="reset_setting_confirmation">אתה מעוניין לאפס את ההגדרה הזו חזרה לברירת המחדל?</string>
+ <string name="reset_to_default">אפס לברירת המחדל</string>
+ <string name="reset_all_settings">לאפס את כל ההגדרות?</string>
+ <string name="reset_all_settings_description">כל ההגדרות המתקדמות יאופסו לברירת המחדל. לא ניתן לבטל פעולה זו.</string>
+ <string name="settings_reset">אפס הגדרות</string>
+ <string name="close">סגור</string>
+ <string name="learn_more">למד עוד</string>
+ <string name="auto">אוטומטי</string>
+ <string name="submit">שלח</string>
+ <string name="string_import">ייבוא</string>
+ <string name="export">ייצוא</string>
+ <string name="export_failed">ייצוא נכשל</string>
+ <string name="import_failed">ייבוא נכשל</string>
+ <string name="cancelling">מבטל</string>
+
+ <!-- GPU driver installation -->
+ <string name="select_gpu_driver">בחר דרייבר למעבד הגרפי</string>
+ <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_use_default">משתמש בדרייבר ברירת המחדל של המעבד הגרפי</string>
+ <string name="select_gpu_driver_error">דרייבר לא חוקי נבחר, משתמש בברירת המחדל של המערכת!</string>
+ <string name="system_gpu_driver">דרייבר של המעבד הגרפי של המערכת</string>
+ <string name="installing_driver">מתקין דרייבר...</string>
+
+ <!-- Preferences Screen -->
+ <string name="preferences_settings">הגדרות</string>
+ <string name="preferences_general">כללי</string>
+ <string name="preferences_system">מערכת</string>
+ <string name="preferences_graphics">גרפיקה</string>
+ <string name="preferences_audio">שמע</string>
+ <string name="preferences_theme">צבע ונושא</string>
+ <!-- ROM loading errors -->
+ <string name="loader_error_encrypted">המשחק שלך מוצפן</string>
+ <string name="loader_error_invalid_format">אין אפשרות לטעון את המשחק</string>
+ <string name="loader_error_file_not_found">קובץ המשחק לא קיים</string>
+
+ <!-- Emulation Menu -->
+ <string name="emulation_exit">צא מהאמולציה</string>
+ <string name="emulation_done">סיום</string>
+ <string name="emulation_fps_counter">סופר FPS</string>
+ <string name="emulation_control_scale">קנה מידה</string>
+ <string name="emulation_control_opacity">שקיפות</string>
+ <string name="emulation_pause">עצור אמולציה</string>
+ <string name="emulation_unpause">המשך אמולציה</string>
+ <string name="load_settings">טוען הגדרות...</string>
+
+ <!-- Software keyboard -->
+ <string name="software_keyboard">מקלדת תוכנה</string>
+
+ <!-- Errors and warnings -->
+ <string name="abort_button">אודות</string>
+ <string name="continue_button">המשך</string>
+ <string name="system_archive_not_found">ארכיון מערכת לא נמצא</string>
+ <string name="system_archive_not_found_message">%s חסר. בבקשה הוצא תא ארכיוני המערכת שלך./nהמשכת האמולציה עלולה לגרום לקריסות ובאגים.</string>
+ <string name="system_archive_general">ארכיון מערכת</string>
+ <string name="save_load_error">בעיית שמירה/טעינה</string>
+ <string name="fatal_error">שגיאה חמורה</string>
+ <string name="device_memory_inadequate">RAM המכשיר: %1$s/nמומלץ: %2$s</string>
+ <string name="memory_formatted">%1$s%2$s</string>
+ <string name="no_game_present">אין משחק שניתן להריץ!</string>
+
+ <!-- Region Names -->
+ <string name="region_japan">יפן</string>
+ <string name="region_usa">ארה״ב</string>
+ <string name="region_europe">אירופה</string>
+ <string name="region_australia">אוסטרליה</string>
+ <string name="region_china">סין</string>
+ <string name="region_korea">קוריאה</string>
+ <string name="region_taiwan">טייוואן</string>
+
+ <!-- Memory Sizes -->
+ <string name="memory_byte">בייט</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
+
+ <!-- Renderer APIs -->
+ <string name="renderer_vulkan">Vulkan</string>
+ <string name="renderer_none">אין שום דבר</string>
+
+ <!-- Renderer Accuracy -->
+ <string name="renderer_accuracy_normal">רגיל</string>
+ <string name="renderer_accuracy_high">גבוה</string>
+ <string name="renderer_accuracy_extreme">אקסטרים (איטי)</string>
+
+ <!-- Resolutions -->
+ <string name="resolution_half">0.5X (360p/540p)</string>
+ <string name="resolution_three_quarter">0.75X (540p/810p)</string>
+ <string name="resolution_one">1X (720p/1080p)</string>
+ <string name="resolution_two">2X (1440p/2160p) (איטי)</string>
+ <string name="resolution_three">3X (2160p/3240p) (איטי)</string>
+ <string name="resolution_four">4X (2880p/4320p) (איטי)</string>
+
+ <string name="renderer_vsync_mailbox">תיבת דואר</string>
+ <string name="renderer_vsync_fifo">FIFO (On)</string>
+ <string name="renderer_vsync_fifo_relaxed">FIFO נינוח</string>
+
+ <!-- Scaling Filters -->
+ <string name="scaling_filter_nearest_neighbor">השכן הקרוב ביותר</string>
+ <string name="scaling_filter_scale_force">ScaleForce</string>
+ <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
+
+ <!-- Anti-Aliasing -->
+ <string name="anti_aliasing_none">אין שום דבר</string>
+ <string name="anti_aliasing_fxaa">FXAA</string>
+ <string name="anti_aliasing_smaa">SMAA</string>
+
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">לרוחב</string>
+ <string name="screen_layout_portrait">לאורך</string>
+ <string name="screen_layout_auto">אוטומטי</string>
+
+ <!-- Aspect Ratios -->
+ <string name="ratio_default">ברירת מחדל (16:9)</string>
+ <string name="ratio_force_four_three">הכרח 4:3</string>
+ <string name="ratio_force_twenty_one_nine">הכרח 21:9</string>
+ <string name="ratio_force_sixteen_ten">הכרח 16:10</string>
+ <string name="ratio_stretch">הרחב לגודל המסך</string>
+
+ <!-- CPU Accuracy -->
+ <string name="cpu_accuracy_accurate">מדויק</string>
+ <string name="cpu_accuracy_unsafe">לא בטוח</string>
+ <string name="cpu_accuracy_paranoid">פראנואידי (איטי)</string>
+
+ <!-- Gamepad Buttons -->
+ <string name="gamepad_d_pad">D-pad</string>
+ <string name="gamepad_left_stick">ג׳ויסטיק שמאלי</string>
+ <string name="gamepad_right_stick">ג׳ויסטיק ימני</string>
+ <string name="gamepad_home">בית</string>
+ <string name="gamepad_screenshot">צילום מסך</string>
+
+ <!-- Theme options -->
+ <string name="change_app_theme">שנה את נושא האפליקצייה</string>
+ <string name="theme_default">ברירת מחדל</string>
+ <string name="theme_material_you">חומר אתה/מאטיריאל יו</string>
+
+ <!-- Theme Modes -->
+ <string name="change_theme_mode">שנה את מצב הנושא</string>
+ <string name="theme_mode_follow_system">עקוב אחרי המערכת</string>
+ <string name="theme_mode_light">בהיר</string>
+ <string name="theme_mode_dark">כהה</string>
+
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
+ <!-- Black backgrounds theme -->
+ <string name="use_black_backgrounds">רקעים שחורים</string>
+ <string name="use_black_backgrounds_description">כשמתשמשים במצב כהה, שם רקעים שחורים.</string>
+
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">תמונה בתוך תמונה</string>
+ <string name="picture_in_picture_description">הקטן את החלון כאשר נמצא ברקע</string>
+ <string name="pause">עצור</string>
+ <string name="play">שחק</string>
+ <string name="mute">השתק</string>
+ <string name="unmute">בטל השתקה</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">רישיונות</string>
+ <string name="license_fidelityfx_fsr_description">אפסקיילינג באיכות גבוהה מ AMD</string>
+ </resources>
diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml
new file mode 100644
index 000000000..6563ba288
--- /dev/null
+++ b/src/android/app/src/main/res/values-hu/strings.xml
@@ -0,0 +1,402 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
+
+ <string name="app_disclaimer">Ez a szoftver Nintendo Switch játékkonzolhoz készült játékokat futtat. Nem tartalmaz játékokat vagy kulcsokat. .&lt;br /&gt;&lt;br /&gt;Mielőtt hozzákezdenél, kérjük, válaszd ki a <![CDATA[<b>prod.keys</b>]]> fájl helyét a készülék tárhelyén&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Tudj meg többet</a>]]></string>
+ <string name="emulation_notification_channel_name">Emuláció aktív</string>
+ <string name="emulation_notification_channel_description">Állandó értesítést jelenít meg, amíg az emuláció fut.</string>
+ <string name="emulation_notification_running">A yuzu fut</string>
+ <string name="notice_notification_channel_name">Megjegyzések és hibák</string>
+ <string name="notice_notification_channel_description">Értesítések megjelenítése, ha valami rosszul sül el.</string>
+ <string name="notification_permission_not_granted">Nincs engedély az értesítés megjelenítéséhez!</string>
+
+ <!-- Setup strings -->
+ <string name="welcome">Üdvözöljük!</string>
+ <string name="welcome_description">Ismerkedj meg a &lt;b>yuzu&lt;/b> beállításával és ugorj bele az emulációba.</string>
+ <string name="get_started">Vágjunk bele</string>
+ <string name="keys">Kulcsok</string>
+ <string name="keys_description">Válaszd ki a(z) &lt;b>prod.keys&lt;/b> fájlodat az alábbi gombbal.</string>
+ <string name="select_keys">Kulcsok kiválasztása</string>
+ <string name="games">Játékok</string>
+ <string name="games_description">
+Válaszd ki a(z) &lt;b>Games&lt;/b> mappát az alábbi gombbal.</string>
+ <string name="done">Kész</string>
+ <string name="done_description">Minden kész.\nJó szórakozást!</string>
+ <string name="text_continue">Folytatás</string>
+ <string name="next">Következő</string>
+ <string name="back">Vissza</string>
+ <string name="add_games">Játékok hozzáadása</string>
+ <string name="add_games_description">Játékaid mappa kiválasztása</string>
+ <string name="step_complete">Kész!</string>
+
+ <!-- Home strings -->
+ <string name="home_games">Játékok</string>
+ <string name="home_search">Keresés</string>
+ <string name="home_settings">Beállítások</string>
+ <string name="empty_gamelist">Nem található fájl, vagy még nincs kiválasztva könyvtár.</string>
+ <string name="search_and_filter_games">Játékok keresése és szűrése</string>
+ <string name="select_games_folder">Játékmappa kiválasztása</string>
+ <string name="add_games_warning">Kihagyod a játékok mappa kiválasztását?</string>
+ <string name="add_games_warning_description">A játékok nem jelennek meg a Játékok listában, ha egy mappa nincs kijelölve.</string>
+ <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
+ <string name="home_search_games">Játékok keresése</string>
+ <string name="search_settings">Beállítások keresése</string>
+ <string name="games_dir_selected">Játékok könyvtár kiválasztva</string>
+ <string name="install_prod_keys">prod.keys telepítése</string>
+ <string name="install_prod_keys_description">Kiskereskedelmi játékok dekódolásához szükséges</string>
+ <string name="install_prod_keys_warning">Kihagyod a kulcsok hozzáadását?</string>
+ <string name="install_prod_keys_warning_description">A kiskereskedelmi játékok emulálásához érvényes kulcsokra van szükség. Csak a homebrew alkalmazások fognak működni, ha folytatod.</string>
+ <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
+ <string name="notifications">Értesítések</string>
+ <string name="notifications_description">Értesítési engedélyek megadása az alábbi gombbal.</string>
+ <string name="give_permission">Engedély megadása</string>
+ <string name="notification_warning">Kihagyod az értesítési engedély megadását?</string>
+ <string name="notification_warning_description">yuzu nem fog tudni értesíteni a fontos imformációkról</string>
+ <string name="permission_denied">Engedély megtagadva</string>
+ <string name="permission_denied_description">Túl gyakran utasítottad el a hozzáférést, így manuálisan kell jóváhagynod a rendszer beállításokban.</string>
+ <string name="about">A programról</string>
+ <string name="about_description">Build verzió, készítők, és még több</string>
+ <string name="warning_help">Segítség</string>
+ <string name="warning_skip">Kihagyás</string>
+ <string name="warning_cancel">Mégse</string>
+ <string name="install_amiibo_keys">Amiibo kulcsok telepítése</string>
+ <string name="install_amiibo_keys_description">Amiibo használata szükséges a játékhoz</string>
+ <string name="invalid_keys_file">Érvénytelen titkosítófájlok kiválasztva</string>
+ <string name="install_keys_success">Kulcsok sikeresen telepítve</string>
+ <string name="reading_keys_failure">Hiba történt a titkosítókulcsok olvasása során</string>
+ <string name="install_prod_keys_failure_extension_description">Győződj meg róla, hogy a titkosító fájlod .keys kiterjesztéssel rendelkezik, majd próbáld újra.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Győződj meg róla, hogy a titkosító fájlod .bin kiterjesztéssel rendelkezik, majd próbáld újra.</string>
+ <string name="invalid_keys_error">Érvénytelen titkosítókulcsok</string>
+ <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
+ <string name="install_keys_failure_description">A kiválasztott fájl helytelen, vagy sérült. Állíts össze egy új kulcsot.</string>
+ <string name="install_gpu_driver">GPU illesztőprogram telepítése</string>
+ <string name="install_gpu_driver_description">Alternatív illesztőprogramok telepítése az esetlegesen elérhető teljesítmény és pontosság érdekében</string>
+ <string name="advanced_settings">Haladó beállítások</string>
+ <string name="advanced_settings_game">Haladó beállítások: %1$s</string>
+ <string name="settings_description">Emulátorbeállítások konfigurálása</string>
+ <string name="search_recently_played">Nemrég játszva</string>
+ <string name="search_recently_added">Nemrég hozzáadva</string>
+ <string name="search_retail">Kiskereskedelmi</string>
+ <string name="open_user_folder">yuzu mappa megnyitása</string>
+ <string name="open_user_folder_description">yuzu belső fájljainak kezelése</string>
+ <string name="theme_and_color_description">Az alkalmazás megjelenésének módosítása</string>
+ <string name="no_file_manager">Nem található fájlkezelő</string>
+ <string name="notification_no_directory_link">Nem sikerült megnyitni a yuzu könyvtárat</string>
+ <string name="notification_no_directory_link_description">Kérjük, manuálisan keresd meg a felhasználói mappát a fájlkezelő oldalsó paneljével.</string>
+ <string name="manage_save_data">Mentésadatok kezelése</string>
+ <string name="manage_save_data_description">Mentés található. Kérjük, válassz egyet az alábbi opciók közül.</string>
+ <string name="import_export_saves_description">Mentési fájlok importálás vagy exportálása</string>
+ <string name="save_file_imported_success">Sikeresen importálva</string>
+ <string name="save_file_invalid_zip_structure">Érvénytelen mentési könyvtárstruktúra</string>
+ <string name="save_file_invalid_zip_structure_description">Az első almappa neve a játék azonosítója kell, hogy legyen.</string>
+ <string name="import_saves">Importálás</string>
+ <string name="export_saves">Exportálás</string>
+ <string name="install_firmware">Firmware telepítés</string>
+ <string name="install_firmware_description">A firmwarenek ZIP archívumban kell lennie, és szükséges a játékok indításához</string>
+ <string name="firmware_installing">Firmware telepítése</string>
+ <string name="firmware_installed_success">Firmware sikeresen telepítve</string>
+ <string name="firmware_installed_failure">Firmware telepítése sikertelen</string>
+ <string name="firmware_installed_failure_description">Győződj meg róla, hogy a firmware nca fájlok a zip gyökerénél vannak, és próbáld meg újra.</string>
+ <string name="share_log">Hibakereső logok megosztása</string>
+ <string name="share_log_description">A yuzu naplófájl megosztása a problémák elhárításához</string>
+ <string name="share_log_missing">Nem található log fájl</string>
+ <string name="install_game_content">Játéktartalom telepítése</string>
+ <string name="install_game_content_description">Játékfrissítések vagy DLC telepítése</string>
+ <string name="installing_game_content">Tartalom telepítése...</string>
+ <string name="install_game_content_failure">Hiba történt a fájl(ok) NAND-ra telepítése közben</string>
+ <string name="install_game_content_failure_description">Győződj meg róla, hogy a tartalom valós, és a prod.keys fájl telepítve van.</string>
+ <string name="install_game_content_failure_base">Az alapjátékok telepítése nem engedélyezett az esetleges konfliktusok elkerülése érdekében.</string>
+ <string name="install_game_content_failure_file_extension">Csak NSP és XCI tartalom támogatott. Győződj meg róla, hogy a játéktartalom érvényes.</string>
+ <string name="install_game_content_failed_count">%1$d telepítési hiba</string>
+ <string name="install_game_content_success">Játéktartalom sikeresen telepítve</string>
+ <string name="install_game_content_success_install">%1$d sikeresen telepítve</string>
+ <string name="install_game_content_success_overwrite">%1$d sikeresen felülírva</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">Egyéni illesztőprogramok nem támogatottak</string>
+ <string name="custom_driver_not_supported_description">Egyéni illesztőprogram telepítése jelenleg nem támogatott ezen az eszközön.\nNézz vissza később, hátha hozzáadtuk a támogatását!</string>
+ <string name="manage_yuzu_data">yuzu adatok kezelése</string>
+ <string name="manage_yuzu_data_description">Firmware, kulcsok, felhasználói adatok és egyebek importálása/exportálása</string>
+ <string name="share_save_file">Mentési fájl megosztása</string>
+ <string name="export_save_failed">A mentés exportálása sikertelen</string>
+
+ <!-- About screen strings -->
+ <string name="gaia_is_not_real">Gaia nem valódi</string>
+ <string name="copied_to_clipboard">Másolva a vágólapra</string>
+ <string name="about_app_description">Egy nyílt forráskódú Switch emulátor</string>
+ <string name="contributors">Hozzájárulók</string>
+ <string name="contributors_description">\u2764 által készítve a yuzu csapattól</string>
+ <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Projektek, amik nélkül a yuzu nem jöhetett volna létre Androidra</string>
+ <string name="user_data">Felhasználói adatok</string>
+ <string name="user_data_description">Az összes alkalmazásadat importálása/exportálása.\n\nA felhasználói adatok importálásakor az összes meglévő felhasználói adat törlődik!</string>
+ <string name="exporting_user_data">Felhasználói adatok exportálása...</string>
+ <string name="importing_user_data">Felhasználói adatok importálása...</string>
+ <string name="import_user_data">Felhasználói adatok importálása</string>
+ <string name="invalid_yuzu_backup">Érvénytelen yuzu biztonsági másolat</string>
+ <string name="user_data_export_success">Felhasználói adatok sikeresen exportálva</string>
+ <string name="user_data_import_success">Felhasználói adatok sikeresen importálva</string>
+ <string name="user_data_export_cancelled">Exportálás megszakítva</string>
+ <string name="user_data_import_failed_description">Ellenőrizd, hogy a felhasználói adatok mappái a zip mappa gyökerében vannak, és tartalmaznak egy konfig fájlt a config/config.ini címen, majd próbáld meg újra.</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>
+
+ <!-- Early access upgrade strings -->
+ <string name="early_access">Korai hozzáférés</string>
+ <string name="get_early_access">Szerezz korai hozzáférést</string>
+ <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
+ <string name="get_early_access_description">Legújabb funkciók, korai hozzáférés a frissítésekhez, és sok más</string>
+ <string name="early_access_benefits">Korai hozzáférés előnyei</string>
+ <string name="cutting_edge_features">Legújabb funkciók</string>
+ <string name="early_access_updates">Korai hozzáférés a frissítésekhez</string>
+ <string name="no_manual_installation">Automatikus telepítések</string>
+ <string name="prioritized_support">Priorizált támogatás</string>
+ <string name="our_eternal_gratitude">Valamint az örök hálánk</string>
+ <string name="are_you_interested">Érdekel a dolog?</string>
+
+ <!-- General settings strings -->
+ <string name="frame_limit_enable">Sebességkorlát</string>
+ <string name="frame_limit_enable_description">Korlátozza az emuláció sebességét a normál sebesség adott százalékára.</string>
+ <string name="frame_limit_slider">Sebességkorlát százaléka</string>
+ <string name="frame_limit_slider_description">Az emuláció sebességét határozza meg. 100% a normál sebesség. A magasabb értékek növelik, az alacsonyabbak csökkentik a sebességkorlátot.</string>
+ <string name="cpu_accuracy">CPU pontosság</string>
+ <string name="value_with_units">%1$s%2$s</string>
+
+ <!-- System settings strings -->
+ <string name="use_docked_mode">Dokkolt mód</string>
+ <string name="use_docked_mode_description">Növeli a felbontást, de csökkenti a teljesítményt. Kikapcsolás esetén a Kézi mód van használatban, ami kisebb felbontást, de nagyobb teljesítményt eredményez.</string>
+ <string name="emulated_region">Emulált régió</string>
+ <string name="emulated_language">Emulált nyelv</string>
+ <string name="select_rtc_date">Válassz RTC dátumot</string>
+ <string name="select_rtc_time">Válassz RTC időt</string>
+ <string name="use_custom_rtc">Egyéni RTC</string>
+ <string name="use_custom_rtc_description">Megadhatsz egy valós idejű órát, amely eltér a rendszer által használt órától.</string>
+ <string name="set_custom_rtc">Egyéni RTC beállítása</string>
+
+ <!-- Graphics settings strings -->
+ <string name="renderer_accuracy">Pontosság szintje</string>
+ <string name="renderer_resolution">Felbontás (Kézi/Dockolt)</string>
+ <string name="renderer_vsync">VSync mód</string>
+ <string name="renderer_screen_layout">Orientáció</string>
+ <string name="renderer_aspect_ratio">Képarány</string>
+ <string name="renderer_scaling_filter">Ablakhoz alkalmazkodó szűrő</string>
+ <string name="renderer_anti_aliasing">Élsimítási módszer</string>
+ <string name="renderer_force_max_clock">Maximum órajel kényszerítése (csak Adreno)</string>
+ <string name="renderer_force_max_clock_description">Kényszeríti a GPU-t a lehető legnagyobb órajelen működésre (a hőmérséklet korlátozások továbbra is érvényben maradnak).</string>
+ <string name="renderer_asynchronous_shaders">Aszinkron árnyékolók használata</string>
+ <string name="renderer_asynchronous_shaders_description">Aszinkron módon fordítja az árnyékolókat, ami csökkenti az akadozást, de hibákat okozhat.</string>
+ <string name="renderer_reactive_flushing">Reaktív ürítés használata</string>
+ <string name="renderer_reactive_flushing_description">Javítja a renderelési pontosságot néhány játékban a teljesítmény rovására.</string>
+ <string name="use_disk_shader_cache">Lemez árnyékoló gyorsítótár</string>
+ <string name="use_disk_shader_cache_description">Csökkenti az akadásokat azáltal, hogy helyileg tárolja és tölti be a generált árnyékolókat.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">CPU hibakeresés</string>
+ <string name="cpu_debug_mode_description">Lassú hibakereső módba állítja a CPU-t.</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Grafikai hibakeresés</string>
+ <string name="renderer_debug_description">Lassú hibakeresési módba állítja a grafikus API-t .</string>
+ <!-- Audio settings strings -->
+ <string name="audio_output_engine">Kimeneti rendszer</string>
+ <string name="audio_volume">Hangerő</string>
+ <string name="audio_volume_description">Hangkimenet hangerejének megadása</string>
+
+ <!-- Miscellaneous -->
+ <string name="slider_default">Alapértelmezett</string>
+ <string name="ini_saved">Beállítások elmentve</string>
+ <string name="gameid_saved">Beállítások elmentve a következőhöz: %1$s</string>
+ <string name="error_saving">Mentési hiba%1$s .ini: %2$s</string>
+ <string name="unimplemented_menu">Nem implementált menü</string>
+ <string name="loading">Betöltés...</string>
+ <string name="shutting_down">Leállítás...</string>
+ <string name="reset_setting_confirmation">Szeretnéd visszaállítani a beállítások az alapértelmezett értékekre?</string>
+ <string name="reset_to_default">Alaphelyzetbe állítás</string>
+ <string name="reset_all_settings">Alaphelyzetbe állítod a beállításokat?</string>
+ <string name="reset_all_settings_description">Minden haladó beállítás vissza lesz állítva az alapértelmezett konfigurációra. Ez a művelet nem vonható vissza.</string>
+ <string name="settings_reset">Beállítások alaphelyzetbe állítva</string>
+ <string name="close">Bezárás</string>
+ <string name="learn_more">Tudj meg többet</string>
+ <string name="auto">Automatikus</string>
+ <string name="submit">Küldés</string>
+ <string name="string_null">Nulla</string>
+ <string name="string_import">Importálás</string>
+ <string name="export">Exportálás</string>
+ <string name="export_failed">Exportálás sikertelen</string>
+ <string name="import_failed">Importálás sikertelen</string>
+ <string name="cancelling">Megszakítás</string>
+
+ <!-- GPU driver installation -->
+ <string name="select_gpu_driver">Válassz GPU illesztőprogramot</string>
+ <string name="select_gpu_driver_title">Szeretnéd lecserélni a jelenlegi GPU illesztőprogramot?</string>
+ <string name="select_gpu_driver_install">Telepítés</string>
+ <string name="select_gpu_driver_default">Alapértelmezett</string>
+ <string name="select_gpu_driver_use_default">Alapértelmezett GPU illesztőprogram használata</string>
+ <string name="select_gpu_driver_error">Érvénytelen driver kiválasztva, a rendszer alapértelmezett lesz használva!</string>
+ <string name="system_gpu_driver">Rendszer GPU illesztőprogram</string>
+ <string name="installing_driver">Illesztőprogram telepítése...</string>
+
+ <!-- Preferences Screen -->
+ <string name="preferences_settings">Beállítások</string>
+ <string name="preferences_general">Általános</string>
+ <string name="preferences_system">Rendszer</string>
+ <string name="preferences_graphics">Grafika</string>
+ <string name="preferences_audio">Hang</string>
+ <string name="preferences_theme">Téma és színek</string>
+ <string name="preferences_debug">Hibakeresés</string>
+
+ <!-- ROM loading errors -->
+ <string name="loader_error_encrypted">ROM titkosítva</string>
+ <string name="loader_error_encrypted_keys_description"><![CDATA[Győződj meg róla, hogy a <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> fájl telepítve van, hogy a játékok visszafejthetők legyenek.]]></string>
+ <string name="loader_error_video_core">Hiba lépett fel a videómag inicializása során</string>
+ <string name="loader_error_video_core_description">Ezt általában egy nem kompatibilis GPU illesztő okozza. Egyéni GPU illesztőprogram telepítése megoldhatja a problémát.</string>
+ <string name="loader_error_invalid_format">Nem sikerült betölteni a ROM-ot</string>
+ <string name="loader_error_file_not_found">ROM fájl nem létezik</string>
+
+ <!-- Emulation Menu -->
+ <string name="emulation_exit">Emuláció bezárása</string>
+ <string name="emulation_done">Kész</string>
+ <string name="emulation_fps_counter">FPS számláló</string>
+ <string name="emulation_toggle_controls">Irányítás átkapcsolása</string>
+ <string name="emulation_dpad_slide">D-pad csúsztatása</string>
+ <string name="emulation_haptics">Érintés haptikája</string>
+ <string name="emulation_show_overlay">Átfedés mutatása</string>
+ <string name="emulation_toggle_all">Össze átkapcsolása</string>
+ <string name="emulation_control_adjust">Átfedés testreszabása</string>
+ <string name="emulation_control_scale">Skálázás</string>
+ <string name="emulation_control_opacity">Átlátszóság</string>
+ <string name="emulation_touch_overlay_reset">Átfedés visszaállítása</string>
+ <string name="emulation_touch_overlay_edit">Átfedés módosítása</string>
+ <string name="emulation_pause">Emuláció szünetelése</string>
+ <string name="emulation_unpause">Emuláció folytatása</string>
+ <string name="emulation_input_overlay">Átfedés beállításai</string>
+
+ <string name="load_settings">Beállítások betöltése...</string>
+
+ <!-- Software keyboard -->
+ <string name="software_keyboard">Szoftver billenytűzet</string>
+
+ <!-- Errors and warnings -->
+ <string name="abort_button">Megszakítás</string>
+ <string name="continue_button">Folytatás</string>
+ <string name="system_archive_not_found">Nem található rendszerarchívum</string>
+ <string name="system_archive_not_found_message">%s hiányzik. Kérjük, mentsd ki a rendszerarchívumaidat.\nAz emuláció folytatása összeomlásokhoz és hibákhoz vezethet.</string>
+ <string name="system_archive_general">Egy rendszerarchívum</string>
+ <string name="save_load_error">Mentési/betöltési hiba</string>
+ <string name="fatal_error">Végzetes hiba</string>
+ <string name="fatal_error_message">Végzetes hiba történt. Ellenőrizd a logot a részletekért.\nAz emuláció folytatása összeomlást és hibákat eredményzhet.</string>
+ <string name="performance_warning">Ennek a beállításnak a kikapcsolása jelentős mértékben csökkenti a teljesítményt! A legjobb élmény érdekében javasolt a beállítás bekapcsolva tartása.</string>
+ <string name="device_memory_inadequate">Eszköz RAM: %1$s\nAjánlott: %2$s</string>
+ <string name="memory_formatted">%1$s %2$s</string>
+ <string name="no_game_present">Nincs indítható játék!</string>
+
+ <!-- Region Names -->
+ <string name="region_japan">Japán</string>
+ <string name="region_usa">USA</string>
+ <string name="region_europe">Európa</string>
+ <string name="region_australia">Ausztrália</string>
+ <string name="region_china">Kína</string>
+ <string name="region_korea">Korea</string>
+ <string name="region_taiwan">Tajvan</string>
+
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Bájt</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
+
+ <!-- Renderer APIs -->
+ <string name="renderer_vulkan">Vulkan</string>
+ <string name="renderer_none">Nincs</string>
+
+ <!-- Renderer Accuracy -->
+ <string name="renderer_accuracy_normal">Normál</string>
+ <string name="renderer_accuracy_high">Magas</string>
+ <string name="renderer_accuracy_extreme">Extrém (Lassú)</string>
+
+ <!-- Resolutions -->
+ <string name="resolution_half">0.5X (360p/540p)</string>
+ <string name="resolution_three_quarter">0.75X (540p/810p)</string>
+ <string name="resolution_one">1X (720p/1080p)</string>
+ <string name="resolution_two">2X (1440p/2160p) (Lassú)</string>
+ <string name="resolution_three">3X (2160p/3240p) (Lassú)</string>
+ <string name="resolution_four">4X (2880p/4320p) (Lassú)</string>
+
+ <!-- Renderer VSync -->
+ <string name="renderer_vsync_immediate">Azonnali (Ki)</string>
+ <string name="renderer_vsync_mailbox">Postaláda</string>
+ <string name="renderer_vsync_fifo">FIFO (Be)</string>
+ <string name="renderer_vsync_fifo_relaxed">FIFO Relaxált</string>
+
+ <!-- Scaling Filters -->
+ <string name="scaling_filter_nearest_neighbor">Legközelebbi szomszéd</string>
+ <string name="scaling_filter_bilinear">Bilineáris</string>
+ <string name="scaling_filter_bicubic">Bikubikus</string>
+ <string name="scaling_filter_gaussian">Gauss-féle</string>
+ <string name="scaling_filter_scale_force">ScaleForce</string>
+ <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
+
+ <!-- Anti-Aliasing -->
+ <string name="anti_aliasing_none">Nincs</string>
+ <string name="anti_aliasing_fxaa">FXAA</string>
+ <string name="anti_aliasing_smaa">SMAA</string>
+
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">Fekvő</string>
+ <string name="screen_layout_portrait">Álló</string>
+ <string name="screen_layout_auto">Automatikus</string>
+
+ <!-- Aspect Ratios -->
+ <string name="ratio_default">Alapértelmezett (16:9)</string>
+ <string name="ratio_force_four_three">4:3 kényszerítése</string>
+ <string name="ratio_force_twenty_one_nine">21:9 kényszerítése</string>
+ <string name="ratio_force_sixteen_ten">16:10 kényszerítése</string>
+ <string name="ratio_stretch">Ablakhoz nyújtás</string>
+
+ <!-- CPU Accuracy -->
+ <string name="cpu_accuracy_accurate">Pontos</string>
+ <string name="cpu_accuracy_unsafe">Nem biztonságos</string>
+ <string name="cpu_accuracy_paranoid">Paranoid (Lassú)</string>
+
+ <!-- Gamepad Buttons -->
+ <string name="gamepad_d_pad">D-pad</string>
+ <string name="gamepad_left_stick">Bal kar</string>
+ <string name="gamepad_right_stick">Jobb kar</string>
+ <string name="gamepad_home">Home</string>
+ <string name="gamepad_screenshot">Képernyőmentés</string>
+
+ <!-- Disk shader cache -->
+ <string name="preparing_shaders">Árnyékolók előkészítése</string>
+ <string name="building_shaders">Árnyékolók létrehozása</string>
+
+ <!-- Theme options -->
+ <string name="change_app_theme">Alkalmazás témájának módosítása</string>
+ <string name="theme_default">Alapértelmezett</string>
+ <!-- Theme Modes -->
+ <string name="change_theme_mode">Téma váltása</string>
+ <string name="theme_mode_follow_system">Rendszerbeállítások használata</string>
+ <string name="theme_mode_light">Világos</string>
+ <string name="theme_mode_dark">Sötét</string>
+
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
+ <!-- Black backgrounds theme -->
+ <string name="use_black_backgrounds">Fekete háttér</string>
+ <string name="use_black_backgrounds_description">Sötét téma használatakor fekete háttér használata.</string>
+
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">Kép a képben</string>
+ <string name="picture_in_picture_description">Ablak minimalizálása, amikor háttérbe kerül</string>
+ <string name="pause">Szünet</string>
+ <string name="play">Lejátszás</string>
+ <string name="mute">Némítás</string>
+ <string name="unmute">Némítás feloldása</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Licenszek</string>
+ <string name="license_fidelityfx_fsr_description">Magas minőségű felskálázás az AMD-től</string>
+ </resources>
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 09c9345b0..5afebb4c4 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">Questo software permette di giocare ai giochi della console Nintendo Switch. Nessun gioco o chiave è inclusa.&lt;br /&gt;&lt;br /&gt;Prima di iniziare, perfavore individua il file <![CDATA[<b>prod.keys </b>]]> nella memoria del tuo dispositivo.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Scopri di più</a>]]></string>
<string name="emulation_notification_channel_name">L\'emulatore è attivo</string>
@@ -13,9 +13,9 @@
<string name="welcome">Benvenuto!</string>
<string name="welcome_description">Scopri come configurare &lt;b>yuzu&lt;/b> e passare all\'emulazione.</string>
<string name="get_started">Iniziare</string>
- <string name="keys">Pulsanti</string>
+ <string name="keys">Chiavi</string>
<string name="keys_description">Seleziona il tuo file &lt;b>prod.keys&lt;/b> con il pulsante in basso.</string>
- <string name="select_keys">Selezione Pulsanti</string>
+ <string name="select_keys">Seleziona le chiavi</string>
<string name="games">Giochi</string>
<string name="games_description">Seleziona la cartella &lt;b>Games&lt;/b> con il pulsante in basso.</string>
<string name="done">Fatto</string>
@@ -25,6 +25,7 @@
<string name="back">Indietro</string>
<string name="add_games">Aggiungi giochi</string>
<string name="add_games_description">Seleziona la cartella dei giochi</string>
+ <string name="step_complete">Completato!</string>
<!-- Home strings -->
<string name="home_games">Giochi</string>
@@ -38,6 +39,7 @@
<string name="add_games_warning_description">I giochi non saranno mostrati nella lista dei giochi se una cartella non è selezionata.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Cerca giochi</string>
+ <string name="search_settings">Cerca impostazione</string>
<string name="games_dir_selected">Cartella dei giochi selezionata</string>
<string name="install_prod_keys">Installa prod.keys</string>
<string name="install_prod_keys_description">Necessario per decrittografare i giochi</string>
@@ -61,15 +63,18 @@
<string name="invalid_keys_file">Selezionate chiavi non valide</string>
<string name="install_keys_success">Chiavi installate correttamente</string>
<string name="reading_keys_failure">Errore durante la lettura delle chiavi di crittografia</string>
+ <string name="install_prod_keys_failure_extension_description">Controlla che le tue chiavi abbiano l\'estensione .keys e prova di nuovo.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Controlla che le tue chiavi abbiano l\'estensione .bin e prova di nuovo</string>
<string name="invalid_keys_error">Chiavi di crittografia non valide</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">Il file selezionato è incorretto o corrotto. Per favore riesegui il dump delle tue chiavi.</string>
<string name="install_gpu_driver">Installa i driver GPU</string>
<string name="install_gpu_driver_description">Installa driver alternativi per potenziali prestazioni migliori o accuratezza.</string>
<string name="advanced_settings">Impostazioni avanzate</string>
+ <string name="advanced_settings_game">Impostazioni Avanzate: %1$s</string>
<string name="settings_description">Configura le impostazioni dell\'emulatore</string>
- <string name="search_recently_played">Giocato recentemente</string>
- <string name="search_recently_added">Aggiunto recentemente</string>
+ <string name="search_recently_played">Giocati recentemente</string>
+ <string name="search_recently_added">Aggiunti recentemente</string>
<string name="search_retail">Rivenditore</string>
<string name="search_homebrew">Homebrew</string>
<string name="open_user_folder">Apri la cartella di yuzu</string>
@@ -86,6 +91,33 @@
<string name="save_file_invalid_zip_structure_description">La prima sotto cartella <b>deve</b> chiamarsi come l\'ID del titolo del gioco.</string>
<string name="import_saves">Importa</string>
<string name="export_saves">Esporta</string>
+ <string name="install_firmware">Installa firmware</string>
+ <string name="install_firmware_description">Il firmware deve essere in un archivio ZIP ed è necessario per avviare alcuni giochi</string>
+ <string name="firmware_installing">Installando il firmware</string>
+ <string name="firmware_installed_success">Firmware installato con successo</string>
+ <string name="firmware_installed_failure">L\'installazione del firmware è fallita</string>
+ <string name="firmware_installed_failure_description">Accertati che i file .nca del firmware siano contenuti direttamente nella radice dello .zip e riprova.</string>
+ <string name="share_log">Condividi log di debug</string>
+ <string name="share_log_description">Condividi i log di yuzu per ricevere supporto</string>
+ <string name="share_log_missing">Nessun file di log trovato</string>
+ <string name="install_game_content">Installa contenuti di gioco</string>
+ <string name="install_game_content_description">Installa aggiornamenti o DLC</string>
+ <string name="installing_game_content">Installazione dei contenuti...</string>
+ <string name="install_game_content_failure">Errore durante l\'installazione del contenuto in NAND.</string>
+ <string name="install_game_content_failure_description">Accertati che i contenuti da installare siano validi e che le prod.keys siano presenti.</string>
+ <string name="install_game_content_failure_base">Installare i giochi base in NAND non è permesso, perché potrebbe causare dei conflitti con altri tipi di contenuti(Aggiornamenti e DLC)</string>
+ <string name="install_game_content_failure_file_extension">Solo i tipi NSP e XCI sono supportati. Verifica che i contenuti di gioco siano validi.</string>
+ <string name="install_game_content_failed_count">Errori di installazione: %1$d</string>
+ <string name="install_game_content_success">Contenuto/i di gioco installato/i con successo.</string>
+ <string name="install_game_content_success_install">%1$dinstallato con successo.</string>
+ <string name="install_game_content_success_overwrite">%1$dsovrascritto con successo</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">I driver personalizzati non sono supportati.</string>
+ <string name="custom_driver_not_supported_description">I driver personalizzati non sono attualmente supportati su questo dispositivo.\n Ricontrolla in futuro.</string>
+ <string name="manage_yuzu_data">Gestisci i dati di Yuzu</string>
+ <string name="manage_yuzu_data_description">Importa/Esporta il firmware, le keys, i dati utente, e altro!</string>
+ <string name="share_save_file">Condividi i tuoi dati di salvataggio</string>
+ <string name="export_save_failed">Errore durante l\'esportazione del salvataggio</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia non è reale</string>
@@ -94,7 +126,18 @@
<string name="contributors">Collaboratori</string>
<string name="contributors_description">Realizzato con \u2764 dal team yuzu</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Progetti che rendono yuzu per Android possibile</string>
<string name="build">Compilazione</string>
+ <string name="user_data">Dati Utente</string>
+ <string name="user_data_description">Importa/Esporta tutti i dati dell\'applicazione.\n\nDurante l\'importazione dei Dati Utente, quelli già esistenti verranno ELIMINATI.</string>
+ <string name="exporting_user_data">Esportazione dei Dati Utente...</string>
+ <string name="importing_user_data">Importazione dei Dati Utente...</string>
+ <string name="import_user_data">Importa i Dati Utente</string>
+ <string name="invalid_yuzu_backup">Backup di Yuzu Invalido</string>
+ <string name="user_data_export_success">Dati Utente esportati con successo</string>
+ <string name="user_data_import_success">Dati Utente importati con successo.</string>
+ <string name="user_data_export_cancelled">Esportazione annullata</string>
+ <string name="user_data_import_failed_description">Assicurati che la cartella dei Dati dell\'utente stiano nella radice del file.zip e che sia presente una cartella config in config/config.ini, poi, riprova.</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>
@@ -114,41 +157,53 @@
<string name="are_you_interested">Sei interessato?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Abilita il limite di velocità</string>
- <string name="frame_limit_enable_description">Quando abilitato, la velocità di emulazione verrà limitata a una specifica percentuale della velocità normale.</string>
+ <string name="frame_limit_enable">Limita velocità</string>
+ <string name="frame_limit_enable_description">Limita la velocità dell\'emulazione a una specifica percentuale della velocità normale.</string>
<string name="frame_limit_slider">Limite velocità percentuale</string>
- <string name="frame_limit_slider_description">Specifica la percentuale del limite della velocità di emulazione. Con quella preimpostata al 100% l\'emulazione verrà limitata alla velocità normale. Valori più alti o bassi aumenteranno o diminuiranno il limite di velocità.</string>
+ <string name="frame_limit_slider_description">Specifica la percentuale per limitare la velocità di emulazione. 100% è la velocità normale. Valori maggiori o minori aumenteranno o diminuiranno il limite di velocità</string>
<string name="cpu_accuracy">Accuratezza della CPU</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
- <string name="use_docked_mode">Modalità docked</string>
- <string name="use_docked_mode_description">Emula in modalità docked, questo aumenta la risoluzione a spese delle performance.</string>
+ <string name="use_docked_mode">Modalità Docked</string>
+ <string name="use_docked_mode_description">Aumenta la risoluzione, diminuendo le performance. La modalità portatile è usata quando disabilitato, diminuendo la risoluzione e aumentando le performance.</string>
<string name="emulated_region">Regione emulata</string>
<string name="emulated_language">Lingua emulata</string>
- <string name="select_rtc_date">Seleziona la data dall\'orologio in tempo reale</string>
- <string name="select_rtc_time">Seleziona il tempo dall\'orologio in tempo reale</string>
- <string name="use_custom_rtc">Abilità l\'orologio in tempo reale personalizzato</string>
- <string name="use_custom_rtc_description">Questa impostazione ti permette di impostare un orologio in tempo reale personalizzato separato da quello del tuo sistema corrente.</string>
- <string name="set_custom_rtc">Imposta l\'orologio in tempo reale personalizzato</string>
+ <string name="select_rtc_date">Imposta la data </string>
+ <string name="select_rtc_time">Imposta l\'ora, i minuti e i secondi.</string>
+ <string name="use_custom_rtc">RTC Personalizzato</string>
+ <string name="use_custom_rtc_description">Ti permette di impostare un orologio in tempo reale personalizzato, completamente separato da quello di sistema.</string>
+ <string name="set_custom_rtc">Imposta un orologio in tempo reale personalizzato</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Livello di accuratezza</string>
- <string name="renderer_resolution">Risoluzione</string>
+ <string name="renderer_resolution">Risoluzione (Portatile/Docked)</string>
<string name="renderer_vsync">Modalità VSync</string>
- <string name="renderer_aspect_ratio">Rapporto d\'aspetto</string>
- <string name="renderer_scaling_filter">Filtro di adattamento alla finestra</string>
+ <string name="renderer_screen_layout">Orientamento</string>
+ <string name="renderer_aspect_ratio">Rapporto d\'aspetto: </string>
+ <string name="renderer_scaling_filter">Filtro adattivo della finestra </string>
<string name="renderer_anti_aliasing">Metodo di anti-aliasing</string>
<string name="renderer_force_max_clock">Forza clock massimi (solo Adreno)</string>
<string name="renderer_force_max_clock_description">Forza la GPU a girare col massimo clock possibile (i vincoli alla temperatura saranno comunque applicati)</string>
<string name="renderer_asynchronous_shaders">Usa shaders asincrone</string>
- <string name="renderer_asynchronous_shaders_description">Compila le shaders asincronamente, questo riduce lo shutter ma potrebbe introdurre dei glitch. </string>
- <string name="renderer_debug">Abilità il debug grafico</string>
- <string name="renderer_debug_description">Quando l\'opzione è selezionata, l\'API grafica entra in una modalità di debug più lenta</string>
- <string name="use_disk_shader_cache">Usa cache shader su disco</string>
- <string name="use_disk_shader_cache_description">Riduce lo stuttering salvando e caricando le shader generate sul disco.</string>
+ <string name="renderer_asynchronous_shaders_description">Compila le shader in modo asincrone, riducendo lo stutter. Può causare glitch grafici.</string>
+ <string name="renderer_reactive_flushing">Abilita il Reactive Flushing</string>
+ <string name="renderer_reactive_flushing_description">Migliora l\'accuratezza della grafica in alcuni giochi, al costo delle performance.</string>
+ <string name="use_disk_shader_cache">Usa la cache delle shader</string>
+ <string name="use_disk_shader_cache_description">Riduce lo stuttering caricando le shader già compilate all\'avvio.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">Debug della CPU</string>
+ <string name="cpu_debug_mode_description">Imposta la CPU in modalità Debug (Più lento)</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Debug GPU</string>
+ <string name="renderer_debug_description">Imposta l\'API grafica in uno stato dedicato al Debugging. Impatta di molto sulle performance.</string>
+ <string name="fastmem">Fastmem</string>
<!-- Audio settings strings -->
+ <string name="audio_output_engine">Motore di Output</string>
<string name="audio_volume">Volume</string>
<string name="audio_volume_description">Specifica il volume dell\'audio in uscita.</string>
@@ -157,14 +212,24 @@
<string name="ini_saved">Impostazioni salvate</string>
<string name="gameid_saved">Impostazioni salvate per %1$s</string>
<string name="error_saving">Errore nel salvare %1$s.ini %2$s</string>
+ <string name="unimplemented_menu">Menu non implementato</string>
<string name="loading">Caricamento…</string>
+ <string name="shutting_down">Spegnimento...</string>
<string name="reset_setting_confirmation">Vuoi ripristinare queste impostazioni al loro valore originale?</string>
<string name="reset_to_default">Riportare alle impostazioni originali</string>
<string name="reset_all_settings">Resettare tutte le impostazioni?</string>
- <string name="reset_all_settings_description">Tutte le Impostazioni Avanzate saranno ripristinate a quelle originali. Questa operazione non è reversibile</string>
+ <string name="reset_all_settings_description">Le impostazione avanzate verranno completamente reimpostate. Questa operazione è IRREVERSIBILE.</string>
<string name="settings_reset">Reimposta le impostazioni</string>
<string name="close">Chiudi</string>
<string name="learn_more">Per saperne di più</string>
+ <string name="auto">Automatico</string>
+ <string name="submit">Invia</string>
+ <string name="string_null">Nullo</string>
+ <string name="string_import">Importa</string>
+ <string name="export">Esporta</string>
+ <string name="export_failed">Esportazione Fallita</string>
+ <string name="import_failed">Importazione Fallita</string>
+ <string name="cancelling">Cancellazione</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Seleziona il driver della GPU</string>
@@ -172,6 +237,7 @@
<string name="select_gpu_driver_install">Installa</string>
<string name="select_gpu_driver_default">Predefinito</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>
@@ -182,10 +248,11 @@
<string name="preferences_graphics">Grafica</string>
<string name="preferences_audio">Audio</string>
<string name="preferences_theme">Tema e colori</string>
+ <string name="preferences_debug">Debug</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">La tua ROM è criptata</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Per favore segui la guida per eseguire il dump della <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">cartuccia di gioco</a> o i <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">titoli installati</a>.]]></string>
+ <string name="loader_error_encrypted_roms_description"><![CDATA[Segui la nostra guida per fare il <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards\">dump delle tue cartucce di gioco</a>oppure <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop\">dei titoli già installati</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Per favore assicurati che il file <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> sia installato in modo che i giochi possano essere decrittati.]]></string>
<string name="loader_error_video_core">È stato riscontrato un errore nell\'inizializzazione del core video</string>
<string name="loader_error_video_core_description">Questo è causato solitamente dal driver incompatibile di una GPU. L\'installazione di driver GPU personalizzati potrebbe risolvere questo problema.</string>
@@ -193,28 +260,28 @@
<string name="loader_error_file_not_found">Il file della ROM non esiste</string>
<!-- Emulation Menu -->
- <string name="emulation_exit">Uscire dall\'emulazione</string>
+ <string name="emulation_exit">Arresta emulazione</string>
<string name="emulation_done">Fatto</string>
- <string name="emulation_fps_counter">Contatore degli FPS</string>
+ <string name="emulation_fps_counter">Contatore FPS</string>
<string name="emulation_toggle_controls">Controlli a interruttore</string>
<string name="emulation_rel_stick_center">Centro relativo degli Stick</string>
- <string name="emulation_dpad_slide">Slittamento del Pad Direzionale</string>
- <string name="emulation_haptics">Aptico</string>
- <string name="emulation_show_overlay">Mostra Overlay</string>
- <string name="emulation_toggle_all">Attiva/disattiva tutto</string>
- <string name="emulation_control_adjust">Aggiusta Overlay</string>
+ <string name="emulation_dpad_slide">DPad A Scorrimento</string>
+ <string name="emulation_haptics">Feedback Aptico</string>
+ <string name="emulation_show_overlay">Mostra l\'Overlay</string>
+ <string name="emulation_toggle_all">Attiva/Disattiva tutto</string>
+ <string name="emulation_control_adjust">Modifica l\'Overlay</string>
<string name="emulation_control_scale">Scala</string>
<string name="emulation_control_opacity">Opacità</string>
- <string name="emulation_touch_overlay_reset">Reimposta Overlay</string>
- <string name="emulation_touch_overlay_edit">Modifica Overlay</string>
- <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_touch_overlay_reset">Reimposta l\'Overlay</string>
+ <string name="emulation_touch_overlay_edit">Modifica l\'Overlay</string>
+ <string name="emulation_pause">Sospendi l\'emulazione</string>
+ <string name="emulation_unpause">Riprendi l\'emulazione</string>
+ <string name="emulation_input_overlay">Opzioni overlay</string>
- <string name="load_settings">Caricamento delle impostazioni...</string>
+ <string name="load_settings">Carico le impostazioni...</string>
<!-- Software keyboard -->
- <string name="software_keyboard">Tastiera software</string>
+ <string name="software_keyboard">Tastiera Software</string>
<!-- Errors and warnings -->
<string name="abort_button">Interrompi</string>
@@ -226,6 +293,9 @@
<string name="fatal_error">Errore Fatale</string>
<string name="fatal_error_message">Un errore fatale è accaduto. Controlla i log per i dettagli.\nContinuare ad emulare potrebbe portare bug o causare crash.</string>
<string name="performance_warning">Disattivare questa impostazione può ridurre significativamente le performance di emulazione! Per una migliore esperienza, è consigliato lasciare questa impostazione attivata.</string>
+ <string name="device_memory_inadequate">RAM Totale:%1$s\nRaccomandati: %2$s</string>
+ <string name="memory_formatted">%1$s%2$s</string>
+ <string name="no_game_present">Non è presente alcun gioco avviabile.</string>
<!-- Region Names -->
<string name="region_japan">Giappone</string>
@@ -236,7 +306,14 @@
<string name="region_korea">Corea</string>
<string name="region_taiwan">Taiwan</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">Kb</string>
+ <string name="memory_megabyte">Mb</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">Tb</string>
+ <string name="memory_petabyte">Pb</string>
+ <string name="memory_exabyte">Eb</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
@@ -274,12 +351,17 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">Layout Orizzontale</string>
+ <string name="screen_layout_portrait">Layout Verticale</string>
+ <string name="screen_layout_auto">Automatico</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Predefinito (16:9)</string>
<string name="ratio_force_four_three">Forza 4:3</string>
<string name="ratio_force_twenty_one_nine">Forza 21:9</string>
<string name="ratio_force_sixteen_ten">Forza 16:10</string>
- <string name="ratio_stretch">Allunga a finestra</string>
+ <string name="ratio_stretch">Adatta alla finestra</string>
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Accurata</string>
@@ -287,9 +369,9 @@
<string name="cpu_accuracy_paranoid">Paranoico (Lento)</string>
<!-- Gamepad Buttons -->
- <string name="gamepad_d_pad">D-Pad</string>
- <string name="gamepad_left_stick">Levetta sinistra</string>
- <string name="gamepad_right_stick">Levetta destra</string>
+ <string name="gamepad_d_pad">D-pad</string>
+ <string name="gamepad_left_stick">Analogico sinistro</string>
+ <string name="gamepad_right_stick">Analogico destro</string>
<string name="gamepad_home">Home</string>
<string name="gamepad_screenshot">Screenshot</string>
@@ -298,7 +380,7 @@
<string name="building_shaders">Costruendo gli shaders</string>
<!-- Theme options -->
- <string name="change_app_theme">Cambia il tema dell\'app</string>
+ <string name="change_app_theme">Cambia tema dell\'app</string>
<string name="theme_default">Predefinito</string>
<string name="theme_material_you">Material You</string>
@@ -308,8 +390,22 @@
<string name="theme_mode_light">Chiaro</string>
<string name="theme_mode_dark">Scuro</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Usa sfondi neri</string>
+ <string name="use_black_backgrounds">Sfondi neri</string>
<string name="use_black_backgrounds_description">Quando utilizzi il tema scuro, applica sfondi neri.</string>
-</resources>
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">Picture in Picture</string>
+ <string name="picture_in_picture_description">Minimizza la finestra quando viene impostata in background</string>
+ <string name="pause">Pausa</string>
+ <string name="play">Gioca</string>
+ <string name="mute">Silenzia</string>
+ <string name="unmute">Riattiva</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Licenze</string>
+ <string name="license_fidelityfx_fsr_description">Upscaling di alta qualità da parte di AMD</string>
+ </resources>
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 a0ea78bef..3be4e7d26 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
- <string name="app_disclaimer">このソフトウェアは、Nintendo Switch用のゲームを実行します。 ゲームソフトやキーは含まれません。&lt;br /&gt;&lt;br /&gt;事前に、 <![CDATA[<b> prod.keys </b>]]> ファイルをデバイスのストレージに配置しておいてください。&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">詳細</a>]]></string>
+ <string name="app_disclaimer">このソフトウェアでは、Nintendo Switchのゲームを実行できます。 ゲームソフトやキーは含まれません。&lt;br /&gt;&lt;br /&gt;事前に、 <![CDATA[<b> prod.keys </b>]]> ファイルをストレージに配置しておいてください。&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">詳細</a>]]></string>
<string name="emulation_notification_channel_name">エミュレーションが有効です</string>
<string name="emulation_notification_channel_description">エミュレーションの実行中に常設通知を表示します。</string>
<string name="emulation_notification_running">yuzu は実行中です</string>
- <string name="notice_notification_channel_description">問題が発生したときに通知を表示します。</string>
+ <string name="notice_notification_channel_name">通知とエラー</string>
+ <string name="notice_notification_channel_description">問題の発生時に通知を表示します。</string>
<string name="notification_permission_not_granted">通知が許可されていません!</string>
<!-- Setup strings -->
@@ -16,7 +17,7 @@
<string name="keys_description">下のボタンから &lt;b>prod.keys&lt;/b> ファイルを選択してください。</string>
<string name="select_keys">キーを選択</string>
<string name="games">ゲーム</string>
- <string name="games_description">下のボタンから&lt;b>ゲーム&lt;/b>があるフォルダを選択してください。</string>
+ <string name="games_description">下のボタンから&lt;b>ゲーム&lt;/b>のあるフォルダを選択してください。</string>
<string name="done">完了</string>
<string name="done_description">準備が完了しました。\nゲームをお楽しみください!</string>
<string name="text_continue">続行</string>
@@ -24,48 +25,53 @@
<string name="back">戻る</string>
<string name="add_games">ゲームを追加</string>
<string name="add_games_description">ゲームフォルダを選択</string>
+ <string name="step_complete">完了!</string>
<!-- Home strings -->
<string name="home_games">ゲーム</string>
<string name="home_search">検索</string>
<string name="home_settings">設定</string>
- <string name="empty_gamelist">ファイルが見つからないか、ゲームディレクトリがまだ選択されていません。</string>
+ <string name="empty_gamelist">ファイルが存在しないかゲームフォルダが選択されていません。</string>
<string name="search_and_filter_games">ゲームの検索と絞り込み</string>
- <string name="select_games_folder">ゲームフォルダを選択</string>
- <string name="select_games_folder_description">yuzu がゲームリストに追加できるようにします</string>
+ <string name="select_games_folder">ゲームフォルダ</string>
+ <string name="select_games_folder_description">ゲームをyuzuのゲームリストに追加します</string>
<string name="add_games_warning">ゲームフォルダの選択をスキップしますか?</string>
- <string name="add_games_warning_description">フォルダを選択しない場合、ゲームはゲームリストに表示されません。</string>
+ <string name="add_games_warning_description">フォルダを選択しないと、ゲームがリストに表示されません。</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">ゲームを検索</string>
- <string name="games_dir_selected">ゲームディレクトリが選択されました</string>
- <string name="install_prod_keys">prod.keys をインストール</string>
- <string name="install_prod_keys_description">ゲームの復号化に必要</string>
+ <string name="search_settings">検索設定</string>
+ <string name="games_dir_selected">フォルダを選択しました</string>
+ <string name="install_prod_keys">prod.keys</string>
+ <string name="install_prod_keys_description">製品版ゲームの復号化に必要です</string>
<string name="install_prod_keys_warning">キーの追加をスキップしますか?</string>
<string name="install_prod_keys_warning_description">製品版ゲームのエミュレーションには、有効なキーが必要です。続行すると自作アプリしか機能しません。</string>
<string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
<string name="notifications">通知</string>
- <string name="notifications_description">下のボタンで通知の権限を許可してください。</string>
+ <string name="notifications_description">下のボタンで通知を許可してください。</string>
<string name="give_permission">許可</string>
<string name="notification_warning">通知の許可をスキップしますか?</string>
<string name="notification_warning_description">yuzuは重要なお知らせを通知できません。</string>
<string name="permission_denied">権限が拒否されました</string>
- <string name="permission_denied_description">この権限を複数回拒否したため、システム設定で手動で許可する必要があります。</string>
+ <string name="permission_denied_description">この権限を複数回拒否したため、設定から手動で許可する必要があります。</string>
<string name="about">情報</string>
<string name="about_description">ビルドバージョン、クレジットなど</string>
<string name="warning_help">ヘルプ</string>
<string name="warning_skip">スキップ</string>
<string name="warning_cancel">キャンセル</string>
- <string name="install_amiibo_keys">Amiibo キーをインストール</string>
- <string name="install_amiibo_keys_description">ゲーム内での Amiibo の使用に必要</string>
- <string name="invalid_keys_file">無効なキーファイルが選択されました</string>
+ <string name="install_amiibo_keys">Amiibo</string>
+ <string name="install_amiibo_keys_description">ゲーム内での Amiibo の使用に必要です</string>
+ <string name="invalid_keys_file">無効なキーファイルです</string>
<string name="install_keys_success">正常にインストールされました</string>
- <string name="reading_keys_failure">暗号化キーの読み取りエラー</string>
- <string name="invalid_keys_error">暗号化キーが無効です</string>
+ <string name="reading_keys_failure">暗号化キーの読み込み失敗</string>
+ <string name="install_prod_keys_failure_extension_description">キーの拡張子が.keysであることを確認し、再度お試しください。</string>
+ <string name="install_amiibo_keys_failure_extension_description">キーの拡張子が.binであることを確認し、再度お試しください。</string>
+ <string name="invalid_keys_error">暗号化キーが無効</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
- <string name="install_keys_failure_description">選択されたファイルが不正または破損しています。キーを再ダンプしてください。</string>
- <string name="install_gpu_driver">GPUドライバーをインストール</string>
+ <string name="install_keys_failure_description">ファイルが間違っているか破損しています。キーを再ダンプしてください。</string>
+ <string name="install_gpu_driver">GPUドライバー</string>
<string name="install_gpu_driver_description">代替ドライバーをインストールしてパフォーマンスや精度を向上させます</string>
<string name="advanced_settings">高度な設定</string>
+ <string name="advanced_settings_game">高度な設定: %1$s</string>
<string name="settings_description">エミュレーターの設定を構成します</string>
<string name="search_recently_played">最近プレイした</string>
<string name="search_recently_added">最近追加された</string>
@@ -77,15 +83,34 @@
<string name="no_file_manager">ファイルマネージャーが見つかりませんでした</string>
<string name="notification_no_directory_link">yuzuのディレクトリを開けません</string>
<string name="notification_no_directory_link_description">ファイルマネージャのサイドパネルでユーザーフォルダを手動で探してください。</string>
- <string name="manage_save_data">セーブデータを管理</string>
- <string name="manage_save_data_description">セーブデータが見つかりました。以下のオプションから選択してください。</string>
+ <string name="manage_save_data">セーブデータ</string>
+ <string name="manage_save_data_description">セーブデータが見つかりました。操作を選択してください。</string>
<string name="import_export_saves_description">セーブファイルをインポート/エクスポート</string>
<string name="save_file_imported_success">インポートが完了しました</string>
- <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効です</string>
+ <string name="save_file_invalid_zip_structure">セーブデータのディレクトリ構造が無効</string>
<string name="save_file_invalid_zip_structure_description">最初のサブフォルダ名は、ゲームのタイトルIDである必要があります。</string>
<string name="import_saves">インポート</string>
<string name="export_saves">エクスポート</string>
-
+ <string name="install_firmware">ファームウェア</string>
+ <string name="install_firmware_description">ファームウェアはZIPアーカイブである必要があり、一部のゲームを起動するのに必要です</string>
+ <string name="firmware_installing">ファームウェアをインストール中</string>
+ <string name="firmware_installed_success">インストールが完了しました</string>
+ <string name="firmware_installed_failure">インストール失敗</string>
+ <string name="share_log">デバッグログ</string>
+ <string name="share_log_description">yuzuのログファイルを共有して問題をデバッグします</string>
+ <string name="share_log_missing">ログが見つかりません</string>
+ <string name="install_game_content">追加コンテンツ</string>
+ <string name="install_game_content_description">更新データやDLCをインストールします</string>
+ <string name="installing_game_content">コンテンツをインストール中...</string>
+ <string name="install_game_content_failure_file_extension">NSPとXCI形式のコンテンツのみサポートされています。ゲームコンテンツが有効なものであるかご確認ください。</string>
+ <string name="install_game_content_failed_count">%1$d のインストールエラー</string>
+ <string name="install_game_content_success">ゲームコンテンツのインストールに成功しました</string>
+ <string name="install_game_content_success_install">%1$d のインストールに成功しました</string>
+ <string name="install_game_content_success_overwrite">%1$d の上書きに成功しました</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">カスタムドライバはサポートされていません</string>
+ <string name="manage_yuzu_data">yuzu データを管理</string>
+ <string name="share_save_file">セーブファイルを共有</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">ガイアは実在しない</string>
<string name="copied_to_clipboard">クリップボードにコピーしました</string>
@@ -93,7 +118,15 @@
<string name="contributors">貢献者</string>
<string name="contributors_description">yuzuチームの\u2764で作られた</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">yuzu for Androidの作成を可能にしたプロジェクト</string>
<string name="build">ビルド</string>
+ <string name="user_data">ユーザデータ</string>
+ <string name="exporting_user_data">ユーザデータをエクスポート中...</string>
+ <string name="importing_user_data">ユーザデータをインポート中...</string>
+ <string name="import_user_data">ユーザデータをインポート</string>
+ <string name="user_data_export_success">ユーザデータのエクスポートに成功しました</string>
+ <string name="user_data_import_success">ユーザデータのインポートに成功しました</string>
+ <string name="user_data_export_cancelled">エクスポートをキャンセルしました</string>
<string name="support_link">https://discord.gg/u77vRWY</string>
<string name="website_link">https://yuzu-emu.org/</string>
<string name="github_link">https://github.com/yuzu-emu</string>
@@ -105,72 +138,91 @@
<string name="get_early_access_description">最先端の機能、アップデートの早期アクセスなど</string>
<string name="early_access_benefits">早期アクセスのメリット</string>
<string name="cutting_edge_features">最先端の機能</string>
- <string name="early_access_updates">アップデートの早期アクセス</string>
+ <string name="early_access_updates">アップデートへの早期アクセス</string>
<string name="no_manual_installation">手動インストールが不要</string>
- <string name="prioritized_support">優先的なサポート</string>
+ <string name="prioritized_support">優先サポート</string>
<string name="helping_game_preservation">ゲームの保存に貢献</string>
- <string name="our_eternal_gratitude">私たちの永遠の感謝</string>
+ <string name="our_eternal_gratitude">私たちから永遠の感謝</string>
<string name="are_you_interested">興味がありますか?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">速度制限を有効化</string>
- <string name="frame_limit_enable_description">有効にすると、エミュレーション速度が任意の割合に制限されます。</string>
- <string name="frame_limit_slider">エミュレーション速度の制限</string>
- <string name="frame_limit_slider_description">エミュレーション速度を制限する割合を指定します。デフォルトの100%では、エミュレーションは通常の速度に制限されます。値が高いまたは低いほど、速度制限が増加または減少します。</string>
+ <string name="frame_limit_enable">エミュレーション速度を制限</string>
+ <string name="frame_limit_enable_description">エミュレーション速度を指定した割合に制限します。</string>
+ <string name="frame_limit_slider">エミュレーション速度</string>
+ <string name="frame_limit_slider_description">エミュレーション速度を制限するパーセンテージを指定します。100%は通常速度です。値の増減で速度も増減します。</string>
<string name="cpu_accuracy">CPU精度</string>
-
<!-- System settings strings -->
<string name="use_docked_mode">TVモード</string>
- <string name="use_docked_mode_description">TVモードでエミュレートします。パフォーマンスが犠牲になりますが、解像度が向上します。</string>
+ <string name="use_docked_mode_description">高解像度、低パフォーマンス。無効時には携帯モードが使用されます(低解像度、高パフォーマンス)。</string>
<string name="emulated_region">地域</string>
<string name="emulated_language">言語</string>
<string name="select_rtc_date">RTCの日付を選択</string>
<string name="select_rtc_time">RTCの時刻を選択</string>
- <string name="use_custom_rtc">カスタムRTC</string>
- <string name="use_custom_rtc_description">現在のシステム時間とは別にカスタムのリアルタイムクロックを設定できます。</string>
+ <string name="use_custom_rtc">カスタム RTC</string>
+ <string name="use_custom_rtc_description">現在のシステム時間とは別に、任意のリアルタイムクロックを設定できます。</string>
<string name="set_custom_rtc">カスタムRTCを設定</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">精度</string>
- <string name="renderer_resolution">解像度</string>
+ <string name="renderer_resolution">解像度(携帯モード/TVモード)</string>
<string name="renderer_vsync">垂直同期モード</string>
+ <string name="renderer_screen_layout">画面の向き</string>
<string name="renderer_aspect_ratio">アスペクト比</string>
<string name="renderer_scaling_filter">ウィンドウ適応フィルター</string>
<string name="renderer_anti_aliasing">アンチエイリアス方式</string>
<string name="renderer_force_max_clock">最大クロックを強制 (Adrenoのみ)</string>
- <string name="renderer_force_max_clock_description">GPUを可能な限り最大クロックで動作させます (過熱制限は引き続き適用されます)。</string>
+ <string name="renderer_force_max_clock_description">GPUを最大限可能な周波数で動作させます (過熱制限は引き続き適用されます)。</string>
<string name="renderer_asynchronous_shaders">非同期シェーダー</string>
<string name="renderer_asynchronous_shaders_description">シェーダーを非同期でコンパイルします。コマ落ちが軽減されますが、不具合が発生する可能性があります。</string>
+ <string name="renderer_reactive_flushing">即時書き込み</string>
+ <string name="renderer_reactive_flushing_description">一部のゲームにおいて、パフォーマンスを犠牲にしながらも、レンダリング精度を向上させます。</string>
+ <string name="use_disk_shader_cache">ディスクシェーダーキャッシュ</string>
+ <string name="use_disk_shader_cache_description">生成したシェーダーを端末に保存して読み込み、コマ落ちを軽減します。</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">CPU デバッギング</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
<string name="renderer_debug">グラフィックデバッグ</string>
- <string name="renderer_debug_description">オンにすると、グラフィックAPI は低速のデバッグモードに入ります。</string>
- <string name="use_disk_shader_cache">シェーダーキャッシュを使用</string>
- <string name="use_disk_shader_cache_description">生成したシェーダーをディスクに保存して読み込むことで、コマ落ちを軽減します。</string>
+ <string name="renderer_debug_description">グラフィックAPIを低速デバッグモードに設定します。</string>
+ <string name="fastmem">Fastmem</string>
<!-- Audio settings strings -->
+ <string name="audio_output_engine">出力エンジン</string>
<string name="audio_volume">音量</string>
<string name="audio_volume_description">オーディオ出力の音量を指定します</string>
<!-- Miscellaneous -->
<string name="slider_default">デフォルト</string>
<string name="ini_saved">設定を保存しました</string>
- <string name="gameid_saved">%1$sの設定を保存しました</string>
+ <string name="gameid_saved">%1$s の設定を保存しました</string>
<string name="error_saving">%1$s.ini の保存エラー: %2$s</string>
+ <string name="unimplemented_menu">未実装のメニュー</string>
<string name="loading">読み込み中…</string>
+ <string name="shutting_down">終了中...</string>
<string name="reset_setting_confirmation">この設定を初期値にリセットしますか?</string>
<string name="reset_to_default">初期設定に戻す</string>
<string name="reset_all_settings">すべての設定をリセットしますか?</string>
- <string name="reset_all_settings_description">すべての詳細設定が初期設定に戻されます。この操作は元に戻せません。</string>
+ <string name="reset_all_settings_description">すべての詳細設定が初期値に戻されます。この操作は元に戻せません。</string>
<string name="settings_reset">設定をリセットしました</string>
<string name="close">閉じる</string>
<string name="learn_more">詳細情報</string>
+ <string name="auto">自動</string>
+ <string name="submit">送信</string>
+ <string name="string_import">インポート</string>
+ <string name="export">エクスポート</string>
+ <string name="export_failed">エクスポート失敗</string>
+ <string name="import_failed">インポート失敗</string>
+ <string name="cancelling">キャンセル中</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">GPUドライバを選択</string>
- <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
+ <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_use_default">デフォルトのGPUドライバーを使用します</string>
+ <string name="select_gpu_driver_use_default">デフォルトのドライバを使用します</string>
+ <string name="select_gpu_driver_error">選択されたドライバが無効、システムのデフォルトを使用します!</string>
<string name="system_gpu_driver">システムのGPUドライバ</string>
<string name="installing_driver">インストール中…</string>
@@ -181,33 +233,34 @@
<string name="preferences_graphics">グラフィック</string>
<string name="preferences_audio">サウンド</string>
<string name="preferences_theme">テーマと色</string>
+ <string name="preferences_debug">デバッグ</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">ROMが暗号化されています</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">ゲームカートリッジ</a>や<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">インストール済みのタイトル</a>を再度ダンプするためのガイドに従ってください。]]></string>
- <string name="loader_error_encrypted_keys_description"><![CDATA[ゲームを復号化するために <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> ファイルがインストールされていることを確認してください。]]></string>
+ <string name="loader_error_encrypted_keys_description"><![CDATA[ゲームの復号化に必要な <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> ファイルがインストールされていることを確認してください。]]></string>
<string name="loader_error_video_core">ビデオコアの初期化中にエラーが発生しました</string>
<string name="loader_error_video_core_description">これは通常、互換性のないGPUドライバーが原因で発生します。 カスタムGPUドライバーをインストールすると、問題が解決する可能性があります。</string>
<string name="loader_error_invalid_format">ROMの読み込みに失敗しました</string>
<string name="loader_error_file_not_found">ROMファイルが存在しません</string>
<!-- Emulation Menu -->
- <string name="emulation_exit">エミュレーションを終了</string>
+ <string name="emulation_exit">終了</string>
<string name="emulation_done">完了</string>
<string name="emulation_fps_counter">FPSカウンター</string>
- <string name="emulation_toggle_controls">コントロールを切り替え</string>
- <string name="emulation_dpad_slide">十字キーのスライド操作</string>
- <string name="emulation_haptics">振動</string>
- <string name="emulation_show_overlay">オーバーレイを表示</string>
- <string name="emulation_toggle_all">すべて選択</string>
- <string name="emulation_control_adjust">オーバーレイを調整</string>
+ <string name="emulation_toggle_controls">ボタンの表示設定</string>
+ <string name="emulation_rel_stick_center">スティックを固定しない</string>
+ <string name="emulation_dpad_slide">十字キーをスライド操作</string>
+ <string name="emulation_haptics">タッチ振動</string>
+ <string name="emulation_show_overlay">ボタンを表示</string>
+ <string name="emulation_toggle_all">すべて切替</string>
+ <string name="emulation_control_adjust">見た目を調整</string>
<string name="emulation_control_scale">大きさ</string>
<string name="emulation_control_opacity">不透明度</string>
<string name="emulation_touch_overlay_reset">リセット</string>
- <string name="emulation_touch_overlay_edit">オーバーレイを編集</string>
- <string name="emulation_pause">エミュレーションを一時停止</string>
- <string name="emulation_unpause">エミュレーションを再開</string>
- <string name="emulation_input_overlay">オーバーレイオプション</string>
+ <string name="emulation_touch_overlay_edit">位置を編集</string>
+ <string name="emulation_pause">一時停止</string>
+ <string name="emulation_unpause">再開</string>
+ <string name="emulation_input_overlay">表示オプション</string>
<string name="load_settings">設定をロード中…</string>
@@ -220,10 +273,13 @@
<string name="system_archive_not_found">システムアーカイブが見つかりません</string>
<string name="system_archive_not_found_message">%s が見つかりません。システムアーカイブをダンプしてください。\nエミュレーションを続行すると、クラッシュやバグが発生する可能性があります。</string>
<string name="system_archive_general">システムアーカイブ</string>
- <string name="save_load_error">セーブ/ロード エラー</string>
+ <string name="save_load_error">セーブ/ロードエラー</string>
<string name="fatal_error">致命的なエラー</string>
<string name="fatal_error_message">致命的なエラーが発生しました。詳細はログを確認してください。\nエミュレーションを続行するとクラッシュやバグが発生する可能性があります。</string>
- <string name="performance_warning">この設定をオフにすると、エミュレーションのパフォーマンスが著しく低下します!最高の体験を得るためには、この設定を有効にしておくことをお勧めします。</string>
+ <string name="performance_warning">この設定をオフにすると、エミュレーションのパフォーマンスが著しく低下します!最高の体験を得るためには、この設定を有効にしておくことを推奨します。</string>
+ <string name="device_memory_inadequate">デバイス RAM: %1$s\n推奨: %2$s</string>
+ <string name="memory_formatted">%1$s %2$s</string>
+ <string name="no_game_present">起動できるゲームがありません!</string>
<!-- Region Names -->
<string name="region_japan">日本</string>
@@ -234,7 +290,14 @@
<string name="region_korea">韓国</string>
<string name="region_taiwan">台湾</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
@@ -242,7 +305,7 @@
<!-- Renderer Accuracy -->
<string name="renderer_accuracy_normal">標準</string>
- <string name="renderer_accuracy_high">高い</string>
+ <string name="renderer_accuracy_high">高</string>
<string name="renderer_accuracy_extreme">最高 (低速)</string>
<!-- Resolutions -->
@@ -272,12 +335,17 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">横長</string>
+ <string name="screen_layout_portrait">縦長</string>
+ <string name="screen_layout_auto">自動</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">デフォルト (16:9)</string>
<string name="ratio_force_four_three">強制 4:3</string>
<string name="ratio_force_twenty_one_nine">強制 21:9</string>
<string name="ratio_force_sixteen_ten">強制 16:10</string>
- <string name="ratio_stretch">ウィンドウに合わせる</string>
+ <string name="ratio_stretch">画面に合わせる</string>
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">正確</string>
@@ -289,7 +357,7 @@
<string name="gamepad_left_stick">Lスティック</string>
<string name="gamepad_right_stick">Rスティック</string>
<string name="gamepad_home">HOMEボタン</string>
- <string name="gamepad_screenshot">スクリーンショット</string>
+ <string name="gamepad_screenshot">キャプチャーボタン</string>
<!-- Disk shader cache -->
<string name="preparing_shaders">シェーダーを準備しています</string>
@@ -306,8 +374,22 @@
<string name="theme_mode_light">ライト</string>
<string name="theme_mode_dark">ダーク</string>
- <!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">黒色の背景を使用</string>
- <string name="use_black_backgrounds_description">ダークテーマの使用時は、黒色の背景を有効にしてください。</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
-</resources>
+ <!-- Black backgrounds theme -->
+ <string name="use_black_backgrounds">完全な黒を使用</string>
+ <string name="use_black_backgrounds_description">ダークテーマの背景色に黒が適用されます。</string>
+
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">ピクチャーインピクチャー</string>
+ <string name="picture_in_picture_description">バックグラウンド時にウインドウを最小化する</string>
+ <string name="pause">中断</string>
+ <string name="play">プレイ</string>
+ <string name="mute">消音</string>
+ <string name="unmute">消音解除</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">ライセンス</string>
+ <string name="license_fidelityfx_fsr_description">AMDの高品質アップスケーリング</string>
+ </resources>
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 214f95706..1b9160a23 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
- <string name="app_disclaimer">이 소프트웨어는 닌텐도 스위치 게임 콘솔용 게임을 실행합니다. 게임 타이틀이나 keys는 포함되어 있지 않습니다.&lt;br /&gt;&lt;br /&gt;시작하기 전에 장치 저장소에서 <![CDATA[<b> prod.keys </b>]]> 파일을 찾아주세요.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">자세히 알아보기</a>]]></string>
+ <string name="app_disclaimer">이 소프트웨어는 Nintendo Switch 게임을 실행합니다. 게임 타이틀이나 키는 포함되어 있지 않습니다.&lt;br /&gt;&lt;br /&gt;시작하기 전에 장치 저장소에서 <![CDATA[<b> prod.keys </b>]]> 파일을 찾아주세요.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">자세히 알아보기</a>]]></string>
<string name="emulation_notification_channel_name">에뮬레이션이 활성화됨</string>
- <string name="emulation_notification_channel_description">에뮬레이션이 실행 중일 때 영구 알림을 표시합니다.</string>
+ <string name="emulation_notification_channel_description">에뮬레이션이 실행 중일 때 지속적으로 알림을 표시합니다.</string>
<string name="emulation_notification_running">yuzu가 실행 중입니다.</string>
<string name="notice_notification_channel_name">알림 및 오류</string>
<string name="notice_notification_channel_description">문제가 발생하면 알림을 표시합니다.</string>
@@ -11,26 +11,25 @@
<!-- Setup strings -->
<string name="welcome">환영합니다!</string>
- <string name="welcome_description">&lt;b>yuzu&lt;/b> 를 설정하고 에뮬레이션으로 이동하는 방법을 알아보세요.</string>
+ <string name="welcome_description">&lt;b>yuzu&lt;/b>를 설정하고 에뮬레이션을 시작하세요.</string>
<string name="get_started">시작하기</string>
- <string name="keys">Keys</string>
- <string name="keys_description">아래 버튼을 사용하여 &lt;b>prod.keys&lt;/b> 파일을 선택합니다.</string>
- <string name="select_keys">keys 선택</string>
+ <string name="keys">키 설정</string>
+ <string name="keys_description">아래 버튼으로 &lt;b>prod.keys&lt;/b> 파일을 선택합니다.</string>
+ <string name="select_keys">키 선택</string>
<string name="games">게임</string>
<string name="games_description">아래 버튼으로 &lt;b>게임&lt;/b> 폴더를 선택합니다.</string>
<string name="done">완료</string>
- <string name="done_description">모든 준비가 완료되었습니다.\n게임을 즐기세요!</string>
+ <string name="done_description">모두 준비되었습니다.\n게임을 즐기세요!</string>
<string name="text_continue">계속</string>
<string name="next">다음</string>
- <string name="back">뒤로</string>
+ <string name="back">이전</string>
<string name="add_games">게임 추가</string>
<string name="add_games_description">게임 폴더 선택</string>
-
<!-- Home strings -->
<string name="home_games">게임</string>
<string name="home_search">검색</string>
<string name="home_settings">설정</string>
- <string name="empty_gamelist">파일을 찾을 수 없거나 아직 게임 디렉토리를 선택하지 않았습니다.</string>
+ <string name="empty_gamelist">파일을 찾을 수 없거나 아직 게임 디렉터리를 선택하지 않았습니다.</string>
<string name="search_and_filter_games">게임 검색 및 필터링</string>
<string name="select_games_folder">게임 폴더 선택</string>
<string name="select_games_folder_description">yuzu가 게임 목록을 채울 수 있도록 허용</string>
@@ -38,140 +37,160 @@
<string name="add_games_warning_description">폴더를 선택하지 않으면 게임 목록에 게임이 표시되지 않습니다.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">게임 검색</string>
- <string name="games_dir_selected">게임 디렉터리 선택</string>
+ <string name="games_dir_selected">게임 디렉터리를 설정했습니다.</string>
<string name="install_prod_keys">prod.keys 설치</string>
- <string name="install_prod_keys_description">판매용 게임 암호 해독에 요구</string>
- <string name="install_prod_keys_warning">keys 추가를 건너뛰겠습니까?</string>
- <string name="install_prod_keys_warning_description">정품 게임을 에뮬레이트하려면 유효한 keys가 필요합니다. 계속하면 자체 제작 앱만 작동합니다.</string>
+ <string name="install_prod_keys_description">패키지 게임 암호 해독에 필요</string>
+ <string name="install_prod_keys_warning">키 추가를 건너뛰겠습니까?</string>
+ <string name="install_prod_keys_warning_description">패키지 게임을 에뮬레이트하려면 유효한 키 값이 필요합니다. 이 단계를 건너뛰면 홈브류 게임만 실행할 수 있습니다.</string>
<string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
<string name="notifications">알림</string>
<string name="notifications_description">아래 버튼으로 알림 권한을 부여합니다.</string>
- <string name="give_permission">권한 부여</string>
- <string name="notification_warning">알림 권한 부여를 건너뛰겠습니까?</string>
- <string name="notification_warning_description">yuzu는 중요한 정보를 알려드리지 않습니다.</string>
+ <string name="give_permission">알림 켜기</string>
+ <string name="notification_warning">알림을 끄겠습니까?</string>
+ <string name="notification_warning_description">yuzu가 중요한 정보를 알려드리지 않습니다.</string>
<string name="permission_denied">권한 거부됨</string>
- <string name="permission_denied_description">이 권한을 너무 많이 거부했으므로 이제 시스템 설정에서 수동으로 권한을 부여해야 합니다.</string>
+ <string name="permission_denied_description">권한 허용을 너무 많이 거부하여 시스템 설정에서 수동으로 권한을 부여해야 합니다.</string>
<string name="about">정보</string>
<string name="about_description">빌드 버전, 크레딧 등</string>
<string name="warning_help">도움말</string>
<string name="warning_skip">건너뛰기</string>
<string name="warning_cancel">취소</string>
- <string name="install_amiibo_keys">Amiibo keys 설치</string>
- <string name="install_amiibo_keys_description">게임에서 아미보 사용 시 필요</string>
- <string name="invalid_keys_file">잘못된 keys 파일 선택</string>
- <string name="install_keys_success">keys가 성공적으로 설치됨</string>
- <string name="reading_keys_failure">암호화 keys 읽기 오류</string>
- <string name="invalid_keys_error">잘못된 암호화 keys</string>
+ <string name="install_amiibo_keys">amiibo 키 설치</string>
+ <string name="install_amiibo_keys_description">게임에서 amiibo 사용 시 필요</string>
+ <string name="invalid_keys_file">잘못된 키 파일이 선택됨</string>
+ <string name="install_keys_success">키 값을 설치했습니다.</string>
+ <string name="reading_keys_failure">암호화 키 읽기 오류</string>
+ <string name="install_prod_keys_failure_extension_description">키 파일의 확장자가 .keys인지 확인하고 다시 시도하세요.</string>
+ <string name="install_amiibo_keys_failure_extension_description">키 파일의 확장자가 .bin인지 확인하고 다시 시도하세요.</string>
+ <string name="invalid_keys_error">암호화 키가 올바르지 않음</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
- <string name="install_keys_failure_description">선택한 파일이 잘못되었거나 손상되었습니다. keys를 다시 덤프하세요.</string>
+ <string name="install_keys_failure_description">선택한 파일이 잘못되었거나 손상되었습니다. 키를 다시 덤프하세요.</string>
<string name="install_gpu_driver">GPU 드라이버 설치</string>
<string name="install_gpu_driver_description">잠재적으로 더 나은 성능 또는 정확성을 위해 대체 드라이버를 설치하세요.</string>
<string name="advanced_settings">고급 설정</string>
<string name="settings_description">에뮬레이터 설정 구성</string>
- <string name="search_recently_played">최근 플레이한 게임</string>
- <string name="search_recently_added">최근 추가한 게임</string>
- <string name="search_retail">판매용</string>
+ <string name="search_recently_played">최근 플레이</string>
+ <string name="search_recently_added">최근 추가</string>
+ <string name="search_retail">패키지</string>
<string name="search_homebrew">홈브류</string>
<string name="open_user_folder">yuzu 폴더 열기</string>
<string name="open_user_folder_description">yuzu의 내부 파일 관리</string>
- <string name="theme_and_color_description">앱 모양 수정</string>
+ <string name="theme_and_color_description">앱 디자인 편집</string>
<string name="no_file_manager">파일 관리자를 찾을 수 없음</string>
- <string name="notification_no_directory_link">yuzu 디렉토리를 열 수 없음</string>
+ <string name="notification_no_directory_link">yuzu 디렉터리를 열 수 없음</string>
<string name="notification_no_directory_link_description">파일 관리자의 사이드 패널에서 사용자 폴더를 수동으로 찾아주세요.</string>
<string name="manage_save_data">저장 데이터 관리</string>
- <string name="manage_save_data_description">데이터를 저장했습니다. 아래에서 옵션을 선택하세요.</string>
+ <string name="manage_save_data_description">저장 데이터를 발견했습니다. 아래에서 옵션을 선택하세요.</string>
<string name="import_export_saves_description">저장 파일 가져오기 또는 내보내기</string>
- <string name="save_file_imported_success">가져오기 성공</string>
- <string name="save_file_invalid_zip_structure">저장 디렉터리 구조가 잘못됨</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>
<string name="import_saves">가져오기</string>
<string name="export_saves">내보내기</string>
-
+ <string name="install_firmware">펌웨어 설치</string>
+ <string name="install_firmware_description">펌웨어는 ZIP 파일이며 일부 게임을 부팅하는 데 필요합니다.</string>
+ <string name="firmware_installing">펌웨어 설치</string>
+ <string name="firmware_installed_success">펌웨어를 설치했습니다.</string>
+ <string name="firmware_installed_failure">펌웨어 설치 실패</string>
+ <string name="share_log">디버그 로그 공유</string>
+ <string name="share_log_description">yuzu의 로그 파일을 공유하여 문제 디버깅하기</string>
+ <string name="share_log_missing">로그 파일을 찾을 수 없습니다.</string>
+ <string name="install_game_content">게임 콘텐츠 설치</string>
+ <string name="install_game_content_description">게임 업데이트 또는 DLC 설치</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">가이아는 진짜가 아님</string>
- <string name="copied_to_clipboard">클립보드에 복사</string>
- <string name="about_app_description">오픈 소스 스위치 에뮬레이터</string>
+ <string name="copied_to_clipboard">클립보드에 복사되었습니다.</string>
+ <string name="about_app_description">오픈 소스 Switch 에뮬레이터</string>
<string name="contributors">기여자</string>
<string name="contributors_description">yuzu 팀의 \u2764로 제작</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Android용 yuzu를 가능하게 하는 프로젝트</string>
<string name="build">빌드</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>
<!-- Early access upgrade strings -->
- <string name="early_access">미리 체험하기</string>
- <string name="get_early_access">미리 체험하기 신청</string>
+ <string name="early_access">앞서 해보기</string>
+ <string name="get_early_access">앞서 해보기 신청</string>
<string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
- <string name="get_early_access_description">최첨단 기능, 미리 체험하기 업데이트 등</string>
- <string name="early_access_benefits">미리 체험하기 혜택</string>
- <string name="cutting_edge_features">최첨단 기능</string>
- <string name="early_access_updates">미리 체험하기 업데이트</string>
+ <string name="get_early_access_description">최신 기능, 업데이트 미리 체험 등</string>
+ <string name="early_access_benefits">앞서 해보기 혜택</string>
+ <string name="cutting_edge_features">최신 기능</string>
+ <string name="early_access_updates">업데이트 미리 체험</string>
<string name="no_manual_installation">수동 설치 불필요</string>
<string name="prioritized_support">우선 지원</string>
- <string name="helping_game_preservation">게임 보존 도움주기</string>
- <string name="our_eternal_gratitude">영원한 감사의 마음을 전합니다</string>
+ <string name="helping_game_preservation">게임 보존 지원</string>
+ <string name="our_eternal_gratitude">우리의 영원한 감사의 마음</string>
<string name="are_you_interested">관심 있으세요?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">제한 속도 활성화</string>
- <string name="frame_limit_enable_description">활성화하면 에뮬레이션 속도가 정상 속도의 지정된 비율로 제한됩니다.</string>
+ <string name="frame_limit_enable">속도 제한</string>
+ <string name="frame_limit_enable_description">에뮬레이션 속도를 정상 속도의 지정된 비율로 제한합니다.</string>
<string name="frame_limit_slider">속도 제한 비율</string>
- <string name="frame_limit_slider_description">에뮬레이션 속도를 제한할 비율을 지정합니다. 기본값인 100%로 설정하면 에뮬레이션이 정상 속도로 제한됩니다. 값이 높거나 낮으면 속도 제한이 증가하거나 감소합니다.</string>
+ <string name="frame_limit_slider_description">에뮬레이션 속도의 제한 비율을 지정합니다. 100%가 정상 속도입니다. 값이 높거나 낮으면 속도 제한이 증가하거나 감소합니다.</string>
<string name="cpu_accuracy">CPU 정확도</string>
-
<!-- System settings strings -->
- <string name="use_docked_mode">도킹 모드</string>
- <string name="use_docked_mode_description">도킹 모드에서 에뮬레이션하면 성능이 저하되는 대신 해상도가 향상됩니다.</string>
- <string name="emulated_region">에뮬레이트된 지역</string>
- <string name="emulated_language">에뮬레이트된 언어</string>
+ <string name="use_docked_mode">독 모드</string>
+ <string name="use_docked_mode_description">해상도를 높이며 성능이 저하됩니다. 비활성화시 휴대 모드가 사용되며 해상도는 낮아지고 성능은 향상됩니다.</string>
+ <string name="emulated_region">에뮬레이트 지역</string>
+ <string name="emulated_language">에뮬레이트 언어</string>
<string name="select_rtc_date">RTC 날짜 선택</string>
<string name="select_rtc_time">RTC 시간 선택</string>
- <string name="use_custom_rtc">커스텀 RTC 활성화</string>
- <string name="use_custom_rtc_description">이 설정을 사용하면 현재 시스템 시간과 별도로 사용자 지정 실시간 시계를 설정할 수 있음</string>
- <string name="set_custom_rtc">커스텀 RTC 설정</string>
+ <string name="use_custom_rtc">사용자 지정 RTC</string>
+ <string name="use_custom_rtc_description">현재 시스템 시간과 별도로 사용자 지정 실시간 시계를 설정할 수 있습니다.</string>
+ <string name="set_custom_rtc">사용자 지정 RTC 설정</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">정확도 수준</string>
- <string name="renderer_resolution">해상도</string>
+ <string name="renderer_resolution">해상도 (휴대 모드/독 모드)</string>
<string name="renderer_vsync">수직동기화 모드</string>
<string name="renderer_aspect_ratio">화면비</string>
- <string name="renderer_scaling_filter">창 적응 필터</string>
- <string name="renderer_anti_aliasing">안티-에일리어싱 방법</string>
- <string name="renderer_force_max_clock">최대 클럭 강제 설정 (아드레노만 해당)</string>
+ <string name="renderer_scaling_filter">윈도우 적응 필터</string>
+ <string name="renderer_anti_aliasing">안티에일리어싱 방법</string>
+ <string name="renderer_force_max_clock">최대 클럭 강제 설정 (아드레노 전용)</string>
<string name="renderer_force_max_clock_description">GPU가 가능한 최대 클럭으로 실행되도록 강제합니다 (열 제약 조건은 여전히 적용됩니다).</string>
<string name="renderer_asynchronous_shaders">비동기 셰이더 사용</string>
- <string name="renderer_asynchronous_shaders_description">셰이더를 비동기식으로 컴파일하므로 끊김 현상이 줄어들지만 글리치가 발생할 수 있습니다.</string>
- <string name="renderer_debug">그래픽 디버깅 활성화</string>
- <string name="renderer_debug_description">이 옵션을 선택하면 그래픽 API가 느린 디버깅 모드로 전환됩니다.</string>
- <string name="use_disk_shader_cache">디스크 셰이더 캐시 사용</string>
- <string name="use_disk_shader_cache_description">생성된 셰이더를 디스크에 저장하고 불러오기하여 끊김 현상을 줄입니다.</string>
-
- <!-- Audio settings strings -->
+ <string name="renderer_asynchronous_shaders_description">셰이더를 비동기식으로 컴파일하여 끊김 현상을 줄이지만 글리치가 발생할 수 있습니다.</string>
+ <string name="renderer_reactive_flushing">반응형 플러싱 사용</string>
+ <string name="renderer_reactive_flushing_description">일부 게임에서 성능 저하를 감수하고 렌더링 정확도를 향상합니다.</string>
+ <string name="use_disk_shader_cache">디스크 셰이더 캐시</string>
+ <string name="use_disk_shader_cache_description">생성된 셰이더를 로컬에 저장하고 로드하여 끊김 현상을 줄입니다.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">그래픽 디버깅</string>
+ <string name="renderer_debug_description">그래픽 API를 느린 디버깅 모드로 설정합니다.</string>
<string name="audio_volume">볼륨</string>
<string name="audio_volume_description">오디오 출력의 볼륨을 지정합니다.</string>
<!-- Miscellaneous -->
<string name="slider_default">기본값</string>
- <string name="ini_saved">저장된 설정</string>
- <string name="gameid_saved">%1$s를 위해 저장된 설정</string>
- <string name="error_saving">%1$s.ini 저장 중 오류: %2$s</string>
- <string name="loading">불러오기 중...</string>
- <string name="reset_setting_confirmation">이 설정을 기본값으로 되돌리겠습니까?</string>
+ <string name="ini_saved">설정이 저장되었습니다.</string>
+ <string name="gameid_saved">%1$s 전용 설정이 저장되었습니다.</string>
+ <string name="error_saving">%1$s.ini 저장 중 오류 발생: %2$s</string>
+ <string name="loading">불러오는 중...</string>
+ <string name="reset_setting_confirmation">이 설정을 기본값으로 재설정하겠습니까?</string>
<string name="reset_to_default">기본값으로 재설정</string>
<string name="reset_all_settings">모든 설정을 초기화하겠습니까?</string>
- <string name="reset_all_settings_description">모든 고급 설정이 기본 구성으로 재설정됩니다. 이 설정은 되돌릴 수 없습니다.</string>
+ <string name="reset_all_settings_description">모든 고급 설정이 기본 구성으로 재설정됩니다. 이 작업은 되돌릴 수 없습니다.</string>
<string name="settings_reset">설정 초기화</string>
<string name="close">닫기</string>
- <string name="learn_more">자세히 알아보기</string>
-
+ <string name="learn_more">자세히</string>
+ <string name="auto">자동</string>
+ <string name="submit">제출</string>
+ <string name="string_null">Null</string>
+ <string name="string_import">가져오기</string>
+ <string name="export">내보내기</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">GPU 드라이버 선택</string>
- <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
+ <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_use_default">기본 GPU 드라이버 사용</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>
@@ -182,51 +201,50 @@
<string name="preferences_graphics">그래픽</string>
<string name="preferences_audio">오디오</string>
<string name="preferences_theme">테마 및 색상</string>
+ <string name="preferences_debug">디버그</string>
<!-- ROM loading errors -->
- <string name="loader_error_encrypted">롬이 암호화되었음</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[가이드에 따라 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">게임 카트리지</a> 또는 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">설치된 타이틀</a>를 다시 덤프하세요.]]></string>
- <string name="loader_error_encrypted_keys_description"><![CDATA[P게임을 해독할 수 있도록 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> 파일이 설치되어 있는지 확인하세요.]]></string>
+ <string name="loader_error_encrypted">롬 파일이 암호화되어있음</string>
+ <string name="loader_error_encrypted_keys_description"><![CDATA[게임을 해독할 수 있도록 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> 파일이 설치되어 있는지 확인하세요.]]></string>
<string name="loader_error_video_core">비디오 코어를 초기화하는 동안 오류 발생</string>
- <string name="loader_error_video_core_description">이 문제는 일반적으로 호환되지 않는 GPU 드라이버로 인해 발생합니다. 사용자 지정 GPU 드라이버를 설치하면 이 문제가 해결될 수 있습니다.</string>
- <string name="loader_error_invalid_format">롬을 불러올 수 없음</string>
+ <string name="loader_error_video_core_description">일반적으로 이 문제는 호환되지 않는 GPU 드라이버로 인해 발생합니다. 사용자 지정 GPU 드라이버를 설치하면 이 문제가 해결될 수 있습니다.</string>
+ <string name="loader_error_invalid_format">롬 파일을 불러올 수 없음</string>
<string name="loader_error_file_not_found">롬 파일이 존재하지 않음</string>
<!-- Emulation Menu -->
<string name="emulation_exit">에뮬레이션 종료</string>
<string name="emulation_done">완료</string>
- <string name="emulation_fps_counter">FPS 카운터</string>
- <string name="emulation_toggle_controls">토글 제어</string>
- <string name="emulation_rel_stick_center">상대 스틱 센터</string>
- <string name="emulation_dpad_slide">십자패드 슬라이드</string>
- <string name="emulation_haptics">햅틱</string>
- <string name="emulation_show_overlay">오버레이 표시</string>
- <string name="emulation_toggle_all">모두 토글</string>
- <string name="emulation_control_adjust">오버레이 조정</string>
- <string name="emulation_control_scale">스케일</string>
+ <string name="emulation_fps_counter">FPS 표시</string>
+ <string name="emulation_toggle_controls">컨트롤러 선택</string>
+ <string name="emulation_rel_stick_center">스틱의 중심 이동</string>
+ <string name="emulation_dpad_slide">십자키 슬라이드</string>
+ <string name="emulation_haptics">터치 햅틱</string>
+ <string name="emulation_show_overlay">컨트롤러 표시</string>
+ <string name="emulation_toggle_all">모두 선택</string>
+ <string name="emulation_control_adjust">컨트롤러 조정</string>
+ <string name="emulation_control_scale">크기</string>
<string name="emulation_control_opacity">불투명도</string>
- <string name="emulation_touch_overlay_reset">오버레이 재설정</string>
- <string name="emulation_touch_overlay_edit">오버레이 편집</string>
+ <string name="emulation_touch_overlay_reset">컨트롤러 설정 초기화</string>
+ <string name="emulation_touch_overlay_edit">컨트롤러 위치 편집</string>
<string name="emulation_pause">에뮬레이션 일시 중지</string>
<string name="emulation_unpause">에뮬레이션 일시 중지 해제</string>
- <string name="emulation_input_overlay">오버레이 옵션</string>
+ <string name="emulation_input_overlay">화면 오버레이 설정</string>
- <string name="load_settings">설정 불러오기 중...</string>
+ <string name="load_settings">설정 불러오는 중...</string>
<!-- Software keyboard -->
- <string name="software_keyboard">가상 키보드</string>
+ <string name="software_keyboard">소프트웨어 키보드</string>
<!-- Errors and warnings -->
- <string name="abort_button">정보</string>
+ <string name="abort_button">중단</string>
<string name="continue_button">계속</string>
<string name="system_archive_not_found">시스템 아카이브를 찾을 수 없음</string>
<string name="system_archive_not_found_message">%s가 누락되었습니다. 시스템 아카이브를 덤프하세요.\n에뮬레이션을 계속하면 충돌 및 버그가 발생할 수 있습니다.</string>
<string name="system_archive_general">시스템 아카이브</string>
<string name="save_load_error">저장하기/불러오기 오류</string>
- <string name="fatal_error">치명적인 오류</string>
- <string name="fatal_error_message">치명적인 오류가 발생했습니다. 자세한 내용은 로그를 확인하십시오.\n에뮬레이션을 계속하면 충돌 및 버그가 발생할 수 있습니다.</string>
+ <string name="fatal_error">치명적 오류</string>
+ <string name="fatal_error_message">치명적 오류가 발생했습니다. 자세한 내용은 로그를 확인하십시오.\n에뮬레이션을 계속하면 충돌 및 버그가 발생할 수 있습니다.</string>
<string name="performance_warning">이 설정을 끄면 에뮬레이션 성능이 크게 저하됩니다! 최상의 환경을 위해 이 설정을 활성화된 상태로 두는 것이 좋습니다.</string>
-
<!-- Region Names -->
<string name="region_japan">일본</string>
<string name="region_usa">미국</string>
@@ -234,12 +252,11 @@
<string name="region_australia">호주</string>
<string name="region_china">중국</string>
<string name="region_korea">대한민국</string>
- <string name="region_taiwan">타이완</string>
-
- <!-- Language Names -->
+ <string name="region_taiwan">대만</string>
+ <string name="memory_gigabyte">영국 하계 표준시(GB)</string>
<!-- Renderer APIs -->
- <string name="renderer_vulkan">불칸</string>
+ <string name="renderer_vulkan">Vulcan</string>
<string name="renderer_none">없음</string>
<!-- Renderer Accuracy -->
@@ -256,17 +273,17 @@
<string name="resolution_four">4X (2880p/4320p) (느림)</string>
<!-- Renderer VSync -->
- <string name="renderer_vsync_immediate">즉시 (끔)</string>
+ <string name="renderer_vsync_immediate">즉각 표시 (끄기)</string>
<string name="renderer_vsync_mailbox">메일박스</string>
- <string name="renderer_vsync_fifo">FIFO (켬)</string>
- <string name="renderer_vsync_fifo_relaxed">FIFO 릴랙스</string>
+ <string name="renderer_vsync_fifo">FIFO (켜기)</string>
+ <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
<!-- Scaling Filters -->
- <string name="scaling_filter_nearest_neighbor">가장 가까운 이웃</string>
- <string name="scaling_filter_bilinear">이중선형</string>
- <string name="scaling_filter_bicubic">고등차수보간</string>
+ <string name="scaling_filter_nearest_neighbor">최근접 보간</string>
+ <string name="scaling_filter_bilinear">쌍선형 보간</string>
+ <string name="scaling_filter_bicubic">쌍입방 보간</string>
<string name="scaling_filter_gaussian">가우시안</string>
- <string name="scaling_filter_scale_force">스케일포스</string>
+ <string name="scaling_filter_scale_force">ScaleForce</string>
<string name="scaling_filter_fsr">AMD FidelityFX™ 초고해상도</string>
<!-- Anti-Aliasing -->
@@ -274,27 +291,29 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <string name="screen_layout_auto">자동</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">기본 (16:9)</string>
<string name="ratio_force_four_three">강제 4:3</string>
<string name="ratio_force_twenty_one_nine">강제 21:9</string>
<string name="ratio_force_sixteen_ten">강제 16:10</string>
- <string name="ratio_stretch">창에 맞게 늘림</string>
+ <string name="ratio_stretch">화면에 맞춤</string>
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">정확함</string>
- <string name="cpu_accuracy_unsafe">안전하지 않음</string>
- <string name="cpu_accuracy_paranoid">편집증 (느림)</string>
+ <string name="cpu_accuracy_unsafe">최적화 (안전하지 않음)</string>
+ <string name="cpu_accuracy_paranoid">최적화하지 않음 (느림)</string>
<!-- Gamepad Buttons -->
- <string name="gamepad_d_pad">십자패드</string>
+ <string name="gamepad_d_pad">십자키</string>
<string name="gamepad_left_stick">L 스틱</string>
<string name="gamepad_right_stick">R 스틱</string>
<string name="gamepad_home">홈</string>
<string name="gamepad_screenshot">스크린샷</string>
<!-- Disk shader cache -->
- <string name="preparing_shaders">셰이더 준비하기</string>
+ <string name="preparing_shaders">셰이더 준비하는 중</string>
<string name="building_shaders">셰이더 빌드 중</string>
<!-- Theme options -->
@@ -303,13 +322,19 @@
<string name="theme_material_you">Material You</string>
<!-- Theme Modes -->
- <string name="change_theme_mode">테마 모드 변경</string>
- <string name="theme_mode_follow_system">팔로우 시스템</string>
- <string name="theme_mode_light">밝음</string>
- <string name="theme_mode_dark">어두움</string>
+ <string name="change_theme_mode">다크 모드 설정</string>
+ <string name="theme_mode_follow_system">시스템 값 사용</string>
+ <string name="theme_mode_light">라이트 모드</string>
+ <string name="theme_mode_dark">다크 모드</string>
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">검은색 배경 사용</string>
- <string name="use_black_backgrounds_description">어두운 테마를 사용할 때는 검은색 배경을 적용합니다.</string>
+ <string name="use_black_backgrounds">검정 배경</string>
+ <string name="use_black_backgrounds_description">어두운 테마를 사용할 때는 검정 배경을 적용합니다.</string>
+
+ <string name="mute">음소거</string>
+ <string name="unmute">음소거 해제</string>
-</resources>
+ <!-- Licenses screen strings -->
+ <string name="licenses">라이센스</string>
+ <string name="license_fidelityfx_fsr_description">AMD의 고품질 업스케일링</string>
+ </resources>
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 5443cef42..3162a9d41 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">Denne programvaren vil kjøre spill for Nintendo Switch-spillkonsollen. Ingen spilltitler eller nøkler er inkludert.&lt;br /&gt;&lt;br /&gt;Før du begynner, må du finne <![CDATA[<b> prod.keys </b>]]> filen din på enhetslagringen.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Lær mer</a>]]></string>
<string name="emulation_notification_channel_name">Emulering er aktiv</string>
@@ -25,7 +25,6 @@
<string name="back">Tilbake</string>
<string name="add_games">Legg til spill</string>
<string name="add_games_description">Velg din spillmappe</string>
-
<!-- Home strings -->
<string name="home_games">Spill</string>
<string name="home_search">Søk</string>
@@ -37,7 +36,7 @@
<string name="add_games_warning">Hoppe over valg av spillmappe?</string>
<string name="add_games_warning_description">Spill vises ikke i Spill-listen hvis en mappe ikke er valgt.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
- <string name="home_search_games">Søk i spill</string>
+ <string name="home_search_games">Søk i spill|</string>
<string name="games_dir_selected">Spillkatalogen er valgt</string>
<string name="install_prod_keys">Installer prod.keys</string>
<string name="install_prod_keys_description">Nødvendig for å dekryptere spill</string>
@@ -61,6 +60,8 @@
<string name="invalid_keys_file">Ugyldig nøkkelfil valgt</string>
<string name="install_keys_success">Nøkler vellykket installert</string>
<string name="reading_keys_failure">Feil ved lesing av krypteringsnøkler</string>
+ <string name="install_prod_keys_failure_extension_description">Kontroller at nøkkelfilen har filtypen .keys, og prøv igjen.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Kontroller at nøkkelfilen har filtypen .bin, og prøv igjen.</string>
<string name="invalid_keys_error">Ugyldige krypteringsnøkler</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">Den valgte filen er feil eller ødelagt. Vennligst dump nøklene på nytt.</string>
@@ -86,7 +87,17 @@
<string name="save_file_invalid_zip_structure_description">Det første undermappenavnet må være spillets tittel-ID.</string>
<string name="import_saves">Importer</string>
<string name="export_saves">Eksporter</string>
-
+ <string name="install_firmware">Installer fastvare</string>
+ <string name="install_firmware_description">Fastvaren må være i et ZIP-arkiv og er nødvendig for å starte noen spill.</string>
+ <string name="firmware_installing">Installering av fastvare</string>
+ <string name="firmware_installed_success">Fastvaren er vellykket installert</string>
+ <string name="firmware_installed_failure">Installasjon av fastvare mislyktes</string>
+ <string name="share_log">Del feilsøkingslogger</string>
+ <string name="share_log_description">Del yuzus loggfil for å feilsøke problemer</string>
+ <string name="share_log_missing">Ingen loggfil funnet</string>
+ <string name="install_game_content">Installer spillinnhold</string>
+ <string name="install_game_content_description">Installer spilloppdateringer eller DLC</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia er ikke ekte</string>
<string name="copied_to_clipboard">Kopiert til utklippstavlen</string>
@@ -94,6 +105,7 @@
<string name="contributors">Bidragsytere</string>
<string name="contributors_description">Laget med \u2764 fra yuzu-teamet</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Prosjekter som gjør yuzu for Android mulig</string>
<string name="build">Bygg</string>
<string name="support_link">https://discord.gg/u77vRWY</string>
<string name="website_link">https://yuzu-emu.org/</string>
@@ -114,41 +126,43 @@
<string name="are_you_interested">Er du interessert?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Aktiver hastighetsbegrensning</string>
- <string name="frame_limit_enable_description">Når aktivert, begrenses emuleringshastigheten til en angitt prosentandel av normal hastighet.</string>
+ <string name="frame_limit_enable">Begrense hastigheten</string>
+ <string name="frame_limit_enable_description">Begrenser emuleringshastigheten til en spesifisert prosentandel av normal hastighet.</string>
<string name="frame_limit_slider">Hastighetsbegrensning i prosent</string>
- <string name="frame_limit_slider_description">Angir prosentandelen som skal begrense emuleringshastigheten. Med standardverdien 100 % vil emuleringen være begrenset til normal hastighet. Høyere eller lavere verdier vil øke eller redusere hastighetsbegrensningen.</string>
+ <string name="frame_limit_slider_description">Angir prosentandelen som skal begrense emuleringshastigheten. 100 % er normal hastighet. Høyere eller lavere verdier vil øke eller redusere hastighetsgrensen.</string>
<string name="cpu_accuracy">CPU-nøyaktighet</string>
-
<!-- System settings strings -->
<string name="use_docked_mode">Dokket modus</string>
- <string name="use_docked_mode_description">Emulerer i dokket modus, noe som øker oppløsningen på bekostning av ytelsen.</string>
+ <string name="use_docked_mode_description">Øker oppløsningen, men reduserer ytelsen. Håndholdt modus brukes når den er deaktivert, noe som reduserer oppløsningen og øker ytelsen.</string>
<string name="emulated_region">Emulert region</string>
<string name="emulated_language">Emulert språk</string>
<string name="select_rtc_date">Velg RTC-dato</string>
<string name="select_rtc_time">Velg RTC-tid</string>
- <string name="use_custom_rtc">Aktiver egendefinert RTC</string>
- <string name="use_custom_rtc_description">Med denne innstillingen kan du stille inn en egendefinert sanntidsklokke som er atskilt fra gjeldende systemtid.</string>
- <string name="set_custom_rtc">Angi egendefinert RTC</string>
+ <string name="use_custom_rtc">Tilpasset Sannhetstidsklokke</string>
+ <string name="use_custom_rtc_description">Gjør det mulig å stille inn en egendefinert sanntidsklokke separat fra den gjeldende systemtiden.</string>
+ <string name="set_custom_rtc">Angi tilpasset RTC</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Nøyaktighetsnivå</string>
- <string name="renderer_resolution">Oppløsning</string>
+ <string name="renderer_resolution">Oppløsning (håndholdt/dokket)</string>
<string name="renderer_vsync">VSync-modus</string>
<string name="renderer_aspect_ratio">Størrelsesforhold</string>
<string name="renderer_scaling_filter">Filter for vindustilpasning</string>
- <string name="renderer_anti_aliasing">Anti-Aliasing-metode</string>
+ <string name="renderer_anti_aliasing">Anti-aliasing-metode</string>
<string name="renderer_force_max_clock">Tving fram maksimal klokkefrekvens (kun Adreno)</string>
<string name="renderer_force_max_clock_description">Tvinger GPU-en til å kjøre med maksimal klokkefrekvens (termiske begrensninger vil fortsatt gjelde).</string>
<string name="renderer_asynchronous_shaders">Bruk asynkrone shaders</string>
- <string name="renderer_asynchronous_shaders_description">Kompilerer shaders asynkront, noe som reduserer hakkingen, men kan føre til feil.</string>
- <string name="renderer_debug">Aktiver feilsøking av grafikk</string>
- <string name="renderer_debug_description">Når dette er merket av, går grafikk-API-et inn i en langsommere feilsøkingsmodus.</string>
- <string name="use_disk_shader_cache">Bruk disk shader-cache</string>
- <string name="use_disk_shader_cache_description">Reduser hakking ved å lagre og laste inn genererte shaders på disken.</string>
-
- <!-- Audio settings strings -->
+ <string name="renderer_asynchronous_shaders_description">Kompilerer shaders asynkront, noe som reduserer hakking, men kan føre til feil.</string>
+ <string name="renderer_reactive_flushing">Bruk reaktiv spyling</string>
+ <string name="renderer_reactive_flushing_description">Forbedrer gjengivelsesnøyaktigheten i enkelte spill på bekostning av ytelsen.</string>
+ <string name="use_disk_shader_cache">Disk shader-hurtigbuffer</string>
+ <string name="use_disk_shader_cache_description">Reduserer hakking ved å lagre og laste inn genererte shaders lokalt.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Feilsøking av grafikk</string>
+ <string name="renderer_debug_description">Setter grafikk-API-et til en langsom feilsøkingsmodus.</string>
<string name="audio_volume">Volum</string>
<string name="audio_volume_description">Angir volumet på lydutgangen.</string>
@@ -164,14 +178,19 @@
<string name="reset_all_settings_description">Alle avanserte innstillinger tilbakestilles til standardkonfigurasjonen. Dette kan ikke angres.</string>
<string name="settings_reset">Tilbakestilling av innstillinger</string>
<string name="close">Lukk</string>
- <string name="learn_more">Lær Mer</string>
-
+ <string name="learn_more">Lær mer</string>
+ <string name="auto">Auto</string>
+ <string name="submit">Send inn</string>
+ <string name="string_null">Null</string>
+ <string name="string_import">Importer</string>
+ <string name="export">Eksporter</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Velg GPU-driver</string>
<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_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>
@@ -182,10 +201,10 @@
<string name="preferences_graphics">Grafikk</string>
<string name="preferences_audio">Lyd</string>
<string name="preferences_theme">Tema og farge</string>
+ <string name="preferences_debug">Feilsøk</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">ROM-en din er kryptert</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Følg veiledningene for å redumpe dine <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">spillkassetter</a> eller <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">installerte titler</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Vennligst sørg for at <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> filen er installert slik at spillene kan dekrypteres.]]></string>
<string name="loader_error_video_core">Det oppstod en feil ved initialisering av videokjernen</string>
<string name="loader_error_video_core_description">Dette skyldes vanligvis en inkompatibel GPU-driver. Installering av en tilpasset GPU-driver kan løse problemet.</string>
@@ -196,25 +215,25 @@
<string name="emulation_exit">Avslutt emulering</string>
<string name="emulation_done">Ferdig</string>
<string name="emulation_fps_counter">FPS-teller</string>
- <string name="emulation_toggle_controls">Veksle kontroller</string>
- <string name="emulation_rel_stick_center">Relativt senter for stikken</string>
- <string name="emulation_dpad_slide">DPad-skyveplate</string>
- <string name="emulation_haptics">Haptikk</string>
+ <string name="emulation_toggle_controls">Veksle mellom kontrollene</string>
+ <string name="emulation_rel_stick_center">Relativt pinnesenter</string>
+ <string name="emulation_dpad_slide">D-pad-skyving</string>
+ <string name="emulation_haptics">Berøringshaptikk</string>
<string name="emulation_show_overlay">Vis overlegg</string>
- <string name="emulation_toggle_all">Slå av alt</string>
+ <string name="emulation_toggle_all">Veksle mellom alle</string>
<string name="emulation_control_adjust">Juster overlegg</string>
<string name="emulation_control_scale">Skaler</string>
<string name="emulation_control_opacity">Gjennomsiktighet</string>
<string name="emulation_touch_overlay_reset">Tilbakestill overlegg</string>
<string name="emulation_touch_overlay_edit">Rediger overlegg</string>
- <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_pause">Pause emulering</string>
+ <string name="emulation_unpause">Ta emuleringen ut av pause</string>
+ <string name="emulation_input_overlay">Overlay-alternativer</string>
<string name="load_settings">Laster inn innstillinger...</string>
<!-- Software keyboard -->
- <string name="software_keyboard">Programvare Tastatur</string>
+ <string name="software_keyboard">Programvaretastatur</string>
<!-- Errors and warnings -->
<string name="abort_button">Avbryt</string>
@@ -226,7 +245,6 @@
<string name="fatal_error">Fatal Feil</string>
<string name="fatal_error_message">Det oppstod en fatal feil. Sjekk loggen for mer informasjon.\nFortsatt emulering kan føre til krasj og feil.</string>
<string name="performance_warning">Hvis du slår av denne innstillingen, reduseres emuleringsytelsen betydelig! Vi anbefaler at du lar denne innstillingen være aktivert for å få den beste opplevelsen.</string>
-
<!-- Region Names -->
<string name="region_japan">Japan</string>
<string name="region_usa">USA</string>
@@ -236,8 +254,7 @@
<string name="region_korea">Korea</string>
<string name="region_taiwan">Taiwan</string>
- <!-- Language Names -->
-
+ <string name="memory_gigabyte">GB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
<string name="renderer_none">Ingen</string>
@@ -274,12 +291,14 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <string name="screen_layout_auto">Auto</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Standard (16:9)</string>
<string name="ratio_force_four_three">Tving 4:3</string>
<string name="ratio_force_twenty_one_nine">Tving 21:9</string>
<string name="ratio_force_sixteen_ten">Tving 16:10</string>
- <string name="ratio_stretch">Strekk til Vindu</string>
+ <string name="ratio_stretch">Strekk til vindu</string>
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Nøyaktig</string>
@@ -287,9 +306,9 @@
<string name="cpu_accuracy_paranoid">Paranoid (Langsom)</string>
<!-- Gamepad Buttons -->
- <string name="gamepad_d_pad">D-Pad</string>
- <string name="gamepad_left_stick">Venstre Pinne</string>
- <string name="gamepad_right_stick">Høyre Pinne</string>
+ <string name="gamepad_d_pad">D-pad</string>
+ <string name="gamepad_left_stick">Venstre spak</string>
+ <string name="gamepad_right_stick">Høyre spak</string>
<string name="gamepad_home">Hjem</string>
<string name="gamepad_screenshot">Skjermbilde</string>
@@ -298,7 +317,7 @@
<string name="building_shaders">Bygging av shaders</string>
<!-- Theme options -->
- <string name="change_app_theme">Endre appens tema</string>
+ <string name="change_app_theme">Endre app-tema</string>
<string name="theme_default">Standard</string>
<string name="theme_material_you">Material You</string>
@@ -309,7 +328,13 @@
<string name="theme_mode_dark">Mørk</string>
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Bruk svart bakgrunn</string>
+ <string name="use_black_backgrounds">Svart bakgrunn</string>
<string name="use_black_backgrounds_description">Bruk svart bakgrunn når du bruker det mørke temaet.</string>
-</resources>
+ <string name="mute">Lydløs</string>
+ <string name="unmute">Slå på lyden</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Lisenser</string>
+ <string name="license_fidelityfx_fsr_description">Oppskalering av høy kvalitet fra AMD</string>
+ </resources>
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 899e233d0..f4d9920c2 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">To oprogramowanie umożliwia uruchomienie gier z konsoli Nintendo Switch. Nie zawiera gier ani wymaganych kluczy.&lt;br /&gt;&lt;br /&gt;Zanim zaczniesz, wybierz plik kluczy <![CDATA[<b> prod.keys </b>]]> z katalogu w pamięci masowej.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Dowiedz się więcej</a>]]></string>
<string name="emulation_notification_channel_name">Emulacja jest uruchomiona</string>
@@ -25,7 +25,6 @@
<string name="back">Wstecz</string>
<string name="add_games">Dodaj gry</string>
<string name="add_games_description">Wybierz folder zawierający Twoje gry</string>
-
<!-- Home strings -->
<string name="home_games">Gry</string>
<string name="home_search">Szukaj</string>
@@ -61,6 +60,8 @@
<string name="invalid_keys_file">Wybrano niepoprawne klucze</string>
<string name="install_keys_success">Klucze zainstalowane pomyślnie</string>
<string name="reading_keys_failure">Błąd podczas odczytu kluczy</string>
+ <string name="install_prod_keys_failure_extension_description">Upewnij się że twoje klucze mają rozszerzenie .keys i spróbuj ponownie.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Upewnij się że twoje klucze mają rozszerzenie .bin i spróbuj ponownie.</string>
<string name="invalid_keys_error">Niepoprawne klucze</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">Wybrany plik jest niepoprawny lub uszkodzony. Zrzuć ponownie swoje klucze.</string>
@@ -86,7 +87,17 @@
<string name="save_file_invalid_zip_structure_description">Pierwszy podkatalog musi zawierać w nazwie numer ID tytułu gry.</string>
<string name="import_saves">Importuj</string>
<string name="export_saves">Eksportuj</string>
-
+ <string name="install_firmware">Zainstaluj firmware</string>
+ <string name="install_firmware_description">Firmware musi być w postaci archiwum ZIP, niektóre gry wymagają go do uruchomienia/prawidłowego działania</string>
+ <string name="firmware_installing">Instaluję firmware</string>
+ <string name="firmware_installed_success">Zainstalowano pomyślnie</string>
+ <string name="firmware_installed_failure">Błąd podczas instalacji firmware</string>
+ <string name="share_log">Udostępnij logi debugowania</string>
+ <string name="share_log_description">Podziel się logami yuzu, pomoże to twórcom w poprawie działania emulatora</string>
+ <string name="share_log_missing">Nie znaleziono plików logów</string>
+ <string name="install_game_content">Zainstaluj zawartość gry</string>
+ <string name="install_game_content_description">Zainstaluj aktualizację gry lub dodatek DLC</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia isn\'t real</string>
<string name="copied_to_clipboard">Skopiowano do schowka</string>
@@ -94,6 +105,7 @@
<string name="contributors">Współtwórcy</string>
<string name="contributors_description">Stworzone z \u2764 przez zespół yuzu</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Projekty dzięki którym yuzu mógł zostać stworzony</string>
<string name="build">Wersja</string>
<string name="support_link">https://discord.gg/u77vRWY</string>
<string name="website_link">https://yuzu-emu.org/</string>
@@ -114,27 +126,25 @@
<string name="are_you_interested">Jesteś zainteresowany?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Włącz limit szybkości emulacji</string>
+ <string name="frame_limit_enable">Limit szybkość</string>
<string name="frame_limit_enable_description">Włącz, aby ustawić procentowy limit szybkości emulacji</string>
<string name="frame_limit_slider">Procentowy limit szybkości emulacji</string>
<string name="frame_limit_slider_description">Określa limit szybkości emulacji gier. Domyślna wartość 100% oznacza normalną szybkość z jaką działa gra. Wartości niższe lub wyższe zmniejszą lub zwiększą limit szybkości.</string>
<string name="cpu_accuracy">Dokładność procesora CPU</string>
-
<!-- System settings strings -->
<string name="use_docked_mode">Tryb zadokowany</string>
- <string name="use_docked_mode_description">Emulacja w trybie stacji dokującej, zwiększa rozdzielczość kosztem wydajności.</string>
+ <string name="use_docked_mode_description">Zwiększa rozdzielczość kosztem wydajności. Kiedy wyłączone, używany jest tryb Handheld, który obniża rozdzielczość i dzięki temu zwiększa wydajność.</string>
<string name="emulated_region">Region emulacji</string>
<string name="emulated_language">Język emulacji</string>
<string name="select_rtc_date">Ustaw datę RTC</string>
<string name="select_rtc_time">Ustaw czas RTC</string>
- <string name="use_custom_rtc">Włącz niestandardowy zegar RTC</string>
+ <string name="use_custom_rtc">Niestandardowy RTC</string>
<string name="use_custom_rtc_description">Ta opcja pozwala na wybranie własnych ustawień czasu używanych w czasie emulacji, innych niż czas systemu Android.</string>
<string name="set_custom_rtc">Ustaw niestandardowy czas RTC</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">Interfejs graficzny</string>
<string name="renderer_accuracy">Poziom precyzji emulacji</string>
- <string name="renderer_resolution">Rozdzielczość</string>
+ <string name="renderer_resolution">Rozdzielczość (Handheld/Zadokowany)</string>
<string name="renderer_vsync">Synchronizacja pionowa VSync</string>
<string name="renderer_aspect_ratio">Proporcje ekranu</string>
<string name="renderer_scaling_filter">Filtr adaptacji rozdzielczości</string>
@@ -143,12 +153,16 @@
<string name="renderer_force_max_clock_description">Wymusza uruchomienie maksymalnego taktowania układu graficznego (zabezpieczenia termiczne będą dalej aktywne).</string>
<string name="renderer_asynchronous_shaders">Wyłącz synchronizację shaderów</string>
<string name="renderer_asynchronous_shaders_description">Kompiluj oświetlenie bez synchronizacji, poprawi wydajność ale może powodować błędy.</string>
- <string name="renderer_debug">Włącz debugowanie grafiki</string>
- <string name="renderer_debug_description">Kiedy włączone, interfejs graficzny korzysta z wolnego trybu debugowania błędów.</string>
- <string name="use_disk_shader_cache">Użyj pamięci podręcznej shaderów na dysku</string>
+ <string name="renderer_reactive_flushing">Użyj spłukiwania reaktywnego - reactive flushing</string>
+ <string name="renderer_reactive_flushing_description">Poprawia jakość renderowania w kilku grach, kosztem wydajności.</string>
+ <string name="use_disk_shader_cache">Pamięć podręczna shaderów</string>
<string name="use_disk_shader_cache_description">Zmniejsza przycięcia przez przechowywanie gotowych wygenerowanych plików oświetlenia w pamięci urządzenia.</string>
- <!-- Audio settings strings -->
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="renderer_api">Interfejs graficzny</string>
+ <string name="renderer_debug">Debugowanie grafiki</string>
+ <string name="renderer_debug_description">Kiedy włączone, interfejs graficzny korzysta z wolnego trybu debugowania błędów.</string>
<string name="audio_volume">Głośność</string>
<string name="audio_volume_description">Ustala poziom głośności wyjścia dźwięku.</string>
@@ -161,17 +175,21 @@
<string name="reset_setting_confirmation">Przywrócić wartość tego ustawienia do wartości domyślnej?</string>
<string name="reset_to_default">Przywróć ustawienia domyślne</string>
<string name="reset_all_settings">Przywrócić WSZYSTKIE ustawienia?</string>
- <string name="reset_all_settings_description">Wszystkie zaawansowane opcje zostaną przywrócone do wartości domyślnych. Czynności nie będzie można cofnąć.</string>
+ <string name="reset_all_settings_description">Wszystkie zaawansowane opcje zostaną przywrócone do wartości domyślnych. Czynności nie będzie można cofnąć</string>
<string name="settings_reset">Reset ustawień</string>
<string name="close">Zamknij</string>
<string name="learn_more">Dowiedz się więcej</string>
-
+ <string name="auto">Automatyczny</string>
+ <string name="submit">Zatwierdź</string>
+ <string name="string_import">Importuj</string>
+ <string name="export">Eksportuj</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Wybierz sterownik GPU </string>
<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_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>
@@ -182,10 +200,10 @@
<string name="preferences_graphics">Grafika</string>
<string name="preferences_audio">Dźwięk</string>
<string name="preferences_theme">Motyw i kolor</string>
+ <string name="preferences_debug">Debug</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Twój ROM jest zakodowany</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Użyj przewodnika aby wykonać zrzuty <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">kardridży</a> lub <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">zainstalowanych gier</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Upewnij się że plik kluczy <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> jest zainstalowany aby gry mogły zostać odczytane.]]></string>
<string name="loader_error_video_core">Błąd inicjacji podsystemu graficznego</string>
<string name="loader_error_video_core_description">Zazwyczaj spowodowane niekompatybilnym sterownikiem GPU, instalacja niestandardowego sterownika może rozwiązać ten problem.</string>
@@ -198,23 +216,23 @@
<string name="emulation_fps_counter">Licznik FPS</string>
<string name="emulation_toggle_controls">Wybierz przyciski</string>
<string name="emulation_rel_stick_center">Wycentruj gałki</string>
- <string name="emulation_dpad_slide">Ruchomy DPad</string>
+ <string name="emulation_dpad_slide">Ruchomy D-pad</string>
<string name="emulation_haptics">Wibracje haptyczne</string>
<string name="emulation_show_overlay">Pokaż przyciski</string>
- <string name="emulation_toggle_all">Zaznacz wszystkie</string>
+ <string name="emulation_toggle_all">Włącz wszystkie</string>
<string name="emulation_control_adjust">Dostosuj nakładkę</string>
<string name="emulation_control_scale">Skala</string>
<string name="emulation_control_opacity">Przeźroczystość</string>
- <string name="emulation_touch_overlay_reset">Resetuj</string>
+ <string name="emulation_touch_overlay_reset">Resetuj nakładkę</string>
<string name="emulation_touch_overlay_edit">Edytuj nakładkę</string>
<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="load_settings">Wczytywanie ustawień...</string>
+ <string name="load_settings">Wczytuję ustawienia...</string>
<!-- Software keyboard -->
- <string name="software_keyboard">Klawiatura systemowa</string>
+ <string name="software_keyboard">Klawiatura programowa</string>
<!-- Errors and warnings -->
<string name="abort_button">Przerwij</string>
@@ -226,7 +244,6 @@
<string name="fatal_error">Błąd krytyczny</string>
<string name="fatal_error_message">Wystąpił błąd krytyczny. Szczegóły znajdziesz w pliku log.\nKontynuowanie może spowodować błędy lub przerwanie emulacji. </string>
<string name="performance_warning">Wyłączenie tej opcji znacząco ograniczy wydajność! Dla najlepszego doświadczenia, zaleca się zostawienie tej opcji włączonej.</string>
-
<!-- Region Names -->
<string name="region_japan">Japonia</string>
<string name="region_usa">USA</string>
@@ -236,8 +253,7 @@
<string name="region_korea">Korea</string>
<string name="region_taiwan">Tajwan</string>
- <!-- Language Names -->
-
+ <string name="memory_gigabyte">GB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
<string name="renderer_none">Żadny</string>
@@ -274,12 +290,14 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <string name="screen_layout_auto">Automatyczny</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Domyślne (16:9)</string>
<string name="ratio_force_four_three">Wymuś 4:3</string>
<string name="ratio_force_twenty_one_nine">Wymuś 21:9</string>
<string name="ratio_force_sixteen_ten">Wymuś 16:10</string>
- <string name="ratio_stretch">Rozciągnij do Okna</string>
+ <string name="ratio_stretch">Rozciągnij do okna</string>
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Dokładny</string>
@@ -287,7 +305,7 @@
<string name="cpu_accuracy_paranoid">Paranoid (Wolny)</string>
<!-- Gamepad Buttons -->
- <string name="gamepad_d_pad">D-Pad</string>
+ <string name="gamepad_d_pad">D-pad</string>
<string name="gamepad_left_stick">Lewa gałka</string>
<string name="gamepad_right_stick">Prawa gałka</string>
<string name="gamepad_home">Home</string>
@@ -298,18 +316,21 @@
<string name="building_shaders">Budowanie shaderów</string>
<!-- Theme options -->
- <string name="change_app_theme">Zmień motyw aplikacji</string>
+ <string name="change_app_theme">Ustaw motyw aplikacji</string>
<string name="theme_default">Domyślny</string>
<string name="theme_material_you">Material You</string>
<!-- Theme Modes -->
- <string name="change_theme_mode">Zmiana trybu motywu</string>
+ <string name="change_theme_mode">Zmień tryb motywu</string>
<string name="theme_mode_follow_system">Podążaj za systemowym</string>
<string name="theme_mode_light">Jasny</string>
<string name="theme_mode_dark">Ciemny</string>
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Używaj czarnego tła</string>
+ <string name="use_black_backgrounds">Czarne tła</string>
<string name="use_black_backgrounds_description">Kiedy używany ciemny motyw, tła zostają zastąpione czernią.</string>
-</resources>
+ <!-- Licenses screen strings -->
+ <string name="licenses">Licencje</string>
+ <string name="license_fidelityfx_fsr_description">Rozciąganie wysokiej jakości od AMD</string>
+ </resources>
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 caa095364..8888fc750 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
@@ -1,30 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
- <string name="app_disclaimer">Este software corre jogos para a consola Nintendo Switch. Não estão incluídas nem jogos ou chaves. &lt;br /&gt;&lt;br /&gt;Antes de começares, por favor localiza o ficheiro <![CDATA[1 prod.keys 1]]> no armazenamento do teu dispositivo.&lt;br /&gt;&lt;br /&gt;<![CDATA[2Learn more2]]></string>
+ <string name="app_disclaimer">Este software executa jogos do console Nintendo Switch. Não estão inclusos nem jogos ou chaves. &lt;br /&gt;&lt;br /&gt;Antes de começar, por favor localize o arquivo <![CDATA[1 prod.keys 1]]> no armazenamento de seu dispositivo.&lt;br /&gt;&lt;br /&gt;<![CDATA[2Saiba mais2]]></string>
<string name="emulation_notification_channel_name">Emulação está Ativa</string>
- <string name="emulation_notification_channel_description">Mostra uma notificação permanente enquanto a emulação está a correr.</string>
+ <string name="emulation_notification_channel_description">Mostra uma notificação permanente enquanto a emulação estiver em andamento.</string>
<string name="emulation_notification_running">Yuzu está em execução </string>
<string name="notice_notification_channel_name">Notificações e erros</string>
- <string name="notice_notification_channel_description">Mostra notificações quendo algo corre mal.</string>
- <string name="notification_permission_not_granted">Permissões de notificação não permitidas </string>
+ <string name="notice_notification_channel_description">Mostra notificações quando algo dá errado.</string>
+ <string name="notification_permission_not_granted">Acesso às notificações não concedido!</string>
<!-- Setup strings -->
- <string name="welcome">Bemvindo! </string>
- <string name="welcome_description">Aprende como configurar &lt;b>yuzu&lt;/b> e arranca a emulação.</string>
- <string name="get_started">Começa</string>
- <string name="keys">Chaves</string>
- <string name="keys_description">Seleciona o teu ficheiro &lt;b>prod.keys&lt;/b> com o botão abaixo.</string>
- <string name="select_keys">Seleciona as Chaves</string>
+ <string name="welcome">Bem-vindo! </string>
+ <string name="welcome_description">Aprenda como configurar o &lt;b>yuzu&lt;/b> e mergulhe na emulação.</string>
+ <string name="get_started">Primeiros passos</string>
+ <string name="keys">Keys</string>
+ <string name="keys_description">Selecione seu arquivo &lt;b>prod.keys&lt;/b> com o botão abaixo.</string>
+ <string name="select_keys">Selecione as Keys</string>
<string name="games">Jogos</string>
- <string name="games_description">Seleciona a tua pasta &lt;b>Games&lt;/b> com o botão abaixo.</string>
+ <string name="games_description">Seleciona sua pasta &lt;b>Jogos&lt;/b> com o botão abaixo.</string>
<string name="done">Feito</string>
- <string name="done_description">Tudo pronto.\nDisfruta dos teus jogos!</string>
+ <string name="done_description">Tudo pronto.\nAproveite seus jogos!</string>
<string name="text_continue">Continuar</string>
<string name="next">Próximo</string>
<string name="back">Voltar</string>
- <string name="add_games">Adiciona Jogos</string>
- <string name="add_games_description">Seleciona a tua pasta de Jogos</string>
+ <string name="add_games">Adicionar Jogos</string>
+ <string name="add_games_description">Selecione sua pasta de Jogos</string>
+ <string name="step_complete">Completo!</string>
<!-- Home strings -->
<string name="home_games">Jogos</string>
@@ -37,7 +38,8 @@
<string name="add_games_warning">Ignorar a seleção da pasta de jogos?</string>
<string name="add_games_warning_description">Os jogos não serão exibidos na lista de jogos se uma pasta não estiver selecionada.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
- <string name="home_search_games">Procurar Jogos</string>
+ <string name="home_search_games">Procurar jogos</string>
+ <string name="search_settings">Procurar nas definições</string>
<string name="games_dir_selected">Pasta de Jogos selecionada</string>
<string name="install_prod_keys">Instala prod.keys</string>
<string name="install_prod_keys_description">Necessário para desencriptar jogos comerciais</string>
@@ -61,15 +63,18 @@
<string name="invalid_keys_file">Ficheiro de chaves inválido</string>
<string name="install_keys_success">Chaves instaladas com sucesso</string>
<string name="reading_keys_failure">Erro ao ler chaves de encriptação</string>
+ <string name="install_prod_keys_failure_extension_description">Verifique se seu arquivo keys possui a extensão .keys e tente novamente.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Verifique se seu arquivo keys possui a extensão .bin e tente novamente.</string>
<string name="invalid_keys_error">Chaves de encriptação inválidas</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">O ficheiro selecionado está corrompido. Por favor recarrega as tuas chaves.</string>
<string name="install_gpu_driver">Instala driver para GPU</string>
<string name="install_gpu_driver_description">Instala drivers alternativos para desempenho ou precisão potencialmente melhores</string>
<string name="advanced_settings">Definições avançadas</string>
+ <string name="advanced_settings_game">Definições avançadas: %1$s</string>
<string name="settings_description">Configura definições do emulador</string>
- <string name="search_recently_played">Jogos recentes</string>
- <string name="search_recently_added">Adicionados recentemente</string>
+ <string name="search_recently_played">Jogado recentemente</string>
+ <string name="search_recently_added">Adicionado recentemente</string>
<string name="search_retail">Jogos comerciais</string>
<string name="search_homebrew">Homebrew</string>
<string name="open_user_folder">Abre a pasta Yuzu</string>
@@ -86,6 +91,33 @@
<string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
<string name="import_saves">Importar</string>
<string name="export_saves">Exportar</string>
+ <string name="install_firmware">Instalar firmware</string>
+ <string name="install_firmware_description">O firmware deve estar em um arquivo ZIP e é necessário para iniciar alguns jogos.</string>
+ <string name="firmware_installing">Instalando firmware</string>
+ <string name="firmware_installed_success">Firmware instalado com sucesso.</string>
+ <string name="firmware_installed_failure">Falha na instalação do firmware</string>
+ <string name="firmware_installed_failure_description">Cofirma que os ficheiros firmware nca estão no root do finheiro zip e tenta de novo.</string>
+ <string name="share_log">Compartilhe registros de debug.</string>
+ <string name="share_log_description">Compartilhe o arquivo de registro do yuzu para obter ajuda com problemas</string>
+ <string name="share_log_missing">Arquivo de registro não encontrado</string>
+ <string name="install_game_content">Instalar conteúdo de jogos</string>
+ <string name="install_game_content_description">Instalar atualizações de jogos ou DLC</string>
+ <string name="installing_game_content">A instalar conteúdo...</string>
+ <string name="install_game_content_failure">Erro ao instalar ficheiro(s) para NAND</string>
+ <string name="install_game_content_failure_description">Por favor confitma que o conteúdo(s) é válido e que as prod.keys estão instaladas.</string>
+ <string name="install_game_content_failure_base">A instalação de jogos base não é permitida para evitar possíveis conflitos.</string>
+ <string name="install_game_content_failure_file_extension">Sò conteúdos NSP e XCI são suportados. Por favor verifica que o conteúdo(s) do jogo são válidos.</string>
+ <string name="install_game_content_failed_count">%1$d erro(s) de instalação</string>
+ <string name="install_game_content_success">Conteúdo(s) de jogo instalados com sucesso</string>
+ <string name="install_game_content_success_install">%1$d instalado com sucesso</string>
+ <string name="install_game_content_success_overwrite">%1$d substituída com êxito</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">Drivers personalizados não suportados</string>
+ <string name="custom_driver_not_supported_description">Carrea«gamento de drivers personalizados não é suportado pr este dispositivo. \nCheck verifica esta opção de futuro para confirmar se o suporte foi adicionado!</string>
+ <string name="manage_yuzu_data">Administrar dados yuzu</string>
+ <string name="manage_yuzu_data_description">Importa/exporta firmware, chaves, dados do usuário e mais!</string>
+ <string name="share_save_file">Partilha ficheiro duardado</string>
+ <string name="export_save_failed">Erro ao exportar dados guardados</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia não é real</string>
@@ -94,7 +126,18 @@
<string name="contributors">Contribuidores</string>
<string name="contributors_description">Feito com \u2764 da equipa do Yuzu</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Projetos que tornam o yuzu para Android possível</string>
<string name="build">Versão</string>
+ <string name="user_data">Dado de utilizados</string>
+ <string name="user_data_description">Importar/exportar todos dados da aplicação data.\n\n Ao importar dados do utilizados, todos os dados existentes do utilizados serão excluídos!</string>
+ <string name="exporting_user_data">A exportar dados de utilizados...</string>
+ <string name="importing_user_data">A importar dados de utilizador...</string>
+ <string name="import_user_data">Importar dados de utilizados...</string>
+ <string name="invalid_yuzu_backup">Backup yuzu inválido</string>
+ <string name="user_data_export_success">Dados de utilizados exportados com sucesso</string>
+ <string name="user_data_import_success">Dados de utilizador importado com sucesso</string>
+ <string name="user_data_export_cancelled">Exportação cancelada</string>
+ <string name="user_data_import_failed_description">Verifiqua se as pastas de dados do utilizados estão na raiz da pasta zip e contêm um arquivo de configuração em config/config.ini e tenta novamente.</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>
@@ -114,41 +157,53 @@
<string name="are_you_interested">Estás interessado?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Ativar limite de velocidade</string>
- <string name="frame_limit_enable_description">Quando ativada, a velocidade da emulação será limitada à percentagem definida da velocidade normal.</string>
+ <string name="frame_limit_enable">Limite de velocidade</string>
+ <string name="frame_limit_enable_description">Limita a velocidade da emulação a uma porcentagem específica da velocidade normal.</string>
<string name="frame_limit_slider">Percentagem do limite de velocidade</string>
- <string name="frame_limit_slider_description">Especifica o limite da percentagem da velocidade da emulação. Com a velocidade por defeito a 100% a emulação será limitada à velocidade normal. Valores maiores ou menores aumentarão ou diminuirão o limite de velocidade.</string>
+ <string name="frame_limit_slider_description">Especifica a porcentagem para limitar a velocidade de emulação. 100% é o normal. Valores mais altos ou mais baixos irão aumentar ou diminuir o limite de velocidade.</string>
<string name="cpu_accuracy">Precisão do CPU</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
- <string name="use_docked_mode">Modo ancorado</string>
- <string name="use_docked_mode_description">Emula em modo ancorado, que aumenta a resolução ás custas da performance.</string>
+ <string name="use_docked_mode">Modo Ancorado</string>
+ <string name="use_docked_mode_description">Aumenta a resolução, diminuindo o desempenho. O Modo Portátil é utilizado quando estiver desabilitado, diminuindo a resolução e melhorando o desempenho.</string>
<string name="emulated_region">Região da emulação</string>
<string name="emulated_language">Idioma da emulação</string>
- <string name="select_rtc_date">Seleciona a data RTC</string>
- <string name="select_rtc_time">Seleciona a hora RTC</string>
- <string name="use_custom_rtc">Ativa RTC personalizado</string>
- <string name="use_custom_rtc_description">Esta configuração permite definir um RTC personalizado diferente da hora atual do sistema</string>
- <string name="set_custom_rtc">Define RTC personalizado</string>
+ <string name="select_rtc_date">Selecione a data do sistema</string>
+ <string name="select_rtc_time">Selecione a hora do sistema</string>
+ <string name="use_custom_rtc">Data e hora personalizada</string>
+ <string name="use_custom_rtc_description">Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo.</string>
+ <string name="set_custom_rtc">Defina um relógio em tempo real personalizado</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Nível de precisão</string>
- <string name="renderer_resolution">Resolução</string>
+ <string name="renderer_resolution">Resolução (Portátil/Ancorado)</string>
<string name="renderer_vsync">Modo VSync</string>
- <string name="renderer_aspect_ratio">Proporção do ecrã</string>
+ <string name="renderer_screen_layout">Oriantação</string>
+ <string name="renderer_aspect_ratio">Proporção da tela</string>
<string name="renderer_scaling_filter">Filtro de Adaptação da Janela</string>
- <string name="renderer_anti_aliasing">Método de Anti-Aliasing </string>
+ <string name="renderer_anti_aliasing">Método de Anti-Serrilhado</string>
<string name="renderer_force_max_clock">Força velocidade máxima (Adreno only)</string>
<string name="renderer_force_max_clock_description">Força o GPU a correr à velocidade máxima (restrições térmicas serão aplicadas)</string>
<string name="renderer_asynchronous_shaders">Usa shaders assíncronos </string>
- <string name="renderer_asynchronous_shaders_description">Compila shaders assincronamente, que aumentará a fluidez, mas poderá causar falhas.</string>
+ <string name="renderer_asynchronous_shaders_description">Compila os shaders de forma assíncrona, reduzindo travamentos, mas pode apresentar problemas.</string>
+ <string name="renderer_reactive_flushing">Usar flushing reativo</string>
+ <string name="renderer_reactive_flushing_description">Melhora a precisão da renderização em alguns jogos ao custo de desempenho.</string>
+ <string name="use_disk_shader_cache">Cache de shaders em disco</string>
+ <string name="use_disk_shader_cache_description">Reduz travamentos ao armazenar e carregar localmente os shaders.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">Depuração da CPU</string>
+ <string name="cpu_debug_mode_description">Coloca a CPU em um modo de depuração lento.</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
<string name="renderer_debug">Ativar depuração de gráficos</string>
<string name="renderer_debug_description">Quando selecionado, a API gráfica entra num modo de depuração mais lento.</string>
- <string name="use_disk_shader_cache">Usar cache de shaders em disco</string>
- <string name="use_disk_shader_cache_description">Aumenta a fluidez ao guardar e carregar shaders gerados para o armazenamento.</string>
+ <string name="fastmem">Fastmem</string>
<!-- Audio settings strings -->
+ <string name="audio_output_engine">Motor de saída</string>
<string name="audio_volume">Volume</string>
<string name="audio_volume_description">Especifica o volume de saída.</string>
@@ -157,14 +212,24 @@
<string name="ini_saved">Definições guardadas</string>
<string name="gameid_saved">Definições guardadas para %1$s</string>
<string name="error_saving">Erro ao guardar %1$s.ini: %2$s</string>
+ <string name="unimplemented_menu">Menu não implementado</string>
<string name="loading">A carregar...</string>
+ <string name="shutting_down">A desligar...</string>
<string name="reset_setting_confirmation">Queres reverter esta definição para os valores padrão?</string>
<string name="reset_to_default">Reverter para padrão</string>
<string name="reset_all_settings">Redefinir todas as definições?</string>
- <string name="reset_all_settings_description">Todas as definições avançadas serão redefinidas para as definições padrão. Isto não pode ser revertido.</string>
+ <string name="reset_all_settings_description">Todas as configurações avançadas retornarão ao padrão. Isto não pode ser desfeito.</string>
<string name="settings_reset">Redefinir definições</string>
<string name="close">Fechar</string>
<string name="learn_more">Saiba mais</string>
+ <string name="auto">Automático</string>
+ <string name="submit">Enviar</string>
+ <string name="string_null">Nenhum (desativado)</string>
+ <string name="string_import">Importar</string>
+ <string name="export">Exportar</string>
+ <string name="export_failed">Exportação falhada</string>
+ <string name="import_failed">IMportação falhada</string>
+ <string name="cancelling">A cancelar</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Seleciona a driver para o GPU</string>
@@ -172,6 +237,7 @@
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Padrão</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>
@@ -182,10 +248,11 @@
<string name="preferences_graphics">Gráficos</string>
<string name="preferences_audio">Áudio</string>
<string name="preferences_theme">Cor e tema.</string>
+ <string name="preferences_debug">Depuração</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">A tua ROM está encriptada</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor segue os guias para fazer redump das tuas<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">Cartidges de Jogo</a> or <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">Jogos Instalados</a>.]]></string>
+ <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor, siga os guias para despejar novamente o seu <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards\">cartucho de jogo</a> or <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop\">títulos instalados</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Por favor confirma que o teu ficheiro <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> está instalado para que os jogos possam ser desencriptados.]]></string>
<string name="loader_error_video_core">Ocorreu um erro ao iniciar o núcleo de vídeo.</string>
<string name="loader_error_video_core_description">Isto é normalmente causado por um driver de GPU incompatível. Instalar um driver GPU pode resolver este problema.</string>
@@ -193,25 +260,25 @@
<string name="loader_error_file_not_found">O ficheiro da ROM não existe</string>
<!-- Emulation Menu -->
- <string name="emulation_exit">Sair da emulação</string>
+ <string name="emulation_exit">Parar emulação</string>
<string name="emulation_done">Feito</string>
<string name="emulation_fps_counter">Contador de FPS</string>
- <string name="emulation_toggle_controls">Alterar Controlos</string>
- <string name="emulation_rel_stick_center">Centro do Analógico Relativo</string>
- <string name="emulation_dpad_slide">Deslizar do DPad</string>
- <string name="emulation_haptics">Hápticos </string>
- <string name="emulation_show_overlay">Mostrar sobreposição </string>
- <string name="emulation_toggle_all">Alterar todos</string>
- <string name="emulation_control_adjust">Ajustar a sobreposição </string>
+ <string name="emulation_toggle_controls">Alterar controles</string>
+ <string name="emulation_rel_stick_center">Centro Relativo de Analógico</string>
+ <string name="emulation_dpad_slide">Deslizamento dos Botões Direcionais</string>
+ <string name="emulation_haptics">Vibração ao tocar</string>
+ <string name="emulation_show_overlay">Mostrar overlay</string>
+ <string name="emulation_toggle_all">Marcar/Desmarcar tudo</string>
+ <string name="emulation_control_adjust">Ajustar overlay</string>
<string name="emulation_control_scale">Escala</string>
<string name="emulation_control_opacity">Opacidade</string>
- <string name="emulation_touch_overlay_reset">Redefinir Sobreposição </string>
- <string name="emulation_touch_overlay_edit">Editar sobreposição </string>
- <string name="emulation_pause">Pausa emulação</string>
+ <string name="emulation_touch_overlay_reset">Restaurar overlay padrão</string>
+ <string name="emulation_touch_overlay_edit">Editar overlay</string>
+ <string name="emulation_pause">Pausar 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_input_overlay">Opções de overlay</string>
- <string name="load_settings">Configurações a carregar...</string>
+ <string name="load_settings">Carregando configurações...</string>
<!-- Software keyboard -->
<string name="software_keyboard">Teclado de software</string>
@@ -226,6 +293,9 @@
<string name="fatal_error">Erro fatal</string>
<string name="fatal_error_message">Ocorreu um erro fatal. Verifica o teu registro para detalhes. \nContinuar a emulação pode causar erros.</string>
<string name="performance_warning">Desligar esta configuração irá reduzir a performance da emulação significantemente! Para a melhor experiência é recomendado que deixes esta configuração ativada.</string>
+ <string name="device_memory_inadequate">RAM do dispositivo: %1$s\nRecommended: %2$s</string>
+ <string name="memory_formatted">%1$s %2$s</string>
+ <string name="no_game_present">Nenhum jogo inicializável presente!</string>
<!-- Region Names -->
<string name="region_japan">Japão</string>
@@ -236,7 +306,14 @@
<string name="region_korea">Coréia</string>
<string name="region_taiwan">Taiwan</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulcano</string>
@@ -274,12 +351,17 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">Landscape</string>
+ <string name="screen_layout_portrait">Portrait</string>
+ <string name="screen_layout_auto">Automático</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Padrão (16:9)</string>
<string name="ratio_force_four_three">Forçar 4:3</string>
<string name="ratio_force_twenty_one_nine">Forçar 21:9</string>
<string name="ratio_force_sixteen_ten">Forçar 16:10</string>
- <string name="ratio_stretch">Esticar para a janela</string>
+ <string name="ratio_stretch">Esticar à janela</string>
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Preciso</string>
@@ -287,7 +369,7 @@
<string name="cpu_accuracy_paranoid">Paranoid (Lento)</string>
<!-- Gamepad Buttons -->
- <string name="gamepad_d_pad">D-pad</string>
+ <string name="gamepad_d_pad">Botões Direcionais</string>
<string name="gamepad_left_stick">Analógico esquerdo</string>
<string name="gamepad_right_stick">Analógico direito</string>
<string name="gamepad_home">Botão Home</string>
@@ -298,18 +380,32 @@
<string name="building_shaders">A criar shaders</string>
<!-- Theme options -->
- <string name="change_app_theme">Muda o Tema da App</string>
+ <string name="change_app_theme">Mudar o tema do aplicativo</string>
<string name="theme_default">Padrão</string>
<string name="theme_material_you">Material You</string>
<!-- Theme Modes -->
- <string name="change_theme_mode">Altera o Modo do Tema</string>
+ <string name="change_theme_mode">Alterar o tema</string>
<string name="theme_mode_follow_system">Igual ao Sistema</string>
<string name="theme_mode_light">Claro</string>
<string name="theme_mode_dark">Escuro</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Usa Fundos Negros</string>
+ <string name="use_black_backgrounds">Plano de fundo preto</string>
<string name="use_black_backgrounds_description">Quando usar tema escuro, aplicar fundos escuros</string>
-</resources>
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">Picture in Picture</string>
+ <string name="picture_in_picture_description">Minimizar a janela quando colocada em segundo plano</string>
+ <string name="pause">Pausa</string>
+ <string name="play">Correr</string>
+ <string name="mute">Mudo</string>
+ <string name="unmute">Unmute</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Licenças</string>
+ <string name="license_fidelityfx_fsr_description">Upscaling de alta qualidade da AMD</string>
+ </resources>
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 0a1a47fbb..6afea9b03 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
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">Este software corre jogos para a consola Nintendo Switch. Não estão incluídas nem jogos ou chaves. &lt;br /&gt;&lt;br /&gt;Antes de começares, por favor localiza o ficheiro <![CDATA[1 prod.keys 1]]> no armazenamento do teu dispositivo.&lt;br /&gt;&lt;br /&gt;<![CDATA[2Learn more2]]></string>
<string name="emulation_notification_channel_name">Emulação está Ativa</string>
@@ -25,6 +25,7 @@
<string name="back">Voltar</string>
<string name="add_games">Adiciona Jogos</string>
<string name="add_games_description">Seleciona a tua pasta de Jogos</string>
+ <string name="step_complete">Completo!</string>
<!-- Home strings -->
<string name="home_games">Jogos</string>
@@ -37,7 +38,8 @@
<string name="add_games_warning">Ignorar a seleção da pasta de jogos?</string>
<string name="add_games_warning_description">Os jogos não serão exibidos na lista de jogos se uma pasta não estiver selecionada.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
- <string name="home_search_games">Procurar Jogos</string>
+ <string name="home_search_games">Procurar jogos</string>
+ <string name="search_settings">Procurar nas definições</string>
<string name="games_dir_selected">Pasta de Jogos selecionada</string>
<string name="install_prod_keys">Instala prod.keys</string>
<string name="install_prod_keys_description">Necessário para desencriptar jogos comerciais</string>
@@ -61,15 +63,18 @@
<string name="invalid_keys_file">Ficheiro de chaves inválido</string>
<string name="install_keys_success">Chaves instaladas com sucesso</string>
<string name="reading_keys_failure">Erro ao ler chaves de encriptação</string>
+ <string name="install_prod_keys_failure_extension_description">Verifique se seu arquivo keys possui a extensão .keys e tente novamente.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Verifique se seu arquivo keys possui a extensão .bin e tente novamente.</string>
<string name="invalid_keys_error">Chaves de encriptação inválidas</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">O ficheiro selecionado está corrompido. Por favor recarrega as tuas chaves.</string>
<string name="install_gpu_driver">Instala driver para GPU</string>
<string name="install_gpu_driver_description">Instala drivers alternativos para desempenho ou precisão potencialmente melhores</string>
<string name="advanced_settings">Configurações avançadas</string>
+ <string name="advanced_settings_game">Definições avançadas: %1$s</string>
<string name="settings_description">Configura configurações do emulador</string>
- <string name="search_recently_played">Jogos recentes</string>
- <string name="search_recently_added">Adicionados recentemente</string>
+ <string name="search_recently_played">Jogado recentemente</string>
+ <string name="search_recently_added">Adicionado recentemente</string>
<string name="search_retail">Jogos comerciais</string>
<string name="search_homebrew">Homebrew</string>
<string name="open_user_folder">Abre a pasta Yuzu</string>
@@ -86,6 +91,33 @@
<string name="save_file_invalid_zip_structure_description">O nome da primeira sub pasta tem de ser a ID do jogo.</string>
<string name="import_saves">Importar</string>
<string name="export_saves">Exportar</string>
+ <string name="install_firmware">Instalar firmware</string>
+ <string name="install_firmware_description">O firmware deve estar em um arquivo ZIP e é necessário para iniciar alguns jogos.</string>
+ <string name="firmware_installing">Instalando firmware</string>
+ <string name="firmware_installed_success">Firmware instalado com sucesso.</string>
+ <string name="firmware_installed_failure">Falha na instalação do firmware</string>
+ <string name="firmware_installed_failure_description">Cofirma que os ficheiros firmware nca estão no root do finheiro zip e tenta de novo.</string>
+ <string name="share_log">Compartilhe registros de debug.</string>
+ <string name="share_log_description">Compartilhe o arquivo de registro do yuzu para obter ajuda com problemas</string>
+ <string name="share_log_missing">Arquivo de registro não encontrado</string>
+ <string name="install_game_content">Instalar conteúdo adicional</string>
+ <string name="install_game_content_description">Instale atualizações de jogos ou DLC</string>
+ <string name="installing_game_content">A instalar conteúdo...</string>
+ <string name="install_game_content_failure">Erro ao instalar ficheiro(s) para NAND</string>
+ <string name="install_game_content_failure_description">Por favor confitma que o conteúdo(s) é válido e que as prod.keys estão instaladas.</string>
+ <string name="install_game_content_failure_base">A instalação de jogos base não é permitida para evitar possíveis conflitos.</string>
+ <string name="install_game_content_failure_file_extension">Sò conteúdos NSP e XCI são suportados. Por favor verifica que o conteúdo(s) do jogo são válidos.</string>
+ <string name="install_game_content_failed_count">%1$d erro(s) de instalação</string>
+ <string name="install_game_content_success">Conteúdo(s) de jogo instalados com sucesso</string>
+ <string name="install_game_content_success_install">%1$d instalado com sucesso</string>
+ <string name="install_game_content_success_overwrite">%1$d substituída com êxito</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">Drivers personalizados não suportados</string>
+ <string name="custom_driver_not_supported_description">Carrea«gamento de drivers personalizados não é suportado pr este dispositivo. \nCheck verifica esta opção de futuro para confirmar se o suporte foi adicionado!</string>
+ <string name="manage_yuzu_data">Administrar dados yuzu</string>
+ <string name="manage_yuzu_data_description">Importa/exporta firmware, chaves, dados do usuário e mais!</string>
+ <string name="share_save_file">Partilha ficheiro duardado</string>
+ <string name="export_save_failed">Erro ao exportar dados guardados</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia não é real</string>
@@ -94,7 +126,18 @@
<string name="contributors">Contribuidores</string>
<string name="contributors_description">Feito com \u2764 da equipa do Yuzu</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Projetos que tornam o yuzu para Android possível</string>
<string name="build">Versão</string>
+ <string name="user_data">Dado de utilizados</string>
+ <string name="user_data_description">Importar/exportar todos dados da aplicação data.\n\n Ao importar dados do utilizados, todos os dados existentes do utilizados serão excluídos!</string>
+ <string name="exporting_user_data">A exportar dados de utilizados...</string>
+ <string name="importing_user_data">A importar dados de utilizador...</string>
+ <string name="import_user_data">Importar dados de utilizados...</string>
+ <string name="invalid_yuzu_backup">Backup yuzu inválido</string>
+ <string name="user_data_export_success">Dados de utilizados exportados com sucesso</string>
+ <string name="user_data_import_success">Dados de utilizador importado com sucesso</string>
+ <string name="user_data_export_cancelled">Exportação cancelada</string>
+ <string name="user_data_import_failed_description">Verifiqua se as pastas de dados do utilizados estão na raiz da pasta zip e contêm um arquivo de configuração em config/config.ini e tenta novamente.</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>
@@ -114,41 +157,53 @@
<string name="are_you_interested">Estás interessado?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Ativar limite de velocidade</string>
- <string name="frame_limit_enable_description">Quando ativada, a velocidade da emulação será limitada à percentagem definida da velocidade normal.</string>
+ <string name="frame_limit_enable">Limite de velocidade</string>
+ <string name="frame_limit_enable_description">Limita a velocidade da emulação a uma porcentagem específica da velocidade normal.</string>
<string name="frame_limit_slider">Percentagem do limite de velocidade</string>
- <string name="frame_limit_slider_description">Especifica o limite da percentagem da velocidade da emulação. Com a velocidade por defeito a 100% a emulação será limitada à velocidade normal. Valores maiores ou menores aumentarão ou diminuirão o limite de velocidade.</string>
+ <string name="frame_limit_slider_description">Especifica a porcentagem para limitar a velocidade de emulação. 100% é o normal. Valores mais altos ou mais baixos irão aumentar ou diminuir o limite de velocidade.</string>
<string name="cpu_accuracy">Precisão do CPU</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
- <string name="use_docked_mode">Modo ancorado</string>
- <string name="use_docked_mode_description">Emula em modo ancorado, que aumenta a resolução ás custas da performance.</string>
+ <string name="use_docked_mode">Modo Ancorado</string>
+ <string name="use_docked_mode_description">Aumenta a resolução, diminuindo o desempenho. O Modo Portátil é utilizado quando estiver desabilitado, diminuindo a resolução e melhorando o desempenho.</string>
<string name="emulated_region">Região da emulação</string>
<string name="emulated_language">Idioma da emulação</string>
- <string name="select_rtc_date">Seleciona a data RTC</string>
- <string name="select_rtc_time">Seleciona a hora RTC</string>
- <string name="use_custom_rtc">Ativa RTC personalizado</string>
- <string name="use_custom_rtc_description">Esta configuração permite definir um RTC personalizado diferente da hora atual do sistema</string>
- <string name="set_custom_rtc">Define RTC personalizado</string>
+ <string name="select_rtc_date">Selecione a data do sistema</string>
+ <string name="select_rtc_time">Selecione a hora do sistema</string>
+ <string name="use_custom_rtc">RTC personalizado</string>
+ <string name="use_custom_rtc_description">Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo.</string>
+ <string name="set_custom_rtc">Defina um relógio em tempo real personalizado</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Nível de precisão</string>
- <string name="renderer_resolution">Resolução</string>
+ <string name="renderer_resolution">Resolução (Portátil/Ancorado)</string>
<string name="renderer_vsync">Modo VSync</string>
- <string name="renderer_aspect_ratio">Proporção do ecrã</string>
+ <string name="renderer_screen_layout">Oriantação</string>
+ <string name="renderer_aspect_ratio">Proporção da tela</string>
<string name="renderer_scaling_filter">Filtro de Adaptação da Janela</string>
- <string name="renderer_anti_aliasing">Método de Anti-Aliasing </string>
+ <string name="renderer_anti_aliasing">Método de Anti-Serrilhado</string>
<string name="renderer_force_max_clock">Força velocidade máxima (Adreno only)</string>
<string name="renderer_force_max_clock_description">Força o GPU a correr à velocidade máxima (restrições térmicas serão aplicadas)</string>
<string name="renderer_asynchronous_shaders">Usa shaders assíncronos </string>
- <string name="renderer_asynchronous_shaders_description">Compila shaders assincronamente, que aumentará a fluidez, mas poderá causar falhas.</string>
+ <string name="renderer_asynchronous_shaders_description">Compila os shaders de forma assíncrona, reduzindo travamentos, mas pode apresentar problemas.</string>
+ <string name="renderer_reactive_flushing">Usar flushing reativo</string>
+ <string name="renderer_reactive_flushing_description">Melhora a precisão da renderização em alguns jogos ao custo de desempenho.</string>
+ <string name="use_disk_shader_cache">Cache de shaders em disco</string>
+ <string name="use_disk_shader_cache_description">Reduz travamentos ao armazenar e carregar localmente os shaders.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">Depuração da CPU</string>
+ <string name="cpu_debug_mode_description">Coloca a CPU em um modo de depuração lento.</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
<string name="renderer_debug">Ativar depuração de gráficos</string>
<string name="renderer_debug_description">Quando selecionado, a API gráfica entra num modo de depuração mais lento.</string>
- <string name="use_disk_shader_cache">Usar cache do disk shader</string>
- <string name="use_disk_shader_cache_description">Aumenta a fluidez ao guardar e carregar shaders gerados para o armazenamento.</string>
+ <string name="fastmem">Fastmem</string>
<!-- Audio settings strings -->
+ <string name="audio_output_engine">Motor de saída</string>
<string name="audio_volume">Volume</string>
<string name="audio_volume_description">Especifica o volume de saída.</string>
@@ -157,14 +212,24 @@
<string name="ini_saved">Configurações guardadas</string>
<string name="gameid_saved">Configurações guardadas para %1$s</string>
<string name="error_saving">Erro ao guardar %1$s.ini: %2$s</string>
+ <string name="unimplemented_menu">Menu não implementado</string>
<string name="loading">A carregar...</string>
+ <string name="shutting_down">A desligar...</string>
<string name="reset_setting_confirmation">Queres reverter esta definição para os valores padrão?</string>
<string name="reset_to_default">Reverter para padrão</string>
<string name="reset_all_settings">Redefinir todas as configurações?</string>
- <string name="reset_all_settings_description">Todas as configurações avançadas serão redefinidas para as definições padrão. Isto não pode ser revertido.</string>
+ <string name="reset_all_settings_description">Todas as configurações avançadas retornarão ao padrão. Isto não pode ser desfeito.</string>
<string name="settings_reset">Redefinir configurações </string>
<string name="close">Fechar</string>
- <string name="learn_more">Saber Mais</string>
+ <string name="learn_more">Saber mais</string>
+ <string name="auto">Automático</string>
+ <string name="submit">Enviar</string>
+ <string name="string_null">Nenhum (desativado)</string>
+ <string name="string_import">Importar</string>
+ <string name="export">Exportar</string>
+ <string name="export_failed">Exportação falhada</string>
+ <string name="import_failed">IMportação falhada</string>
+ <string name="cancelling">A cancelar</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Seleciona a driver para o GPU</string>
@@ -172,6 +237,7 @@
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Padrão</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>
@@ -182,10 +248,11 @@
<string name="preferences_graphics">Gráficos</string>
<string name="preferences_audio">Audio</string>
<string name="preferences_theme">Cor e tema.</string>
+ <string name="preferences_debug">Depurar</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">A tua ROM está encriptada</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor segue os guias para fazer redump das tuas<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">Cartidges de Jogo</a> or <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">Jogos Instalados</a>.]]></string>
+ <string name="loader_error_encrypted_roms_description"><![CDATA[Por favor, siga os guias para despejar novamente o seu <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards\">cartucho de jogo</a> or <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop\">títulos instalados</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Por favor confirma que o teu ficheiro <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> está instalado para que os jogos possam ser desencriptados.]]></string>
<string name="loader_error_video_core">Ocorreu um erro ao iniciar o núcleo de vídeo.</string>
<string name="loader_error_video_core_description">Isto é normalmente causado por um driver de GPU incompatível. Instalar um driver GPU pode resolver este problema.</string>
@@ -193,28 +260,28 @@
<string name="loader_error_file_not_found">O ficheiro da ROM não existe</string>
<!-- Emulation Menu -->
- <string name="emulation_exit">Sair da emulação</string>
+ <string name="emulation_exit">Parar emulação</string>
<string name="emulation_done">Feito</string>
<string name="emulation_fps_counter">Contador de FPS</string>
- <string name="emulation_toggle_controls">Alterar Controlos</string>
- <string name="emulation_rel_stick_center">Centro do Analógico Relativo</string>
- <string name="emulation_dpad_slide">Deslizar do DPad</string>
- <string name="emulation_haptics">Hápticos </string>
- <string name="emulation_show_overlay">Mostrar sobreposição </string>
- <string name="emulation_toggle_all">Alterar todos</string>
- <string name="emulation_control_adjust">Ajustar a sobreposição </string>
+ <string name="emulation_toggle_controls">Alterar controles</string>
+ <string name="emulation_rel_stick_center">Centro Relativo de Analógico</string>
+ <string name="emulation_dpad_slide">Deslizamento dos Botões Direcionais</string>
+ <string name="emulation_haptics">Vibração ao tocar</string>
+ <string name="emulation_show_overlay">Mostrar overlay</string>
+ <string name="emulation_toggle_all">Marcar/Desmarcar tudo</string>
+ <string name="emulation_control_adjust">Ajustar overlay</string>
<string name="emulation_control_scale">Escala</string>
<string name="emulation_control_opacity">Opacidade</string>
- <string name="emulation_touch_overlay_reset">Redefinir Sobreposição </string>
- <string name="emulation_touch_overlay_edit">Editar sobreposição </string>
- <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_touch_overlay_reset">Restaurar overlay padrão</string>
+ <string name="emulation_touch_overlay_edit">Editar overlay</string>
+ <string name="emulation_pause">Pausar emulação</string>
+ <string name="emulation_unpause">Despausar emulação</string>
+ <string name="emulation_input_overlay">Opções de overlay</string>
- <string name="load_settings">Configurações a carregar...</string>
+ <string name="load_settings">Carregando configurações...</string>
<!-- Software keyboard -->
- <string name="software_keyboard">Teclado de Software</string>
+ <string name="software_keyboard">Teclado de software</string>
<!-- Errors and warnings -->
<string name="abort_button">Abortar</string>
@@ -226,6 +293,9 @@
<string name="fatal_error">Erro fatal</string>
<string name="fatal_error_message">Ocorreu um erro fatal. Verifica o teu registro para detalhes. \nContinuar a emulação pode causar erros.</string>
<string name="performance_warning">Desligar esta configuração irá reduzir a performance da emulação significantemente! Para a melhor experiência é recomendado que deixes esta configuração ativada.</string>
+ <string name="device_memory_inadequate">RAM do dispositivo: %1$s\nRecommended: %2$s</string>
+ <string name="memory_formatted">%1$s %2$s</string>
+ <string name="no_game_present">Nenhum jogo inicializável presente!</string>
<!-- Region Names -->
<string name="region_japan">Japão</string>
@@ -236,7 +306,14 @@
<string name="region_korea">Coreia</string>
<string name="region_taiwan">Taiwan</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulcano</string>
@@ -274,12 +351,17 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">Landscape</string>
+ <string name="screen_layout_portrait">Portrait</string>
+ <string name="screen_layout_auto">Automático</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Padrão (16:9)</string>
<string name="ratio_force_four_three">Forçar 4:3</string>
<string name="ratio_force_twenty_one_nine">Forçar 21:9</string>
<string name="ratio_force_sixteen_ten">Forçar 16:10</string>
- <string name="ratio_stretch">Esticar à Janela</string>
+ <string name="ratio_stretch">Esticar à janela</string>
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Preciso</string>
@@ -287,9 +369,9 @@
<string name="cpu_accuracy_paranoid">Paranoid (Lento)</string>
<!-- Gamepad Buttons -->
- <string name="gamepad_d_pad">D-Pad</string>
- <string name="gamepad_left_stick">Analógico Esquerdo</string>
- <string name="gamepad_right_stick">Analógico Direito</string>
+ <string name="gamepad_d_pad">Botões Direcionais</string>
+ <string name="gamepad_left_stick">Analógico esquerdo</string>
+ <string name="gamepad_right_stick">Analógico direito</string>
<string name="gamepad_home">Home</string>
<string name="gamepad_screenshot">Captura de ecrã</string>
@@ -298,18 +380,32 @@
<string name="building_shaders">A criar shaders</string>
<!-- Theme options -->
- <string name="change_app_theme">Muda o Tema da App</string>
+ <string name="change_app_theme">Mudar o tema do aplicativo</string>
<string name="theme_default">Padrão</string>
<string name="theme_material_you">Material You</string>
<!-- Theme Modes -->
- <string name="change_theme_mode">Altera o Modo do Tema</string>
+ <string name="change_theme_mode">Alterar o tema</string>
<string name="theme_mode_follow_system">Igual ao Sistema</string>
<string name="theme_mode_light">Claro</string>
<string name="theme_mode_dark">Escuro</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Usa Fundos Escuros</string>
+ <string name="use_black_backgrounds">Plano de fundo preto</string>
<string name="use_black_backgrounds_description">Quando usar tema escuro, aplicar fundos escuros</string>
-</resources>
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">Picture in Picture</string>
+ <string name="picture_in_picture_description">Minimizar a janela quando colocada em segundo plano</string>
+ <string name="pause">Pausa</string>
+ <string name="play">Correr</string>
+ <string name="mute">Mute</string>
+ <string name="unmute">Unmute</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Licenças</string>
+ <string name="license_fidelityfx_fsr_description">Upscaling de alta qualidade da AMD</string>
+ </resources>
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 0bef035d6..c614257a8 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">Это программное обеспечение позволяет запускать игры для игровой консоли Nintendo Switch. Мы не предоставляем сами игры или ключи.&lt;br /&gt;&lt;br /&gt;Перед началом работы найдите файл <![CDATA[<b> prod.keys </b>]]> в хранилище устройства..&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Узнать больше</a>]]></string>
<string name="emulation_notification_channel_name">Эмуляция активна</string>
@@ -7,7 +7,7 @@
<string name="emulation_notification_running">yuzu запущен</string>
<string name="notice_notification_channel_name">Уведомления и ошибки</string>
<string name="notice_notification_channel_description">Показывать уведомления, когда что-то пошло не так</string>
- <string name="notification_permission_not_granted">Вы не предоставили разрешение уведомлений!</string>
+ <string name="notification_permission_not_granted">Вы не предоставили разрешение на уведомления!</string>
<!-- Setup strings -->
<string name="welcome">Добро пожаловать!</string>
@@ -25,6 +25,7 @@
<string name="back">Назад</string>
<string name="add_games">Добавить игры</string>
<string name="add_games_description">Выберите папку с играми</string>
+ <string name="step_complete">Выполнено!</string>
<!-- Home strings -->
<string name="home_games">Игры</string>
@@ -38,6 +39,7 @@
<string name="add_games_warning_description">Игры не будут отображаться в списке Игры, если папка не выбрана.</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">Найти игры</string>
+ <string name="search_settings">Настройки поиска</string>
<string name="games_dir_selected">Выбрана папка с играми</string>
<string name="install_prod_keys">Установить prod.keys</string>
<string name="install_prod_keys_description">Требуется для расшифровки розничных игр</string>
@@ -61,14 +63,17 @@
<string name="invalid_keys_file">Выбран неверный файл ключей</string>
<string name="install_keys_success">Ключи успешно установлены</string>
<string name="reading_keys_failure">Ошибка при чтении ключей шифрования</string>
+ <string name="install_prod_keys_failure_extension_description">Убедитесь, что файл ключей имеет расширение .keys, и повторите попытку.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Убедитесь, что файл ключей имеет расширение .bin, и повторите попытку.</string>
<string name="invalid_keys_error">Неверные ключи шифрования</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">Выбранный файл неверен или поврежден. Пожалуйста, пере-дампите ваши ключи.</string>
<string name="install_gpu_driver">Установить драйвер ГП</string>
<string name="install_gpu_driver_description">Установите альтернативные драйверы для потенциально лучшей производительности и/или точности</string>
<string name="advanced_settings">Расширенные настройки</string>
+ <string name="advanced_settings_game">Расширенные настройки: %1$s</string>
<string name="settings_description">Настройка параметров эмулятора</string>
- <string name="search_recently_played">Недавно сыграно</string>
+ <string name="search_recently_played">Недавно сыгранные</string>
<string name="search_recently_added">Недавно добавлено</string>
<string name="search_retail">Розничные</string>
<string name="search_homebrew">Homebrew</string>
@@ -86,6 +91,34 @@
<string name="save_file_invalid_zip_structure_description">Название первой вложенной папки должно быть идентификатором игры.</string>
<string name="import_saves">Импорт</string>
<string name="export_saves">Экспорт</string>
+ <string name="install_firmware">Установить прошивку</string>
+ <string name="install_firmware_description">Прошивка должна находиться в ZIP-архиве и необходима для загрузки некоторых игр</string>
+ <string name="firmware_installing">Установка прошивки</string>
+ <string name="firmware_installed_success">Прошивка успешно установлена</string>
+ <string name="firmware_installed_failure">Не удалось установить прошивку</string>
+ <string name="firmware_installed_failure_description">Убедитесь что файлы прошивки nca находятся в корне zip-архива и повторите попытку.</string>
+ <string name="share_log">Поделиться журналом отладки</string>
+ <string name="share_log_description">Поделиться журналом отладки yuzu для устранения проблем</string>
+ <string name="share_log_missing">Файл журнала не найден</string>
+ <string name="install_game_content">Установить игровой контент</string>
+ <string name="install_game_content_description">Установить обновления игры или дополнений</string>
+ <string name="installing_game_content">Установка контента...</string>
+ <string name="install_game_content_failure">Ошибка установки файл(ов) в NAND.</string>
+ <string name="install_game_content_failure_description">Убедитесь что содержимое допустимо и что файл prod.keys установлен.</string>
+ <string name="install_game_content_failure_base">Установка базовых игр запрещена во избежание возможных конфликтов.</string>
+ <string name="install_game_content_failure_file_extension">Поддерживается только контент NSP и XCI. Пожалуйста убедитесь что игровой контент действителен.</string>
+ <string name="install_game_content_failed_count">%1$d ошибка установки</string>
+ <string name="install_game_content_success">Игровой контент успешно установлен</string>
+ <string name="install_game_content_success_install">%1$d Успешно установлено</string>
+ <string name="install_game_content_success_overwrite">%1$d Успешно перезаписано</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">Пользовательские драйверы не поддерживаются</string>
+ <string name="custom_driver_not_supported_description">Загрузка пользовательского драйвера в настоящее время не поддерживается для этого устройства.\nПроверьте этот параметр еще раз в будущем чтобы узнать была ли добавлена ​​поддержка!
+ </string>
+ <string name="manage_yuzu_data">Управление данными yuzu</string>
+ <string name="manage_yuzu_data_description">Импортируйте/экспортируйте прошивку, ключи, пользовательские данные и многое другое!</string>
+ <string name="share_save_file">Поделиться файлом сохранения</string>
+ <string name="export_save_failed">Не удалось экспортировать сохранение</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia не существует</string>
@@ -94,7 +127,18 @@
<string name="contributors">Контрибьюторы</string>
<string name="contributors_description">Сделано с \u2764 от команды yuzu</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Проекты, которые сделали yuzu для Android возможным</string>
<string name="build">Сборка</string>
+ <string name="user_data">Данные пользователя</string>
+ <string name="user_data_description">Импортируйте/экспортируйте все данные приложения.\n\nПри импорте пользовательских данных все существующие пользовательские данные будут удалены!</string>
+ <string name="exporting_user_data">Экспорт пользовательских данных…</string>
+ <string name="importing_user_data">Импорт пользовательских данных…</string>
+ <string name="import_user_data">Импортировать пользовательские данные</string>
+ <string name="invalid_yuzu_backup">Неверная резервная копия yuzu</string>
+ <string name="user_data_export_success">Пользовательские данные успешно экспортированы</string>
+ <string name="user_data_import_success">Пользовательские данные успешно импортированы</string>
+ <string name="user_data_export_cancelled">Экспорт отменен</string>
+ <string name="user_data_import_failed_description">Убедитесь что папки пользовательских данных находятся в корне zip-папки и содержат файл конфигурации config/config.ini и повторите попытку.</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>
@@ -114,41 +158,51 @@
<string name="are_you_interested">Вы заинтересованы?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">Включить ограничение скорости</string>
- <string name="frame_limit_enable_description">Если эта функция включена, скорость эмуляции будет ограничена указанным процентом от нормальной скорости.</string>
+ <string name="frame_limit_enable">Ограничить скорость</string>
+ <string name="frame_limit_enable_description">Ограничивает скорость эмуляции указанным процентом от нормальной скорости.</string>
<string name="frame_limit_slider">Ограничение процента cкорости</string>
- <string name="frame_limit_slider_description">Указывает процент для ограничения скорости эмуляции. При значении по умолчанию 100% эмуляция будет ограничена нормальной скоростью. Значения выше или ниже будут увеличивать или уменьшать ограничение скорости.</string>
+ <string name="frame_limit_slider_description">Указывает процент ограничения скорости эмуляции. 100% - это нормальная скорость. Значения больше или меньше увеличивают или уменьшают ограничение скорости.</string>
<string name="cpu_accuracy">Точность ЦП</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
<string name="use_docked_mode">Режим док-станции</string>
- <string name="use_docked_mode_description">Эмуляция режима док-станции, что увеличивает разрешение за счет снижения производительности.</string>
- <string name="emulated_region">Эмулируемый регион</string>
- <string name="emulated_language">Эмулируемый язык</string>
+ <string name="use_docked_mode_description">Увеличивает разрешение, снижая производительность. Портативный режим используется при отключении, снижая разрешение и повышая производительность.</string>
+ <string name="emulated_region">Регион консоли</string>
+ <string name="emulated_language">Язык консоли</string>
<string name="select_rtc_date">Выберите дату RTC</string>
<string name="select_rtc_time">Выберите время RTC</string>
- <string name="use_custom_rtc">Включить пользовательский RTC</string>
- <string name="use_custom_rtc_description">Этот параметр позволяет установить пользовательские часы реального времени отдельно от текущего системного времени</string>
+ <string name="use_custom_rtc">Пользовательский RTC</string>
+ <string name="use_custom_rtc_description">Позволяет установить пользовательские часы реального времени отдельно от текущего системного времени.</string>
<string name="set_custom_rtc">Установить пользовательский RTC</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Уровень точности</string>
- <string name="renderer_resolution">Разрешение</string>
+ <string name="renderer_resolution">Разрешение (портативное/в док-станции)</string>
<string name="renderer_vsync">Режим верт. синхронизации</string>
+ <string name="renderer_screen_layout">Ориентация</string>
<string name="renderer_aspect_ratio">Соотношение сторон</string>
<string name="renderer_scaling_filter">Фильтр адаптации окна</string>
<string name="renderer_anti_aliasing">Метод сглаживания</string>
<string name="renderer_force_max_clock">Принудительно заставить максимальную тактовую частоту (только для Adreno)</string>
<string name="renderer_force_max_clock_description">Заставляет ГП работать на максимально возможных тактовых частотах (тепловые ограничения все равно будут применяться).</string>
<string name="renderer_asynchronous_shaders">Использовать асинхронные шейдеры</string>
- <string name="renderer_asynchronous_shaders_description">Компилирует шейдеры асинхронно, что уменьшает зависания, но может взамен предоставить визуальные баги.</string>
- <string name="renderer_debug">Включить отладку графики</string>
- <string name="renderer_debug_description">Если включено, графический API переходит в более медленный режим отладки</string>
- <string name="use_disk_shader_cache">Использовать кэш шейдеров на диске</string>
- <string name="use_disk_shader_cache_description">Уменьшение зависаний за счет хранения и загрузки сгенерированных шейдеров на хранилище.</string>
+ <string name="renderer_asynchronous_shaders_description">Компиляция шейдеров происходит асинхронно, что уменьшает зависания, но может привести к появлению багов.</string>
+ <string name="renderer_reactive_flushing">Реактивная очистка</string>
+ <string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string>
+ <string name="use_disk_shader_cache">Кэш шейдеров на диске</string>
+ <string name="use_disk_shader_cache_description">Уменьшение зависаний за счет хранения и загрузки сгенерированных шейдеров.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">ЦП</string>
+ <string name="cpu_debug_mode">Отладка ЦП</string>
+ <string name="cpu_debug_mode_description">Переводит ЦП в режим медленной отладки.</string>
+ <string name="gpu">графический процессор</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Отладка графики</string>
+ <string name="renderer_debug_description">Переводит графический API в режим медленной отладки.</string>
+ <string name="fastmem">Fastmem</string>
- <!-- Audio settings strings -->
<string name="audio_volume">Громкость</string>
<string name="audio_volume_description">Задает громкость аудиовыхода.</string>
@@ -157,7 +211,9 @@
<string name="ini_saved">Сохраненные настройки</string>
<string name="gameid_saved">Настройки сохранены для %1$s</string>
<string name="error_saving">Ошибка сохранения %1$s.ini: %2$s</string>
+ <string name="unimplemented_menu">Нереализованное меню</string>
<string name="loading">Загрузка...</string>
+ <string name="shutting_down">Выключение…</string>
<string name="reset_setting_confirmation">Хотите ли вы вернуть этот параметр к значению по умолчанию?</string>
<string name="reset_to_default">Сброс к настройкам по умолчанию</string>
<string name="reset_all_settings">Сбросить все настройки?</string>
@@ -165,6 +221,14 @@
<string name="settings_reset">Настройки сброшены</string>
<string name="close">Закрыть</string>
<string name="learn_more">Узнать больше</string>
+ <string name="auto">Авто</string>
+ <string name="submit">Отправить</string>
+ <string name="string_null">Null</string>
+ <string name="string_import">Импорт</string>
+ <string name="export">Экспорт</string>
+ <string name="export_failed">Ошибка экспорта</string>
+ <string name="import_failed">Ошибка импортирования</string>
+ <string name="cancelling">Отменяю</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Выбрать драйвер ГП</string>
@@ -172,6 +236,7 @@
<string name="select_gpu_driver_install">Установить</string>
<string name="select_gpu_driver_default">По умолчанию</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>
@@ -182,10 +247,11 @@
<string name="preferences_graphics">Графика</string>
<string name="preferences_audio">Аудио</string>
<string name="preferences_theme">Тема и цвет</string>
+ <string name="preferences_debug">Отладка</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Ваш ROM зашифрованный</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Пожалуйста, следуйте инструкциям, чтобы пере-дампить ваши <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">игровые картриджи</a> или <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">установленные игры</a>.]]></string>
+ <string name="loader_error_encrypted_roms_description"><![CDATA[Следуйте инструкциям, чтобы пере-дампить игровые картриджи <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards\"> или <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop\"> установленные игры</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Пожалуйста, убедитесь, что ваш файл <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> установлен, чтобы игры можно было расшифровать.]]></string>
<string name="loader_error_video_core">Произошла ошибка при инициализации видеоядра.</string>
<string name="loader_error_video_core_description">Обычно это вызвано несовместимым драйвером ГП. Установка пользовательского драйвера ГП может решить эту проблему.</string>
@@ -199,17 +265,17 @@
<string name="emulation_toggle_controls">Переключение управления</string>
<string name="emulation_rel_stick_center">Относительный центр стика</string>
<string name="emulation_dpad_slide">Слайд крестовиной</string>
- <string name="emulation_haptics">Тактильная обратная связь</string>
+ <string name="emulation_haptics">Обратная связь от нажатий</string>
<string name="emulation_show_overlay">Показать оверлей</string>
<string name="emulation_toggle_all">Переключить всё</string>
- <string name="emulation_control_adjust">Настроить оверлей</string>
+ <string name="emulation_control_adjust">Регулировка оверлея</string>
<string name="emulation_control_scale">Масштаб</string>
<string name="emulation_control_opacity">Непрозрачность</string>
<string name="emulation_touch_overlay_reset">Сбросить оверлей</string>
- <string name="emulation_touch_overlay_edit">Изменить оверлей</string>
+ <string name="emulation_touch_overlay_edit">Редактировать оверлей</string>
<string name="emulation_pause">Пауза эмуляции</string>
- <string name="emulation_unpause">Возобновление эмуляции</string>
- <string name="emulation_input_overlay">Настройки оверлея</string>
+ <string name="emulation_unpause">Возобновить эмуляцию</string>
+ <string name="emulation_input_overlay">Настройка оверлея</string>
<string name="load_settings">Загрузка настроек...</string>
@@ -226,6 +292,9 @@
<string name="fatal_error">Фатальная ошибка</string>
<string name="fatal_error_message">Произошла фатальная ошибка. Проверьте журнал для получения подробной информации.\nПродолжение эмуляции может привести к сбоям и ошибкам.</string>
<string name="performance_warning">Отключение этой настройки значительно снизит производительность эмуляции! Для достижения наилучших результатов рекомендуется оставить эту настройку включенной.</string>
+ <string name="device_memory_inadequate">Оперативная память устройства: %1$s\nРекомендовано: %2$s</string>
+ <string name="memory_formatted">%1$s%2$s</string>
+ <string name="no_game_present">Загрузочной игры нету!</string>
<!-- Region Names -->
<string name="region_japan">Япония</string>
@@ -236,7 +305,14 @@
<string name="region_korea">Корея</string>
<string name="region_taiwan">Тайвань</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Байт</string>
+ <string name="memory_kilobyte">КБ</string>
+ <string name="memory_megabyte">МБ</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">ТБ</string>
+ <string name="memory_petabyte">ПБ</string>
+ <string name="memory_exabyte">ЕВ</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
@@ -274,6 +350,11 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">Пейзаж</string>
+ <string name="screen_layout_portrait">Портрет</string>
+ <string name="screen_layout_auto">Авто</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">Стандартное (16:9)</string>
<string name="ratio_force_four_three">Заставить 4:3</string>
@@ -288,8 +369,8 @@
<!-- Gamepad Buttons -->
<string name="gamepad_d_pad">Крестовина</string>
- <string name="gamepad_left_stick">Левый мини-джойстик</string>
- <string name="gamepad_right_stick">Правый мини-джойстик</string>
+ <string name="gamepad_left_stick">Левый стик</string>
+ <string name="gamepad_right_stick">Правый стик</string>
<string name="gamepad_home">Home</string>
<string name="gamepad_screenshot">Скриншот</string>
@@ -298,18 +379,32 @@
<string name="building_shaders">Постройка шейдеров</string>
<!-- Theme options -->
- <string name="change_app_theme">Изменить тему приложения</string>
+ <string name="change_app_theme">Сменить тему</string>
<string name="theme_default">По умолчанию</string>
<string name="theme_material_you">Material You</string>
<!-- Theme Modes -->
- <string name="change_theme_mode">Изменить режим темы</string>
+ <string name="change_theme_mode">Сменить режим темы</string>
<string name="theme_mode_follow_system">Системная</string>
<string name="theme_mode_light">Светлая</string>
<string name="theme_mode_dark">Темная</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Использовать черный фон</string>
+ <string name="use_black_backgrounds">Чёрный фон</string>
<string name="use_black_backgrounds_description">При использовании темной темы применяйте черный фон.</string>
-</resources>
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">Картинка в картинке</string>
+ <string name="picture_in_picture_description">Свернуть окно при размещении в фоновом режиме</string>
+ <string name="pause">Пауза</string>
+ <string name="play">Играть</string>
+ <string name="mute">Выключить звук</string>
+ <string name="unmute">Включить звук</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Лицензии</string>
+ <string name="license_fidelityfx_fsr_description">Высококачественное масштабирование от AMD</string>
+ </resources>
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 5b789ee98..34809dbb8 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">Це програмне забезпечення дозволяє запускати ігри для ігрової консолі Nintendo Switch. Ми не надаємо самі ігри або ключі.&lt;br /&gt;&lt;br /&gt;Перед початком роботи знайдіть ваш файл <![CDATA[<b> prod.keys </b>]]> у сховищі пристрою.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Дізнатися більше</a>]]></string>
<string name="emulation_notification_channel_name">Емуляція активна</string>
@@ -25,7 +25,6 @@
<string name="back">Назад</string>
<string name="add_games">Додати ігри</string>
<string name="add_games_description">Виберіть папку з іграми</string>
-
<!-- Home strings -->
<string name="home_games">Ігри</string>
<string name="home_search">Пошук</string>
@@ -61,6 +60,7 @@
<string name="invalid_keys_file">Вибрано неправильний файл ключів</string>
<string name="install_keys_success">Ключі успішно встановлено</string>
<string name="reading_keys_failure">Помилка під час зчитування ключів шифрування</string>
+ <string name="install_prod_keys_failure_extension_description">Переконайтеся, що файл ключів має розширення .keys, і повторіть спробу.</string>
<string name="invalid_keys_error">Невірні ключі шифрування</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">Обраний файл невірний або пошкоджений. Будь ласка, пере-дампіть ваші ключі.</string>
@@ -68,8 +68,6 @@
<string name="install_gpu_driver_description">Встановіть альтернативні драйвери для потенційно кращої продуктивності та/або точності</string>
<string name="advanced_settings">Розширені налаштування</string>
<string name="settings_description">Налаштування параметрів емулятора</string>
- <string name="search_recently_played">Нещодавно зіграно</string>
- <string name="search_recently_added">Нещодавно додано</string>
<string name="search_retail">Роздрібні</string>
<string name="search_homebrew">Homebrew</string>
<string name="open_user_folder">Відкрити папку yuzu</string>
@@ -86,7 +84,6 @@
<string name="save_file_invalid_zip_structure_description">Назва першої вкладеної папки має бути ідентифікатором гри.</string>
<string name="import_saves">Імпорт</string>
<string name="export_saves">Експорт</string>
-
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia не існує</string>
<string name="copied_to_clipboard">Скопійовано в буфер обміну</string>
@@ -113,42 +110,20 @@
<string name="our_eternal_gratitude">Наша нескінченна вдячність</string>
<string name="are_you_interested">Ви зацікавлені?</string>
- <!-- General settings strings -->
- <string name="frame_limit_enable">Увімкнути обмеження швидкості</string>
- <string name="frame_limit_enable_description">Якщо цю функцію ввімкнено, швидкість емуляції буде обмежена зазначеним відсотком від нормальної швидкості.</string>
<string name="frame_limit_slider">Обмеження відсотка швидкості</string>
- <string name="frame_limit_slider_description">Вказує відсоток для обмеження швидкості емуляції. При значенні за замовчуванням 100% емуляція буде обмежена нормальною швидкістю. Значення вище або нижче збільшуватимуть або зменшуватимуть обмеження швидкості.</string>
<string name="cpu_accuracy">Точність ЦП</string>
-
- <!-- System settings strings -->
- <string name="use_docked_mode">Режим док-станції</string>
- <string name="use_docked_mode_description">Емуляція режиму док-станції, що збільшує роздільну здатність за рахунок зниження продуктивності.</string>
<string name="emulated_region">Емульований регіон</string>
<string name="emulated_language">Емульована мова</string>
- <string name="select_rtc_date">Оберіть дату RTC</string>
- <string name="select_rtc_time">Оберіть час RTC</string>
- <string name="use_custom_rtc">Увімкнути користувацький RTC</string>
- <string name="use_custom_rtc_description">Цей параметр дає змогу встановити користувацький годинник реального часу окремо від поточного системного часу</string>
- <string name="set_custom_rtc">Встановити користувацький RTC</string>
-
+ <string name="use_custom_rtc">Користувацький RTC</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">Рівень точності</string>
- <string name="renderer_resolution">Роздільна здатність</string>
<string name="renderer_vsync">Режим верт. синхронізації</string>
- <string name="renderer_aspect_ratio">Співвідношення сторін</string>
- <string name="renderer_scaling_filter">Фільтр адаптації вікна</string>
- <string name="renderer_anti_aliasing">Метод згладжування</string>
<string name="renderer_force_max_clock">Примусово змусити максимальну тактову частоту (тільки для Adreno)</string>
<string name="renderer_force_max_clock_description">Змушує ГП працювати на максимально можливих тактових частотах (теплові обмеження все одно будуть застосовуватися).</string>
<string name="renderer_asynchronous_shaders">Використовувати асинхронні шейдери</string>
- <string name="renderer_asynchronous_shaders_description">Компілює шейдери асинхронно, що зменшує зависання, але може натомість надати візуальні баги.</string>
- <string name="renderer_debug">Увімкнути налагодження графіки</string>
- <string name="renderer_debug_description">Якщо увімкнено, графічний API переходить у повільніший режим налагодження</string>
- <string name="use_disk_shader_cache">Використовувати кеш шейдерів на диску</string>
- <string name="use_disk_shader_cache_description">Зменшення зависань завдяки зберіганню та завантаженню згенерованих шейдерів на сховище.</string>
-
- <!-- Audio settings strings -->
+ <!-- Debug settings strings -->
+ <string name="cpu">ЦП</string>
+ <string name="renderer_api">API</string>
<string name="audio_volume">Гучність</string>
<string name="audio_volume_description">Вказує гучність аудіовиходу.</string>
@@ -161,17 +136,20 @@
<string name="reset_setting_confirmation">Чи хочете ви повернути цей параметр до значення за замовчуванням?</string>
<string name="reset_to_default">Скидання до налаштувань за замовчуванням</string>
<string name="reset_all_settings">Скинути всі налаштування</string>
- <string name="reset_all_settings_description">Усі додаткові налаштування буде скинуто до налаштування за замовчуванням. Це неможливо скасувати.</string>
<string name="settings_reset">Налаштування скинуто</string>
<string name="close">Закрити</string>
<string name="learn_more">Дізнатися більше</string>
-
+ <string name="auto">Авто</string>
+ <string name="string_null">Null</string>
+ <string name="string_import">Імпорт</string>
+ <string name="export">Експорт</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Вибрати драйвер ГП</string>
<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_use_default">Використовується стандартний драйвер ГП</string>
+ <string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
<string name="system_gpu_driver">Системний драйвер ГП</string>
<string name="installing_driver">Встановлення драйвера...</string>
@@ -182,40 +160,19 @@
<string name="preferences_graphics">Графіка</string>
<string name="preferences_audio">Аудіо</string>
<string name="preferences_theme">Тема і колір</string>
+ <string name="preferences_debug">Налагодження</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Ваш ROM зашифрований</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[Будь ласка, дотримуйтесь інструкцій, щоб пере-дампити ваші <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">ігрові картриджі</a> або <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">встановлені ігри</a>.]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[Будь ласка, переконайтеся, що ваш файл <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> встановлено, щоб ігри можна було розшифрувати.]]></string>
<string name="loader_error_video_core">Сталася помилка під час ініціалізації відеоядра.</string>
<string name="loader_error_video_core_description">Зазвичай це спричинено несумісним драйвером ГП. Встановлення користувацького драйвера ГП може вирішити цю проблему.</string>
<string name="loader_error_invalid_format">Не вдалося запустити ROM</string>
<string name="loader_error_file_not_found">Файл ROM не існує</string>
- <!-- Emulation Menu -->
- <string name="emulation_exit">Вихід з емуляції</string>
<string name="emulation_done">Готово</string>
- <string name="emulation_fps_counter">Лічильник FPS</string>
- <string name="emulation_toggle_controls">Перемикання керування</string>
- <string name="emulation_rel_stick_center">Відносний центр стіка</string>
- <string name="emulation_dpad_slide">Слайд хрестовиною</string>
- <string name="emulation_haptics">Тактильний зворотний зв\'язок</string>
- <string name="emulation_show_overlay">Показати оверлей</string>
- <string name="emulation_toggle_all">Перемкнути все</string>
- <string name="emulation_control_adjust">Налаштувати оверлей</string>
<string name="emulation_control_scale">Масштаб</string>
<string name="emulation_control_opacity">Непрозорість</string>
- <string name="emulation_touch_overlay_reset">Скинути оверлей</string>
- <string name="emulation_touch_overlay_edit">Змінити оверлей</string>
- <string name="emulation_pause">Пауза емуляції</string>
- <string name="emulation_unpause">Відновлення емуляції</string>
- <string name="emulation_input_overlay">Налаштування оверлея</string>
-
- <string name="load_settings">Завантаження налаштувань...</string>
-
- <!-- Software keyboard -->
- <string name="software_keyboard">Віртуальна клавіатура</string>
-
<!-- Errors and warnings -->
<string name="abort_button">Перервати</string>
<string name="continue_button">Продовжити</string>
@@ -226,7 +183,6 @@
<string name="fatal_error">Фатальна помилка</string>
<string name="fatal_error_message">Сталася фатальна помилка. Перевірте журнал для отримання докладної інформації.\nПродовження емуляції може призвести до збоїв і помилок.</string>
<string name="performance_warning">Вимкнення цього налаштування значно знизить продуктивність емуляції! Для досягнення найкращих результатів рекомендується залишити це налаштування увімкненим.</string>
-
<!-- Region Names -->
<string name="region_japan">Японія</string>
<string name="region_usa">США</string>
@@ -236,8 +192,7 @@
<string name="region_korea">Корея</string>
<string name="region_taiwan">Тайвань</string>
- <!-- Language Names -->
-
+ <string name="memory_gigabyte">GB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
<string name="renderer_none">Вимкнено</string>
@@ -274,22 +229,18 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <string name="screen_layout_auto">Авто</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">За замовчуванням (16:9)</string>
<string name="ratio_force_four_three">Змусити 4:3</string>
<string name="ratio_force_twenty_one_nine">Змусити 21:9</string>
<string name="ratio_force_sixteen_ten">Змусити 16:10</string>
- <string name="ratio_stretch">Розтягнути до вікна</string>
-
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Точно</string>
<string name="cpu_accuracy_unsafe">Небезпечно</string>
<string name="cpu_accuracy_paranoid">Параноїк (повільно)</string>
- <!-- Gamepad Buttons -->
- <string name="gamepad_d_pad">Кнопки напрямків</string>
- <string name="gamepad_left_stick">Лівий міні-джойстик</string>
- <string name="gamepad_right_stick">Правий міні-джойстик</string>
<string name="gamepad_home">Home</string>
<string name="gamepad_screenshot">Знімок екрану</string>
@@ -297,19 +248,16 @@
<string name="preparing_shaders">Підготовка шейдерів</string>
<string name="building_shaders">Побудова шейдерів</string>
- <!-- Theme options -->
- <string name="change_app_theme">Змінити тему застосунку</string>
<string name="theme_default">За замовчуванням</string>
<string name="theme_material_you">Material You</string>
- <!-- Theme Modes -->
- <string name="change_theme_mode">Змінити режим теми</string>
<string name="theme_mode_follow_system">Системна</string>
<string name="theme_mode_light">Світла</string>
<string name="theme_mode_dark">Темна</string>
- <!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">Використовувати чорне тло</string>
<string name="use_black_backgrounds_description">У разі використання темної теми застосовуйте чорне тло.</string>
-</resources>
+ <string name="mute">Вимкнути звук</string>
+ <string name="unmute">Увімкнути звук</string>
+
+ </resources>
diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml
new file mode 100644
index 000000000..f977db3a2
--- /dev/null
+++ b/src/android/app/src/main/res/values-vi/strings.xml
@@ -0,0 +1,340 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
+
+ <string name="app_disclaimer">Phần mềm này sẽ chạy các game cho máy chơi game Nintendo Switch. Không có title games hoặc keys được bao gồm.&lt;br /&gt;&lt;br /&gt;Trước khi bạn bắt đầu, hãy tìm tập tin <![CDATA[<b> prod.keys </b>]]> trên bộ nhớ thiết bị của bạn.&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">Tìm hiểu thêm</a>]]></string>
+ <string name="emulation_notification_channel_name">Giả lập đang chạy</string>
+ <string name="emulation_notification_channel_description">Hiển thị thông báo liên tục khi giả lập đang chạy.</string>
+ <string name="emulation_notification_running">yuzu đang chạy</string>
+ <string name="notice_notification_channel_name">Thông báo và lỗi</string>
+ <string name="notice_notification_channel_description">Hiển thị thông báo khi có sự cố xảy ra.</string>
+ <string name="notification_permission_not_granted">Ứng dụng không được cấp quyền thông báo!</string>
+
+ <!-- Setup strings -->
+ <string name="welcome">Chào mừng!</string>
+ <string name="welcome_description">Tìm hiểu cách cài đặt &lt;b>yuzu&lt;/b> và bắt đầu giả lập.</string>
+ <string name="get_started">Bắt đầu</string>
+ <string name="keys">Keys</string>
+ <string name="keys_description">Chọn tệp &lt;b>prod.keys&lt;/b> của bạn bằng nút bên dưới.</string>
+ <string name="select_keys">Chọn Keys</string>
+ <string name="games">Game</string>
+ <string name="games_description">Chọn thư mục &lt;b>Game&lt;/b> của bạn bằng nút bên dưới.</string>
+ <string name="done">Hoàn thành</string>
+ <string name="done_description">Tất cả đã hoàn tất.\nHãy tận hưởng các game của bạn!</string>
+ <string name="text_continue">Tiếp tục</string>
+ <string name="next">Tiếp theo</string>
+ <string name="back">Trở lại</string>
+ <string name="add_games">Thêm Game</string>
+ <string name="add_games_description">Chọn thư mục game của bạn</string>
+ <!-- Home strings -->
+ <string name="home_games">Game</string>
+ <string name="home_search">Tìm kiếm</string>
+ <string name="home_settings">Cài đặt</string>
+ <string name="empty_gamelist">Không tìm thấy tập tin hoặc chưa có thư mục game nào được chọn.</string>
+ <string name="search_and_filter_games">Tìm và lọc game</string>
+ <string name="select_games_folder">Chọn thư mục game</string>
+ <string name="select_games_folder_description">Cho phép yuzu thêm vào danh sách game</string>
+ <string name="add_games_warning">Bỏ qua việc lựa chọn thư mục game?</string>
+ <string name="add_games_warning_description">Game sẽ không hiển thị trong danh sách nếu một thư mục không được chọn.</string>
+ <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
+ <string name="home_search_games">Tìm kiếm game</string>
+ <string name="games_dir_selected">Thư mục game đã được chọn</string>
+ <string name="install_prod_keys">Cài đặt prod.keys</string>
+ <string name="install_prod_keys_description">Yêu cầu để giải mã các game bán lẻ</string>
+ <string name="install_prod_keys_warning">Bỏ qua việc thêm keys?</string>
+ <string name="install_prod_keys_warning_description">Cần có keys hợp lệ để giả lập các game bán lẻ. Chỉ có các ứng dụng homebrew có thể vận hành nếu bạn tiếp tục.</string>
+ <string name="install_prod_keys_warning_help">https://yuzu-emu.org/help/quickstart/#guide-introduction</string>
+ <string name="notifications">Thông báo</string>
+ <string name="notifications_description">Cấp quyền thông báo bằng nút bên dưới.</string>
+ <string name="give_permission">Cấp quyền</string>
+ <string name="notification_warning">Bỏ qua việc cấp quyền thông báo?</string>
+ <string name="notification_warning_description">yuzu sẽ không thể gửi những thông báo quan trọng đến bạn.</string>
+ <string name="permission_denied">Đã từ chối cấp quyền</string>
+ <string name="permission_denied_description">Bạn từ chối cấp quyền này quá nhiều lần và giờ bạn phải cấp quyền thủ công trong cài đặt máy.</string>
+ <string name="about">Thông tin</string>
+ <string name="about_description">Phiên bản, đóng góp và những thứ khác</string>
+ <string name="warning_help">Trợ giúp</string>
+ <string name="warning_skip">Bỏ qua</string>
+ <string name="warning_cancel">Hủy bỏ</string>
+ <string name="install_amiibo_keys">Cài đặt keys Amiibo</string>
+ <string name="install_amiibo_keys_description">Cần thiết để dùng Amiibo trong game</string>
+ <string name="invalid_keys_file">Tệp keys không hợp lệ đã được chọn</string>
+ <string name="install_keys_success">Cài đặt keys thành công</string>
+ <string name="reading_keys_failure">Lỗi đọc keys mã hóa</string>
+ <string name="install_prod_keys_failure_extension_description">Xác minh rằng tệp keys của bạn có đuôi .keys và thử lại.</string>
+ <string name="install_amiibo_keys_failure_extension_description">Xác minh rằng tệp keys của bạn có đuôi .bin và thử lại.</string>
+ <string name="invalid_keys_error">Keys mã hoá không hợp lệ</string>
+ <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
+ <string name="install_keys_failure_description">Tệp đã chọn sai hoặc hỏng. Vui lòng trích xuất lại keys của bạn.</string>
+ <string name="install_gpu_driver">Cài đặt driver GPU</string>
+ <string name="install_gpu_driver_description">Cài đặt driver thay thế để có thể có hiệu suất tốt và chính xác hơn</string>
+ <string name="advanced_settings">Cài đặt nâng cao</string>
+ <string name="settings_description">Cấu hình cài đặt giả lập</string>
+ <string name="search_recently_played">Đã chơi gần đây</string>
+ <string name="search_recently_added">Đã thêm gần đây</string>
+ <string name="search_retail">Bán lẻ</string>
+ <string name="search_homebrew">Homebrew</string>
+ <string name="open_user_folder">Mở thư mục yuzu</string>
+ <string name="open_user_folder_description">Quản lý tệp nội bộ của yuzu</string>
+ <string name="theme_and_color_description">Thay đổi giao diện ứng dụng</string>
+ <string name="no_file_manager">Không tìm thấy trình quản lý tập tin</string>
+ <string name="notification_no_directory_link">Không thể mở thư mục yuzu</string>
+ <string name="notification_no_directory_link_description">Vui lòng xác định thư mục người dùng với bảng điều khiển bên của trình quản lý tệp thủ công.</string>
+ <string name="manage_save_data">Quản lý dữ liệu save</string>
+ <string name="manage_save_data_description">Đã tìm thấy dữ liệu save. Vui lòng chọn một tuỳ chọn bên dưới.</string>
+ <string name="import_export_saves_description">Nhập hoặc xuất tệp save</string>
+ <string name="save_file_imported_success">Nhập thành công</string>
+ <string name="save_file_invalid_zip_structure">Cấu trúc thư mục save không hợp lệ</string>
+ <string name="save_file_invalid_zip_structure_description">Tên thư mục con đầu tiên phải là ID title của game.</string>
+ <string name="import_saves">Nhập</string>
+ <string name="export_saves">Xuất</string>
+ <string name="install_firmware">Cài đặt firmware</string>
+ <string name="install_firmware_description">Firmware phải được đặt trong một tập tin nén ZIP và cần thiết để khởi chạy một số game</string>
+ <string name="firmware_installing">Đang cài đặt firmware</string>
+ <string name="firmware_installed_success">Cài đặt firmware thành công</string>
+ <string name="firmware_installed_failure">Cài đặt firmware thất bại</string>
+ <string name="share_log">Chia sẻ nhật ký gỡ lỗi</string>
+ <string name="share_log_description">Chia sẻ tập tin nhật ký của yuzu để gỡ lỗi vấn đề</string>
+ <string name="share_log_missing">Không tìm thấy tập tin nhật ký</string>
+ <string name="install_game_content">Cài đặt nội dung game</string>
+ <string name="install_game_content_description">Cài đặt cập nhật game hoặc DLC</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <!-- About screen strings -->
+ <string name="gaia_is_not_real">Gaia không có thật</string>
+ <string name="copied_to_clipboard">Đã sao chép vào bộ nhớ tạm</string>
+ <string name="about_app_description">Một giả lập Switch mã nguồn mở</string>
+ <string name="contributors">Người đóng góp</string>
+ <string name="contributors_description">Được làm với \u2764 từ nhóm yuzu</string>
+ <string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Các dự án làm cho yuzu trên Android trở thành điều có thể</string>
+ <string name="build">Dựng</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>
+
+ <!-- Early access upgrade strings -->
+ <string name="early_access">Early Access</string>
+ <string name="get_early_access">Tải Early Access</string>
+ <string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
+ <string name="get_early_access_description">Các tính năng tiên tiến, truy cập sớm các bản cập nhật và nhiều hơn nữa</string>
+ <string name="early_access_benefits">Lợi ích của Early Access</string>
+ <string name="cutting_edge_features">Tính năng tiên tiến</string>
+ <string name="early_access_updates">Truy cập sớm các bản cập nhật</string>
+ <string name="no_manual_installation">Không có cài đặt thủ công</string>
+ <string name="prioritized_support">Ưu tiên hỗ trợ</string>
+ <string name="helping_game_preservation">Hỗ trợ bảo tồn game</string>
+ <string name="our_eternal_gratitude">Sự biết ơn vô hạn của chúng tôi</string>
+ <string name="are_you_interested">Bạn có thấy hứng thú không?</string>
+
+ <!-- General settings strings -->
+ <string name="frame_limit_enable">Giới hạn tốc độ</string>
+ <string name="frame_limit_enable_description">Giới hạn tốc độ giả lập ở một phần trăm cụ thể của tốc độ bình thường.</string>
+ <string name="frame_limit_slider">Giới hạn phần trăm tốc độ</string>
+ <string name="frame_limit_slider_description">Xác định phần trăm để giới hạn tốc độ giả lập. 100% là tốc độ bình thường. Giá trị cao hơn hoặc thấp hơn sẽ tăng hoặc giảm giới hạn tốc độ.</string>
+ <string name="cpu_accuracy">Độ chính xác CPU</string>
+ <!-- System settings strings -->
+ <string name="use_docked_mode">Chế độ docked</string>
+ <string name="use_docked_mode_description">Tăng độ phân giải, giảm hiệu suất. Chế độ handheld được sử dụng khi tắt, giảm độ phân giải và tăng hiệu suất.</string>
+ <string name="emulated_region">Khu vực giả lập</string>
+ <string name="emulated_language">Ngôn ngữ giả lập</string>
+ <string name="select_rtc_date">Chọn ngày RTC</string>
+ <string name="select_rtc_time">Chọn giờ RTC</string>
+ <string name="use_custom_rtc">RTC tuỳ chỉnh</string>
+ <string name="use_custom_rtc_description">Cho phép bạn thiết lập một đồng hồ thời gian thực tùy chỉnh riêng biệt so với thời gian hệ thống hiện tại.</string>
+ <string name="set_custom_rtc">Thiết lập RTC tùy chỉnh</string>
+
+ <!-- Graphics settings strings -->
+ <string name="renderer_accuracy">Mức độ chính xác</string>
+ <string name="renderer_resolution">Độ phân giải (Handheld/Docked)</string>
+ <string name="renderer_vsync">Chế độ VSync</string>
+ <string name="renderer_aspect_ratio">Tỉ lệ khung hình</string>
+ <string name="renderer_scaling_filter">Bộ lọc điều chỉnh cửa sổ</string>
+ <string name="renderer_anti_aliasing">Phương pháp khử răng cưa</string>
+ <string name="renderer_force_max_clock">Buộc chạy ở xung nhịp tối đa (chỉ cho Adreno)</string>
+ <string name="renderer_force_max_clock_description">Buộc GPU hoạt động ở xung nhịp tối đa có thể (ràng buộc nhiệt độ vẫn sẽ được áp dụng).</string>
+ <string name="renderer_asynchronous_shaders">Dùng các shader bất đồng bộ</string>
+ <string name="renderer_asynchronous_shaders_description">Biên dịch các shader bất đồng bộ, giảm tình trạng giật lag nhưng có thể gây ra các lỗi.</string>
+ <string name="renderer_reactive_flushing">Dùng xả tương ứng</string>
+ <string name="renderer_reactive_flushing_description">Cải thiện độ chính xác kết xuất trong một số game nhưng đồng thời giảm hiệu suất.</string>
+ <string name="use_disk_shader_cache">Lưu bộ nhớ đệm shader trên ổ cứng</string>
+ <string name="use_disk_shader_cache_description">Giảm tình trạng giật lag bằng cách lưu trữ và tải các shader được tạo ra nội bộ.</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">Gỡ lỗi đồ hoạ</string>
+ <string name="renderer_debug_description">Đặt API đồ họa vào chế độ gỡ lỗi chậm.</string>
+ <string name="audio_volume">Âm lượng</string>
+ <string name="audio_volume_description">Xác định âm lượng của đầu ra âm thanh.</string>
+
+ <!-- Miscellaneous -->
+ <string name="slider_default">Mặc định</string>
+ <string name="ini_saved">Cài đặt đã lưu</string>
+ <string name="gameid_saved">Cài đặt đã lưu cho %1$s</string>
+ <string name="error_saving">Lỗi khi lưu %1$s.ini: %2$s</string>
+ <string name="loading">Đang tải...</string>
+ <string name="reset_setting_confirmation">Bạn có muốn đặt lại cài đặt này về giá trị mặc định không?</string>
+ <string name="reset_to_default">Đặt lại về mặc định</string>
+ <string name="reset_all_settings">Bạn có muốn đặt lại tất cả các cài đặt về giá trị mặc định không?</string>
+ <string name="reset_all_settings_description">Tất cả các cài đặt nâng cao sẽ được đặt lại về cấu hình mặc định. Điều này không thể hoàn tác.</string>
+ <string name="settings_reset">Cài đặt đã được đặt lại</string>
+ <string name="close">Đóng</string>
+ <string name="learn_more">Tìm hiểu thêm</string>
+ <string name="auto">Tự động</string>
+ <string name="submit">Gửi</string>
+ <string name="string_null">Null</string>
+ <string name="string_import">Nhập</string>
+ <string name="export">Xuất</string>
+ <!-- GPU driver installation -->
+ <string name="select_gpu_driver">Chọn driver GPU</string>
+ <string name="select_gpu_driver_title">Bạn có muốn thay thế driver GPU hiện tại không?</string>
+ <string name="select_gpu_driver_install">Cài đặt</string>
+ <string name="select_gpu_driver_default">Mặc định</string>
+ <string name="select_gpu_driver_use_default">Dùng driver GPU mặc định</string>
+ <string name="select_gpu_driver_error">Driver không hợp lệ đã được chọn, dùng mặc định hệ thống!</string>
+ <string name="system_gpu_driver">Driver GPU hệ thống</string>
+ <string name="installing_driver">Đang cài đặt driver...</string>
+
+ <!-- Preferences Screen -->
+ <string name="preferences_settings">Cài đặt</string>
+ <string name="preferences_general">Chung</string>
+ <string name="preferences_system">Hệ thống</string>
+ <string name="preferences_graphics">Đồ hoạ</string>
+ <string name="preferences_audio">Âm thanh</string>
+ <string name="preferences_theme">Chủ đề và màu sắc</string>
+ <string name="preferences_debug">Gỡ lỗi</string>
+
+ <!-- ROM loading errors -->
+ <string name="loader_error_encrypted">ROM của bạn đã bị mã hoá</string>
+ <string name="loader_error_encrypted_keys_description"><![CDATA[Vui lòng đảm bảo tệp <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> đã được cài đặt để các game có thể được giải mã.]]></string>
+ <string name="loader_error_video_core">Đã xảy ra lỗi khi khởi tạo lõi video</string>
+ <string name="loader_error_video_core_description">Việc này thường do driver GPU không tương thích. Cài đặt một driver GPU tùy chỉnh có thể giải quyết vấn đề này.</string>
+ <string name="loader_error_invalid_format">Không thể nạp ROM</string>
+ <string name="loader_error_file_not_found">Tệp ROM không tồn tại</string>
+
+ <!-- Emulation Menu -->
+ <string name="emulation_exit">Thoát giả lập</string>
+ <string name="emulation_done">Hoàn thành</string>
+ <string name="emulation_fps_counter">Bộ đếm FPS</string>
+ <string name="emulation_toggle_controls">Chuyển đổi điều khiển</string>
+ <string name="emulation_rel_stick_center">Trung tâm nút cần xoay tương đối</string>
+ <string name="emulation_dpad_slide">Trượt D-pad</string>
+ <string name="emulation_haptics">Chạm haptics</string>
+ <string name="emulation_show_overlay">Hiện lớp phủ</string>
+ <string name="emulation_toggle_all">Chuyển đổi tất cả</string>
+ <string name="emulation_control_adjust">Điều chỉnh lớp phủ</string>
+ <string name="emulation_control_scale">Tỉ lệ thu phóng</string>
+ <string name="emulation_control_opacity">Độ mờ</string>
+ <string name="emulation_touch_overlay_reset">Đặt lại lớp phủ</string>
+ <string name="emulation_touch_overlay_edit">Chỉnh sửa lớp phủ</string>
+ <string name="emulation_pause">Tạm đừng giả lập</string>
+ <string name="emulation_unpause">Tiếp tục giả lập</string>
+ <string name="emulation_input_overlay">Tuỳ chọn lớp phủ</string>
+
+ <string name="load_settings">Đang tải cài đặt...</string>
+
+ <!-- Software keyboard -->
+ <string name="software_keyboard">Bàn phím mềm</string>
+
+ <!-- Errors and warnings -->
+ <string name="abort_button">Hủy bỏ</string>
+ <string name="continue_button">Tiếp tục</string>
+ <string name="system_archive_not_found">Không tìm thấy bản lưu trữ của hệ thống</string>
+ <string name="system_archive_not_found_message">%s bị thiếu. Vui lòng trích xuất các bản lưu trữ hệ thống của bạn.\nNếu chạy tiếp giả lập có thể bị crash và lỗi.</string>
+ <string name="system_archive_general">Một bản lưu trữ của hệ thống</string>
+ <string name="save_load_error">Lỗi Lưu/Tải</string>
+ <string name="fatal_error">Lỗi nghiêm trọng</string>
+ <string name="fatal_error_message">Đã xảy ra lỗi nghiêm trọng. Kiểm tra nhật ký để biết thêm chi tiết.\nNếu chạy tiếp giả lập có thể bị crash và lỗi.</string>
+ <string name="performance_warning">Tắt cài đặt này sẽ làm giảm đáng kể hiệu suất giả lập! Để có trải nghiệm tốt nhất, bạn nên bật cài đặt này.</string>
+ <!-- Region Names -->
+ <string name="region_japan">Nhật Bản</string>
+ <string name="region_usa">Hoa Kỳ</string>
+ <string name="region_europe">Châu Âu</string>
+ <string name="region_australia">Úc</string>
+ <string name="region_china">Trung Quốc</string>
+ <string name="region_korea">Hàn Quốc</string>
+ <string name="region_taiwan">Đài Loan</string>
+
+ <string name="memory_gigabyte">GB</string>
+ <!-- Renderer APIs -->
+ <string name="renderer_vulkan">Vulkan</string>
+ <string name="renderer_none">Không có</string>
+
+ <!-- Renderer Accuracy -->
+ <string name="renderer_accuracy_normal">Bình thường</string>
+ <string name="renderer_accuracy_high">Cao</string>
+ <string name="renderer_accuracy_extreme">Cực đại (Chậm)</string>
+
+ <!-- Resolutions -->
+ <string name="resolution_half">0.5X (360p/540p)</string>
+ <string name="resolution_three_quarter">0.75X (540p/810p)</string>
+ <string name="resolution_one">1X (720p/1080p)</string>
+ <string name="resolution_two">2X (1440p/2160p) (Chậm)</string>
+ <string name="resolution_three">3X (2160p/3240p) (Chậm)</string>
+ <string name="resolution_four">4X (2880p/4320p) (Chậm)</string>
+
+ <!-- Renderer VSync -->
+ <string name="renderer_vsync_immediate">Immediate (Tắt)</string>
+ <string name="renderer_vsync_mailbox">Mailbox</string>
+ <string name="renderer_vsync_fifo">FIFO (Bật)</string>
+ <string name="renderer_vsync_fifo_relaxed">FIFO Relaxed</string>
+
+ <!-- Scaling Filters -->
+ <string name="scaling_filter_nearest_neighbor">Nearest Neighbor</string>
+ <string name="scaling_filter_bilinear">Bilinear</string>
+ <string name="scaling_filter_bicubic">Bicubic</string>
+ <string name="scaling_filter_gaussian">Gaussian</string>
+ <string name="scaling_filter_scale_force">ScaleForce</string>
+ <string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
+
+ <!-- Anti-Aliasing -->
+ <string name="anti_aliasing_none">Không có</string>
+ <string name="anti_aliasing_fxaa">FXAA</string>
+ <string name="anti_aliasing_smaa">SMAA</string>
+
+ <string name="screen_layout_auto">Tự động</string>
+
+ <!-- Aspect Ratios -->
+ <string name="ratio_default">Mặc định (16:9)</string>
+ <string name="ratio_force_four_three">Dùng 4:3</string>
+ <string name="ratio_force_twenty_one_nine">Dùng 21:9</string>
+ <string name="ratio_force_sixteen_ten">Dùng 16:10</string>
+ <string name="ratio_stretch">Mở rộng đến cửa sổ</string>
+
+ <!-- CPU Accuracy -->
+ <string name="cpu_accuracy_accurate">Chính xác</string>
+ <string name="cpu_accuracy_unsafe">Không an toàn</string>
+ <string name="cpu_accuracy_paranoid">Paranoid (Chậm)</string>
+
+ <!-- Gamepad Buttons -->
+ <string name="gamepad_d_pad">D-pad</string>
+ <string name="gamepad_left_stick">Cần trái</string>
+ <string name="gamepad_right_stick">Cần phải</string>
+ <string name="gamepad_home">Home</string>
+ <string name="gamepad_screenshot">Ảnh chụp màn hình</string>
+
+ <!-- Disk shader cache -->
+ <string name="preparing_shaders">Đang chuẩn bị shader</string>
+ <string name="building_shaders">Đang đựng shader</string>
+
+ <!-- Theme options -->
+ <string name="change_app_theme">Thay đổi chủ đề ứng dụng</string>
+ <string name="theme_default">Mặc định</string>
+ <string name="theme_material_you">Material You</string>
+
+ <!-- Theme Modes -->
+ <string name="change_theme_mode">Thay đổi chủ đề</string>
+ <string name="theme_mode_follow_system">Theo hệ thống</string>
+ <string name="theme_mode_light">Sáng</string>
+ <string name="theme_mode_dark">Tối</string>
+
+ <!-- Black backgrounds theme -->
+ <string name="use_black_backgrounds">Nền đen</string>
+ <string name="use_black_backgrounds_description">Khi sử dụng chủ đề tối, hãy áp dụng nền đen.</string>
+
+ <string name="mute">Tắt tiếng</string>
+ <string name="unmute">Bật tiếng</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">Giấy phép</string>
+ <string name="license_fidelityfx_fsr_description">Upscaling chất lượng cao từ AMD</string>
+ </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 c0e885751..13455564f 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
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">此软件可以运行 Nintendo Switch 游戏,但不包含任何游戏和密钥文件。&lt;br /&gt;&lt;br /&gt;在开始前,请找到放置于设备存储中的 <![CDATA[<b> prod.keys </b>]]> 文件。&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">了解更多</a>]]></string>
<string name="emulation_notification_channel_name">正在进行模拟</string>
@@ -17,7 +17,7 @@
<string name="keys_description">使用下方的按钮来选择你的 &lt;b>prod.keys&lt;/b> 文件。</string>
<string name="select_keys">选择密钥文件</string>
<string name="games">游戏</string>
- <string name="games_description">使用下方的按钮选择你的 &lt;b>游戏&lt;/b> 文件夹。</string>
+ <string name="games_description">使用下方的按钮选择你的&lt;b>游戏&lt;/b>文件夹。</string>
<string name="done">完成</string>
<string name="done_description">你完成了全部设置。\n玩的开心!</string>
<string name="text_continue">继续</string>
@@ -25,6 +25,7 @@
<string name="back">上一步</string>
<string name="add_games">添加游戏</string>
<string name="add_games_description">选择你的游戏文件夹</string>
+ <string name="step_complete">完成!</string>
<!-- Home strings -->
<string name="home_games">游戏</string>
@@ -38,6 +39,7 @@
<string name="add_games_warning_description">如果未选择游戏文件夹,游戏将不会显示在游戏列表中。</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">搜索游戏</string>
+ <string name="search_settings">搜索设置</string>
<string name="games_dir_selected">已选择游戏文件夹</string>
<string name="install_prod_keys">安装 prod.keys 文件</string>
<string name="install_prod_keys_description">需要密钥文件来解密游戏</string>
@@ -61,12 +63,15 @@
<string name="invalid_keys_file">选择的密钥文件无效</string>
<string name="install_keys_success">密钥文件已成功安装</string>
<string name="reading_keys_failure">读取加密密钥时出错</string>
+ <string name="install_prod_keys_failure_extension_description">请确保您的密钥文件扩展名为 .keys 并重试。</string>
+ <string name="install_amiibo_keys_failure_extension_description">请确保您的密钥文件扩展名为 .bin 并重试。</string>
<string name="invalid_keys_error">无效的加密密钥</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">选择的密钥文件不正确或已损坏。请重新转储密钥文件。</string>
<string name="install_gpu_driver">安装 GPU 驱动</string>
<string name="install_gpu_driver_description">安装替代的驱动程序以获得更好的性能和精度</string>
<string name="advanced_settings">高级选项</string>
+ <string name="advanced_settings_game">高级选项: %1$s</string>
<string name="settings_description">更改模拟器设置</string>
<string name="search_recently_played">最近游玩</string>
<string name="search_recently_added">最近添加</string>
@@ -86,6 +91,33 @@
<string name="save_file_invalid_zip_structure_description">第一个子文件夹名称必须为当前游戏的 ID。</string>
<string name="import_saves">导入</string>
<string name="export_saves">导出</string>
+ <string name="install_firmware">安装固件</string>
+ <string name="install_firmware_description">固件文件必须为 zip 格式,启动某些游戏时必需</string>
+ <string name="firmware_installing">正在安装固件</string>
+ <string name="firmware_installed_success">固件已成功安装</string>
+ <string name="firmware_installed_failure">固件安装失败</string>
+ <string name="firmware_installed_failure_description">请确保固件 nca 文件位于 zip 压缩包的根目录,然后重试。</string>
+ <string name="share_log">分享调试日志</string>
+ <string name="share_log_description">分享 yuzu 日志文件以便调试</string>
+ <string name="share_log_missing">未找到日志文件</string>
+ <string name="install_game_content">安装游戏附加内容</string>
+ <string name="install_game_content_description">安装游戏更新及 DLC</string>
+ <string name="installing_game_content">安装中...</string>
+ <string name="install_game_content_failure">向 NAND 安装文件时失败</string>
+ <string name="install_game_content_failure_description">请确保附加内容的有效性,并且 prod.keys 密钥文件已安装。</string>
+ <string name="install_game_content_failure_base">为避免产生冲突,此功能不能用于安装游戏本体。</string>
+ <string name="install_game_content_failure_file_extension">只有 NSP 或 XCI 格式的附加内容可以安装。请确保您的游戏附加内容是有效的。</string>
+ <string name="install_game_content_failed_count">%1$d 安装出错</string>
+ <string name="install_game_content_success">游戏附加内容已成功安装</string>
+ <string name="install_game_content_success_install">%1$d 安装成功</string>
+ <string name="install_game_content_success_overwrite">%1$d 覆盖安装成功</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">不支持自定义驱动</string>
+ <string name="custom_driver_not_supported_description">此设备不支持自定义驱动。\n请之后再访问此项,查看是否已为此设备添加支持。</string>
+ <string name="manage_yuzu_data">管理 yuzu 数据</string>
+ <string name="manage_yuzu_data_description">导入/导出固件、密钥、用户数据及其他。</string>
+ <string name="share_save_file">分享存档文件</string>
+ <string name="export_save_failed">导出存档文件失败</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia 不真实</string>
@@ -94,14 +126,25 @@
<string name="contributors">贡献者</string>
<string name="contributors_description">使用来自 yuzu 团队的 \u2764 制作</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">Android 版 yuzu 离不开这些项目的支持</string>
<string name="build">构建版本</string>
+ <string name="user_data">用户数据</string>
+ <string name="user_data_description">导入/导出应用程序所有数据。\n\n导入用户数据时,将删除当前所有的用户数据!</string>
+ <string name="exporting_user_data">正在导出用户数据...</string>
+ <string name="importing_user_data">正在导入用户数据...</string>
+ <string name="import_user_data">导入用户数据</string>
+ <string name="invalid_yuzu_backup">无效的 yuzu 备份</string>
+ <string name="user_data_export_success">导出用户数据成功</string>
+ <string name="user_data_import_success">导入用户数据成功</string>
+ <string name="user_data_export_cancelled">已取消导出数据</string>
+ <string name="user_data_import_failed_description">请确保用户数据文件夹位于 zip 压缩包的根目录,并在 config/config.ini 路径中包含配置文件,然后重试。</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>
<!-- Early access upgrade strings -->
<string name="early_access">抢先体验</string>
- <string name="get_early_access">取得抢先体验</string>
+ <string name="get_early_access">获取抢先体验!</string>
<string name="play_store_link">https://play.google.com/store/apps/details?id=org.yuzu.yuzu_emu.ea</string>
<string name="get_early_access_description">最新的功能、抢先更新、以及更多</string>
<string name="early_access_benefits">抢先体验的权益</string>
@@ -109,33 +152,34 @@
<string name="early_access_updates">抢先更新</string>
<string name="no_manual_installation">无需手动安装</string>
<string name="prioritized_support">优先支持</string>
- <string name="helping_game_preservation">帮助保留游戏</string>
+ <string name="helping_game_preservation">帮助保留游玩历史</string>
<string name="our_eternal_gratitude">我们真诚的感激</string>
<string name="are_you_interested">您对此感兴趣吗?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">启用运行速度限制</string>
- <string name="frame_limit_enable_description">启用后,模拟速度将限制在正常运行速度的指定百分比。</string>
+ <string name="frame_limit_enable">运行速度限制</string>
+ <string name="frame_limit_enable_description">将运行速度限制为正常速度的指定百分比。</string>
<string name="frame_limit_slider">限制速度百分比</string>
- <string name="frame_limit_slider_description">指定限制模拟速度的百分比。预设为 100%,此时模拟速度将被限制为标准速度。更高或更低的值将增加或降低速度限制上限。</string>
+ <string name="frame_limit_slider_description">指定限制运行速度的百分比。100% 为正常速度。更高或更低的值将增加或降低速度限制上限。</string>
<string name="cpu_accuracy">CPU 精度</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
<string name="use_docked_mode">主机模式</string>
- <string name="use_docked_mode_description">以主机模式进行模拟,牺牲性能并提高画面分辨率。</string>
+ <string name="use_docked_mode_description">提高分辨率,但降低性能。禁用此项时使用掌机模式,降低分辨率并提高性能。</string>
<string name="emulated_region">模拟区域</string>
<string name="emulated_language">模拟语言</string>
<string name="select_rtc_date">选择日期</string>
<string name="select_rtc_time">选择时间</string>
- <string name="use_custom_rtc">启用自定义系统时钟</string>
- <string name="use_custom_rtc_description">此选项允许您设置与目前系统时间相独立的自定义系统时钟</string>
- <string name="set_custom_rtc">设置自定义系统时钟</string>
+ <string name="use_custom_rtc">自定义系统时间</string>
+ <string name="use_custom_rtc_description">此选项允许您设置与目前系统时间相独立的自定义系统时钟。</string>
+ <string name="set_custom_rtc">设置自定义系统时间</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">精度等级</string>
- <string name="renderer_resolution">分辨率</string>
+ <string name="renderer_resolution">分辨率 (掌机模式/主机模式)</string>
<string name="renderer_vsync">垂直同步模式</string>
+ <string name="renderer_screen_layout">屏幕方向</string>
<string name="renderer_aspect_ratio">屏幕纵横比</string>
<string name="renderer_scaling_filter">窗口滤镜</string>
<string name="renderer_anti_aliasing">抗锯齿方式</string>
@@ -143,12 +187,23 @@
<string name="renderer_force_max_clock_description">强制 GPU 以最大时钟运行 (仍被温控限制)。</string>
<string name="renderer_asynchronous_shaders">使用异步着色器</string>
<string name="renderer_asynchronous_shaders_description">异步编译着色器,减少卡顿,但可能引入故障。</string>
- <string name="renderer_debug">启用图形调试</string>
- <string name="renderer_debug_description">启用时,图形 API 将进入较慢的调试模式。</string>
- <string name="use_disk_shader_cache">使用磁盘着色器缓存</string>
- <string name="use_disk_shader_cache_description">将生成的着色器缓存于磁盘中并进行读取以减少卡顿。</string>
+ <string name="renderer_reactive_flushing">启用反应性刷新</string>
+ <string name="renderer_reactive_flushing_description">牺牲性能,提高某些游戏的渲染精度。</string>
+ <string name="use_disk_shader_cache">磁盘着色器缓存</string>
+ <string name="use_disk_shader_cache_description">将生成的着色器缓存于磁盘中并进行读取,以减少卡顿。</string>
+
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">CPU 调试</string>
+ <string name="cpu_debug_mode_description">将 CPU 设置为较慢的调试模式。</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">图形调试</string>
+ <string name="renderer_debug_description">将图形 API 设置为较慢的调试模式。</string>
+ <string name="fastmem">Fastmem</string>
<!-- Audio settings strings -->
+ <string name="audio_output_engine">输出引擎</string>
<string name="audio_volume">音量</string>
<string name="audio_volume_description">指定输出的音量。</string>
@@ -157,7 +212,9 @@
<string name="ini_saved">已保存设置</string>
<string name="gameid_saved">已保存 %1$s 的设置</string>
<string name="error_saving">保存 %1$s.ini 时出错: %2$s</string>
+ <string name="unimplemented_menu">未生效菜单</string>
<string name="loading">加载中…</string>
+ <string name="shutting_down">正在关闭…</string>
<string name="reset_setting_confirmation">您要将此设定重设为默认值吗?</string>
<string name="reset_to_default">恢复默认</string>
<string name="reset_all_settings">重置所有设置项?</string>
@@ -165,6 +222,14 @@
<string name="settings_reset">重设设置项</string>
<string name="close">关闭</string>
<string name="learn_more">了解更多</string>
+ <string name="auto">自动</string>
+ <string name="submit">提交</string>
+ <string name="string_null">无</string>
+ <string name="string_import">导入</string>
+ <string name="export">导出</string>
+ <string name="export_failed">导出失败</string>
+ <string name="import_failed">导入失败</string>
+ <string name="cancelling">取消中</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">选择 GPU 驱动程序</string>
@@ -172,6 +237,7 @@
<string name="select_gpu_driver_install">安装</string>
<string name="select_gpu_driver_default">系统默认</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>
@@ -182,10 +248,11 @@
<string name="preferences_graphics">图形</string>
<string name="preferences_audio">声音</string>
<string name="preferences_theme">主题和色彩</string>
+ <string name="preferences_debug">调试</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">您的 ROM 已加密</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[请参考指南重新转储你的<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">游戏卡带</a>或<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">已安装的游戏</a>。]]></string>
+ <string name="loader_error_encrypted_roms_description"><![CDATA[请按照指南重新转储您的<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards\">游戏卡带</a>或<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop\">已安装的游戏</a>。]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[请确保 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> 文件已安装,使得游戏可以被解密。]]></string>
<string name="loader_error_video_core">初始化视频核心时发生错误</string>
<string name="loader_error_video_core_description">这通常由不兼容的 GPU 驱动程序造成,安装自定义 GPU 驱动程序可能解决此问题。</string>
@@ -226,6 +293,9 @@
<string name="fatal_error">致命错误</string>
<string name="fatal_error_message">发生致命错误,请查阅日志获取详细信息。\n继续模拟可能会造成崩溃和错误。</string>
<string name="performance_warning">关闭此项会显著降低模拟性能!建议您将此项保持为启用状态。</string>
+ <string name="device_memory_inadequate">设备 RAM: %1$s\n推荐 RAM: %2$s</string>
+ <string name="memory_formatted">%1$s%2$s</string>
+ <string name="no_game_present">当前没有可启动的游戏!</string>
<!-- Region Names -->
<string name="region_japan">日本</string>
@@ -236,7 +306,14 @@
<string name="region_korea">韩国</string>
<string name="region_taiwan">中国台湾</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
@@ -274,6 +351,11 @@
<string name="anti_aliasing_fxaa">快速近似抗锯齿</string>
<string name="anti_aliasing_smaa">子像素形态学抗锯齿</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">横向大屏</string>
+ <string name="screen_layout_portrait">纵向屏幕</string>
+ <string name="screen_layout_auto">自动</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">默认 (16:9)</string>
<string name="ratio_force_four_three">强制 4:3</string>
@@ -303,13 +385,27 @@
<string name="theme_material_you">Material You</string>
<!-- Theme Modes -->
- <string name="change_theme_mode">主题模式</string>
+ <string name="change_theme_mode">更改主题模式</string>
<string name="theme_mode_follow_system">跟随系统</string>
<string name="theme_mode_light">浅色</string>
<string name="theme_mode_dark">深色</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">使用黑色背景</string>
<string name="use_black_backgrounds_description">使用深色主题时,套用黑色背景。</string>
-</resources>
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">画中画</string>
+ <string name="picture_in_picture_description">模拟器位于后台时最小化窗口</string>
+ <string name="pause">暂停</string>
+ <string name="play">开始</string>
+ <string name="mute">静音</string>
+ <string name="unmute">取消静音</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">许可证</string>
+ <string name="license_fidelityfx_fsr_description">来自 AMD 的高品质画质升级</string>
+ </resources>
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 4a21bf893..b8f468c68 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
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_disclaimer">此軟體可以執行 Nintendo Switch 主機遊戲,但不包含任何遊戲和金鑰。&lt;br /&gt;&lt;br /&gt;在您開始前,請找到放置於您的裝置儲存空間的 <![CDATA[<b> prod.keys </b>]]> 檔案。&lt;br /&gt;&lt;br /&gt;<![CDATA[<a href=\"https://yuzu-emu.org/help/quickstart\">深入瞭解</a>]]></string>
<string name="emulation_notification_channel_name">模擬進行中</string>
@@ -25,6 +25,7 @@
<string name="back">上一步</string>
<string name="add_games">新增遊戲</string>
<string name="add_games_description">選取您的遊戲資料夾</string>
+ <string name="step_complete">完成!</string>
<!-- Home strings -->
<string name="home_games">遊戲</string>
@@ -33,11 +34,12 @@
<string name="empty_gamelist">找不到檔案,或者尚未選取遊戲目錄。</string>
<string name="search_and_filter_games">搜尋並篩選遊戲</string>
<string name="select_games_folder">選取遊戲資料夾</string>
- <string name="select_games_folder_description">一律允許 yuzu 填入遊戲清單</string>
+ <string name="select_games_folder_description">允許 yuzu 填入遊戲清單</string>
<string name="add_games_warning">跳過選取遊戲資料夾?</string>
<string name="add_games_warning_description">如果資料夾未選取,遊戲將不會顯示在遊戲清單。</string>
<string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
<string name="home_search_games">搜尋遊戲</string>
+ <string name="search_settings">搜索设置</string>
<string name="games_dir_selected">遊戲目錄已選取</string>
<string name="install_prod_keys">安裝 prod.keys</string>
<string name="install_prod_keys_description">需要解密零售遊戲</string>
@@ -60,13 +62,16 @@
<string name="install_amiibo_keys_description">需要在遊戲中使用 Amiibo</string>
<string name="invalid_keys_file">無效的金鑰檔案已選取</string>
<string name="install_keys_success">金鑰已成功安裝</string>
- <string name="reading_keys_failure">讀取加密金鑰時出現錯誤</string>
+ <string name="reading_keys_failure">讀取加密金鑰時發生錯誤</string>
+ <string name="install_prod_keys_failure_extension_description">驗證您的金鑰檔案是否具有 .keys 副檔名並再試一次。</string>
+ <string name="install_amiibo_keys_failure_extension_description">驗證您的金鑰檔案是否具有 .bin 副檔名並再試一次。</string>
<string name="invalid_keys_error">無效的加密金鑰</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">選取的檔案不正確或已損毀,請重新傾印您的金鑰。</string>
<string name="install_gpu_driver">安裝 GPU 驅動程式</string>
<string name="install_gpu_driver_description">安裝替代驅動程式以取得潛在的更佳效能或準確度</string>
<string name="advanced_settings">進階設定</string>
+ <string name="advanced_settings_game">高级选项: %1$s</string>
<string name="settings_description">進行模擬器設定</string>
<string name="search_recently_played">最近遊玩</string>
<string name="search_recently_added">最近新增</string>
@@ -86,6 +91,33 @@
<string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲標題 ID。</string>
<string name="import_saves">匯入</string>
<string name="export_saves">匯出</string>
+ <string name="install_firmware">安裝韌體</string>
+ <string name="install_firmware_description">韌體必須為 ZIP 封存檔,將會用於部分遊戲的啟動</string>
+ <string name="firmware_installing">正在安裝韌體</string>
+ <string name="firmware_installed_success">韌體已成功安裝</string>
+ <string name="firmware_installed_failure">韌體安裝失敗</string>
+ <string name="firmware_installed_failure_description">请确保固件 nca 文件位于 zip 压缩包的根目录,然后重试。</string>
+ <string name="share_log">分享偵錯記錄</string>
+ <string name="share_log_description">分享 yuzu 的記錄檔以便對相關問題進行偵錯</string>
+ <string name="share_log_missing">找不到記錄檔</string>
+ <string name="install_game_content">安裝遊戲內容</string>
+ <string name="install_game_content_description">安裝遊戲更新或 DLC</string>
+ <string name="installing_game_content">安装中...</string>
+ <string name="install_game_content_failure">向 NAND 安装文件时失败</string>
+ <string name="install_game_content_failure_description">请确保附加内容的有效性,并且 prod.keys 密钥文件已安装。</string>
+ <string name="install_game_content_failure_base">为避免产生冲突,此功能不能用于安装游戏本体。</string>
+ <string name="install_game_content_failure_file_extension">只有 NSP 或 XCI 格式的附加内容可以安装。请确保您的游戏附加内容是有效的。</string>
+ <string name="install_game_content_failed_count">%1$d 安装出错</string>
+ <string name="install_game_content_success">游戏附加内容已成功安装</string>
+ <string name="install_game_content_success_install">%1$d 安装成功</string>
+ <string name="install_game_content_success_overwrite">%1$d 覆盖安装成功</string>
+ <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
+ <string name="custom_driver_not_supported">不支持自定义驱动</string>
+ <string name="custom_driver_not_supported_description">此设备不支持自定义驱动。\n请之后再访问此项,查看是否已为此设备添加支持。</string>
+ <string name="manage_yuzu_data">管理 yuzu 数据</string>
+ <string name="manage_yuzu_data_description">导入/导出固件、密钥、用户数据及其他。</string>
+ <string name="share_save_file">分享存档文件</string>
+ <string name="export_save_failed">导出存档文件失败</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia 不真實</string>
@@ -94,7 +126,18 @@
<string name="contributors">參與者</string>
<string name="contributors_description">使用來自 yuzu 團隊的 \u2764 製作</string>
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
+ <string name="licenses_description">這些專案使 yuzu Android 版成為可能</string>
<string name="build">組建</string>
+ <string name="user_data">用户数据</string>
+ <string name="user_data_description">导入/导出应用程序所有数据。\n\n导入用户数据时,将删除当前所有的用户数据!</string>
+ <string name="exporting_user_data">正在导出用户数据...</string>
+ <string name="importing_user_data">正在导入用户数据...</string>
+ <string name="import_user_data">导入用户数据</string>
+ <string name="invalid_yuzu_backup">无效的 yuzu 备份</string>
+ <string name="user_data_export_success">导出用户数据成功</string>
+ <string name="user_data_import_success">导入用户数据成功</string>
+ <string name="user_data_export_cancelled">已取消导出数据</string>
+ <string name="user_data_import_failed_description">请确保用户数据文件夹位于 zip 压缩包的根目录,并在 config/config.ini 路径中包含配置文件,然后重试。</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>
@@ -114,28 +157,29 @@
<string name="are_you_interested">您仍感興趣嗎?</string>
<!-- General settings strings -->
- <string name="frame_limit_enable">啟用限制速度</string>
- <string name="frame_limit_enable_description">若啟用,模擬速度將會限制在標準速度的指定百分比。</string>
+ <string name="frame_limit_enable">限制速度</string>
+ <string name="frame_limit_enable_description">將模擬速度限制在標準速度的指定百分比。</string>
<string name="frame_limit_slider">限制速度百分比</string>
- <string name="frame_limit_slider_description">指定限制模擬速度的百分比。預設為 100%,模擬速度將被限制為標準速度。更高或更低的值將會增加或減少速度限制。</string>
+ <string name="frame_limit_slider_description">指定限制模擬速度的百分比。100% 為標準速度,更高或更低的值將會增加或減少速度限制。</string>
<string name="cpu_accuracy">CPU 準確度</string>
+ <string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
<string name="use_docked_mode">底座模式</string>
- <string name="use_docked_mode_description">以底座模式模擬,以犧牲效能的代價提高解析度。</string>
+ <string name="use_docked_mode_description">提高解析度,降低效能。停用後將會使用手提模式,會降低解析度並提高效能。</string>
<string name="emulated_region">模擬區域</string>
<string name="emulated_language">模擬語言</string>
<string name="select_rtc_date">選取 RTC 日期</string>
<string name="select_rtc_time">選取 RTC 時間</string>
- <string name="use_custom_rtc">啟用自訂 RTC</string>
- <string name="use_custom_rtc_description">此設定允許您設定與您的目前系統時間相互獨立的自訂即時時鐘</string>
+ <string name="use_custom_rtc">自訂 RTC</string>
+ <string name="use_custom_rtc_description">允許您設定與您的目前系統時間相互獨立的自訂即時時鐘。</string>
<string name="set_custom_rtc">設定自訂 RTC</string>
<!-- Graphics settings strings -->
- <string name="renderer_api">API</string>
<string name="renderer_accuracy">準確度層級</string>
- <string name="renderer_resolution">解析度</string>
+ <string name="renderer_resolution">解析度 (手提/底座)</string>
<string name="renderer_vsync">VSync 模式</string>
+ <string name="renderer_screen_layout">屏幕方向</string>
<string name="renderer_aspect_ratio">長寬比</string>
<string name="renderer_scaling_filter">視窗適應過濾器</string>
<string name="renderer_anti_aliasing">消除鋸齒方法</string>
@@ -143,12 +187,23 @@
<string name="renderer_force_max_clock_description">強制 GPU 以最大可能時脈執行 (熱溫限制仍被套用)。</string>
<string name="renderer_asynchronous_shaders">使用非同步著色器</string>
<string name="renderer_asynchronous_shaders_description">非同步編譯著色器,將會減少間斷,但可能會引入故障。</string>
- <string name="renderer_debug">啟用圖形偵錯</string>
- <string name="renderer_debug_description">核取時,圖形 API 將會進入慢速偵錯模式。</string>
- <string name="use_disk_shader_cache">使用磁碟著色器快取</string>
+ <string name="renderer_reactive_flushing">使用重新啟用排清</string>
+ <string name="renderer_reactive_flushing_description">犧牲效能,以改善部分遊戲的轉譯準確度。</string>
+ <string name="use_disk_shader_cache">磁碟著色器快取</string>
<string name="use_disk_shader_cache_description">透過將產生的著色器儲存並載入至磁碟,減少中斷。</string>
+ <!-- Debug settings strings -->
+ <string name="cpu">CPU</string>
+ <string name="cpu_debug_mode">CPU 调试</string>
+ <string name="cpu_debug_mode_description">将 CPU 设置为较慢的调试模式。</string>
+ <string name="gpu">GPU</string>
+ <string name="renderer_api">API</string>
+ <string name="renderer_debug">圖形偵錯</string>
+ <string name="renderer_debug_description">將圖形 API 設為慢速偵錯模式。</string>
+ <string name="fastmem">Fastmem</string>
+
<!-- Audio settings strings -->
+ <string name="audio_output_engine">输出引擎</string>
<string name="audio_volume">音量</string>
<string name="audio_volume_description">指定音訊輸出音量。</string>
@@ -157,7 +212,9 @@
<string name="ini_saved">已儲存設定</string>
<string name="gameid_saved">已儲存 %1$s 設定</string>
<string name="error_saving">儲存 %1$s 時發生錯誤 ini: %2$s</string>
+ <string name="unimplemented_menu">未生效菜单</string>
<string name="loading">正在載入…</string>
+ <string name="shutting_down">正在关闭…</string>
<string name="reset_setting_confirmation">要將此設定重設回預設值嗎?</string>
<string name="reset_to_default">重設為預設值</string>
<string name="reset_all_settings">重設所有設定?</string>
@@ -165,6 +222,14 @@
<string name="settings_reset">設定已重設</string>
<string name="close">關閉</string>
<string name="learn_more">深入瞭解</string>
+ <string name="auto">自動</string>
+ <string name="submit">提交</string>
+ <string name="string_null">無</string>
+ <string name="string_import">匯入</string>
+ <string name="export">匯出</string>
+ <string name="export_failed">导出失败</string>
+ <string name="import_failed">导入失败</string>
+ <string name="cancelling">取消中</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">選取 GPU 驅動程式</string>
@@ -172,6 +237,7 @@
<string name="select_gpu_driver_install">安裝</string>
<string name="select_gpu_driver_default">預設</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>
@@ -182,10 +248,11 @@
<string name="preferences_graphics">圖形</string>
<string name="preferences_audio">音訊</string>
<string name="preferences_theme">主題和色彩</string>
+ <string name="preferences_debug">偵錯</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">您的 ROM 已加密</string>
- <string name="loader_error_encrypted_roms_description"><![CDATA[請依循指南重新傾印您的<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games\">遊戲卡匣</a>或<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop\">安裝標題</a>。]]></string>
+ <string name="loader_error_encrypted_roms_description"><![CDATA[请按照指南重新转储您的<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards\">游戏卡带</a>或<a href=\"https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop\">已安装的游戏</a>。]]></string>
<string name="loader_error_encrypted_keys_description"><![CDATA[請確保您的 <a href=\"https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> 檔案已安裝,讓遊戲可以解密。]]></string>
<string name="loader_error_video_core">初始化視訊核心時發生錯誤</string>
<string name="loader_error_video_core_description">這經常由不相容的 GPU 驅動程式造成,安裝自訂 GPU 驅動程式可能會解決此問題。</string>
@@ -219,13 +286,16 @@
<!-- Errors and warnings -->
<string name="abort_button">中止</string>
<string name="continue_button">繼續</string>
- <string name="system_archive_not_found">找不到系統檔案</string>
+ <string name="system_archive_not_found">找不到系統封存</string>
<string name="system_archive_not_found_message">%s 遺失,請傾印您的系統封存。\n繼續模擬可能會造成當機和錯誤。</string>
<string name="system_archive_general">系統封存</string>
<string name="save_load_error">儲存/載入發生錯誤</string>
<string name="fatal_error">嚴重錯誤</string>
<string name="fatal_error_message">發生嚴重錯誤,檢查記錄以取得詳細資訊。\n繼續模擬可能會造成當機和錯誤。</string>
<string name="performance_warning">關閉此設定會顯著降低模擬效能!如需最佳體驗,建議您將此設定保持為啟用狀態。</string>
+ <string name="device_memory_inadequate">设备 RAM: %1$s\n推荐 RAM: %2$s</string>
+ <string name="memory_formatted">%1$s%2$s</string>
+ <string name="no_game_present">当前没有可启动的游戏!</string>
<!-- Region Names -->
<string name="region_japan">日本</string>
@@ -236,7 +306,14 @@
<string name="region_korea">南韓</string>
<string name="region_taiwan">台灣</string>
- <!-- Language Names -->
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">英國</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
@@ -274,14 +351,20 @@
<string name="anti_aliasing_fxaa">FXAA</string>
<string name="anti_aliasing_smaa">SMAA</string>
+ <!-- Screen Layouts -->
+ <string name="screen_layout_landscape">横向大屏</string>
+ <string name="screen_layout_portrait">纵向屏幕</string>
+ <string name="screen_layout_auto">自動</string>
+
<!-- Aspect Ratios -->
<string name="ratio_default">預設 (16:9)</string>
<string name="ratio_force_four_three">強制 4:3</string>
<string name="ratio_force_twenty_one_nine">強制 21:9</string>
<string name="ratio_force_sixteen_ten">強制 16:10</string>
- <string name="ratio_stretch">延伸視窗</string>
+ <string name="ratio_stretch">延展視窗</string>
<!-- CPU Accuracy -->
+ <string name="cpu_accuracy_accurate">高精度</string>
<string name="cpu_accuracy_unsafe">低精度</string>
<string name="cpu_accuracy_paranoid">不合理 (慢)</string>
@@ -307,8 +390,22 @@
<string name="theme_mode_light">淺色</string>
<string name="theme_mode_dark">深色</string>
+ <!-- Audio output engines -->
+ <string name="cubeb">cubeb</string>
+
<!-- Black backgrounds theme -->
- <string name="use_black_backgrounds">使用黑色背景</string>
+ <string name="use_black_backgrounds">黑色背景</string>
<string name="use_black_backgrounds_description">使用深色主題時,套用黑色背景。</string>
-</resources>
+ <!-- Picture-In-Picture -->
+ <string name="picture_in_picture">画中画</string>
+ <string name="picture_in_picture_description">模拟器位于后台时最小化窗口</string>
+ <string name="pause">暂停</string>
+ <string name="play">开始</string>
+ <string name="mute">靜音</string>
+ <string name="unmute">取消靜音</string>
+
+ <!-- Licenses screen strings -->
+ <string name="licenses">授權</string>
+ <string name="license_fidelityfx_fsr_description">來自 AMD 的升級圖像品質</string>
+ </resources>
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index dc10159c9..ab435dce9 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -2,7 +2,6 @@
<resources>
<string-array name="regionNames">
- <item>@string/auto</item>
<item>@string/region_australia</item>
<item>@string/region_china</item>
<item>@string/region_europe</item>
@@ -13,7 +12,6 @@
</string-array>
<integer-array name="regionValues">
- <item>-1</item>
<item>3</item>
<item>4</item>
<item>2</item>
@@ -177,6 +175,24 @@
<item>2</item>
</integer-array>
+ <string-array name="cpuBackendArm64Names">
+ <item>@string/cpu_backend_dynarmic</item>
+ <item>@string/cpu_backend_nce</item>
+ </string-array>
+
+ <integer-array name="cpuBackendArm64Values">
+ <item>0</item>
+ <item>1</item>
+ </integer-array>
+
+ <string-array name="cpuBackendX86Names">
+ <item>@string/cpu_backend_dynarmic</item>
+ </string-array>
+
+ <integer-array name="cpuBackendX86Values">
+ <item>0</item>
+ </integer-array>
+
<string-array name="cpuAccuracyNames">
<item>@string/auto</item>
<item>@string/cpu_accuracy_accurate</item>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index ef855ea6f..380d14213 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -13,7 +13,7 @@
<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_bottom_list_fab">76dp</dimen>
<dimen name="spacing_fab">24dp</dimen>
<dimen name="dialog_margin">20dp</dimen>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 9e4854221..a6ccef8a1 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -38,6 +38,7 @@
<string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
<string name="search_and_filter_games">Search and filter games</string>
<string name="select_games_folder">Select games folder</string>
+ <string name="manage_game_folders">Manage game folders</string>
<string name="select_games_folder_description">Allows yuzu to populate the games list</string>
<string name="add_games_warning">Skip selecting games folder?</string>
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
@@ -72,7 +73,7 @@
<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="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>
@@ -91,6 +92,7 @@
<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="save_files_exporting">Exporting save files…</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>
@@ -123,6 +125,29 @@
<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>
+ <string name="game_folders">Game folders</string>
+ <string name="deep_scan">Deep scan</string>
+ <string name="add_game_folder">Add game folder</string>
+ <string name="folder_already_added">This folder was already added!</string>
+ <string name="game_folder_properties">Game folder properties</string>
+
+ <!-- Applet launcher strings -->
+ <string name="applets">Applet launcher</string>
+ <string name="applets_description">Launch system applets using installed firmware</string>
+ <string name="applets_error_firmware">Firmware not installed</string>
+ <string name="applets_error_applet">Applet not available</string>
+ <string name="applets_error_description"><![CDATA[Please ensure your <a href="https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys">prod.keys</a> file and <a href="https://yuzu-emu.org/help/quickstart/#dumping-system-firmware">firmware</a> are installed and try again.]]></string>
+ <string name="album_applet">Album</string>
+ <string name="album_applet_description">See images stored in the user screenshots folder with the system photo viewer</string>
+ <string name="mii_edit_applet">Mii edit</string>
+ <string name="mii_edit_applet_description">View and edit Miis with the system editor</string>
+ <string name="cabinet_applet">Cabinet</string>
+ <string name="cabinet_applet_description">Edit and delete data stored on amiibo</string>
+ <string name="cabinet_launcher">Cabinet launcher</string>
+ <string name="cabinet_nickname_and_owner">Nickname and owner settings</string>
+ <string name="cabinet_game_data_eraser">Game data eraser</string>
+ <string name="cabinet_restorer">Restorer</string>
+ <string name="cabinet_formatter">Formatter</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia isn\'t real</string>
@@ -166,6 +191,7 @@
<string name="frame_limit_enable_description">Limits emulation speed to a specified percentage of normal speed.</string>
<string name="frame_limit_slider">Limit speed percent</string>
<string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string>
+ <string name="cpu_backend">CPU backend</string>
<string name="cpu_accuracy">CPU accuracy</string>
<string name="value_with_units">%1$s%2$s</string>
@@ -222,6 +248,7 @@
<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_to_default_description">Resets all advanced settings</string>
<string name="reset_all_settings">Reset all settings?</string>
<string name="reset_all_settings_description">All advanced settings will be reset to their default configuration. This can not be undone.</string>
<string name="settings_reset">Settings reset</string>
@@ -237,6 +264,8 @@
<string name="cancelling">Cancelling</string>
<string name="install">Install</string>
<string name="delete">Delete</string>
+ <string name="edit">Edit</string>
+ <string name="export_success">Exported successfully</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
@@ -253,10 +282,14 @@
<string name="preferences_settings">Settings</string>
<string name="preferences_general">General</string>
<string name="preferences_system">System</string>
+ <string name="preferences_system_description">Docked mode, region, language</string>
<string name="preferences_graphics">Graphics</string>
+ <string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
<string name="preferences_audio">Audio</string>
+ <string name="preferences_audio_description">Output engine, volume</string>
<string name="preferences_theme">Theme and color</string>
<string name="preferences_debug">Debug</string>
+ <string name="preferences_debug_description">CPU/GPU debugging, graphics API, fastmem</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Your ROM is encrypted</string>
@@ -391,6 +424,10 @@
<string name="ratio_force_sixteen_ten">Force 16:10</string>
<string name="ratio_stretch">Stretch to window</string>
+ <!-- CPU Backend -->
+ <string name="cpu_backend_dynarmic">Dynarmic (Slow)</string>
+ <string name="cpu_backend_nce">Native code execution (NCE)</string>
+
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Accurate</string>
<string name="cpu_accuracy_unsafe">Unsafe</string>
diff --git a/src/android/app/src/main/res/xml/locales_config.xml b/src/android/app/src/main/res/xml/locales_config.xml
deleted file mode 100644
index 51b88d9dc..000000000
--- a/src/android/app/src/main/res/xml/locales_config.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
- <locale android:name="en" /> <!-- English (default) -->
- <locale android:name="de" /> <!-- German -->
- <locale android:name="es" /> <!-- Spanish -->
- <locale android:name="fr" /> <!-- French -->
- <locale android:name="it" /> <!-- Italian -->
- <locale android:name="ja" /> <!-- Japanese -->
- <locale android:name="nb" /> <!-- Norwegian Bokmal -->
- <locale android:name="pl" /> <!-- Polish -->
- <locale android:name="pt-rBR" /> <!-- Portuguese (Brazil) -->
- <locale android:name="pt-RPT" /> <!-- Portuguese (Portugal) -->
- <locale android:name="ru" /> <!-- Russian -->
- <locale android:name="uk" /> <!-- Ukranian -->
- <locale android:name="zh-rCN" /> <!-- Chinese (China) -->
- <locale android:name="zh-rTW" /> <!-- Chinese (Taiwan) -->
-</locale-config>
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp
index 2c16d3769..e2b9eb566 100644
--- a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp
+++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp
@@ -1,107 +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
+// 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_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp
index 2084de128..75f0fb9ad 100644
--- a/src/audio_core/adsp/apps/opus/opus_decoder.cpp
+++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp
@@ -30,9 +30,9 @@ bool IsValidMultiStreamChannelCount(u32 channel_count) {
return channel_count <= OpusStreamCountMax;
}
-bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) {
+bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 stereo_stream_count) {
return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
- sterero_stream_count > 0 && sterero_stream_count <= total_stream_count;
+ stereo_stream_count >= 0 && stereo_stream_count <= total_stream_count;
}
} // namespace
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
index f6d362e68..05cf3975d 100644
--- a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
+++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
@@ -1,111 +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
+// 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 && static_cast<s32>(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/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp
index 5b23fce14..b7fed5304 100644
--- a/src/audio_core/opus/decoder.cpp
+++ b/src/audio_core/opus/decoder.cpp
@@ -1,179 +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
+// 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_TRACE(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
index d08d8a4a4..fd728958a 100644
--- a/src/audio_core/opus/decoder.h
+++ b/src/audio_core/opus/decoder.h
@@ -1,53 +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
+// 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
index 4a5382973..1464880a1 100644
--- a/src/audio_core/opus/decoder_manager.cpp
+++ b/src/audio_core/opus/decoder_manager.cpp
@@ -1,102 +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
+// 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 && static_cast<s32>(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
index 466e1967b..70ebc4bab 100644
--- a/src/audio_core/opus/decoder_manager.h
+++ b/src/audio_core/opus/decoder_manager.h
@@ -1,38 +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
+// 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
index d6544dcb0..5ff71ab2d 100644
--- a/src/audio_core/opus/hardware_opus.cpp
+++ b/src/audio_core/opus/hardware_opus.cpp
@@ -1,241 +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
+// 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
index 7013a6b40..b10184baa 100644
--- a/src/audio_core/opus/hardware_opus.h
+++ b/src/audio_core/opus/hardware_opus.h
@@ -1,45 +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
+// 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/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index bbb598bc5..51a23fe15 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -146,7 +146,7 @@ public:
return;
}
- paused = true;
+ SignalPause();
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
}
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index 7b89151de..96e0efce2 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -111,7 +111,7 @@ public:
if (device == 0 || paused) {
return;
}
- paused = true;
+ SignalPause();
SDL_PauseAudioDevice(device, 1);
}
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index d66d04fae..2a09db599 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -282,11 +282,19 @@ u64 SinkStream::GetExpectedPlayedSampleCount() {
void SinkStream::WaitFreeSpace(std::stop_token stop_token) {
std::unique_lock lk{release_mutex};
release_cv.wait_for(lk, std::chrono::milliseconds(5),
- [this]() { return queued_buffers < max_queue_size; });
+ [this]() { return paused || queued_buffers < max_queue_size; });
if (queued_buffers > max_queue_size + 3) {
Common::CondvarWait(release_cv, lk, stop_token,
- [this] { return queued_buffers < max_queue_size; });
+ [this] { return paused || queued_buffers < max_queue_size; });
}
}
+void SinkStream::SignalPause() {
+ {
+ std::scoped_lock lk{release_mutex};
+ paused = true;
+ }
+ release_cv.notify_one();
+}
+
} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h
index 6a4996ca3..f2ccd19b8 100644
--- a/src/audio_core/sink/sink_stream.h
+++ b/src/audio_core/sink/sink_stream.h
@@ -214,6 +214,12 @@ public:
void WaitFreeSpace(std::stop_token stop_token);
protected:
+ /**
+ * Unblocks the ADSP if the stream is paused.
+ */
+ void SignalPause();
+
+protected:
/// Core system
Core::System& system;
/// Type of this stream
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index e216eb3de..b58a7073f 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -52,6 +52,7 @@ add_library(common STATIC
fiber.cpp
fiber.h
fixed_point.h
+ free_region_manager.h
fs/file.cpp
fs/file.h
fs/fs.cpp
@@ -166,6 +167,13 @@ if (WIN32)
target_link_libraries(common PRIVATE ntdll)
endif()
+if (NOT WIN32)
+ target_sources(common PRIVATE
+ signal_chain.cpp
+ signal_chain.h
+ )
+endif()
+
if(ANDROID)
target_sources(common
PRIVATE
@@ -174,6 +182,15 @@ if(ANDROID)
)
endif()
+if (UNIX AND NOT APPLE)
+ target_sources(common PRIVATE
+ linux/gamemode.cpp
+ linux/gamemode.h
+ )
+
+ target_link_libraries(common PRIVATE gamemode::headers)
+endif()
+
if(ARCHITECTURE_x86_64)
target_sources(common
PRIVATE
@@ -191,7 +208,7 @@ if(ARCHITECTURE_x86_64)
target_link_libraries(common PRIVATE xbyak::xbyak)
endif()
-if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
+if (HAS_NCE)
target_sources(common
PRIVATE
arm64/native_clock.cpp
diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp
index 88fdba527..f437d7187 100644
--- a/src/common/arm64/native_clock.cpp
+++ b/src/common/arm64/native_clock.cpp
@@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#ifdef ANDROID
+#include <sys/system_properties.h>
+#endif
#include "common/arm64/native_clock.h"
namespace Common::Arm64 {
@@ -65,7 +68,23 @@ bool NativeClock::IsNative() const {
u64 NativeClock::GetHostCNTFRQ() {
u64 cntfrq_el0 = 0;
- asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
+ std::string_view board{""};
+#ifdef ANDROID
+ char buffer[PROP_VALUE_MAX];
+ int len{__system_property_get("ro.product.board", buffer)};
+ board = std::string_view(buffer, static_cast<size_t>(len));
+#endif
+ if (board == "s5e9925") { // Exynos 2200
+ cntfrq_el0 = 25600000;
+ } else if (board == "exynos2100") { // Exynos 2100
+ cntfrq_el0 = 26000000;
+ } else if (board == "exynos9810") { // Exynos 9810
+ cntfrq_el0 = 26000000;
+ } else if (board == "s5e8825") { // Exynos 1280
+ cntfrq_el0 = 26000000;
+ } else {
+ asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
+ }
return cntfrq_el0;
}
diff --git a/src/common/free_region_manager.h b/src/common/free_region_manager.h
new file mode 100644
index 000000000..2e590d609
--- /dev/null
+++ b/src/common/free_region_manager.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+#include <boost/icl/interval_set.hpp>
+
+namespace Common {
+
+class FreeRegionManager {
+public:
+ explicit FreeRegionManager() = default;
+ ~FreeRegionManager() = default;
+
+ void SetAddressSpace(void* start, size_t size) {
+ this->FreeBlock(start, size);
+ }
+
+ std::pair<void*, size_t> FreeBlock(void* block_ptr, size_t size) {
+ std::scoped_lock lk(m_mutex);
+
+ // Check to see if we are adjacent to any regions.
+ auto start_address = reinterpret_cast<uintptr_t>(block_ptr);
+ auto end_address = start_address + size;
+ auto it = m_free_regions.find({start_address - 1, end_address + 1});
+
+ // If we are, join with them, ensuring we stay in bounds.
+ if (it != m_free_regions.end()) {
+ start_address = std::min(start_address, it->lower());
+ end_address = std::max(end_address, it->upper());
+ }
+
+ // Free the relevant region.
+ m_free_regions.insert({start_address, end_address});
+
+ // Return the adjusted pointers.
+ block_ptr = reinterpret_cast<void*>(start_address);
+ size = end_address - start_address;
+ return {block_ptr, size};
+ }
+
+ void AllocateBlock(void* block_ptr, size_t size) {
+ std::scoped_lock lk(m_mutex);
+
+ auto address = reinterpret_cast<uintptr_t>(block_ptr);
+ m_free_regions.subtract({address, address + size});
+ }
+
+private:
+ std::mutex m_mutex;
+ boost::icl::interval_set<uintptr_t> m_free_regions;
+};
+
+} // namespace Common
diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp
index 298a79bac..1dd826a4a 100644
--- a/src/common/fs/fs_android.cpp
+++ b/src/common/fs/fs_android.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fs/fs_android.h"
+#include "common/string_util.h"
namespace Common::FS::Android {
@@ -28,28 +29,35 @@ void RegisterCallbacks(JNIEnv* env, jclass clazz) {
env->GetJavaVM(&g_jvm);
native_library = clazz;
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
+ F(JMethodID, JMethodName, Signature)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
F(JMethodID, JMethodName, Signature)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
F(JMethodID, JMethodName, Signature)
#define F(JMethodID, JMethodName, Signature) \
JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature);
+ ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS)
#undef F
#undef FS
#undef FR
+#undef FH
}
void UnRegisterCallbacks() {
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
#define F(JMethodID) JMethodID = nullptr;
+ ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS)
#undef F
#undef FS
#undef FR
+#undef FH
}
bool IsContentUri(const std::string& path) {
@@ -95,4 +103,29 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
#undef F
#undef FR
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
+ F(FunctionName, JMethodID, Caller)
+#define F(FunctionName, JMethodID, Caller) \
+ std::string FunctionName(const std::string& filepath) { \
+ if (JMethodID == nullptr) { \
+ return 0; \
+ } \
+ auto env = GetEnvForThread(); \
+ jstring j_filepath = env->NewStringUTF(filepath.c_str()); \
+ jstring j_return = \
+ static_cast<jstring>(env->Caller(native_library, JMethodID, j_filepath)); \
+ if (!j_return) { \
+ return {}; \
+ } \
+ const jchar* jchars = env->GetStringChars(j_return, nullptr); \
+ const jsize length = env->GetStringLength(j_return); \
+ const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length); \
+ const std::string converted_string = Common::UTF16ToUTF8(string_view); \
+ env->ReleaseStringChars(j_return, jchars); \
+ return converted_string; \
+ }
+ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
+#undef F
+#undef FH
+
} // namespace Common::FS::Android
diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h
index b441c2a12..2c9234313 100644
--- a/src/common/fs/fs_android.h
+++ b/src/common/fs/fs_android.h
@@ -17,19 +17,28 @@
"(Ljava/lang/String;)Z") \
V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z")
+#define ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(V) \
+ V(GetParentDirectory, get_parent_directory, CallStaticObjectMethod, "getParentDirectory", \
+ "(Ljava/lang/String;)Ljava/lang/String;") \
+ V(GetFilename, get_filename, CallStaticObjectMethod, "getFilename", \
+ "(Ljava/lang/String;)Ljava/lang/String;")
+
namespace Common::FS::Android {
static JavaVM* g_jvm = nullptr;
static jclass native_library = nullptr;
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
#define F(JMethodID) static jmethodID JMethodID = nullptr;
+ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS)
#undef F
#undef FS
#undef FR
+#undef FH
enum class OpenMode {
Read,
@@ -62,4 +71,10 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
#undef F
#undef FR
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(FunctionName)
+#define F(FunctionName) std::string FunctionName(const std::string& filepath);
+ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
+#undef F
+#undef FH
+
} // namespace Common::FS::Android
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 441c8af97..bcf447089 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -13,6 +13,7 @@
#define AMIIBO_DIR "amiibo"
#define CACHE_DIR "cache"
#define CONFIG_DIR "config"
+#define CRASH_DUMPS_DIR "crash_dumps"
#define DUMP_DIR "dump"
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index 0abd81a45..c3a81f9a9 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -119,6 +119,7 @@ public:
GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
+ GenerateYuzuPath(YuzuPath::CrashDumpsDir, yuzu_path / CRASH_DUMPS_DIR);
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
@@ -400,6 +401,16 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
}
std::string_view GetParentPath(std::string_view path) {
+ if (path.empty()) {
+ return path;
+ }
+
+#ifdef ANDROID
+ if (path[0] != '/') {
+ std::string path_string{path};
+ return FS::Android::GetParentDirectory(path_string);
+ }
+#endif
const auto name_bck_index = path.rfind('\\');
const auto name_fwd_index = path.rfind('/');
std::size_t name_index;
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 63801c924..2874ea738 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -15,6 +15,7 @@ enum class YuzuPath {
AmiiboDir, // Where Amiibo backups are stored.
CacheDir, // Where cached filesystem data is stored.
ConfigDir, // Where config files are stored.
+ CrashDumpsDir, // Where crash dumps are stored.
DumpDir, // Where dumped data is stored.
KeysDir, // Where key files are stored.
LoadDir, // Where cheat/mod files are stored.
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index ba22595e0..3a9ea6eb4 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -21,15 +21,18 @@
#include <boost/icl/interval_set.hpp>
#include <fcntl.h>
#include <sys/mman.h>
+#include <sys/random.h>
#include <unistd.h>
#include "common/scope_exit.h"
#endif // ^^^ Linux ^^^
#include <mutex>
+#include <random>
#include "common/alignment.h"
#include "common/assert.h"
+#include "common/free_region_manager.h"
#include "common/host_memory.h"
#include "common/logging/log.h"
@@ -141,7 +144,7 @@ public:
Release();
}
- void Map(size_t virtual_offset, size_t host_offset, size_t length) {
+ void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
std::unique_lock lock{placeholder_mutex};
if (!IsNiechePlaceholder(virtual_offset, length)) {
Split(virtual_offset, length);
@@ -160,7 +163,7 @@ public:
}
}
- void Protect(size_t virtual_offset, size_t length, bool read, bool write) {
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {
DWORD new_flags{};
if (read && write) {
new_flags = PAGE_READWRITE;
@@ -186,6 +189,11 @@ public:
}
}
+ void EnableDirectMappedAddress() {
+ // TODO
+ UNREACHABLE();
+ }
+
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@@ -353,6 +361,55 @@ private:
#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
+#ifdef ARCHITECTURE_arm64
+
+static void* ChooseVirtualBase(size_t virtual_size) {
+ constexpr uintptr_t Map39BitSize = (1ULL << 39);
+ constexpr uintptr_t Map36BitSize = (1ULL << 36);
+
+ // This is not a cryptographic application, we just want something random.
+ std::mt19937_64 rng;
+
+ // We want to ensure we are allocating at an address aligned to the L2 block size.
+ // For Qualcomm devices, we must also allocate memory above 36 bits.
+ const size_t lower = Map36BitSize / HugePageSize;
+ const size_t upper = (Map39BitSize - virtual_size) / HugePageSize;
+ const size_t range = upper - lower;
+
+ // Try up to 64 times to allocate memory at random addresses in the range.
+ for (int i = 0; i < 64; i++) {
+ // Calculate a possible location.
+ uintptr_t hint_address = ((rng() % range) + lower) * HugePageSize;
+
+ // Try to map.
+ // Note: we may be able to take advantage of MAP_FIXED_NOREPLACE here.
+ void* map_pointer =
+ mmap(reinterpret_cast<void*>(hint_address), virtual_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+
+ // If we successfully mapped, we're done.
+ if (reinterpret_cast<uintptr_t>(map_pointer) == hint_address) {
+ return map_pointer;
+ }
+
+ // Unmap if necessary, and try again.
+ if (map_pointer != MAP_FAILED) {
+ munmap(map_pointer, virtual_size);
+ }
+ }
+
+ return MAP_FAILED;
+}
+
+#else
+
+static void* ChooseVirtualBase(size_t virtual_size) {
+ return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+}
+
+#endif
+
class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
@@ -415,8 +472,7 @@ public:
}
}
#else
- virtual_base = static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
+ virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
throw std::bad_alloc{};
@@ -424,7 +480,7 @@ public:
madvise(virtual_base, virtual_size, MADV_HUGEPAGE);
#endif
- placeholders.add({0, virtual_size});
+ free_manager.SetAddressSpace(virtual_base, virtual_size);
good = true;
}
@@ -432,14 +488,29 @@ public:
Release();
}
- void Map(size_t virtual_offset, size_t host_offset, size_t length) {
- {
- std::scoped_lock lock{placeholder_mutex};
- placeholders.subtract({virtual_offset, virtual_offset + length});
+ void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
+ // Intersect the range with our address space.
+ AdjustMap(&virtual_offset, &length);
+
+ // We are removing a placeholder.
+ free_manager.AllocateBlock(virtual_base + virtual_offset, length);
+
+ // Deduce mapping protection flags.
+ int flags = PROT_NONE;
+ if (True(perms & MemoryPermission::Read)) {
+ flags |= PROT_READ;
}
+ if (True(perms & MemoryPermission::Write)) {
+ flags |= PROT_WRITE;
+ }
+#ifdef ARCHITECTURE_arm64
+ if (True(perms & MemoryPermission::Execute)) {
+ flags |= PROT_EXEC;
+ }
+#endif
- void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_FIXED, fd, host_offset);
+ void* ret = mmap(virtual_base + virtual_offset, length, flags, MAP_SHARED | MAP_FIXED, fd,
+ host_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
}
@@ -447,47 +518,54 @@ public:
// The method name is wrong. We're still talking about the virtual range.
// We don't want to unmap, we want to reserve this memory.
- {
- std::scoped_lock lock{placeholder_mutex};
- auto it = placeholders.find({virtual_offset - 1, virtual_offset + length + 1});
+ // Intersect the range with our address space.
+ AdjustMap(&virtual_offset, &length);
- if (it != placeholders.end()) {
- size_t prev_upper = virtual_offset + length;
- virtual_offset = std::min(virtual_offset, it->lower());
- length = std::max(it->upper(), prev_upper) - virtual_offset;
- }
-
- placeholders.add({virtual_offset, virtual_offset + length});
- }
+ // Merge with any adjacent placeholder mappings.
+ auto [merged_pointer, merged_size] =
+ free_manager.FreeBlock(virtual_base + virtual_offset, length);
- void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE,
+ void* ret = mmap(merged_pointer, merged_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
}
- void Protect(size_t virtual_offset, size_t length, bool read, bool write) {
- int flags = 0;
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {
+ // Intersect the range with our address space.
+ AdjustMap(&virtual_offset, &length);
+
+ int flags = PROT_NONE;
if (read) {
flags |= PROT_READ;
}
if (write) {
flags |= PROT_WRITE;
}
+#ifdef HAS_NCE
+ if (execute) {
+ flags |= PROT_EXEC;
+ }
+#endif
int ret = mprotect(virtual_base + virtual_offset, length, flags);
ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
}
+ void EnableDirectMappedAddress() {
+ virtual_base = nullptr;
+ }
+
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
+ u8* virtual_map_base{reinterpret_cast<u8*>(MAP_FAILED)};
private:
/// Release all resources in the object
void Release() {
- if (virtual_base != MAP_FAILED) {
- int ret = munmap(virtual_base, virtual_size);
+ if (virtual_map_base != MAP_FAILED) {
+ int ret = munmap(virtual_map_base, virtual_size);
ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno));
}
@@ -502,10 +580,29 @@ private:
}
}
- int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
+ void AdjustMap(size_t* virtual_offset, size_t* length) {
+ if (virtual_base != nullptr) {
+ return;
+ }
+
+ // If we are direct mapped, we want to make sure we are operating on a region
+ // that is in range of our virtual mapping.
+ size_t intended_start = *virtual_offset;
+ size_t intended_end = intended_start + *length;
+ size_t address_space_start = reinterpret_cast<size_t>(virtual_map_base);
+ size_t address_space_end = address_space_start + virtual_size;
+
+ if (address_space_start > intended_end || intended_start > address_space_end) {
+ *virtual_offset = 0;
+ *length = 0;
+ } else {
+ *virtual_offset = std::max(intended_start, address_space_start);
+ *length = std::min(intended_end, address_space_end) - *virtual_offset;
+ }
+ }
- boost::icl::interval_set<size_t> placeholders; ///< Mapped placeholders
- std::mutex placeholder_mutex; ///< Mutex for placeholders
+ int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
+ FreeRegionManager free_manager{};
};
#else // ^^^ Linux ^^^ vvv Generic vvv
@@ -518,11 +615,13 @@ public:
throw std::bad_alloc{};
}
- void Map(size_t virtual_offset, size_t host_offset, size_t length) {}
+ void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm) {}
void Unmap(size_t virtual_offset, size_t length) {}
- void Protect(size_t virtual_offset, size_t length, bool read, bool write) {}
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {}
+
+ void EnableDirectMappedAddress() {}
u8* backing_base{nullptr};
u8* virtual_base{nullptr};
@@ -535,15 +634,16 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
try {
// Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors.
- impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment),
- AlignUp(virtual_size, PageAlignment) +
- 3 * HugePageSize);
+ impl =
+ std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment),
+ AlignUp(virtual_size, PageAlignment) + HugePageSize);
backing_base = impl->backing_base;
virtual_base = impl->virtual_base;
if (virtual_base) {
- virtual_base += 2 * HugePageSize - 1;
- virtual_base -= reinterpret_cast<size_t>(virtual_base) & (HugePageSize - 1);
+ // Ensure the virtual base is aligned to the L2 block size.
+ virtual_base = reinterpret_cast<u8*>(
+ Common::AlignUp(reinterpret_cast<uintptr_t>(virtual_base), HugePageSize));
virtual_base_offset = virtual_base - impl->virtual_base;
}
@@ -562,7 +662,8 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
-void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) {
+void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
+ MemoryPermission perms) {
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(host_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
@@ -571,7 +672,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) {
if (length == 0 || !virtual_base || !impl) {
return;
}
- impl->Map(virtual_offset + virtual_base_offset, host_offset, length);
+ impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms);
}
void HostMemory::Unmap(size_t virtual_offset, size_t length) {
@@ -584,14 +685,22 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length) {
impl->Unmap(virtual_offset + virtual_base_offset, length);
}
-void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write) {
+void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write,
+ bool execute) {
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
ASSERT(virtual_offset + length <= virtual_size);
if (length == 0 || !virtual_base || !impl) {
return;
}
- impl->Protect(virtual_offset + virtual_base_offset, length, read, write);
+ impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute);
+}
+
+void HostMemory::EnableDirectMappedAddress() {
+ if (impl) {
+ impl->EnableDirectMappedAddress();
+ virtual_size += reinterpret_cast<uintptr_t>(virtual_base);
+ }
}
} // namespace Common
diff --git a/src/common/host_memory.h b/src/common/host_memory.h
index 447975ded..cebfacab2 100644
--- a/src/common/host_memory.h
+++ b/src/common/host_memory.h
@@ -4,11 +4,20 @@
#pragma once
#include <memory>
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/virtual_buffer.h"
namespace Common {
+enum class MemoryPermission : u32 {
+ Read = 1 << 0,
+ Write = 1 << 1,
+ ReadWrite = Read | Write,
+ Execute = 1 << 2,
+};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
+
/**
* A low level linear memory buffer, which supports multiple mappings
* Its purpose is to rebuild a given sparse memory layout, including mirrors.
@@ -31,11 +40,13 @@ public:
HostMemory(HostMemory&& other) noexcept;
HostMemory& operator=(HostMemory&& other) noexcept;
- void Map(size_t virtual_offset, size_t host_offset, size_t length);
+ void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms);
void Unmap(size_t virtual_offset, size_t length);
- void Protect(size_t virtual_offset, size_t length, bool read, bool write);
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute = false);
+
+ void EnableDirectMappedAddress();
[[nodiscard]] u8* BackingBasePointer() noexcept {
return backing_base;
diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp
new file mode 100644
index 000000000..8d3e2934a
--- /dev/null
+++ b/src/common/linux/gamemode.cpp
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <gamemode_client.h>
+
+#include "common/linux/gamemode.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+
+namespace Common::Linux {
+
+void StartGamemode() {
+ if (Settings::values.enable_gamemode) {
+ if (gamemode_request_start() < 0) {
+ LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string());
+ } else {
+ LOG_INFO(Frontend, "Started gamemode");
+ }
+ }
+}
+
+void StopGamemode() {
+ if (Settings::values.enable_gamemode) {
+ if (gamemode_request_end() < 0) {
+ LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string());
+ } else {
+ LOG_INFO(Frontend, "Stopped gamemode");
+ }
+ }
+}
+
+void SetGamemodeState(bool state) {
+ if (state) {
+ StartGamemode();
+ } else {
+ StopGamemode();
+ }
+}
+
+} // namespace Common::Linux
diff --git a/src/common/linux/gamemode.h b/src/common/linux/gamemode.h
new file mode 100644
index 000000000..b80646ae2
--- /dev/null
+++ b/src/common/linux/gamemode.h
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+namespace Common::Linux {
+
+/**
+ * Start the (Feral Interactive) Linux gamemode if it is installed and it is activated
+ */
+void StartGamemode();
+
+/**
+ * Stop the (Feral Interactive) Linux gamemode if it is installed and it is activated
+ */
+void StopGamemode();
+
+/**
+ * Start or stop the (Feral Interactive) Linux gamemode if it is installed and it is activated
+ * @param state The new state the gamemode should have
+ */
+void SetGamemodeState(bool state);
+
+} // namespace Common::Linux
diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp
index 7ed7690ee..fa3747782 100644
--- a/src/common/nvidia_flags.cpp
+++ b/src/common/nvidia_flags.cpp
@@ -25,6 +25,7 @@ void ConfigureNvidiaEnvironmentFlags() {
void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str()));
void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1"));
+ void(_putenv("__GL_THREADED_OPTIMIZATIONS=1"));
#endif
}
diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
index 4b1690269..166dc3dce 100644
--- a/src/common/page_table.cpp
+++ b/src/common/page_table.cpp
@@ -9,12 +9,12 @@ PageTable::PageTable() = default;
PageTable::~PageTable() noexcept = default;
-bool PageTable::BeginTraversal(TraversalEntry& out_entry, TraversalContext& out_context,
- u64 address) const {
+bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
+ Common::ProcessAddress address) const {
// Setup invalid defaults.
- out_entry.phys_addr = 0;
- out_entry.block_size = page_size;
- out_context.next_page = 0;
+ out_entry->phys_addr = 0;
+ out_entry->block_size = page_size;
+ out_context->next_page = 0;
// Validate that we can read the actual entry.
const auto page = address / page_size;
@@ -29,20 +29,20 @@ bool PageTable::BeginTraversal(TraversalEntry& out_entry, TraversalContext& out_
}
// Populate the results.
- out_entry.phys_addr = phys_addr + address;
- out_context.next_page = page + 1;
- out_context.next_offset = address + page_size;
+ out_entry->phys_addr = phys_addr + GetInteger(address);
+ out_context->next_page = page + 1;
+ out_context->next_offset = GetInteger(address) + page_size;
return true;
}
-bool PageTable::ContinueTraversal(TraversalEntry& out_entry, TraversalContext& context) const {
+bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const {
// Setup invalid defaults.
- out_entry.phys_addr = 0;
- out_entry.block_size = page_size;
+ out_entry->phys_addr = 0;
+ out_entry->block_size = page_size;
// Validate that we can read the actual entry.
- const auto page = context.next_page;
+ const auto page = context->next_page;
if (page >= backing_addr.size()) {
return false;
}
@@ -54,9 +54,9 @@ bool PageTable::ContinueTraversal(TraversalEntry& out_entry, TraversalContext& c
}
// Populate the results.
- out_entry.phys_addr = phys_addr + context.next_offset;
- context.next_page = page + 1;
- context.next_offset += page_size;
+ out_entry->phys_addr = phys_addr + context->next_offset;
+ context->next_page = page + 1;
+ context->next_offset += page_size;
return true;
}
diff --git a/src/common/page_table.h b/src/common/page_table.h
index e653d52ad..5340f7d86 100644
--- a/src/common/page_table.h
+++ b/src/common/page_table.h
@@ -6,6 +6,7 @@
#include <atomic>
#include "common/common_types.h"
+#include "common/typed_address.h"
#include "common/virtual_buffer.h"
namespace Common {
@@ -100,9 +101,9 @@ struct PageTable {
PageTable(PageTable&&) noexcept = default;
PageTable& operator=(PageTable&&) noexcept = default;
- bool BeginTraversal(TraversalEntry& out_entry, TraversalContext& out_context,
- u64 address) const;
- bool ContinueTraversal(TraversalEntry& out_entry, TraversalContext& context) const;
+ bool BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
+ Common::ProcessAddress address) const;
+ bool ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const;
/**
* Resizes the page table to be able to accommodate enough pages within
@@ -117,6 +118,16 @@ struct PageTable {
return current_address_space_width_in_bits;
}
+ bool GetPhysicalAddress(Common::PhysicalAddress* out_phys_addr,
+ Common::ProcessAddress virt_addr) const {
+ if (virt_addr > (1ULL << this->GetAddressSpaceBits())) {
+ return false;
+ }
+
+ *out_phys_addr = backing_addr[virt_addr / page_size] + GetInteger(virt_addr);
+ return true;
+ }
+
/**
* Vector of memory pointers backing each page. An entry can only be non-null if the
* corresponding attribute element is of type `Memory`.
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 98b43e49c..4666bd0a0 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -41,6 +41,7 @@ SWITCHABLE(AspectRatio, true);
SWITCHABLE(AstcDecodeMode, true);
SWITCHABLE(AstcRecompression, true);
SWITCHABLE(AudioMode, true);
+SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
@@ -155,6 +156,22 @@ bool IsFastmemEnabled() {
return true;
}
+static bool is_nce_enabled = false;
+
+void SetNceEnabled(bool is_39bit) {
+ const bool is_nce_selected = values.cpu_backend.GetValue() == CpuBackend::Nce;
+ is_nce_enabled = IsFastmemEnabled() && is_nce_selected && is_39bit;
+ if (is_nce_selected && !is_nce_enabled) {
+ LOG_WARNING(
+ Common,
+ "Program does not utilize 39-bit address space, unable to natively execute code");
+ }
+}
+
+bool IsNceEnabled() {
+ return is_nce_enabled;
+}
+
bool IsDockedMode() {
return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
}
@@ -203,10 +220,12 @@ const char* TranslateCategory(Category category) {
case Category::Ui:
case Category::UiGeneral:
return "UI";
+ case Category::UiAudio:
+ return "UiAudio";
case Category::UiLayout:
- return "UiLayout";
+ return "UILayout";
case Category::UiGameList:
- return "UiGameList";
+ return "UIGameList";
case Category::Screenshots:
return "Screenshots";
case Category::Shortcuts:
@@ -217,6 +236,8 @@ const char* TranslateCategory(Category category) {
return "Services";
case Category::Paths:
return "Paths";
+ case Category::Linux:
+ return "Linux";
case Category::MaxEnum:
break;
}
diff --git a/src/common/settings.h b/src/common/settings.h
index 236e33bee..98341ad96 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -63,6 +63,7 @@ SWITCHABLE(AspectRatio, true);
SWITCHABLE(AstcDecodeMode, true);
SWITCHABLE(AstcRecompression, true);
SWITCHABLE(AudioMode, true);
+SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
@@ -153,7 +154,7 @@ struct Values {
true,
true};
Setting<bool, false> audio_muted{
- linkage, false, "audio_muted", Category::Audio, Specialization::Default, false, true};
+ linkage, false, "audio_muted", Category::Audio, Specialization::Default, true, true};
Setting<bool, false> dump_audio_commands{
linkage, false, "dump_audio_commands", Category::Audio, Specialization::Default, false};
@@ -179,6 +180,14 @@ struct Values {
&use_speed_limit};
// Cpu
+ SwitchableSetting<CpuBackend, true> cpu_backend{
+ linkage, CpuBackend::Dynarmic, CpuBackend::Dynarmic,
+#ifdef HAS_NCE
+ CpuBackend::Nce,
+#else
+ CpuBackend::Dynarmic,
+#endif
+ "cpu_backend", Category::Cpu};
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
CpuAccuracy::Auto, CpuAccuracy::Paranoid,
"cpu_accuracy", Category::Cpu};
@@ -232,7 +241,11 @@ struct Values {
SwitchableSetting<bool> use_asynchronous_gpu_emulation{
linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer};
SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage,
+#ifdef ANDROID
+ AstcDecodeMode::Cpu,
+#else
AstcDecodeMode::Gpu,
+#endif
AstcDecodeMode::Cpu,
AstcDecodeMode::CpuAsynchronous,
"accelerate_astc",
@@ -304,7 +317,11 @@ struct Values {
linkage, 0, "bg_blue", Category::Renderer, Specialization::Default, true, true};
SwitchableSetting<GpuAccuracy, true> gpu_accuracy{linkage,
+#ifdef ANDROID
+ GpuAccuracy::Normal,
+#else
GpuAccuracy::High,
+#endif
GpuAccuracy::Normal,
GpuAccuracy::Extreme,
"gpu_accuracy",
@@ -313,20 +330,38 @@ struct Values {
true,
true};
GpuAccuracy current_gpu_accuracy{GpuAccuracy::High};
- SwitchableSetting<AnisotropyMode, true> max_anisotropy{
- linkage, AnisotropyMode::Automatic, AnisotropyMode::Automatic, AnisotropyMode::X16,
- "max_anisotropy", Category::RendererAdvanced};
+ SwitchableSetting<AnisotropyMode, true> max_anisotropy{linkage,
+#ifdef ANDROID
+ AnisotropyMode::Default,
+#else
+ AnisotropyMode::Automatic,
+#endif
+ AnisotropyMode::Automatic,
+ AnisotropyMode::X16,
+ "max_anisotropy",
+ Category::RendererAdvanced};
SwitchableSetting<AstcRecompression, true> astc_recompression{linkage,
AstcRecompression::Uncompressed,
AstcRecompression::Uncompressed,
AstcRecompression::Bc3,
"astc_recompression",
Category::RendererAdvanced};
- SwitchableSetting<bool> async_presentation{linkage, false, "async_presentation",
- Category::RendererAdvanced};
+ SwitchableSetting<bool> async_presentation{linkage,
+#ifdef ANDROID
+ true,
+#else
+ false,
+#endif
+ "async_presentation", Category::RendererAdvanced};
SwitchableSetting<bool> renderer_force_max_clock{linkage, false, "force_max_clock",
Category::RendererAdvanced};
- SwitchableSetting<bool> use_reactive_flushing{linkage, true, "use_reactive_flushing",
+ SwitchableSetting<bool> use_reactive_flushing{linkage,
+#ifdef ANDROID
+ false,
+#else
+ true,
+#endif
+ "use_reactive_flushing",
Category::RendererAdvanced};
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererAdvanced};
@@ -358,6 +393,8 @@ struct Values {
Category::RendererDebug};
// TODO: remove this once AMDVLK supports VK_EXT_depth_bias_control
bool renderer_amdvlk_depth_bias_workaround{};
+ Setting<bool> disable_buffer_reorder{linkage, false, "disable_buffer_reorder",
+ Category::RendererDebug};
// System
SwitchableSetting<Language, true> language_index{linkage,
@@ -390,13 +427,20 @@ struct Values {
Setting<s32> current_user{linkage, 0, "current_user", Category::System};
SwitchableSetting<ConsoleMode> use_docked_mode{linkage,
+#ifdef ANDROID
+ ConsoleMode::Handheld,
+#else
ConsoleMode::Docked,
+#endif
"use_docked_mode",
Category::System,
Specialization::Radio,
true,
true};
+ // Linux
+ SwitchableSetting<bool> enable_gamemode{linkage, true, "enable_gamemode", Category::Linux};
+
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
@@ -505,7 +549,6 @@ struct Values {
linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false};
Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
Category::Debugging};
- Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging};
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
// Miscellaneous
@@ -535,6 +578,8 @@ bool IsGPULevelExtreme();
bool IsGPULevelHigh();
bool IsFastmemEnabled();
+void SetNceEnabled(bool is_64bit);
+bool IsNceEnabled();
bool IsDockedMode();
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 1800ab10d..344c04439 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -32,6 +32,7 @@ enum class Category : u32 {
AddOns,
Controls,
Ui,
+ UiAudio,
UiGeneral,
UiLayout,
UiGameList,
@@ -40,6 +41,7 @@ enum class Category : u32 {
Multiplayer,
Services,
Paths,
+ Linux,
MaxEnum,
};
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index 11429d7a8..d6351e57e 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -129,6 +129,8 @@ ENUM(ShaderBackend, Glsl, Glasm, SpirV);
ENUM(GpuAccuracy, Normal, High, Extreme);
+ENUM(CpuBackend, Dynarmic, Nce);
+
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
diff --git a/src/common/settings_input.cpp b/src/common/settings_input.cpp
index 0a6eea3cf..a6007e7b2 100644
--- a/src/common/settings_input.cpp
+++ b/src/common/settings_input.cpp
@@ -6,10 +6,11 @@
namespace Settings {
namespace NativeButton {
const std::array<const char*, NumButtons> mapping = {{
- "button_a", "button_b", "button_x", "button_y", "button_lstick",
- "button_rstick", "button_l", "button_r", "button_zl", "button_zr",
- "button_plus", "button_minus", "button_dleft", "button_dup", "button_dright",
- "button_ddown", "button_sl", "button_sr", "button_home", "button_screenshot",
+ "button_a", "button_b", "button_x", "button_y", "button_lstick",
+ "button_rstick", "button_l", "button_r", "button_zl", "button_zr",
+ "button_plus", "button_minus", "button_dleft", "button_dup", "button_dright",
+ "button_ddown", "button_slleft", "button_srleft", "button_home", "button_screenshot",
+ "button_slright", "button_srright",
}};
}
diff --git a/src/common/settings_input.h b/src/common/settings_input.h
index 46f38c703..53a95ef8f 100644
--- a/src/common/settings_input.h
+++ b/src/common/settings_input.h
@@ -29,12 +29,15 @@ enum Values : int {
DRight,
DDown,
- SL,
- SR,
+ SLLeft,
+ SRLeft,
Home,
Screenshot,
+ SLRight,
+ SRRight,
+
NumButtons,
};
diff --git a/src/common/signal_chain.cpp b/src/common/signal_chain.cpp
new file mode 100644
index 000000000..2e4fecc48
--- /dev/null
+++ b/src/common/signal_chain.cpp
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <dlfcn.h>
+
+#include "common/assert.h"
+#include "common/dynamic_library.h"
+#include "common/scope_exit.h"
+#include "common/signal_chain.h"
+
+namespace Common {
+
+template <typename T>
+T* LookupLibcSymbol(const char* name) {
+#if defined(__BIONIC__)
+ Common::DynamicLibrary provider("libc.so");
+ if (!provider.IsOpen()) {
+ UNREACHABLE_MSG("Failed to open libc!");
+ }
+#else
+ // For other operating environments, we assume the symbol is not overridden.
+ const char* base = nullptr;
+ Common::DynamicLibrary provider(base);
+#endif
+
+ void* sym = provider.GetSymbolAddress(name);
+ if (sym == nullptr) {
+ sym = dlsym(RTLD_DEFAULT, name);
+ }
+ if (sym == nullptr) {
+ UNREACHABLE_MSG("Unable to find symbol {}!", name);
+ }
+
+ return reinterpret_cast<T*>(sym);
+}
+
+int SigAction(int signum, const struct sigaction* act, struct sigaction* oldact) {
+ static auto libc_sigaction = LookupLibcSymbol<decltype(sigaction)>("sigaction");
+ return libc_sigaction(signum, act, oldact);
+}
+
+} // namespace Common
diff --git a/src/common/signal_chain.h b/src/common/signal_chain.h
new file mode 100644
index 000000000..8d06a1bd1
--- /dev/null
+++ b/src/common/signal_chain.h
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#ifndef _WIN32
+
+#include <signal.h>
+
+namespace Common {
+
+// Android's ART overrides sigaction with its own wrapper. This is problematic for SIGSEGV
+// in particular, because ART's handler accesses tpidr_el0, which conflicts with NCE.
+// This extracts the libc symbol and calls it directly.
+int SigAction(int signum, const struct sigaction* act, struct sigaction* oldact);
+
+} // namespace Common
+
+#endif
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 4c7aba3f5..72c481798 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -14,6 +14,10 @@
#include <windows.h>
#endif
+#ifdef ANDROID
+#include <common/fs/fs_android.h>
+#endif
+
namespace Common {
/// Make a string lowercase
@@ -63,6 +67,14 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
if (full_path.empty())
return false;
+#ifdef ANDROID
+ if (full_path[0] != '/') {
+ *_pPath = Common::FS::Android::GetParentDirectory(full_path);
+ *_pFilename = Common::FS::Android::GetFilename(full_path);
+ return true;
+ }
+#endif
+
std::size_t dir_end = full_path.find_last_of("/"
// windows needs the : included for something like just "C:" to be considered a directory
#ifdef _WIN32
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index caca9a123..012fdc1e0 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -10,7 +10,7 @@
#include "common/x64/rdtsc.h"
#endif
-#if defined(ARCHITECTURE_arm64) && defined(__linux__)
+#ifdef HAS_NCE
#include "common/arm64/native_clock.h"
#endif
@@ -68,7 +68,7 @@ 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__)
+#elif defined(HAS_NCE)
return std::make_unique<Arm64::NativeClock>();
#else
return std::make_unique<StandardWallClock>();
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index e4f499135..85583941c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -271,8 +271,9 @@ add_library(core STATIC
hle/kernel/k_page_heap.h
hle/kernel/k_page_group.cpp
hle/kernel/k_page_group.h
- hle/kernel/k_page_table.cpp
hle/kernel/k_page_table.h
+ hle/kernel/k_page_table_base.cpp
+ hle/kernel/k_page_table_base.h
hle/kernel/k_page_table_manager.h
hle/kernel/k_page_table_slab_heap.h
hle/kernel/k_port.cpp
@@ -280,6 +281,7 @@ add_library(core STATIC
hle/kernel/k_priority_queue.h
hle/kernel/k_process.cpp
hle/kernel/k_process.h
+ hle/kernel/k_process_page_table.h
hle/kernel/k_readable_event.cpp
hle/kernel/k_readable_event.h
hle/kernel/k_resource_limit.cpp
@@ -330,8 +332,6 @@ add_library(core STATIC
hle/kernel/physical_core.cpp
hle/kernel/physical_core.h
hle/kernel/physical_memory.h
- hle/kernel/process_capability.cpp
- hle/kernel/process_capability.h
hle/kernel/slab_helpers.h
hle/kernel/svc.cpp
hle/kernel/svc.h
@@ -521,17 +521,28 @@ add_library(core STATIC
hle/service/grc/grc.h
hle/service/hid/hid.cpp
hle/service/hid/hid.h
+ hle/service/hid/hid_debug_server.cpp
+ hle/service/hid/hid_debug_server.h
+ hle/service/hid/hid_firmware_settings.cpp
+ hle/service/hid/hid_firmware_settings.h
+ hle/service/hid/hid_server.cpp
+ hle/service/hid/hid_server.h
+ hle/service/hid/hid_system_server.cpp
+ hle/service/hid/hid_system_server.h
+ hle/service/hid/hid_util.h
hle/service/hid/hidbus.cpp
hle/service/hid/hidbus.h
hle/service/hid/irs.cpp
hle/service/hid/irs.h
hle/service/hid/irs_ring_lifo.h
+ hle/service/hid/resource_manager.cpp
+ hle/service/hid/resource_manager.h
hle/service/hid/ring_lifo.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
hle/service/hid/errors.h
- hle/service/hid/controllers/console_sixaxis.cpp
- hle/service/hid/controllers/console_sixaxis.h
+ hle/service/hid/controllers/console_six_axis.cpp
+ hle/service/hid/controllers/console_six_axis.h
hle/service/hid/controllers/controller_base.cpp
hle/service/hid/controllers/controller_base.h
hle/service/hid/controllers/debug_pad.cpp
@@ -546,6 +557,10 @@ add_library(core STATIC
hle/service/hid/controllers/npad.h
hle/service/hid/controllers/palma.cpp
hle/service/hid/controllers/palma.h
+ hle/service/hid/controllers/seven_six_axis.cpp
+ hle/service/hid/controllers/seven_six_axis.h
+ hle/service/hid/controllers/six_axis.cpp
+ hle/service/hid/controllers/six_axis.h
hle/service/hid/controllers/stubbed.cpp
hle/service/hid/controllers/stubbed.h
hle/service/hid/controllers/touchscreen.cpp
@@ -715,6 +730,7 @@ add_library(core STATIC
hle/service/nvnflinger/producer_listener.h
hle/service/nvnflinger/status.h
hle/service/nvnflinger/ui/fence.h
+ hle/service/nvnflinger/ui/graphic_buffer.cpp
hle/service/nvnflinger/ui/graphic_buffer.h
hle/service/nvnflinger/window.h
hle/service/olsc/olsc.cpp
@@ -910,6 +926,22 @@ if (ENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
endif()
+if (HAS_NCE)
+ enable_language(C ASM)
+ set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
+
+ target_sources(core PRIVATE
+ arm/nce/arm_nce.cpp
+ arm/nce/arm_nce.h
+ arm/nce/arm_nce.s
+ arm/nce/guest_context.h
+ arm/nce/patcher.cpp
+ arm/nce/patcher.h
+ arm/nce/instructions.h
+ )
+ target_link_libraries(core PRIVATE merry::oaknut)
+endif()
+
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_sources(core PRIVATE
arm/dynarmic/arm_dynarmic.h
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index 0c012f094..d231bf89c 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -86,9 +86,9 @@ void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<Backt
std::map<std::string, Symbols::Symbols> symbols;
for (const auto& module : modules) {
- symbols.insert_or_assign(
- module.second, Symbols::GetSymbols(module.first, system.ApplicationMemory(),
- system.ApplicationProcess()->Is64BitProcess()));
+ symbols.insert_or_assign(module.second,
+ Symbols::GetSymbols(module.first, system.ApplicationMemory(),
+ system.ApplicationProcess()->Is64Bit()));
}
for (auto& entry : out) {
@@ -153,6 +153,14 @@ void ARM_Interface::Run() {
Kernel::KThread* current_thread{Kernel::GetCurrentThreadPointer(system.Kernel())};
HaltReason hr{};
+ // If the thread is scheduled for termination, exit the thread.
+ if (current_thread->HasDpc()) {
+ if (current_thread->IsTerminationRequested()) {
+ current_thread->Exit();
+ UNREACHABLE();
+ }
+ }
+
// Notify the debugger and go to sleep if a step was performed
// and this thread has been scheduled again.
if (current_thread->GetStepState() == StepState::StepPerformed) {
@@ -174,14 +182,6 @@ void ARM_Interface::Run() {
}
system.ExitCPUProfile();
- // If the thread is scheduled for termination, exit the thread.
- if (current_thread->HasDpc()) {
- if (current_thread->IsTerminationRequested()) {
- current_thread->Exit();
- UNREACHABLE();
- }
- }
-
// Notify the debugger and go to sleep if a breakpoint was hit,
// or if the thread is unable to continue for any reason.
if (True(hr & HaltReason::InstructionBreakpoint) || True(hr & HaltReason::PrefetchAbort)) {
@@ -201,6 +201,8 @@ void ARM_Interface::Run() {
if (True(hr & HaltReason::DataAbort)) {
if (system.DebuggerEnabled()) {
system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint());
+ } else {
+ LogBacktrace();
}
current_thread->RequestSuspend(SuspendType::Debug);
break;
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 3d866ff6f..a9d9ac09d 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -81,6 +81,9 @@ public:
// thread context to be 800 bytes in size.
static_assert(sizeof(ThreadContext64) == 0x320);
+ /// Perform any backend-specific initialization.
+ virtual void Initialize() {}
+
/// Runs the CPU until an event happens
void Run();
diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp
new file mode 100644
index 000000000..f7bdafd39
--- /dev/null
+++ b/src/core/arm/nce/arm_nce.cpp
@@ -0,0 +1,400 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <cinttypes>
+#include <memory>
+
+#include "common/signal_chain.h"
+#include "core/arm/nce/arm_nce.h"
+#include "core/arm/nce/patcher.h"
+#include "core/core.h"
+#include "core/memory.h"
+
+#include "core/hle/kernel/k_process.h"
+
+#include <signal.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+namespace Core {
+
+namespace {
+
+struct sigaction g_orig_action;
+
+// Verify assembly offsets.
+using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
+static_assert(offsetof(NativeExecutionParameters, native_context) == TpidrEl0NativeContext);
+static_assert(offsetof(NativeExecutionParameters, lock) == TpidrEl0Lock);
+static_assert(offsetof(NativeExecutionParameters, magic) == TpidrEl0TlsMagic);
+
+fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
+ _aarch64_ctx* header = reinterpret_cast<_aarch64_ctx*>(&host_ctx.__reserved);
+ while (header->magic != FPSIMD_MAGIC) {
+ header = reinterpret_cast<_aarch64_ctx*>(reinterpret_cast<char*>(header) + header->size);
+ }
+ return reinterpret_cast<fpsimd_context*>(header);
+}
+
+} // namespace
+
+void* ARM_NCE::RestoreGuestContext(void* raw_context) {
+ // Retrieve the host context.
+ auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
+
+ // Thread-local parameters will be located in x9.
+ auto* tpidr = reinterpret_cast<NativeExecutionParameters*>(host_ctx.regs[9]);
+ auto* guest_ctx = static_cast<GuestContext*>(tpidr->native_context);
+
+ // Retrieve the host floating point state.
+ auto* fpctx = GetFloatingPointState(host_ctx);
+
+ // Save host callee-saved registers.
+ std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->vregs[8],
+ sizeof(guest_ctx->host_ctx.host_saved_vregs));
+ std::memcpy(guest_ctx->host_ctx.host_saved_regs.data(), &host_ctx.regs[19],
+ sizeof(guest_ctx->host_ctx.host_saved_regs));
+
+ // Save stack pointer.
+ guest_ctx->host_ctx.host_sp = host_ctx.sp;
+
+ // Restore all guest state except tpidr_el0.
+ host_ctx.sp = guest_ctx->sp;
+ host_ctx.pc = guest_ctx->pc;
+ host_ctx.pstate = guest_ctx->pstate;
+ fpctx->fpcr = guest_ctx->fpcr;
+ fpctx->fpsr = guest_ctx->fpsr;
+ std::memcpy(host_ctx.regs, guest_ctx->cpu_registers.data(), sizeof(host_ctx.regs));
+ std::memcpy(fpctx->vregs, guest_ctx->vector_registers.data(), sizeof(fpctx->vregs));
+
+ // Return the new thread-local storage pointer.
+ return tpidr;
+}
+
+void ARM_NCE::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
+ // Retrieve the host context.
+ auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
+
+ // Retrieve the host floating point state.
+ auto* fpctx = GetFloatingPointState(host_ctx);
+
+ // Save all guest registers except tpidr_el0.
+ std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs, sizeof(host_ctx.regs));
+ std::memcpy(guest_ctx->vector_registers.data(), fpctx->vregs, sizeof(fpctx->vregs));
+ guest_ctx->fpsr = fpctx->fpsr;
+ guest_ctx->fpcr = fpctx->fpcr;
+ guest_ctx->pstate = static_cast<u32>(host_ctx.pstate);
+ guest_ctx->pc = host_ctx.pc;
+ guest_ctx->sp = host_ctx.sp;
+
+ // Restore stack pointer.
+ host_ctx.sp = guest_ctx->host_ctx.host_sp;
+
+ // Restore host callee-saved registers.
+ std::memcpy(&host_ctx.regs[19], guest_ctx->host_ctx.host_saved_regs.data(),
+ sizeof(guest_ctx->host_ctx.host_saved_regs));
+ std::memcpy(&fpctx->vregs[8], guest_ctx->host_ctx.host_saved_vregs.data(),
+ sizeof(guest_ctx->host_ctx.host_saved_vregs));
+
+ // Return from the call on exit by setting pc to x30.
+ host_ctx.pc = guest_ctx->host_ctx.host_saved_regs[11];
+
+ // Clear esr_el1 and return it.
+ host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
+}
+
+bool ARM_NCE::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
+ auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
+ auto* info = static_cast<siginfo_t*>(raw_info);
+
+ // Try to handle an invalid access.
+ // TODO: handle accesses which split a page?
+ const Common::ProcessAddress addr =
+ (reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
+ if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
+ // We handled the access successfully and are returning to guest code.
+ return true;
+ }
+
+ // We can't handle the access, so determine why we crashed.
+ const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
+
+ // For data aborts, skip the instruction and return to guest code.
+ // This will allow games to continue in many scenarios where they would otherwise crash.
+ if (!is_prefetch_abort) {
+ host_ctx.pc += 4;
+ return true;
+ }
+
+ // This is a prefetch abort.
+ guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::PrefetchAbort));
+
+ // Forcibly mark the context as locked. We are still running.
+ // We may race with SignalInterrupt here:
+ // - If we lose the race, then SignalInterrupt will send us a signal we are masking,
+ // and it will do nothing when it is unmasked, as we have already left guest code.
+ // - If we win the race, then SignalInterrupt will wait for us to unlock first.
+ auto& thread_params = guest_ctx->parent->running_thread->GetNativeExecutionParameters();
+ thread_params.lock.store(SpinLockLocked);
+
+ // Return to host.
+ SaveGuestContext(guest_ctx, raw_context);
+ return false;
+}
+
+void ARM_NCE::HandleHostFault(int sig, void* raw_info, void* raw_context) {
+ return g_orig_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
+}
+
+HaltReason ARM_NCE::RunJit() {
+ // Get the thread parameters.
+ // TODO: pass the current thread down from ::Run
+ auto* thread = Kernel::GetCurrentThreadPointer(system.Kernel());
+ auto* thread_params = &thread->GetNativeExecutionParameters();
+
+ {
+ // Lock our core context.
+ std::scoped_lock lk{lock};
+
+ // We should not be running.
+ ASSERT(running_thread == nullptr);
+
+ // Check if we need to run. If we have already been halted, we are done.
+ u64 halt = guest_ctx.esr_el1.exchange(0);
+ if (halt != 0) {
+ return static_cast<HaltReason>(halt);
+ }
+
+ // Mark that we are running.
+ running_thread = thread;
+
+ // Acquire the lock on the thread parameters.
+ // This allows us to force synchronization with SignalInterrupt.
+ LockThreadParameters(thread_params);
+ }
+
+ // Assign current members.
+ guest_ctx.parent = this;
+ thread_params->native_context = &guest_ctx;
+ thread_params->tpidr_el0 = guest_ctx.tpidr_el0;
+ thread_params->tpidrro_el0 = guest_ctx.tpidrro_el0;
+ thread_params->is_running = true;
+
+ HaltReason halt{};
+
+ // TODO: finding and creating the post handler needs to be locked
+ // to deal with dynamic loading of NROs.
+ const auto& post_handlers = system.ApplicationProcess()->GetPostHandlers();
+ if (auto it = post_handlers.find(guest_ctx.pc); it != post_handlers.end()) {
+ halt = ReturnToRunCodeByTrampoline(thread_params, &guest_ctx, it->second);
+ } else {
+ halt = ReturnToRunCodeByExceptionLevelChange(thread_id, thread_params);
+ }
+
+ // Unload members.
+ // The thread does not change, so we can persist the old reference.
+ guest_ctx.tpidr_el0 = thread_params->tpidr_el0;
+ thread_params->native_context = nullptr;
+ thread_params->is_running = false;
+
+ // Unlock the thread parameters.
+ UnlockThreadParameters(thread_params);
+
+ {
+ // Lock the core context.
+ std::scoped_lock lk{lock};
+
+ // On exit, we no longer have an active thread.
+ running_thread = nullptr;
+ }
+
+ // Return the halt reason.
+ return halt;
+}
+
+HaltReason ARM_NCE::StepJit() {
+ return HaltReason::StepThread;
+}
+
+u32 ARM_NCE::GetSvcNumber() const {
+ return guest_ctx.svc_swi;
+}
+
+ARM_NCE::ARM_NCE(System& system_, bool uses_wall_clock_, std::size_t core_index_)
+ : ARM_Interface{system_, uses_wall_clock_}, core_index{core_index_} {
+ guest_ctx.system = &system_;
+}
+
+ARM_NCE::~ARM_NCE() = default;
+
+void ARM_NCE::Initialize() {
+ thread_id = gettid();
+
+ // Setup our signals
+ static std::once_flag flag;
+ std::call_once(flag, [] {
+ using HandlerType = decltype(sigaction::sa_sigaction);
+
+ sigset_t signal_mask;
+ sigemptyset(&signal_mask);
+ sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal);
+ sigaddset(&signal_mask, BreakFromRunCodeSignal);
+ sigaddset(&signal_mask, GuestFaultSignal);
+
+ struct sigaction return_to_run_code_action {};
+ return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ return_to_run_code_action.sa_sigaction = reinterpret_cast<HandlerType>(
+ &ARM_NCE::ReturnToRunCodeByExceptionLevelChangeSignalHandler);
+ return_to_run_code_action.sa_mask = signal_mask;
+ Common::SigAction(ReturnToRunCodeByExceptionLevelChangeSignal, &return_to_run_code_action,
+ nullptr);
+
+ struct sigaction break_from_run_code_action {};
+ break_from_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ break_from_run_code_action.sa_sigaction =
+ reinterpret_cast<HandlerType>(&ARM_NCE::BreakFromRunCodeSignalHandler);
+ break_from_run_code_action.sa_mask = signal_mask;
+ Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr);
+
+ struct sigaction fault_action {};
+ fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
+ fault_action.sa_sigaction =
+ reinterpret_cast<HandlerType>(&ARM_NCE::GuestFaultSignalHandler);
+ fault_action.sa_mask = signal_mask;
+ Common::SigAction(GuestFaultSignal, &fault_action, &g_orig_action);
+
+ // Simplify call for g_orig_action.
+ // These fields occupy the same space in memory, so this should be a no-op in practice.
+ if (!(g_orig_action.sa_flags & SA_SIGINFO)) {
+ g_orig_action.sa_sigaction =
+ reinterpret_cast<decltype(g_orig_action.sa_sigaction)>(g_orig_action.sa_handler);
+ }
+ });
+}
+
+void ARM_NCE::SetPC(u64 pc) {
+ guest_ctx.pc = pc;
+}
+
+u64 ARM_NCE::GetPC() const {
+ return guest_ctx.pc;
+}
+
+u64 ARM_NCE::GetSP() const {
+ return guest_ctx.sp;
+}
+
+u64 ARM_NCE::GetReg(int index) const {
+ return guest_ctx.cpu_registers[index];
+}
+
+void ARM_NCE::SetReg(int index, u64 value) {
+ guest_ctx.cpu_registers[index] = value;
+}
+
+u128 ARM_NCE::GetVectorReg(int index) const {
+ return guest_ctx.vector_registers[index];
+}
+
+void ARM_NCE::SetVectorReg(int index, u128 value) {
+ guest_ctx.vector_registers[index] = value;
+}
+
+u32 ARM_NCE::GetPSTATE() const {
+ return guest_ctx.pstate;
+}
+
+void ARM_NCE::SetPSTATE(u32 pstate) {
+ guest_ctx.pstate = pstate;
+}
+
+u64 ARM_NCE::GetTlsAddress() const {
+ return guest_ctx.tpidrro_el0;
+}
+
+void ARM_NCE::SetTlsAddress(u64 address) {
+ guest_ctx.tpidrro_el0 = address;
+}
+
+u64 ARM_NCE::GetTPIDR_EL0() const {
+ return guest_ctx.tpidr_el0;
+}
+
+void ARM_NCE::SetTPIDR_EL0(u64 value) {
+ guest_ctx.tpidr_el0 = value;
+}
+
+void ARM_NCE::SaveContext(ThreadContext64& ctx) const {
+ ctx.cpu_registers = guest_ctx.cpu_registers;
+ ctx.sp = guest_ctx.sp;
+ ctx.pc = guest_ctx.pc;
+ ctx.pstate = guest_ctx.pstate;
+ ctx.vector_registers = guest_ctx.vector_registers;
+ ctx.fpcr = guest_ctx.fpcr;
+ ctx.fpsr = guest_ctx.fpsr;
+ ctx.tpidr = guest_ctx.tpidr_el0;
+}
+
+void ARM_NCE::LoadContext(const ThreadContext64& ctx) {
+ guest_ctx.cpu_registers = ctx.cpu_registers;
+ guest_ctx.sp = ctx.sp;
+ guest_ctx.pc = ctx.pc;
+ guest_ctx.pstate = ctx.pstate;
+ guest_ctx.vector_registers = ctx.vector_registers;
+ guest_ctx.fpcr = ctx.fpcr;
+ guest_ctx.fpsr = ctx.fpsr;
+ guest_ctx.tpidr_el0 = ctx.tpidr;
+}
+
+void ARM_NCE::SignalInterrupt() {
+ // Lock core context.
+ std::scoped_lock lk{lock};
+
+ // Add break loop condition.
+ guest_ctx.esr_el1.fetch_or(static_cast<u64>(HaltReason::BreakLoop));
+
+ // If there is no thread running, we are done.
+ if (running_thread == nullptr) {
+ return;
+ }
+
+ // Lock the thread context.
+ auto* params = &running_thread->GetNativeExecutionParameters();
+ LockThreadParameters(params);
+
+ if (params->is_running) {
+ // We should signal to the running thread.
+ // The running thread will unlock the thread context.
+ syscall(SYS_tkill, thread_id, BreakFromRunCodeSignal);
+ } else {
+ // If the thread is no longer running, we have nothing to do.
+ UnlockThreadParameters(params);
+ }
+}
+
+void ARM_NCE::ClearInterrupt() {
+ guest_ctx.esr_el1 = {};
+}
+
+void ARM_NCE::ClearInstructionCache() {
+ // TODO: This is not possible to implement correctly on Linux because
+ // we do not have any access to ic iallu.
+
+ // Require accesses to complete.
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
+
+void ARM_NCE::InvalidateCacheRange(u64 addr, std::size_t size) {
+ this->ClearInstructionCache();
+}
+
+void ARM_NCE::ClearExclusiveState() {
+ // No-op.
+}
+
+void ARM_NCE::PageTableChanged(Common::PageTable& page_table,
+ std::size_t new_address_space_size_in_bits) {
+ // No-op. Page table is never used.
+}
+
+} // namespace Core
diff --git a/src/core/arm/nce/arm_nce.h b/src/core/arm/nce/arm_nce.h
new file mode 100644
index 000000000..5fbd6dbf3
--- /dev/null
+++ b/src/core/arm/nce/arm_nce.h
@@ -0,0 +1,108 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <span>
+#include <unordered_map>
+#include <vector>
+
+#include "core/arm/arm_interface.h"
+#include "core/arm/nce/guest_context.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace Core {
+
+class System;
+
+class ARM_NCE final : public ARM_Interface {
+public:
+ ARM_NCE(System& system_, bool uses_wall_clock_, std::size_t core_index_);
+
+ ~ARM_NCE() override;
+
+ void Initialize() override;
+ void SetPC(u64 pc) override;
+ u64 GetPC() const override;
+ u64 GetSP() const override;
+ u64 GetReg(int index) const override;
+ void SetReg(int index, u64 value) override;
+ u128 GetVectorReg(int index) const override;
+ void SetVectorReg(int index, u128 value) override;
+
+ u32 GetPSTATE() const override;
+ void SetPSTATE(u32 pstate) override;
+ u64 GetTlsAddress() const override;
+ void SetTlsAddress(u64 address) override;
+ void SetTPIDR_EL0(u64 value) override;
+ u64 GetTPIDR_EL0() const override;
+
+ Architecture GetArchitecture() const override {
+ return Architecture::Aarch64;
+ }
+
+ void SaveContext(ThreadContext32& ctx) const override {}
+ void SaveContext(ThreadContext64& ctx) const override;
+ void LoadContext(const ThreadContext32& ctx) override {}
+ void LoadContext(const ThreadContext64& ctx) override;
+
+ void SignalInterrupt() override;
+ void ClearInterrupt() override;
+ void ClearExclusiveState() override;
+ void ClearInstructionCache() override;
+ void InvalidateCacheRange(u64 addr, std::size_t size) override;
+ void PageTableChanged(Common::PageTable& new_page_table,
+ std::size_t new_address_space_size_in_bits) override;
+
+protected:
+ HaltReason RunJit() override;
+ HaltReason StepJit() override;
+
+ u32 GetSvcNumber() const override;
+
+ const Kernel::DebugWatchpoint* HaltedWatchpoint() const override {
+ return nullptr;
+ }
+
+ void RewindBreakpointInstruction() override {}
+
+private:
+ // Assembly definitions.
+ static HaltReason ReturnToRunCodeByTrampoline(void* tpidr, GuestContext* ctx,
+ u64 trampoline_addr);
+ static HaltReason ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr);
+
+ static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info,
+ void* raw_context);
+ static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context);
+ static void GuestFaultSignalHandler(int sig, void* info, void* raw_context);
+
+ static void LockThreadParameters(void* tpidr);
+ static void UnlockThreadParameters(void* tpidr);
+
+private:
+ // C++ implementation functions for assembly definitions.
+ static void* RestoreGuestContext(void* raw_context);
+ static void SaveGuestContext(GuestContext* ctx, void* raw_context);
+ static bool HandleGuestFault(GuestContext* ctx, void* info, void* raw_context);
+ static void HandleHostFault(int sig, void* info, void* raw_context);
+
+public:
+ // Members set on initialization.
+ std::size_t core_index{};
+ pid_t thread_id{-1};
+
+ // Core context.
+ GuestContext guest_ctx;
+
+ // Thread and invalidation info.
+ std::mutex lock;
+ Kernel::KThread* running_thread{};
+};
+
+} // namespace Core
diff --git a/src/core/arm/nce/arm_nce.s b/src/core/arm/nce/arm_nce.s
new file mode 100644
index 000000000..b98e09f31
--- /dev/null
+++ b/src/core/arm/nce/arm_nce.s
@@ -0,0 +1,222 @@
+/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "core/arm/nce/arm_nce_asm_definitions.h"
+
+#define LOAD_IMMEDIATE_32(reg, val) \
+ mov reg, #(((val) >> 0x00) & 0xFFFF); \
+ movk reg, #(((val) >> 0x10) & 0xFFFF), lsl #16
+
+
+/* static HaltReason Core::ARM_NCE::ReturnToRunCodeByTrampoline(void* tpidr, Core::GuestContext* ctx, u64 trampoline_addr) */
+.section .text._ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, "ax", %progbits
+.global _ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm
+.type _ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, %function
+_ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm:
+ /* Back up host sp to x3. */
+ /* Back up host tpidr_el0 to x4. */
+ mov x3, sp
+ mrs x4, tpidr_el0
+
+ /* Load guest sp. x5 is used as a scratch register. */
+ ldr x5, [x1, #(GuestContextSp)]
+ mov sp, x5
+
+ /* Offset GuestContext pointer to the host member. */
+ add x5, x1, #(GuestContextHostContext)
+
+ /* Save original host sp and tpidr_el0 (x3, x4) to host context. */
+ stp x3, x4, [x5, #(HostContextSpTpidrEl0)]
+
+ /* Save all callee-saved host GPRs. */
+ stp x19, x20, [x5, #(HostContextRegs+0x0)]
+ stp x21, x22, [x5, #(HostContextRegs+0x10)]
+ stp x23, x24, [x5, #(HostContextRegs+0x20)]
+ stp x25, x26, [x5, #(HostContextRegs+0x30)]
+ stp x27, x28, [x5, #(HostContextRegs+0x40)]
+ stp x29, x30, [x5, #(HostContextRegs+0x50)]
+
+ /* Save all callee-saved host FPRs. */
+ stp q8, q9, [x5, #(HostContextVregs+0x0)]
+ stp q10, q11, [x5, #(HostContextVregs+0x20)]
+ stp q12, q13, [x5, #(HostContextVregs+0x40)]
+ stp q14, q15, [x5, #(HostContextVregs+0x60)]
+
+ /* Load guest tpidr_el0 from argument. */
+ msr tpidr_el0, x0
+
+ /* Tail call the trampoline to restore guest state. */
+ br x2
+
+
+/* static HaltReason Core::ARM_NCE::ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr) */
+.section .text._ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv, "ax", %progbits
+.global _ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv
+.type _ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv, %function
+_ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv:
+ /* This jumps to the signal handler, which will restore the entire context. */
+ /* On entry, x0 = thread id, which is already in the right place. */
+
+ /* Move tpidr to x9 so it is not trampled. */
+ mov x9, x1
+
+ /* Set up arguments. */
+ mov x8, #(__NR_tkill)
+ mov x1, #(ReturnToRunCodeByExceptionLevelChangeSignal)
+
+ /* Tail call the signal handler. */
+ svc #0
+
+ /* Block execution from flowing here. */
+ brk #1000
+
+
+/* static void Core::ARM_NCE::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info, void* raw_context) */
+.section .text._ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, "ax", %progbits
+.global _ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_
+.type _ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, %function
+_ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_:
+ stp x29, x30, [sp, #-0x10]!
+ mov x29, sp
+
+ /* Call the context restorer with the raw context. */
+ mov x0, x2
+ bl _ZN4Core7ARM_NCE19RestoreGuestContextEPv
+
+ /* Save the old value of tpidr_el0. */
+ mrs x8, tpidr_el0
+ ldr x9, [x0, #(TpidrEl0NativeContext)]
+ str x8, [x9, #(GuestContextHostContext + HostContextTpidrEl0)]
+
+ /* Set our new tpidr_el0. */
+ msr tpidr_el0, x0
+
+ /* Unlock the context. */
+ bl _ZN4Core7ARM_NCE22UnlockThreadParametersEPv
+
+ /* Returning from here will enter the guest. */
+ ldp x29, x30, [sp], #0x10
+ ret
+
+
+/* static void Core::ARM_NCE::BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context) */
+.section .text._ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_, "ax", %progbits
+.global _ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_
+.type _ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_, %function
+_ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_:
+ /* Check to see if we have the correct TLS magic. */
+ mrs x8, tpidr_el0
+ ldr w9, [x8, #(TpidrEl0TlsMagic)]
+
+ LOAD_IMMEDIATE_32(w10, TlsMagic)
+
+ cmp w9, w10
+ b.ne 1f
+
+ /* Correct TLS magic, so this is a guest interrupt. */
+ /* Restore host tpidr_el0. */
+ ldr x0, [x8, #(TpidrEl0NativeContext)]
+ ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)]
+ msr tpidr_el0, x3
+
+ /* Tail call the restorer. */
+ mov x1, x2
+ b _ZN4Core7ARM_NCE16SaveGuestContextEPNS_12GuestContextEPv
+
+ /* Returning from here will enter host code. */
+
+1:
+ /* Incorrect TLS magic, so this is a spurious signal. */
+ ret
+
+
+/* static void Core::ARM_NCE::GuestFaultSignalHandler(int sig, void* info, void* raw_context) */
+.section .text._ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_, "ax", %progbits
+.global _ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_
+.type _ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_, %function
+_ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_:
+ /* Check to see if we have the correct TLS magic. */
+ mrs x8, tpidr_el0
+ ldr w9, [x8, #(TpidrEl0TlsMagic)]
+
+ LOAD_IMMEDIATE_32(w10, TlsMagic)
+
+ cmp w9, w10
+ b.eq 1f
+
+ /* Incorrect TLS magic, so this is a host fault. */
+ /* Tail call the handler. */
+ b _ZN4Core7ARM_NCE15HandleHostFaultEiPvS1_
+
+1:
+ /* Correct TLS magic, so this is a guest fault. */
+ stp x29, x30, [sp, #-0x20]!
+ str x19, [sp, #0x10]
+ mov x29, sp
+
+ /* Save the old tpidr_el0. */
+ mov x19, x8
+
+ /* Restore host tpidr_el0. */
+ ldr x0, [x8, #(TpidrEl0NativeContext)]
+ ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)]
+ msr tpidr_el0, x3
+
+ /* Call the handler. */
+ bl _ZN4Core7ARM_NCE16HandleGuestFaultEPNS_12GuestContextEPvS3_
+
+ /* If the handler returned false, we want to preserve the host tpidr_el0. */
+ cbz x0, 2f
+
+ /* Otherwise, restore guest tpidr_el0. */
+ msr tpidr_el0, x19
+
+2:
+ ldr x19, [sp, #0x10]
+ ldp x29, x30, [sp], #0x20
+ ret
+
+
+/* static void Core::ARM_NCE::LockThreadParameters(void* tpidr) */
+.section .text._ZN4Core7ARM_NCE20LockThreadParametersEPv, "ax", %progbits
+.global _ZN4Core7ARM_NCE20LockThreadParametersEPv
+.type _ZN4Core7ARM_NCE20LockThreadParametersEPv, %function
+_ZN4Core7ARM_NCE20LockThreadParametersEPv:
+ /* Offset to lock member. */
+ add x0, x0, #(TpidrEl0Lock)
+
+1:
+ /* Clear the monitor. */
+ clrex
+
+2:
+ /* Load-linked with acquire ordering. */
+ ldaxr w1, [x0]
+
+ /* If the value was SpinLockLocked, clear monitor and retry. */
+ cbz w1, 1b
+
+ /* Store-conditional SpinLockLocked with relaxed ordering. */
+ stxr w1, wzr, [x0]
+
+ /* If we failed to store, retry. */
+ cbnz w1, 2b
+
+ ret
+
+
+/* static void Core::ARM_NCE::UnlockThreadParameters(void* tpidr) */
+.section .text._ZN4Core7ARM_NCE22UnlockThreadParametersEPv, "ax", %progbits
+.global _ZN4Core7ARM_NCE22UnlockThreadParametersEPv
+.type _ZN4Core7ARM_NCE22UnlockThreadParametersEPv, %function
+_ZN4Core7ARM_NCE22UnlockThreadParametersEPv:
+ /* Offset to lock member. */
+ add x0, x0, #(TpidrEl0Lock)
+
+ /* Load SpinLockUnlocked. */
+ mov w1, #(SpinLockUnlocked)
+
+ /* Store value with release ordering. */
+ stlr w1, [x0]
+
+ ret
diff --git a/src/core/arm/nce/arm_nce_asm_definitions.h b/src/core/arm/nce/arm_nce_asm_definitions.h
new file mode 100644
index 000000000..8a9b285b5
--- /dev/null
+++ b/src/core/arm/nce/arm_nce_asm_definitions.h
@@ -0,0 +1,29 @@
+/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+#define __ASSEMBLY__
+
+#include <asm-generic/signal.h>
+#include <asm-generic/unistd.h>
+
+#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
+#define BreakFromRunCodeSignal SIGURG
+#define GuestFaultSignal SIGSEGV
+
+#define GuestContextSp 0xF8
+#define GuestContextHostContext 0x320
+
+#define HostContextSpTpidrEl0 0xE0
+#define HostContextTpidrEl0 0xE8
+#define HostContextRegs 0x0
+#define HostContextVregs 0x60
+
+#define TpidrEl0NativeContext 0x10
+#define TpidrEl0Lock 0x18
+#define TpidrEl0TlsMagic 0x20
+#define TlsMagic 0x555a5559
+
+#define SpinLockLocked 0
+#define SpinLockUnlocked 1
diff --git a/src/core/arm/nce/guest_context.h b/src/core/arm/nce/guest_context.h
new file mode 100644
index 000000000..0767a0337
--- /dev/null
+++ b/src/core/arm/nce/guest_context.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/arm/arm_interface.h"
+#include "core/arm/nce/arm_nce_asm_definitions.h"
+
+namespace Core {
+
+class ARM_NCE;
+class System;
+
+struct HostContext {
+ alignas(16) std::array<u64, 12> host_saved_regs{};
+ alignas(16) std::array<u128, 8> host_saved_vregs{};
+ u64 host_sp{};
+ void* host_tpidr_el0{};
+};
+
+struct GuestContext {
+ std::array<u64, 31> cpu_registers{};
+ u64 sp{};
+ u64 pc{};
+ u32 fpcr{};
+ u32 fpsr{};
+ std::array<u128, 32> vector_registers{};
+ u32 pstate{};
+ alignas(16) HostContext host_ctx{};
+ u64 tpidrro_el0{};
+ u64 tpidr_el0{};
+ std::atomic<u64> esr_el1{};
+ u32 nzcv{};
+ u32 svc_swi{};
+ System* system{};
+ ARM_NCE* parent{};
+};
+
+// Verify assembly offsets.
+static_assert(offsetof(GuestContext, sp) == GuestContextSp);
+static_assert(offsetof(GuestContext, host_ctx) == GuestContextHostContext);
+static_assert(offsetof(HostContext, host_sp) == HostContextSpTpidrEl0);
+static_assert(offsetof(HostContext, host_tpidr_el0) - 8 == HostContextSpTpidrEl0);
+static_assert(offsetof(HostContext, host_tpidr_el0) == HostContextTpidrEl0);
+static_assert(offsetof(HostContext, host_saved_regs) == HostContextRegs);
+static_assert(offsetof(HostContext, host_saved_vregs) == HostContextVregs);
+
+} // namespace Core
diff --git a/src/core/arm/nce/instructions.h b/src/core/arm/nce/instructions.h
new file mode 100644
index 000000000..5b56ff857
--- /dev/null
+++ b/src/core/arm/nce/instructions.h
@@ -0,0 +1,147 @@
+// SPDX-FileCopyrightText: Copyright © 2020 Skyline Team and Contributors
+// SPDX-License-Identifier: MPL-2.0
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Core::NCE {
+
+enum SystemRegister : u32 {
+ TpidrEl0 = 0x5E82,
+ TpidrroEl0 = 0x5E83,
+ CntfrqEl0 = 0x5F00,
+ CntpctEl0 = 0x5F01,
+};
+
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SVC--Supervisor-Call-
+union SVC {
+ constexpr explicit SVC(u32 raw_) : raw{raw_} {}
+
+ constexpr bool Verify() {
+ return (this->GetSig0() == 0x1 && this->GetSig1() == 0x6A0);
+ }
+
+ constexpr u32 GetSig0() {
+ return decltype(sig0)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetValue() {
+ return decltype(value)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSig1() {
+ return decltype(sig1)::ExtractValue(raw);
+ }
+
+ u32 raw;
+
+private:
+ BitField<0, 5, u32> sig0; // 0x1
+ BitField<5, 16, u32> value; // 16-bit immediate
+ BitField<21, 11, u32> sig1; // 0x6A0
+};
+static_assert(sizeof(SVC) == sizeof(u32));
+static_assert(SVC(0xD40000C1).Verify());
+static_assert(SVC(0xD40000C1).GetValue() == 0x6);
+
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MRS--Move-System-Register-
+union MRS {
+ constexpr explicit MRS(u32 raw_) : raw{raw_} {}
+
+ constexpr bool Verify() {
+ return (this->GetSig() == 0xD53);
+ }
+
+ constexpr u32 GetRt() {
+ return decltype(rt)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSystemReg() {
+ return decltype(system_reg)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSig() {
+ return decltype(sig)::ExtractValue(raw);
+ }
+
+ u32 raw;
+
+private:
+ BitField<0, 5, u32> rt; // destination register
+ BitField<5, 15, u32> system_reg; // source system register
+ BitField<20, 12, u32> sig; // 0xD53
+};
+static_assert(sizeof(MRS) == sizeof(u32));
+static_assert(MRS(0xD53BE020).Verify());
+static_assert(MRS(0xD53BE020).GetSystemReg() == CntpctEl0);
+static_assert(MRS(0xD53BE020).GetRt() == 0x0);
+
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MSR--register---Move-general-purpose-register-to-System-Register-
+union MSR {
+ constexpr explicit MSR(u32 raw_) : raw{raw_} {}
+
+ constexpr bool Verify() {
+ return this->GetSig() == 0xD51;
+ }
+
+ constexpr u32 GetRt() {
+ return decltype(rt)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSystemReg() {
+ return decltype(system_reg)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSig() {
+ return decltype(sig)::ExtractValue(raw);
+ }
+
+ u32 raw;
+
+private:
+ BitField<0, 5, u32> rt; // source register
+ BitField<5, 15, u32> system_reg; // destination system register
+ BitField<20, 12, u32> sig; // 0xD51
+};
+static_assert(sizeof(MSR) == sizeof(u32));
+static_assert(MSR(0xD51BD040).Verify());
+static_assert(MSR(0xD51BD040).GetSystemReg() == TpidrEl0);
+static_assert(MSR(0xD51BD040).GetRt() == 0x0);
+
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDXR--Load-Exclusive-Register-
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDXP--Load-Exclusive-Pair-of-Registers-
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/STXR--Store-Exclusive-Register-
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/STXP--Store-Exclusive-Pair-of-registers-
+union Exclusive {
+ constexpr explicit Exclusive(u32 raw_) : raw{raw_} {}
+
+ constexpr bool Verify() {
+ return this->GetSig() == 0x10;
+ }
+
+ constexpr u32 GetSig() {
+ return decltype(sig)::ExtractValue(raw);
+ }
+
+ constexpr u32 AsOrdered() {
+ return raw | decltype(o0)::FormatValue(1);
+ }
+
+ u32 raw;
+
+private:
+ BitField<0, 5, u32> rt; // memory operand
+ BitField<5, 5, u32> rn; // register operand 1
+ BitField<10, 5, u32> rt2; // register operand 2
+ BitField<15, 1, u32> o0; // ordered
+ BitField<16, 5, u32> rs; // status register
+ BitField<21, 2, u32> l; // operation type
+ BitField<23, 7, u32> sig; // 0x10
+ BitField<30, 2, u32> size; // size
+};
+static_assert(Exclusive(0xC85FFC00).Verify());
+static_assert(Exclusive(0xC85FFC00).AsOrdered() == 0xC85FFC00);
+static_assert(Exclusive(0xC85F7C00).AsOrdered() == 0xC85FFC00);
+static_assert(Exclusive(0xC8200440).AsOrdered() == 0xC8208440);
+
+} // namespace Core::NCE
diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp
new file mode 100644
index 000000000..ec8527224
--- /dev/null
+++ b/src/core/arm/nce/patcher.cpp
@@ -0,0 +1,474 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/arm64/native_clock.h"
+#include "common/bit_cast.h"
+#include "common/literals.h"
+#include "core/arm/nce/arm_nce.h"
+#include "core/arm/nce/guest_context.h"
+#include "core/arm/nce/instructions.h"
+#include "core/arm/nce/patcher.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/svc.h"
+
+namespace Core::NCE {
+
+using namespace Common::Literals;
+using namespace oaknut::util;
+
+using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
+
+constexpr size_t MaxRelativeBranch = 128_MiB;
+constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
+
+Patcher::Patcher() : c(m_patch_instructions) {}
+
+Patcher::~Patcher() = default;
+
+void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
+ const Kernel::CodeSet::Segment& code) {
+
+ // Write save context helper function.
+ c.l(m_save_context);
+ WriteSaveContext();
+
+ // Write load context helper function.
+ c.l(m_load_context);
+ WriteLoadContext();
+
+ // Retrieve text segment data.
+ const auto text = std::span{program_image}.subspan(code.offset, code.size);
+ const auto text_words =
+ std::span<const u32>{reinterpret_cast<const u32*>(text.data()), text.size() / sizeof(u32)};
+
+ // Loop through instructions, patching as needed.
+ for (u32 i = ModuleCodeIndex; i < static_cast<u32>(text_words.size()); i++) {
+ const u32 inst = text_words[i];
+
+ const auto AddRelocations = [&] {
+ const uintptr_t this_offset = i * sizeof(u32);
+ const uintptr_t next_offset = this_offset + sizeof(u32);
+
+ // Relocate from here to patch.
+ this->BranchToPatch(this_offset);
+
+ // Relocate from patch to next instruction.
+ return next_offset;
+ };
+
+ // SVC
+ if (auto svc = SVC{inst}; svc.Verify()) {
+ WriteSvcTrampoline(AddRelocations(), svc.GetValue());
+ continue;
+ }
+
+ // MRS Xn, TPIDR_EL0
+ // MRS Xn, TPIDRRO_EL0
+ if (auto mrs = MRS{inst};
+ mrs.Verify() && (mrs.GetSystemReg() == TpidrroEl0 || mrs.GetSystemReg() == TpidrEl0)) {
+ const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0
+ : oaknut::SystemReg::TPIDR_EL0;
+ const auto dest_reg = oaknut::XReg{static_cast<int>(mrs.GetRt())};
+ WriteMrsHandler(AddRelocations(), dest_reg, src_reg);
+ continue;
+ }
+
+ // MRS Xn, CNTPCT_EL0
+ if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) {
+ WriteCntpctHandler(AddRelocations(), oaknut::XReg{static_cast<int>(mrs.GetRt())});
+ continue;
+ }
+
+ // MRS Xn, CNTFRQ_EL0
+ if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) {
+ UNREACHABLE();
+ }
+
+ // MSR TPIDR_EL0, Xn
+ if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) {
+ WriteMsrHandler(AddRelocations(), oaknut::XReg{static_cast<int>(msr.GetRt())});
+ continue;
+ }
+
+ if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
+ m_exclusives.push_back(i);
+ }
+ }
+
+ // Determine patching mode for the final relocation step
+ const size_t image_size = program_image.size();
+ this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
+}
+
+void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
+ const Kernel::CodeSet::Segment& code,
+ Kernel::PhysicalMemory& program_image,
+ EntryTrampolines* out_trampolines) {
+ const size_t patch_size = GetSectionSize();
+ const size_t image_size = program_image.size();
+
+ // Retrieve text segment data.
+ const auto text = std::span{program_image}.subspan(code.offset, code.size);
+ const auto text_words =
+ std::span<u32>{reinterpret_cast<u32*>(text.data()), text.size() / sizeof(u32)};
+
+ const auto ApplyBranchToPatchRelocation = [&](u32* target, const Relocation& rel) {
+ oaknut::CodeGenerator rc{target};
+ if (mode == PatchMode::PreText) {
+ rc.B(rel.patch_offset - patch_size - rel.module_offset);
+ } else {
+ rc.B(image_size - rel.module_offset + rel.patch_offset);
+ }
+ };
+
+ const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) {
+ oaknut::CodeGenerator rc{target};
+ if (mode == PatchMode::PreText) {
+ rc.B(patch_size - rel.patch_offset + rel.module_offset);
+ } else {
+ rc.B(rel.module_offset - image_size - rel.patch_offset);
+ }
+ };
+
+ const auto RebasePatch = [&](ptrdiff_t patch_offset) {
+ if (mode == PatchMode::PreText) {
+ return GetInteger(load_base) + patch_offset;
+ } else {
+ return GetInteger(load_base) + image_size + patch_offset;
+ }
+ };
+
+ const auto RebasePc = [&](uintptr_t module_offset) {
+ if (mode == PatchMode::PreText) {
+ return GetInteger(load_base) + patch_size + module_offset;
+ } else {
+ return GetInteger(load_base) + module_offset;
+ }
+ };
+
+ // We are now ready to relocate!
+ for (const Relocation& rel : m_branch_to_patch_relocations) {
+ ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
+ }
+ for (const Relocation& rel : m_branch_to_module_relocations) {
+ ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
+ rel);
+ }
+
+ // Rewrite PC constants and record post trampolines
+ for (const Relocation& rel : m_write_module_pc_relocations) {
+ oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
+ rc.dx(RebasePc(rel.module_offset));
+ }
+ for (const Trampoline& rel : m_trampolines) {
+ out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
+ }
+
+ // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
+ // Convert to ordered to preserve this assumption.
+ for (const ModuleTextAddress i : m_exclusives) {
+ auto exclusive = Exclusive{text_words[i]};
+ text_words[i] = exclusive.AsOrdered();
+ }
+
+ // Copy to program image
+ if (this->mode == PatchMode::PreText) {
+ std::memcpy(program_image.data(), m_patch_instructions.data(),
+ m_patch_instructions.size() * sizeof(u32));
+ } else {
+ program_image.resize(image_size + patch_size);
+ std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
+ m_patch_instructions.size() * sizeof(u32));
+ }
+}
+
+size_t Patcher::GetSectionSize() const noexcept {
+ return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE);
+}
+
+void Patcher::WriteLoadContext() {
+ // This function was called, which modifies X30, so use that as a scratch register.
+ // SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes
+ // of stack.
+ c.STR(X30, SP, 8);
+ c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
+
+ // Load system registers.
+ c.LDR(W0, X30, offsetof(GuestContext, fpsr));
+ c.MSR(oaknut::SystemReg::FPSR, X0);
+ c.LDR(W0, X30, offsetof(GuestContext, fpcr));
+ c.MSR(oaknut::SystemReg::FPCR, X0);
+ c.LDR(W0, X30, offsetof(GuestContext, nzcv));
+ c.MSR(oaknut::SystemReg::NZCV, X0);
+
+ // Load all vector registers.
+ static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
+ for (int i = 0; i <= 30; i += 2) {
+ c.LDP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i);
+ }
+
+ // Load all general-purpose registers except X30.
+ for (int i = 0; i <= 28; i += 2) {
+ c.LDP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i);
+ }
+
+ // Reload our return X30 from the stack and return.
+ // The patch code will reload the guest X30 for us.
+ c.LDR(X30, SP, 8);
+ c.RET();
+}
+
+void Patcher::WriteSaveContext() {
+ // This function was called, which modifies X30, so use that as a scratch register.
+ // SP contains the guest X30, so save our X30 to SP + 8, since we have allocated 16 bytes of
+ // stack.
+ c.STR(X30, SP, 8);
+ c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
+
+ // Store all general-purpose registers except X30.
+ for (int i = 0; i <= 28; i += 2) {
+ c.STP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i);
+ }
+
+ // Store all vector registers.
+ static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
+ for (int i = 0; i <= 30; i += 2) {
+ c.STP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i);
+ }
+
+ // Store guest system registers, X30 and SP, using X0 as a scratch register.
+ c.STR(X0, SP, PRE_INDEXED, -16);
+ c.LDR(X0, SP, 16);
+ c.STR(X0, X30, 8 * 30);
+ c.ADD(X0, SP, 32);
+ c.STR(X0, X30, offsetof(GuestContext, sp));
+ c.MRS(X0, oaknut::SystemReg::FPSR);
+ c.STR(W0, X30, offsetof(GuestContext, fpsr));
+ c.MRS(X0, oaknut::SystemReg::FPCR);
+ c.STR(W0, X30, offsetof(GuestContext, fpcr));
+ c.MRS(X0, oaknut::SystemReg::NZCV);
+ c.STR(W0, X30, offsetof(GuestContext, nzcv));
+ c.LDR(X0, SP, POST_INDEXED, 16);
+
+ // Reload our return X30 from the stack, and return.
+ c.LDR(X30, SP, 8);
+ c.RET();
+}
+
+void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
+ // We are about to start saving state, so we need to lock the context.
+ this->LockContext();
+
+ // Store guest X30 to the stack. Then, save the context and restore the stack.
+ // This will save all registers except PC, but we know PC at patch time.
+ c.STR(X30, SP, PRE_INDEXED, -16);
+ c.BL(m_save_context);
+ c.LDR(X30, SP, POST_INDEXED, 16);
+
+ // Now that we've saved all registers, we can use any registers as scratch.
+ // Store PC + 4 to arm interface, since we know the instruction offset from the entry point.
+ oaknut::Label pc_after_svc;
+ c.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
+ c.LDR(X2, pc_after_svc);
+ c.STR(X2, X1, offsetof(GuestContext, pc));
+
+ // Store SVC number to execute when we return
+ c.MOV(X2, svc_id);
+ c.STR(W2, X1, offsetof(GuestContext, svc_swi));
+
+ // We are calling a SVC. Clear esr_el1 and return it.
+ static_assert(std::is_same_v<std::underlying_type_t<HaltReason>, u64>);
+ oaknut::Label retry;
+ c.ADD(X2, X1, offsetof(GuestContext, esr_el1));
+ c.l(retry);
+ c.LDAXR(X0, X2);
+ c.STLXR(W3, XZR, X2);
+ c.CBNZ(W3, retry);
+
+ // Add "calling SVC" flag. Since this is X0, this is now our return value.
+ c.ORR(X0, X0, static_cast<u64>(HaltReason::SupervisorCall));
+
+ // Offset the GuestContext pointer to the HostContext member.
+ // STP has limited range of [-512, 504] which we can't reach otherwise
+ // NB: Due to this all offsets below are from the start of HostContext.
+ c.ADD(X1, X1, offsetof(GuestContext, host_ctx));
+
+ // Reload host TPIDR_EL0 and SP.
+ static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
+ c.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
+ c.MOV(SP, X2);
+ c.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
+
+ // Load callee-saved host registers and return to host.
+ static constexpr size_t HOST_REGS_OFF = offsetof(HostContext, host_saved_regs);
+ static constexpr size_t HOST_VREGS_OFF = offsetof(HostContext, host_saved_vregs);
+ c.LDP(X19, X20, X1, HOST_REGS_OFF);
+ c.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64));
+ c.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64));
+ c.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64));
+ c.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64));
+ c.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64));
+ c.LDP(Q8, Q9, X1, HOST_VREGS_OFF);
+ c.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128));
+ c.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128));
+ c.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128));
+ c.RET();
+
+ // Write the post-SVC trampoline address, which will jump back to the guest after restoring its
+ // state.
+ m_trampolines.push_back({c.offset(), module_dest});
+
+ // Host called this location. Save the return address so we can
+ // unwind the stack properly when jumping back.
+ c.MRS(X2, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context));
+ c.ADD(X0, X2, offsetof(GuestContext, host_ctx));
+ c.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64));
+
+ // Reload all guest registers except X30 and PC.
+ // The function also expects 16 bytes of stack already allocated.
+ c.STR(X30, SP, PRE_INDEXED, -16);
+ c.BL(m_load_context);
+ c.LDR(X30, SP, POST_INDEXED, 16);
+
+ // Use X1 as a scratch register to restore X30.
+ c.STR(X1, SP, PRE_INDEXED, -16);
+ c.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
+ c.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30);
+ c.LDR(X1, SP, POST_INDEXED, 16);
+
+ // Unlock the context.
+ this->UnlockContext();
+
+ // Jump back to the instruction after the emulated SVC.
+ this->BranchToModule(module_dest);
+
+ // Store PC after call.
+ c.l(pc_after_svc);
+ this->WriteModulePc(module_dest);
+}
+
+void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
+ oaknut::SystemReg src_reg) {
+ // Retrieve emulated TLS register from GuestContext.
+ c.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0);
+ if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) {
+ c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0));
+ } else {
+ c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0));
+ }
+
+ // Jump back to the instruction after the emulated MRS.
+ this->BranchToModule(module_dest);
+}
+
+void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) {
+ const auto scratch_reg = src_reg.index() == 0 ? X1 : X0;
+ c.STR(scratch_reg, SP, PRE_INDEXED, -16);
+
+ // Save guest value to NativeExecutionParameters::tpidr_el0.
+ c.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0);
+ c.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0));
+
+ // Restore scratch register.
+ c.LDR(scratch_reg, SP, POST_INDEXED, 16);
+
+ // Jump back to the instruction after the emulated MSR.
+ this->BranchToModule(module_dest);
+}
+
+void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) {
+ static Common::Arm64::NativeClock clock{};
+ const auto factor = clock.GetGuestCNTFRQFactor();
+ const auto raw_factor = Common::BitCast<std::array<u64, 2>>(factor);
+
+ const auto use_x2_x3 = dest_reg.index() == 0 || dest_reg.index() == 1;
+ oaknut::XReg scratch0 = use_x2_x3 ? X2 : X0;
+ oaknut::XReg scratch1 = use_x2_x3 ? X3 : X1;
+
+ oaknut::Label factorlo;
+ oaknut::Label factorhi;
+
+ // Save scratches.
+ c.STP(scratch0, scratch1, SP, PRE_INDEXED, -16);
+
+ // Load counter value.
+ c.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0);
+
+ // Load scaling factor.
+ c.LDR(scratch0, factorlo);
+ c.LDR(scratch1, factorhi);
+
+ // Multiply low bits and get result.
+ c.UMULH(scratch0, dest_reg, scratch0);
+
+ // Multiply high bits and add low bit result.
+ c.MADD(dest_reg, dest_reg, scratch1, scratch0);
+
+ // Reload scratches.
+ c.LDP(scratch0, scratch1, SP, POST_INDEXED, 16);
+
+ // Jump back to the instruction after the emulated MRS.
+ this->BranchToModule(module_dest);
+
+ // Scaling factor constant values.
+ c.l(factorlo);
+ c.dx(raw_factor[0]);
+ c.l(factorhi);
+ c.dx(raw_factor[1]);
+}
+
+void Patcher::LockContext() {
+ oaknut::Label retry;
+
+ // Save scratches.
+ c.STP(X0, X1, SP, PRE_INDEXED, -16);
+
+ // Reload lock pointer.
+ c.l(retry);
+ c.CLREX();
+ c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
+ c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
+
+ static_assert(SpinLockLocked == 0);
+
+ // Load-linked with acquire ordering.
+ c.LDAXR(W1, X0);
+
+ // If the value was SpinLockLocked, clear monitor and retry.
+ c.CBZ(W1, retry);
+
+ // Store-conditional SpinLockLocked with relaxed ordering.
+ c.STXR(W1, WZR, X0);
+
+ // If we failed to store, retry.
+ c.CBNZ(W1, retry);
+
+ // We succeeded! Reload scratches.
+ c.LDP(X0, X1, SP, POST_INDEXED, 16);
+}
+
+void Patcher::UnlockContext() {
+ // Save scratches.
+ c.STP(X0, X1, SP, PRE_INDEXED, -16);
+
+ // Load lock pointer.
+ c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
+ c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
+
+ // Load SpinLockUnlocked.
+ c.MOV(W1, SpinLockUnlocked);
+
+ // Store value with release ordering.
+ c.STLR(W1, X0);
+
+ // Load scratches.
+ c.LDP(X0, X1, SP, POST_INDEXED, 16);
+}
+
+} // namespace Core::NCE
diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h
new file mode 100644
index 000000000..c6d1608c1
--- /dev/null
+++ b/src/core/arm/nce/patcher.h
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+#include <unordered_map>
+#include <vector>
+#include <oaknut/code_block.hpp>
+#include <oaknut/oaknut.hpp>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/k_typed_address.h"
+#include "core/hle/kernel/physical_memory.h"
+
+namespace Core::NCE {
+
+enum class PatchMode : u32 {
+ None,
+ PreText, ///< Patch section is inserted before .text
+ PostData, ///< Patch section is inserted after .data
+};
+
+using ModuleTextAddress = u64;
+using PatchTextAddress = u64;
+using EntryTrampolines = std::unordered_map<ModuleTextAddress, PatchTextAddress>;
+
+class Patcher {
+public:
+ explicit Patcher();
+ ~Patcher();
+
+ void PatchText(const Kernel::PhysicalMemory& program_image,
+ const Kernel::CodeSet::Segment& code);
+ void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
+ Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
+ size_t GetSectionSize() const noexcept;
+
+ [[nodiscard]] PatchMode GetPatchMode() const noexcept {
+ return mode;
+ }
+
+private:
+ using ModuleDestLabel = uintptr_t;
+
+ struct Trampoline {
+ ptrdiff_t patch_offset;
+ uintptr_t module_offset;
+ };
+
+ void WriteLoadContext();
+ void WriteSaveContext();
+ void LockContext();
+ void UnlockContext();
+ void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id);
+ void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
+ oaknut::SystemReg src_reg);
+ void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg);
+ void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg);
+
+private:
+ void BranchToPatch(uintptr_t module_dest) {
+ m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
+ }
+
+ void BranchToModule(uintptr_t module_dest) {
+ m_branch_to_module_relocations.push_back({c.offset(), module_dest});
+ c.dw(0);
+ }
+
+ void WriteModulePc(uintptr_t module_dest) {
+ m_write_module_pc_relocations.push_back({c.offset(), module_dest});
+ c.dx(0);
+ }
+
+private:
+ // List of patch instructions we have generated.
+ std::vector<u32> m_patch_instructions{};
+
+ // Relocation type for relative branch from module to patch.
+ struct Relocation {
+ ptrdiff_t patch_offset; ///< Offset in bytes from the start of the patch section.
+ uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
+ };
+
+ oaknut::VectorCodeGenerator c;
+ std::vector<Trampoline> m_trampolines;
+ std::vector<Relocation> m_branch_to_patch_relocations{};
+ std::vector<Relocation> m_branch_to_module_relocations{};
+ std::vector<Relocation> m_write_module_pc_relocations{};
+ std::vector<ModuleTextAddress> m_exclusives{};
+ oaknut::Label m_save_context{};
+ oaknut::Label m_load_context{};
+ PatchMode mode{PatchMode::None};
+};
+
+} // namespace Core::NCE
diff --git a/src/core/core.cpp b/src/core/core.cpp
index d7e2efbd7..14d6c8c27 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -309,17 +309,10 @@ struct System::Impl {
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
- // Create a resource limit for the process.
- const auto physical_memory_size =
- kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
- auto* resource_limit = Kernel::CreateResourceLimitForProcess(system, physical_memory_size);
-
// Create the process.
auto main_process = Kernel::KProcess::Create(system.Kernel());
- ASSERT(Kernel::KProcess::Initialize(main_process, system, "main",
- Kernel::KProcess::ProcessType::Userland, resource_limit)
- .IsSuccess());
Kernel::KProcess::Register(system.Kernel(), main_process);
+ kernel.AppendNewProcess(main_process);
kernel.MakeApplicationProcess(main_process);
const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
@@ -418,6 +411,7 @@ struct System::Impl {
services->KillNVNFlinger();
}
kernel.CloseServices();
+ kernel.ShutdownCores();
services.reset();
service_manager.reset();
cheat_engine.reset();
@@ -429,7 +423,6 @@ struct System::Impl {
gpu_core.reset();
host1x_core.reset();
perf_stats.reset();
- kernel.ShutdownCores();
cpu_manager.Shutdown();
debugger.reset();
kernel.Shutdown();
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index e671b270f..d6b5abc68 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -76,6 +76,7 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
}
void CoreTiming::ClearPendingEvents() {
+ std::scoped_lock lock{basic_lock};
event_queue.clear();
}
@@ -113,6 +114,7 @@ bool CoreTiming::IsRunning() const {
}
bool CoreTiming::HasPendingEvents() const {
+ std::scoped_lock lock{basic_lock};
return !(wait_set && event_queue.empty());
}
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 26a8b93a7..21548f0a9 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -161,7 +161,7 @@ private:
std::shared_ptr<EventType> ev_lost;
Common::Event event{};
Common::Event pause_event{};
- std::mutex basic_lock;
+ mutable std::mutex basic_lock;
std::mutex advance_lock;
std::unique_ptr<std::jthread> timer_thread;
std::atomic<bool> paused{};
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 980bb97f9..151eb3870 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -211,6 +211,8 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
system.GPU().ObtainContext();
}
+ system.ArmInterface(core).Initialize();
+
auto& kernel = system.Kernel();
auto& scheduler = *kernel.CurrentScheduler();
auto* thread = scheduler.GetSchedulerCurrentThread();
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index a1589fecb..0e270eb50 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -258,20 +258,20 @@ private:
Kernel::KScopedSchedulerLock sl{system.Kernel()};
// Put all threads to sleep on next scheduler round.
- for (auto* thread : ThreadList()) {
- thread->RequestSuspend(Kernel::SuspendType::Debug);
+ for (auto& thread : ThreadList()) {
+ thread.RequestSuspend(Kernel::SuspendType::Debug);
}
}
void ResumeEmulation(Kernel::KThread* except = nullptr) {
// Wake up all threads.
- for (auto* thread : ThreadList()) {
- if (thread == except) {
+ for (auto& thread : ThreadList()) {
+ if (std::addressof(thread) == except) {
continue;
}
- thread->SetStepState(Kernel::StepState::NotStepping);
- thread->Resume(Kernel::SuspendType::Debug);
+ thread.SetStepState(Kernel::StepState::NotStepping);
+ thread.Resume(Kernel::SuspendType::Debug);
}
}
@@ -283,13 +283,17 @@ private:
}
void UpdateActiveThread() {
- const auto& threads{ThreadList()};
- if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) {
- state->active_thread = threads.front();
+ auto& threads{ThreadList()};
+ for (auto& thread : threads) {
+ if (std::addressof(thread) == state->active_thread) {
+ // Thread is still alive, no need to update.
+ return;
+ }
}
+ state->active_thread = std::addressof(threads.front());
}
- const std::list<Kernel::KThread*>& ThreadList() {
+ Kernel::KProcess::ThreadList& ThreadList() {
return system.ApplicationProcess()->GetThreadList();
}
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 2076aa8a2..148dd3e39 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -109,7 +109,7 @@ static std::string EscapeXML(std::string_view data) {
GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
: DebuggerFrontend(backend_), system{system_} {
- if (system.ApplicationProcess()->Is64BitProcess()) {
+ if (system.ApplicationProcess()->Is64Bit()) {
arch = std::make_unique<GDBStubA64>();
} else {
arch = std::make_unique<GDBStubA32>();
@@ -446,10 +446,10 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory,
- const Kernel::KThread* thread) {
+ const Kernel::KThread& thread) {
// Read thread type from TLS
- const VAddr tls_thread_type{memory.Read32(thread->GetTlsAddress() + 0x1fc)};
- const VAddr argument_thread_type{thread->GetArgument()};
+ const VAddr tls_thread_type{memory.Read32(thread.GetTlsAddress() + 0x1fc)};
+ const VAddr argument_thread_type{thread.GetArgument()};
if (argument_thread_type && tls_thread_type != argument_thread_type) {
// Probably not created by nnsdk, no name available.
@@ -477,10 +477,10 @@ static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory&
}
static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory,
- const Kernel::KThread* thread) {
+ const Kernel::KThread& thread) {
// Read thread type from TLS
- const VAddr tls_thread_type{memory.Read64(thread->GetTlsAddress() + 0x1f8)};
- const VAddr argument_thread_type{thread->GetArgument()};
+ const VAddr tls_thread_type{memory.Read64(thread.GetTlsAddress() + 0x1f8)};
+ const VAddr argument_thread_type{thread.GetArgument()};
if (argument_thread_type && tls_thread_type != argument_thread_type) {
// Probably not created by nnsdk, no name available.
@@ -508,16 +508,16 @@ static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory&
}
static std::optional<std::string> GetThreadName(Core::System& system,
- const Kernel::KThread* thread) {
- if (system.ApplicationProcess()->Is64BitProcess()) {
+ const Kernel::KThread& thread) {
+ if (system.ApplicationProcess()->Is64Bit()) {
return GetNameFromThreadType64(system.ApplicationMemory(), thread);
} else {
return GetNameFromThreadType32(system.ApplicationMemory(), thread);
}
}
-static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
- switch (thread->GetWaitReasonForDebugging()) {
+static std::string_view GetThreadWaitReason(const Kernel::KThread& thread) {
+ switch (thread.GetWaitReasonForDebugging()) {
case Kernel::ThreadWaitReasonForDebugging::Sleep:
return "Sleep";
case Kernel::ThreadWaitReasonForDebugging::IPC:
@@ -535,8 +535,8 @@ static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
}
}
-static std::string GetThreadState(const Kernel::KThread* thread) {
- switch (thread->GetState()) {
+static std::string GetThreadState(const Kernel::KThread& thread) {
+ switch (thread.GetState()) {
case Kernel::ThreadState::Initialized:
return "Initialized";
case Kernel::ThreadState::Waiting:
@@ -562,6 +562,120 @@ static std::string PaginateBuffer(std::string_view buffer, std::string_view requ
}
}
+static VAddr GetModuleEnd(Kernel::KProcessPageTable& page_table, VAddr base) {
+ Kernel::KMemoryInfo mem_info;
+ Kernel::Svc::MemoryInfo svc_mem_info;
+ Kernel::Svc::PageInfo page_info;
+ VAddr cur_addr{base};
+
+ // Expect: r-x Code (.text)
+ R_ASSERT(page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info), cur_addr));
+ svc_mem_info = mem_info.GetSvcMemoryInfo();
+ cur_addr = svc_mem_info.base_address + svc_mem_info.size;
+ if (svc_mem_info.state != Kernel::Svc::MemoryState::Code ||
+ svc_mem_info.permission != Kernel::Svc::MemoryPermission::ReadExecute) {
+ return cur_addr - 1;
+ }
+
+ // Expect: r-- Code (.rodata)
+ R_ASSERT(page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info), cur_addr));
+ svc_mem_info = mem_info.GetSvcMemoryInfo();
+ cur_addr = svc_mem_info.base_address + svc_mem_info.size;
+ if (svc_mem_info.state != Kernel::Svc::MemoryState::Code ||
+ svc_mem_info.permission != Kernel::Svc::MemoryPermission::Read) {
+ return cur_addr - 1;
+ }
+
+ // Expect: rw- CodeData (.data)
+ R_ASSERT(page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info), cur_addr));
+ svc_mem_info = mem_info.GetSvcMemoryInfo();
+ cur_addr = svc_mem_info.base_address + svc_mem_info.size;
+ return cur_addr - 1;
+}
+
+static Loader::AppLoader::Modules FindModules(Core::System& system) {
+ Loader::AppLoader::Modules modules;
+
+ auto& page_table = system.ApplicationProcess()->GetPageTable();
+ auto& memory = system.ApplicationMemory();
+ VAddr cur_addr = 0;
+
+ // Look for executable sections in Code or AliasCode regions.
+ while (true) {
+ Kernel::KMemoryInfo mem_info{};
+ Kernel::Svc::PageInfo page_info{};
+ R_ASSERT(
+ page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info), cur_addr));
+ auto svc_mem_info = mem_info.GetSvcMemoryInfo();
+
+ if (svc_mem_info.permission == Kernel::Svc::MemoryPermission::ReadExecute &&
+ (svc_mem_info.state == Kernel::Svc::MemoryState::Code ||
+ svc_mem_info.state == Kernel::Svc::MemoryState::AliasCode)) {
+ // Try to read the module name from its path.
+ constexpr s32 PathLengthMax = 0x200;
+ struct {
+ u32 zero;
+ s32 path_length;
+ std::array<char, PathLengthMax> path;
+ } module_path;
+
+ if (memory.ReadBlock(svc_mem_info.base_address + svc_mem_info.size, &module_path,
+ sizeof(module_path))) {
+ if (module_path.zero == 0 && module_path.path_length > 0) {
+ // Truncate module name.
+ module_path.path[PathLengthMax - 1] = '\0';
+
+ // Ignore leading directories.
+ char* path_pointer = module_path.path.data();
+
+ for (s32 i = 0; i < std::min(PathLengthMax, module_path.path_length) &&
+ module_path.path[i] != '\0';
+ i++) {
+ if (module_path.path[i] == '/' || module_path.path[i] == '\\') {
+ path_pointer = module_path.path.data() + i + 1;
+ }
+ }
+
+ // Insert output.
+ modules.emplace(svc_mem_info.base_address, path_pointer);
+ }
+ }
+ }
+
+ // Check if we're done.
+ const uintptr_t next_address = svc_mem_info.base_address + svc_mem_info.size;
+ if (next_address <= cur_addr) {
+ break;
+ }
+
+ cur_addr = next_address;
+ }
+
+ return modules;
+}
+
+static VAddr FindMainModuleEntrypoint(Core::System& system) {
+ Loader::AppLoader::Modules modules;
+ system.GetAppLoader().ReadNSOModules(modules);
+
+ // Do we have a module named main?
+ const auto main = std::find_if(modules.begin(), modules.end(),
+ [](const auto& key) { return key.second == "main"; });
+
+ if (main != modules.end()) {
+ return main->first;
+ }
+
+ // Do we have any loaded executable sections?
+ modules = FindModules(system);
+ if (!modules.empty()) {
+ return modules.begin()->first;
+ }
+
+ // As a last resort, use the start of the code region.
+ return GetInteger(system.ApplicationProcess()->GetPageTable().GetCodeRegionStart());
+}
+
void GDBStub::HandleQuery(std::string_view command) {
if (command.starts_with("TStatus")) {
// no tracepoint support
@@ -573,21 +687,10 @@ void GDBStub::HandleQuery(std::string_view command) {
const auto target_xml{arch->GetTargetXML()};
SendReply(PaginateBuffer(target_xml, command.substr(30)));
} else if (command.starts_with("Offsets")) {
- Loader::AppLoader::Modules modules;
- system.GetAppLoader().ReadNSOModules(modules);
-
- const auto main = std::find_if(modules.begin(), modules.end(),
- [](const auto& key) { return key.second == "main"; });
- if (main != modules.end()) {
- SendReply(fmt::format("TextSeg={:x}", main->first));
- } else {
- SendReply(fmt::format(
- "TextSeg={:x}",
- GetInteger(system.ApplicationProcess()->GetPageTable().GetCodeRegionStart())));
- }
+ const auto main_offset = FindMainModuleEntrypoint(system);
+ SendReply(fmt::format("TextSeg={:x}", main_offset));
} else if (command.starts_with("Xfer:libraries:read::")) {
- Loader::AppLoader::Modules modules;
- system.GetAppLoader().ReadNSOModules(modules);
+ auto modules = FindModules(system);
std::string buffer;
buffer += R"(<?xml version="1.0"?>)";
@@ -604,7 +707,7 @@ void GDBStub::HandleQuery(std::string_view command) {
const auto& threads = system.ApplicationProcess()->GetThreadList();
std::vector<std::string> thread_ids;
for (const auto& thread : threads) {
- thread_ids.push_back(fmt::format("{:x}", thread->GetThreadId()));
+ thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId()));
}
SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
} else if (command.starts_with("sThreadInfo")) {
@@ -616,14 +719,14 @@ void GDBStub::HandleQuery(std::string_view command) {
buffer += "<threads>";
const auto& threads = system.ApplicationProcess()->GetThreadList();
- for (const auto* thread : threads) {
+ for (const auto& thread : threads) {
auto thread_name{GetThreadName(system, thread)};
if (!thread_name) {
- thread_name = fmt::format("Thread {:d}", thread->GetThreadId());
+ thread_name = fmt::format("Thread {:d}", thread.GetThreadId());
}
buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)",
- thread->GetThreadId(), thread->GetActiveCore(),
+ thread.GetThreadId(), thread.GetActiveCore(),
EscapeXML(*thread_name), GetThreadState(thread));
}
@@ -727,32 +830,6 @@ static constexpr const char* GetMemoryPermissionString(const Kernel::Svc::Memory
}
}
-static VAddr GetModuleEnd(Kernel::KPageTable& page_table, VAddr base) {
- Kernel::Svc::MemoryInfo mem_info;
- VAddr cur_addr{base};
-
- // Expect: r-x Code (.text)
- mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
- cur_addr = mem_info.base_address + mem_info.size;
- if (mem_info.state != Kernel::Svc::MemoryState::Code ||
- mem_info.permission != Kernel::Svc::MemoryPermission::ReadExecute) {
- return cur_addr - 1;
- }
-
- // Expect: r-- Code (.rodata)
- mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
- cur_addr = mem_info.base_address + mem_info.size;
- if (mem_info.state != Kernel::Svc::MemoryState::Code ||
- mem_info.permission != Kernel::Svc::MemoryPermission::Read) {
- return cur_addr - 1;
- }
-
- // Expect: rw- CodeData (.data)
- mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
- cur_addr = mem_info.base_address + mem_info.size;
- return cur_addr - 1;
-}
-
void GDBStub::HandleRcmd(const std::vector<u8>& command) {
std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()};
std::string reply;
@@ -767,7 +844,7 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
if (command_str == "get fastmem") {
if (Settings::IsFastmemEnabled()) {
- const auto& impl = page_table.PageTableImpl();
+ const auto& impl = page_table.GetImpl();
const auto region = reinterpret_cast<uintptr_t>(impl.fastmem_arena);
const auto region_bits = impl.current_address_space_width_in_bits;
const auto region_size = 1ULL << region_bits;
@@ -779,26 +856,27 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
reply = "Fastmem is not enabled.\n";
}
} else if (command_str == "get info") {
- Loader::AppLoader::Modules modules;
- system.GetAppLoader().ReadNSOModules(modules);
+ auto modules = FindModules(system);
reply = fmt::format("Process: {:#x} ({})\n"
"Program Id: {:#018x}\n",
process->GetProcessId(), process->GetName(), process->GetProgramId());
- reply += fmt::format("Layout:\n"
- " Alias: {:#012x} - {:#012x}\n"
- " Heap: {:#012x} - {:#012x}\n"
- " Aslr: {:#012x} - {:#012x}\n"
- " Stack: {:#012x} - {:#012x}\n"
- "Modules:\n",
- GetInteger(page_table.GetAliasRegionStart()),
- GetInteger(page_table.GetAliasRegionEnd()),
- GetInteger(page_table.GetHeapRegionStart()),
- GetInteger(page_table.GetHeapRegionEnd()),
- GetInteger(page_table.GetAliasCodeRegionStart()),
- GetInteger(page_table.GetAliasCodeRegionEnd()),
- GetInteger(page_table.GetStackRegionStart()),
- GetInteger(page_table.GetStackRegionEnd()));
+ reply += fmt::format(
+ "Layout:\n"
+ " Alias: {:#012x} - {:#012x}\n"
+ " Heap: {:#012x} - {:#012x}\n"
+ " Aslr: {:#012x} - {:#012x}\n"
+ " Stack: {:#012x} - {:#012x}\n"
+ "Modules:\n",
+ GetInteger(page_table.GetAliasRegionStart()),
+ GetInteger(page_table.GetAliasRegionStart()) + page_table.GetAliasRegionSize() - 1,
+ GetInteger(page_table.GetHeapRegionStart()),
+ GetInteger(page_table.GetHeapRegionStart()) + page_table.GetHeapRegionSize() - 1,
+ GetInteger(page_table.GetAliasCodeRegionStart()),
+ GetInteger(page_table.GetAliasCodeRegionStart()) + page_table.GetAliasCodeRegionSize() -
+ 1,
+ GetInteger(page_table.GetStackRegionStart()),
+ GetInteger(page_table.GetStackRegionStart()) + page_table.GetStackRegionSize() - 1);
for (const auto& [vaddr, name] : modules) {
reply += fmt::format(" {:#012x} - {:#012x} {}\n", vaddr,
@@ -811,27 +889,34 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
while (true) {
using MemoryAttribute = Kernel::Svc::MemoryAttribute;
- auto mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
-
- if (mem_info.state != Kernel::Svc::MemoryState::Inaccessible ||
- mem_info.base_address + mem_info.size - 1 != std::numeric_limits<u64>::max()) {
- const char* state = GetMemoryStateName(mem_info.state);
- const char* perm = GetMemoryPermissionString(mem_info);
-
- const char l = True(mem_info.attribute & MemoryAttribute::Locked) ? 'L' : '-';
- const char i = True(mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-';
- const char d = True(mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-';
- const char u = True(mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-';
+ Kernel::KMemoryInfo mem_info{};
+ Kernel::Svc::PageInfo page_info{};
+ R_ASSERT(page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info),
+ cur_addr));
+ auto svc_mem_info = mem_info.GetSvcMemoryInfo();
+
+ if (svc_mem_info.state != Kernel::Svc::MemoryState::Inaccessible ||
+ svc_mem_info.base_address + svc_mem_info.size - 1 !=
+ std::numeric_limits<u64>::max()) {
+ const char* state = GetMemoryStateName(svc_mem_info.state);
+ const char* perm = GetMemoryPermissionString(svc_mem_info);
+
+ const char l = True(svc_mem_info.attribute & MemoryAttribute::Locked) ? 'L' : '-';
+ const char i =
+ True(svc_mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-';
+ const char d =
+ True(svc_mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-';
+ const char u = True(svc_mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-';
const char p =
- True(mem_info.attribute & MemoryAttribute::PermissionLocked) ? 'P' : '-';
+ True(svc_mem_info.attribute & MemoryAttribute::PermissionLocked) ? 'P' : '-';
- reply += fmt::format(" {:#012x} - {:#012x} {} {} {}{}{}{}{} [{}, {}]\n",
- mem_info.base_address,
- mem_info.base_address + mem_info.size - 1, perm, state, l, i,
- d, u, p, mem_info.ipc_count, mem_info.device_count);
+ reply += fmt::format(
+ " {:#012x} - {:#012x} {} {} {}{}{}{}{} [{}, {}]\n", svc_mem_info.base_address,
+ svc_mem_info.base_address + svc_mem_info.size - 1, perm, state, l, i, d, u, p,
+ svc_mem_info.ipc_count, svc_mem_info.device_count);
}
- const uintptr_t next_address = mem_info.base_address + mem_info.size;
+ const uintptr_t next_address = svc_mem_info.base_address + svc_mem_info.size;
if (next_address <= cur_addr) {
break;
}
@@ -850,10 +935,10 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
}
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
- const auto& threads{system.ApplicationProcess()->GetThreadList()};
- for (auto* thread : threads) {
- if (thread->GetThreadId() == thread_id) {
- return thread;
+ auto& threads{system.ApplicationProcess()->GetThreadList()};
+ for (auto& thread : threads) {
+ if (thread.GetThreadId() == thread_id) {
+ return std::addressof(thread);
}
}
diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp
index de3f8ef8f..1aea56a99 100644
--- a/src/core/device_memory.cpp
+++ b/src/core/device_memory.cpp
@@ -6,7 +6,7 @@
namespace Core {
-#ifdef ANDROID
+#ifdef HAS_NCE
constexpr size_t VirtualReserveSize = 1ULL << 38;
#else
constexpr size_t VirtualReserveSize = 1ULL << 39;
@@ -15,6 +15,7 @@ constexpr size_t VirtualReserveSize = 1ULL << 39;
DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize} {}
+
DeviceMemory::~DeviceMemory() = default;
} // namespace Core
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 8e291ff67..763a44fee 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -104,16 +104,16 @@ Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
}
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
- // Allow use of cores 0~3 and thread priorities 1~63.
- constexpr u32 default_thread_info_capability = 0x30007F7;
+ // Allow use of cores 0~3 and thread priorities 16~63.
+ constexpr u32 default_thread_info_capability = 0x30043F7;
ProgramMetadata result;
result.LoadManual(
true /*is_64_bit*/, FileSys::ProgramAddressSpaceType::Is39Bit /*address_space*/,
- 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x00100000 /*main_thread_stack_size*/,
- 0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/,
- 0x1FE00000 /*system_resource_size*/, {default_thread_info_capability} /*capabilities*/);
+ 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x100000 /*main_thread_stack_size*/,
+ 0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/, 0 /*system_resource_size*/,
+ {default_thread_info_capability} /*capabilities*/);
return result;
}
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 9f8e74b13..76ee97d78 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -73,6 +73,9 @@ public:
u64 GetFilesystemPermissions() const;
u32 GetSystemResourceSize() const;
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
+ const std::array<u8, 0x10>& GetName() const {
+ return npdm_header.application_name;
+ }
void Print() const;
@@ -164,14 +167,14 @@ private:
u32_le unk_size_2;
};
- Header npdm_header;
- AciHeader aci_header;
- AcidHeader acid_header;
+ Header npdm_header{};
+ AciHeader aci_header{};
+ AcidHeader acid_header{};
- FileAccessControl acid_file_access;
- FileAccessHeader aci_file_access;
+ FileAccessControl acid_file_access{};
+ FileAccessHeader aci_file_access{};
- KernelCapabilityDescriptors aci_kernel_capabilities;
+ KernelCapabilityDescriptors aci_kernel_capabilities{};
};
} // namespace FileSys
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 1c580de57..1eb1f439a 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -35,13 +35,14 @@ struct RomFSHeader {
static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
struct DirectoryEntry {
+ u32_le parent;
u32_le sibling;
u32_le child_dir;
u32_le child_file;
u32_le hash;
u32_le name_length;
};
-static_assert(sizeof(DirectoryEntry) == 0x14, "DirectoryEntry has incorrect size.");
+static_assert(sizeof(DirectoryEntry) == 0x18, "DirectoryEntry has incorrect size.");
struct FileEntry {
u32_le parent;
@@ -64,25 +65,22 @@ std::pair<Entry, std::string> GetEntry(const VirtualFile& file, std::size_t offs
return {entry, string};
}
-void ProcessFile(VirtualFile file, std::size_t file_offset, std::size_t data_offset,
- u32 this_file_offset, std::shared_ptr<VectorVfsDirectory> parent) {
- while (true) {
+void ProcessFile(const VirtualFile& file, std::size_t file_offset, std::size_t data_offset,
+ u32 this_file_offset, std::shared_ptr<VectorVfsDirectory>& parent) {
+ while (this_file_offset != ROMFS_ENTRY_EMPTY) {
auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
parent->AddFile(std::make_shared<OffsetVfsFile>(
file, entry.first.size, entry.first.offset + data_offset, entry.second));
- if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
- break;
-
this_file_offset = entry.first.sibling;
}
}
-void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file_offset,
+void ProcessDirectory(const VirtualFile& file, std::size_t dir_offset, std::size_t file_offset,
std::size_t data_offset, u32 this_dir_offset,
- std::shared_ptr<VectorVfsDirectory> parent) {
- while (true) {
+ std::shared_ptr<VectorVfsDirectory>& parent) {
+ while (this_dir_offset != ROMFS_ENTRY_EMPTY) {
auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
auto current = std::make_shared<VectorVfsDirectory>(
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
@@ -97,14 +95,12 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file
}
parent->AddDirectory(current);
- if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
- break;
this_dir_offset = entry.first.sibling;
}
}
} // Anonymous namespace
-VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
+VirtualDir ExtractRomFS(VirtualFile file) {
RomFSHeader header{};
if (file->ReadObject(&header) != sizeof(RomFSHeader))
return nullptr;
@@ -113,27 +109,17 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
return nullptr;
const u64 file_offset = header.file_meta.offset;
- const u64 dir_offset = header.directory_meta.offset + 4;
-
- auto root =
- std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
- file->GetName(), file->GetContainingDirectory());
-
- ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);
+ const u64 dir_offset = header.directory_meta.offset;
- VirtualDir out = std::move(root);
+ auto root_container = std::make_shared<VectorVfsDirectory>();
- if (type == RomFSExtractionType::SingleDiscard)
- return out->GetSubdirectories().front();
+ ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root_container);
- while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
- if (Common::ToLower(out->GetSubdirectories().front()->GetName()) == "data" &&
- type == RomFSExtractionType::Truncated)
- break;
- out = out->GetSubdirectories().front();
+ if (auto root = root_container->GetSubdirectory(""); root) {
+ return std::make_shared<CachedVfsDirectory>(std::move(root));
}
- return std::make_shared<CachedVfsDirectory>(std::move(out));
+ return nullptr;
}
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 5d7f0c2a8..b75ff1aad 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -7,16 +7,9 @@
namespace FileSys {
-enum class RomFSExtractionType {
- Full, // Includes data directory
- Truncated, // Traverses into data directory
- SingleDiscard, // Traverses into the first subdirectory of root
-};
-
// Converts a RomFS binary blob to VFS Filesystem
// Returns nullptr on failure
-VirtualDir ExtractRomFS(VirtualFile file,
- RomFSExtractionType type = RomFSExtractionType::Truncated);
+VirtualDir ExtractRomFS(VirtualFile file);
// Converts a VFS filesystem into a RomFS binary
// Returns nullptr on failure
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index a72df034e..c7b48a58d 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -167,6 +167,11 @@ protected:
*/
std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
+ /**
+ * Clip the provided coordinates to be inside the touchscreen area.
+ */
+ std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
+
WindowSystemInfo window_info;
bool strict_context_required = false;
@@ -181,11 +186,6 @@ private:
// By default, ignore this request and do nothing.
}
- /**
- * Clip the provided coordinates to be inside the touchscreen area.
- */
- std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
-
Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
u32 client_area_width; ///< Current client width, should be set by window impl.
diff --git a/src/core/hid/emulated_console.h b/src/core/hid/emulated_console.h
index 79114bb6d..fae15a556 100644
--- a/src/core/hid/emulated_console.h
+++ b/src/core/hid/emulated_console.h
@@ -38,14 +38,6 @@ using TouchParams = std::array<Common::ParamPackage, MaxTouchDevices>;
using ConsoleMotionValues = ConsoleMotionInfo;
using TouchValues = std::array<Common::Input::TouchStatus, MaxTouchDevices>;
-struct TouchFinger {
- u64 last_touch{};
- Common::Point<float> position{};
- u32 id{};
- TouchAttribute attribute{};
- bool pressed{};
-};
-
// Contains all motion related data that is used on the services
struct ConsoleMotion {
Common::Vec3f accel{};
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 2af3f06fc..a6e681e15 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -8,6 +8,7 @@
#include "common/thread.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/input_converter.h"
+#include "core/hle/service/hid/hid_util.h"
namespace Core::HID {
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
@@ -82,7 +83,7 @@ Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleInde
}
void EmulatedController::ReloadFromSettings() {
- const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
for (std::size_t index = 0; index < player.buttons.size(); ++index) {
@@ -96,18 +97,7 @@ void EmulatedController::ReloadFromSettings() {
}
controller.color_values = {};
- controller.colors_state.fullkey = {
- .body = GetNpadColor(player.body_color_left),
- .button = GetNpadColor(player.button_color_left),
- };
- controller.colors_state.left = {
- .body = GetNpadColor(player.body_color_left),
- .button = GetNpadColor(player.button_color_left),
- };
- controller.colors_state.right = {
- .body = GetNpadColor(player.body_color_right),
- .button = GetNpadColor(player.button_color_right),
- };
+ ReloadColorsFromSettings();
ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs);
@@ -128,6 +118,30 @@ void EmulatedController::ReloadFromSettings() {
ReloadInput();
}
+void EmulatedController::ReloadColorsFromSettings() {
+ const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
+ const auto& player = Settings::values.players.GetValue()[player_index];
+
+ // Avoid updating colors if overridden by physical controller
+ if (controller.color_values[LeftIndex].body != 0 &&
+ controller.color_values[RightIndex].body != 0) {
+ return;
+ }
+
+ controller.colors_state.fullkey = {
+ .body = GetNpadColor(player.body_color_left),
+ .button = GetNpadColor(player.button_color_left),
+ };
+ controller.colors_state.left = {
+ .body = GetNpadColor(player.body_color_left),
+ .button = GetNpadColor(player.button_color_left),
+ };
+ controller.colors_state.right = {
+ .body = GetNpadColor(player.body_color_right),
+ .button = GetNpadColor(player.button_color_right),
+ };
+}
+
void EmulatedController::LoadDevices() {
// TODO(german77): Use more buttons to detect the correct device
const auto left_joycon = button_params[Settings::NativeButton::DRight];
@@ -202,7 +216,7 @@ void EmulatedController::LoadDevices() {
}
void EmulatedController::LoadTASParams() {
- const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
Common::ParamPackage common_params{};
common_params.Set("engine", "tas");
common_params.Set("port", static_cast<int>(player_index));
@@ -230,10 +244,12 @@ void EmulatedController::LoadTASParams() {
tas_button_params[Settings::NativeButton::DUp].Set("button", 13);
tas_button_params[Settings::NativeButton::DRight].Set("button", 14);
tas_button_params[Settings::NativeButton::DDown].Set("button", 15);
- tas_button_params[Settings::NativeButton::SL].Set("button", 16);
- tas_button_params[Settings::NativeButton::SR].Set("button", 17);
+ tas_button_params[Settings::NativeButton::SLLeft].Set("button", 16);
+ tas_button_params[Settings::NativeButton::SRLeft].Set("button", 17);
tas_button_params[Settings::NativeButton::Home].Set("button", 18);
tas_button_params[Settings::NativeButton::Screenshot].Set("button", 19);
+ tas_button_params[Settings::NativeButton::SLRight].Set("button", 20);
+ tas_button_params[Settings::NativeButton::SRRight].Set("button", 21);
tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_x", 0);
tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
@@ -249,7 +265,7 @@ void EmulatedController::LoadTASParams() {
}
void EmulatedController::LoadVirtualGamepadParams() {
- const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
Common::ParamPackage common_params{};
common_params.Set("engine", "virtual_gamepad");
common_params.Set("port", static_cast<int>(player_index));
@@ -283,10 +299,12 @@ void EmulatedController::LoadVirtualGamepadParams() {
virtual_button_params[Settings::NativeButton::DUp].Set("button", 13);
virtual_button_params[Settings::NativeButton::DRight].Set("button", 14);
virtual_button_params[Settings::NativeButton::DDown].Set("button", 15);
- virtual_button_params[Settings::NativeButton::SL].Set("button", 16);
- virtual_button_params[Settings::NativeButton::SR].Set("button", 17);
+ virtual_button_params[Settings::NativeButton::SLLeft].Set("button", 16);
+ virtual_button_params[Settings::NativeButton::SRLeft].Set("button", 17);
virtual_button_params[Settings::NativeButton::Home].Set("button", 18);
virtual_button_params[Settings::NativeButton::Screenshot].Set("button", 19);
+ virtual_button_params[Settings::NativeButton::SLRight].Set("button", 20);
+ virtual_button_params[Settings::NativeButton::SRRight].Set("button", 21);
virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_x", 0);
virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
@@ -491,9 +509,11 @@ void EmulatedController::ReloadInput() {
});
}
turbo_button_state = 0;
+ is_initalized = true;
}
void EmulatedController::UnloadInput() {
+ is_initalized = false;
for (auto& button : button_devices) {
button.reset();
}
@@ -598,7 +618,7 @@ bool EmulatedController::IsConfiguring() const {
}
void EmulatedController::SaveCurrentConfig() {
- const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
auto& player = Settings::values.players.GetValue()[player_index];
player.connected = is_connected;
player.controller_type = MapNPadToSettingsType(npad_type);
@@ -854,12 +874,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
controller.npad_button_state.down.Assign(current_status.value);
controller.debug_pad_button_state.d_down.Assign(current_status.value);
break;
- case Settings::NativeButton::SL:
+ case Settings::NativeButton::SLLeft:
controller.npad_button_state.left_sl.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::SLRight:
controller.npad_button_state.right_sl.Assign(current_status.value);
break;
- case Settings::NativeButton::SR:
+ case Settings::NativeButton::SRLeft:
controller.npad_button_state.left_sr.Assign(current_status.value);
+ break;
+ case Settings::NativeButton::SRRight:
controller.npad_button_state.right_sr.Assign(current_status.value);
break;
case Settings::NativeButton::Home:
@@ -1091,30 +1115,30 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
bool is_charging = false;
bool is_powered = false;
- NpadBatteryLevel battery_level = 0;
+ NpadBatteryLevel battery_level = NpadBatteryLevel::Empty;
switch (controller.battery_values[index]) {
case Common::Input::BatteryLevel::Charging:
is_charging = true;
is_powered = true;
- battery_level = 6;
+ battery_level = NpadBatteryLevel::Full;
break;
case Common::Input::BatteryLevel::Medium:
- battery_level = 6;
+ battery_level = NpadBatteryLevel::High;
break;
case Common::Input::BatteryLevel::Low:
- battery_level = 4;
+ battery_level = NpadBatteryLevel::Low;
break;
case Common::Input::BatteryLevel::Critical:
- battery_level = 2;
+ battery_level = NpadBatteryLevel::Critical;
break;
case Common::Input::BatteryLevel::Empty:
- battery_level = 0;
+ battery_level = NpadBatteryLevel::Empty;
break;
case Common::Input::BatteryLevel::None:
case Common::Input::BatteryLevel::Full:
default:
is_powered = true;
- battery_level = 8;
+ battery_level = NpadBatteryLevel::Full;
break;
}
@@ -1185,13 +1209,16 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
}
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
+ if (!is_initalized) {
+ return false;
+ }
if (device_index >= output_devices.size()) {
return false;
}
if (!output_devices[device_index]) {
return false;
}
- const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
const f32 strength = static_cast<f32>(player.vibration_strength) / 100.0f;
@@ -1217,9 +1244,13 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
}
bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
- const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
+ if (!is_initalized) {
+ return false;
+ }
+
if (!player.vibration_enabled) {
return false;
}
@@ -1239,6 +1270,10 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
+ if (!is_initalized) {
+ return Common::Input::DriverResult::InvalidHandle;
+ }
+
auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_output_device = output_devices[3];
@@ -1284,6 +1319,10 @@ bool EmulatedController::SetCameraFormat(
Core::IrSensor::ImageTransferProcessorFormat camera_format) {
LOG_INFO(Service_HID, "Set camera format {}", camera_format);
+ if (!is_initalized) {
+ return false;
+ }
+
auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& camera_output_device = output_devices[2];
@@ -1307,6 +1346,11 @@ void EmulatedController::SetRingParam(Common::ParamPackage param) {
}
bool EmulatedController::HasNfc() const {
+
+ if (!is_initalized) {
+ return false;
+ }
+
const auto& nfc_output_device = output_devices[3];
switch (npad_type) {
@@ -1344,6 +1388,10 @@ bool EmulatedController::RemoveNfcHandle() {
}
bool EmulatedController::StartNfcPolling() {
+ if (!is_initalized) {
+ return false;
+ }
+
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
@@ -1355,6 +1403,10 @@ bool EmulatedController::StartNfcPolling() {
}
bool EmulatedController::StopNfcPolling() {
+ if (!is_initalized) {
+ return false;
+ }
+
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
@@ -1366,6 +1418,10 @@ bool EmulatedController::StopNfcPolling() {
}
bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
+ if (!is_initalized) {
+ return false;
+ }
+
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
@@ -1378,6 +1434,10 @@ bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data) {
+ if (!is_initalized) {
+ return false;
+ }
+
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
@@ -1390,6 +1450,10 @@ bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& requ
}
bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
+ if (!is_initalized) {
+ return false;
+ }
+
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
@@ -1401,6 +1465,10 @@ bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& req
}
bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
+ if (!is_initalized) {
+ return false;
+ }
+
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
@@ -1412,6 +1480,10 @@ bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
}
void EmulatedController::SetLedPattern() {
+ if (!is_initalized) {
+ return;
+ }
+
for (auto& device : output_devices) {
if (!device) {
continue;
@@ -1627,7 +1699,7 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
}
if (is_connected) {
LOG_WARNING(Service_HID, "Controller {} type changed while it's connected",
- NpadIdTypeToIndex(npad_id_type));
+ Service::HID::NpadIdTypeToIndex(npad_id_type));
}
npad_type = npad_type_;
}
@@ -1877,12 +1949,16 @@ NpadButton EmulatedController::GetTurboButtonMask() const {
case Settings::NativeButton::DDown:
button_mask.down.Assign(1);
break;
- case Settings::NativeButton::SL:
+ case Settings::NativeButton::SLLeft:
button_mask.left_sl.Assign(1);
+ break;
+ case Settings::NativeButton::SLRight:
button_mask.right_sl.Assign(1);
break;
- case Settings::NativeButton::SR:
+ case Settings::NativeButton::SRLeft:
button_mask.left_sr.Assign(1);
+ break;
+ case Settings::NativeButton::SRRight:
button_mask.right_sr.Assign(1);
break;
default:
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index d4500583e..d6e20ab66 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -253,6 +253,9 @@ public:
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
+ /// Updates current colors with the ones stored in the configuration
+ void ReloadColorsFromSettings();
+
/// Saves the current mapped configuration
void SaveCurrentConfig();
@@ -556,6 +559,7 @@ private:
NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false};
bool is_configuring{false};
+ bool is_initalized{false};
bool system_buttons_enabled{true};
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
u32 turbo_button_state{0};
diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp
index cf53c04d9..2cf25a870 100644
--- a/src/core/hid/hid_core.cpp
+++ b/src/core/hid/hid_core.cpp
@@ -6,6 +6,7 @@
#include "core/hid/emulated_controller.h"
#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/hid_util.h"
namespace Core::HID {
@@ -98,11 +99,11 @@ const EmulatedDevices* HIDCore::GetEmulatedDevices() const {
}
EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) {
- return GetEmulatedController(IndexToNpadIdType(index));
+ return GetEmulatedController(Service::HID::IndexToNpadIdType(index));
}
const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const {
- return GetEmulatedController(IndexToNpadIdType(index));
+ return GetEmulatedController(Service::HID::IndexToNpadIdType(index));
}
void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index 00beb40dd..4bf285f36 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -8,6 +8,7 @@
#include "common/common_types.h"
#include "common/point.h"
#include "common/uuid.h"
+#include "common/vector_math.h"
namespace Core::HID {
@@ -218,6 +219,13 @@ enum class NpadIdType : u32 {
Invalid = 0xFFFFFFFF,
};
+enum class NpadInterfaceType : u8 {
+ Bluetooth = 1,
+ Rail = 2,
+ Usb = 3,
+ Embedded = 4,
+};
+
// This is nn::hid::NpadStyleIndex
enum class NpadStyleIndex : u8 {
None = 0,
@@ -302,6 +310,15 @@ enum class TouchScreenModeForNx : u8 {
Heat2,
};
+// This is nn::hid::system::NpadBatteryLevel
+enum class NpadBatteryLevel : u32 {
+ Empty,
+ Critical,
+ Low,
+ High,
+ Full,
+};
+
// This is nn::hid::NpadStyleTag
struct NpadStyleTag {
union {
@@ -347,6 +364,14 @@ struct TouchState {
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
+struct TouchFinger {
+ u64 last_touch{};
+ Common::Point<float> position{};
+ u32 id{};
+ TouchAttribute attribute{};
+ bool pressed{};
+};
+
// This is nn::hid::TouchScreenConfigurationForNx
struct TouchScreenConfigurationForNx {
TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
@@ -385,16 +410,12 @@ struct NpadGcTriggerState {
};
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
-// This is nn::hid::system::NpadBatteryLevel
-using NpadBatteryLevel = u32;
-static_assert(sizeof(NpadBatteryLevel) == 0x4, "NpadBatteryLevel is an invalid size");
-
// This is nn::hid::system::NpadPowerInfo
struct NpadPowerInfo {
bool is_powered{};
bool is_charging{};
INSERT_PADDING_BYTES(0x6);
- NpadBatteryLevel battery_level{8};
+ NpadBatteryLevel battery_level{NpadBatteryLevel::Full};
};
static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
@@ -578,6 +599,29 @@ struct SixAxisSensorIcInformation {
static_assert(sizeof(SixAxisSensorIcInformation) == 0xC8,
"SixAxisSensorIcInformation is an invalid size");
+// This is nn::hid::SixAxisSensorAttribute
+struct SixAxisSensorAttribute {
+ union {
+ u32 raw{};
+ BitField<0, 1, u32> is_connected;
+ BitField<1, 1, u32> is_interpolated;
+ };
+};
+static_assert(sizeof(SixAxisSensorAttribute) == 4, "SixAxisSensorAttribute is an invalid size");
+
+// This is nn::hid::SixAxisSensorState
+struct SixAxisSensorState {
+ s64 delta_time{};
+ s64 sampling_number{};
+ Common::Vec3f accel{};
+ Common::Vec3f gyro{};
+ Common::Vec3f rotation{};
+ std::array<Common::Vec3f, 3> orientation{};
+ SixAxisSensorAttribute attribute{};
+ INSERT_PADDING_BYTES(4); // Reserved
+};
+static_assert(sizeof(SixAxisSensorState) == 0x60, "SixAxisSensorState is an invalid size");
+
// This is nn::hid::VibrationDeviceHandle
struct VibrationDeviceHandle {
NpadStyleIndex npad_type{NpadStyleIndex::None};
@@ -688,60 +732,4 @@ struct UniquePadId {
};
static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size");
-/// Converts a NpadIdType to an array index.
-constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
- switch (npad_id_type) {
- case NpadIdType::Player1:
- return 0;
- case NpadIdType::Player2:
- return 1;
- case NpadIdType::Player3:
- return 2;
- case NpadIdType::Player4:
- return 3;
- case NpadIdType::Player5:
- return 4;
- case NpadIdType::Player6:
- return 5;
- case NpadIdType::Player7:
- return 6;
- case NpadIdType::Player8:
- return 7;
- case NpadIdType::Handheld:
- return 8;
- case NpadIdType::Other:
- return 9;
- default:
- return 0;
- }
-}
-
-/// Converts an array index to a NpadIdType
-constexpr NpadIdType IndexToNpadIdType(size_t index) {
- switch (index) {
- case 0:
- return NpadIdType::Player1;
- case 1:
- return NpadIdType::Player2;
- case 2:
- return NpadIdType::Player3;
- case 3:
- return NpadIdType::Player4;
- case 4:
- return NpadIdType::Player5;
- case 5:
- return NpadIdType::Player6;
- case 6:
- return NpadIdType::Player7;
- case 7:
- return NpadIdType::Player8;
- case 8:
- return NpadIdType::Handheld;
- case 9:
- return NpadIdType::Other;
- default:
- return NpadIdType::Invalid;
- }
-}
-
} // namespace Core::HID
diff --git a/src/core/hid/input_interpreter.cpp b/src/core/hid/input_interpreter.cpp
index 76d6b8ab0..a6bdd28f2 100644
--- a/src/core/hid/input_interpreter.cpp
+++ b/src/core/hid/input_interpreter.cpp
@@ -5,21 +5,22 @@
#include "core/hid/hid_types.h"
#include "core/hid/input_interpreter.h"
#include "core/hle/service/hid/controllers/npad.h"
-#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/hid/hid_server.h"
+#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/sm/sm.h"
InputInterpreter::InputInterpreter(Core::System& system)
: npad{system.ServiceManager()
- .GetService<Service::HID::Hid>("hid")
- ->GetAppletResource()
- ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)} {
+ .GetService<Service::HID::IHidServer>("hid")
+ ->GetResourceManager()
+ ->GetNpad()} {
ResetButtonStates();
}
InputInterpreter::~InputInterpreter() = default;
void InputInterpreter::PollInput() {
- const auto button_state = npad.GetAndResetPressState();
+ const auto button_state = npad->GetAndResetPressState();
previous_index = current_index;
current_index = (current_index + 1) % button_states.size();
diff --git a/src/core/hid/input_interpreter.h b/src/core/hid/input_interpreter.h
index 8c521b381..3569aac93 100644
--- a/src/core/hid/input_interpreter.h
+++ b/src/core/hid/input_interpreter.h
@@ -16,7 +16,7 @@ enum class NpadButton : u64;
}
namespace Service::HID {
-class Controller_NPad;
+class NPad;
}
/**
@@ -101,7 +101,7 @@ public:
}
private:
- Service::HID::Controller_NPad& npad;
+ std::shared_ptr<Service::HID::NPad> npad;
/// Stores 9 consecutive button states polled from HID.
std::array<Core::HID::NpadButton, 9> button_states{};
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
index 4cfdf4558..37fa39a73 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
@@ -8,7 +8,11 @@
#include "core/hle/kernel/board/nintendo/nx/k_system_control.h"
#include "core/hle/kernel/board/nintendo/nx/secure_monitor.h"
+#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_trace.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/svc_results.h"
namespace Kernel::Board::Nintendo::Nx {
@@ -30,6 +34,8 @@ constexpr const std::size_t RequiredNonSecureSystemMemorySize =
constexpr const std::size_t RequiredNonSecureSystemMemorySizeWithFatal =
RequiredNonSecureSystemMemorySize + impl::RequiredNonSecureSystemMemorySizeViFatal;
+constexpr const std::size_t SecureAlignment = 128_KiB;
+
namespace {
using namespace Common::Literals;
@@ -183,4 +189,66 @@ u64 KSystemControl::GenerateRandomRange(u64 min, u64 max) {
return GenerateUniformRange(min, max, GenerateRandomU64);
}
+size_t KSystemControl::CalculateRequiredSecureMemorySize(size_t size, u32 pool) {
+ if (pool == static_cast<u32>(KMemoryManager::Pool::Applet)) {
+ return 0;
+ } else {
+ // return KSystemControlBase::CalculateRequiredSecureMemorySize(size, pool);
+ return size;
+ }
+}
+
+Result KSystemControl::AllocateSecureMemory(KernelCore& kernel, KVirtualAddress* out, size_t size,
+ u32 pool) {
+ // Applet secure memory is handled separately.
+ UNIMPLEMENTED_IF(pool == static_cast<u32>(KMemoryManager::Pool::Applet));
+
+ // Ensure the size is aligned.
+ const size_t alignment =
+ (pool == static_cast<u32>(KMemoryManager::Pool::System) ? PageSize : SecureAlignment);
+ R_UNLESS(Common::IsAligned(size, alignment), ResultInvalidSize);
+
+ // Allocate the memory.
+ const size_t num_pages = size / PageSize;
+ const KPhysicalAddress paddr = kernel.MemoryManager().AllocateAndOpenContinuous(
+ num_pages, alignment / PageSize,
+ KMemoryManager::EncodeOption(static_cast<KMemoryManager::Pool>(pool),
+ KMemoryManager::Direction::FromFront));
+ R_UNLESS(paddr != 0, ResultOutOfMemory);
+
+ // Ensure we don't leak references to the memory on error.
+ ON_RESULT_FAILURE {
+ kernel.MemoryManager().Close(paddr, num_pages);
+ };
+
+ // We succeeded.
+ *out = KPageTable::GetHeapVirtualAddress(kernel, paddr);
+ R_SUCCEED();
+}
+
+void KSystemControl::FreeSecureMemory(KernelCore& kernel, KVirtualAddress address, size_t size,
+ u32 pool) {
+ // Applet secure memory is handled separately.
+ UNIMPLEMENTED_IF(pool == static_cast<u32>(KMemoryManager::Pool::Applet));
+
+ // Ensure the size is aligned.
+ const size_t alignment =
+ (pool == static_cast<u32>(KMemoryManager::Pool::System) ? PageSize : SecureAlignment);
+ ASSERT(Common::IsAligned(GetInteger(address), alignment));
+ ASSERT(Common::IsAligned(size, alignment));
+
+ // Close the secure region's pages.
+ kernel.MemoryManager().Close(KPageTable::GetHeapPhysicalAddress(kernel, address),
+ size / PageSize);
+}
+
+// Insecure Memory.
+KResourceLimit* KSystemControl::GetInsecureMemoryResourceLimit(KernelCore& kernel) {
+ return kernel.GetSystemResourceLimit();
+}
+
+u32 KSystemControl::GetInsecureMemoryPool() {
+ return static_cast<u32>(KMemoryManager::Pool::SystemNonSecure);
+}
+
} // namespace Kernel::Board::Nintendo::Nx
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
index b477e8193..60c5e58b7 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
@@ -4,6 +4,12 @@
#pragma once
#include "core/hle/kernel/k_typed_address.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+class KernelCore;
+class KResourceLimit;
+} // namespace Kernel
namespace Kernel::Board::Nintendo::Nx {
@@ -25,8 +31,20 @@ public:
static std::size_t GetMinimumNonSecureSystemPoolSize();
};
+ // Randomness.
static u64 GenerateRandomRange(u64 min, u64 max);
static u64 GenerateRandomU64();
+
+ // Secure Memory.
+ static size_t CalculateRequiredSecureMemorySize(size_t size, u32 pool);
+ static Result AllocateSecureMemory(KernelCore& kernel, KVirtualAddress* out, size_t size,
+ u32 pool);
+ static void FreeSecureMemory(KernelCore& kernel, KVirtualAddress address, size_t size,
+ u32 pool);
+
+ // Insecure Memory.
+ static KResourceLimit* GetInsecureMemoryResourceLimit(KernelCore& kernel);
+ static u32 GetInsecureMemoryPool();
};
} // namespace Kernel::Board::Nintendo::Nx
diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h
index af1af2b78..4d2d0098e 100644
--- a/src/core/hle/kernel/code_set.h
+++ b/src/core/hle/kernel/code_set.h
@@ -75,12 +75,26 @@ struct CodeSet final {
return segments[2];
}
+#ifdef HAS_NCE
+ Segment& PatchSegment() {
+ return patch_segment;
+ }
+
+ const Segment& PatchSegment() const {
+ return patch_segment;
+ }
+#endif
+
/// The overall data that backs this code set.
Kernel::PhysicalMemory memory;
/// The segments that comprise this code set.
std::array<Segment, 3> segments;
+#ifdef HAS_NCE
+ Segment patch_segment;
+#endif
+
/// The entry point address for this code set.
KProcessAddress entrypoint = 0;
};
diff --git a/src/core/hle/kernel/k_address_space_info.cpp b/src/core/hle/kernel/k_address_space_info.cpp
index 32173e52b..23258071e 100644
--- a/src/core/hle/kernel/k_address_space_info.cpp
+++ b/src/core/hle/kernel/k_address_space_info.cpp
@@ -25,8 +25,8 @@ constexpr std::array<KAddressSpaceInfo, 13> AddressSpaceInfos{{
{ .bit_width = 36, .address = 2_GiB , .size = 64_GiB - 2_GiB , .type = KAddressSpaceInfo::Type::MapLarge, },
{ .bit_width = 36, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, },
{ .bit_width = 36, .address = Size_Invalid, .size = 6_GiB , .type = KAddressSpaceInfo::Type::Alias, },
-#ifdef ANDROID
- // With Android, we use a 38-bit address space due to memory limitations. This should (safely) truncate ASLR region.
+#ifdef HAS_NCE
+ // With NCE, we use a 38-bit address space due to memory limitations. This should (safely) truncate ASLR region.
{ .bit_width = 39, .address = 128_MiB , .size = 256_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, },
#else
{ .bit_width = 39, .address = 128_MiB , .size = 512_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, },
diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp
index e7da7a21d..274fee493 100644
--- a/src/core/hle/kernel/k_capabilities.cpp
+++ b/src/core/hle/kernel/k_capabilities.cpp
@@ -4,14 +4,16 @@
#include "core/hardware_properties.h"
#include "core/hle/kernel/k_capabilities.h"
#include "core/hle/kernel/k_memory_layout.h"
-#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_process_page_table.h"
+#include "core/hle/kernel/k_trace.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/svc_version.h"
namespace Kernel {
-Result KCapabilities::InitializeForKip(std::span<const u32> kern_caps, KPageTable* page_table) {
+Result KCapabilities::InitializeForKip(std::span<const u32> kern_caps,
+ KProcessPageTable* page_table) {
// We're initializing an initial process.
m_svc_access_flags.reset();
m_irq_access_flags.reset();
@@ -41,7 +43,8 @@ Result KCapabilities::InitializeForKip(std::span<const u32> kern_caps, KPageTabl
R_RETURN(this->SetCapabilities(kern_caps, page_table));
}
-Result KCapabilities::InitializeForUser(std::span<const u32> user_caps, KPageTable* page_table) {
+Result KCapabilities::InitializeForUser(std::span<const u32> user_caps,
+ KProcessPageTable* page_table) {
// We're initializing a user process.
m_svc_access_flags.reset();
m_irq_access_flags.reset();
@@ -121,7 +124,7 @@ Result KCapabilities::SetSyscallMaskCapability(const u32 cap, u32& set_svc) {
R_SUCCEED();
}
-Result KCapabilities::MapRange_(const u32 cap, const u32 size_cap, KPageTable* page_table) {
+Result KCapabilities::MapRange_(const u32 cap, const u32 size_cap, KProcessPageTable* page_table) {
const auto range_pack = MapRange{cap};
const auto size_pack = MapRangeSize{size_cap};
@@ -142,16 +145,13 @@ Result KCapabilities::MapRange_(const u32 cap, const u32 size_cap, KPageTable* p
? KMemoryPermission::UserRead
: KMemoryPermission::UserReadWrite;
if (MapRangeSize{size_cap}.normal) {
- // R_RETURN(page_table->MapStatic(phys_addr, size, perm));
+ R_RETURN(page_table->MapStatic(phys_addr, size, perm));
} else {
- // R_RETURN(page_table->MapIo(phys_addr, size, perm));
+ R_RETURN(page_table->MapIo(phys_addr, size, perm));
}
-
- UNIMPLEMENTED();
- R_SUCCEED();
}
-Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) {
+Result KCapabilities::MapIoPage_(const u32 cap, KProcessPageTable* page_table) {
// Get/validate address/size
const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize;
const size_t num_pages = 1;
@@ -160,10 +160,7 @@ Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) {
R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress);
// Do the mapping.
- // R_RETURN(page_table->MapIo(phys_addr, size, KMemoryPermission_UserReadWrite));
-
- UNIMPLEMENTED();
- R_SUCCEED();
+ R_RETURN(page_table->MapIo(phys_addr, size, KMemoryPermission::UserReadWrite));
}
template <typename F>
@@ -200,13 +197,11 @@ Result KCapabilities::ProcessMapRegionCapability(const u32 cap, F f) {
R_SUCCEED();
}
-Result KCapabilities::MapRegion_(const u32 cap, KPageTable* page_table) {
+Result KCapabilities::MapRegion_(const u32 cap, KProcessPageTable* page_table) {
// Map each region into the process's page table.
return ProcessMapRegionCapability(
- cap, [](KMemoryRegionType region_type, KMemoryPermission perm) -> Result {
- // R_RETURN(page_table->MapRegion(region_type, perm));
- UNIMPLEMENTED();
- R_SUCCEED();
+ cap, [page_table](KMemoryRegionType region_type, KMemoryPermission perm) -> Result {
+ R_RETURN(page_table->MapRegion(region_type, perm));
});
}
@@ -280,7 +275,7 @@ Result KCapabilities::SetDebugFlagsCapability(const u32 cap) {
}
Result KCapabilities::SetCapability(const u32 cap, u32& set_flags, u32& set_svc,
- KPageTable* page_table) {
+ KProcessPageTable* page_table) {
// Validate this is a capability we can act on.
const auto type = GetCapabilityType(cap);
R_UNLESS(type != CapabilityType::Invalid, ResultInvalidArgument);
@@ -318,7 +313,7 @@ Result KCapabilities::SetCapability(const u32 cap, u32& set_flags, u32& set_svc,
}
}
-Result KCapabilities::SetCapabilities(std::span<const u32> caps, KPageTable* page_table) {
+Result KCapabilities::SetCapabilities(std::span<const u32> caps, KProcessPageTable* page_table) {
u32 set_flags = 0, set_svc = 0;
for (size_t i = 0; i < caps.size(); i++) {
@@ -335,6 +330,8 @@ Result KCapabilities::SetCapabilities(std::span<const u32> caps, KPageTable* pag
// Map the range.
R_TRY(this->MapRange_(cap, size_cap, page_table));
+ } else if (GetCapabilityType(cap) == CapabilityType::MapRegion && !IsKTraceEnabled) {
+ continue;
} else {
R_TRY(this->SetCapability(cap, set_flags, set_svc, page_table));
}
diff --git a/src/core/hle/kernel/k_capabilities.h b/src/core/hle/kernel/k_capabilities.h
index de766c811..013d952ad 100644
--- a/src/core/hle/kernel/k_capabilities.h
+++ b/src/core/hle/kernel/k_capabilities.h
@@ -15,15 +15,15 @@
namespace Kernel {
-class KPageTable;
+class KProcessPageTable;
class KernelCore;
class KCapabilities {
public:
constexpr explicit KCapabilities() = default;
- Result InitializeForKip(std::span<const u32> kern_caps, KPageTable* page_table);
- Result InitializeForUser(std::span<const u32> user_caps, KPageTable* page_table);
+ Result InitializeForKip(std::span<const u32> kern_caps, KProcessPageTable* page_table);
+ Result InitializeForUser(std::span<const u32> user_caps, KProcessPageTable* page_table);
static Result CheckCapabilities(KernelCore& kernel, std::span<const u32> user_caps);
@@ -200,8 +200,8 @@ private:
RawCapabilityValue raw;
BitField<0, 15, CapabilityType> id;
- BitField<15, 4, u32> major_version;
- BitField<19, 13, u32> minor_version;
+ BitField<15, 4, u32> minor_version;
+ BitField<19, 13, u32> major_version;
};
union HandleTable {
@@ -264,9 +264,9 @@ private:
Result SetCorePriorityCapability(const u32 cap);
Result SetSyscallMaskCapability(const u32 cap, u32& set_svc);
- Result MapRange_(const u32 cap, const u32 size_cap, KPageTable* page_table);
- Result MapIoPage_(const u32 cap, KPageTable* page_table);
- Result MapRegion_(const u32 cap, KPageTable* page_table);
+ Result MapRange_(const u32 cap, const u32 size_cap, KProcessPageTable* page_table);
+ Result MapIoPage_(const u32 cap, KProcessPageTable* page_table);
+ Result MapRegion_(const u32 cap, KProcessPageTable* page_table);
Result SetInterruptPairCapability(const u32 cap);
Result SetProgramTypeCapability(const u32 cap);
Result SetKernelVersionCapability(const u32 cap);
@@ -277,8 +277,9 @@ private:
static Result ProcessMapRegionCapability(const u32 cap, F f);
static Result CheckMapRegion(KernelCore& kernel, const u32 cap);
- Result SetCapability(const u32 cap, u32& set_flags, u32& set_svc, KPageTable* page_table);
- Result SetCapabilities(std::span<const u32> caps, KPageTable* page_table);
+ Result SetCapability(const u32 cap, u32& set_flags, u32& set_svc,
+ KProcessPageTable* page_table);
+ Result SetCapabilities(std::span<const u32> caps, KProcessPageTable* page_table);
private:
Svc::SvcAccessFlagSet m_svc_access_flags{};
diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp
index efbac0e6a..7633a51fb 100644
--- a/src/core/hle/kernel/k_condition_variable.cpp
+++ b/src/core/hle/kernel/k_condition_variable.cpp
@@ -107,12 +107,12 @@ KConditionVariable::KConditionVariable(Core::System& system)
KConditionVariable::~KConditionVariable() = default;
-Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
- KThread* owner_thread = GetCurrentThreadPointer(m_kernel);
+Result KConditionVariable::SignalToAddress(KernelCore& kernel, KProcessAddress addr) {
+ KThread* owner_thread = GetCurrentThreadPointer(kernel);
// Signal the address.
{
- KScopedSchedulerLock sl(m_kernel);
+ KScopedSchedulerLock sl(kernel);
// Remove waiter thread.
bool has_waiters{};
@@ -133,7 +133,7 @@ Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
// Write the value to userspace.
Result result{ResultSuccess};
- if (WriteToUser(m_kernel, addr, std::addressof(next_value))) [[likely]] {
+ if (WriteToUser(kernel, addr, std::addressof(next_value))) [[likely]] {
result = ResultSuccess;
} else {
result = ResultInvalidCurrentMemory;
@@ -148,28 +148,28 @@ Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
}
}
-Result KConditionVariable::WaitForAddress(Handle handle, KProcessAddress addr, u32 value) {
- KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
- ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(m_kernel);
+Result KConditionVariable::WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr,
+ u32 value) {
+ KThread* cur_thread = GetCurrentThreadPointer(kernel);
+ ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel);
// Wait for the address.
KThread* owner_thread{};
{
- KScopedSchedulerLock sl(m_kernel);
+ KScopedSchedulerLock sl(kernel);
// Check if the thread should terminate.
R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);
// Read the tag from userspace.
u32 test_tag{};
- R_UNLESS(ReadFromUser(m_kernel, std::addressof(test_tag), addr),
- ResultInvalidCurrentMemory);
+ R_UNLESS(ReadFromUser(kernel, std::addressof(test_tag), addr), ResultInvalidCurrentMemory);
// If the tag isn't the handle (with wait mask), we're done.
R_SUCCEED_IF(test_tag != (handle | Svc::HandleWaitMask));
// Get the lock owner thread.
- owner_thread = GetCurrentProcess(m_kernel)
+ owner_thread = GetCurrentProcess(kernel)
.GetHandleTable()
.GetObjectWithoutPseudoHandle<KThread>(handle)
.ReleasePointerUnsafe();
diff --git a/src/core/hle/kernel/k_condition_variable.h b/src/core/hle/kernel/k_condition_variable.h
index 8c2f3ae51..2620c8e39 100644
--- a/src/core/hle/kernel/k_condition_variable.h
+++ b/src/core/hle/kernel/k_condition_variable.h
@@ -24,11 +24,12 @@ public:
explicit KConditionVariable(Core::System& system);
~KConditionVariable();
- // Arbitration
- Result SignalToAddress(KProcessAddress addr);
- Result WaitForAddress(Handle handle, KProcessAddress addr, u32 value);
+ // Arbitration.
+ static Result SignalToAddress(KernelCore& kernel, KProcessAddress addr);
+ static Result WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr,
+ u32 value);
- // Condition variable
+ // Condition variable.
void Signal(u64 cv_key, s32 count);
Result Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout);
diff --git a/src/core/hle/kernel/k_device_address_space.cpp b/src/core/hle/kernel/k_device_address_space.cpp
index f48896715..f0703f795 100644
--- a/src/core/hle/kernel/k_device_address_space.cpp
+++ b/src/core/hle/kernel/k_device_address_space.cpp
@@ -54,7 +54,7 @@ Result KDeviceAddressSpace::Detach(Svc::DeviceName device_name) {
R_SUCCEED();
}
-Result KDeviceAddressSpace::Map(KPageTable* page_table, KProcessAddress process_address,
+Result KDeviceAddressSpace::Map(KProcessPageTable* page_table, KProcessAddress process_address,
size_t size, u64 device_address, u32 option, bool is_aligned) {
// Check that the address falls within the space.
R_UNLESS((m_space_address <= device_address &&
@@ -113,7 +113,7 @@ Result KDeviceAddressSpace::Map(KPageTable* page_table, KProcessAddress process_
R_SUCCEED();
}
-Result KDeviceAddressSpace::Unmap(KPageTable* page_table, KProcessAddress process_address,
+Result KDeviceAddressSpace::Unmap(KProcessPageTable* page_table, KProcessAddress process_address,
size_t size, u64 device_address) {
// Check that the address falls within the space.
R_UNLESS((m_space_address <= device_address &&
diff --git a/src/core/hle/kernel/k_device_address_space.h b/src/core/hle/kernel/k_device_address_space.h
index 18556e3cc..ff0ec8152 100644
--- a/src/core/hle/kernel/k_device_address_space.h
+++ b/src/core/hle/kernel/k_device_address_space.h
@@ -5,7 +5,7 @@
#include <string>
-#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_process_page_table.h"
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/result.h"
@@ -31,23 +31,23 @@ public:
Result Attach(Svc::DeviceName device_name);
Result Detach(Svc::DeviceName device_name);
- Result MapByForce(KPageTable* page_table, KProcessAddress process_address, size_t size,
+ Result MapByForce(KProcessPageTable* page_table, KProcessAddress process_address, size_t size,
u64 device_address, u32 option) {
R_RETURN(this->Map(page_table, process_address, size, device_address, option, false));
}
- Result MapAligned(KPageTable* page_table, KProcessAddress process_address, size_t size,
+ Result MapAligned(KProcessPageTable* page_table, KProcessAddress process_address, size_t size,
u64 device_address, u32 option) {
R_RETURN(this->Map(page_table, process_address, size, device_address, option, true));
}
- Result Unmap(KPageTable* page_table, KProcessAddress process_address, size_t size,
+ Result Unmap(KProcessPageTable* page_table, KProcessAddress process_address, size_t size,
u64 device_address);
static void Initialize();
private:
- Result Map(KPageTable* page_table, KProcessAddress process_address, size_t size,
+ Result Map(KProcessPageTable* page_table, KProcessAddress process_address, size_t size,
u64 device_address, u32 option, bool is_aligned);
private:
diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp
index fe6a20168..22d79569a 100644
--- a/src/core/hle/kernel/k_interrupt_manager.cpp
+++ b/src/core/hle/kernel/k_interrupt_manager.cpp
@@ -22,7 +22,7 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
KScopedSchedulerLock sl{kernel};
// Pin the current thread.
- process->PinCurrentThread(core_id);
+ process->PinCurrentThread();
// Set the interrupt flag for the thread.
GetCurrentThread(kernel).SetInterruptFlag();
diff --git a/src/core/hle/kernel/k_memory_layout.h b/src/core/hle/kernel/k_memory_layout.h
index c8122644f..d7adb3169 100644
--- a/src/core/hle/kernel/k_memory_layout.h
+++ b/src/core/hle/kernel/k_memory_layout.h
@@ -394,6 +394,14 @@ private:
return region.GetEndAddress();
}
+public:
+ static const KMemoryRegion* Find(const KMemoryLayout& layout, KVirtualAddress address) {
+ return Find(address, layout.GetVirtualMemoryRegionTree());
+ }
+ static const KMemoryRegion* Find(const KMemoryLayout& layout, KPhysicalAddress address) {
+ return Find(address, layout.GetPhysicalMemoryRegionTree());
+ }
+
private:
u64 m_linear_phys_to_virt_diff{};
u64 m_linear_virt_to_phys_diff{};
diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp
index 637558e10..0a973ec8c 100644
--- a/src/core/hle/kernel/k_memory_manager.cpp
+++ b/src/core/hle/kernel/k_memory_manager.cpp
@@ -11,6 +11,7 @@
#include "core/hle/kernel/initial_process.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_page_group.h"
+#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
@@ -168,11 +169,37 @@ void KMemoryManager::Initialize(KVirtualAddress management_region, size_t manage
}
Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) {
- UNREACHABLE();
+ const u32 pool_index = static_cast<u32>(pool);
+
+ // Lock the pool.
+ KScopedLightLock lk(m_pool_locks[pool_index]);
+
+ // Check that we don't already have an optimized process.
+ R_UNLESS(!m_has_optimized_process[pool_index], ResultBusy);
+
+ // Set the optimized process id.
+ m_optimized_process_ids[pool_index] = process_id;
+ m_has_optimized_process[pool_index] = true;
+
+ // Clear the management area for the optimized process.
+ for (auto* manager = this->GetFirstManager(pool, Direction::FromFront); manager != nullptr;
+ manager = this->GetNextManager(manager, Direction::FromFront)) {
+ manager->InitializeOptimizedMemory(m_system.Kernel());
+ }
+
+ R_SUCCEED();
}
void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) {
- UNREACHABLE();
+ const u32 pool_index = static_cast<u32>(pool);
+
+ // Lock the pool.
+ KScopedLightLock lk(m_pool_locks[pool_index]);
+
+ // If the process was optimized, clear it.
+ if (m_has_optimized_process[pool_index] && m_optimized_process_ids[pool_index] == process_id) {
+ m_has_optimized_process[pool_index] = false;
+ }
}
KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages,
@@ -207,7 +234,7 @@ KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, siz
// Maintain the optimized memory bitmap, if we should.
if (m_has_optimized_process[static_cast<size_t>(pool)]) {
- UNIMPLEMENTED();
+ chosen_manager->TrackUnoptimizedAllocation(m_system.Kernel(), allocated_block, num_pages);
}
// Open the first reference to the pages.
@@ -255,7 +282,8 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
// Maintain the optimized memory bitmap, if we should.
if (unoptimized) {
- UNIMPLEMENTED();
+ cur_manager->TrackUnoptimizedAllocation(m_system.Kernel(), allocated_block,
+ pages_per_alloc);
}
num_pages -= pages_per_alloc;
@@ -358,8 +386,8 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
// Process part or all of the block.
const size_t cur_pages =
std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
- any_new =
- manager.ProcessOptimizedAllocation(cur_address, cur_pages, fill_pattern);
+ any_new = manager.ProcessOptimizedAllocation(m_system.Kernel(), cur_address,
+ cur_pages, fill_pattern);
// Advance.
cur_address += cur_pages * PageSize;
@@ -382,7 +410,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
// Track some or all of the current pages.
const size_t cur_pages =
std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
- manager.TrackOptimizedAllocation(cur_address, cur_pages);
+ manager.TrackOptimizedAllocation(m_system.Kernel(), cur_address, cur_pages);
// Advance.
cur_address += cur_pages * PageSize;
@@ -427,17 +455,82 @@ size_t KMemoryManager::Impl::Initialize(KPhysicalAddress address, size_t size,
return total_management_size;
}
-void KMemoryManager::Impl::TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages) {
- UNREACHABLE();
+void KMemoryManager::Impl::InitializeOptimizedMemory(KernelCore& kernel) {
+ auto optimize_pa = KPageTable::GetHeapPhysicalAddress(kernel, m_management_region);
+ auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
+
+ std::memset(optimize_map, 0, CalculateOptimizedProcessOverheadSize(m_heap.GetSize()));
}
-void KMemoryManager::Impl::TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages) {
- UNREACHABLE();
+void KMemoryManager::Impl::TrackUnoptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages) {
+ auto optimize_pa = KPageTable::GetHeapPhysicalAddress(kernel, m_management_region);
+ auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
+
+ // Get the range we're tracking.
+ size_t offset = this->GetPageOffset(block);
+ const size_t last = offset + num_pages - 1;
+
+ // Track.
+ while (offset <= last) {
+ // Mark the page as not being optimized-allocated.
+ optimize_map[offset / Common::BitSize<u64>()] &=
+ ~(u64(1) << (offset % Common::BitSize<u64>()));
+
+ offset++;
+ }
+}
+
+void KMemoryManager::Impl::TrackOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages) {
+ auto optimize_pa = KPageTable::GetHeapPhysicalAddress(kernel, m_management_region);
+ auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
+
+ // Get the range we're tracking.
+ size_t offset = this->GetPageOffset(block);
+ const size_t last = offset + num_pages - 1;
+
+ // Track.
+ while (offset <= last) {
+ // Mark the page as being optimized-allocated.
+ optimize_map[offset / Common::BitSize<u64>()] |=
+ (u64(1) << (offset % Common::BitSize<u64>()));
+
+ offset++;
+ }
}
-bool KMemoryManager::Impl::ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages,
- u8 fill_pattern) {
- UNREACHABLE();
+bool KMemoryManager::Impl::ProcessOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages, u8 fill_pattern) {
+ auto& device_memory = kernel.System().DeviceMemory();
+ auto optimize_pa = KPageTable::GetHeapPhysicalAddress(kernel, m_management_region);
+ auto* optimize_map = device_memory.GetPointer<u64>(optimize_pa);
+
+ // We want to return whether any pages were newly allocated.
+ bool any_new = false;
+
+ // Get the range we're processing.
+ size_t offset = this->GetPageOffset(block);
+ const size_t last = offset + num_pages - 1;
+
+ // Process.
+ while (offset <= last) {
+ // Check if the page has been optimized-allocated before.
+ if ((optimize_map[offset / Common::BitSize<u64>()] &
+ (u64(1) << (offset % Common::BitSize<u64>()))) == 0) {
+ // If not, it's new.
+ any_new = true;
+
+ // Fill the page.
+ auto* ptr = device_memory.GetPointer<u8>(m_heap.GetAddress());
+ std::memset(ptr + offset * PageSize, fill_pattern, PageSize);
+ }
+
+ offset++;
+ }
+
+ // Return the number of pages we processed.
+ return any_new;
}
size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) {
diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h
index 7e4b41319..c5a487af9 100644
--- a/src/core/hle/kernel/k_memory_manager.h
+++ b/src/core/hle/kernel/k_memory_manager.h
@@ -216,14 +216,14 @@ private:
m_heap.SetInitialUsedSize(reserved_size);
}
- void InitializeOptimizedMemory() {
- UNIMPLEMENTED();
- }
+ void InitializeOptimizedMemory(KernelCore& kernel);
- void TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages);
- void TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages);
+ void TrackUnoptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages);
+ void TrackOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, size_t num_pages);
- bool ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, u8 fill_pattern);
+ bool ProcessOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages, u8 fill_pattern);
constexpr Pool GetPool() const {
return m_pool;
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
deleted file mode 100644
index 217ccbae3..000000000
--- a/src/core/hle/kernel/k_page_table.cpp
+++ /dev/null
@@ -1,3519 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/alignment.h"
-#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"
-#include "core/hle/kernel/k_memory_block_manager.h"
-#include "core/hle/kernel/k_page_group.h"
-#include "core/hle/kernel/k_page_table.h"
-#include "core/hle/kernel/k_process.h"
-#include "core/hle/kernel/k_resource_limit.h"
-#include "core/hle/kernel/k_scoped_resource_reservation.h"
-#include "core/hle/kernel/k_system_control.h"
-#include "core/hle/kernel/k_system_resource.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/svc_results.h"
-#include "core/memory.h"
-
-namespace Kernel {
-
-namespace {
-
-class KScopedLightLockPair {
- YUZU_NON_COPYABLE(KScopedLightLockPair);
- YUZU_NON_MOVEABLE(KScopedLightLockPair);
-
-private:
- KLightLock* m_lower;
- KLightLock* m_upper;
-
-public:
- KScopedLightLockPair(KLightLock& lhs, KLightLock& rhs) {
- // Ensure our locks are in a consistent order.
- if (std::addressof(lhs) <= std::addressof(rhs)) {
- m_lower = std::addressof(lhs);
- m_upper = std::addressof(rhs);
- } else {
- m_lower = std::addressof(rhs);
- m_upper = std::addressof(lhs);
- }
-
- // Acquire both locks.
- m_lower->Lock();
- if (m_lower != m_upper) {
- m_upper->Lock();
- }
- }
-
- ~KScopedLightLockPair() {
- // Unlock the upper lock.
- if (m_upper != nullptr && m_upper != m_lower) {
- m_upper->Unlock();
- }
-
- // Unlock the lower lock.
- if (m_lower != nullptr) {
- m_lower->Unlock();
- }
- }
-
-public:
- // Utility.
- void TryUnlockHalf(KLightLock& lock) {
- // Only allow unlocking if the lock is half the pair.
- if (m_lower != m_upper) {
- // We want to be sure the lock is one we own.
- if (m_lower == std::addressof(lock)) {
- lock.Unlock();
- m_lower = nullptr;
- } else if (m_upper == std::addressof(lock)) {
- lock.Unlock();
- m_upper = nullptr;
- }
- }
- }
-};
-
-using namespace Common::Literals;
-
-constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) {
- switch (as_type) {
- case FileSys::ProgramAddressSpaceType::Is32Bit:
- case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
- return 32;
- case FileSys::ProgramAddressSpaceType::Is36Bit:
- return 36;
- case FileSys::ProgramAddressSpaceType::Is39Bit:
- return 39;
- default:
- ASSERT(false);
- return {};
- }
-}
-
-} // namespace
-
-KPageTable::KPageTable(Core::System& system_)
- : m_general_lock{system_.Kernel()},
- m_map_physical_memory_lock{system_.Kernel()}, m_system{system_}, m_kernel{system_.Kernel()} {}
-
-KPageTable::~KPageTable() = default;
-
-Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
- bool enable_das_merge, bool from_back,
- KMemoryManager::Pool pool, KProcessAddress code_addr,
- size_t code_size, KSystemResource* system_resource,
- KResourceLimit* resource_limit,
- Core::Memory::Memory& memory) {
-
- const auto GetSpaceStart = [this](KAddressSpaceInfo::Type type) {
- return KAddressSpaceInfo::GetAddressSpaceStart(m_address_space_width, type);
- };
- const auto GetSpaceSize = [this](KAddressSpaceInfo::Type type) {
- return KAddressSpaceInfo::GetAddressSpaceSize(m_address_space_width, type);
- };
-
- // Set the tracking memory
- m_memory = std::addressof(memory);
-
- // Set our width and heap/alias sizes
- m_address_space_width = GetAddressSpaceWidthFromType(as_type);
- const KProcessAddress start = 0;
- const KProcessAddress end{1ULL << m_address_space_width};
- size_t alias_region_size{GetSpaceSize(KAddressSpaceInfo::Type::Alias)};
- size_t heap_region_size{GetSpaceSize(KAddressSpaceInfo::Type::Heap)};
-
- ASSERT(code_addr < code_addr + code_size);
- ASSERT(code_addr + code_size - 1 <= end - 1);
-
- // Adjust heap/alias size if we don't have an alias region
- if (as_type == FileSys::ProgramAddressSpaceType::Is32BitNoMap) {
- heap_region_size += alias_region_size;
- alias_region_size = 0;
- }
-
- // Set code regions and determine remaining
- constexpr size_t RegionAlignment{2_MiB};
- KProcessAddress process_code_start{};
- KProcessAddress process_code_end{};
- size_t stack_region_size{};
- size_t kernel_map_region_size{};
-
- if (m_address_space_width == 39) {
- alias_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Alias);
- heap_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Heap);
- stack_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Stack);
- kernel_map_region_size = GetSpaceSize(KAddressSpaceInfo::Type::MapSmall);
- m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::Map39Bit);
- m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::Map39Bit);
- m_alias_code_region_start = m_code_region_start;
- m_alias_code_region_end = m_code_region_end;
- process_code_start = Common::AlignDown(GetInteger(code_addr), RegionAlignment);
- process_code_end = Common::AlignUp(GetInteger(code_addr) + code_size, RegionAlignment);
- } else {
- stack_region_size = 0;
- kernel_map_region_size = 0;
- m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::MapSmall);
- m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::MapSmall);
- m_stack_region_start = m_code_region_start;
- m_alias_code_region_start = m_code_region_start;
- m_alias_code_region_end = GetSpaceStart(KAddressSpaceInfo::Type::MapLarge) +
- GetSpaceSize(KAddressSpaceInfo::Type::MapLarge);
- m_stack_region_end = m_code_region_end;
- m_kernel_map_region_start = m_code_region_start;
- m_kernel_map_region_end = m_code_region_end;
- process_code_start = m_code_region_start;
- process_code_end = m_code_region_end;
- }
-
- // Set other basic fields
- m_enable_aslr = enable_aslr;
- m_enable_device_address_space_merge = enable_das_merge;
- m_address_space_start = start;
- m_address_space_end = end;
- m_is_kernel = false;
- m_memory_block_slab_manager = system_resource->GetMemoryBlockSlabManagerPointer();
- m_block_info_manager = system_resource->GetBlockInfoManagerPointer();
- m_resource_limit = resource_limit;
-
- // Determine the region we can place our undetermineds in
- KProcessAddress alloc_start{};
- size_t alloc_size{};
- if ((process_code_start - m_code_region_start) >= (end - process_code_end)) {
- alloc_start = m_code_region_start;
- alloc_size = process_code_start - m_code_region_start;
- } else {
- alloc_start = process_code_end;
- alloc_size = end - process_code_end;
- }
- const size_t needed_size =
- (alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size);
- R_UNLESS(alloc_size >= needed_size, ResultOutOfMemory);
-
- const size_t remaining_size{alloc_size - needed_size};
-
- // Determine random placements for each region
- size_t alias_rnd{}, heap_rnd{}, stack_rnd{}, kmap_rnd{};
- if (enable_aslr) {
- alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
- RegionAlignment;
- heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
- RegionAlignment;
- stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
- RegionAlignment;
- kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
- RegionAlignment;
- }
-
- // Setup heap and alias regions
- m_alias_region_start = alloc_start + alias_rnd;
- m_alias_region_end = m_alias_region_start + alias_region_size;
- m_heap_region_start = alloc_start + heap_rnd;
- m_heap_region_end = m_heap_region_start + heap_region_size;
-
- if (alias_rnd <= heap_rnd) {
- m_heap_region_start += alias_region_size;
- m_heap_region_end += alias_region_size;
- } else {
- m_alias_region_start += heap_region_size;
- m_alias_region_end += heap_region_size;
- }
-
- // Setup stack region
- if (stack_region_size) {
- m_stack_region_start = alloc_start + stack_rnd;
- m_stack_region_end = m_stack_region_start + stack_region_size;
-
- if (alias_rnd < stack_rnd) {
- m_stack_region_start += alias_region_size;
- m_stack_region_end += alias_region_size;
- } else {
- m_alias_region_start += stack_region_size;
- m_alias_region_end += stack_region_size;
- }
-
- if (heap_rnd < stack_rnd) {
- m_stack_region_start += heap_region_size;
- m_stack_region_end += heap_region_size;
- } else {
- m_heap_region_start += stack_region_size;
- m_heap_region_end += stack_region_size;
- }
- }
-
- // Setup kernel map region
- if (kernel_map_region_size) {
- m_kernel_map_region_start = alloc_start + kmap_rnd;
- m_kernel_map_region_end = m_kernel_map_region_start + kernel_map_region_size;
-
- if (alias_rnd < kmap_rnd) {
- m_kernel_map_region_start += alias_region_size;
- m_kernel_map_region_end += alias_region_size;
- } else {
- m_alias_region_start += kernel_map_region_size;
- m_alias_region_end += kernel_map_region_size;
- }
-
- if (heap_rnd < kmap_rnd) {
- m_kernel_map_region_start += heap_region_size;
- m_kernel_map_region_end += heap_region_size;
- } else {
- m_heap_region_start += kernel_map_region_size;
- m_heap_region_end += kernel_map_region_size;
- }
-
- if (stack_region_size) {
- if (stack_rnd < kmap_rnd) {
- m_kernel_map_region_start += stack_region_size;
- m_kernel_map_region_end += stack_region_size;
- } else {
- m_stack_region_start += kernel_map_region_size;
- m_stack_region_end += kernel_map_region_size;
- }
- }
- }
-
- // Set heap and fill members.
- m_current_heap_end = m_heap_region_start;
- m_max_heap_size = 0;
- m_mapped_physical_memory_size = 0;
- m_mapped_unsafe_physical_memory = 0;
- m_mapped_insecure_memory = 0;
- m_mapped_ipc_server_memory = 0;
-
- m_heap_fill_value = 0;
- m_ipc_fill_value = 0;
- m_stack_fill_value = 0;
-
- // Set allocation option.
- m_allocate_option =
- KMemoryManager::EncodeOption(pool, from_back ? KMemoryManager::Direction::FromBack
- : KMemoryManager::Direction::FromFront);
-
- // Ensure that we regions inside our address space
- auto IsInAddressSpace = [&](KProcessAddress addr) {
- return m_address_space_start <= addr && addr <= m_address_space_end;
- };
- ASSERT(IsInAddressSpace(m_alias_region_start));
- ASSERT(IsInAddressSpace(m_alias_region_end));
- ASSERT(IsInAddressSpace(m_heap_region_start));
- ASSERT(IsInAddressSpace(m_heap_region_end));
- ASSERT(IsInAddressSpace(m_stack_region_start));
- ASSERT(IsInAddressSpace(m_stack_region_end));
- ASSERT(IsInAddressSpace(m_kernel_map_region_start));
- ASSERT(IsInAddressSpace(m_kernel_map_region_end));
-
- // Ensure that we selected regions that don't overlap
- const KProcessAddress alias_start{m_alias_region_start};
- const KProcessAddress alias_last{m_alias_region_end - 1};
- const KProcessAddress heap_start{m_heap_region_start};
- const KProcessAddress heap_last{m_heap_region_end - 1};
- const KProcessAddress stack_start{m_stack_region_start};
- const KProcessAddress stack_last{m_stack_region_end - 1};
- const KProcessAddress kmap_start{m_kernel_map_region_start};
- const KProcessAddress kmap_last{m_kernel_map_region_end - 1};
- ASSERT(alias_last < heap_start || heap_last < alias_start);
- ASSERT(alias_last < stack_start || stack_last < alias_start);
- ASSERT(alias_last < kmap_start || kmap_last < alias_start);
- ASSERT(heap_last < stack_start || stack_last < heap_start);
- ASSERT(heap_last < kmap_start || kmap_last < heap_start);
-
- m_current_heap_end = m_heap_region_start;
- m_max_heap_size = 0;
- m_mapped_physical_memory_size = 0;
- m_memory_pool = pool;
-
- m_page_table_impl = std::make_unique<Common::PageTable>();
- m_page_table_impl->Resize(m_address_space_width, PageBits);
-
- // Initialize our memory block manager.
- R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end,
- m_memory_block_slab_manager));
-}
-
-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, std::move(HostUnmapCallback));
-
- // Release any insecure mapped memory.
- if (m_mapped_insecure_memory) {
- UNIMPLEMENTED();
- }
-
- // Release any ipc server memory.
- if (m_mapped_ipc_server_memory) {
- UNIMPLEMENTED();
- }
-
- // Close the backing page table, as the destructor is not called for guest objects.
- m_page_table_impl.reset();
-}
-
-Result KPageTable::MapProcessCode(KProcessAddress addr, size_t num_pages, KMemoryState state,
- KMemoryPermission perm) {
- const u64 size{num_pages * PageSize};
-
- // Validate the mapping request.
- R_UNLESS(this->CanContain(addr, size, state), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Verify that the destination memory is unmapped.
- R_TRY(this->CheckMemoryState(addr, size, KMemoryState::All, KMemoryState::Free,
- KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::None, KMemoryAttribute::None));
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager);
-
- // Allocate and open.
- KPageGroup pg{m_kernel, m_block_info_manager};
- R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
- &pg, num_pages,
- KMemoryManager::EncodeOption(KMemoryManager::Pool::Application, m_allocation_option)));
-
- R_TRY(Operate(addr, num_pages, pg, OperationType::MapGroup));
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
- KMemoryBlockDisableMergeAttribute::None);
-
- R_SUCCEED();
-}
-
-Result KPageTable::MapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address,
- size_t size) {
- // Validate the mapping request.
- R_UNLESS(this->CanContain(dst_address, size, KMemoryState::AliasCode),
- ResultInvalidMemoryRegion);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Verify that the source memory is normal heap.
- KMemoryState src_state{};
- KMemoryPermission src_perm{};
- size_t num_src_allocator_blocks{};
- R_TRY(this->CheckMemoryState(&src_state, &src_perm, nullptr, &num_src_allocator_blocks,
- src_address, size, KMemoryState::All, KMemoryState::Normal,
- KMemoryPermission::All, KMemoryPermission::UserReadWrite,
- KMemoryAttribute::All, KMemoryAttribute::None));
-
- // Verify that the destination memory is unmapped.
- size_t num_dst_allocator_blocks{};
- R_TRY(this->CheckMemoryState(&num_dst_allocator_blocks, dst_address, size, KMemoryState::All,
- KMemoryState::Free, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::None,
- KMemoryAttribute::None));
-
- // Create an update allocator for the source.
- Result src_allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
- m_memory_block_slab_manager,
- num_src_allocator_blocks);
- R_TRY(src_allocator_result);
-
- // Create an update allocator for the destination.
- Result dst_allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
- m_memory_block_slab_manager,
- num_dst_allocator_blocks);
- R_TRY(dst_allocator_result);
-
- // Map the code memory.
- {
- // Determine the number of pages being operated on.
- const size_t num_pages = size / PageSize;
-
- // Create page groups for the memory being mapped.
- KPageGroup pg{m_kernel, m_block_info_manager};
- AddRegionToPages(src_address, num_pages, pg);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Reprotect the source as kernel-read/not mapped.
- const auto new_perm = static_cast<KMemoryPermission>(KMemoryPermission::KernelRead |
- KMemoryPermission::NotMapped);
- R_TRY(Operate(src_address, num_pages, new_perm, OperationType::ChangePermissions));
-
- // Ensure that we unprotect the source pages on failure.
- auto unprot_guard = SCOPE_GUARD({
- ASSERT(this->Operate(src_address, num_pages, src_perm, OperationType::ChangePermissions)
- .IsSuccess());
- });
-
- // Map the alias pages.
- const KPageProperties dst_properties = {new_perm, false, false,
- DisableMergeAttribute::DisableHead};
- R_TRY(
- this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_properties, false));
-
- // We successfully mapped the alias pages, so we don't need to unprotect the src pages on
- // failure.
- unprot_guard.Cancel();
-
- // Apply the memory block updates.
- m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages,
- src_state, new_perm, KMemoryAttribute::Locked,
- KMemoryBlockDisableMergeAttribute::Locked,
- KMemoryBlockDisableMergeAttribute::None);
- m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages,
- KMemoryState::AliasCode, new_perm, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::Normal,
- KMemoryBlockDisableMergeAttribute::None);
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address,
- size_t size,
- ICacheInvalidationStrategy icache_invalidation_strategy) {
- // Validate the mapping request.
- R_UNLESS(this->CanContain(dst_address, size, KMemoryState::AliasCode),
- ResultInvalidMemoryRegion);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Verify that the source memory is locked normal heap.
- size_t num_src_allocator_blocks{};
- R_TRY(this->CheckMemoryState(std::addressof(num_src_allocator_blocks), src_address, size,
- KMemoryState::All, KMemoryState::Normal, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::All,
- KMemoryAttribute::Locked));
-
- // Verify that the destination memory is aliasable code.
- size_t num_dst_allocator_blocks{};
- R_TRY(this->CheckMemoryStateContiguous(
- std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState::FlagCanCodeAlias,
- KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::All & ~KMemoryAttribute::PermissionLocked, KMemoryAttribute::None));
-
- // Determine whether any pages being unmapped are code.
- bool any_code_pages = false;
- {
- KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(dst_address);
- while (true) {
- // Get the memory info.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // Check if the memory has code flag.
- if ((info.GetState() & KMemoryState::FlagCode) != KMemoryState::None) {
- any_code_pages = true;
- break;
- }
-
- // Check if we're done.
- if (dst_address + size - 1 <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- ++it;
- }
- }
-
- // Ensure that we maintain the instruction cache.
- bool reprotected_pages = false;
- SCOPE_EXIT({
- if (reprotected_pages && any_code_pages) {
- if (icache_invalidation_strategy == ICacheInvalidationStrategy::InvalidateRange) {
- m_system.InvalidateCpuInstructionCacheRange(GetInteger(dst_address), size);
- } else {
- m_system.InvalidateCpuInstructionCaches();
- }
- }
- });
-
- // Unmap.
- {
- // Determine the number of pages being operated on.
- const size_t num_pages = size / PageSize;
-
- // Create an update allocator for the source.
- Result src_allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
- m_memory_block_slab_manager,
- num_src_allocator_blocks);
- R_TRY(src_allocator_result);
-
- // Create an update allocator for the destination.
- Result dst_allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
- m_memory_block_slab_manager,
- num_dst_allocator_blocks);
- R_TRY(dst_allocator_result);
-
- // Unmap the aliased copy of the pages.
- R_TRY(Operate(dst_address, num_pages, KMemoryPermission::None, OperationType::Unmap));
-
- // Try to set the permissions for the source pages back to what they should be.
- R_TRY(Operate(src_address, num_pages, KMemoryPermission::UserReadWrite,
- OperationType::ChangePermissions));
-
- // Apply the memory block updates.
- m_memory_block_manager.Update(
- std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::None,
- KMemoryPermission::None, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal);
- m_memory_block_manager.Update(
- std::addressof(src_allocator), src_address, num_pages, KMemoryState::Normal,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked);
-
- // Note that we reprotected pages.
- reprotected_pages = true;
- }
-
- R_SUCCEED();
-}
-
-KProcessAddress KPageTable::FindFreeArea(KProcessAddress region_start, size_t region_num_pages,
- size_t num_pages, size_t alignment, size_t offset,
- size_t guard_pages) {
- KProcessAddress address = 0;
-
- if (num_pages <= region_num_pages) {
- if (this->IsAslrEnabled()) {
- UNIMPLEMENTED();
- }
- // Find the first free area.
- if (address == 0) {
- address = m_memory_block_manager.FindFreeArea(region_start, region_num_pages, num_pages,
- alignment, offset, guard_pages);
- }
- }
-
- return address;
-}
-
-Result KPageTable::MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_t num_pages) {
- ASSERT(this->IsLockedByCurrentThread());
-
- const size_t size = num_pages * PageSize;
-
- // We're making a new group, not adding to an existing one.
- R_UNLESS(pg.empty(), ResultInvalidCurrentMemory);
-
- // Begin traversal.
- Common::PageTable::TraversalContext context;
- Common::PageTable::TraversalEntry next_entry;
- R_UNLESS(m_page_table_impl->BeginTraversal(next_entry, context, GetInteger(addr)),
- ResultInvalidCurrentMemory);
-
- // Prepare tracking variables.
- KPhysicalAddress cur_addr = next_entry.phys_addr;
- size_t cur_size = next_entry.block_size - (cur_addr & (next_entry.block_size - 1));
- size_t tot_size = cur_size;
-
- // Iterate, adding to group as we go.
- const auto& memory_layout = m_system.Kernel().MemoryLayout();
- while (tot_size < size) {
- R_UNLESS(m_page_table_impl->ContinueTraversal(next_entry, context),
- ResultInvalidCurrentMemory);
-
- if (next_entry.phys_addr != (cur_addr + cur_size)) {
- const size_t cur_pages = cur_size / PageSize;
-
- R_UNLESS(IsHeapPhysicalAddress(memory_layout, cur_addr), ResultInvalidCurrentMemory);
- R_TRY(pg.AddBlock(cur_addr, cur_pages));
-
- cur_addr = next_entry.phys_addr;
- cur_size = next_entry.block_size;
- } else {
- cur_size += next_entry.block_size;
- }
-
- tot_size += next_entry.block_size;
- }
-
- // Ensure we add the right amount for the last block.
- if (tot_size > size) {
- cur_size -= (tot_size - size);
- }
-
- // Add the last block.
- const size_t cur_pages = cur_size / PageSize;
- R_UNLESS(IsHeapPhysicalAddress(memory_layout, cur_addr), ResultInvalidCurrentMemory);
- R_TRY(pg.AddBlock(cur_addr, cur_pages));
-
- R_SUCCEED();
-}
-
-bool KPageTable::IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr, size_t num_pages) {
- ASSERT(this->IsLockedByCurrentThread());
-
- const size_t size = num_pages * PageSize;
- const auto& memory_layout = m_system.Kernel().MemoryLayout();
-
- // Empty groups are necessarily invalid.
- if (pg.empty()) {
- return false;
- }
-
- // We're going to validate that the group we'd expect is the group we see.
- auto cur_it = pg.begin();
- KPhysicalAddress cur_block_address = cur_it->GetAddress();
- size_t cur_block_pages = cur_it->GetNumPages();
-
- auto UpdateCurrentIterator = [&]() {
- if (cur_block_pages == 0) {
- if ((++cur_it) == pg.end()) {
- return false;
- }
-
- cur_block_address = cur_it->GetAddress();
- cur_block_pages = cur_it->GetNumPages();
- }
- return true;
- };
-
- // Begin traversal.
- Common::PageTable::TraversalContext context;
- Common::PageTable::TraversalEntry next_entry;
- if (!m_page_table_impl->BeginTraversal(next_entry, context, GetInteger(addr))) {
- return false;
- }
-
- // Prepare tracking variables.
- KPhysicalAddress cur_addr = next_entry.phys_addr;
- size_t cur_size = next_entry.block_size - (cur_addr & (next_entry.block_size - 1));
- size_t tot_size = cur_size;
-
- // Iterate, comparing expected to actual.
- while (tot_size < size) {
- if (!m_page_table_impl->ContinueTraversal(next_entry, context)) {
- return false;
- }
-
- if (next_entry.phys_addr != (cur_addr + cur_size)) {
- const size_t cur_pages = cur_size / PageSize;
-
- if (!IsHeapPhysicalAddress(memory_layout, cur_addr)) {
- return false;
- }
-
- if (!UpdateCurrentIterator()) {
- return false;
- }
-
- if (cur_block_address != cur_addr || cur_block_pages < cur_pages) {
- return false;
- }
-
- cur_block_address += cur_size;
- cur_block_pages -= cur_pages;
- cur_addr = next_entry.phys_addr;
- cur_size = next_entry.block_size;
- } else {
- cur_size += next_entry.block_size;
- }
-
- tot_size += next_entry.block_size;
- }
-
- // Ensure we compare the right amount for the last block.
- if (tot_size > size) {
- cur_size -= (tot_size - size);
- }
-
- if (!IsHeapPhysicalAddress(memory_layout, cur_addr)) {
- return false;
- }
-
- if (!UpdateCurrentIterator()) {
- return false;
- }
-
- return cur_block_address == cur_addr && cur_block_pages == (cur_size / PageSize);
-}
-
-Result KPageTable::UnmapProcessMemory(KProcessAddress dst_addr, size_t size,
- KPageTable& src_page_table, KProcessAddress src_addr) {
- // Acquire the table locks.
- KScopedLightLockPair lk(src_page_table.m_general_lock, m_general_lock);
-
- const size_t num_pages{size / PageSize};
-
- // Check that the memory is mapped in the destination process.
- size_t num_allocator_blocks;
- R_TRY(CheckMemoryState(&num_allocator_blocks, dst_addr, size, KMemoryState::All,
- KMemoryState::SharedCode, KMemoryPermission::UserReadWrite,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
- KMemoryAttribute::None));
-
- // Check that the memory is mapped in the source process.
- R_TRY(src_page_table.CheckMemoryState(src_addr, size, KMemoryState::FlagCanMapProcess,
- KMemoryState::FlagCanMapProcess, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::All,
- KMemoryAttribute::None));
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- R_TRY(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
-
- // Apply the memory block update.
- m_memory_block_manager.Update(std::addressof(allocator), dst_addr, num_pages,
- KMemoryState::Free, KMemoryPermission::None,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::Normal);
-
- m_system.InvalidateCpuInstructionCaches();
-
- R_SUCCEED();
-}
-
-Result KPageTable::SetupForIpcClient(PageLinkedList* page_list, size_t* out_blocks_needed,
- KProcessAddress address, size_t size,
- KMemoryPermission test_perm, KMemoryState dst_state) {
- // Validate pre-conditions.
- ASSERT(this->IsLockedByCurrentThread());
- ASSERT(test_perm == KMemoryPermission::UserReadWrite ||
- test_perm == KMemoryPermission::UserRead);
-
- // Check that the address is in range.
- R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
-
- // Get the source permission.
- const auto src_perm = (test_perm == KMemoryPermission::UserReadWrite)
- ? KMemoryPermission::KernelReadWrite | KMemoryPermission::NotMapped
- : KMemoryPermission::UserRead;
-
- // Get aligned extents.
- const KProcessAddress aligned_src_start = Common::AlignDown(GetInteger(address), PageSize);
- const KProcessAddress aligned_src_end = Common::AlignUp(GetInteger(address) + size, PageSize);
- const KProcessAddress mapping_src_start = Common::AlignUp(GetInteger(address), PageSize);
- const KProcessAddress mapping_src_end = Common::AlignDown(GetInteger(address) + size, PageSize);
-
- const auto aligned_src_last = (aligned_src_end)-1;
- const auto mapping_src_last = (mapping_src_end)-1;
-
- // Get the test state and attribute mask.
- KMemoryState test_state;
- KMemoryAttribute test_attr_mask;
- switch (dst_state) {
- case KMemoryState::Ipc:
- test_state = KMemoryState::FlagCanUseIpc;
- test_attr_mask =
- KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
- break;
- case KMemoryState::NonSecureIpc:
- test_state = KMemoryState::FlagCanUseNonSecureIpc;
- test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
- break;
- case KMemoryState::NonDeviceIpc:
- test_state = KMemoryState::FlagCanUseNonDeviceIpc;
- test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
- break;
- default:
- R_THROW(ResultInvalidCombination);
- }
-
- // Ensure that on failure, we roll back appropriately.
- size_t mapped_size = 0;
- ON_RESULT_FAILURE {
- if (mapped_size > 0) {
- this->CleanupForIpcClientOnServerSetupFailure(page_list, mapping_src_start, mapped_size,
- src_perm);
- }
- };
-
- size_t blocks_needed = 0;
-
- // Iterate, mapping as needed.
- KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(aligned_src_start);
- while (true) {
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // Validate the current block.
- R_TRY(this->CheckMemoryState(info, test_state, test_state, test_perm, test_perm,
- test_attr_mask, KMemoryAttribute::None));
-
- if (mapping_src_start < mapping_src_end && (mapping_src_start) < info.GetEndAddress() &&
- info.GetAddress() < GetInteger(mapping_src_end)) {
- const auto cur_start = info.GetAddress() >= GetInteger(mapping_src_start)
- ? info.GetAddress()
- : (mapping_src_start);
- const auto cur_end = mapping_src_last >= info.GetLastAddress() ? info.GetEndAddress()
- : (mapping_src_end);
- const size_t cur_size = cur_end - cur_start;
-
- if (info.GetAddress() < GetInteger(mapping_src_start)) {
- ++blocks_needed;
- }
- if (mapping_src_last < info.GetLastAddress()) {
- ++blocks_needed;
- }
-
- // Set the permissions on the block, if we need to.
- if ((info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != src_perm) {
- R_TRY(Operate(cur_start, cur_size / PageSize, src_perm,
- OperationType::ChangePermissions));
- }
-
- // Note that we mapped this part.
- mapped_size += cur_size;
- }
-
- // If the block is at the end, we're done.
- if (aligned_src_last <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- ++it;
- ASSERT(it != m_memory_block_manager.end());
- }
-
- if (out_blocks_needed != nullptr) {
- ASSERT(blocks_needed <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
- *out_blocks_needed = blocks_needed;
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
- KProcessAddress src_addr, KMemoryPermission test_perm,
- KMemoryState dst_state, KPageTable& src_page_table,
- bool send) {
- ASSERT(this->IsLockedByCurrentThread());
- ASSERT(src_page_table.IsLockedByCurrentThread());
-
- // Check that we can theoretically map.
- const KProcessAddress region_start = m_alias_region_start;
- const size_t region_size = m_alias_region_end - m_alias_region_start;
- R_UNLESS(size < region_size, ResultOutOfAddressSpace);
-
- // Get aligned source extents.
- const KProcessAddress src_start = src_addr;
- const KProcessAddress src_end = src_addr + size;
- const KProcessAddress aligned_src_start = Common::AlignDown(GetInteger(src_start), PageSize);
- const KProcessAddress aligned_src_end = Common::AlignUp(GetInteger(src_start) + size, PageSize);
- const KProcessAddress mapping_src_start = Common::AlignUp(GetInteger(src_start), PageSize);
- const KProcessAddress mapping_src_end =
- Common::AlignDown(GetInteger(src_start) + size, PageSize);
- const size_t aligned_src_size = aligned_src_end - aligned_src_start;
- const size_t mapping_src_size =
- (mapping_src_start < mapping_src_end) ? (mapping_src_end - mapping_src_start) : 0;
-
- // Select a random address to map at.
- KProcessAddress dst_addr =
- this->FindFreeArea(region_start, region_size / PageSize, aligned_src_size / PageSize,
- PageSize, 0, this->GetNumGuardPages());
-
- R_UNLESS(dst_addr != 0, ResultOutOfAddressSpace);
-
- // Check that we can perform the operation we're about to perform.
- ASSERT(this->CanContain(dst_addr, aligned_src_size, dst_state));
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Reserve space for any partial pages we allocate.
- const size_t unmapped_size = aligned_src_size - mapping_src_size;
- KScopedResourceReservation memory_reservation(
- m_resource_limit, LimitableResource::PhysicalMemoryMax, unmapped_size);
- R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
-
- // Ensure that we manage page references correctly.
- KPhysicalAddress start_partial_page = 0;
- KPhysicalAddress end_partial_page = 0;
- KProcessAddress cur_mapped_addr = dst_addr;
-
- // If the partial pages are mapped, an extra reference will have been opened. Otherwise, they'll
- // free on scope exit.
- SCOPE_EXIT({
- if (start_partial_page != 0) {
- m_system.Kernel().MemoryManager().Close(start_partial_page, 1);
- }
- if (end_partial_page != 0) {
- m_system.Kernel().MemoryManager().Close(end_partial_page, 1);
- }
- });
-
- ON_RESULT_FAILURE {
- if (cur_mapped_addr != dst_addr) {
- ASSERT(Operate(dst_addr, (cur_mapped_addr - dst_addr) / PageSize,
- KMemoryPermission::None, OperationType::Unmap)
- .IsSuccess());
- }
- };
-
- // Allocate the start page as needed.
- if (aligned_src_start < mapping_src_start) {
- start_partial_page =
- m_system.Kernel().MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option);
- R_UNLESS(start_partial_page != 0, ResultOutOfMemory);
- }
-
- // Allocate the end page as needed.
- if (mapping_src_end < aligned_src_end &&
- (aligned_src_start < mapping_src_end || aligned_src_start == mapping_src_start)) {
- end_partial_page =
- m_system.Kernel().MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option);
- R_UNLESS(end_partial_page != 0, ResultOutOfMemory);
- }
-
- // Get the implementation.
- auto& src_impl = src_page_table.PageTableImpl();
-
- // Get the fill value for partial pages.
- const auto fill_val = m_ipc_fill_value;
-
- // Begin traversal.
- Common::PageTable::TraversalContext context;
- Common::PageTable::TraversalEntry next_entry;
- bool traverse_valid =
- src_impl.BeginTraversal(next_entry, context, GetInteger(aligned_src_start));
- ASSERT(traverse_valid);
-
- // Prepare tracking variables.
- KPhysicalAddress cur_block_addr = next_entry.phys_addr;
- size_t cur_block_size =
- next_entry.block_size - ((cur_block_addr) & (next_entry.block_size - 1));
- size_t tot_block_size = cur_block_size;
-
- // Map the start page, if we have one.
- if (start_partial_page != 0) {
- // Ensure the page holds correct data.
- const KVirtualAddress start_partial_virt =
- GetHeapVirtualAddress(m_system.Kernel().MemoryLayout(), start_partial_page);
- if (send) {
- const size_t partial_offset = src_start - aligned_src_start;
- size_t copy_size, clear_size;
- if (src_end < mapping_src_start) {
- copy_size = size;
- clear_size = mapping_src_start - src_end;
- } else {
- copy_size = mapping_src_start - src_start;
- clear_size = 0;
- }
-
- std::memset(m_memory->GetPointer<void>(GetInteger(start_partial_virt)), fill_val,
- partial_offset);
- std::memcpy(
- m_memory->GetPointer<void>(GetInteger(start_partial_virt) + partial_offset),
- m_memory->GetPointer<void>(GetInteger(GetHeapVirtualAddress(
- m_system.Kernel().MemoryLayout(), cur_block_addr)) +
- partial_offset),
- copy_size);
- if (clear_size > 0) {
- std::memset(m_memory->GetPointer<void>(GetInteger(start_partial_virt) +
- partial_offset + copy_size),
- fill_val, clear_size);
- }
- } else {
- std::memset(m_memory->GetPointer<void>(GetInteger(start_partial_virt)), fill_val,
- PageSize);
- }
-
- // Map the page.
- R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, start_partial_page));
-
- // Update tracking extents.
- cur_mapped_addr += PageSize;
- cur_block_addr += PageSize;
- cur_block_size -= PageSize;
-
- // If the block's size was one page, we may need to continue traversal.
- if (cur_block_size == 0 && aligned_src_size > PageSize) {
- traverse_valid = src_impl.ContinueTraversal(next_entry, context);
- ASSERT(traverse_valid);
-
- cur_block_addr = next_entry.phys_addr;
- cur_block_size = next_entry.block_size;
- tot_block_size += next_entry.block_size;
- }
- }
-
- // Map the remaining pages.
- while (aligned_src_start + tot_block_size < mapping_src_end) {
- // Continue the traversal.
- traverse_valid = src_impl.ContinueTraversal(next_entry, context);
- ASSERT(traverse_valid);
-
- // Process the block.
- if (next_entry.phys_addr != cur_block_addr + cur_block_size) {
- // Map the block we've been processing so far.
- R_TRY(Operate(cur_mapped_addr, cur_block_size / PageSize, test_perm, OperationType::Map,
- cur_block_addr));
-
- // Update tracking extents.
- cur_mapped_addr += cur_block_size;
- cur_block_addr = next_entry.phys_addr;
- cur_block_size = next_entry.block_size;
- } else {
- cur_block_size += next_entry.block_size;
- }
- tot_block_size += next_entry.block_size;
- }
-
- // Handle the last direct-mapped page.
- if (const KProcessAddress mapped_block_end =
- aligned_src_start + tot_block_size - cur_block_size;
- mapped_block_end < mapping_src_end) {
- const size_t last_block_size = mapping_src_end - mapped_block_end;
-
- // Map the last block.
- R_TRY(Operate(cur_mapped_addr, last_block_size / PageSize, test_perm, OperationType::Map,
- cur_block_addr));
-
- // Update tracking extents.
- cur_mapped_addr += last_block_size;
- cur_block_addr += last_block_size;
- if (mapped_block_end + cur_block_size < aligned_src_end &&
- cur_block_size == last_block_size) {
- traverse_valid = src_impl.ContinueTraversal(next_entry, context);
- ASSERT(traverse_valid);
-
- cur_block_addr = next_entry.phys_addr;
- }
- }
-
- // Map the end page, if we have one.
- if (end_partial_page != 0) {
- // Ensure the page holds correct data.
- const KVirtualAddress end_partial_virt =
- GetHeapVirtualAddress(m_system.Kernel().MemoryLayout(), end_partial_page);
- if (send) {
- const size_t copy_size = src_end - mapping_src_end;
- std::memcpy(m_memory->GetPointer<void>(GetInteger(end_partial_virt)),
- m_memory->GetPointer<void>(GetInteger(GetHeapVirtualAddress(
- m_system.Kernel().MemoryLayout(), cur_block_addr))),
- copy_size);
- std::memset(m_memory->GetPointer<void>(GetInteger(end_partial_virt) + copy_size),
- fill_val, PageSize - copy_size);
- } else {
- std::memset(m_memory->GetPointer<void>(GetInteger(end_partial_virt)), fill_val,
- PageSize);
- }
-
- // Map the page.
- R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, end_partial_page));
- }
-
- // Update memory blocks to reflect our changes
- m_memory_block_manager.Update(std::addressof(allocator), dst_addr, aligned_src_size / PageSize,
- dst_state, test_perm, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::Normal,
- KMemoryBlockDisableMergeAttribute::None);
-
- // Set the output address.
- *out_addr = dst_addr + (src_start - aligned_src_start);
-
- // We succeeded.
- memory_reservation.Commit();
- R_SUCCEED();
-}
-
-Result KPageTable::SetupForIpc(KProcessAddress* out_dst_addr, size_t size, KProcessAddress src_addr,
- KPageTable& src_page_table, KMemoryPermission test_perm,
- KMemoryState dst_state, bool send) {
- // For convenience, alias this.
- KPageTable& dst_page_table = *this;
-
- // Acquire the table locks.
- KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(std::addressof(src_page_table));
-
- // Perform client setup.
- size_t num_allocator_blocks;
- R_TRY(src_page_table.SetupForIpcClient(updater.GetPageList(),
- std::addressof(num_allocator_blocks), src_addr, size,
- test_perm, dst_state));
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- src_page_table.m_memory_block_slab_manager,
- num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Get the mapped extents.
- const KProcessAddress src_map_start = Common::AlignUp(GetInteger(src_addr), PageSize);
- const KProcessAddress src_map_end = Common::AlignDown(GetInteger(src_addr) + size, PageSize);
- const size_t src_map_size = src_map_end - src_map_start;
-
- // Ensure that we clean up appropriately if we fail after this.
- const auto src_perm = (test_perm == KMemoryPermission::UserReadWrite)
- ? KMemoryPermission::KernelReadWrite | KMemoryPermission::NotMapped
- : KMemoryPermission::UserRead;
- ON_RESULT_FAILURE {
- if (src_map_end > src_map_start) {
- src_page_table.CleanupForIpcClientOnServerSetupFailure(
- updater.GetPageList(), src_map_start, src_map_size, src_perm);
- }
- };
-
- // Perform server setup.
- R_TRY(dst_page_table.SetupForIpcServer(out_dst_addr, size, src_addr, test_perm, dst_state,
- src_page_table, send));
-
- // If anything was mapped, ipc-lock the pages.
- if (src_map_start < src_map_end) {
- // Get the source permission.
- src_page_table.m_memory_block_manager.UpdateLock(std::addressof(allocator), src_map_start,
- (src_map_end - src_map_start) / PageSize,
- &KMemoryBlock::LockForIpc, src_perm);
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::CleanupForIpcServer(KProcessAddress address, size_t size,
- KMemoryState dst_state) {
- // Validate the address.
- R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Validate the memory state.
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
- KMemoryState::All, dst_state, KMemoryPermission::UserRead,
- KMemoryPermission::UserRead, KMemoryAttribute::All,
- KMemoryAttribute::None));
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Get aligned extents.
- const KProcessAddress aligned_start = Common::AlignDown(GetInteger(address), PageSize);
- const KProcessAddress aligned_end = Common::AlignUp(GetInteger(address) + size, PageSize);
- const size_t aligned_size = aligned_end - aligned_start;
- const size_t aligned_num_pages = aligned_size / PageSize;
-
- // Unmap the pages.
- R_TRY(Operate(aligned_start, aligned_num_pages, KMemoryPermission::None, OperationType::Unmap));
-
- // Update memory blocks.
- m_memory_block_manager.Update(std::addressof(allocator), aligned_start, aligned_num_pages,
- KMemoryState::None, KMemoryPermission::None,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::Normal);
-
- // Release from the resource limit as relevant.
- const KProcessAddress mapping_start = Common::AlignUp(GetInteger(address), PageSize);
- const KProcessAddress mapping_end = Common::AlignDown(GetInteger(address) + size, PageSize);
- const size_t mapping_size = (mapping_start < mapping_end) ? mapping_end - mapping_start : 0;
- m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, aligned_size - mapping_size);
-
- R_SUCCEED();
-}
-
-Result KPageTable::CleanupForIpcClient(KProcessAddress address, size_t size,
- KMemoryState dst_state) {
- // Validate the address.
- R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
-
- // Get aligned source extents.
- const KProcessAddress mapping_start = Common::AlignUp(GetInteger(address), PageSize);
- const KProcessAddress mapping_end = Common::AlignDown(GetInteger(address) + size, PageSize);
- const KProcessAddress mapping_last = mapping_end - 1;
- const size_t mapping_size = (mapping_start < mapping_end) ? (mapping_end - mapping_start) : 0;
-
- // If nothing was mapped, we're actually done immediately.
- R_SUCCEED_IF(mapping_size == 0);
-
- // Get the test state and attribute mask.
- KMemoryState test_state;
- KMemoryAttribute test_attr_mask;
- switch (dst_state) {
- case KMemoryState::Ipc:
- test_state = KMemoryState::FlagCanUseIpc;
- test_attr_mask =
- KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
- break;
- case KMemoryState::NonSecureIpc:
- test_state = KMemoryState::FlagCanUseNonSecureIpc;
- test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
- break;
- case KMemoryState::NonDeviceIpc:
- test_state = KMemoryState::FlagCanUseNonDeviceIpc;
- test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
- break;
- default:
- R_THROW(ResultInvalidCombination);
- }
-
- // Lock the table.
- // NOTE: Nintendo does this *after* creating the updater below, but this does not follow
- // convention elsewhere in KPageTable.
- KScopedLightLock lk(m_general_lock);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Ensure that on failure, we roll back appropriately.
- size_t mapped_size = 0;
- ON_RESULT_FAILURE {
- if (mapped_size > 0) {
- // Determine where the mapping ends.
- const auto mapped_end = (mapping_start) + mapped_size;
- const auto mapped_last = mapped_end - 1;
-
- // Get current and next iterators.
- KMemoryBlockManager::const_iterator start_it =
- m_memory_block_manager.FindIterator(mapping_start);
- KMemoryBlockManager::const_iterator next_it = start_it;
- ++next_it;
-
- // Get the current block info.
- KMemoryInfo cur_info = start_it->GetMemoryInfo();
-
- // Create tracking variables.
- KProcessAddress cur_address = cur_info.GetAddress();
- size_t cur_size = cur_info.GetSize();
- bool cur_perm_eq = cur_info.GetPermission() == cur_info.GetOriginalPermission();
- bool cur_needs_set_perm = !cur_perm_eq && cur_info.GetIpcLockCount() == 1;
- bool first =
- cur_info.GetIpcDisableMergeCount() == 1 &&
- (cur_info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Locked) ==
- KMemoryBlockDisableMergeAttribute::None;
-
- while (((cur_address) + cur_size - 1) < mapped_last) {
- // Check that we have a next block.
- ASSERT(next_it != m_memory_block_manager.end());
-
- // Get the next info.
- const KMemoryInfo next_info = next_it->GetMemoryInfo();
-
- // Check if we can consolidate the next block's permission set with the current one.
-
- const bool next_perm_eq =
- next_info.GetPermission() == next_info.GetOriginalPermission();
- const bool next_needs_set_perm = !next_perm_eq && next_info.GetIpcLockCount() == 1;
- if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm &&
- cur_info.GetOriginalPermission() == next_info.GetOriginalPermission()) {
- // We can consolidate the reprotection for the current and next block into a
- // single call.
- cur_size += next_info.GetSize();
- } else {
- // We have to operate on the current block.
- if ((cur_needs_set_perm || first) && !cur_perm_eq) {
- ASSERT(Operate(cur_address, cur_size / PageSize, cur_info.GetPermission(),
- OperationType::ChangePermissions)
- .IsSuccess());
- }
-
- // Advance.
- cur_address = next_info.GetAddress();
- cur_size = next_info.GetSize();
- first = false;
- }
-
- // Advance.
- cur_info = next_info;
- cur_perm_eq = next_perm_eq;
- cur_needs_set_perm = next_needs_set_perm;
- ++next_it;
- }
-
- // Process the last block.
- if ((first || cur_needs_set_perm) && !cur_perm_eq) {
- ASSERT(Operate(cur_address, cur_size / PageSize, cur_info.GetPermission(),
- OperationType::ChangePermissions)
- .IsSuccess());
- }
- }
- };
-
- // Iterate, reprotecting as needed.
- {
- // Get current and next iterators.
- KMemoryBlockManager::const_iterator start_it =
- m_memory_block_manager.FindIterator(mapping_start);
- KMemoryBlockManager::const_iterator next_it = start_it;
- ++next_it;
-
- // Validate the current block.
- KMemoryInfo cur_info = start_it->GetMemoryInfo();
- ASSERT(this->CheckMemoryState(cur_info, test_state, test_state, KMemoryPermission::None,
- KMemoryPermission::None,
- test_attr_mask | KMemoryAttribute::IpcLocked,
- KMemoryAttribute::IpcLocked)
- .IsSuccess());
-
- // Create tracking variables.
- KProcessAddress cur_address = cur_info.GetAddress();
- size_t cur_size = cur_info.GetSize();
- bool cur_perm_eq = cur_info.GetPermission() == cur_info.GetOriginalPermission();
- bool cur_needs_set_perm = !cur_perm_eq && cur_info.GetIpcLockCount() == 1;
- bool first =
- cur_info.GetIpcDisableMergeCount() == 1 &&
- (cur_info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Locked) ==
- KMemoryBlockDisableMergeAttribute::None;
-
- while ((cur_address + cur_size - 1) < mapping_last) {
- // Check that we have a next block.
- ASSERT(next_it != m_memory_block_manager.end());
-
- // Get the next info.
- const KMemoryInfo next_info = next_it->GetMemoryInfo();
-
- // Validate the next block.
- ASSERT(this->CheckMemoryState(next_info, test_state, test_state,
- KMemoryPermission::None, KMemoryPermission::None,
- test_attr_mask | KMemoryAttribute::IpcLocked,
- KMemoryAttribute::IpcLocked)
- .IsSuccess());
-
- // Check if we can consolidate the next block's permission set with the current one.
- const bool next_perm_eq =
- next_info.GetPermission() == next_info.GetOriginalPermission();
- const bool next_needs_set_perm = !next_perm_eq && next_info.GetIpcLockCount() == 1;
- if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm &&
- cur_info.GetOriginalPermission() == next_info.GetOriginalPermission()) {
- // We can consolidate the reprotection for the current and next block into a single
- // call.
- cur_size += next_info.GetSize();
- } else {
- // We have to operate on the current block.
- if ((cur_needs_set_perm || first) && !cur_perm_eq) {
- R_TRY(Operate(cur_address, cur_size / PageSize,
- cur_needs_set_perm ? cur_info.GetOriginalPermission()
- : cur_info.GetPermission(),
- OperationType::ChangePermissions));
- }
-
- // Mark that we mapped the block.
- mapped_size += cur_size;
-
- // Advance.
- cur_address = next_info.GetAddress();
- cur_size = next_info.GetSize();
- first = false;
- }
-
- // Advance.
- cur_info = next_info;
- cur_perm_eq = next_perm_eq;
- cur_needs_set_perm = next_needs_set_perm;
- ++next_it;
- }
-
- // Process the last block.
- const auto lock_count =
- cur_info.GetIpcLockCount() +
- (next_it != m_memory_block_manager.end()
- ? (next_it->GetIpcDisableMergeCount() - next_it->GetIpcLockCount())
- : 0);
- if ((first || cur_needs_set_perm || (lock_count == 1)) && !cur_perm_eq) {
- R_TRY(Operate(cur_address, cur_size / PageSize,
- cur_needs_set_perm ? cur_info.GetOriginalPermission()
- : cur_info.GetPermission(),
- OperationType::ChangePermissions));
- }
- }
-
- // Create an update allocator.
- // NOTE: Guaranteed zero blocks needed here.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, 0);
- R_TRY(allocator_result);
-
- // Unlock the pages.
- m_memory_block_manager.UpdateLock(std::addressof(allocator), mapping_start,
- mapping_size / PageSize, &KMemoryBlock::UnlockForIpc,
- KMemoryPermission::None);
-
- R_SUCCEED();
-}
-
-void KPageTable::CleanupForIpcClientOnServerSetupFailure([[maybe_unused]] PageLinkedList* page_list,
- KProcessAddress address, size_t size,
- KMemoryPermission prot_perm) {
- ASSERT(this->IsLockedByCurrentThread());
- ASSERT(Common::IsAligned(GetInteger(address), PageSize));
- ASSERT(Common::IsAligned(size, PageSize));
-
- // Get the mapped extents.
- const KProcessAddress src_map_start = address;
- const KProcessAddress src_map_end = address + size;
- const KProcessAddress src_map_last = src_map_end - 1;
-
- // This function is only invoked when there's something to do.
- ASSERT(src_map_end > src_map_start);
-
- // Iterate over blocks, fixing permissions.
- KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(address);
- while (true) {
- const KMemoryInfo info = it->GetMemoryInfo();
-
- const auto cur_start = info.GetAddress() >= GetInteger(src_map_start)
- ? info.GetAddress()
- : GetInteger(src_map_start);
- const auto cur_end =
- src_map_last <= info.GetLastAddress() ? src_map_end : info.GetEndAddress();
-
- // If we can, fix the protections on the block.
- if ((info.GetIpcLockCount() == 0 &&
- (info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm) ||
- (info.GetIpcLockCount() != 0 &&
- (info.GetOriginalPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm)) {
- // Check if we actually need to fix the protections on the block.
- if (cur_end == src_map_end || info.GetAddress() <= GetInteger(src_map_start) ||
- (info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm) {
- ASSERT(Operate(cur_start, (cur_end - cur_start) / PageSize, info.GetPermission(),
- OperationType::ChangePermissions)
- .IsSuccess());
- }
- }
-
- // If we're past the end of the region, we're done.
- if (src_map_last <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- ++it;
- ASSERT(it != m_memory_block_manager.end());
- }
-}
-
-Result KPageTable::MapPhysicalMemory(KProcessAddress address, size_t size) {
- // Lock the physical memory lock.
- KScopedLightLock phys_lk(m_map_physical_memory_lock);
-
- // Calculate the last address for convenience.
- const KProcessAddress last_address = address + size - 1;
-
- // Define iteration variables.
- KProcessAddress cur_address;
- size_t mapped_size;
-
- // The entire mapping process can be retried.
- while (true) {
- // Check if the memory is already mapped.
- {
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Iterate over the memory.
- cur_address = address;
- mapped_size = 0;
-
- auto it = m_memory_block_manager.FindIterator(cur_address);
- while (true) {
- // Check that the iterator is valid.
- ASSERT(it != m_memory_block_manager.end());
-
- // Get the memory info.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // Check if we're done.
- if (last_address <= info.GetLastAddress()) {
- if (info.GetState() != KMemoryState::Free) {
- mapped_size += (last_address + 1 - cur_address);
- }
- break;
- }
-
- // Track the memory if it's mapped.
- if (info.GetState() != KMemoryState::Free) {
- mapped_size += KProcessAddress(info.GetEndAddress()) - cur_address;
- }
-
- // Advance.
- cur_address = info.GetEndAddress();
- ++it;
- }
-
- // If the size mapped is the size requested, we've nothing to do.
- R_SUCCEED_IF(size == mapped_size);
- }
-
- // Allocate and map the memory.
- {
- // Reserve the memory from the process resource limit.
- KScopedResourceReservation memory_reservation(
- m_resource_limit, LimitableResource::PhysicalMemoryMax, size - mapped_size);
- R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
-
- // Allocate pages for the new memory.
- KPageGroup pg{m_kernel, m_block_info_manager};
- R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
- &pg, (size - mapped_size) / PageSize, m_allocate_option, 0, 0));
-
- // If we fail in the next bit (or retry), we need to cleanup the pages.
- // auto pg_guard = SCOPE_GUARD {
- // pg.OpenFirst();
- // pg.Close();
- //};
-
- // Map the memory.
- {
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- size_t num_allocator_blocks = 0;
-
- // Verify that nobody has mapped memory since we first checked.
- {
- // Iterate over the memory.
- size_t checked_mapped_size = 0;
- cur_address = address;
-
- auto it = m_memory_block_manager.FindIterator(cur_address);
- while (true) {
- // Check that the iterator is valid.
- ASSERT(it != m_memory_block_manager.end());
-
- // Get the memory info.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- const bool is_free = info.GetState() == KMemoryState::Free;
- if (is_free) {
- if (info.GetAddress() < GetInteger(address)) {
- ++num_allocator_blocks;
- }
- if (last_address < info.GetLastAddress()) {
- ++num_allocator_blocks;
- }
- }
-
- // Check if we're done.
- if (last_address <= info.GetLastAddress()) {
- if (!is_free) {
- checked_mapped_size += (last_address + 1 - cur_address);
- }
- break;
- }
-
- // Track the memory if it's mapped.
- if (!is_free) {
- checked_mapped_size +=
- KProcessAddress(info.GetEndAddress()) - cur_address;
- }
-
- // Advance.
- cur_address = info.GetEndAddress();
- ++it;
- }
-
- // If the size now isn't what it was before, somebody mapped or unmapped
- // concurrently. If this happened, retry.
- if (mapped_size != checked_mapped_size) {
- continue;
- }
- }
-
- // Create an update allocator.
- ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager,
- num_allocator_blocks);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Prepare to iterate over the memory.
- auto pg_it = pg.begin();
- KPhysicalAddress pg_phys_addr = pg_it->GetAddress();
- size_t pg_pages = pg_it->GetNumPages();
-
- // Reset the current tracking address, and make sure we clean up on failure.
- // pg_guard.Cancel();
- cur_address = address;
- ON_RESULT_FAILURE {
- if (cur_address > address) {
- const KProcessAddress last_unmap_address = cur_address - 1;
-
- // Iterate, unmapping the pages.
- cur_address = address;
-
- auto it = m_memory_block_manager.FindIterator(cur_address);
- while (true) {
- // Check that the iterator is valid.
- ASSERT(it != m_memory_block_manager.end());
-
- // Get the memory info.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // If the memory state is free, we mapped it and need to unmap it.
- if (info.GetState() == KMemoryState::Free) {
- // Determine the range to unmap.
- const size_t cur_pages =
- std::min(KProcessAddress(info.GetEndAddress()) - cur_address,
- last_unmap_address + 1 - cur_address) /
- PageSize;
-
- // Unmap.
- ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None,
- OperationType::Unmap)
- .IsSuccess());
- }
-
- // Check if we're done.
- if (last_unmap_address <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- cur_address = info.GetEndAddress();
- ++it;
- }
- }
-
- // Release any remaining unmapped memory.
- m_system.Kernel().MemoryManager().OpenFirst(pg_phys_addr, pg_pages);
- m_system.Kernel().MemoryManager().Close(pg_phys_addr, pg_pages);
- for (++pg_it; pg_it != pg.end(); ++pg_it) {
- m_system.Kernel().MemoryManager().OpenFirst(pg_it->GetAddress(),
- pg_it->GetNumPages());
- m_system.Kernel().MemoryManager().Close(pg_it->GetAddress(),
- pg_it->GetNumPages());
- }
- };
-
- auto it = m_memory_block_manager.FindIterator(cur_address);
- while (true) {
- // Check that the iterator is valid.
- ASSERT(it != m_memory_block_manager.end());
-
- // Get the memory info.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // If it's unmapped, we need to map it.
- if (info.GetState() == KMemoryState::Free) {
- // Determine the range to map.
- size_t map_pages =
- std::min(KProcessAddress(info.GetEndAddress()) - cur_address,
- last_address + 1 - cur_address) /
- PageSize;
-
- // While we have pages to map, map them.
- {
- // Create a page group for the current mapping range.
- KPageGroup cur_pg(m_kernel, m_block_info_manager);
- {
- ON_RESULT_FAILURE_2 {
- cur_pg.OpenFirst();
- cur_pg.Close();
- };
-
- size_t remain_pages = map_pages;
- while (remain_pages > 0) {
- // Check if we're at the end of the physical block.
- if (pg_pages == 0) {
- // Ensure there are more pages to map.
- ASSERT(pg_it != pg.end());
-
- // Advance our physical block.
- ++pg_it;
- pg_phys_addr = pg_it->GetAddress();
- pg_pages = pg_it->GetNumPages();
- }
-
- // Add whatever we can to the current block.
- const size_t cur_pages = std::min(pg_pages, remain_pages);
- R_TRY(cur_pg.AddBlock(pg_phys_addr +
- ((pg_pages - cur_pages) * PageSize),
- cur_pages));
-
- // Advance.
- remain_pages -= cur_pages;
- pg_pages -= cur_pages;
- }
- }
-
- // Map the pages.
- R_TRY(this->Operate(cur_address, map_pages, cur_pg,
- OperationType::MapFirstGroup));
- }
- }
-
- // Check if we're done.
- if (last_address <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- cur_address = info.GetEndAddress();
- ++it;
- }
-
- // We succeeded, so commit the memory reservation.
- memory_reservation.Commit();
-
- // Increase our tracked mapped size.
- m_mapped_physical_memory_size += (size - mapped_size);
-
- // Update the relevant memory blocks.
- m_memory_block_manager.UpdateIfMatch(
- std::addressof(allocator), address, size / PageSize, KMemoryState::Free,
- KMemoryPermission::None, KMemoryAttribute::None, KMemoryState::Normal,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
- address == this->GetAliasRegionStart()
- ? KMemoryBlockDisableMergeAttribute::Normal
- : KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::None);
-
- R_SUCCEED();
- }
- }
- }
-}
-
-Result KPageTable::UnmapPhysicalMemory(KProcessAddress address, size_t size) {
- // Lock the physical memory lock.
- KScopedLightLock phys_lk(m_map_physical_memory_lock);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Calculate the last address for convenience.
- const KProcessAddress last_address = address + size - 1;
-
- // Define iteration variables.
- KProcessAddress map_start_address = 0;
- KProcessAddress map_last_address = 0;
-
- KProcessAddress cur_address;
- size_t mapped_size;
- size_t num_allocator_blocks = 0;
-
- // Check if the memory is mapped.
- {
- // Iterate over the memory.
- cur_address = address;
- mapped_size = 0;
-
- auto it = m_memory_block_manager.FindIterator(cur_address);
- while (true) {
- // Check that the iterator is valid.
- ASSERT(it != m_memory_block_manager.end());
-
- // Get the memory info.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // Verify the memory's state.
- const bool is_normal = info.GetState() == KMemoryState::Normal &&
- info.GetAttribute() == KMemoryAttribute::None;
- const bool is_free = info.GetState() == KMemoryState::Free;
- R_UNLESS(is_normal || is_free, ResultInvalidCurrentMemory);
-
- if (is_normal) {
- R_UNLESS(info.GetAttribute() == KMemoryAttribute::None, ResultInvalidCurrentMemory);
-
- if (map_start_address == 0) {
- map_start_address = cur_address;
- }
- map_last_address =
- (last_address >= info.GetLastAddress()) ? info.GetLastAddress() : last_address;
-
- if (info.GetAddress() < GetInteger(address)) {
- ++num_allocator_blocks;
- }
- if (last_address < info.GetLastAddress()) {
- ++num_allocator_blocks;
- }
-
- mapped_size += (map_last_address + 1 - cur_address);
- }
-
- // Check if we're done.
- if (last_address <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- cur_address = info.GetEndAddress();
- ++it;
- }
-
- // If there's nothing mapped, we've nothing to do.
- R_SUCCEED_IF(mapped_size == 0);
- }
-
- // Create an update allocator.
- ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Separate the mapping.
- R_TRY(Operate(map_start_address, (map_last_address + 1 - map_start_address) / PageSize,
- KMemoryPermission::None, OperationType::Separate));
-
- // Reset the current tracking address, and make sure we clean up on failure.
- cur_address = address;
-
- // Iterate over the memory, unmapping as we go.
- auto it = m_memory_block_manager.FindIterator(cur_address);
-
- const auto clear_merge_attr =
- (it->GetState() == KMemoryState::Normal &&
- it->GetAddress() == this->GetAliasRegionStart() && it->GetAddress() == address)
- ? KMemoryBlockDisableMergeAttribute::Normal
- : KMemoryBlockDisableMergeAttribute::None;
-
- while (true) {
- // Check that the iterator is valid.
- ASSERT(it != m_memory_block_manager.end());
-
- // Get the memory info.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // If the memory state is normal, we need to unmap it.
- if (info.GetState() == KMemoryState::Normal) {
- // Determine the range to unmap.
- const size_t cur_pages = std::min(KProcessAddress(info.GetEndAddress()) - cur_address,
- last_address + 1 - cur_address) /
- PageSize;
-
- // Unmap.
- ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap)
- .IsSuccess());
- }
-
- // Check if we're done.
- if (last_address <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- cur_address = info.GetEndAddress();
- ++it;
- }
-
- // Release the memory resource.
- m_mapped_physical_memory_size -= mapped_size;
- m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, mapped_size);
-
- // Update memory blocks.
- m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize,
- KMemoryState::Free, KMemoryPermission::None,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
- clear_merge_attr);
-
- // We succeeded.
- R_SUCCEED();
-}
-
-Result KPageTable::MapMemory(KProcessAddress dst_address, KProcessAddress src_address,
- size_t size) {
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Validate that the source address's state is valid.
- KMemoryState src_state;
- size_t num_src_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(src_state), nullptr, nullptr,
- std::addressof(num_src_allocator_blocks), src_address, size,
- KMemoryState::FlagCanAlias, KMemoryState::FlagCanAlias,
- KMemoryPermission::All, KMemoryPermission::UserReadWrite,
- KMemoryAttribute::All, KMemoryAttribute::None));
-
- // Validate that the dst address's state is valid.
- size_t num_dst_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_dst_allocator_blocks), dst_address, size,
- KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::None,
- KMemoryAttribute::None));
-
- // Create an update allocator for the source.
- Result src_allocator_result;
- KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
- m_memory_block_slab_manager,
- num_src_allocator_blocks);
- R_TRY(src_allocator_result);
-
- // Create an update allocator for the destination.
- Result dst_allocator_result;
- KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
- m_memory_block_slab_manager,
- num_dst_allocator_blocks);
- R_TRY(dst_allocator_result);
-
- // Map the memory.
- {
- // Determine the number of pages being operated on.
- const size_t num_pages = size / PageSize;
-
- // Create page groups for the memory being unmapped.
- KPageGroup pg{m_kernel, m_block_info_manager};
-
- // Create the page group representing the source.
- R_TRY(this->MakePageGroup(pg, src_address, num_pages));
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Reprotect the source as kernel-read/not mapped.
- const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>(
- KMemoryPermission::KernelRead | KMemoryPermission::NotMapped);
- const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked;
- const KPageProperties src_properties = {new_src_perm, false, false,
- DisableMergeAttribute::DisableHeadBodyTail};
- R_TRY(this->Operate(src_address, num_pages, src_properties.perm,
- OperationType::ChangePermissions));
-
- // Ensure that we unprotect the source pages on failure.
- ON_RESULT_FAILURE {
- const KPageProperties unprotect_properties = {
- KMemoryPermission::UserReadWrite, false, false,
- DisableMergeAttribute::EnableHeadBodyTail};
- ASSERT(this->Operate(src_address, num_pages, unprotect_properties.perm,
- OperationType::ChangePermissions) == ResultSuccess);
- };
-
- // Map the alias pages.
- const KPageProperties dst_map_properties = {KMemoryPermission::UserReadWrite, false, false,
- DisableMergeAttribute::DisableHead};
- R_TRY(this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_map_properties,
- false));
-
- // Apply the memory block updates.
- m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages,
- src_state, new_src_perm, new_src_attr,
- KMemoryBlockDisableMergeAttribute::Locked,
- KMemoryBlockDisableMergeAttribute::None);
- m_memory_block_manager.Update(
- std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::Stack,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None);
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address,
- size_t size) {
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Validate that the source address's state is valid.
- KMemoryState src_state;
- size_t num_src_allocator_blocks;
- R_TRY(this->CheckMemoryState(
- std::addressof(src_state), nullptr, nullptr, std::addressof(num_src_allocator_blocks),
- src_address, size, KMemoryState::FlagCanAlias, KMemoryState::FlagCanAlias,
- KMemoryPermission::All, KMemoryPermission::NotMapped | KMemoryPermission::KernelRead,
- KMemoryAttribute::All, KMemoryAttribute::Locked));
-
- // Validate that the dst address's state is valid.
- KMemoryPermission dst_perm;
- size_t num_dst_allocator_blocks;
- R_TRY(this->CheckMemoryState(
- nullptr, std::addressof(dst_perm), nullptr, std::addressof(num_dst_allocator_blocks),
- dst_address, size, KMemoryState::All, KMemoryState::Stack, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None));
-
- // Create an update allocator for the source.
- Result src_allocator_result;
- KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
- m_memory_block_slab_manager,
- num_src_allocator_blocks);
- R_TRY(src_allocator_result);
-
- // Create an update allocator for the destination.
- Result dst_allocator_result;
- KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
- m_memory_block_slab_manager,
- num_dst_allocator_blocks);
- R_TRY(dst_allocator_result);
-
- // Unmap the memory.
- {
- // Determine the number of pages being operated on.
- const size_t num_pages = size / PageSize;
-
- // Create page groups for the memory being unmapped.
- KPageGroup pg{m_kernel, m_block_info_manager};
-
- // Create the page group representing the destination.
- R_TRY(this->MakePageGroup(pg, dst_address, num_pages));
-
- // Ensure the page group is the valid for the source.
- R_UNLESS(this->IsValidPageGroup(pg, src_address, num_pages), ResultInvalidMemoryRegion);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Unmap the aliased copy of the pages.
- const KPageProperties dst_unmap_properties = {KMemoryPermission::None, false, false,
- DisableMergeAttribute::None};
- R_TRY(
- this->Operate(dst_address, num_pages, dst_unmap_properties.perm, OperationType::Unmap));
-
- // Ensure that we re-map the aliased pages on failure.
- ON_RESULT_FAILURE {
- this->RemapPageGroup(updater.GetPageList(), dst_address, size, pg);
- };
-
- // Try to set the permissions for the source pages back to what they should be.
- const KPageProperties src_properties = {KMemoryPermission::UserReadWrite, false, false,
- DisableMergeAttribute::EnableAndMergeHeadBodyTail};
- R_TRY(this->Operate(src_address, num_pages, src_properties.perm,
- OperationType::ChangePermissions));
-
- // Apply the memory block updates.
- m_memory_block_manager.Update(
- std::addressof(src_allocator), src_address, num_pages, src_state,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked);
- m_memory_block_manager.Update(
- std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::None,
- KMemoryPermission::None, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal);
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address,
- size_t num_pages, KMemoryPermission perm) {
- ASSERT(this->IsLockedByCurrentThread());
-
- // Create a page group to hold the pages we allocate.
- KPageGroup pg{m_kernel, m_block_info_manager};
-
- // Allocate the pages.
- R_TRY(
- m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, m_allocate_option));
-
- // Ensure that the page group is closed when we're done working with it.
- SCOPE_EXIT({ pg.Close(); });
-
- // Clear all pages.
- for (const auto& it : pg) {
- std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), m_heap_fill_value,
- it.GetSize());
- }
-
- // Map the pages.
- R_RETURN(this->Operate(address, num_pages, pg, OperationType::MapGroup));
-}
-
-Result KPageTable::MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address,
- const KPageGroup& pg, const KPageProperties properties,
- bool reuse_ll) {
- ASSERT(this->IsLockedByCurrentThread());
-
- // Note the current address, so that we can iterate.
- const KProcessAddress start_address = address;
- KProcessAddress cur_address = address;
-
- // Ensure that we clean up on failure.
- ON_RESULT_FAILURE {
- ASSERT(!reuse_ll);
- if (cur_address != start_address) {
- const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
- DisableMergeAttribute::None};
- ASSERT(this->Operate(start_address, (cur_address - start_address) / PageSize,
- unmap_properties.perm, OperationType::Unmap) == ResultSuccess);
- }
- };
-
- // Iterate, mapping all pages in the group.
- for (const auto& block : pg) {
- // Map and advance.
- const KPageProperties cur_properties =
- (cur_address == start_address)
- ? properties
- : KPageProperties{properties.perm, properties.io, properties.uncached,
- DisableMergeAttribute::None};
- this->Operate(cur_address, block.GetNumPages(), cur_properties.perm, OperationType::Map,
- block.GetAddress());
- cur_address += block.GetSize();
- }
-
- // We succeeded!
- R_SUCCEED();
-}
-
-void KPageTable::RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size,
- const KPageGroup& pg) {
- ASSERT(this->IsLockedByCurrentThread());
-
- // Note the current address, so that we can iterate.
- const KProcessAddress start_address = address;
- const KProcessAddress last_address = start_address + size - 1;
- const KProcessAddress end_address = last_address + 1;
-
- // Iterate over the memory.
- auto pg_it = pg.begin();
- ASSERT(pg_it != pg.end());
-
- KPhysicalAddress pg_phys_addr = pg_it->GetAddress();
- size_t pg_pages = pg_it->GetNumPages();
-
- auto it = m_memory_block_manager.FindIterator(start_address);
- while (true) {
- // Check that the iterator is valid.
- ASSERT(it != m_memory_block_manager.end());
-
- // Get the memory info.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // Determine the range to map.
- KProcessAddress map_address = std::max<KProcessAddress>(info.GetAddress(), start_address);
- const KProcessAddress map_end_address =
- std::min<KProcessAddress>(info.GetEndAddress(), end_address);
- ASSERT(map_end_address != map_address);
-
- // Determine if we should disable head merge.
- const bool disable_head_merge =
- info.GetAddress() >= GetInteger(start_address) &&
- True(info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Normal);
- const KPageProperties map_properties = {
- info.GetPermission(), false, false,
- disable_head_merge ? DisableMergeAttribute::DisableHead : DisableMergeAttribute::None};
-
- // While we have pages to map, map them.
- size_t map_pages = (map_end_address - map_address) / PageSize;
- while (map_pages > 0) {
- // Check if we're at the end of the physical block.
- if (pg_pages == 0) {
- // Ensure there are more pages to map.
- ASSERT(pg_it != pg.end());
-
- // Advance our physical block.
- ++pg_it;
- pg_phys_addr = pg_it->GetAddress();
- pg_pages = pg_it->GetNumPages();
- }
-
- // Map whatever we can.
- const size_t cur_pages = std::min(pg_pages, map_pages);
- ASSERT(this->Operate(map_address, map_pages, map_properties.perm, OperationType::Map,
- pg_phys_addr) == ResultSuccess);
-
- // Advance.
- map_address += cur_pages * PageSize;
- map_pages -= cur_pages;
-
- pg_phys_addr += cur_pages * PageSize;
- pg_pages -= cur_pages;
- }
-
- // Check if we're done.
- if (last_address <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- ++it;
- }
-
- // Check that we re-mapped precisely the page group.
- ASSERT((++pg_it) == pg.end());
-}
-
-Result KPageTable::MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
- KPhysicalAddress phys_addr, bool is_pa_valid,
- KProcessAddress region_start, size_t region_num_pages,
- KMemoryState state, KMemoryPermission perm) {
- ASSERT(Common::IsAligned(alignment, PageSize) && alignment >= PageSize);
-
- // Ensure this is a valid map request.
- R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state),
- ResultInvalidCurrentMemory);
- R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Find a random address to map at.
- KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment,
- 0, this->GetNumGuardPages());
- R_UNLESS(addr != 0, ResultOutOfMemory);
- ASSERT(Common::IsAligned(GetInteger(addr), alignment));
- ASSERT(this->CanContain(addr, num_pages * PageSize, state));
- ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free,
- KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::None, KMemoryAttribute::None) == ResultSuccess);
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Perform mapping operation.
- if (is_pa_valid) {
- const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
- R_TRY(this->Operate(addr, num_pages, properties.perm, OperationType::Map, phys_addr));
- } else {
- R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), addr, num_pages, perm));
- }
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
- KMemoryBlockDisableMergeAttribute::None);
-
- // We successfully mapped the pages.
- *out_addr = addr;
- R_SUCCEED();
-}
-
-Result KPageTable::MapPages(KProcessAddress address, size_t num_pages, KMemoryState state,
- KMemoryPermission perm) {
- // Check that the map is in range.
- const size_t size = num_pages * PageSize;
- R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check the memory state.
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
- KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::None,
- KMemoryAttribute::None));
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Map the pages.
- R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), address, num_pages, perm));
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
- KMemoryBlockDisableMergeAttribute::None);
-
- R_SUCCEED();
-}
-
-Result KPageTable::UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state) {
- // Check that the unmap is in range.
- const size_t size = num_pages * PageSize;
- R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check the memory state.
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
- KMemoryState::All, state, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::All,
- KMemoryAttribute::None));
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Perform the unmap.
- const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
- DisableMergeAttribute::None};
- R_TRY(this->Operate(address, num_pages, unmap_properties.perm, OperationType::Unmap));
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free,
- KMemoryPermission::None, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::Normal);
-
- R_SUCCEED();
-}
-
-Result KPageTable::MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg,
- KProcessAddress region_start, size_t region_num_pages,
- KMemoryState state, KMemoryPermission perm) {
- ASSERT(!this->IsLockedByCurrentThread());
-
- // Ensure this is a valid map request.
- const size_t num_pages = pg.GetNumPages();
- R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state),
- ResultInvalidCurrentMemory);
- R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Find a random address to map at.
- KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, PageSize,
- 0, this->GetNumGuardPages());
- R_UNLESS(addr != 0, ResultOutOfMemory);
- ASSERT(this->CanContain(addr, num_pages * PageSize, state));
- ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free,
- KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::None, KMemoryAttribute::None) == ResultSuccess);
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Perform mapping operation.
- const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
- R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false));
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
- KMemoryBlockDisableMergeAttribute::None);
-
- // We successfully mapped the pages.
- *out_addr = addr;
- R_SUCCEED();
-}
-
-Result KPageTable::MapPageGroup(KProcessAddress addr, const KPageGroup& pg, KMemoryState state,
- KMemoryPermission perm) {
- ASSERT(!this->IsLockedByCurrentThread());
-
- // Ensure this is a valid map request.
- const size_t num_pages = pg.GetNumPages();
- const size_t size = num_pages * PageSize;
- R_UNLESS(this->CanContain(addr, size, state), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check if state allows us to map.
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), addr, size,
- KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::None,
- KMemoryAttribute::None));
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Perform mapping operation.
- const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
- R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false));
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
- KMemoryBlockDisableMergeAttribute::None);
-
- // We successfully mapped the pages.
- R_SUCCEED();
-}
-
-Result KPageTable::UnmapPageGroup(KProcessAddress address, const KPageGroup& pg,
- KMemoryState state) {
- ASSERT(!this->IsLockedByCurrentThread());
-
- // Ensure this is a valid unmap request.
- const size_t num_pages = pg.GetNumPages();
- const size_t size = num_pages * PageSize;
- R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check if state allows us to unmap.
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
- KMemoryState::All, state, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::All,
- KMemoryAttribute::None));
-
- // Check that the page group is valid.
- R_UNLESS(this->IsValidPageGroup(pg, address, num_pages), ResultInvalidCurrentMemory);
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // We're going to perform an update, so create a helper.
- KScopedPageTableUpdater updater(this);
-
- // Perform unmapping operation.
- const KPageProperties properties = {KMemoryPermission::None, false, false,
- DisableMergeAttribute::None};
- R_TRY(this->Operate(address, num_pages, properties.perm, OperationType::Unmap));
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free,
- KMemoryPermission::None, KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::Normal);
-
- R_SUCCEED();
-}
-
-Result KPageTable::MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
- KMemoryState state_mask, KMemoryState state,
- KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr) {
- // Ensure that the page group isn't null.
- ASSERT(out != nullptr);
-
- // Make sure that the region we're mapping is valid for the table.
- const size_t size = num_pages * PageSize;
- R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check if state allows us to create the group.
- R_TRY(this->CheckMemoryState(address, size, state_mask | KMemoryState::FlagReferenceCounted,
- state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
- attr_mask, attr));
-
- // Create a new page group for the region.
- R_TRY(this->MakePageGroup(*out, address, num_pages));
-
- R_SUCCEED();
-}
-
-Result KPageTable::SetProcessMemoryPermission(KProcessAddress addr, size_t size,
- Svc::MemoryPermission svc_perm) {
- const size_t num_pages = size / PageSize;
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Verify we can change the memory permission.
- KMemoryState old_state;
- KMemoryPermission old_perm;
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), nullptr,
- std::addressof(num_allocator_blocks), addr, size,
- KMemoryState::FlagCode, KMemoryState::FlagCode,
- KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::All, KMemoryAttribute::None));
-
- // Determine new perm/state.
- const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm);
- KMemoryState new_state = old_state;
- const bool is_w = (new_perm & KMemoryPermission::UserWrite) == KMemoryPermission::UserWrite;
- const bool is_x = (new_perm & KMemoryPermission::UserExecute) == KMemoryPermission::UserExecute;
- const bool was_x =
- (old_perm & KMemoryPermission::UserExecute) == KMemoryPermission::UserExecute;
- ASSERT(!(is_w && is_x));
-
- if (is_w) {
- switch (old_state) {
- case KMemoryState::Code:
- new_state = KMemoryState::CodeData;
- break;
- case KMemoryState::AliasCode:
- new_state = KMemoryState::AliasCodeData;
- break;
- default:
- ASSERT(false);
- break;
- }
- }
-
- // Succeed if there's nothing to do.
- R_SUCCEED_IF(old_perm == new_perm && old_state == new_state);
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Perform mapping operation.
- const auto operation =
- was_x ? OperationType::ChangePermissionsAndRefresh : OperationType::ChangePermissions;
- R_TRY(Operate(addr, num_pages, new_perm, operation));
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, new_state, new_perm,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::None);
-
- // Ensure cache coherency, if we're setting pages as executable.
- if (is_x) {
- m_system.InvalidateCpuInstructionCacheRange(GetInteger(addr), size);
- }
-
- R_SUCCEED();
-}
-
-KMemoryInfo KPageTable::QueryInfoImpl(KProcessAddress addr) {
- KScopedLightLock lk(m_general_lock);
-
- return m_memory_block_manager.FindBlock(addr)->GetMemoryInfo();
-}
-
-KMemoryInfo KPageTable::QueryInfo(KProcessAddress addr) {
- if (!Contains(addr, 1)) {
- return {
- .m_address = GetInteger(m_address_space_end),
- .m_size = 0 - GetInteger(m_address_space_end),
- .m_state = static_cast<KMemoryState>(Svc::MemoryState::Inaccessible),
- .m_device_disable_merge_left_count = 0,
- .m_device_disable_merge_right_count = 0,
- .m_ipc_lock_count = 0,
- .m_device_use_count = 0,
- .m_ipc_disable_merge_count = 0,
- .m_permission = KMemoryPermission::None,
- .m_attribute = KMemoryAttribute::None,
- .m_original_permission = KMemoryPermission::None,
- .m_disable_merge_attribute = KMemoryBlockDisableMergeAttribute::None,
- };
- }
-
- return QueryInfoImpl(addr);
-}
-
-Result KPageTable::SetMemoryPermission(KProcessAddress addr, size_t size,
- Svc::MemoryPermission svc_perm) {
- const size_t num_pages = size / PageSize;
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Verify we can change the memory permission.
- KMemoryState old_state;
- KMemoryPermission old_perm;
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), nullptr,
- std::addressof(num_allocator_blocks), addr, size,
- KMemoryState::FlagCanReprotect, KMemoryState::FlagCanReprotect,
- KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::All, KMemoryAttribute::None));
-
- // Determine new perm.
- const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm);
- R_SUCCEED_IF(old_perm == new_perm);
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Perform mapping operation.
- R_TRY(Operate(addr, num_pages, new_perm, OperationType::ChangePermissions));
-
- // Update the blocks.
- m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm,
- KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::None);
-
- R_SUCCEED();
-}
-
-Result KPageTable::SetMemoryAttribute(KProcessAddress addr, size_t size, u32 mask, u32 attr) {
- const size_t num_pages = size / PageSize;
- ASSERT((static_cast<KMemoryAttribute>(mask) | KMemoryAttribute::SetMask) ==
- KMemoryAttribute::SetMask);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Verify we can change the memory attribute.
- KMemoryState old_state;
- KMemoryPermission old_perm;
- KMemoryAttribute old_attr;
- size_t num_allocator_blocks;
- constexpr auto AttributeTestMask =
- ~(KMemoryAttribute::SetMask | KMemoryAttribute::DeviceShared);
- const KMemoryState state_test_mask =
- static_cast<KMemoryState>(((mask & static_cast<u32>(KMemoryAttribute::Uncached))
- ? static_cast<u32>(KMemoryState::FlagCanChangeAttribute)
- : 0) |
- ((mask & static_cast<u32>(KMemoryAttribute::PermissionLocked))
- ? static_cast<u32>(KMemoryState::FlagCanPermissionLock)
- : 0));
- R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
- std::addressof(old_attr), std::addressof(num_allocator_blocks),
- addr, size, state_test_mask, state_test_mask,
- KMemoryPermission::None, KMemoryPermission::None,
- AttributeTestMask, KMemoryAttribute::None, ~AttributeTestMask));
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // If we need to, perform a change attribute operation.
- if (True(KMemoryAttribute::Uncached & static_cast<KMemoryAttribute>(mask))) {
- // Perform operation.
- R_TRY(this->Operate(addr, num_pages, old_perm,
- OperationType::ChangePermissionsAndRefreshAndFlush, 0));
- }
-
- // Update the blocks.
- m_memory_block_manager.UpdateAttribute(std::addressof(allocator), addr, num_pages,
- static_cast<KMemoryAttribute>(mask),
- static_cast<KMemoryAttribute>(attr));
-
- R_SUCCEED();
-}
-
-Result KPageTable::SetMaxHeapSize(size_t size) {
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Only process page tables are allowed to set heap size.
- ASSERT(!this->IsKernel());
-
- m_max_heap_size = size;
-
- R_SUCCEED();
-}
-
-Result KPageTable::SetHeapSize(u64* out, size_t size) {
- // Lock the physical memory mutex.
- KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock);
-
- // Try to perform a reduction in heap, instead of an extension.
- KProcessAddress cur_address{};
- size_t allocation_size{};
- {
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Validate that setting heap size is possible at all.
- R_UNLESS(!m_is_kernel, ResultOutOfMemory);
- R_UNLESS(size <= static_cast<size_t>(m_heap_region_end - m_heap_region_start),
- ResultOutOfMemory);
- R_UNLESS(size <= m_max_heap_size, ResultOutOfMemory);
-
- if (size < GetHeapSize()) {
- // The size being requested is less than the current size, so we need to free the end of
- // the heap.
-
- // Validate memory state.
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks),
- m_heap_region_start + size, GetHeapSize() - size,
- KMemoryState::All, KMemoryState::Normal,
- KMemoryPermission::All, KMemoryPermission::UserReadWrite,
- KMemoryAttribute::All, KMemoryAttribute::None));
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager,
- num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Unmap the end of the heap.
- const auto num_pages = (GetHeapSize() - size) / PageSize;
- R_TRY(Operate(m_heap_region_start + size, num_pages, KMemoryPermission::None,
- OperationType::Unmap));
-
- // Release the memory from the resource limit.
- m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, num_pages * PageSize);
-
- // Apply the memory block update.
- m_memory_block_manager.Update(std::addressof(allocator), m_heap_region_start + size,
- num_pages, KMemoryState::Free, KMemoryPermission::None,
- KMemoryAttribute::None,
- KMemoryBlockDisableMergeAttribute::None,
- size == 0 ? KMemoryBlockDisableMergeAttribute::Normal
- : KMemoryBlockDisableMergeAttribute::None);
-
- // Update the current heap end.
- m_current_heap_end = m_heap_region_start + size;
-
- // Set the output.
- *out = GetInteger(m_heap_region_start);
- R_SUCCEED();
- } else if (size == GetHeapSize()) {
- // The size requested is exactly the current size.
- *out = GetInteger(m_heap_region_start);
- R_SUCCEED();
- } else {
- // We have to allocate memory. Determine how much to allocate and where while the table
- // is locked.
- cur_address = m_current_heap_end;
- allocation_size = size - GetHeapSize();
- }
- }
-
- // Reserve memory for the heap extension.
- KScopedResourceReservation memory_reservation(
- m_resource_limit, LimitableResource::PhysicalMemoryMax, allocation_size);
- R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
-
- // Allocate pages for the heap extension.
- KPageGroup pg{m_kernel, m_block_info_manager};
- R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
- &pg, allocation_size / PageSize,
- KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option)));
-
- // Clear all the newly allocated pages.
- for (const auto& it : pg) {
- std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), m_heap_fill_value,
- it.GetSize());
- }
-
- // Map the pages.
- {
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Ensure that the heap hasn't changed since we began executing.
- ASSERT(cur_address == m_current_heap_end);
-
- // Check the memory state.
- size_t num_allocator_blocks{};
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), m_current_heap_end,
- allocation_size, KMemoryState::All, KMemoryState::Free,
- KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::None, KMemoryAttribute::None));
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(
- std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Map the pages.
- const auto num_pages = allocation_size / PageSize;
- R_TRY(Operate(m_current_heap_end, num_pages, pg, OperationType::MapGroup));
-
- // Clear all the newly allocated pages.
- for (size_t cur_page = 0; cur_page < num_pages; ++cur_page) {
- std::memset(m_memory->GetPointer(m_current_heap_end + (cur_page * PageSize)), 0,
- PageSize);
- }
-
- // We succeeded, so commit our memory reservation.
- memory_reservation.Commit();
-
- // Apply the memory block update.
- m_memory_block_manager.Update(
- std::addressof(allocator), m_current_heap_end, num_pages, KMemoryState::Normal,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
- m_heap_region_start == m_current_heap_end ? KMemoryBlockDisableMergeAttribute::Normal
- : KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::None);
-
- // Update the current heap end.
- m_current_heap_end = m_heap_region_start + size;
-
- // Set the output.
- *out = GetInteger(m_heap_region_start);
- R_SUCCEED();
- }
-}
-
-Result KPageTable::LockForMapDeviceAddressSpace(bool* out_is_io, KProcessAddress address,
- size_t size, KMemoryPermission perm,
- bool is_aligned, bool check_heap) {
- // Lightly validate the range before doing anything else.
- const size_t num_pages = size / PageSize;
- R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check the memory state.
- const auto test_state =
- (is_aligned ? KMemoryState::FlagCanAlignedDeviceMap : KMemoryState::FlagCanDeviceMap) |
- (check_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::None);
- size_t num_allocator_blocks;
- KMemoryState old_state;
- R_TRY(this->CheckMemoryState(std::addressof(old_state), nullptr, nullptr,
- std::addressof(num_allocator_blocks), address, size, test_state,
- test_state, perm, perm,
- KMemoryAttribute::IpcLocked | KMemoryAttribute::Locked,
- KMemoryAttribute::None, KMemoryAttribute::DeviceShared));
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Update the memory blocks.
- m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages,
- &KMemoryBlock::ShareToDevice, KMemoryPermission::None);
-
- // Set whether the locked memory was io.
- *out_is_io =
- static_cast<Svc::MemoryState>(old_state & KMemoryState::Mask) == Svc::MemoryState::Io;
-
- R_SUCCEED();
-}
-
-Result KPageTable::LockForUnmapDeviceAddressSpace(KProcessAddress address, size_t size,
- bool check_heap) {
- // Lightly validate the range before doing anything else.
- const size_t num_pages = size / PageSize;
- R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check the memory state.
- const auto test_state = KMemoryState::FlagCanDeviceMap |
- (check_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::None);
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryStateContiguous(
- std::addressof(num_allocator_blocks), address, size, test_state, test_state,
- KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared));
-
- // Create an update allocator.
- Result allocator_result;
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Update the memory blocks.
- const KMemoryBlockManager::MemoryBlockLockFunction lock_func =
- m_enable_device_address_space_merge
- ? &KMemoryBlock::UpdateDeviceDisableMergeStateForShare
- : &KMemoryBlock::UpdateDeviceDisableMergeStateForShareRight;
- m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, lock_func,
- KMemoryPermission::None);
-
- R_SUCCEED();
-}
-
-Result KPageTable::UnlockForDeviceAddressSpace(KProcessAddress address, size_t size) {
- // Lightly validate the range before doing anything else.
- const size_t num_pages = size / PageSize;
- R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check the memory state.
- size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryStateContiguous(
- std::addressof(num_allocator_blocks), address, size, KMemoryState::FlagCanDeviceMap,
- KMemoryState::FlagCanDeviceMap, KMemoryPermission::None, KMemoryPermission::None,
- KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared));
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Update the memory blocks.
- m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages,
- &KMemoryBlock::UnshareToDevice, KMemoryPermission::None);
-
- R_SUCCEED();
-}
-
-Result KPageTable::LockForIpcUserBuffer(KPhysicalAddress* out, KProcessAddress address,
- size_t size) {
- R_RETURN(this->LockMemoryAndOpen(
- nullptr, out, address, size, KMemoryState::FlagCanIpcUserBuffer,
- KMemoryState::FlagCanIpcUserBuffer, KMemoryPermission::All,
- KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None,
- KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite,
- KMemoryAttribute::Locked));
-}
-
-Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size) {
- R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanIpcUserBuffer,
- KMemoryState::FlagCanIpcUserBuffer, KMemoryPermission::None,
- KMemoryPermission::None, KMemoryAttribute::All,
- KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
- 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,
- KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
- KMemoryAttribute::None, KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite,
- KMemoryAttribute::Locked));
-}
-
-Result KPageTable::UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg) {
- R_RETURN(this->UnlockMemory(
- addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
- KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All,
- KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, KMemoryAttribute::Locked, &pg));
-}
-
-bool KPageTable::IsRegionContiguous(KProcessAddress addr, u64 size) const {
- auto start_ptr = m_system.DeviceMemory().GetPointer<u8>(GetInteger(addr));
- for (u64 offset{}; offset < size; offset += PageSize) {
- if (start_ptr != m_system.DeviceMemory().GetPointer<u8>(GetInteger(addr) + offset)) {
- return false;
- }
- start_ptr += PageSize;
- }
- return true;
-}
-
-void KPageTable::AddRegionToPages(KProcessAddress start, size_t num_pages,
- KPageGroup& page_linked_list) {
- KProcessAddress addr{start};
- while (addr < start + (num_pages * PageSize)) {
- const KPhysicalAddress paddr{GetPhysicalAddr(addr)};
- ASSERT(paddr != 0);
- page_linked_list.AddBlock(paddr, 1);
- addr += PageSize;
- }
-}
-
-KProcessAddress KPageTable::AllocateVirtualMemory(KProcessAddress start, size_t region_num_pages,
- u64 needed_num_pages, size_t align) {
- if (m_enable_aslr) {
- UNIMPLEMENTED();
- }
- return m_memory_block_manager.FindFreeArea(start, region_num_pages, needed_num_pages, align, 0,
- IsKernel() ? 1 : 4);
-}
-
-Result KPageTable::Operate(KProcessAddress addr, size_t num_pages, const KPageGroup& page_group,
- OperationType operation) {
- ASSERT(this->IsLockedByCurrentThread());
-
- ASSERT(Common::IsAligned(GetInteger(addr), PageSize));
- ASSERT(num_pages > 0);
- ASSERT(num_pages == page_group.GetNumPages());
-
- switch (operation) {
- case OperationType::MapGroup:
- case OperationType::MapFirstGroup: {
- // We want to maintain a new reference to every page in the group.
- KScopedPageGroup spg(page_group, operation != OperationType::MapFirstGroup);
-
- for (const auto& node : page_group) {
- const size_t size{node.GetNumPages() * PageSize};
-
- // Map the pages.
- m_memory->MapMemoryRegion(*m_page_table_impl, addr, size, node.GetAddress());
-
- addr += size;
- }
-
- // We succeeded! We want to persist the reference to the pages.
- spg.CancelClose();
-
- break;
- }
- default:
- ASSERT(false);
- break;
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::Operate(KProcessAddress addr, size_t num_pages, KMemoryPermission perm,
- OperationType operation, KPhysicalAddress map_addr) {
- ASSERT(this->IsLockedByCurrentThread());
-
- ASSERT(num_pages > 0);
- ASSERT(Common::IsAligned(GetInteger(addr), PageSize));
- ASSERT(ContainsPages(addr, num_pages));
-
- switch (operation) {
- case OperationType::Unmap: {
- // Ensure that any pages we track close on exit.
- KPageGroup pages_to_close{m_kernel, this->GetBlockInfoManager()};
- SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
-
- this->AddRegionToPages(addr, num_pages, pages_to_close);
- m_memory->UnmapRegion(*m_page_table_impl, addr, num_pages * PageSize);
- break;
- }
- case OperationType::Map: {
- ASSERT(map_addr);
- ASSERT(Common::IsAligned(GetInteger(map_addr), PageSize));
- m_memory->MapMemoryRegion(*m_page_table_impl, addr, num_pages * PageSize, map_addr);
-
- // Open references to pages, if we should.
- if (IsHeapPhysicalAddress(m_kernel.MemoryLayout(), map_addr)) {
- m_kernel.MemoryManager().Open(map_addr, num_pages);
- }
- break;
- }
- case OperationType::Separate: {
- // HACK: Unimplemented.
- break;
- }
- case OperationType::ChangePermissions:
- case OperationType::ChangePermissionsAndRefresh:
- case OperationType::ChangePermissionsAndRefreshAndFlush:
- break;
- default:
- ASSERT(false);
- break;
- }
- R_SUCCEED();
-}
-
-void KPageTable::FinalizeUpdate(PageLinkedList* page_list) {
- while (page_list->Peek()) {
- [[maybe_unused]] auto page = page_list->Pop();
-
- // TODO(bunnei): Free pages once they are allocated in guest memory
- // ASSERT(this->GetPageTableManager().IsInPageTableHeap(page));
- // ASSERT(this->GetPageTableManager().GetRefCount(page) == 0);
- // this->GetPageTableManager().Free(page);
- }
-}
-
-KProcessAddress KPageTable::GetRegionAddress(Svc::MemoryState state) const {
- switch (state) {
- case Svc::MemoryState::Free:
- case Svc::MemoryState::Kernel:
- return m_address_space_start;
- case Svc::MemoryState::Normal:
- return m_heap_region_start;
- case Svc::MemoryState::Ipc:
- case Svc::MemoryState::NonSecureIpc:
- case Svc::MemoryState::NonDeviceIpc:
- return m_alias_region_start;
- case Svc::MemoryState::Stack:
- return m_stack_region_start;
- case Svc::MemoryState::Static:
- case Svc::MemoryState::ThreadLocal:
- return m_kernel_map_region_start;
- case Svc::MemoryState::Io:
- case Svc::MemoryState::Shared:
- case Svc::MemoryState::AliasCode:
- case Svc::MemoryState::AliasCodeData:
- case Svc::MemoryState::Transfered:
- case Svc::MemoryState::SharedTransfered:
- case Svc::MemoryState::SharedCode:
- case Svc::MemoryState::GeneratedCode:
- case Svc::MemoryState::CodeOut:
- case Svc::MemoryState::Coverage:
- case Svc::MemoryState::Insecure:
- return m_alias_code_region_start;
- case Svc::MemoryState::Code:
- case Svc::MemoryState::CodeData:
- return m_code_region_start;
- default:
- UNREACHABLE();
- }
-}
-
-size_t KPageTable::GetRegionSize(Svc::MemoryState state) const {
- switch (state) {
- case Svc::MemoryState::Free:
- case Svc::MemoryState::Kernel:
- return m_address_space_end - m_address_space_start;
- case Svc::MemoryState::Normal:
- return m_heap_region_end - m_heap_region_start;
- case Svc::MemoryState::Ipc:
- case Svc::MemoryState::NonSecureIpc:
- case Svc::MemoryState::NonDeviceIpc:
- return m_alias_region_end - m_alias_region_start;
- case Svc::MemoryState::Stack:
- return m_stack_region_end - m_stack_region_start;
- case Svc::MemoryState::Static:
- case Svc::MemoryState::ThreadLocal:
- return m_kernel_map_region_end - m_kernel_map_region_start;
- case Svc::MemoryState::Io:
- case Svc::MemoryState::Shared:
- case Svc::MemoryState::AliasCode:
- case Svc::MemoryState::AliasCodeData:
- case Svc::MemoryState::Transfered:
- case Svc::MemoryState::SharedTransfered:
- case Svc::MemoryState::SharedCode:
- case Svc::MemoryState::GeneratedCode:
- case Svc::MemoryState::CodeOut:
- case Svc::MemoryState::Coverage:
- case Svc::MemoryState::Insecure:
- return m_alias_code_region_end - m_alias_code_region_start;
- case Svc::MemoryState::Code:
- case Svc::MemoryState::CodeData:
- return m_code_region_end - m_code_region_start;
- default:
- UNREACHABLE();
- }
-}
-
-bool KPageTable::CanContain(KProcessAddress addr, size_t size, Svc::MemoryState state) const {
- const KProcessAddress end = addr + size;
- const KProcessAddress last = end - 1;
-
- const KProcessAddress region_start = this->GetRegionAddress(state);
- const size_t region_size = this->GetRegionSize(state);
-
- const bool is_in_region =
- region_start <= addr && addr < end && last <= region_start + region_size - 1;
- const bool is_in_heap = !(end <= m_heap_region_start || m_heap_region_end <= addr ||
- m_heap_region_start == m_heap_region_end);
- const bool is_in_alias = !(end <= m_alias_region_start || m_alias_region_end <= addr ||
- m_alias_region_start == m_alias_region_end);
- switch (state) {
- case Svc::MemoryState::Free:
- case Svc::MemoryState::Kernel:
- return is_in_region;
- case Svc::MemoryState::Io:
- case Svc::MemoryState::Static:
- case Svc::MemoryState::Code:
- case Svc::MemoryState::CodeData:
- case Svc::MemoryState::Shared:
- case Svc::MemoryState::AliasCode:
- case Svc::MemoryState::AliasCodeData:
- case Svc::MemoryState::Stack:
- case Svc::MemoryState::ThreadLocal:
- case Svc::MemoryState::Transfered:
- case Svc::MemoryState::SharedTransfered:
- case Svc::MemoryState::SharedCode:
- case Svc::MemoryState::GeneratedCode:
- case Svc::MemoryState::CodeOut:
- case Svc::MemoryState::Coverage:
- case Svc::MemoryState::Insecure:
- return is_in_region && !is_in_heap && !is_in_alias;
- case Svc::MemoryState::Normal:
- ASSERT(is_in_heap);
- return is_in_region && !is_in_alias;
- case Svc::MemoryState::Ipc:
- case Svc::MemoryState::NonSecureIpc:
- case Svc::MemoryState::NonDeviceIpc:
- ASSERT(is_in_alias);
- return is_in_region && !is_in_heap;
- default:
- return false;
- }
-}
-
-Result KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr) const {
- // Validate the states match expectation.
- R_UNLESS((info.m_state & state_mask) == state, ResultInvalidCurrentMemory);
- R_UNLESS((info.m_permission & perm_mask) == perm, ResultInvalidCurrentMemory);
- R_UNLESS((info.m_attribute & attr_mask) == attr, ResultInvalidCurrentMemory);
-
- R_SUCCEED();
-}
-
-Result KPageTable::CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr,
- size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr) const {
- ASSERT(this->IsLockedByCurrentThread());
-
- // Get information about the first block.
- const KProcessAddress last_addr = addr + size - 1;
- KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr);
- KMemoryInfo info = it->GetMemoryInfo();
-
- // If the start address isn't aligned, we need a block.
- const size_t blocks_for_start_align =
- (Common::AlignDown(GetInteger(addr), PageSize) != info.GetAddress()) ? 1 : 0;
-
- while (true) {
- // Validate against the provided masks.
- R_TRY(this->CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr));
-
- // Break once we're done.
- if (last_addr <= info.GetLastAddress()) {
- break;
- }
-
- // Advance our iterator.
- it++;
- ASSERT(it != m_memory_block_manager.cend());
- info = it->GetMemoryInfo();
- }
-
- // If the end address isn't aligned, we need a block.
- const size_t blocks_for_end_align =
- (Common::AlignUp(GetInteger(addr) + size, PageSize) != info.GetEndAddress()) ? 1 : 0;
-
- if (out_blocks_needed != nullptr) {
- *out_blocks_needed = blocks_for_start_align + blocks_for_end_align;
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
- KMemoryAttribute* out_attr, size_t* out_blocks_needed,
- KMemoryBlockManager::const_iterator it,
- KProcessAddress last_addr, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr, KMemoryAttribute ignore_attr) const {
- ASSERT(this->IsLockedByCurrentThread());
-
- // Get information about the first block.
- KMemoryInfo info = it->GetMemoryInfo();
-
- // Validate all blocks in the range have correct state.
- const KMemoryState first_state = info.m_state;
- const KMemoryPermission first_perm = info.m_permission;
- const KMemoryAttribute first_attr = info.m_attribute;
- while (true) {
- // Validate the current block.
- R_UNLESS(info.m_state == first_state, ResultInvalidCurrentMemory);
- R_UNLESS(info.m_permission == first_perm, ResultInvalidCurrentMemory);
- R_UNLESS((info.m_attribute | ignore_attr) == (first_attr | ignore_attr),
- ResultInvalidCurrentMemory);
-
- // Validate against the provided masks.
- R_TRY(this->CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr));
-
- // Break once we're done.
- if (last_addr <= info.GetLastAddress()) {
- break;
- }
-
- // Advance our iterator.
- it++;
- ASSERT(it != m_memory_block_manager.cend());
- info = it->GetMemoryInfo();
- }
-
- // Write output state.
- if (out_state != nullptr) {
- *out_state = first_state;
- }
- if (out_perm != nullptr) {
- *out_perm = first_perm;
- }
- if (out_attr != nullptr) {
- *out_attr = static_cast<KMemoryAttribute>(first_attr & ~ignore_attr);
- }
-
- // If the end address isn't aligned, we need a block.
- if (out_blocks_needed != nullptr) {
- const size_t blocks_for_end_align =
- (Common::AlignDown(GetInteger(last_addr), PageSize) + PageSize != info.GetEndAddress())
- ? 1
- : 0;
- *out_blocks_needed = blocks_for_end_align;
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
- KMemoryAttribute* out_attr, size_t* out_blocks_needed,
- KProcessAddress addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr, KMemoryAttribute ignore_attr) const {
- ASSERT(this->IsLockedByCurrentThread());
-
- // Check memory state.
- const KProcessAddress last_addr = addr + size - 1;
- KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr);
- R_TRY(this->CheckMemoryState(out_state, out_perm, out_attr, out_blocks_needed, it, last_addr,
- state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr));
-
- // If the start address isn't aligned, we need a block.
- if (out_blocks_needed != nullptr &&
- Common::AlignDown(GetInteger(addr), PageSize) != it->GetAddress()) {
- ++(*out_blocks_needed);
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_KPhysicalAddress,
- KProcessAddress addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr, KMemoryPermission new_perm,
- KMemoryAttribute lock_attr) {
- // Validate basic preconditions.
- ASSERT((lock_attr & attr) == KMemoryAttribute::None);
- ASSERT((lock_attr & (KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared)) ==
- KMemoryAttribute::None);
-
- // Validate the lock request.
- const size_t num_pages = size / PageSize;
- R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check that the output page group is empty, if it exists.
- if (out_pg) {
- ASSERT(out_pg->GetNumPages() == 0);
- }
-
- // Check the state.
- KMemoryState old_state{};
- KMemoryPermission old_perm{};
- KMemoryAttribute old_attr{};
- size_t num_allocator_blocks{};
- R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
- std::addressof(old_attr), std::addressof(num_allocator_blocks),
- addr, size, state_mask | KMemoryState::FlagReferenceCounted,
- state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
- attr_mask, attr));
-
- // Get the physical address, if we're supposed to.
- if (out_KPhysicalAddress != nullptr) {
- ASSERT(this->GetPhysicalAddressLocked(out_KPhysicalAddress, addr));
- }
-
- // Make the page group, if we're supposed to.
- if (out_pg != nullptr) {
- R_TRY(this->MakePageGroup(*out_pg, addr, num_pages));
- }
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Decide on new perm and attr.
- new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
- KMemoryAttribute new_attr = static_cast<KMemoryAttribute>(old_attr | lock_attr);
-
- // Update permission, if we need to.
- if (new_perm != old_perm) {
- R_TRY(Operate(addr, num_pages, new_perm, OperationType::ChangePermissions));
- }
-
- // Apply the memory block updates.
- m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm,
- new_attr, KMemoryBlockDisableMergeAttribute::Locked,
- KMemoryBlockDisableMergeAttribute::None);
-
- // If we have an output page group, open.
- if (out_pg) {
- out_pg->Open();
- }
-
- R_SUCCEED();
-}
-
-Result KPageTable::UnlockMemory(KProcessAddress addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr, KMemoryPermission new_perm,
- KMemoryAttribute lock_attr, const KPageGroup* pg) {
- // Validate basic preconditions.
- ASSERT((attr_mask & lock_attr) == lock_attr);
- ASSERT((attr & lock_attr) == lock_attr);
-
- // Validate the unlock request.
- const size_t num_pages = size / PageSize;
- R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory);
-
- // Lock the table.
- KScopedLightLock lk(m_general_lock);
-
- // Check the state.
- KMemoryState old_state{};
- KMemoryPermission old_perm{};
- KMemoryAttribute old_attr{};
- size_t num_allocator_blocks{};
- R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
- std::addressof(old_attr), std::addressof(num_allocator_blocks),
- addr, size, state_mask | KMemoryState::FlagReferenceCounted,
- state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
- attr_mask, attr));
-
- // Check the page group.
- if (pg != nullptr) {
- R_UNLESS(this->IsValidPageGroup(*pg, addr, num_pages), ResultInvalidMemoryRegion);
- }
-
- // Decide on new perm and attr.
- new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
- KMemoryAttribute new_attr = static_cast<KMemoryAttribute>(old_attr & ~lock_attr);
-
- // Create an update allocator.
- Result allocator_result{ResultSuccess};
- KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
- m_memory_block_slab_manager, num_allocator_blocks);
- R_TRY(allocator_result);
-
- // Update permission, if we need to.
- if (new_perm != old_perm) {
- R_TRY(Operate(addr, num_pages, new_perm, OperationType::ChangePermissions));
- }
-
- // Apply the memory block updates.
- m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm,
- new_attr, KMemoryBlockDisableMergeAttribute::None,
- KMemoryBlockDisableMergeAttribute::Locked);
-
- R_SUCCEED();
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index 3d64b6fb0..5541bc13f 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -3,548 +3,14 @@
#pragma once
-#include <memory>
-
-#include "common/common_funcs.h"
-#include "common/page_table.h"
-#include "core/file_sys/program_metadata.h"
-#include "core/hle/kernel/k_dynamic_resource_manager.h"
-#include "core/hle/kernel/k_light_lock.h"
-#include "core/hle/kernel/k_memory_block.h"
-#include "core/hle/kernel/k_memory_block_manager.h"
-#include "core/hle/kernel/k_memory_layout.h"
-#include "core/hle/kernel/k_memory_manager.h"
-#include "core/hle/kernel/k_typed_address.h"
-#include "core/hle/result.h"
-#include "core/memory.h"
-
-namespace Core {
-class System;
-}
+#include "core/hle/kernel/k_page_table_base.h"
namespace Kernel {
-enum class DisableMergeAttribute : u8 {
- None = (0U << 0),
- DisableHead = (1U << 0),
- DisableHeadAndBody = (1U << 1),
- EnableHeadAndBody = (1U << 2),
- DisableTail = (1U << 3),
- EnableTail = (1U << 4),
- EnableAndMergeHeadBodyTail = (1U << 5),
- EnableHeadBodyTail = EnableHeadAndBody | EnableTail,
- DisableHeadBodyTail = DisableHeadAndBody | DisableTail,
-};
-
-struct KPageProperties {
- KMemoryPermission perm;
- bool io;
- bool uncached;
- DisableMergeAttribute disable_merge_attributes;
-};
-static_assert(std::is_trivial_v<KPageProperties>);
-static_assert(sizeof(KPageProperties) == sizeof(u32));
-
-class KBlockInfoManager;
-class KMemoryBlockManager;
-class KResourceLimit;
-class KSystemResource;
-
-class KPageTable final {
-protected:
- struct PageLinkedList;
-
-public:
- enum class ICacheInvalidationStrategy : u32 { InvalidateRange, InvalidateAll };
-
- YUZU_NON_COPYABLE(KPageTable);
- YUZU_NON_MOVEABLE(KPageTable);
-
- explicit KPageTable(Core::System& system_);
- ~KPageTable();
-
- Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
- bool enable_das_merge, bool from_back, KMemoryManager::Pool pool,
- KProcessAddress code_addr, size_t code_size,
- KSystemResource* system_resource, KResourceLimit* resource_limit,
- Core::Memory::Memory& memory);
-
- void Finalize();
-
- Result MapProcessCode(KProcessAddress addr, size_t pages_count, KMemoryState state,
- KMemoryPermission perm);
- Result MapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size);
- Result UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size,
- ICacheInvalidationStrategy icache_invalidation_strategy);
- Result UnmapProcessMemory(KProcessAddress dst_addr, size_t size, KPageTable& src_page_table,
- KProcessAddress src_addr);
- Result MapPhysicalMemory(KProcessAddress addr, size_t size);
- Result UnmapPhysicalMemory(KProcessAddress addr, size_t size);
- Result MapMemory(KProcessAddress dst_addr, KProcessAddress src_addr, size_t size);
- Result UnmapMemory(KProcessAddress dst_addr, KProcessAddress src_addr, size_t size);
- Result SetProcessMemoryPermission(KProcessAddress addr, size_t size,
- Svc::MemoryPermission svc_perm);
- KMemoryInfo QueryInfo(KProcessAddress addr);
- Result SetMemoryPermission(KProcessAddress addr, size_t size, Svc::MemoryPermission perm);
- Result SetMemoryAttribute(KProcessAddress addr, size_t size, u32 mask, u32 attr);
- Result SetMaxHeapSize(size_t size);
- Result SetHeapSize(u64* out, size_t size);
- Result LockForMapDeviceAddressSpace(bool* out_is_io, KProcessAddress address, size_t size,
- KMemoryPermission perm, bool is_aligned, bool check_heap);
- Result LockForUnmapDeviceAddressSpace(KProcessAddress address, size_t size, bool check_heap);
-
- Result UnlockForDeviceAddressSpace(KProcessAddress addr, size_t size);
-
- Result LockForIpcUserBuffer(KPhysicalAddress* out, KProcessAddress address, size_t size);
- Result UnlockForIpcUserBuffer(KProcessAddress address, size_t size);
-
- Result SetupForIpc(KProcessAddress* out_dst_addr, size_t size, KProcessAddress src_addr,
- KPageTable& src_page_table, KMemoryPermission test_perm,
- KMemoryState dst_state, bool send);
- 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,
- KMemoryState state_mask, KMemoryState state,
- KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr);
-
- Common::PageTable& PageTableImpl() {
- return *m_page_table_impl;
- }
-
- const Common::PageTable& PageTableImpl() const {
- return *m_page_table_impl;
- }
-
- KBlockInfoManager* GetBlockInfoManager() {
- return m_block_info_manager;
- }
-
- Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
- KPhysicalAddress phys_addr, KProcessAddress region_start,
- size_t region_num_pages, KMemoryState state, KMemoryPermission perm) {
- R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, region_start,
- region_num_pages, state, perm));
- }
-
- Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
- KPhysicalAddress phys_addr, KMemoryState state, KMemoryPermission perm) {
- R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true,
- this->GetRegionAddress(state),
- this->GetRegionSize(state) / PageSize, state, perm));
- }
-
- Result MapPages(KProcessAddress* out_addr, size_t num_pages, KMemoryState state,
- KMemoryPermission perm) {
- R_RETURN(this->MapPages(out_addr, num_pages, PageSize, 0, false,
- this->GetRegionAddress(state),
- this->GetRegionSize(state) / PageSize, state, perm));
- }
-
- Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state,
- KMemoryPermission perm);
- Result UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state);
-
- Result MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg,
- KProcessAddress region_start, size_t region_num_pages, KMemoryState state,
- KMemoryPermission perm);
- Result MapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state,
- KMemoryPermission perm);
- Result UnmapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state);
- void RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size,
- const KPageGroup& pg);
-
- KProcessAddress GetRegionAddress(Svc::MemoryState state) const;
- size_t GetRegionSize(Svc::MemoryState state) const;
- bool CanContain(KProcessAddress addr, size_t size, Svc::MemoryState state) const;
-
- KProcessAddress GetRegionAddress(KMemoryState state) const {
- return this->GetRegionAddress(static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
- }
- size_t GetRegionSize(KMemoryState state) const {
- return this->GetRegionSize(static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
- }
- bool CanContain(KProcessAddress addr, size_t size, KMemoryState state) const {
- return this->CanContain(addr, size,
- static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
- }
-
-protected:
- struct PageLinkedList {
- private:
- struct Node {
- Node* m_next;
- std::array<u8, PageSize - sizeof(Node*)> m_buffer;
- };
-
- public:
- constexpr PageLinkedList() = default;
-
- void Push(Node* n) {
- ASSERT(Common::IsAligned(reinterpret_cast<uintptr_t>(n), PageSize));
- n->m_next = m_root;
- m_root = n;
- }
-
- void Push(Core::Memory::Memory& memory, KVirtualAddress addr) {
- this->Push(memory.GetPointer<Node>(GetInteger(addr)));
- }
-
- Node* Peek() const {
- return m_root;
- }
-
- Node* Pop() {
- Node* const r = m_root;
-
- m_root = r->m_next;
- r->m_next = nullptr;
-
- return r;
- }
-
- private:
- Node* m_root{};
- };
- static_assert(std::is_trivially_destructible<PageLinkedList>::value);
-
-private:
- enum class OperationType : u32 {
- Map = 0,
- MapGroup = 1,
- MapFirstGroup = 2,
- Unmap = 3,
- ChangePermissions = 4,
- ChangePermissionsAndRefresh = 5,
- ChangePermissionsAndRefreshAndFlush = 6,
- Separate = 7,
- };
-
- static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr =
- KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared;
-
- Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
- KPhysicalAddress phys_addr, bool is_pa_valid, KProcessAddress region_start,
- size_t region_num_pages, KMemoryState state, KMemoryPermission perm);
- bool IsRegionContiguous(KProcessAddress addr, u64 size) const;
- void AddRegionToPages(KProcessAddress start, size_t num_pages, KPageGroup& page_linked_list);
- KMemoryInfo QueryInfoImpl(KProcessAddress addr);
- KProcessAddress AllocateVirtualMemory(KProcessAddress start, size_t region_num_pages,
- u64 needed_num_pages, size_t align);
- Result Operate(KProcessAddress addr, size_t num_pages, const KPageGroup& page_group,
- OperationType operation);
- Result Operate(KProcessAddress addr, size_t num_pages, KMemoryPermission perm,
- OperationType operation, KPhysicalAddress map_addr = 0);
- void FinalizeUpdate(PageLinkedList* page_list);
-
- KProcessAddress FindFreeArea(KProcessAddress region_start, size_t region_num_pages,
- size_t num_pages, size_t alignment, size_t offset,
- size_t guard_pages);
-
- Result CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr, size_t size,
- KMemoryState state_mask, KMemoryState state,
- KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
- Result CheckMemoryStateContiguous(KProcessAddress addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr) const {
- R_RETURN(this->CheckMemoryStateContiguous(nullptr, addr, size, state_mask, state, perm_mask,
- perm, attr_mask, attr));
- }
-
- Result CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask, KMemoryState state,
- KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
- Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
- KMemoryAttribute* out_attr, size_t* out_blocks_needed,
- KMemoryBlockManager::const_iterator it, KProcessAddress last_addr,
- KMemoryState state_mask, KMemoryState state,
- KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr,
- KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const;
- Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
- KMemoryAttribute* out_attr, size_t* out_blocks_needed,
- KProcessAddress addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr,
- KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const;
- Result CheckMemoryState(size_t* out_blocks_needed, KProcessAddress addr, size_t size,
- KMemoryState state_mask, KMemoryState state,
- KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr,
- KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const {
- R_RETURN(CheckMemoryState(nullptr, nullptr, nullptr, out_blocks_needed, addr, size,
- state_mask, state, perm_mask, perm, attr_mask, attr,
- ignore_attr));
- }
- Result CheckMemoryState(KProcessAddress addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr,
- KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const {
- R_RETURN(this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm,
- attr_mask, attr, ignore_attr));
- }
-
- Result LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_KPhysicalAddress,
- KProcessAddress addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask,
- KMemoryPermission perm, KMemoryAttribute attr_mask,
- KMemoryAttribute attr, KMemoryPermission new_perm,
- KMemoryAttribute lock_attr);
- Result UnlockMemory(KProcessAddress addr, size_t size, KMemoryState state_mask,
- KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
- KMemoryAttribute attr_mask, KMemoryAttribute attr,
- KMemoryPermission new_perm, KMemoryAttribute lock_attr,
- const KPageGroup* pg);
-
- Result MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_t num_pages);
- bool IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr, size_t num_pages);
-
- bool IsLockedByCurrentThread() const {
- return m_general_lock.IsLockedByCurrentThread();
- }
-
- bool IsHeapPhysicalAddress(const KMemoryLayout& layout, KPhysicalAddress phys_addr) {
- ASSERT(this->IsLockedByCurrentThread());
-
- return layout.IsHeapPhysicalAddress(m_cached_physical_heap_region, phys_addr);
- }
-
- bool GetPhysicalAddressLocked(KPhysicalAddress* out, KProcessAddress virt_addr) const {
- ASSERT(this->IsLockedByCurrentThread());
-
- *out = GetPhysicalAddr(virt_addr);
-
- return *out != 0;
- }
-
- Result SetupForIpcClient(PageLinkedList* page_list, size_t* out_blocks_needed,
- KProcessAddress address, size_t size, KMemoryPermission test_perm,
- KMemoryState dst_state);
- Result SetupForIpcServer(KProcessAddress* out_addr, size_t size, KProcessAddress src_addr,
- KMemoryPermission test_perm, KMemoryState dst_state,
- KPageTable& src_page_table, bool send);
- void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, KProcessAddress address,
- size_t size, KMemoryPermission prot_perm);
-
- Result AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address,
- size_t num_pages, KMemoryPermission perm);
- Result MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address,
- const KPageGroup& pg, const KPageProperties properties, bool reuse_ll);
-
- mutable KLightLock m_general_lock;
- mutable KLightLock m_map_physical_memory_lock;
-
-public:
- constexpr KProcessAddress GetAddressSpaceStart() const {
- return m_address_space_start;
- }
- constexpr KProcessAddress GetAddressSpaceEnd() const {
- return m_address_space_end;
- }
- constexpr size_t GetAddressSpaceSize() const {
- return m_address_space_end - m_address_space_start;
- }
- constexpr KProcessAddress GetHeapRegionStart() const {
- return m_heap_region_start;
- }
- constexpr KProcessAddress GetHeapRegionEnd() const {
- return m_heap_region_end;
- }
- constexpr size_t GetHeapRegionSize() const {
- return m_heap_region_end - m_heap_region_start;
- }
- constexpr KProcessAddress GetAliasRegionStart() const {
- return m_alias_region_start;
- }
- constexpr KProcessAddress GetAliasRegionEnd() const {
- return m_alias_region_end;
- }
- constexpr size_t GetAliasRegionSize() const {
- return m_alias_region_end - m_alias_region_start;
- }
- constexpr KProcessAddress GetStackRegionStart() const {
- return m_stack_region_start;
- }
- constexpr KProcessAddress GetStackRegionEnd() const {
- return m_stack_region_end;
- }
- constexpr size_t GetStackRegionSize() const {
- return m_stack_region_end - m_stack_region_start;
- }
- constexpr KProcessAddress GetKernelMapRegionStart() const {
- return m_kernel_map_region_start;
- }
- constexpr KProcessAddress GetKernelMapRegionEnd() const {
- return m_kernel_map_region_end;
- }
- constexpr KProcessAddress GetCodeRegionStart() const {
- return m_code_region_start;
- }
- constexpr KProcessAddress GetCodeRegionEnd() const {
- return m_code_region_end;
- }
- constexpr KProcessAddress GetAliasCodeRegionStart() const {
- return m_alias_code_region_start;
- }
- constexpr KProcessAddress GetAliasCodeRegionEnd() const {
- return m_alias_code_region_end;
- }
- constexpr size_t GetAliasCodeRegionSize() const {
- return m_alias_code_region_end - m_alias_code_region_start;
- }
- size_t GetNormalMemorySize() {
- KScopedLightLock lk(m_general_lock);
- return GetHeapSize() + m_mapped_physical_memory_size;
- }
- constexpr size_t GetAddressSpaceWidth() const {
- return m_address_space_width;
- }
- constexpr size_t GetHeapSize() const {
- return m_current_heap_end - m_heap_region_start;
- }
- constexpr size_t GetNumGuardPages() const {
- return IsKernel() ? 1 : 4;
- }
- KPhysicalAddress GetPhysicalAddr(KProcessAddress addr) const {
- const auto backing_addr = m_page_table_impl->backing_addr[addr >> PageBits];
- ASSERT(backing_addr);
- return backing_addr + GetInteger(addr);
- }
- constexpr bool Contains(KProcessAddress addr) const {
- return m_address_space_start <= addr && addr <= m_address_space_end - 1;
- }
- constexpr bool Contains(KProcessAddress addr, size_t size) const {
- return m_address_space_start <= addr && addr < addr + size &&
- addr + size - 1 <= m_address_space_end - 1;
- }
- constexpr bool IsInAliasRegion(KProcessAddress addr, size_t size) const {
- return this->Contains(addr, size) && m_alias_region_start <= addr &&
- addr + size - 1 <= m_alias_region_end - 1;
- }
- constexpr bool IsInHeapRegion(KProcessAddress addr, size_t size) const {
- return this->Contains(addr, size) && m_heap_region_start <= addr &&
- addr + size - 1 <= m_heap_region_end - 1;
- }
-
+class KPageTable final : public KPageTableBase {
public:
- static KVirtualAddress GetLinearMappedVirtualAddress(const KMemoryLayout& layout,
- KPhysicalAddress addr) {
- return layout.GetLinearVirtualAddress(addr);
- }
-
- static KPhysicalAddress GetLinearMappedPhysicalAddress(const KMemoryLayout& layout,
- KVirtualAddress addr) {
- return layout.GetLinearPhysicalAddress(addr);
- }
-
- static KVirtualAddress GetHeapVirtualAddress(const KMemoryLayout& layout,
- KPhysicalAddress addr) {
- return GetLinearMappedVirtualAddress(layout, addr);
- }
-
- static KPhysicalAddress GetHeapPhysicalAddress(const KMemoryLayout& layout,
- KVirtualAddress addr) {
- return GetLinearMappedPhysicalAddress(layout, addr);
- }
-
- static KVirtualAddress GetPageTableVirtualAddress(const KMemoryLayout& layout,
- KPhysicalAddress addr) {
- return GetLinearMappedVirtualAddress(layout, addr);
- }
-
- static KPhysicalAddress GetPageTablePhysicalAddress(const KMemoryLayout& layout,
- KVirtualAddress addr) {
- return GetLinearMappedPhysicalAddress(layout, addr);
- }
-
-private:
- constexpr bool IsKernel() const {
- return m_is_kernel;
- }
- constexpr bool IsAslrEnabled() const {
- return m_enable_aslr;
- }
-
- constexpr bool ContainsPages(KProcessAddress addr, size_t num_pages) const {
- return (m_address_space_start <= addr) &&
- (num_pages <= (m_address_space_end - m_address_space_start) / PageSize) &&
- (addr + num_pages * PageSize - 1 <= m_address_space_end - 1);
- }
-
-private:
- class KScopedPageTableUpdater {
- private:
- KPageTable* m_pt{};
- PageLinkedList m_ll;
-
- public:
- explicit KScopedPageTableUpdater(KPageTable* pt) : m_pt(pt) {}
- explicit KScopedPageTableUpdater(KPageTable& pt) : KScopedPageTableUpdater(&pt) {}
- ~KScopedPageTableUpdater() {
- m_pt->FinalizeUpdate(this->GetPageList());
- }
-
- PageLinkedList* GetPageList() {
- return std::addressof(m_ll);
- }
- };
-
-private:
- KProcessAddress m_address_space_start{};
- KProcessAddress m_address_space_end{};
- KProcessAddress m_heap_region_start{};
- KProcessAddress m_heap_region_end{};
- KProcessAddress m_current_heap_end{};
- KProcessAddress m_alias_region_start{};
- KProcessAddress m_alias_region_end{};
- KProcessAddress m_stack_region_start{};
- KProcessAddress m_stack_region_end{};
- KProcessAddress m_kernel_map_region_start{};
- KProcessAddress m_kernel_map_region_end{};
- KProcessAddress m_code_region_start{};
- KProcessAddress m_code_region_end{};
- KProcessAddress m_alias_code_region_start{};
- KProcessAddress m_alias_code_region_end{};
-
- size_t m_max_heap_size{};
- size_t m_mapped_physical_memory_size{};
- size_t m_mapped_unsafe_physical_memory{};
- size_t m_mapped_insecure_memory{};
- size_t m_mapped_ipc_server_memory{};
- size_t m_address_space_width{};
-
- KMemoryBlockManager m_memory_block_manager;
- u32 m_allocate_option{};
-
- bool m_is_kernel{};
- bool m_enable_aslr{};
- bool m_enable_device_address_space_merge{};
-
- KMemoryBlockSlabManager* m_memory_block_slab_manager{};
- KBlockInfoManager* m_block_info_manager{};
- KResourceLimit* m_resource_limit{};
-
- u32 m_heap_fill_value{};
- u32 m_ipc_fill_value{};
- u32 m_stack_fill_value{};
- const KMemoryRegion* m_cached_physical_heap_region{};
-
- KMemoryManager::Pool m_memory_pool{KMemoryManager::Pool::Application};
- KMemoryManager::Direction m_allocation_option{KMemoryManager::Direction::FromFront};
-
- std::unique_ptr<Common::PageTable> m_page_table_impl;
-
- Core::System& m_system;
- KernelCore& m_kernel;
- Core::Memory::Memory* m_memory{};
+ explicit KPageTable(KernelCore& kernel) : KPageTableBase(kernel) {}
+ ~KPageTable() = default;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp
new file mode 100644
index 000000000..6691586ed
--- /dev/null
+++ b/src/core/hle/kernel/k_page_table_base.cpp
@@ -0,0 +1,5739 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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_page_table_base.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
+#include "core/hle/kernel/k_system_resource.h"
+
+namespace Kernel {
+
+namespace {
+
+class KScopedLightLockPair {
+ YUZU_NON_COPYABLE(KScopedLightLockPair);
+ YUZU_NON_MOVEABLE(KScopedLightLockPair);
+
+private:
+ KLightLock* m_lower;
+ KLightLock* m_upper;
+
+public:
+ KScopedLightLockPair(KLightLock& lhs, KLightLock& rhs) {
+ // Ensure our locks are in a consistent order.
+ if (std::addressof(lhs) <= std::addressof(rhs)) {
+ m_lower = std::addressof(lhs);
+ m_upper = std::addressof(rhs);
+ } else {
+ m_lower = std::addressof(rhs);
+ m_upper = std::addressof(lhs);
+ }
+
+ // Acquire both locks.
+ m_lower->Lock();
+ if (m_lower != m_upper) {
+ m_upper->Lock();
+ }
+ }
+
+ ~KScopedLightLockPair() {
+ // Unlock the upper lock.
+ if (m_upper != nullptr && m_upper != m_lower) {
+ m_upper->Unlock();
+ }
+
+ // Unlock the lower lock.
+ if (m_lower != nullptr) {
+ m_lower->Unlock();
+ }
+ }
+
+public:
+ // Utility.
+ void TryUnlockHalf(KLightLock& lock) {
+ // Only allow unlocking if the lock is half the pair.
+ if (m_lower != m_upper) {
+ // We want to be sure the lock is one we own.
+ if (m_lower == std::addressof(lock)) {
+ lock.Unlock();
+ m_lower = nullptr;
+ } else if (m_upper == std::addressof(lock)) {
+ lock.Unlock();
+ m_upper = nullptr;
+ }
+ }
+ }
+};
+
+template <typename AddressType>
+void InvalidateInstructionCache(Core::System& system, AddressType addr, u64 size) {
+ system.InvalidateCpuInstructionCacheRange(GetInteger(addr), size);
+}
+
+template <typename AddressType>
+Result InvalidateDataCache(AddressType addr, u64 size) {
+ R_SUCCEED();
+}
+
+template <typename AddressType>
+Result StoreDataCache(AddressType addr, u64 size) {
+ R_SUCCEED();
+}
+
+template <typename AddressType>
+Result FlushDataCache(AddressType addr, u64 size) {
+ R_SUCCEED();
+}
+
+constexpr Common::MemoryPermission ConvertToMemoryPermission(KMemoryPermission perm) {
+ Common::MemoryPermission perms{};
+ if (True(perm & KMemoryPermission::UserRead)) {
+ perms |= Common::MemoryPermission::Read;
+ }
+ if (True(perm & KMemoryPermission::UserWrite)) {
+ perms |= Common::MemoryPermission::Write;
+ }
+#ifdef HAS_NCE
+ if (True(perm & KMemoryPermission::UserExecute)) {
+ perms |= Common::MemoryPermission::Execute;
+ }
+#endif
+ return perms;
+}
+
+} // namespace
+
+void KPageTableBase::MemoryRange::Open() {
+ // If the range contains heap pages, open them.
+ if (this->IsHeap()) {
+ m_kernel.MemoryManager().Open(this->GetAddress(), this->GetSize() / PageSize);
+ }
+}
+
+void KPageTableBase::MemoryRange::Close() {
+ // If the range contains heap pages, close them.
+ if (this->IsHeap()) {
+ m_kernel.MemoryManager().Close(this->GetAddress(), this->GetSize() / PageSize);
+ }
+}
+
+KPageTableBase::KPageTableBase(KernelCore& kernel)
+ : m_kernel(kernel), m_system(kernel.System()), m_general_lock(kernel),
+ m_map_physical_memory_lock(kernel), m_device_map_lock(kernel) {}
+KPageTableBase::~KPageTableBase() = default;
+
+Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start,
+ KVirtualAddress end, Core::Memory::Memory& memory) {
+ // Initialize our members.
+ m_address_space_width =
+ static_cast<u32>(is_64_bit ? Common::BitSize<u64>() : Common::BitSize<u32>());
+ m_address_space_start = KProcessAddress(GetInteger(start));
+ m_address_space_end = KProcessAddress(GetInteger(end));
+ m_is_kernel = true;
+ m_enable_aslr = true;
+ m_enable_device_address_space_merge = false;
+
+ m_heap_region_start = 0;
+ m_heap_region_end = 0;
+ m_current_heap_end = 0;
+ m_alias_region_start = 0;
+ m_alias_region_end = 0;
+ m_stack_region_start = 0;
+ m_stack_region_end = 0;
+ m_kernel_map_region_start = 0;
+ m_kernel_map_region_end = 0;
+ m_alias_code_region_start = 0;
+ m_alias_code_region_end = 0;
+ m_code_region_start = 0;
+ m_code_region_end = 0;
+ m_max_heap_size = 0;
+ m_mapped_physical_memory_size = 0;
+ m_mapped_unsafe_physical_memory = 0;
+ m_mapped_insecure_memory = 0;
+ m_mapped_ipc_server_memory = 0;
+
+ m_memory_block_slab_manager =
+ m_kernel.GetSystemSystemResource().GetMemoryBlockSlabManagerPointer();
+ m_block_info_manager = m_kernel.GetSystemSystemResource().GetBlockInfoManagerPointer();
+ m_resource_limit = m_kernel.GetSystemResourceLimit();
+
+ m_allocate_option = KMemoryManager::EncodeOption(KMemoryManager::Pool::System,
+ KMemoryManager::Direction::FromFront);
+ m_heap_fill_value = MemoryFillValue_Zero;
+ m_ipc_fill_value = MemoryFillValue_Zero;
+ m_stack_fill_value = MemoryFillValue_Zero;
+
+ m_cached_physical_linear_region = nullptr;
+ m_cached_physical_heap_region = nullptr;
+
+ // Initialize our implementation.
+ m_impl = std::make_unique<Common::PageTable>();
+ m_impl->Resize(m_address_space_width, PageBits);
+
+ // Set the tracking memory.
+ m_memory = std::addressof(memory);
+
+ // Initialize our memory block manager.
+ R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end,
+ m_memory_block_slab_manager));
+}
+
+Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr,
+ bool enable_das_merge, bool from_back,
+ KMemoryManager::Pool pool, KProcessAddress code_address,
+ size_t code_size, KSystemResource* system_resource,
+ KResourceLimit* resource_limit,
+ Core::Memory::Memory& memory,
+ KProcessAddress aslr_space_start) {
+ // Calculate region extents.
+ const size_t as_width = GetAddressSpaceWidth(as_type);
+ const KProcessAddress start = 0;
+ const KProcessAddress end = (1ULL << as_width);
+
+ // Validate the region.
+ ASSERT(start <= code_address);
+ ASSERT(code_address < code_address + code_size);
+ ASSERT(code_address + code_size - 1 <= end - 1);
+
+ // Define helpers.
+ auto GetSpaceStart = [&](KAddressSpaceInfo::Type type) {
+ return KAddressSpaceInfo::GetAddressSpaceStart(m_address_space_width, type);
+ };
+ auto GetSpaceSize = [&](KAddressSpaceInfo::Type type) {
+ return KAddressSpaceInfo::GetAddressSpaceSize(m_address_space_width, type);
+ };
+
+ // Set our bit width and heap/alias sizes.
+ m_address_space_width = static_cast<u32>(GetAddressSpaceWidth(as_type));
+ size_t alias_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Alias);
+ size_t heap_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Heap);
+
+ // Adjust heap/alias size if we don't have an alias region.
+ if ((as_type & Svc::CreateProcessFlag::AddressSpaceMask) ==
+ Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias) {
+ heap_region_size += alias_region_size;
+ alias_region_size = 0;
+ }
+
+ // Set code regions and determine remaining sizes.
+ KProcessAddress process_code_start;
+ KProcessAddress process_code_end;
+ size_t stack_region_size;
+ size_t kernel_map_region_size;
+ if (m_address_space_width == 39) {
+ alias_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Alias);
+ heap_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Heap);
+ stack_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Stack);
+ kernel_map_region_size = GetSpaceSize(KAddressSpaceInfo::Type::MapSmall);
+ m_code_region_start = m_address_space_start + aslr_space_start +
+ GetSpaceStart(KAddressSpaceInfo::Type::Map39Bit);
+ m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::Map39Bit);
+ m_alias_code_region_start = m_code_region_start;
+ m_alias_code_region_end = m_code_region_end;
+ process_code_start = Common::AlignDown(GetInteger(code_address), RegionAlignment);
+ process_code_end = Common::AlignUp(GetInteger(code_address) + code_size, RegionAlignment);
+ } else {
+ stack_region_size = 0;
+ kernel_map_region_size = 0;
+ m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::MapSmall);
+ m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::MapSmall);
+ m_stack_region_start = m_code_region_start;
+ m_alias_code_region_start = m_code_region_start;
+ m_alias_code_region_end = GetSpaceStart(KAddressSpaceInfo::Type::MapLarge) +
+ GetSpaceSize(KAddressSpaceInfo::Type::MapLarge);
+ m_stack_region_end = m_code_region_end;
+ m_kernel_map_region_start = m_code_region_start;
+ m_kernel_map_region_end = m_code_region_end;
+ process_code_start = m_code_region_start;
+ process_code_end = m_code_region_end;
+ }
+
+ // Set other basic fields.
+ m_enable_aslr = enable_aslr;
+ m_enable_device_address_space_merge = enable_das_merge;
+ m_address_space_start = start;
+ m_address_space_end = end;
+ m_is_kernel = false;
+ m_memory_block_slab_manager = system_resource->GetMemoryBlockSlabManagerPointer();
+ m_block_info_manager = system_resource->GetBlockInfoManagerPointer();
+ m_resource_limit = resource_limit;
+
+ // Determine the region we can place our undetermineds in.
+ KProcessAddress alloc_start;
+ size_t alloc_size;
+ if ((GetInteger(process_code_start) - GetInteger(m_code_region_start)) >=
+ (GetInteger(end) - GetInteger(process_code_end))) {
+ alloc_start = m_code_region_start;
+ alloc_size = GetInteger(process_code_start) - GetInteger(m_code_region_start);
+ } else {
+ alloc_start = process_code_end;
+ alloc_size = GetInteger(end) - GetInteger(process_code_end);
+ }
+ const size_t needed_size =
+ (alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size);
+ R_UNLESS(alloc_size >= needed_size, ResultOutOfMemory);
+
+ const size_t remaining_size = alloc_size - needed_size;
+
+ // Determine random placements for each region.
+ size_t alias_rnd = 0, heap_rnd = 0, stack_rnd = 0, kmap_rnd = 0;
+ if (enable_aslr) {
+ alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
+ RegionAlignment;
+ heap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
+ RegionAlignment;
+ stack_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
+ RegionAlignment;
+ kmap_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) *
+ RegionAlignment;
+ }
+
+ // Setup heap and alias regions.
+ m_alias_region_start = alloc_start + alias_rnd;
+ m_alias_region_end = m_alias_region_start + alias_region_size;
+ m_heap_region_start = alloc_start + heap_rnd;
+ m_heap_region_end = m_heap_region_start + heap_region_size;
+
+ if (alias_rnd <= heap_rnd) {
+ m_heap_region_start += alias_region_size;
+ m_heap_region_end += alias_region_size;
+ } else {
+ m_alias_region_start += heap_region_size;
+ m_alias_region_end += heap_region_size;
+ }
+
+ // Setup stack region.
+ if (stack_region_size) {
+ m_stack_region_start = alloc_start + stack_rnd;
+ m_stack_region_end = m_stack_region_start + stack_region_size;
+
+ if (alias_rnd < stack_rnd) {
+ m_stack_region_start += alias_region_size;
+ m_stack_region_end += alias_region_size;
+ } else {
+ m_alias_region_start += stack_region_size;
+ m_alias_region_end += stack_region_size;
+ }
+
+ if (heap_rnd < stack_rnd) {
+ m_stack_region_start += heap_region_size;
+ m_stack_region_end += heap_region_size;
+ } else {
+ m_heap_region_start += stack_region_size;
+ m_heap_region_end += stack_region_size;
+ }
+ }
+
+ // Setup kernel map region.
+ if (kernel_map_region_size) {
+ m_kernel_map_region_start = alloc_start + kmap_rnd;
+ m_kernel_map_region_end = m_kernel_map_region_start + kernel_map_region_size;
+
+ if (alias_rnd < kmap_rnd) {
+ m_kernel_map_region_start += alias_region_size;
+ m_kernel_map_region_end += alias_region_size;
+ } else {
+ m_alias_region_start += kernel_map_region_size;
+ m_alias_region_end += kernel_map_region_size;
+ }
+
+ if (heap_rnd < kmap_rnd) {
+ m_kernel_map_region_start += heap_region_size;
+ m_kernel_map_region_end += heap_region_size;
+ } else {
+ m_heap_region_start += kernel_map_region_size;
+ m_heap_region_end += kernel_map_region_size;
+ }
+
+ if (stack_region_size) {
+ if (stack_rnd < kmap_rnd) {
+ m_kernel_map_region_start += stack_region_size;
+ m_kernel_map_region_end += stack_region_size;
+ } else {
+ m_stack_region_start += kernel_map_region_size;
+ m_stack_region_end += kernel_map_region_size;
+ }
+ }
+ }
+
+ // Set heap and fill members.
+ m_current_heap_end = m_heap_region_start;
+ m_max_heap_size = 0;
+ m_mapped_physical_memory_size = 0;
+ m_mapped_unsafe_physical_memory = 0;
+ m_mapped_insecure_memory = 0;
+ m_mapped_ipc_server_memory = 0;
+
+ // const bool fill_memory = KTargetSystem::IsDebugMemoryFillEnabled();
+ const bool fill_memory = false;
+ m_heap_fill_value = fill_memory ? MemoryFillValue_Heap : MemoryFillValue_Zero;
+ m_ipc_fill_value = fill_memory ? MemoryFillValue_Ipc : MemoryFillValue_Zero;
+ m_stack_fill_value = fill_memory ? MemoryFillValue_Stack : MemoryFillValue_Zero;
+
+ // Set allocation option.
+ m_allocate_option =
+ KMemoryManager::EncodeOption(pool, from_back ? KMemoryManager::Direction::FromBack
+ : KMemoryManager::Direction::FromFront);
+
+ // Ensure that we regions inside our address space.
+ auto IsInAddressSpace = [&](KProcessAddress addr) {
+ return m_address_space_start <= addr && addr <= m_address_space_end;
+ };
+ ASSERT(IsInAddressSpace(m_alias_region_start));
+ ASSERT(IsInAddressSpace(m_alias_region_end));
+ ASSERT(IsInAddressSpace(m_heap_region_start));
+ ASSERT(IsInAddressSpace(m_heap_region_end));
+ ASSERT(IsInAddressSpace(m_stack_region_start));
+ ASSERT(IsInAddressSpace(m_stack_region_end));
+ ASSERT(IsInAddressSpace(m_kernel_map_region_start));
+ ASSERT(IsInAddressSpace(m_kernel_map_region_end));
+
+ // Ensure that we selected regions that don't overlap.
+ const KProcessAddress alias_start = m_alias_region_start;
+ const KProcessAddress alias_last = m_alias_region_end - 1;
+ const KProcessAddress heap_start = m_heap_region_start;
+ const KProcessAddress heap_last = m_heap_region_end - 1;
+ const KProcessAddress stack_start = m_stack_region_start;
+ const KProcessAddress stack_last = m_stack_region_end - 1;
+ const KProcessAddress kmap_start = m_kernel_map_region_start;
+ const KProcessAddress kmap_last = m_kernel_map_region_end - 1;
+ ASSERT(alias_last < heap_start || heap_last < alias_start);
+ ASSERT(alias_last < stack_start || stack_last < alias_start);
+ ASSERT(alias_last < kmap_start || kmap_last < alias_start);
+ ASSERT(heap_last < stack_start || stack_last < heap_start);
+ ASSERT(heap_last < kmap_start || kmap_last < heap_start);
+
+ // Initialize our implementation.
+ m_impl = std::make_unique<Common::PageTable>();
+ m_impl->Resize(m_address_space_width, PageBits);
+
+ // Set the tracking memory.
+ m_memory = std::addressof(memory);
+
+ // Initialize our memory block manager.
+ R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end,
+ m_memory_block_slab_manager));
+}
+
+void KPageTableBase::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, std::move(HostUnmapCallback));
+
+ // Free any unsafe mapped memory.
+ if (m_mapped_unsafe_physical_memory) {
+ UNIMPLEMENTED();
+ }
+
+ // Release any insecure mapped memory.
+ if (m_mapped_insecure_memory) {
+ if (auto* const insecure_resource_limit =
+ KSystemControl::GetInsecureMemoryResourceLimit(m_kernel);
+ insecure_resource_limit != nullptr) {
+ insecure_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
+ m_mapped_insecure_memory);
+ }
+ }
+
+ // Release any ipc server memory.
+ if (m_mapped_ipc_server_memory) {
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
+ m_mapped_ipc_server_memory);
+ }
+
+ // Close the backing page table, as the destructor is not called for guest objects.
+ m_impl.reset();
+}
+
+KProcessAddress KPageTableBase::GetRegionAddress(Svc::MemoryState state) const {
+ switch (state) {
+ case Svc::MemoryState::Free:
+ case Svc::MemoryState::Kernel:
+ return m_address_space_start;
+ case Svc::MemoryState::Normal:
+ return m_heap_region_start;
+ case Svc::MemoryState::Ipc:
+ case Svc::MemoryState::NonSecureIpc:
+ case Svc::MemoryState::NonDeviceIpc:
+ return m_alias_region_start;
+ case Svc::MemoryState::Stack:
+ return m_stack_region_start;
+ case Svc::MemoryState::Static:
+ case Svc::MemoryState::ThreadLocal:
+ return m_kernel_map_region_start;
+ case Svc::MemoryState::Io:
+ case Svc::MemoryState::Shared:
+ case Svc::MemoryState::AliasCode:
+ case Svc::MemoryState::AliasCodeData:
+ case Svc::MemoryState::Transfered:
+ case Svc::MemoryState::SharedTransfered:
+ case Svc::MemoryState::SharedCode:
+ case Svc::MemoryState::GeneratedCode:
+ case Svc::MemoryState::CodeOut:
+ case Svc::MemoryState::Coverage:
+ case Svc::MemoryState::Insecure:
+ return m_alias_code_region_start;
+ case Svc::MemoryState::Code:
+ case Svc::MemoryState::CodeData:
+ return m_code_region_start;
+ default:
+ UNREACHABLE();
+ }
+}
+
+size_t KPageTableBase::GetRegionSize(Svc::MemoryState state) const {
+ switch (state) {
+ case Svc::MemoryState::Free:
+ case Svc::MemoryState::Kernel:
+ return m_address_space_end - m_address_space_start;
+ case Svc::MemoryState::Normal:
+ return m_heap_region_end - m_heap_region_start;
+ case Svc::MemoryState::Ipc:
+ case Svc::MemoryState::NonSecureIpc:
+ case Svc::MemoryState::NonDeviceIpc:
+ return m_alias_region_end - m_alias_region_start;
+ case Svc::MemoryState::Stack:
+ return m_stack_region_end - m_stack_region_start;
+ case Svc::MemoryState::Static:
+ case Svc::MemoryState::ThreadLocal:
+ return m_kernel_map_region_end - m_kernel_map_region_start;
+ case Svc::MemoryState::Io:
+ case Svc::MemoryState::Shared:
+ case Svc::MemoryState::AliasCode:
+ case Svc::MemoryState::AliasCodeData:
+ case Svc::MemoryState::Transfered:
+ case Svc::MemoryState::SharedTransfered:
+ case Svc::MemoryState::SharedCode:
+ case Svc::MemoryState::GeneratedCode:
+ case Svc::MemoryState::CodeOut:
+ case Svc::MemoryState::Coverage:
+ case Svc::MemoryState::Insecure:
+ return m_alias_code_region_end - m_alias_code_region_start;
+ case Svc::MemoryState::Code:
+ case Svc::MemoryState::CodeData:
+ return m_code_region_end - m_code_region_start;
+ default:
+ UNREACHABLE();
+ }
+}
+
+bool KPageTableBase::CanContain(KProcessAddress addr, size_t size, Svc::MemoryState state) const {
+ const KProcessAddress end = addr + size;
+ const KProcessAddress last = end - 1;
+
+ const KProcessAddress region_start = this->GetRegionAddress(state);
+ const size_t region_size = this->GetRegionSize(state);
+
+ const bool is_in_region =
+ region_start <= addr && addr < end && last <= region_start + region_size - 1;
+ const bool is_in_heap = !(end <= m_heap_region_start || m_heap_region_end <= addr ||
+ m_heap_region_start == m_heap_region_end);
+ const bool is_in_alias = !(end <= m_alias_region_start || m_alias_region_end <= addr ||
+ m_alias_region_start == m_alias_region_end);
+ switch (state) {
+ case Svc::MemoryState::Free:
+ case Svc::MemoryState::Kernel:
+ return is_in_region;
+ case Svc::MemoryState::Io:
+ case Svc::MemoryState::Static:
+ case Svc::MemoryState::Code:
+ case Svc::MemoryState::CodeData:
+ case Svc::MemoryState::Shared:
+ case Svc::MemoryState::AliasCode:
+ case Svc::MemoryState::AliasCodeData:
+ case Svc::MemoryState::Stack:
+ case Svc::MemoryState::ThreadLocal:
+ case Svc::MemoryState::Transfered:
+ case Svc::MemoryState::SharedTransfered:
+ case Svc::MemoryState::SharedCode:
+ case Svc::MemoryState::GeneratedCode:
+ case Svc::MemoryState::CodeOut:
+ case Svc::MemoryState::Coverage:
+ case Svc::MemoryState::Insecure:
+ return is_in_region && !is_in_heap && !is_in_alias;
+ case Svc::MemoryState::Normal:
+ ASSERT(is_in_heap);
+ return is_in_region && !is_in_alias;
+ case Svc::MemoryState::Ipc:
+ case Svc::MemoryState::NonSecureIpc:
+ case Svc::MemoryState::NonDeviceIpc:
+ ASSERT(is_in_alias);
+ return is_in_region && !is_in_heap;
+ default:
+ return false;
+ }
+}
+
+Result KPageTableBase::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr) const {
+ // Validate the states match expectation.
+ R_UNLESS((info.m_state & state_mask) == state, ResultInvalidCurrentMemory);
+ R_UNLESS((info.m_permission & perm_mask) == perm, ResultInvalidCurrentMemory);
+ R_UNLESS((info.m_attribute & attr_mask) == attr, ResultInvalidCurrentMemory);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr,
+ size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm,
+ KMemoryAttribute attr_mask,
+ KMemoryAttribute attr) const {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ // Get information about the first block.
+ const KProcessAddress last_addr = addr + size - 1;
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr);
+ KMemoryInfo info = it->GetMemoryInfo();
+
+ // If the start address isn't aligned, we need a block.
+ const size_t blocks_for_start_align =
+ (Common::AlignDown(GetInteger(addr), PageSize) != info.GetAddress()) ? 1 : 0;
+
+ while (true) {
+ // Validate against the provided masks.
+ R_TRY(this->CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr));
+
+ // Break once we're done.
+ if (last_addr <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance our iterator.
+ it++;
+ ASSERT(it != m_memory_block_manager.cend());
+ info = it->GetMemoryInfo();
+ }
+
+ // If the end address isn't aligned, we need a block.
+ const size_t blocks_for_end_align =
+ (Common::AlignUp(GetInteger(addr) + size, PageSize) != info.GetEndAddress()) ? 1 : 0;
+
+ if (out_blocks_needed != nullptr) {
+ *out_blocks_needed = blocks_for_start_align + blocks_for_end_align;
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
+ KMemoryAttribute* out_attr, size_t* out_blocks_needed,
+ KMemoryBlockManager::const_iterator it,
+ KProcessAddress last_addr, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr, KMemoryAttribute ignore_attr) const {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ // Get information about the first block.
+ KMemoryInfo info = it->GetMemoryInfo();
+
+ // Validate all blocks in the range have correct state.
+ const KMemoryState first_state = info.m_state;
+ const KMemoryPermission first_perm = info.m_permission;
+ const KMemoryAttribute first_attr = info.m_attribute;
+ while (true) {
+ // Validate the current block.
+ R_UNLESS(info.m_state == first_state, ResultInvalidCurrentMemory);
+ R_UNLESS(info.m_permission == first_perm, ResultInvalidCurrentMemory);
+ R_UNLESS((info.m_attribute | ignore_attr) == (first_attr | ignore_attr),
+ ResultInvalidCurrentMemory);
+
+ // Validate against the provided masks.
+ R_TRY(this->CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr));
+
+ // Break once we're done.
+ if (last_addr <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance our iterator.
+ it++;
+ ASSERT(it != m_memory_block_manager.cend());
+ info = it->GetMemoryInfo();
+ }
+
+ // Write output state.
+ if (out_state != nullptr) {
+ *out_state = first_state;
+ }
+ if (out_perm != nullptr) {
+ *out_perm = first_perm;
+ }
+ if (out_attr != nullptr) {
+ *out_attr = first_attr & ~ignore_attr;
+ }
+
+ // If the end address isn't aligned, we need a block.
+ if (out_blocks_needed != nullptr) {
+ const size_t blocks_for_end_align =
+ (Common::AlignDown(GetInteger(last_addr), PageSize) + PageSize != info.GetEndAddress())
+ ? 1
+ : 0;
+ *out_blocks_needed = blocks_for_end_align;
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
+ KMemoryAttribute* out_attr, size_t* out_blocks_needed,
+ KProcessAddress addr, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr, KMemoryAttribute ignore_attr) const {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ // Check memory state.
+ const KProcessAddress last_addr = addr + size - 1;
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr);
+ R_TRY(this->CheckMemoryState(out_state, out_perm, out_attr, out_blocks_needed, it, last_addr,
+ state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr));
+
+ // If the start address isn't aligned, we need a block.
+ if (out_blocks_needed != nullptr &&
+ Common::AlignDown(GetInteger(addr), PageSize) != it->GetAddress()) {
+ ++(*out_blocks_needed);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_paddr,
+ KProcessAddress addr, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr, KMemoryPermission new_perm,
+ KMemoryAttribute lock_attr) {
+ // Validate basic preconditions.
+ ASSERT(False(lock_attr & attr));
+ ASSERT(False(lock_attr & (KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared)));
+
+ // Validate the lock request.
+ const size_t num_pages = size / PageSize;
+ R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check that the output page group is empty, if it exists.
+ if (out_pg) {
+ ASSERT(out_pg->GetNumPages() == 0);
+ }
+
+ // Check the state.
+ KMemoryState old_state;
+ KMemoryPermission old_perm;
+ KMemoryAttribute old_attr;
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
+ std::addressof(old_attr), std::addressof(num_allocator_blocks),
+ addr, size, state_mask | KMemoryState::FlagReferenceCounted,
+ state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
+ attr_mask, attr));
+
+ // Get the physical address, if we're supposed to.
+ if (out_paddr != nullptr) {
+ ASSERT(this->GetPhysicalAddressLocked(out_paddr, addr));
+ }
+
+ // Make the page group, if we're supposed to.
+ if (out_pg != nullptr) {
+ R_TRY(this->MakePageGroup(*out_pg, addr, num_pages));
+ }
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // Decide on new perm and attr.
+ new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
+ KMemoryAttribute new_attr = old_attr | static_cast<KMemoryAttribute>(lock_attr);
+
+ // Update permission, if we need to.
+ if (new_perm != old_perm) {
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ const KPageProperties properties = {new_perm, false,
+ True(old_attr & KMemoryAttribute::Uncached),
+ DisableMergeAttribute::DisableHeadBodyTail};
+ R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, 0, false, properties,
+ OperationType::ChangePermissions, false));
+ }
+
+ // Apply the memory block updates.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm,
+ new_attr, KMemoryBlockDisableMergeAttribute::Locked,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // If we have an output group, open.
+ if (out_pg) {
+ out_pg->Open();
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnlockMemory(KProcessAddress addr, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr, KMemoryPermission new_perm,
+ KMemoryAttribute lock_attr, const KPageGroup* pg) {
+ // Validate basic preconditions.
+ ASSERT((attr_mask & lock_attr) == lock_attr);
+ ASSERT((attr & lock_attr) == lock_attr);
+
+ // Validate the unlock request.
+ const size_t num_pages = size / PageSize;
+ R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the state.
+ KMemoryState old_state;
+ KMemoryPermission old_perm;
+ KMemoryAttribute old_attr;
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
+ std::addressof(old_attr), std::addressof(num_allocator_blocks),
+ addr, size, state_mask | KMemoryState::FlagReferenceCounted,
+ state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
+ attr_mask, attr));
+
+ // Check the page group.
+ if (pg != nullptr) {
+ R_UNLESS(this->IsValidPageGroup(*pg, addr, num_pages), ResultInvalidMemoryRegion);
+ }
+
+ // Decide on new perm and attr.
+ new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
+ KMemoryAttribute new_attr = old_attr & ~static_cast<KMemoryAttribute>(lock_attr);
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // Update permission, if we need to.
+ if (new_perm != old_perm) {
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ const KPageProperties properties = {new_perm, false,
+ True(old_attr & KMemoryAttribute::Uncached),
+ DisableMergeAttribute::EnableAndMergeHeadBodyTail};
+ R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, 0, false, properties,
+ OperationType::ChangePermissions, false));
+ }
+
+ // Apply the memory block updates.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm,
+ new_attr, KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Locked);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::QueryInfoImpl(KMemoryInfo* out_info, Svc::PageInfo* out_page,
+ KProcessAddress address) const {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(out_info != nullptr);
+ ASSERT(out_page != nullptr);
+
+ const KMemoryBlock* block = m_memory_block_manager.FindBlock(address);
+ R_UNLESS(block != nullptr, ResultInvalidCurrentMemory);
+
+ *out_info = block->GetMemoryInfo();
+ out_page->flags = 0;
+ R_SUCCEED();
+}
+
+Result KPageTableBase::QueryMappingImpl(KProcessAddress* out, KPhysicalAddress address, size_t size,
+ Svc::MemoryState state) const {
+ ASSERT(!this->IsLockedByCurrentThread());
+ ASSERT(out != nullptr);
+
+ const KProcessAddress region_start = this->GetRegionAddress(state);
+ const size_t region_size = this->GetRegionSize(state);
+
+ // Check that the address/size are potentially valid.
+ R_UNLESS((address < address + size), ResultNotFound);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ auto& impl = this->GetImpl();
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
+ bool cur_valid = false;
+ TraversalEntry next_entry;
+ bool next_valid;
+ size_t tot_size = 0;
+
+ next_valid =
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), region_start);
+ next_entry.block_size =
+ (next_entry.block_size - (GetInteger(region_start) & (next_entry.block_size - 1)));
+
+ // Iterate, looking for entry.
+ while (true) {
+ if ((!next_valid && !cur_valid) ||
+ (next_valid && cur_valid &&
+ next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) {
+ cur_entry.block_size += next_entry.block_size;
+ } else {
+ if (cur_valid && cur_entry.phys_addr <= address &&
+ address + size <= cur_entry.phys_addr + cur_entry.block_size) {
+ // Check if this region is valid.
+ const KProcessAddress mapped_address =
+ (region_start + tot_size) + GetInteger(address - cur_entry.phys_addr);
+ if (R_SUCCEEDED(this->CheckMemoryState(
+ mapped_address, size, KMemoryState::Mask, static_cast<KMemoryState>(state),
+ KMemoryPermission::UserRead, KMemoryPermission::UserRead,
+ KMemoryAttribute::None, KMemoryAttribute::None))) {
+ // It is!
+ *out = mapped_address;
+ R_SUCCEED();
+ }
+ }
+
+ // Update tracking variables.
+ tot_size += cur_entry.block_size;
+ cur_entry = next_entry;
+ cur_valid = next_valid;
+ }
+
+ if (cur_entry.block_size + tot_size >= region_size) {
+ break;
+ }
+
+ next_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ }
+
+ // Check the last entry.
+ R_UNLESS(cur_valid, ResultNotFound);
+ R_UNLESS(cur_entry.phys_addr <= address, ResultNotFound);
+ R_UNLESS(address + size <= cur_entry.phys_addr + cur_entry.block_size, ResultNotFound);
+
+ // Check if the last region is valid.
+ const KProcessAddress mapped_address =
+ (region_start + tot_size) + GetInteger(address - cur_entry.phys_addr);
+ R_TRY_CATCH(this->CheckMemoryState(mapped_address, size, KMemoryState::All,
+ static_cast<KMemoryState>(state),
+ KMemoryPermission::UserRead, KMemoryPermission::UserRead,
+ KMemoryAttribute::None, KMemoryAttribute::None)) {
+ R_CONVERT_ALL(ResultNotFound);
+ }
+ R_END_TRY_CATCH;
+
+ // We found the region.
+ *out = mapped_address;
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapMemory(KProcessAddress dst_address, KProcessAddress src_address,
+ size_t size) {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate that the source address's state is valid.
+ KMemoryState src_state;
+ size_t num_src_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(src_state), nullptr, nullptr,
+ std::addressof(num_src_allocator_blocks), src_address, size,
+ KMemoryState::FlagCanAlias, KMemoryState::FlagCanAlias,
+ KMemoryPermission::All, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::All, KMemoryAttribute::None));
+
+ // Validate that the dst address's state is valid.
+ size_t num_dst_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_dst_allocator_blocks), dst_address, size,
+ KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
+
+ // Create an update allocator for the source.
+ Result src_allocator_result;
+ KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
+ m_memory_block_slab_manager,
+ num_src_allocator_blocks);
+ R_TRY(src_allocator_result);
+
+ // Create an update allocator for the destination.
+ Result dst_allocator_result;
+ KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
+ m_memory_block_slab_manager,
+ num_dst_allocator_blocks);
+ R_TRY(dst_allocator_result);
+
+ // Map the memory.
+ {
+ // Determine the number of pages being operated on.
+ const size_t num_pages = size / PageSize;
+
+ // Create page groups for the memory being unmapped.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+
+ // Create the page group representing the source.
+ R_TRY(this->MakePageGroup(pg, src_address, num_pages));
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Reprotect the source as kernel-read/not mapped.
+ const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>(
+ KMemoryPermission::KernelRead | KMemoryPermission::NotMapped);
+ const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked;
+ const KPageProperties src_properties = {new_src_perm, false, false,
+ DisableMergeAttribute::DisableHeadBodyTail};
+ R_TRY(this->Operate(updater.GetPageList(), src_address, num_pages, 0, false, src_properties,
+ OperationType::ChangePermissions, false));
+
+ // Ensure that we unprotect the source pages on failure.
+ ON_RESULT_FAILURE {
+ const KPageProperties unprotect_properties = {
+ KMemoryPermission::UserReadWrite, false, false,
+ DisableMergeAttribute::EnableHeadBodyTail};
+ R_ASSERT(this->Operate(updater.GetPageList(), src_address, num_pages, 0, false,
+ unprotect_properties, OperationType::ChangePermissions, true));
+ };
+
+ // Map the alias pages.
+ const KPageProperties dst_map_properties = {KMemoryPermission::UserReadWrite, false, false,
+ DisableMergeAttribute::DisableHead};
+ R_TRY(this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_map_properties,
+ false));
+
+ // Apply the memory block updates.
+ m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages,
+ src_state, new_src_perm, new_src_attr,
+ KMemoryBlockDisableMergeAttribute::Locked,
+ KMemoryBlockDisableMergeAttribute::None);
+ m_memory_block_manager.Update(
+ std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::Stack,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address,
+ size_t size) {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate that the source address's state is valid.
+ KMemoryState src_state;
+ size_t num_src_allocator_blocks;
+ R_TRY(this->CheckMemoryState(
+ std::addressof(src_state), nullptr, nullptr, std::addressof(num_src_allocator_blocks),
+ src_address, size, KMemoryState::FlagCanAlias, KMemoryState::FlagCanAlias,
+ KMemoryPermission::All, KMemoryPermission::NotMapped | KMemoryPermission::KernelRead,
+ KMemoryAttribute::All, KMemoryAttribute::Locked));
+
+ // Validate that the dst address's state is valid.
+ KMemoryPermission dst_perm;
+ size_t num_dst_allocator_blocks;
+ R_TRY(this->CheckMemoryState(
+ nullptr, std::addressof(dst_perm), nullptr, std::addressof(num_dst_allocator_blocks),
+ dst_address, size, KMemoryState::All, KMemoryState::Stack, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None));
+
+ // Create an update allocator for the source.
+ Result src_allocator_result;
+ KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
+ m_memory_block_slab_manager,
+ num_src_allocator_blocks);
+ R_TRY(src_allocator_result);
+
+ // Create an update allocator for the destination.
+ Result dst_allocator_result;
+ KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
+ m_memory_block_slab_manager,
+ num_dst_allocator_blocks);
+ R_TRY(dst_allocator_result);
+
+ // Unmap the memory.
+ {
+ // Determine the number of pages being operated on.
+ const size_t num_pages = size / PageSize;
+
+ // Create page groups for the memory being unmapped.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+
+ // Create the page group representing the destination.
+ R_TRY(this->MakePageGroup(pg, dst_address, num_pages));
+
+ // Ensure the page group is the valid for the source.
+ R_UNLESS(this->IsValidPageGroup(pg, src_address, num_pages), ResultInvalidMemoryRegion);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Unmap the aliased copy of the pages.
+ const KPageProperties dst_unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), dst_address, num_pages, 0, false,
+ dst_unmap_properties, OperationType::Unmap, false));
+
+ // Ensure that we re-map the aliased pages on failure.
+ ON_RESULT_FAILURE {
+ this->RemapPageGroup(updater.GetPageList(), dst_address, size, pg);
+ };
+
+ // Try to set the permissions for the source pages back to what they should be.
+ const KPageProperties src_properties = {KMemoryPermission::UserReadWrite, false, false,
+ DisableMergeAttribute::EnableAndMergeHeadBodyTail};
+ R_TRY(this->Operate(updater.GetPageList(), src_address, num_pages, 0, false, src_properties,
+ OperationType::ChangePermissions, false));
+
+ // Apply the memory block updates.
+ m_memory_block_manager.Update(
+ std::addressof(src_allocator), src_address, num_pages, src_state,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked);
+ m_memory_block_manager.Update(
+ std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address,
+ size_t size) {
+ // Validate the mapping request.
+ R_UNLESS(this->CanContain(dst_address, size, KMemoryState::AliasCode),
+ ResultInvalidMemoryRegion);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Verify that the source memory is normal heap.
+ KMemoryState src_state;
+ KMemoryPermission src_perm;
+ size_t num_src_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(src_state), std::addressof(src_perm), nullptr,
+ std::addressof(num_src_allocator_blocks), src_address, size,
+ KMemoryState::All, KMemoryState::Normal, KMemoryPermission::All,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Verify that the destination memory is unmapped.
+ size_t num_dst_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_dst_allocator_blocks), dst_address, size,
+ KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
+
+ // Create an update allocator for the source.
+ Result src_allocator_result;
+ KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
+ m_memory_block_slab_manager,
+ num_src_allocator_blocks);
+ R_TRY(src_allocator_result);
+
+ // Create an update allocator for the destination.
+ Result dst_allocator_result;
+ KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
+ m_memory_block_slab_manager,
+ num_dst_allocator_blocks);
+ R_TRY(dst_allocator_result);
+
+ // Map the code memory.
+ {
+ // Determine the number of pages being operated on.
+ const size_t num_pages = size / PageSize;
+
+ // Create page groups for the memory being unmapped.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+
+ // Create the page group representing the source.
+ R_TRY(this->MakePageGroup(pg, src_address, num_pages));
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Reprotect the source as kernel-read/not mapped.
+ const KMemoryPermission new_perm = static_cast<KMemoryPermission>(
+ KMemoryPermission::KernelRead | KMemoryPermission::NotMapped);
+ const KPageProperties src_properties = {new_perm, false, false,
+ DisableMergeAttribute::DisableHeadBodyTail};
+ R_TRY(this->Operate(updater.GetPageList(), src_address, num_pages, 0, false, src_properties,
+ OperationType::ChangePermissions, false));
+
+ // Ensure that we unprotect the source pages on failure.
+ ON_RESULT_FAILURE {
+ const KPageProperties unprotect_properties = {
+ src_perm, false, false, DisableMergeAttribute::EnableHeadBodyTail};
+ R_ASSERT(this->Operate(updater.GetPageList(), src_address, num_pages, 0, false,
+ unprotect_properties, OperationType::ChangePermissions, true));
+ };
+
+ // Map the alias pages.
+ const KPageProperties dst_properties = {new_perm, false, false,
+ DisableMergeAttribute::DisableHead};
+ R_TRY(
+ this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_properties, false));
+
+ // Apply the memory block updates.
+ m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages,
+ src_state, new_perm, KMemoryAttribute::Locked,
+ KMemoryBlockDisableMergeAttribute::Locked,
+ KMemoryBlockDisableMergeAttribute::None);
+ m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages,
+ KMemoryState::AliasCode, new_perm, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address,
+ size_t size) {
+ // Validate the mapping request.
+ R_UNLESS(this->CanContain(dst_address, size, KMemoryState::AliasCode),
+ ResultInvalidMemoryRegion);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Verify that the source memory is locked normal heap.
+ size_t num_src_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_src_allocator_blocks), src_address, size,
+ KMemoryState::All, KMemoryState::Normal, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked));
+
+ // Verify that the destination memory is aliasable code.
+ size_t num_dst_allocator_blocks;
+ R_TRY(this->CheckMemoryStateContiguous(
+ std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState::FlagCanCodeAlias,
+ KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::All & ~KMemoryAttribute::PermissionLocked, KMemoryAttribute::None));
+
+ // Determine whether any pages being unmapped are code.
+ bool any_code_pages = false;
+ {
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(dst_address);
+ while (true) {
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Check if the memory has code flag.
+ if (True(info.GetState() & KMemoryState::FlagCode)) {
+ any_code_pages = true;
+ break;
+ }
+
+ // Check if we're done.
+ if (dst_address + size - 1 <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
+ }
+ }
+
+ // Ensure that we maintain the instruction cache.
+ bool reprotected_pages = false;
+ SCOPE_EXIT({
+ if (reprotected_pages && any_code_pages) {
+ InvalidateInstructionCache(m_system, dst_address, size);
+ }
+ });
+
+ // Unmap.
+ {
+ // Determine the number of pages being operated on.
+ const size_t num_pages = size / PageSize;
+
+ // Create page groups for the memory being unmapped.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+
+ // Create the page group representing the destination.
+ R_TRY(this->MakePageGroup(pg, dst_address, num_pages));
+
+ // Verify that the page group contains the same pages as the source.
+ R_UNLESS(this->IsValidPageGroup(pg, src_address, num_pages), ResultInvalidMemoryRegion);
+
+ // Create an update allocator for the source.
+ Result src_allocator_result;
+ KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
+ m_memory_block_slab_manager,
+ num_src_allocator_blocks);
+ R_TRY(src_allocator_result);
+
+ // Create an update allocator for the destination.
+ Result dst_allocator_result;
+ KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
+ m_memory_block_slab_manager,
+ num_dst_allocator_blocks);
+ R_TRY(dst_allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Unmap the aliased copy of the pages.
+ const KPageProperties dst_unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), dst_address, num_pages, 0, false,
+ dst_unmap_properties, OperationType::Unmap, false));
+
+ // Ensure that we re-map the aliased pages on failure.
+ ON_RESULT_FAILURE {
+ this->RemapPageGroup(updater.GetPageList(), dst_address, size, pg);
+ };
+
+ // Try to set the permissions for the source pages back to what they should be.
+ const KPageProperties src_properties = {KMemoryPermission::UserReadWrite, false, false,
+ DisableMergeAttribute::EnableAndMergeHeadBodyTail};
+ R_TRY(this->Operate(updater.GetPageList(), src_address, num_pages, 0, false, src_properties,
+ OperationType::ChangePermissions, false));
+
+ // Apply the memory block updates.
+ m_memory_block_manager.Update(
+ std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal);
+ m_memory_block_manager.Update(
+ std::addressof(src_allocator), src_address, num_pages, KMemoryState::Normal,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked);
+
+ // Note that we reprotected pages.
+ reprotected_pages = true;
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapInsecureMemory(KProcessAddress address, size_t size) {
+ // Get the insecure memory resource limit and pool.
+ auto* const insecure_resource_limit = KSystemControl::GetInsecureMemoryResourceLimit(m_kernel);
+ const auto insecure_pool =
+ static_cast<KMemoryManager::Pool>(KSystemControl::GetInsecureMemoryPool());
+
+ // Reserve the insecure memory.
+ // NOTE: ResultOutOfMemory is returned here instead of the usual LimitReached.
+ KScopedResourceReservation memory_reservation(insecure_resource_limit,
+ Svc::LimitableResource::PhysicalMemoryMax, size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultOutOfMemory);
+
+ // Allocate pages for the insecure memory.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+ R_TRY(m_kernel.MemoryManager().AllocateAndOpen(
+ std::addressof(pg), size / PageSize,
+ KMemoryManager::EncodeOption(insecure_pool, KMemoryManager::Direction::FromFront)));
+
+ // Close the opened pages when we're done with them.
+ // If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed
+ // automatically.
+ SCOPE_EXIT({ pg.Close(); });
+
+ // Clear all the newly allocated pages.
+ for (const auto& it : pg) {
+ std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()),
+ static_cast<u32>(m_heap_fill_value), it.GetSize());
+ }
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate that the address's state is valid.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Map the pages.
+ const size_t num_pages = size / PageSize;
+ const KPageProperties map_properties = {KMemoryPermission::UserReadWrite, false, false,
+ DisableMergeAttribute::DisableHead};
+ R_TRY(this->Operate(updater.GetPageList(), address, num_pages, pg, map_properties,
+ OperationType::MapGroup, false));
+
+ // Apply the memory block update.
+ m_memory_block_manager.Update(std::addressof(allocator), address, num_pages,
+ KMemoryState::Insecure, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // Update our mapped insecure size.
+ m_mapped_insecure_memory += size;
+
+ // Commit the memory reservation.
+ memory_reservation.Commit();
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnmapInsecureMemory(KProcessAddress address, size_t size) {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, KMemoryState::Insecure, KMemoryPermission::All,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Unmap the memory.
+ const size_t num_pages = size / PageSize;
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), address, num_pages, 0, false, unmap_properties,
+ OperationType::Unmap, false));
+
+ // Apply the memory block update.
+ m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal);
+
+ // Update our mapped insecure size.
+ m_mapped_insecure_memory -= size;
+
+ // Release the insecure memory from the insecure limit.
+ if (auto* const insecure_resource_limit =
+ KSystemControl::GetInsecureMemoryResourceLimit(m_kernel);
+ insecure_resource_limit != nullptr) {
+ insecure_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, size);
+ }
+
+ R_SUCCEED();
+}
+
+KProcessAddress KPageTableBase::FindFreeArea(KProcessAddress region_start, size_t region_num_pages,
+ size_t num_pages, size_t alignment, size_t offset,
+ size_t guard_pages) const {
+ KProcessAddress address = 0;
+
+ if (num_pages <= region_num_pages) {
+ if (this->IsAslrEnabled()) {
+ // Try to directly find a free area up to 8 times.
+ for (size_t i = 0; i < 8; i++) {
+ const size_t random_offset =
+ KSystemControl::GenerateRandomRange(
+ 0, (region_num_pages - num_pages - guard_pages) * PageSize / alignment) *
+ alignment;
+ const KProcessAddress candidate =
+ Common::AlignDown(GetInteger(region_start + random_offset), alignment) + offset;
+
+ KMemoryInfo info;
+ Svc::PageInfo page_info;
+ R_ASSERT(this->QueryInfoImpl(std::addressof(info), std::addressof(page_info),
+ candidate));
+
+ if (info.m_state != KMemoryState::Free) {
+ continue;
+ }
+ if (!(region_start <= candidate)) {
+ continue;
+ }
+ if (!(info.GetAddress() + guard_pages * PageSize <= GetInteger(candidate))) {
+ continue;
+ }
+ if (!(candidate + (num_pages + guard_pages) * PageSize - 1 <=
+ info.GetLastAddress())) {
+ continue;
+ }
+ if (!(candidate + (num_pages + guard_pages) * PageSize - 1 <=
+ region_start + region_num_pages * PageSize - 1)) {
+ continue;
+ }
+
+ address = candidate;
+ break;
+ }
+ // Fall back to finding the first free area with a random offset.
+ if (address == 0) {
+ // NOTE: Nintendo does not account for guard pages here.
+ // This may theoretically cause an offset to be chosen that cannot be mapped.
+ // We will account for guard pages.
+ const size_t offset_pages = KSystemControl::GenerateRandomRange(
+ 0, region_num_pages - num_pages - guard_pages);
+ address = m_memory_block_manager.FindFreeArea(
+ region_start + offset_pages * PageSize, region_num_pages - offset_pages,
+ num_pages, alignment, offset, guard_pages);
+ }
+ }
+ // Find the first free area.
+ if (address == 0) {
+ address = m_memory_block_manager.FindFreeArea(region_start, region_num_pages, num_pages,
+ alignment, offset, guard_pages);
+ }
+ }
+
+ return address;
+}
+
+size_t KPageTableBase::GetSize(KMemoryState state) const {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Iterate, counting blocks with the desired state.
+ size_t total_size = 0;
+ for (KMemoryBlockManager::const_iterator it =
+ m_memory_block_manager.FindIterator(m_address_space_start);
+ it != m_memory_block_manager.end(); ++it) {
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+ if (info.GetState() == state) {
+ total_size += info.GetSize();
+ }
+ }
+
+ return total_size;
+}
+
+size_t KPageTableBase::GetCodeSize() const {
+ return this->GetSize(KMemoryState::Code);
+}
+
+size_t KPageTableBase::GetCodeDataSize() const {
+ return this->GetSize(KMemoryState::CodeData);
+}
+
+size_t KPageTableBase::GetAliasCodeSize() const {
+ return this->GetSize(KMemoryState::AliasCode);
+}
+
+size_t KPageTableBase::GetAliasCodeDataSize() const {
+ return this->GetSize(KMemoryState::AliasCodeData);
+}
+
+Result KPageTableBase::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address,
+ size_t num_pages, KMemoryPermission perm) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ // Create a page group to hold the pages we allocate.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+
+ // Allocate the pages.
+ R_TRY(
+ m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, m_allocate_option));
+
+ // Ensure that the page group is closed when we're done working with it.
+ SCOPE_EXIT({ pg.Close(); });
+
+ // Clear all pages.
+ for (const auto& it : pg) {
+ std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()),
+ static_cast<u32>(m_heap_fill_value), it.GetSize());
+ }
+
+ // Map the pages.
+ const KPageProperties properties = {perm, false, false, DisableMergeAttribute::None};
+ R_RETURN(this->Operate(page_list, address, num_pages, pg, properties, OperationType::MapGroup,
+ false));
+}
+
+Result KPageTableBase::MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address,
+ const KPageGroup& pg, const KPageProperties properties,
+ bool reuse_ll) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ // Note the current address, so that we can iterate.
+ const KProcessAddress start_address = address;
+ KProcessAddress cur_address = address;
+
+ // Ensure that we clean up on failure.
+ ON_RESULT_FAILURE {
+ ASSERT(!reuse_ll);
+ if (cur_address != start_address) {
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_ASSERT(this->Operate(page_list, start_address,
+ (cur_address - start_address) / PageSize, 0, false,
+ unmap_properties, OperationType::Unmap, true));
+ }
+ };
+
+ // Iterate, mapping all pages in the group.
+ for (const auto& block : pg) {
+ // Map and advance.
+ const KPageProperties cur_properties =
+ (cur_address == start_address)
+ ? properties
+ : KPageProperties{properties.perm, properties.io, properties.uncached,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(page_list, cur_address, block.GetNumPages(), block.GetAddress(), true,
+ cur_properties, OperationType::Map, reuse_ll));
+ cur_address += block.GetSize();
+ }
+
+ // We succeeded!
+ R_SUCCEED();
+}
+
+void KPageTableBase::RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size,
+ const KPageGroup& pg) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ // Note the current address, so that we can iterate.
+ const KProcessAddress start_address = address;
+ const KProcessAddress last_address = start_address + size - 1;
+ const KProcessAddress end_address = last_address + 1;
+
+ // Iterate over the memory.
+ auto pg_it = pg.begin();
+ ASSERT(pg_it != pg.end());
+
+ KPhysicalAddress pg_phys_addr = pg_it->GetAddress();
+ size_t pg_pages = pg_it->GetNumPages();
+
+ auto it = m_memory_block_manager.FindIterator(start_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != m_memory_block_manager.end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Determine the range to map.
+ KProcessAddress map_address = std::max<u64>(info.GetAddress(), GetInteger(start_address));
+ const KProcessAddress map_end_address =
+ std::min<u64>(info.GetEndAddress(), GetInteger(end_address));
+ ASSERT(map_end_address != map_address);
+
+ // Determine if we should disable head merge.
+ const bool disable_head_merge =
+ info.GetAddress() >= GetInteger(start_address) &&
+ True(info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Normal);
+ const KPageProperties map_properties = {
+ info.GetPermission(), false, false,
+ disable_head_merge ? DisableMergeAttribute::DisableHead : DisableMergeAttribute::None};
+
+ // While we have pages to map, map them.
+ size_t map_pages = (map_end_address - map_address) / PageSize;
+ while (map_pages > 0) {
+ // Check if we're at the end of the physical block.
+ if (pg_pages == 0) {
+ // Ensure there are more pages to map.
+ ASSERT(pg_it != pg.end());
+
+ // Advance our physical block.
+ ++pg_it;
+ pg_phys_addr = pg_it->GetAddress();
+ pg_pages = pg_it->GetNumPages();
+ }
+
+ // Map whatever we can.
+ const size_t cur_pages = std::min(pg_pages, map_pages);
+ R_ASSERT(this->Operate(page_list, map_address, map_pages, pg_phys_addr, true,
+ map_properties, OperationType::Map, true));
+
+ // Advance.
+ map_address += cur_pages * PageSize;
+ map_pages -= cur_pages;
+
+ pg_phys_addr += cur_pages * PageSize;
+ pg_pages -= cur_pages;
+ }
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
+ }
+
+ // Check that we re-mapped precisely the page group.
+ ASSERT((++pg_it) == pg.end());
+}
+
+Result KPageTableBase::MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_t num_pages) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ const size_t size = num_pages * PageSize;
+
+ // We're making a new group, not adding to an existing one.
+ R_UNLESS(pg.empty(), ResultInvalidCurrentMemory);
+
+ auto& impl = this->GetImpl();
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ R_UNLESS(impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr),
+ ResultInvalidCurrentMemory);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ // Iterate, adding to group as we go.
+ while (tot_size < size) {
+ R_UNLESS(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)),
+ ResultInvalidCurrentMemory);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ const size_t cur_pages = cur_size / PageSize;
+
+ R_UNLESS(IsHeapPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+ R_TRY(pg.AddBlock(cur_addr, cur_pages));
+
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we add the right amount for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // add the last block.
+ const size_t cur_pages = cur_size / PageSize;
+ R_UNLESS(IsHeapPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+ R_TRY(pg.AddBlock(cur_addr, cur_pages));
+
+ R_SUCCEED();
+}
+
+bool KPageTableBase::IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr,
+ size_t num_pages) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ const size_t size = num_pages * PageSize;
+
+ // Empty groups are necessarily invalid.
+ if (pg.empty()) {
+ return false;
+ }
+
+ auto& impl = this->GetImpl();
+
+ // We're going to validate that the group we'd expect is the group we see.
+ auto cur_it = pg.begin();
+ KPhysicalAddress cur_block_address = cur_it->GetAddress();
+ size_t cur_block_pages = cur_it->GetNumPages();
+
+ auto UpdateCurrentIterator = [&]() {
+ if (cur_block_pages == 0) {
+ if ((++cur_it) == pg.end()) {
+ return false;
+ }
+
+ cur_block_address = cur_it->GetAddress();
+ cur_block_pages = cur_it->GetNumPages();
+ }
+ return true;
+ };
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ if (!impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr)) {
+ return false;
+ }
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ // Iterate, comparing expected to actual.
+ while (tot_size < size) {
+ if (!impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context))) {
+ return false;
+ }
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ const size_t cur_pages = cur_size / PageSize;
+
+ if (!IsHeapPhysicalAddress(cur_addr)) {
+ return false;
+ }
+
+ if (!UpdateCurrentIterator()) {
+ return false;
+ }
+
+ if (cur_block_address != cur_addr || cur_block_pages < cur_pages) {
+ return false;
+ }
+
+ cur_block_address += cur_size;
+ cur_block_pages -= cur_pages;
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we compare the right amount for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ if (!IsHeapPhysicalAddress(cur_addr)) {
+ return false;
+ }
+
+ if (!UpdateCurrentIterator()) {
+ return false;
+ }
+
+ return cur_block_address == cur_addr && cur_block_pages == (cur_size / PageSize);
+}
+
+Result KPageTableBase::GetContiguousMemoryRangeWithState(
+ MemoryRange* out, KProcessAddress address, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ auto& impl = this->GetImpl();
+
+ // Begin a traversal.
+ TraversalContext context;
+ TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
+ R_UNLESS(impl.BeginTraversal(std::addressof(cur_entry), std::addressof(context), address),
+ ResultInvalidCurrentMemory);
+
+ // Traverse until we have enough size or we aren't contiguous any more.
+ const KPhysicalAddress phys_address = cur_entry.phys_addr;
+ size_t contig_size;
+ for (contig_size =
+ cur_entry.block_size - (GetInteger(phys_address) & (cur_entry.block_size - 1));
+ contig_size < size; contig_size += cur_entry.block_size) {
+ if (!impl.ContinueTraversal(std::addressof(cur_entry), std::addressof(context))) {
+ break;
+ }
+ if (cur_entry.phys_addr != phys_address + contig_size) {
+ break;
+ }
+ }
+
+ // Take the minimum size for our region.
+ size = std::min(size, contig_size);
+
+ // Check that the memory is contiguous (modulo the reference count bit).
+ const KMemoryState test_state_mask = state_mask | KMemoryState::FlagReferenceCounted;
+ const bool is_heap = R_SUCCEEDED(this->CheckMemoryStateContiguous(
+ address, size, test_state_mask, state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
+ attr_mask, attr));
+ if (!is_heap) {
+ R_TRY(this->CheckMemoryStateContiguous(address, size, test_state_mask, state, perm_mask,
+ perm, attr_mask, attr));
+ }
+
+ // The memory is contiguous, so set the output range.
+ out->Set(phys_address, size, is_heap);
+ R_SUCCEED();
+}
+
+Result KPageTableBase::SetMemoryPermission(KProcessAddress addr, size_t size,
+ Svc::MemoryPermission svc_perm) {
+ const size_t num_pages = size / PageSize;
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Verify we can change the memory permission.
+ KMemoryState old_state;
+ KMemoryPermission old_perm;
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), nullptr,
+ std::addressof(num_allocator_blocks), addr, size,
+ KMemoryState::FlagCanReprotect, KMemoryState::FlagCanReprotect,
+ KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::All, KMemoryAttribute::None));
+
+ // Determine new perm.
+ const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm);
+ R_SUCCEED_IF(old_perm == new_perm);
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform mapping operation.
+ const KPageProperties properties = {new_perm, false, false, DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, 0, false, properties,
+ OperationType::ChangePermissions, false));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::SetProcessMemoryPermission(KProcessAddress addr, size_t size,
+ Svc::MemoryPermission svc_perm) {
+ const size_t num_pages = size / PageSize;
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Verify we can change the memory permission.
+ KMemoryState old_state;
+ KMemoryPermission old_perm;
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), nullptr,
+ std::addressof(num_allocator_blocks), addr, size,
+ KMemoryState::FlagCode, KMemoryState::FlagCode,
+ KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::All, KMemoryAttribute::None));
+
+ // Make a new page group for the region.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+
+ // Determine new perm/state.
+ const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm);
+ KMemoryState new_state = old_state;
+ const bool is_w = (new_perm & KMemoryPermission::UserWrite) == KMemoryPermission::UserWrite;
+ const bool is_x = (new_perm & KMemoryPermission::UserExecute) == KMemoryPermission::UserExecute;
+ const bool was_x =
+ (old_perm & KMemoryPermission::UserExecute) == KMemoryPermission::UserExecute;
+ ASSERT(!(is_w && is_x));
+
+ if (is_w) {
+ switch (old_state) {
+ case KMemoryState::Code:
+ new_state = KMemoryState::CodeData;
+ break;
+ case KMemoryState::AliasCode:
+ new_state = KMemoryState::AliasCodeData;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ // Create a page group, if we're setting execute permissions.
+ if (is_x) {
+ R_TRY(this->MakePageGroup(pg, GetInteger(addr), num_pages));
+ }
+
+ // Succeed if there's nothing to do.
+ R_SUCCEED_IF(old_perm == new_perm && old_state == new_state);
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform mapping operation.
+ const KPageProperties properties = {new_perm, false, false, DisableMergeAttribute::None};
+ const auto operation = was_x ? OperationType::ChangePermissionsAndRefreshAndFlush
+ : OperationType::ChangePermissions;
+ R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, 0, false, properties, operation,
+ false));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, new_state, new_perm,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // Ensure cache coherency, if we're setting pages as executable.
+ if (is_x) {
+ for (const auto& block : pg) {
+ StoreDataCache(GetHeapVirtualPointer(m_kernel, block.GetAddress()), block.GetSize());
+ }
+ InvalidateInstructionCache(m_system, addr, size);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::SetMemoryAttribute(KProcessAddress addr, size_t size, KMemoryAttribute mask,
+ KMemoryAttribute attr) {
+ const size_t num_pages = size / PageSize;
+ ASSERT((mask | KMemoryAttribute::SetMask) == KMemoryAttribute::SetMask);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Verify we can change the memory attribute.
+ KMemoryState old_state;
+ KMemoryPermission old_perm;
+ KMemoryAttribute old_attr;
+ size_t num_allocator_blocks;
+ constexpr KMemoryAttribute AttributeTestMask =
+ ~(KMemoryAttribute::SetMask | KMemoryAttribute::DeviceShared);
+ const KMemoryState state_test_mask =
+ (True(mask & KMemoryAttribute::Uncached) ? KMemoryState::FlagCanChangeAttribute
+ : KMemoryState::None) |
+ (True(mask & KMemoryAttribute::PermissionLocked) ? KMemoryState::FlagCanPermissionLock
+ : KMemoryState::None);
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
+ std::addressof(old_attr), std::addressof(num_allocator_blocks),
+ addr, size, state_test_mask, state_test_mask,
+ KMemoryPermission::None, KMemoryPermission::None,
+ AttributeTestMask, KMemoryAttribute::None, ~AttributeTestMask));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // If we need to, perform a change attribute operation.
+ if (True(mask & KMemoryAttribute::Uncached)) {
+ // Determine the new attribute.
+ const KMemoryAttribute new_attr =
+ static_cast<KMemoryAttribute>(((old_attr & ~mask) | (attr & mask)));
+
+ // Perform operation.
+ const KPageProperties properties = {old_perm, false,
+ True(new_attr & KMemoryAttribute::Uncached),
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, 0, false, properties,
+ OperationType::ChangePermissionsAndRefreshAndFlush, false));
+ }
+
+ // Update the blocks.
+ m_memory_block_manager.UpdateAttribute(std::addressof(allocator), addr, num_pages, mask, attr);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::SetHeapSize(KProcessAddress* out, size_t size) {
+ // Lock the physical memory mutex.
+ KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock);
+
+ // Try to perform a reduction in heap, instead of an extension.
+ KProcessAddress cur_address;
+ size_t allocation_size;
+ {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate that setting heap size is possible at all.
+ R_UNLESS(!m_is_kernel, ResultOutOfMemory);
+ R_UNLESS(size <= static_cast<size_t>(m_heap_region_end - m_heap_region_start),
+ ResultOutOfMemory);
+ R_UNLESS(size <= m_max_heap_size, ResultOutOfMemory);
+
+ if (size < static_cast<size_t>(m_current_heap_end - m_heap_region_start)) {
+ // The size being requested is less than the current size, so we need to free the end of
+ // the heap.
+
+ // Validate memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(
+ std::addressof(num_allocator_blocks), m_heap_region_start + size,
+ (m_current_heap_end - m_heap_region_start) - size, KMemoryState::All,
+ KMemoryState::Normal, KMemoryPermission::All, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::All, KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager,
+ num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Unmap the end of the heap.
+ const size_t num_pages = ((m_current_heap_end - m_heap_region_start) - size) / PageSize;
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), m_heap_region_start + size, num_pages, 0,
+ false, unmap_properties, OperationType::Unmap, false));
+
+ // Release the memory from the resource limit.
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
+ num_pages * PageSize);
+
+ // Apply the memory block update.
+ m_memory_block_manager.Update(std::addressof(allocator), m_heap_region_start + size,
+ num_pages, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None,
+ size == 0 ? KMemoryBlockDisableMergeAttribute::Normal
+ : KMemoryBlockDisableMergeAttribute::None);
+
+ // Update the current heap end.
+ m_current_heap_end = m_heap_region_start + size;
+
+ // Set the output.
+ *out = m_heap_region_start;
+ R_SUCCEED();
+ } else if (size == static_cast<size_t>(m_current_heap_end - m_heap_region_start)) {
+ // The size requested is exactly the current size.
+ *out = m_heap_region_start;
+ R_SUCCEED();
+ } else {
+ // We have to allocate memory. Determine how much to allocate and where while the table
+ // is locked.
+ cur_address = m_current_heap_end;
+ allocation_size = size - (m_current_heap_end - m_heap_region_start);
+ }
+ }
+
+ // Reserve memory for the heap extension.
+ KScopedResourceReservation memory_reservation(
+ m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, allocation_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+
+ // Allocate pages for the heap extension.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+ R_TRY(m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), allocation_size / PageSize,
+ m_allocate_option));
+
+ // Close the opened pages when we're done with them.
+ // If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed
+ // automatically.
+ SCOPE_EXIT({ pg.Close(); });
+
+ // Clear all the newly allocated pages.
+ for (const auto& it : pg) {
+ std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()), m_heap_fill_value,
+ it.GetSize());
+ }
+
+ // Map the pages.
+ {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Ensure that the heap hasn't changed since we began executing.
+ ASSERT(cur_address == m_current_heap_end);
+
+ // Check the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), m_current_heap_end,
+ allocation_size, KMemoryState::All, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(
+ std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Map the pages.
+ const size_t num_pages = allocation_size / PageSize;
+ const KPageProperties map_properties = {KMemoryPermission::UserReadWrite, false, false,
+ (m_current_heap_end == m_heap_region_start)
+ ? DisableMergeAttribute::DisableHead
+ : DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), m_current_heap_end, num_pages, pg,
+ map_properties, OperationType::MapGroup, false));
+
+ // We succeeded, so commit our memory reservation.
+ memory_reservation.Commit();
+
+ // Apply the memory block update.
+ m_memory_block_manager.Update(
+ std::addressof(allocator), m_current_heap_end, num_pages, KMemoryState::Normal,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
+ m_heap_region_start == m_current_heap_end ? KMemoryBlockDisableMergeAttribute::Normal
+ : KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // Update the current heap end.
+ m_current_heap_end = m_heap_region_start + size;
+
+ // Set the output.
+ *out = m_heap_region_start;
+ R_SUCCEED();
+ }
+}
+
+Result KPageTableBase::SetMaxHeapSize(size_t size) {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Only process page tables are allowed to set heap size.
+ ASSERT(!this->IsKernel());
+
+ m_max_heap_size = size;
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::QueryInfo(KMemoryInfo* out_info, Svc::PageInfo* out_page_info,
+ KProcessAddress addr) const {
+ // If the address is invalid, create a fake block.
+ if (!this->Contains(addr, 1)) {
+ *out_info = {
+ .m_address = GetInteger(m_address_space_end),
+ .m_size = 0 - GetInteger(m_address_space_end),
+ .m_state = static_cast<KMemoryState>(Svc::MemoryState::Inaccessible),
+ .m_device_disable_merge_left_count = 0,
+ .m_device_disable_merge_right_count = 0,
+ .m_ipc_lock_count = 0,
+ .m_device_use_count = 0,
+ .m_ipc_disable_merge_count = 0,
+ .m_permission = KMemoryPermission::None,
+ .m_attribute = KMemoryAttribute::None,
+ .m_original_permission = KMemoryPermission::None,
+ .m_disable_merge_attribute = KMemoryBlockDisableMergeAttribute::None,
+ };
+ out_page_info->flags = 0;
+
+ R_SUCCEED();
+ }
+
+ // Otherwise, lock the table and query.
+ KScopedLightLock lk(m_general_lock);
+ R_RETURN(this->QueryInfoImpl(out_info, out_page_info, addr));
+}
+
+Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
+ KProcessAddress address) const {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Align the address down to page size.
+ address = Common::AlignDown(GetInteger(address), PageSize);
+
+ // Verify that we can query the address.
+ KMemoryInfo info;
+ Svc::PageInfo page_info;
+ R_TRY(this->QueryInfoImpl(std::addressof(info), std::addressof(page_info), address));
+
+ // Check the memory state.
+ R_TRY(this->CheckMemoryState(info, KMemoryState::FlagCanQueryPhysical,
+ KMemoryState::FlagCanQueryPhysical,
+ KMemoryPermission::UserReadExecute, KMemoryPermission::UserRead,
+ KMemoryAttribute::None, KMemoryAttribute::None));
+
+ // Prepare to traverse.
+ KPhysicalAddress phys_addr;
+ size_t phys_size;
+
+ KProcessAddress virt_addr = info.GetAddress();
+ KProcessAddress end_addr = info.GetEndAddress();
+
+ // Perform traversal.
+ {
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid =
+ m_impl->BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr);
+ R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
+
+ // Set tracking variables.
+ phys_addr = next_entry.phys_addr;
+ phys_size = next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1));
+
+ // Iterate.
+ while (true) {
+ // Continue the traversal.
+ traverse_valid =
+ m_impl->ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ if (!traverse_valid) {
+ break;
+ }
+
+ if (next_entry.phys_addr != (phys_addr + phys_size)) {
+ // Check if we're done.
+ if (virt_addr <= address && address <= virt_addr + phys_size - 1) {
+ break;
+ }
+
+ // Advance.
+ phys_addr = next_entry.phys_addr;
+ virt_addr += next_entry.block_size;
+ phys_size =
+ next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1));
+ } else {
+ phys_size += next_entry.block_size;
+ }
+
+ // Check if we're done.
+ if (end_addr < virt_addr + phys_size) {
+ break;
+ }
+ }
+ ASSERT(virt_addr <= address && address <= virt_addr + phys_size - 1);
+
+ // Ensure we use the right size.
+ if (end_addr < virt_addr + phys_size) {
+ phys_size = end_addr - virt_addr;
+ }
+ }
+
+ // Set the output.
+ out->physical_address = GetInteger(phys_addr);
+ out->virtual_address = GetInteger(virt_addr);
+ out->size = phys_size;
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapIoImpl(KProcessAddress* out, PageLinkedList* page_list,
+ KPhysicalAddress phys_addr, size_t size, KMemoryState state,
+ KMemoryPermission perm) {
+ // Check pre-conditions.
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(Common::IsAligned(GetInteger(phys_addr), PageSize));
+ ASSERT(Common::IsAligned(size, PageSize));
+ ASSERT(size > 0);
+
+ R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress);
+ const size_t num_pages = size / PageSize;
+ const KPhysicalAddress last = phys_addr + size - 1;
+
+ // Get region extents.
+ const KProcessAddress region_start = m_kernel_map_region_start;
+ const size_t region_size = m_kernel_map_region_end - m_kernel_map_region_start;
+ const size_t region_num_pages = region_size / PageSize;
+
+ ASSERT(this->CanContain(region_start, region_size, state));
+
+ // Locate the memory region.
+ const KMemoryRegion* region = KMemoryLayout::Find(m_kernel.MemoryLayout(), phys_addr);
+ R_UNLESS(region != nullptr, ResultInvalidAddress);
+
+ ASSERT(region->Contains(GetInteger(phys_addr)));
+
+ // Ensure that the region is mappable.
+ const bool is_rw = perm == KMemoryPermission::UserReadWrite;
+ while (true) {
+ // Check that the region exists.
+ R_UNLESS(region != nullptr, ResultInvalidAddress);
+
+ // Check the region attributes.
+ R_UNLESS(!region->IsDerivedFrom(KMemoryRegionType_Dram), ResultInvalidAddress);
+ R_UNLESS(!region->HasTypeAttribute(KMemoryRegionAttr_UserReadOnly) || !is_rw,
+ ResultInvalidAddress);
+ R_UNLESS(!region->HasTypeAttribute(KMemoryRegionAttr_NoUserMap), ResultInvalidAddress);
+
+ // Check if we're done.
+ if (GetInteger(last) <= region->GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ region = region->GetNext();
+ };
+
+ // Select an address to map at.
+ KProcessAddress addr = 0;
+ {
+ const size_t alignment = 4_KiB;
+ const KPhysicalAddress aligned_phys =
+ Common::AlignUp(GetInteger(phys_addr), alignment) + alignment - 1;
+ R_UNLESS(aligned_phys > phys_addr, ResultInvalidAddress);
+
+ const KPhysicalAddress last_aligned_paddr =
+ Common::AlignDown(GetInteger(last) + 1, alignment) - 1;
+ R_UNLESS((last_aligned_paddr <= last && aligned_phys <= last_aligned_paddr),
+ ResultInvalidAddress);
+
+ addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0,
+ this->GetNumGuardPages());
+ R_UNLESS(addr != 0, ResultOutOfMemory);
+ }
+
+ // Check that we can map IO here.
+ ASSERT(this->CanContain(addr, size, state));
+ R_ASSERT(this->CheckMemoryState(addr, size, KMemoryState::All, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryAttribute::None));
+
+ // Perform mapping operation.
+ const KPageProperties properties = {perm, state == KMemoryState::IoRegister, false,
+ DisableMergeAttribute::DisableHead};
+ R_TRY(this->Operate(page_list, addr, num_pages, phys_addr, true, properties, OperationType::Map,
+ false));
+
+ // Set the output address.
+ *out = addr;
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapIo(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm) {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Map the io memory.
+ KProcessAddress addr;
+ R_TRY(this->MapIoImpl(std::addressof(addr), updater.GetPageList(), phys_addr, size,
+ KMemoryState::IoRegister, perm));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, size / PageSize,
+ KMemoryState::IoRegister, perm, KMemoryAttribute::Locked,
+ KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // We successfully mapped the pages.
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr,
+ size_t size, Svc::MemoryMapping mapping,
+ Svc::MemoryPermission svc_perm) {
+ const size_t num_pages = size / PageSize;
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), dst_address, size,
+ KMemoryState::All, KMemoryState::None, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform mapping operation.
+ const KMemoryPermission perm = ConvertToKMemoryPermission(svc_perm);
+ const KPageProperties properties = {perm, mapping == Svc::MemoryMapping::IoRegister,
+ mapping == Svc::MemoryMapping::Uncached,
+ DisableMergeAttribute::DisableHead};
+ R_TRY(this->Operate(updater.GetPageList(), dst_address, num_pages, phys_addr, true, properties,
+ OperationType::Map, false));
+
+ // Update the blocks.
+ const auto state =
+ mapping == Svc::MemoryMapping::Memory ? KMemoryState::IoMemory : KMemoryState::IoRegister;
+ m_memory_block_manager.Update(
+ std::addressof(allocator), dst_address, num_pages, state, perm, KMemoryAttribute::Locked,
+ KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None);
+
+ // We successfully mapped the pages.
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr,
+ size_t size, Svc::MemoryMapping mapping) {
+ const size_t num_pages = size / PageSize;
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate the memory state.
+ KMemoryState old_state;
+ KMemoryPermission old_perm;
+ KMemoryAttribute old_attr;
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(
+ std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr),
+ std::addressof(num_allocator_blocks), dst_address, size, KMemoryState::All,
+ mapping == Svc::MemoryMapping::Memory ? KMemoryState::IoMemory : KMemoryState::IoRegister,
+ KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked));
+
+ // Validate that the region being unmapped corresponds to the physical range described.
+ {
+ // Get the impl.
+ auto& impl = this->GetImpl();
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ ASSERT(
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address));
+
+ // Check that the physical region matches.
+ R_UNLESS(next_entry.phys_addr == phys_addr, ResultInvalidMemoryRegion);
+
+ // Iterate.
+ for (size_t checked_size =
+ next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1));
+ checked_size < size; checked_size += next_entry.block_size) {
+ // Continue the traversal.
+ ASSERT(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)));
+
+ // Check that the physical region matches.
+ R_UNLESS(next_entry.phys_addr == phys_addr + checked_size, ResultInvalidMemoryRegion);
+ }
+ }
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // If the region being unmapped is Memory, synchronize.
+ if (mapping == Svc::MemoryMapping::Memory) {
+ // Change the region to be uncached.
+ const KPageProperties properties = {old_perm, false, true, DisableMergeAttribute::None};
+ R_ASSERT(this->Operate(updater.GetPageList(), dst_address, num_pages, 0, false, properties,
+ OperationType::ChangePermissionsAndRefresh, false));
+
+ // Temporarily unlock ourselves, so that other operations can occur while we flush the
+ // region.
+ m_general_lock.Unlock();
+ SCOPE_EXIT({ m_general_lock.Lock(); });
+
+ // Flush the region.
+ R_ASSERT(FlushDataCache(dst_address, size));
+ }
+
+ // Perform the unmap.
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_ASSERT(this->Operate(updater.GetPageList(), dst_address, num_pages, 0, false,
+ unmap_properties, OperationType::Unmap, false));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), dst_address, num_pages,
+ KMemoryState::Free, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapStatic(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm) {
+ ASSERT(Common::IsAligned(GetInteger(phys_addr), PageSize));
+ ASSERT(Common::IsAligned(size, PageSize));
+ ASSERT(size > 0);
+ R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress);
+ const size_t num_pages = size / PageSize;
+ const KPhysicalAddress last = phys_addr + size - 1;
+
+ // Get region extents.
+ const KProcessAddress region_start = this->GetRegionAddress(KMemoryState::Static);
+ const size_t region_size = this->GetRegionSize(KMemoryState::Static);
+ const size_t region_num_pages = region_size / PageSize;
+
+ // Locate the memory region.
+ const KMemoryRegion* region = KMemoryLayout::Find(m_kernel.MemoryLayout(), phys_addr);
+ R_UNLESS(region != nullptr, ResultInvalidAddress);
+
+ ASSERT(region->Contains(GetInteger(phys_addr)));
+ R_UNLESS(GetInteger(last) <= region->GetLastAddress(), ResultInvalidAddress);
+
+ // Check the region attributes.
+ const bool is_rw = perm == KMemoryPermission::UserReadWrite;
+ R_UNLESS(region->IsDerivedFrom(KMemoryRegionType_Dram), ResultInvalidAddress);
+ R_UNLESS(!region->HasTypeAttribute(KMemoryRegionAttr_NoUserMap), ResultInvalidAddress);
+ R_UNLESS(!region->HasTypeAttribute(KMemoryRegionAttr_UserReadOnly) || !is_rw,
+ ResultInvalidAddress);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Select an address to map at.
+ KProcessAddress addr = 0;
+ {
+ const size_t alignment = 4_KiB;
+ const KPhysicalAddress aligned_phys =
+ Common::AlignUp(GetInteger(phys_addr), alignment) + alignment - 1;
+ R_UNLESS(aligned_phys > phys_addr, ResultInvalidAddress);
+
+ const KPhysicalAddress last_aligned_paddr =
+ Common::AlignDown(GetInteger(last) + 1, alignment) - 1;
+ R_UNLESS((last_aligned_paddr <= last && aligned_phys <= last_aligned_paddr),
+ ResultInvalidAddress);
+
+ addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0,
+ this->GetNumGuardPages());
+ R_UNLESS(addr != 0, ResultOutOfMemory);
+ }
+
+ // Check that we can map static here.
+ ASSERT(this->CanContain(addr, size, KMemoryState::Static));
+ R_ASSERT(this->CheckMemoryState(addr, size, KMemoryState::All, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform mapping operation.
+ const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
+ R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, phys_addr, true, properties,
+ OperationType::Map, false));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, KMemoryState::Static,
+ perm, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // We successfully mapped the pages.
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapRegion(KMemoryRegionType region_type, KMemoryPermission perm) {
+ // Get the memory region.
+ const KMemoryRegion* region =
+ m_kernel.MemoryLayout().GetPhysicalMemoryRegionTree().FindFirstDerived(region_type);
+ R_UNLESS(region != nullptr, ResultOutOfRange);
+
+ // Check that the region is valid.
+ ASSERT(region->GetEndAddress() != 0);
+
+ // Map the region.
+ R_TRY_CATCH(this->MapStatic(region->GetAddress(), region->GetSize(), perm)){
+ R_CONVERT(ResultInvalidAddress, ResultOutOfRange)} R_END_TRY_CATCH;
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
+ KPhysicalAddress phys_addr, bool is_pa_valid,
+ KProcessAddress region_start, size_t region_num_pages,
+ KMemoryState state, KMemoryPermission perm) {
+ ASSERT(Common::IsAligned(alignment, PageSize) && alignment >= PageSize);
+
+ // Ensure this is a valid map request.
+ R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state),
+ ResultInvalidCurrentMemory);
+ R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Find a random address to map at.
+ KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment,
+ 0, this->GetNumGuardPages());
+ R_UNLESS(addr != 0, ResultOutOfMemory);
+ ASSERT(Common::IsAligned(GetInteger(addr), alignment));
+ ASSERT(this->CanContain(addr, num_pages * PageSize, state));
+ R_ASSERT(this->CheckMemoryState(
+ addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None, KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform mapping operation.
+ if (is_pa_valid) {
+ const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
+ R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, phys_addr, true, properties,
+ OperationType::Map, false));
+ } else {
+ R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), addr, num_pages, perm));
+ }
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // We successfully mapped the pages.
+ *out_addr = addr;
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapPages(KProcessAddress address, size_t num_pages, KMemoryState state,
+ KMemoryPermission perm) {
+ // Check that the map is in range.
+ const size_t size = num_pages * PageSize;
+ R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Map the pages.
+ R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), address, num_pages, perm));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state) {
+ // Check that the unmap is in range.
+ const size_t size = num_pages * PageSize;
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, state, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform the unmap.
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), address, num_pages, 0, false, unmap_properties,
+ OperationType::Unmap, false));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg,
+ KProcessAddress region_start, size_t region_num_pages,
+ KMemoryState state, KMemoryPermission perm) {
+ ASSERT(!this->IsLockedByCurrentThread());
+
+ // Ensure this is a valid map request.
+ const size_t num_pages = pg.GetNumPages();
+ R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state),
+ ResultInvalidCurrentMemory);
+ R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Find a random address to map at.
+ KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, PageSize,
+ 0, this->GetNumGuardPages());
+ R_UNLESS(addr != 0, ResultOutOfMemory);
+ ASSERT(this->CanContain(addr, num_pages * PageSize, state));
+ R_ASSERT(this->CheckMemoryState(
+ addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None, KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform mapping operation.
+ const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
+ R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // We successfully mapped the pages.
+ *out_addr = addr;
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapPageGroup(KProcessAddress addr, const KPageGroup& pg, KMemoryState state,
+ KMemoryPermission perm) {
+ ASSERT(!this->IsLockedByCurrentThread());
+
+ // Ensure this is a valid map request.
+ const size_t num_pages = pg.GetNumPages();
+ const size_t size = num_pages * PageSize;
+ R_UNLESS(this->CanContain(addr, size, state), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check if state allows us to map.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), addr, size,
+ KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform mapping operation.
+ const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
+ R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // We successfully mapped the pages.
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnmapPageGroup(KProcessAddress address, const KPageGroup& pg,
+ KMemoryState state) {
+ ASSERT(!this->IsLockedByCurrentThread());
+
+ // Ensure this is a valid unmap request.
+ const size_t num_pages = pg.GetNumPages();
+ const size_t size = num_pages * PageSize;
+ R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check if state allows us to unmap.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, state, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Check that the page group is valid.
+ R_UNLESS(this->IsValidPageGroup(pg, address, num_pages), ResultInvalidCurrentMemory);
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Perform unmapping operation.
+ const KPageProperties properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), address, num_pages, 0, false, properties,
+ OperationType::Unmap, false));
+
+ // Update the blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address,
+ size_t num_pages, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr) {
+ // Ensure that the page group isn't null.
+ ASSERT(out != nullptr);
+
+ // Make sure that the region we're mapping is valid for the table.
+ const size_t size = num_pages * PageSize;
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check if state allows us to create the group.
+ R_TRY(this->CheckMemoryState(address, size, state_mask | KMemoryState::FlagReferenceCounted,
+ state | KMemoryState::FlagReferenceCounted, perm_mask, perm,
+ attr_mask, attr));
+
+ // Create a new page group for the region.
+ R_TRY(this->MakePageGroup(*out, address, num_pages));
+
+ // Open a new reference to the pages in the group.
+ out->Open();
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_t size) {
+ // Check that the region is in range.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the memory state.
+ R_TRY(this->CheckMemoryStateContiguous(
+ address, size, KMemoryState::FlagReferenceCounted, KMemoryState::FlagReferenceCounted,
+ KMemoryPermission::UserReadWrite, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Uncached, KMemoryAttribute::None));
+
+ // Get the impl.
+ auto& impl = this->GetImpl();
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid =
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), address);
+ R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ // Iterate.
+ while (tot_size < size) {
+ // Continue the traversal.
+ traverse_valid =
+ impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ // Check that the pages are linearly mapped.
+ R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+
+ // Invalidate the block.
+ if (cur_size > 0) {
+ // NOTE: Nintendo does not check the result of invalidation.
+ InvalidateDataCache(GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size);
+ }
+
+ // Advance.
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we use the right size for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // Check that the last block is linearly mapped.
+ R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+
+ // Invalidate the last block.
+ if (cur_size > 0) {
+ // NOTE: Nintendo does not check the result of invalidation.
+ InvalidateDataCache(GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size) {
+ // Check pre-condition: this is being called on the current process.
+ ASSERT(this == std::addressof(GetCurrentProcess(m_kernel).GetPageTable().GetBasePageTable()));
+
+ // Check that the region is in range.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the memory state.
+ R_TRY(this->CheckMemoryStateContiguous(
+ address, size, KMemoryState::FlagReferenceCounted, KMemoryState::FlagReferenceCounted,
+ KMemoryPermission::UserReadWrite, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Uncached, KMemoryAttribute::None));
+
+ // Invalidate the data cache.
+ R_RETURN(InvalidateDataCache(address, size));
+}
+
+Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddress src_address,
+ size_t size) {
+ // Lightly validate the region is in range.
+ R_UNLESS(this->Contains(src_address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Require that the memory either be user readable or debuggable.
+ const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(
+ src_address, size, KMemoryState::None, KMemoryState::None, KMemoryPermission::UserRead,
+ KMemoryPermission::UserRead, KMemoryAttribute::None, KMemoryAttribute::None));
+ if (!can_read) {
+ const bool can_debug = R_SUCCEEDED(this->CheckMemoryStateContiguous(
+ src_address, size, KMemoryState::FlagCanDebug, KMemoryState::FlagCanDebug,
+ KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
+ R_UNLESS(can_debug, ResultInvalidCurrentMemory);
+ }
+
+ // Get the impl.
+ auto& impl = this->GetImpl();
+ auto& dst_memory = GetCurrentMemory(m_system.Kernel());
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid =
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_address);
+ R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ auto PerformCopy = [&]() -> Result {
+ // Ensure the address is linear mapped.
+ R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+
+ // Copy as much aligned data as we can.
+ if (cur_size >= sizeof(u32)) {
+ const size_t copy_size = Common::AlignDown(cur_size, sizeof(u32));
+ const void* copy_src = GetLinearMappedVirtualPointer(m_kernel, cur_addr);
+ FlushDataCache(copy_src, copy_size);
+ R_UNLESS(dst_memory.WriteBlock(dst_address, copy_src, copy_size), ResultInvalidPointer);
+
+ dst_address += copy_size;
+ cur_addr += copy_size;
+ cur_size -= copy_size;
+ }
+
+ // Copy remaining data.
+ if (cur_size > 0) {
+ const void* copy_src = GetLinearMappedVirtualPointer(m_kernel, cur_addr);
+ FlushDataCache(copy_src, cur_size);
+ R_UNLESS(dst_memory.WriteBlock(dst_address, copy_src, cur_size), ResultInvalidPointer);
+ }
+
+ R_SUCCEED();
+ };
+
+ // Iterate.
+ while (tot_size < size) {
+ // Continue the traversal.
+ traverse_valid =
+ impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ // Perform copy.
+ R_TRY(PerformCopy());
+
+ // Advance.
+ dst_address += cur_size;
+
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we use the right size for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // Perform copy for the last block.
+ R_TRY(PerformCopy());
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAddress src_address,
+ size_t size) {
+ // Lightly validate the region is in range.
+ R_UNLESS(this->Contains(dst_address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Require that the memory either be user writable or debuggable.
+ const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(
+ dst_address, size, KMemoryState::None, KMemoryState::None, KMemoryPermission::UserReadWrite,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::None, KMemoryAttribute::None));
+ if (!can_read) {
+ const bool can_debug = R_SUCCEEDED(this->CheckMemoryStateContiguous(
+ dst_address, size, KMemoryState::FlagCanDebug, KMemoryState::FlagCanDebug,
+ KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::None,
+ KMemoryAttribute::None));
+ R_UNLESS(can_debug, ResultInvalidCurrentMemory);
+ }
+
+ // Get the impl.
+ auto& impl = this->GetImpl();
+ auto& src_memory = GetCurrentMemory(m_system.Kernel());
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid =
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address);
+ R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ auto PerformCopy = [&]() -> Result {
+ // Ensure the address is linear mapped.
+ R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+
+ // Copy as much aligned data as we can.
+ if (cur_size >= sizeof(u32)) {
+ const size_t copy_size = Common::AlignDown(cur_size, sizeof(u32));
+ void* copy_dst = GetLinearMappedVirtualPointer(m_kernel, cur_addr);
+ R_UNLESS(src_memory.ReadBlock(src_address, copy_dst, copy_size),
+ ResultInvalidCurrentMemory);
+
+ StoreDataCache(GetLinearMappedVirtualPointer(m_kernel, cur_addr), copy_size);
+
+ src_address += copy_size;
+ cur_addr += copy_size;
+ cur_size -= copy_size;
+ }
+
+ // Copy remaining data.
+ if (cur_size > 0) {
+ void* copy_dst = GetLinearMappedVirtualPointer(m_kernel, cur_addr);
+ R_UNLESS(src_memory.ReadBlock(src_address, copy_dst, cur_size),
+ ResultInvalidCurrentMemory);
+
+ StoreDataCache(GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size);
+ }
+
+ R_SUCCEED();
+ };
+
+ // Iterate.
+ while (tot_size < size) {
+ // Continue the traversal.
+ traverse_valid =
+ impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ // Perform copy.
+ R_TRY(PerformCopy());
+
+ // Advance.
+ src_address += cur_size;
+
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we use the right size for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // Perform copy for the last block.
+ R_TRY(PerformCopy());
+
+ // Invalidate the instruction cache, as this svc allows modifying executable pages.
+ InvalidateInstructionCache(m_system, dst_address, size);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::ReadIoMemoryImpl(KProcessAddress dst_addr, KPhysicalAddress phys_addr,
+ size_t size, KMemoryState state) {
+ // Check pre-conditions.
+ ASSERT(this->IsLockedByCurrentThread());
+
+ // Determine the mapping extents.
+ const KPhysicalAddress map_start = Common::AlignDown(GetInteger(phys_addr), PageSize);
+ const KPhysicalAddress map_end = Common::AlignUp(GetInteger(phys_addr) + size, PageSize);
+ const size_t map_size = map_end - map_start;
+
+ // Get the memory reference to write into.
+ auto& dst_memory = GetCurrentMemory(m_kernel);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Temporarily map the io memory.
+ KProcessAddress io_addr;
+ R_TRY(this->MapIoImpl(std::addressof(io_addr), updater.GetPageList(), map_start, map_size,
+ state, KMemoryPermission::UserRead));
+
+ // Ensure we unmap the io memory when we're done with it.
+ const KPageProperties unmap_properties =
+ KPageProperties{KMemoryPermission::None, false, false, DisableMergeAttribute::None};
+ SCOPE_EXIT({
+ R_ASSERT(this->Operate(updater.GetPageList(), io_addr, map_size / PageSize, 0, false,
+ unmap_properties, OperationType::Unmap, true));
+ });
+
+ // Read the memory.
+ const KProcessAddress read_addr = io_addr + (GetInteger(phys_addr) & (PageSize - 1));
+ dst_memory.CopyBlock(dst_addr, read_addr, size);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::WriteIoMemoryImpl(KPhysicalAddress phys_addr, KProcessAddress src_addr,
+ size_t size, KMemoryState state) {
+ // Check pre-conditions.
+ ASSERT(this->IsLockedByCurrentThread());
+
+ // Determine the mapping extents.
+ const KPhysicalAddress map_start = Common::AlignDown(GetInteger(phys_addr), PageSize);
+ const KPhysicalAddress map_end = Common::AlignUp(GetInteger(phys_addr) + size, PageSize);
+ const size_t map_size = map_end - map_start;
+
+ // Get the memory reference to read from.
+ auto& src_memory = GetCurrentMemory(m_kernel);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Temporarily map the io memory.
+ KProcessAddress io_addr;
+ R_TRY(this->MapIoImpl(std::addressof(io_addr), updater.GetPageList(), map_start, map_size,
+ state, KMemoryPermission::UserReadWrite));
+
+ // Ensure we unmap the io memory when we're done with it.
+ const KPageProperties unmap_properties =
+ KPageProperties{KMemoryPermission::None, false, false, DisableMergeAttribute::None};
+ SCOPE_EXIT({
+ R_ASSERT(this->Operate(updater.GetPageList(), io_addr, map_size / PageSize, 0, false,
+ unmap_properties, OperationType::Unmap, true));
+ });
+
+ // Write the memory.
+ const KProcessAddress write_addr = io_addr + (GetInteger(phys_addr) & (PageSize - 1));
+ R_UNLESS(src_memory.CopyBlock(write_addr, src_addr, size), ResultInvalidPointer);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::ReadDebugIoMemory(KProcessAddress dst_address, KProcessAddress src_address,
+ size_t size, KMemoryState state) {
+ // Lightly validate the range before doing anything else.
+ R_UNLESS(this->Contains(src_address, size), ResultInvalidCurrentMemory);
+
+ // We need to lock both this table, and the current process's table, so set up some aliases.
+ KPageTableBase& src_page_table = *this;
+ KPageTableBase& dst_page_table = GetCurrentProcess(m_kernel).GetPageTable().GetBasePageTable();
+
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
+
+ // Check that the desired range is readable io memory.
+ R_TRY(this->CheckMemoryStateContiguous(src_address, size, KMemoryState::All, state,
+ KMemoryPermission::UserRead, KMemoryPermission::UserRead,
+ KMemoryAttribute::None, KMemoryAttribute::None));
+
+ // Read the memory.
+ KProcessAddress dst = dst_address;
+ const KProcessAddress last_address = src_address + size - 1;
+ while (src_address <= last_address) {
+ // Get the current physical address.
+ KPhysicalAddress phys_addr;
+ ASSERT(src_page_table.GetPhysicalAddressLocked(std::addressof(phys_addr), src_address));
+
+ // Determine the current read size.
+ const size_t cur_size =
+ std::min<size_t>(last_address - src_address + 1,
+ Common::AlignDown(GetInteger(src_address) + PageSize, PageSize) -
+ GetInteger(src_address));
+
+ // Read.
+ R_TRY(dst_page_table.ReadIoMemoryImpl(dst, phys_addr, cur_size, state));
+
+ // Advance.
+ src_address += cur_size;
+ dst += cur_size;
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::WriteDebugIoMemory(KProcessAddress dst_address, KProcessAddress src_address,
+ size_t size, KMemoryState state) {
+ // Lightly validate the range before doing anything else.
+ R_UNLESS(this->Contains(dst_address, size), ResultInvalidCurrentMemory);
+
+ // We need to lock both this table, and the current process's table, so set up some aliases.
+ KPageTableBase& src_page_table = *this;
+ KPageTableBase& dst_page_table = GetCurrentProcess(m_kernel).GetPageTable().GetBasePageTable();
+
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
+
+ // Check that the desired range is writable io memory.
+ R_TRY(this->CheckMemoryStateContiguous(
+ dst_address, size, KMemoryState::All, state, KMemoryPermission::UserReadWrite,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::None, KMemoryAttribute::None));
+
+ // Read the memory.
+ KProcessAddress src = src_address;
+ const KProcessAddress last_address = dst_address + size - 1;
+ while (dst_address <= last_address) {
+ // Get the current physical address.
+ KPhysicalAddress phys_addr;
+ ASSERT(src_page_table.GetPhysicalAddressLocked(std::addressof(phys_addr), dst_address));
+
+ // Determine the current read size.
+ const size_t cur_size =
+ std::min<size_t>(last_address - dst_address + 1,
+ Common::AlignDown(GetInteger(dst_address) + PageSize, PageSize) -
+ GetInteger(dst_address));
+
+ // Read.
+ R_TRY(dst_page_table.WriteIoMemoryImpl(phys_addr, src, cur_size, state));
+
+ // Advance.
+ dst_address += cur_size;
+ src += cur_size;
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::LockForMapDeviceAddressSpace(bool* out_is_io, KProcessAddress address,
+ size_t size, KMemoryPermission perm,
+ bool is_aligned, bool check_heap) {
+ // Lightly validate the range before doing anything else.
+ const size_t num_pages = size / PageSize;
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the memory state.
+ const KMemoryState test_state =
+ (is_aligned ? KMemoryState::FlagCanAlignedDeviceMap : KMemoryState::FlagCanDeviceMap) |
+ (check_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::None);
+ size_t num_allocator_blocks;
+ KMemoryState old_state;
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), nullptr, nullptr,
+ std::addressof(num_allocator_blocks), address, size, test_state,
+ test_state, perm, perm,
+ KMemoryAttribute::IpcLocked | KMemoryAttribute::Locked,
+ KMemoryAttribute::None, KMemoryAttribute::DeviceShared));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // Update the memory blocks.
+ m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages,
+ &KMemoryBlock::ShareToDevice, KMemoryPermission::None);
+
+ // Set whether the locked memory was io.
+ *out_is_io =
+ static_cast<Svc::MemoryState>(old_state & KMemoryState::Mask) == Svc::MemoryState::Io;
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::LockForUnmapDeviceAddressSpace(KProcessAddress address, size_t size,
+ bool check_heap) {
+ // Lightly validate the range before doing anything else.
+ const size_t num_pages = size / PageSize;
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the memory state.
+ const KMemoryState test_state =
+ KMemoryState::FlagCanDeviceMap |
+ (check_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::None);
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryStateContiguous(
+ std::addressof(num_allocator_blocks), address, size, test_state, test_state,
+ KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // Update the memory blocks.
+ const KMemoryBlockManager::MemoryBlockLockFunction lock_func =
+ m_enable_device_address_space_merge
+ ? &KMemoryBlock::UpdateDeviceDisableMergeStateForShare
+ : &KMemoryBlock::UpdateDeviceDisableMergeStateForShareRight;
+ m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, lock_func,
+ KMemoryPermission::None);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnlockForDeviceAddressSpace(KProcessAddress address, size_t size) {
+ // Lightly validate the range before doing anything else.
+ const size_t num_pages = size / PageSize;
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryStateContiguous(
+ std::addressof(num_allocator_blocks), address, size, KMemoryState::FlagCanDeviceMap,
+ KMemoryState::FlagCanDeviceMap, KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // Update the memory blocks.
+ m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages,
+ &KMemoryBlock::UnshareToDevice, KMemoryPermission::None);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::UnlockForDeviceAddressSpacePartialMap(KProcessAddress address, size_t size) {
+ // Lightly validate the range before doing anything else.
+ const size_t num_pages = size / PageSize;
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check memory state.
+ size_t allocator_num_blocks = 0;
+ R_TRY(this->CheckMemoryStateContiguous(
+ std::addressof(allocator_num_blocks), address, size, KMemoryState::FlagCanDeviceMap,
+ KMemoryState::FlagCanDeviceMap, KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared));
+
+ // Create an update allocator for the region.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, allocator_num_blocks);
+ R_TRY(allocator_result);
+
+ // Update the memory blocks.
+ m_memory_block_manager.UpdateLock(
+ std::addressof(allocator), address, num_pages,
+ m_enable_device_address_space_merge
+ ? &KMemoryBlock::UpdateDeviceDisableMergeStateForUnshare
+ : &KMemoryBlock::UpdateDeviceDisableMergeStateForUnshareRight,
+ KMemoryPermission::None);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::OpenMemoryRangeForMapDeviceAddressSpace(KPageTableBase::MemoryRange* out,
+ KProcessAddress address, size_t size,
+ KMemoryPermission perm,
+ bool is_aligned) {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Get the range.
+ const KMemoryState test_state =
+ (is_aligned ? KMemoryState::FlagCanAlignedDeviceMap : KMemoryState::FlagCanDeviceMap);
+ R_TRY(this->GetContiguousMemoryRangeWithState(
+ out, address, size, test_state, test_state, perm, perm,
+ KMemoryAttribute::IpcLocked | KMemoryAttribute::Locked, KMemoryAttribute::None));
+
+ // We got the range, so open it.
+ out->Open();
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::OpenMemoryRangeForUnmapDeviceAddressSpace(MemoryRange* out,
+ KProcessAddress address,
+ size_t size) {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Get the range.
+ R_TRY(this->GetContiguousMemoryRangeWithState(
+ out, address, size, KMemoryState::FlagCanDeviceMap, KMemoryState::FlagCanDeviceMap,
+ KMemoryPermission::None, KMemoryPermission::None,
+ KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared));
+
+ // We got the range, so open it.
+ out->Open();
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::LockForIpcUserBuffer(KPhysicalAddress* out, KProcessAddress address,
+ size_t size) {
+ R_RETURN(this->LockMemoryAndOpen(
+ nullptr, out, address, size, KMemoryState::FlagCanIpcUserBuffer,
+ KMemoryState::FlagCanIpcUserBuffer, KMemoryPermission::All,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None,
+ static_cast<KMemoryPermission>(KMemoryPermission::NotMapped |
+ KMemoryPermission::KernelReadWrite),
+ KMemoryAttribute::Locked));
+}
+
+Result KPageTableBase::UnlockForIpcUserBuffer(KProcessAddress address, size_t size) {
+ R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanIpcUserBuffer,
+ KMemoryState::FlagCanIpcUserBuffer, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Locked, nullptr));
+}
+
+Result KPageTableBase::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 KPageTableBase::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 KPageTableBase::LockForCodeMemory(KPageGroup* out, KProcessAddress address, size_t size) {
+ R_RETURN(this->LockMemoryAndOpen(
+ out, nullptr, address, size, KMemoryState::FlagCanCodeMemory,
+ KMemoryState::FlagCanCodeMemory, KMemoryPermission::All, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::All, KMemoryAttribute::None,
+ static_cast<KMemoryPermission>(KMemoryPermission::NotMapped |
+ KMemoryPermission::KernelReadWrite),
+ KMemoryAttribute::Locked));
+}
+
+Result KPageTableBase::UnlockForCodeMemory(KProcessAddress address, size_t size,
+ const KPageGroup& pg) {
+ R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanCodeMemory,
+ KMemoryState::FlagCanCodeMemory, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Locked, std::addressof(pg)));
+}
+
+Result KPageTableBase::OpenMemoryRangeForProcessCacheOperation(MemoryRange* out,
+ KProcessAddress address,
+ size_t size) {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Get the range.
+ R_TRY(this->GetContiguousMemoryRangeWithState(
+ out, address, size, KMemoryState::FlagReferenceCounted, KMemoryState::FlagReferenceCounted,
+ KMemoryPermission::UserRead, KMemoryPermission::UserRead, KMemoryAttribute::Uncached,
+ KMemoryAttribute::None));
+
+ // We got the range, so open it.
+ out->Open();
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CopyMemoryFromLinearToUser(
+ KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, KMemoryState src_state_mask,
+ KMemoryState src_state, KMemoryPermission src_test_perm, KMemoryAttribute src_attr_mask,
+ KMemoryAttribute src_attr) {
+ // Lightly validate the range before doing anything else.
+ R_UNLESS(this->Contains(src_addr, size), ResultInvalidCurrentMemory);
+
+ // Get the destination memory reference.
+ auto& dst_memory = GetCurrentMemory(m_kernel);
+
+ // Copy the memory.
+ {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check memory state.
+ R_TRY(this->CheckMemoryStateContiguous(
+ src_addr, size, src_state_mask, src_state, src_test_perm, src_test_perm,
+ src_attr_mask | KMemoryAttribute::Uncached, src_attr));
+
+ auto& impl = this->GetImpl();
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid =
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size =
+ next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ auto PerformCopy = [&]() -> Result {
+ // Ensure the address is linear mapped.
+ R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+
+ // Copy as much aligned data as we can.
+ if (cur_size >= sizeof(u32)) {
+ const size_t copy_size = Common::AlignDown(cur_size, sizeof(u32));
+ R_UNLESS(dst_memory.WriteBlock(dst_addr,
+ GetLinearMappedVirtualPointer(m_kernel, cur_addr),
+ copy_size),
+ ResultInvalidCurrentMemory);
+
+ dst_addr += copy_size;
+ cur_addr += copy_size;
+ cur_size -= copy_size;
+ }
+
+ // Copy remaining data.
+ if (cur_size > 0) {
+ R_UNLESS(dst_memory.WriteBlock(
+ dst_addr, GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size),
+ ResultInvalidCurrentMemory);
+ }
+
+ R_SUCCEED();
+ };
+
+ // Iterate.
+ while (tot_size < size) {
+ // Continue the traversal.
+ traverse_valid =
+ impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ // Perform copy.
+ R_TRY(PerformCopy());
+
+ // Advance.
+ dst_addr += cur_size;
+
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we use the right size for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // Perform copy for the last block.
+ R_TRY(PerformCopy());
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CopyMemoryFromLinearToKernel(
+ void* buffer, size_t size, KProcessAddress src_addr, KMemoryState src_state_mask,
+ KMemoryState src_state, KMemoryPermission src_test_perm, KMemoryAttribute src_attr_mask,
+ KMemoryAttribute src_attr) {
+ // Lightly validate the range before doing anything else.
+ R_UNLESS(this->Contains(src_addr, size), ResultInvalidCurrentMemory);
+
+ // Copy the memory.
+ {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check memory state.
+ R_TRY(this->CheckMemoryStateContiguous(
+ src_addr, size, src_state_mask, src_state, src_test_perm, src_test_perm,
+ src_attr_mask | KMemoryAttribute::Uncached, src_attr));
+
+ auto& impl = this->GetImpl();
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid =
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size =
+ next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ auto PerformCopy = [&]() -> Result {
+ // Ensure the address is linear mapped.
+ R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+
+ // Copy the data.
+ std::memcpy(buffer, GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size);
+
+ R_SUCCEED();
+ };
+
+ // Iterate.
+ while (tot_size < size) {
+ // Continue the traversal.
+ traverse_valid =
+ impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ // Perform copy.
+ R_TRY(PerformCopy());
+
+ // Advance.
+ buffer = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(buffer) + cur_size);
+
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we use the right size for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // Perform copy for the last block.
+ R_TRY(PerformCopy());
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CopyMemoryFromUserToLinear(
+ KProcessAddress dst_addr, size_t size, KMemoryState dst_state_mask, KMemoryState dst_state,
+ KMemoryPermission dst_test_perm, KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr,
+ KProcessAddress src_addr) {
+ // Lightly validate the range before doing anything else.
+ R_UNLESS(this->Contains(dst_addr, size), ResultInvalidCurrentMemory);
+
+ // Get the source memory reference.
+ auto& src_memory = GetCurrentMemory(m_kernel);
+
+ // Copy the memory.
+ {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check memory state.
+ R_TRY(this->CheckMemoryStateContiguous(
+ dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_test_perm,
+ dst_attr_mask | KMemoryAttribute::Uncached, dst_attr));
+
+ auto& impl = this->GetImpl();
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid =
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size =
+ next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ auto PerformCopy = [&]() -> Result {
+ // Ensure the address is linear mapped.
+ R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+
+ // Copy as much aligned data as we can.
+ if (cur_size >= sizeof(u32)) {
+ const size_t copy_size = Common::AlignDown(cur_size, sizeof(u32));
+ R_UNLESS(src_memory.ReadBlock(src_addr,
+ GetLinearMappedVirtualPointer(m_kernel, cur_addr),
+ copy_size),
+ ResultInvalidCurrentMemory);
+ src_addr += copy_size;
+ cur_addr += copy_size;
+ cur_size -= copy_size;
+ }
+
+ // Copy remaining data.
+ if (cur_size > 0) {
+ R_UNLESS(src_memory.ReadBlock(
+ src_addr, GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size),
+ ResultInvalidCurrentMemory);
+ }
+
+ R_SUCCEED();
+ };
+
+ // Iterate.
+ while (tot_size < size) {
+ // Continue the traversal.
+ traverse_valid =
+ impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ // Perform copy.
+ R_TRY(PerformCopy());
+
+ // Advance.
+ src_addr += cur_size;
+
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we use the right size for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // Perform copy for the last block.
+ R_TRY(PerformCopy());
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask,
+ KMemoryState dst_state,
+ KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask,
+ KMemoryAttribute dst_attr, void* buffer) {
+ // Lightly validate the range before doing anything else.
+ R_UNLESS(this->Contains(dst_addr, size), ResultInvalidCurrentMemory);
+
+ // Copy the memory.
+ {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Check memory state.
+ R_TRY(this->CheckMemoryStateContiguous(
+ dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_test_perm,
+ dst_attr_mask | KMemoryAttribute::Uncached, dst_attr));
+
+ auto& impl = this->GetImpl();
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid =
+ impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_addr = next_entry.phys_addr;
+ size_t cur_size =
+ next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1));
+ size_t tot_size = cur_size;
+
+ auto PerformCopy = [&]() -> Result {
+ // Ensure the address is linear mapped.
+ R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory);
+
+ // Copy the data.
+ std::memcpy(GetLinearMappedVirtualPointer(m_kernel, cur_addr), buffer, cur_size);
+
+ R_SUCCEED();
+ };
+
+ // Iterate.
+ while (tot_size < size) {
+ // Continue the traversal.
+ traverse_valid =
+ impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ if (next_entry.phys_addr != (cur_addr + cur_size)) {
+ // Perform copy.
+ R_TRY(PerformCopy());
+
+ // Advance.
+ buffer = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(buffer) + cur_size);
+
+ cur_addr = next_entry.phys_addr;
+ cur_size = next_entry.block_size;
+ } else {
+ cur_size += next_entry.block_size;
+ }
+
+ tot_size += next_entry.block_size;
+ }
+
+ // Ensure we use the right size for the last block.
+ if (tot_size > size) {
+ cur_size -= (tot_size - size);
+ }
+
+ // Perform copy for the last block.
+ R_TRY(PerformCopy());
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CopyMemoryFromHeapToHeap(
+ KPageTableBase& dst_page_table, KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask, KMemoryState dst_state, KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr, KProcessAddress src_addr,
+ KMemoryState src_state_mask, KMemoryState src_state, KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr) {
+ // For convenience, alias this.
+ KPageTableBase& src_page_table = *this;
+
+ // Lightly validate the ranges before doing anything else.
+ R_UNLESS(src_page_table.Contains(src_addr, size), ResultInvalidCurrentMemory);
+ R_UNLESS(dst_page_table.Contains(dst_addr, size), ResultInvalidCurrentMemory);
+
+ // Copy the memory.
+ {
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
+
+ // Check memory state.
+ R_TRY(src_page_table.CheckMemoryStateContiguous(
+ src_addr, size, src_state_mask, src_state, src_test_perm, src_test_perm,
+ src_attr_mask | KMemoryAttribute::Uncached, src_attr));
+ R_TRY(dst_page_table.CheckMemoryStateContiguous(
+ dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_test_perm,
+ dst_attr_mask | KMemoryAttribute::Uncached, dst_attr));
+
+ // Get implementations.
+ auto& src_impl = src_page_table.GetImpl();
+ auto& dst_impl = dst_page_table.GetImpl();
+
+ // Prepare for traversal.
+ TraversalContext src_context;
+ TraversalContext dst_context;
+ TraversalEntry src_next_entry;
+ TraversalEntry dst_next_entry;
+ bool traverse_valid;
+
+ // Begin traversal.
+ traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry),
+ std::addressof(src_context), src_addr);
+ ASSERT(traverse_valid);
+ traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry),
+ std::addressof(dst_context), dst_addr);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_src_block_addr = src_next_entry.phys_addr;
+ KPhysicalAddress cur_dst_block_addr = dst_next_entry.phys_addr;
+ size_t cur_src_size = src_next_entry.block_size -
+ (GetInteger(cur_src_block_addr) & (src_next_entry.block_size - 1));
+ size_t cur_dst_size = dst_next_entry.block_size -
+ (GetInteger(cur_dst_block_addr) & (dst_next_entry.block_size - 1));
+
+ // Adjust the initial block sizes.
+ src_next_entry.block_size = cur_src_size;
+ dst_next_entry.block_size = cur_dst_size;
+
+ // Before we get any crazier, succeed if there's nothing to do.
+ R_SUCCEED_IF(size == 0);
+
+ // We're going to manage dual traversal via an offset against the total size.
+ KPhysicalAddress cur_src_addr = cur_src_block_addr;
+ KPhysicalAddress cur_dst_addr = cur_dst_block_addr;
+ size_t cur_min_size = std::min<size_t>(cur_src_size, cur_dst_size);
+
+ // Iterate.
+ size_t ofs = 0;
+ while (ofs < size) {
+ // Determine how much we can copy this iteration.
+ const size_t cur_copy_size = std::min<size_t>(cur_min_size, size - ofs);
+
+ // If we need to advance the traversals, do so.
+ bool updated_src = false, updated_dst = false, skip_copy = false;
+ if (ofs + cur_copy_size != size) {
+ if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) {
+ // Continue the src traversal.
+ traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry),
+ std::addressof(src_context));
+ ASSERT(traverse_valid);
+
+ // Update source.
+ updated_src = cur_src_addr + cur_min_size != src_next_entry.phys_addr;
+ }
+
+ if (cur_dst_addr + cur_min_size ==
+ dst_next_entry.phys_addr + dst_next_entry.block_size) {
+ // Continue the dst traversal.
+ traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry),
+ std::addressof(dst_context));
+ ASSERT(traverse_valid);
+
+ // Update destination.
+ updated_dst = cur_dst_addr + cur_min_size != dst_next_entry.phys_addr;
+ }
+
+ // If we didn't update either of source/destination, skip the copy this iteration.
+ if (!updated_src && !updated_dst) {
+ skip_copy = true;
+
+ // Update the source block address.
+ cur_src_block_addr = src_next_entry.phys_addr;
+ }
+ }
+
+ // Do the copy, unless we're skipping it.
+ if (!skip_copy) {
+ // We need both ends of the copy to be heap blocks.
+ R_UNLESS(IsHeapPhysicalAddress(cur_src_addr), ResultInvalidCurrentMemory);
+ R_UNLESS(IsHeapPhysicalAddress(cur_dst_addr), ResultInvalidCurrentMemory);
+
+ // Copy the data.
+ std::memcpy(GetHeapVirtualPointer(m_kernel, cur_dst_addr),
+ GetHeapVirtualPointer(m_kernel, cur_src_addr), cur_copy_size);
+
+ // Update.
+ cur_src_block_addr = src_next_entry.phys_addr;
+ cur_src_addr = updated_src ? cur_src_block_addr : cur_src_addr + cur_copy_size;
+ cur_dst_block_addr = dst_next_entry.phys_addr;
+ cur_dst_addr = updated_dst ? cur_dst_block_addr : cur_dst_addr + cur_copy_size;
+
+ // Advance offset.
+ ofs += cur_copy_size;
+ }
+
+ // Update min size.
+ cur_src_size = src_next_entry.block_size;
+ cur_dst_size = dst_next_entry.block_size;
+ cur_min_size = std::min<size_t>(cur_src_block_addr - cur_src_addr + cur_src_size,
+ cur_dst_block_addr - cur_dst_addr + cur_dst_size);
+ }
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination(
+ KPageTableBase& dst_page_table, KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask, KMemoryState dst_state, KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr, KProcessAddress src_addr,
+ KMemoryState src_state_mask, KMemoryState src_state, KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr) {
+ // For convenience, alias this.
+ KPageTableBase& src_page_table = *this;
+
+ // Lightly validate the ranges before doing anything else.
+ R_UNLESS(src_page_table.Contains(src_addr, size), ResultInvalidCurrentMemory);
+ R_UNLESS(dst_page_table.Contains(dst_addr, size), ResultInvalidCurrentMemory);
+
+ // Copy the memory.
+ {
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
+
+ // Check memory state for source.
+ R_TRY(src_page_table.CheckMemoryStateContiguous(
+ src_addr, size, src_state_mask, src_state, src_test_perm, src_test_perm,
+ src_attr_mask | KMemoryAttribute::Uncached, src_attr));
+
+ // Destination state is intentionally unchecked.
+
+ // Get implementations.
+ auto& src_impl = src_page_table.GetImpl();
+ auto& dst_impl = dst_page_table.GetImpl();
+
+ // Prepare for traversal.
+ TraversalContext src_context;
+ TraversalContext dst_context;
+ TraversalEntry src_next_entry;
+ TraversalEntry dst_next_entry;
+ bool traverse_valid;
+
+ // Begin traversal.
+ traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry),
+ std::addressof(src_context), src_addr);
+ ASSERT(traverse_valid);
+ traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry),
+ std::addressof(dst_context), dst_addr);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_src_block_addr = src_next_entry.phys_addr;
+ KPhysicalAddress cur_dst_block_addr = dst_next_entry.phys_addr;
+ size_t cur_src_size = src_next_entry.block_size -
+ (GetInteger(cur_src_block_addr) & (src_next_entry.block_size - 1));
+ size_t cur_dst_size = dst_next_entry.block_size -
+ (GetInteger(cur_dst_block_addr) & (dst_next_entry.block_size - 1));
+
+ // Adjust the initial block sizes.
+ src_next_entry.block_size = cur_src_size;
+ dst_next_entry.block_size = cur_dst_size;
+
+ // Before we get any crazier, succeed if there's nothing to do.
+ R_SUCCEED_IF(size == 0);
+
+ // We're going to manage dual traversal via an offset against the total size.
+ KPhysicalAddress cur_src_addr = cur_src_block_addr;
+ KPhysicalAddress cur_dst_addr = cur_dst_block_addr;
+ size_t cur_min_size = std::min<size_t>(cur_src_size, cur_dst_size);
+
+ // Iterate.
+ size_t ofs = 0;
+ while (ofs < size) {
+ // Determine how much we can copy this iteration.
+ const size_t cur_copy_size = std::min<size_t>(cur_min_size, size - ofs);
+
+ // If we need to advance the traversals, do so.
+ bool updated_src = false, updated_dst = false, skip_copy = false;
+ if (ofs + cur_copy_size != size) {
+ if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) {
+ // Continue the src traversal.
+ traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry),
+ std::addressof(src_context));
+ ASSERT(traverse_valid);
+
+ // Update source.
+ updated_src = cur_src_addr + cur_min_size != src_next_entry.phys_addr;
+ }
+
+ if (cur_dst_addr + cur_min_size ==
+ dst_next_entry.phys_addr + dst_next_entry.block_size) {
+ // Continue the dst traversal.
+ traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry),
+ std::addressof(dst_context));
+ ASSERT(traverse_valid);
+
+ // Update destination.
+ updated_dst = cur_dst_addr + cur_min_size != dst_next_entry.phys_addr;
+ }
+
+ // If we didn't update either of source/destination, skip the copy this iteration.
+ if (!updated_src && !updated_dst) {
+ skip_copy = true;
+
+ // Update the source block address.
+ cur_src_block_addr = src_next_entry.phys_addr;
+ }
+ }
+
+ // Do the copy, unless we're skipping it.
+ if (!skip_copy) {
+ // We need both ends of the copy to be heap blocks.
+ R_UNLESS(IsHeapPhysicalAddress(cur_src_addr), ResultInvalidCurrentMemory);
+ R_UNLESS(IsHeapPhysicalAddress(cur_dst_addr), ResultInvalidCurrentMemory);
+
+ // Copy the data.
+ std::memcpy(GetHeapVirtualPointer(m_kernel, cur_dst_addr),
+ GetHeapVirtualPointer(m_kernel, cur_src_addr), cur_copy_size);
+
+ // Update.
+ cur_src_block_addr = src_next_entry.phys_addr;
+ cur_src_addr = updated_src ? cur_src_block_addr : cur_src_addr + cur_copy_size;
+ cur_dst_block_addr = dst_next_entry.phys_addr;
+ cur_dst_addr = updated_dst ? cur_dst_block_addr : cur_dst_addr + cur_copy_size;
+
+ // Advance offset.
+ ofs += cur_copy_size;
+ }
+
+ // Update min size.
+ cur_src_size = src_next_entry.block_size;
+ cur_dst_size = dst_next_entry.block_size;
+ cur_min_size = std::min<size_t>(cur_src_block_addr - cur_src_addr + cur_src_size,
+ cur_dst_block_addr - cur_dst_addr + cur_dst_size);
+ }
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::SetupForIpcClient(PageLinkedList* page_list, size_t* out_blocks_needed,
+ KProcessAddress address, size_t size,
+ KMemoryPermission test_perm, KMemoryState dst_state) {
+ // Validate pre-conditions.
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(test_perm == KMemoryPermission::UserReadWrite ||
+ test_perm == KMemoryPermission::UserRead);
+
+ // Check that the address is in range.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Get the source permission.
+ const auto src_perm = static_cast<KMemoryPermission>(
+ (test_perm == KMemoryPermission::UserReadWrite)
+ ? KMemoryPermission::KernelReadWrite | KMemoryPermission::NotMapped
+ : KMemoryPermission::UserRead);
+
+ // Get aligned extents.
+ const KProcessAddress aligned_src_start = Common::AlignDown(GetInteger(address), PageSize);
+ const KProcessAddress aligned_src_end = Common::AlignUp(GetInteger(address) + size, PageSize);
+ const KProcessAddress mapping_src_start = Common::AlignUp(GetInteger(address), PageSize);
+ const KProcessAddress mapping_src_end = Common::AlignDown(GetInteger(address) + size, PageSize);
+
+ const auto aligned_src_last = GetInteger(aligned_src_end) - 1;
+ const auto mapping_src_last = GetInteger(mapping_src_end) - 1;
+
+ // Get the test state and attribute mask.
+ KMemoryState test_state;
+ KMemoryAttribute test_attr_mask;
+ switch (dst_state) {
+ case KMemoryState::Ipc:
+ test_state = KMemoryState::FlagCanUseIpc;
+ test_attr_mask =
+ KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonSecureIpc:
+ test_state = KMemoryState::FlagCanUseNonSecureIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonDeviceIpc:
+ test_state = KMemoryState::FlagCanUseNonDeviceIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ default:
+ R_THROW(ResultInvalidCombination);
+ }
+
+ // Ensure that on failure, we roll back appropriately.
+ size_t mapped_size = 0;
+ ON_RESULT_FAILURE {
+ if (mapped_size > 0) {
+ this->CleanupForIpcClientOnServerSetupFailure(page_list, mapping_src_start, mapped_size,
+ src_perm);
+ }
+ };
+
+ size_t blocks_needed = 0;
+
+ // Iterate, mapping as needed.
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(aligned_src_start);
+ while (true) {
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Validate the current block.
+ R_TRY(this->CheckMemoryState(info, test_state, test_state, test_perm, test_perm,
+ test_attr_mask, KMemoryAttribute::None));
+
+ if (mapping_src_start < mapping_src_end &&
+ GetInteger(mapping_src_start) < info.GetEndAddress() &&
+ info.GetAddress() < GetInteger(mapping_src_end)) {
+ const auto cur_start = info.GetAddress() >= GetInteger(mapping_src_start)
+ ? info.GetAddress()
+ : GetInteger(mapping_src_start);
+ const auto cur_end = mapping_src_last >= info.GetLastAddress()
+ ? info.GetEndAddress()
+ : GetInteger(mapping_src_end);
+ const size_t cur_size = cur_end - cur_start;
+
+ if (info.GetAddress() < GetInteger(mapping_src_start)) {
+ ++blocks_needed;
+ }
+ if (mapping_src_last < info.GetLastAddress()) {
+ ++blocks_needed;
+ }
+
+ // Set the permissions on the block, if we need to.
+ if ((info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != src_perm) {
+ const DisableMergeAttribute head_body_attr =
+ (GetInteger(mapping_src_start) >= info.GetAddress())
+ ? DisableMergeAttribute::DisableHeadAndBody
+ : DisableMergeAttribute::None;
+ const DisableMergeAttribute tail_attr = (cur_end == GetInteger(mapping_src_end))
+ ? DisableMergeAttribute::DisableTail
+ : DisableMergeAttribute::None;
+ const KPageProperties properties = {
+ src_perm, false, false,
+ static_cast<DisableMergeAttribute>(head_body_attr | tail_attr)};
+ R_TRY(this->Operate(page_list, cur_start, cur_size / PageSize, 0, false, properties,
+ OperationType::ChangePermissions, false));
+ }
+
+ // Note that we mapped this part.
+ mapped_size += cur_size;
+ }
+
+ // If the block is at the end, we're done.
+ if (aligned_src_last <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
+ ASSERT(it != m_memory_block_manager.end());
+ }
+
+ if (out_blocks_needed != nullptr) {
+ ASSERT(blocks_needed <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
+ *out_blocks_needed = blocks_needed;
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
+ KProcessAddress src_addr, KMemoryPermission test_perm,
+ KMemoryState dst_state, KPageTableBase& src_page_table,
+ bool send) {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(src_page_table.IsLockedByCurrentThread());
+
+ // Check that we can theoretically map.
+ const KProcessAddress region_start = m_alias_region_start;
+ const size_t region_size = m_alias_region_end - m_alias_region_start;
+ R_UNLESS(size < region_size, ResultOutOfAddressSpace);
+
+ // Get aligned source extents.
+ const KProcessAddress src_start = src_addr;
+ const KProcessAddress src_end = src_addr + size;
+ const KProcessAddress aligned_src_start = Common::AlignDown(GetInteger(src_start), PageSize);
+ const KProcessAddress aligned_src_end = Common::AlignUp(GetInteger(src_start) + size, PageSize);
+ const KProcessAddress mapping_src_start = Common::AlignUp(GetInteger(src_start), PageSize);
+ const KProcessAddress mapping_src_end =
+ Common::AlignDown(GetInteger(src_start) + size, PageSize);
+ const size_t aligned_src_size = aligned_src_end - aligned_src_start;
+ const size_t mapping_src_size =
+ (mapping_src_start < mapping_src_end) ? (mapping_src_end - mapping_src_start) : 0;
+
+ // Select a random address to map at.
+ KProcessAddress dst_addr = 0;
+ {
+ const size_t alignment = 4_KiB;
+ const size_t offset = GetInteger(aligned_src_start) & (alignment - 1);
+
+ dst_addr =
+ this->FindFreeArea(region_start, region_size / PageSize, aligned_src_size / PageSize,
+ alignment, offset, this->GetNumGuardPages());
+ R_UNLESS(dst_addr != 0, ResultOutOfAddressSpace);
+ }
+
+ // Check that we can perform the operation we're about to perform.
+ ASSERT(this->CanContain(dst_addr, aligned_src_size, dst_state));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Reserve space for any partial pages we allocate.
+ const size_t unmapped_size = aligned_src_size - mapping_src_size;
+ KScopedResourceReservation memory_reservation(
+ m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, unmapped_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+
+ // Ensure that we manage page references correctly.
+ KPhysicalAddress start_partial_page = 0;
+ KPhysicalAddress end_partial_page = 0;
+ KProcessAddress cur_mapped_addr = dst_addr;
+
+ // If the partial pages are mapped, an extra reference will have been opened. Otherwise, they'll
+ // free on scope exit.
+ SCOPE_EXIT({
+ if (start_partial_page != 0) {
+ m_kernel.MemoryManager().Close(start_partial_page, 1);
+ }
+ if (end_partial_page != 0) {
+ m_kernel.MemoryManager().Close(end_partial_page, 1);
+ }
+ });
+
+ ON_RESULT_FAILURE {
+ if (cur_mapped_addr != dst_addr) {
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_ASSERT(this->Operate(updater.GetPageList(), dst_addr,
+ (cur_mapped_addr - dst_addr) / PageSize, 0, false,
+ unmap_properties, OperationType::Unmap, true));
+ }
+ };
+
+ // Allocate the start page as needed.
+ if (aligned_src_start < mapping_src_start) {
+ start_partial_page =
+ m_kernel.MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option);
+ R_UNLESS(start_partial_page != 0, ResultOutOfMemory);
+ }
+
+ // Allocate the end page as needed.
+ if (mapping_src_end < aligned_src_end &&
+ (aligned_src_start < mapping_src_end || aligned_src_start == mapping_src_start)) {
+ end_partial_page =
+ m_kernel.MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option);
+ R_UNLESS(end_partial_page != 0, ResultOutOfMemory);
+ }
+
+ // Get the implementation.
+ auto& src_impl = src_page_table.GetImpl();
+
+ // Get the fill value for partial pages.
+ const auto fill_val = m_ipc_fill_value;
+
+ // Begin traversal.
+ TraversalContext context;
+ TraversalEntry next_entry;
+ bool traverse_valid = src_impl.BeginTraversal(std::addressof(next_entry),
+ std::addressof(context), aligned_src_start);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ KPhysicalAddress cur_block_addr = next_entry.phys_addr;
+ size_t cur_block_size =
+ next_entry.block_size - (GetInteger(cur_block_addr) & (next_entry.block_size - 1));
+ size_t tot_block_size = cur_block_size;
+
+ // Map the start page, if we have one.
+ if (start_partial_page != 0) {
+ // Ensure the page holds correct data.
+ u8* const start_partial_virt = GetHeapVirtualPointer(m_kernel, start_partial_page);
+ if (send) {
+ const size_t partial_offset = src_start - aligned_src_start;
+ size_t copy_size, clear_size;
+ if (src_end < mapping_src_start) {
+ copy_size = size;
+ clear_size = mapping_src_start - src_end;
+ } else {
+ copy_size = mapping_src_start - src_start;
+ clear_size = 0;
+ }
+
+ std::memset(start_partial_virt, fill_val, partial_offset);
+ std::memcpy(start_partial_virt + partial_offset,
+ GetHeapVirtualPointer(m_kernel, cur_block_addr) + partial_offset,
+ copy_size);
+ if (clear_size > 0) {
+ std::memset(start_partial_virt + partial_offset + copy_size, fill_val, clear_size);
+ }
+ } else {
+ std::memset(start_partial_virt, fill_val, PageSize);
+ }
+
+ // Map the page.
+ const KPageProperties start_map_properties = {test_perm, false, false,
+ DisableMergeAttribute::DisableHead};
+ R_TRY(this->Operate(updater.GetPageList(), cur_mapped_addr, 1, start_partial_page, true,
+ start_map_properties, OperationType::Map, false));
+
+ // Update tracking extents.
+ cur_mapped_addr += PageSize;
+ cur_block_addr += PageSize;
+ cur_block_size -= PageSize;
+
+ // If the block's size was one page, we may need to continue traversal.
+ if (cur_block_size == 0 && aligned_src_size > PageSize) {
+ traverse_valid =
+ src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ cur_block_addr = next_entry.phys_addr;
+ cur_block_size = next_entry.block_size;
+ tot_block_size += next_entry.block_size;
+ }
+ }
+
+ // Map the remaining pages.
+ while (aligned_src_start + tot_block_size < mapping_src_end) {
+ // Continue the traversal.
+ traverse_valid =
+ src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ // Process the block.
+ if (next_entry.phys_addr != cur_block_addr + cur_block_size) {
+ // Map the block we've been processing so far.
+ const KPageProperties map_properties = {test_perm, false, false,
+ (cur_mapped_addr == dst_addr)
+ ? DisableMergeAttribute::DisableHead
+ : DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), cur_mapped_addr, cur_block_size / PageSize,
+ cur_block_addr, true, map_properties, OperationType::Map, false));
+
+ // Update tracking extents.
+ cur_mapped_addr += cur_block_size;
+ cur_block_addr = next_entry.phys_addr;
+ cur_block_size = next_entry.block_size;
+ } else {
+ cur_block_size += next_entry.block_size;
+ }
+ tot_block_size += next_entry.block_size;
+ }
+
+ // Handle the last direct-mapped page.
+ if (const KProcessAddress mapped_block_end =
+ aligned_src_start + tot_block_size - cur_block_size;
+ mapped_block_end < mapping_src_end) {
+ const size_t last_block_size = mapping_src_end - mapped_block_end;
+
+ // Map the last block.
+ const KPageProperties map_properties = {test_perm, false, false,
+ (cur_mapped_addr == dst_addr)
+ ? DisableMergeAttribute::DisableHead
+ : DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), cur_mapped_addr, last_block_size / PageSize,
+ cur_block_addr, true, map_properties, OperationType::Map, false));
+
+ // Update tracking extents.
+ cur_mapped_addr += last_block_size;
+ cur_block_addr += last_block_size;
+ if (mapped_block_end + cur_block_size < aligned_src_end &&
+ cur_block_size == last_block_size) {
+ traverse_valid =
+ src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+ ASSERT(traverse_valid);
+
+ cur_block_addr = next_entry.phys_addr;
+ }
+ }
+
+ // Map the end page, if we have one.
+ if (end_partial_page != 0) {
+ // Ensure the page holds correct data.
+ u8* const end_partial_virt = GetHeapVirtualPointer(m_kernel, end_partial_page);
+ if (send) {
+ const size_t copy_size = src_end - mapping_src_end;
+ std::memcpy(end_partial_virt, GetHeapVirtualPointer(m_kernel, cur_block_addr),
+ copy_size);
+ std::memset(end_partial_virt + copy_size, fill_val, PageSize - copy_size);
+ } else {
+ std::memset(end_partial_virt, fill_val, PageSize);
+ }
+
+ // Map the page.
+ const KPageProperties map_properties = {test_perm, false, false,
+ (cur_mapped_addr == dst_addr)
+ ? DisableMergeAttribute::DisableHead
+ : DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), cur_mapped_addr, 1, end_partial_page, true,
+ map_properties, OperationType::Map, false));
+ }
+
+ // Update memory blocks to reflect our changes
+ m_memory_block_manager.Update(std::addressof(allocator), dst_addr, aligned_src_size / PageSize,
+ dst_state, test_perm, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // Set the output address.
+ *out_addr = dst_addr + (src_start - aligned_src_start);
+
+ // We succeeded.
+ memory_reservation.Commit();
+ R_SUCCEED();
+}
+
+Result KPageTableBase::SetupForIpc(KProcessAddress* out_dst_addr, size_t size,
+ KProcessAddress src_addr, KPageTableBase& src_page_table,
+ KMemoryPermission test_perm, KMemoryState dst_state, bool send) {
+ // For convenience, alias this.
+ KPageTableBase& dst_page_table = *this;
+
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(std::addressof(src_page_table));
+
+ // Perform client setup.
+ size_t num_allocator_blocks;
+ R_TRY(src_page_table.SetupForIpcClient(updater.GetPageList(),
+ std::addressof(num_allocator_blocks), src_addr, size,
+ test_perm, dst_state));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ src_page_table.m_memory_block_slab_manager,
+ num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // Get the mapped extents.
+ const KProcessAddress src_map_start = Common::AlignUp(GetInteger(src_addr), PageSize);
+ const KProcessAddress src_map_end = Common::AlignDown(GetInteger(src_addr) + size, PageSize);
+ const size_t src_map_size = src_map_end - src_map_start;
+
+ // Ensure that we clean up appropriately if we fail after this.
+ const auto src_perm = static_cast<KMemoryPermission>(
+ (test_perm == KMemoryPermission::UserReadWrite)
+ ? KMemoryPermission::KernelReadWrite | KMemoryPermission::NotMapped
+ : KMemoryPermission::UserRead);
+ ON_RESULT_FAILURE {
+ if (src_map_end > src_map_start) {
+ src_page_table.CleanupForIpcClientOnServerSetupFailure(
+ updater.GetPageList(), src_map_start, src_map_size, src_perm);
+ }
+ };
+
+ // Perform server setup.
+ R_TRY(dst_page_table.SetupForIpcServer(out_dst_addr, size, src_addr, test_perm, dst_state,
+ src_page_table, send));
+
+ // If anything was mapped, ipc-lock the pages.
+ if (src_map_start < src_map_end) {
+ // Get the source permission.
+ src_page_table.m_memory_block_manager.UpdateLock(std::addressof(allocator), src_map_start,
+ (src_map_end - src_map_start) / PageSize,
+ &KMemoryBlock::LockForIpc, src_perm);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CleanupForIpcServer(KProcessAddress address, size_t size,
+ KMemoryState dst_state) {
+ // Validate the address.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, dst_state, KMemoryPermission::UserRead,
+ KMemoryPermission::UserRead, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Get aligned extents.
+ const KProcessAddress aligned_start = Common::AlignDown(GetInteger(address), PageSize);
+ const KProcessAddress aligned_end = Common::AlignUp(GetInteger(address) + size, PageSize);
+ const size_t aligned_size = aligned_end - aligned_start;
+ const size_t aligned_num_pages = aligned_size / PageSize;
+
+ // Unmap the pages.
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), aligned_start, aligned_num_pages, 0, false,
+ unmap_properties, OperationType::Unmap, false));
+
+ // Update memory blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), aligned_start, aligned_num_pages,
+ KMemoryState::None, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal);
+
+ // Release from the resource limit as relevant.
+ const KProcessAddress mapping_start = Common::AlignUp(GetInteger(address), PageSize);
+ const KProcessAddress mapping_end = Common::AlignDown(GetInteger(address) + size, PageSize);
+ const size_t mapping_size = (mapping_start < mapping_end) ? mapping_end - mapping_start : 0;
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
+ aligned_size - mapping_size);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::CleanupForIpcClient(KProcessAddress address, size_t size,
+ KMemoryState dst_state) {
+ // Validate the address.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Get aligned source extents.
+ const KProcessAddress mapping_start = Common::AlignUp(GetInteger(address), PageSize);
+ const KProcessAddress mapping_end = Common::AlignDown(GetInteger(address) + size, PageSize);
+ const KProcessAddress mapping_last = mapping_end - 1;
+ const size_t mapping_size = (mapping_start < mapping_end) ? (mapping_end - mapping_start) : 0;
+
+ // If nothing was mapped, we're actually done immediately.
+ R_SUCCEED_IF(mapping_size == 0);
+
+ // Get the test state and attribute mask.
+ KMemoryState test_state;
+ KMemoryAttribute test_attr_mask;
+ switch (dst_state) {
+ case KMemoryState::Ipc:
+ test_state = KMemoryState::FlagCanUseIpc;
+ test_attr_mask =
+ KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonSecureIpc:
+ test_state = KMemoryState::FlagCanUseNonSecureIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonDeviceIpc:
+ test_state = KMemoryState::FlagCanUseNonDeviceIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ default:
+ R_THROW(ResultInvalidCombination);
+ }
+
+ // Lock the table.
+ // NOTE: Nintendo does this *after* creating the updater below, but this does not follow
+ // convention elsewhere in KPageTableBase.
+ KScopedLightLock lk(m_general_lock);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Ensure that on failure, we roll back appropriately.
+ size_t mapped_size = 0;
+ ON_RESULT_FAILURE {
+ if (mapped_size > 0) {
+ // Determine where the mapping ends.
+ const auto mapped_end = GetInteger(mapping_start) + mapped_size;
+ const auto mapped_last = mapped_end - 1;
+
+ // Get current and next iterators.
+ KMemoryBlockManager::const_iterator start_it =
+ m_memory_block_manager.FindIterator(mapping_start);
+ KMemoryBlockManager::const_iterator next_it = start_it;
+ ++next_it;
+
+ // Get the current block info.
+ KMemoryInfo cur_info = start_it->GetMemoryInfo();
+
+ // Create tracking variables.
+ KProcessAddress cur_address = cur_info.GetAddress();
+ size_t cur_size = cur_info.GetSize();
+ bool cur_perm_eq = cur_info.GetPermission() == cur_info.GetOriginalPermission();
+ bool cur_needs_set_perm = !cur_perm_eq && cur_info.GetIpcLockCount() == 1;
+ bool first = cur_info.GetIpcDisableMergeCount() == 1 &&
+ False(cur_info.GetDisableMergeAttribute() &
+ KMemoryBlockDisableMergeAttribute::Locked);
+
+ while ((GetInteger(cur_address) + cur_size - 1) < mapped_last) {
+ // Check that we have a next block.
+ ASSERT(next_it != m_memory_block_manager.end());
+
+ // Get the next info.
+ const KMemoryInfo next_info = next_it->GetMemoryInfo();
+
+ // Check if we can consolidate the next block's permission set with the current one.
+ const bool next_perm_eq =
+ next_info.GetPermission() == next_info.GetOriginalPermission();
+ const bool next_needs_set_perm = !next_perm_eq && next_info.GetIpcLockCount() == 1;
+ if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm &&
+ cur_info.GetOriginalPermission() == next_info.GetOriginalPermission()) {
+ // We can consolidate the reprotection for the current and next block into a
+ // single call.
+ cur_size += next_info.GetSize();
+ } else {
+ // We have to operate on the current block.
+ if ((cur_needs_set_perm || first) && !cur_perm_eq) {
+ const KPageProperties properties = {
+ cur_info.GetPermission(), false, false,
+ first ? DisableMergeAttribute::EnableAndMergeHeadBodyTail
+ : DisableMergeAttribute::None};
+ R_ASSERT(this->Operate(updater.GetPageList(), cur_address,
+ cur_size / PageSize, 0, false, properties,
+ OperationType::ChangePermissions, true));
+ }
+
+ // Advance.
+ cur_address = next_info.GetAddress();
+ cur_size = next_info.GetSize();
+ first = false;
+ }
+
+ // Advance.
+ cur_info = next_info;
+ cur_perm_eq = next_perm_eq;
+ cur_needs_set_perm = next_needs_set_perm;
+ ++next_it;
+ }
+
+ // Process the last block.
+ if ((first || cur_needs_set_perm) && !cur_perm_eq) {
+ const KPageProperties properties = {
+ cur_info.GetPermission(), false, false,
+ first ? DisableMergeAttribute::EnableAndMergeHeadBodyTail
+ : DisableMergeAttribute::None};
+ R_ASSERT(this->Operate(updater.GetPageList(), cur_address, cur_size / PageSize, 0,
+ false, properties, OperationType::ChangePermissions, true));
+ }
+ }
+ };
+
+ // Iterate, reprotecting as needed.
+ {
+ // Get current and next iterators.
+ KMemoryBlockManager::const_iterator start_it =
+ m_memory_block_manager.FindIterator(mapping_start);
+ KMemoryBlockManager::const_iterator next_it = start_it;
+ ++next_it;
+
+ // Validate the current block.
+ KMemoryInfo cur_info = start_it->GetMemoryInfo();
+ R_ASSERT(this->CheckMemoryState(
+ cur_info, test_state, test_state, KMemoryPermission::None, KMemoryPermission::None,
+ test_attr_mask | KMemoryAttribute::IpcLocked, KMemoryAttribute::IpcLocked));
+
+ // Create tracking variables.
+ KProcessAddress cur_address = cur_info.GetAddress();
+ size_t cur_size = cur_info.GetSize();
+ bool cur_perm_eq = cur_info.GetPermission() == cur_info.GetOriginalPermission();
+ bool cur_needs_set_perm = !cur_perm_eq && cur_info.GetIpcLockCount() == 1;
+ bool first =
+ cur_info.GetIpcDisableMergeCount() == 1 &&
+ False(cur_info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Locked);
+
+ while ((cur_address + cur_size - 1) < mapping_last) {
+ // Check that we have a next block.
+ ASSERT(next_it != m_memory_block_manager.end());
+
+ // Get the next info.
+ const KMemoryInfo next_info = next_it->GetMemoryInfo();
+
+ // Validate the next block.
+ R_ASSERT(this->CheckMemoryState(
+ next_info, test_state, test_state, KMemoryPermission::None, KMemoryPermission::None,
+ test_attr_mask | KMemoryAttribute::IpcLocked, KMemoryAttribute::IpcLocked));
+
+ // Check if we can consolidate the next block's permission set with the current one.
+ const bool next_perm_eq =
+ next_info.GetPermission() == next_info.GetOriginalPermission();
+ const bool next_needs_set_perm = !next_perm_eq && next_info.GetIpcLockCount() == 1;
+ if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm &&
+ cur_info.GetOriginalPermission() == next_info.GetOriginalPermission()) {
+ // We can consolidate the reprotection for the current and next block into a single
+ // call.
+ cur_size += next_info.GetSize();
+ } else {
+ // We have to operate on the current block.
+ if ((cur_needs_set_perm || first) && !cur_perm_eq) {
+ const KPageProperties properties = {
+ cur_needs_set_perm ? cur_info.GetOriginalPermission()
+ : cur_info.GetPermission(),
+ false, false,
+ first ? DisableMergeAttribute::EnableHeadAndBody
+ : DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), cur_address, cur_size / PageSize, 0,
+ false, properties, OperationType::ChangePermissions,
+ false));
+ }
+
+ // Mark that we mapped the block.
+ mapped_size += cur_size;
+
+ // Advance.
+ cur_address = next_info.GetAddress();
+ cur_size = next_info.GetSize();
+ first = false;
+ }
+
+ // Advance.
+ cur_info = next_info;
+ cur_perm_eq = next_perm_eq;
+ cur_needs_set_perm = next_needs_set_perm;
+ ++next_it;
+ }
+
+ // Process the last block.
+ const auto lock_count =
+ cur_info.GetIpcLockCount() +
+ (next_it != m_memory_block_manager.end()
+ ? (next_it->GetIpcDisableMergeCount() - next_it->GetIpcLockCount())
+ : 0);
+ if ((first || cur_needs_set_perm || (lock_count == 1)) && !cur_perm_eq) {
+ const DisableMergeAttribute head_body_attr =
+ first ? DisableMergeAttribute::EnableHeadAndBody : DisableMergeAttribute::None;
+ const DisableMergeAttribute tail_attr =
+ lock_count == 1 ? DisableMergeAttribute::EnableTail : DisableMergeAttribute::None;
+ const KPageProperties properties = {
+ cur_needs_set_perm ? cur_info.GetOriginalPermission() : cur_info.GetPermission(),
+ false, false, static_cast<DisableMergeAttribute>(head_body_attr | tail_attr)};
+ R_TRY(this->Operate(updater.GetPageList(), cur_address, cur_size / PageSize, 0, false,
+ properties, OperationType::ChangePermissions, false));
+ }
+ }
+
+ // Create an update allocator.
+ // NOTE: Guaranteed zero blocks needed here.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, 0);
+ R_TRY(allocator_result);
+
+ // Unlock the pages.
+ m_memory_block_manager.UpdateLock(std::addressof(allocator), mapping_start,
+ mapping_size / PageSize, &KMemoryBlock::UnlockForIpc,
+ KMemoryPermission::None);
+
+ R_SUCCEED();
+}
+
+void KPageTableBase::CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list,
+ KProcessAddress address, size_t size,
+ KMemoryPermission prot_perm) {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(Common::IsAligned(GetInteger(address), PageSize));
+ ASSERT(Common::IsAligned(size, PageSize));
+
+ // Get the mapped extents.
+ const KProcessAddress src_map_start = address;
+ const KProcessAddress src_map_end = address + size;
+ const KProcessAddress src_map_last = src_map_end - 1;
+
+ // This function is only invoked when there's something to do.
+ ASSERT(src_map_end > src_map_start);
+
+ // Iterate over blocks, fixing permissions.
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(address);
+ while (true) {
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ const auto cur_start = info.GetAddress() >= GetInteger(src_map_start)
+ ? info.GetAddress()
+ : GetInteger(src_map_start);
+ const auto cur_end =
+ src_map_last <= info.GetLastAddress() ? src_map_end : info.GetEndAddress();
+
+ // If we can, fix the protections on the block.
+ if ((info.GetIpcLockCount() == 0 &&
+ (info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm) ||
+ (info.GetIpcLockCount() != 0 &&
+ (info.GetOriginalPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm)) {
+ // Check if we actually need to fix the protections on the block.
+ if (cur_end == src_map_end || info.GetAddress() <= GetInteger(src_map_start) ||
+ (info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm) {
+ const bool start_nc = (info.GetAddress() == GetInteger(src_map_start))
+ ? (False(info.GetDisableMergeAttribute() &
+ (KMemoryBlockDisableMergeAttribute::Locked |
+ KMemoryBlockDisableMergeAttribute::IpcLeft)))
+ : info.GetAddress() <= GetInteger(src_map_start);
+
+ const DisableMergeAttribute head_body_attr =
+ start_nc ? DisableMergeAttribute::EnableHeadAndBody
+ : DisableMergeAttribute::None;
+ DisableMergeAttribute tail_attr;
+ if (cur_end == src_map_end && info.GetEndAddress() == src_map_end) {
+ auto next_it = it;
+ ++next_it;
+
+ const auto lock_count =
+ info.GetIpcLockCount() +
+ (next_it != m_memory_block_manager.end()
+ ? (next_it->GetIpcDisableMergeCount() - next_it->GetIpcLockCount())
+ : 0);
+ tail_attr = lock_count == 0 ? DisableMergeAttribute::EnableTail
+ : DisableMergeAttribute::None;
+ } else {
+ tail_attr = DisableMergeAttribute::None;
+ }
+
+ const KPageProperties properties = {
+ info.GetPermission(), false, false,
+ static_cast<DisableMergeAttribute>(head_body_attr | tail_attr)};
+ R_ASSERT(this->Operate(page_list, cur_start, (cur_end - cur_start) / PageSize, 0,
+ false, properties, OperationType::ChangePermissions, true));
+ }
+ }
+
+ // If we're past the end of the region, we're done.
+ if (src_map_last <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
+ ASSERT(it != m_memory_block_manager.end());
+ }
+}
+
+Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) {
+ // Lock the physical memory lock.
+ KScopedLightLock phys_lk(m_map_physical_memory_lock);
+
+ // Calculate the last address for convenience.
+ const KProcessAddress last_address = address + size - 1;
+
+ // Define iteration variables.
+ KProcessAddress cur_address;
+ size_t mapped_size;
+
+ // The entire mapping process can be retried.
+ while (true) {
+ // Check if the memory is already mapped.
+ {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Iterate over the memory.
+ cur_address = address;
+ mapped_size = 0;
+
+ auto it = m_memory_block_manager.FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != m_memory_block_manager.end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ if (info.GetState() != KMemoryState::Free) {
+ mapped_size += (last_address + 1 - cur_address);
+ }
+ break;
+ }
+
+ // Track the memory if it's mapped.
+ if (info.GetState() != KMemoryState::Free) {
+ mapped_size += KProcessAddress(info.GetEndAddress()) - cur_address;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+
+ // If the size mapped is the size requested, we've nothing to do.
+ R_SUCCEED_IF(size == mapped_size);
+ }
+
+ // Allocate and map the memory.
+ {
+ // Reserve the memory from the process resource limit.
+ KScopedResourceReservation memory_reservation(
+ m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, size - mapped_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+
+ // Allocate pages for the new memory.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+ R_TRY(m_kernel.MemoryManager().AllocateForProcess(
+ std::addressof(pg), (size - mapped_size) / PageSize, m_allocate_option,
+ GetCurrentProcess(m_kernel).GetId(), m_heap_fill_value));
+
+ // If we fail in the next bit (or retry), we need to cleanup the pages.
+ auto pg_guard = SCOPE_GUARD({
+ pg.OpenFirst();
+ pg.Close();
+ });
+
+ // Map the memory.
+ {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ size_t num_allocator_blocks = 0;
+
+ // Verify that nobody has mapped memory since we first checked.
+ {
+ // Iterate over the memory.
+ size_t checked_mapped_size = 0;
+ cur_address = address;
+
+ auto it = m_memory_block_manager.FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != m_memory_block_manager.end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ const bool is_free = info.GetState() == KMemoryState::Free;
+ if (is_free) {
+ if (info.GetAddress() < GetInteger(address)) {
+ ++num_allocator_blocks;
+ }
+ if (last_address < info.GetLastAddress()) {
+ ++num_allocator_blocks;
+ }
+ }
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ if (!is_free) {
+ checked_mapped_size += (last_address + 1 - cur_address);
+ }
+ break;
+ }
+
+ // Track the memory if it's mapped.
+ if (!is_free) {
+ checked_mapped_size +=
+ KProcessAddress(info.GetEndAddress()) - cur_address;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+
+ // If the size now isn't what it was before, somebody mapped or unmapped
+ // concurrently. If this happened, retry.
+ if (mapped_size != checked_mapped_size) {
+ continue;
+ }
+ }
+
+ // Create an update allocator.
+ ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager,
+ num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Prepare to iterate over the memory.
+ auto pg_it = pg.begin();
+ KPhysicalAddress pg_phys_addr = pg_it->GetAddress();
+ size_t pg_pages = pg_it->GetNumPages();
+
+ // Reset the current tracking address, and make sure we clean up on failure.
+ pg_guard.Cancel();
+ cur_address = address;
+ ON_RESULT_FAILURE {
+ if (cur_address > address) {
+ const KProcessAddress last_unmap_address = cur_address - 1;
+
+ // Iterate, unmapping the pages.
+ cur_address = address;
+
+ auto it = m_memory_block_manager.FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != m_memory_block_manager.end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // If the memory state is free, we mapped it and need to unmap it.
+ if (info.GetState() == KMemoryState::Free) {
+ // Determine the range to unmap.
+ const KPageProperties unmap_properties = {
+ KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ const size_t cur_pages =
+ std::min(KProcessAddress(info.GetEndAddress()) - cur_address,
+ last_unmap_address + 1 - cur_address) /
+ PageSize;
+
+ // Unmap.
+ R_ASSERT(this->Operate(updater.GetPageList(), cur_address,
+ cur_pages, 0, false, unmap_properties,
+ OperationType::Unmap, true));
+ }
+
+ // Check if we're done.
+ if (last_unmap_address <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+ }
+
+ // Release any remaining unmapped memory.
+ m_kernel.MemoryManager().OpenFirst(pg_phys_addr, pg_pages);
+ m_kernel.MemoryManager().Close(pg_phys_addr, pg_pages);
+ for (++pg_it; pg_it != pg.end(); ++pg_it) {
+ m_kernel.MemoryManager().OpenFirst(pg_it->GetAddress(),
+ pg_it->GetNumPages());
+ m_kernel.MemoryManager().Close(pg_it->GetAddress(), pg_it->GetNumPages());
+ }
+ };
+
+ auto it = m_memory_block_manager.FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != m_memory_block_manager.end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // If it's unmapped, we need to map it.
+ if (info.GetState() == KMemoryState::Free) {
+ // Determine the range to map.
+ const KPageProperties map_properties = {
+ KMemoryPermission::UserReadWrite, false, false,
+ cur_address == this->GetAliasRegionStart()
+ ? DisableMergeAttribute::DisableHead
+ : DisableMergeAttribute::None};
+ size_t map_pages =
+ std::min(KProcessAddress(info.GetEndAddress()) - cur_address,
+ last_address + 1 - cur_address) /
+ PageSize;
+
+ // While we have pages to map, map them.
+ {
+ // Create a page group for the current mapping range.
+ KPageGroup cur_pg(m_kernel, m_block_info_manager);
+ {
+ ON_RESULT_FAILURE_2 {
+ cur_pg.OpenFirst();
+ cur_pg.Close();
+ };
+
+ size_t remain_pages = map_pages;
+ while (remain_pages > 0) {
+ // Check if we're at the end of the physical block.
+ if (pg_pages == 0) {
+ // Ensure there are more pages to map.
+ ASSERT(pg_it != pg.end());
+
+ // Advance our physical block.
+ ++pg_it;
+ pg_phys_addr = pg_it->GetAddress();
+ pg_pages = pg_it->GetNumPages();
+ }
+
+ // Add whatever we can to the current block.
+ const size_t cur_pages = std::min(pg_pages, remain_pages);
+ R_TRY(cur_pg.AddBlock(pg_phys_addr +
+ ((pg_pages - cur_pages) * PageSize),
+ cur_pages));
+
+ // Advance.
+ remain_pages -= cur_pages;
+ pg_pages -= cur_pages;
+ }
+ }
+
+ // Map the papges.
+ R_TRY(this->Operate(updater.GetPageList(), cur_address, map_pages,
+ cur_pg, map_properties,
+ OperationType::MapFirstGroup, false));
+ }
+ }
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+
+ // We succeeded, so commit the memory reservation.
+ memory_reservation.Commit();
+
+ // Increase our tracked mapped size.
+ m_mapped_physical_memory_size += (size - mapped_size);
+
+ // Update the relevant memory blocks.
+ m_memory_block_manager.UpdateIfMatch(
+ std::addressof(allocator), address, size / PageSize, KMemoryState::Free,
+ KMemoryPermission::None, KMemoryAttribute::None, KMemoryState::Normal,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
+ address == this->GetAliasRegionStart()
+ ? KMemoryBlockDisableMergeAttribute::Normal
+ : KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ R_SUCCEED();
+ }
+ }
+ }
+}
+
+Result KPageTableBase::UnmapPhysicalMemory(KProcessAddress address, size_t size) {
+ // Lock the physical memory lock.
+ KScopedLightLock phys_lk(m_map_physical_memory_lock);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Calculate the last address for convenience.
+ const KProcessAddress last_address = address + size - 1;
+
+ // Define iteration variables.
+ KProcessAddress map_start_address = 0;
+ KProcessAddress map_last_address = 0;
+
+ KProcessAddress cur_address;
+ size_t mapped_size;
+ size_t num_allocator_blocks = 0;
+
+ // Check if the memory is mapped.
+ {
+ // Iterate over the memory.
+ cur_address = address;
+ mapped_size = 0;
+
+ auto it = m_memory_block_manager.FindIterator(cur_address);
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != m_memory_block_manager.end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Verify the memory's state.
+ const bool is_normal = info.GetState() == KMemoryState::Normal &&
+ info.GetAttribute() == KMemoryAttribute::None;
+ const bool is_free = info.GetState() == KMemoryState::Free;
+ R_UNLESS(is_normal || is_free, ResultInvalidCurrentMemory);
+
+ if (is_normal) {
+ R_UNLESS(info.GetAttribute() == KMemoryAttribute::None, ResultInvalidCurrentMemory);
+
+ if (map_start_address == 0) {
+ map_start_address = cur_address;
+ }
+ map_last_address =
+ (last_address >= info.GetLastAddress()) ? info.GetLastAddress() : last_address;
+
+ if (info.GetAddress() < GetInteger(address)) {
+ ++num_allocator_blocks;
+ }
+ if (last_address < info.GetLastAddress()) {
+ ++num_allocator_blocks;
+ }
+
+ mapped_size += (map_last_address + 1 - cur_address);
+ }
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+
+ // If there's nothing mapped, we've nothing to do.
+ R_SUCCEED_IF(mapped_size == 0);
+ }
+
+ // Create an update allocator.
+ ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Separate the mapping.
+ const KPageProperties sep_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), map_start_address,
+ (map_last_address + 1 - map_start_address) / PageSize, 0, false,
+ sep_properties, OperationType::Separate, false));
+
+ // Reset the current tracking address, and make sure we clean up on failure.
+ cur_address = address;
+
+ // Iterate over the memory, unmapping as we go.
+ auto it = m_memory_block_manager.FindIterator(cur_address);
+
+ const auto clear_merge_attr =
+ (it->GetState() == KMemoryState::Normal &&
+ it->GetAddress() == this->GetAliasRegionStart() && it->GetAddress() == address)
+ ? KMemoryBlockDisableMergeAttribute::Normal
+ : KMemoryBlockDisableMergeAttribute::None;
+
+ while (true) {
+ // Check that the iterator is valid.
+ ASSERT(it != m_memory_block_manager.end());
+
+ // Get the memory info.
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // If the memory state is normal, we need to unmap it.
+ if (info.GetState() == KMemoryState::Normal) {
+ // Determine the range to unmap.
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ const size_t cur_pages = std::min(KProcessAddress(info.GetEndAddress()) - cur_address,
+ last_address + 1 - cur_address) /
+ PageSize;
+
+ // Unmap.
+ R_ASSERT(this->Operate(updater.GetPageList(), cur_address, cur_pages, 0, false,
+ unmap_properties, OperationType::Unmap, false));
+ }
+
+ // Check if we're done.
+ if (last_address <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ cur_address = info.GetEndAddress();
+ ++it;
+ }
+
+ // Release the memory resource.
+ m_mapped_physical_memory_size -= mapped_size;
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, mapped_size);
+
+ // Update memory blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize,
+ KMemoryState::Free, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
+ clear_merge_attr);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result KPageTableBase::MapPhysicalMemoryUnsafe(KProcessAddress address, size_t size) {
+ UNIMPLEMENTED();
+ R_THROW(ResultNotImplemented);
+}
+
+Result KPageTableBase::UnmapPhysicalMemoryUnsafe(KProcessAddress address, size_t size) {
+ UNIMPLEMENTED();
+ R_THROW(ResultNotImplemented);
+}
+
+Result KPageTableBase::UnmapProcessMemory(KProcessAddress dst_address, size_t size,
+ KPageTableBase& src_page_table,
+ KProcessAddress src_address) {
+ // We need to lock both this table, and the current process's table, so set up an alias.
+ KPageTableBase& dst_page_table = *this;
+
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
+
+ // Check that the memory is mapped in the destination process.
+ size_t num_allocator_blocks;
+ R_TRY(dst_page_table.CheckMemoryState(
+ std::addressof(num_allocator_blocks), dst_address, size, KMemoryState::All,
+ KMemoryState::SharedCode, KMemoryPermission::UserReadWrite,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None));
+
+ // Check that the memory is mapped in the source process.
+ R_TRY(src_page_table.CheckMemoryState(src_address, size, KMemoryState::FlagCanMapProcess,
+ KMemoryState::FlagCanMapProcess, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Validate that the memory ranges are compatible.
+ {
+ // Define a helper type.
+ struct ContiguousRangeInfo {
+ public:
+ KPageTableBase& m_pt;
+ TraversalContext m_context;
+ TraversalEntry m_entry;
+ KPhysicalAddress m_phys_addr;
+ size_t m_cur_size;
+ size_t m_remaining_size;
+
+ public:
+ ContiguousRangeInfo(KPageTableBase& pt, KProcessAddress address, size_t size)
+ : m_pt(pt), m_remaining_size(size) {
+ // Begin a traversal.
+ ASSERT(m_pt.GetImpl().BeginTraversal(std::addressof(m_entry),
+ std::addressof(m_context), address));
+
+ // Setup tracking fields.
+ m_phys_addr = m_entry.phys_addr;
+ m_cur_size = std::min<size_t>(
+ m_remaining_size,
+ m_entry.block_size - (GetInteger(m_phys_addr) & (m_entry.block_size - 1)));
+
+ // Consume the whole contiguous block.
+ this->DetermineContiguousBlockExtents();
+ }
+
+ void ContinueTraversal() {
+ // Update our remaining size.
+ m_remaining_size = m_remaining_size - m_cur_size;
+
+ // Update our tracking fields.
+ if (m_remaining_size > 0) {
+ m_phys_addr = m_entry.phys_addr;
+ m_cur_size = std::min<size_t>(m_remaining_size, m_entry.block_size);
+
+ // Consume the whole contiguous block.
+ this->DetermineContiguousBlockExtents();
+ }
+ }
+
+ private:
+ void DetermineContiguousBlockExtents() {
+ // Continue traversing until we're not contiguous, or we have enough.
+ while (m_cur_size < m_remaining_size) {
+ ASSERT(m_pt.GetImpl().ContinueTraversal(std::addressof(m_entry),
+ std::addressof(m_context)));
+
+ // If we're not contiguous, we're done.
+ if (m_entry.phys_addr != m_phys_addr + m_cur_size) {
+ break;
+ }
+
+ // Update our current size.
+ m_cur_size = std::min(m_remaining_size, m_cur_size + m_entry.block_size);
+ }
+ }
+ };
+
+ // Create ranges for both tables.
+ ContiguousRangeInfo src_range(src_page_table, src_address, size);
+ ContiguousRangeInfo dst_range(dst_page_table, dst_address, size);
+
+ // Validate the ranges.
+ while (src_range.m_remaining_size > 0 && dst_range.m_remaining_size > 0) {
+ R_UNLESS(src_range.m_phys_addr == dst_range.m_phys_addr, ResultInvalidMemoryRegion);
+ R_UNLESS(src_range.m_cur_size == dst_range.m_cur_size, ResultInvalidMemoryRegion);
+
+ src_range.ContinueTraversal();
+ dst_range.ContinueTraversal();
+ }
+ }
+
+ // We no longer need to hold our lock on the source page table.
+ lk.TryUnlockHalf(src_page_table.m_general_lock);
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Unmap the memory.
+ const size_t num_pages = size / PageSize;
+ const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
+ DisableMergeAttribute::None};
+ R_TRY(this->Operate(updater.GetPageList(), dst_address, num_pages, 0, false, unmap_properties,
+ OperationType::Unmap, false));
+
+ // Apply the memory block update.
+ m_memory_block_manager.Update(std::addressof(allocator), dst_address, num_pages,
+ KMemoryState::Free, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal);
+
+ R_SUCCEED();
+}
+
+Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_addr,
+ size_t num_pages, KPhysicalAddress phys_addr, bool is_pa_valid,
+ const KPageProperties properties, OperationType operation,
+ bool reuse_ll) {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(num_pages > 0);
+ ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
+ ASSERT(this->ContainsPages(virt_addr, num_pages));
+
+ // As we don't allocate page entries in guest memory, we don't need to allocate them from
+ // or free them to the page list, and so it goes unused (along with page properties).
+
+ switch (operation) {
+ case OperationType::Unmap: {
+ // Ensure that any pages we track are closed on exit.
+ KPageGroup pages_to_close(m_kernel, this->GetBlockInfoManager());
+ SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
+
+ // Make a page group representing the region to unmap.
+ this->MakePageGroup(pages_to_close, virt_addr, num_pages);
+
+ // Unmap.
+ m_memory->UnmapRegion(*m_impl, virt_addr, num_pages * PageSize);
+
+ R_SUCCEED();
+ }
+ case OperationType::Map: {
+ ASSERT(virt_addr != 0);
+ ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
+ m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr,
+ ConvertToMemoryPermission(properties.perm));
+
+ // Open references to pages, if we should.
+ if (this->IsHeapPhysicalAddress(phys_addr)) {
+ m_kernel.MemoryManager().Open(phys_addr, num_pages);
+ }
+
+ R_SUCCEED();
+ }
+ case OperationType::Separate: {
+ // TODO: Unimplemented.
+ R_SUCCEED();
+ }
+ case OperationType::ChangePermissions:
+ case OperationType::ChangePermissionsAndRefresh:
+ case OperationType::ChangePermissionsAndRefreshAndFlush: {
+ m_memory->ProtectRegion(*m_impl, virt_addr, num_pages * PageSize,
+ ConvertToMemoryPermission(properties.perm));
+ R_SUCCEED();
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_addr,
+ size_t num_pages, const KPageGroup& page_group,
+ const KPageProperties properties, OperationType operation,
+ bool reuse_ll) {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
+ ASSERT(num_pages > 0);
+ ASSERT(num_pages == page_group.GetNumPages());
+
+ // As we don't allocate page entries in guest memory, we don't need to allocate them from
+ // the page list, and so it goes unused (along with page properties).
+
+ switch (operation) {
+ case OperationType::MapGroup:
+ case OperationType::MapFirstGroup: {
+ // We want to maintain a new reference to every page in the group.
+ KScopedPageGroup spg(page_group, operation != OperationType::MapFirstGroup);
+
+ for (const auto& node : page_group) {
+ const size_t size{node.GetNumPages() * PageSize};
+
+ // Map the pages.
+ m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress(),
+ ConvertToMemoryPermission(properties.perm));
+
+ virt_addr += size;
+ }
+
+ // We succeeded! We want to persist the reference to the pages.
+ spg.CancelClose();
+
+ R_SUCCEED();
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+void KPageTableBase::FinalizeUpdate(PageLinkedList* page_list) {
+ while (page_list->Peek()) {
+ [[maybe_unused]] auto page = page_list->Pop();
+
+ // TODO: Free page entries once they are allocated in guest memory.
+ // ASSERT(this->GetPageTableManager().IsInPageTableHeap(page));
+ // ASSERT(this->GetPageTableManager().GetRefCount(page) == 0);
+ // this->GetPageTableManager().Free(page);
+ }
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_table_base.h b/src/core/hle/kernel/k_page_table_base.h
new file mode 100644
index 000000000..556d230b3
--- /dev/null
+++ b/src/core/hle/kernel/k_page_table_base.h
@@ -0,0 +1,760 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_funcs.h"
+#include "common/page_table.h"
+#include "core/core.h"
+#include "core/hle/kernel/k_dynamic_resource_manager.h"
+#include "core/hle/kernel/k_light_lock.h"
+#include "core/hle/kernel/k_memory_block.h"
+#include "core/hle/kernel/k_memory_block_manager.h"
+#include "core/hle/kernel/k_memory_layout.h"
+#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_typed_address.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/result.h"
+#include "core/memory.h"
+
+namespace Kernel {
+
+enum class DisableMergeAttribute : u8 {
+ None = (0U << 0),
+
+ DisableHead = (1U << 0),
+ DisableHeadAndBody = (1U << 1),
+ EnableHeadAndBody = (1U << 2),
+ DisableTail = (1U << 3),
+ EnableTail = (1U << 4),
+ EnableAndMergeHeadBodyTail = (1U << 5),
+
+ EnableHeadBodyTail = EnableHeadAndBody | EnableTail,
+ DisableHeadBodyTail = DisableHeadAndBody | DisableTail,
+};
+DECLARE_ENUM_FLAG_OPERATORS(DisableMergeAttribute);
+
+struct KPageProperties {
+ KMemoryPermission perm;
+ bool io;
+ bool uncached;
+ DisableMergeAttribute disable_merge_attributes;
+};
+static_assert(std::is_trivial_v<KPageProperties>);
+static_assert(sizeof(KPageProperties) == sizeof(u32));
+
+class KResourceLimit;
+class KSystemResource;
+
+class KPageTableBase {
+ YUZU_NON_COPYABLE(KPageTableBase);
+ YUZU_NON_MOVEABLE(KPageTableBase);
+
+public:
+ using TraversalEntry = Common::PageTable::TraversalEntry;
+ using TraversalContext = Common::PageTable::TraversalContext;
+
+ class MemoryRange {
+ private:
+ KernelCore& m_kernel;
+ KPhysicalAddress m_address;
+ size_t m_size;
+ bool m_heap;
+
+ public:
+ explicit MemoryRange(KernelCore& kernel)
+ : m_kernel(kernel), m_address(0), m_size(0), m_heap(false) {}
+
+ void Set(KPhysicalAddress address, size_t size, bool heap) {
+ m_address = address;
+ m_size = size;
+ m_heap = heap;
+ }
+
+ KPhysicalAddress GetAddress() const {
+ return m_address;
+ }
+ size_t GetSize() const {
+ return m_size;
+ }
+ bool IsHeap() const {
+ return m_heap;
+ }
+
+ void Open();
+ void Close();
+ };
+
+protected:
+ enum MemoryFillValue : u8 {
+ MemoryFillValue_Zero = 0,
+ MemoryFillValue_Stack = 'X',
+ MemoryFillValue_Ipc = 'Y',
+ MemoryFillValue_Heap = 'Z',
+ };
+
+ enum class OperationType {
+ Map = 0,
+ MapGroup = 1,
+ MapFirstGroup = 2,
+ Unmap = 3,
+ ChangePermissions = 4,
+ ChangePermissionsAndRefresh = 5,
+ ChangePermissionsAndRefreshAndFlush = 6,
+ Separate = 7,
+ };
+
+ static constexpr size_t MaxPhysicalMapAlignment = 1_GiB;
+ static constexpr size_t RegionAlignment = 2_MiB;
+ static_assert(RegionAlignment == KernelAslrAlignment);
+
+ struct PageLinkedList {
+ private:
+ struct Node {
+ Node* m_next;
+ std::array<u8, PageSize - sizeof(Node*)> m_buffer;
+ };
+ static_assert(std::is_trivial_v<Node>);
+
+ private:
+ Node* m_root{};
+
+ public:
+ constexpr PageLinkedList() : m_root(nullptr) {}
+
+ void Push(Node* n) {
+ ASSERT(Common::IsAligned(reinterpret_cast<uintptr_t>(n), PageSize));
+ n->m_next = m_root;
+ m_root = n;
+ }
+
+ Node* Peek() const {
+ return m_root;
+ }
+
+ Node* Pop() {
+ Node* const r = m_root;
+
+ m_root = r->m_next;
+ r->m_next = nullptr;
+
+ return r;
+ }
+ };
+ static_assert(std::is_trivially_destructible_v<PageLinkedList>);
+
+ static constexpr auto DefaultMemoryIgnoreAttr =
+ KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared;
+
+ static constexpr size_t GetAddressSpaceWidth(Svc::CreateProcessFlag as_type) {
+ switch (static_cast<Svc::CreateProcessFlag>(as_type &
+ Svc::CreateProcessFlag::AddressSpaceMask)) {
+ case Svc::CreateProcessFlag::AddressSpace64Bit:
+ return 39;
+ case Svc::CreateProcessFlag::AddressSpace64BitDeprecated:
+ return 36;
+ case Svc::CreateProcessFlag::AddressSpace32Bit:
+ case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias:
+ return 32;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+private:
+ class KScopedPageTableUpdater {
+ private:
+ KPageTableBase* m_pt;
+ PageLinkedList m_ll;
+
+ public:
+ explicit KScopedPageTableUpdater(KPageTableBase* pt) : m_pt(pt), m_ll() {}
+ explicit KScopedPageTableUpdater(KPageTableBase& pt)
+ : KScopedPageTableUpdater(std::addressof(pt)) {}
+ ~KScopedPageTableUpdater() {
+ m_pt->FinalizeUpdate(this->GetPageList());
+ }
+
+ PageLinkedList* GetPageList() {
+ return std::addressof(m_ll);
+ }
+ };
+
+private:
+ KernelCore& m_kernel;
+ Core::System& m_system;
+ KProcessAddress m_address_space_start{};
+ KProcessAddress m_address_space_end{};
+ KProcessAddress m_heap_region_start{};
+ KProcessAddress m_heap_region_end{};
+ KProcessAddress m_current_heap_end{};
+ KProcessAddress m_alias_region_start{};
+ KProcessAddress m_alias_region_end{};
+ KProcessAddress m_stack_region_start{};
+ KProcessAddress m_stack_region_end{};
+ KProcessAddress m_kernel_map_region_start{};
+ KProcessAddress m_kernel_map_region_end{};
+ KProcessAddress m_alias_code_region_start{};
+ KProcessAddress m_alias_code_region_end{};
+ KProcessAddress m_code_region_start{};
+ KProcessAddress m_code_region_end{};
+ size_t m_max_heap_size{};
+ size_t m_mapped_physical_memory_size{};
+ size_t m_mapped_unsafe_physical_memory{};
+ size_t m_mapped_insecure_memory{};
+ size_t m_mapped_ipc_server_memory{};
+ mutable KLightLock m_general_lock;
+ mutable KLightLock m_map_physical_memory_lock;
+ KLightLock m_device_map_lock;
+ std::unique_ptr<Common::PageTable> m_impl{};
+ Core::Memory::Memory* m_memory{};
+ KMemoryBlockManager m_memory_block_manager{};
+ u32 m_allocate_option{};
+ u32 m_address_space_width{};
+ bool m_is_kernel{};
+ bool m_enable_aslr{};
+ bool m_enable_device_address_space_merge{};
+ KMemoryBlockSlabManager* m_memory_block_slab_manager{};
+ KBlockInfoManager* m_block_info_manager{};
+ KResourceLimit* m_resource_limit{};
+ const KMemoryRegion* m_cached_physical_linear_region{};
+ const KMemoryRegion* m_cached_physical_heap_region{};
+ MemoryFillValue m_heap_fill_value{};
+ MemoryFillValue m_ipc_fill_value{};
+ MemoryFillValue m_stack_fill_value{};
+
+public:
+ explicit KPageTableBase(KernelCore& kernel);
+ ~KPageTableBase();
+
+ Result InitializeForKernel(bool is_64_bit, KVirtualAddress start, KVirtualAddress end,
+ Core::Memory::Memory& memory);
+ Result InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr,
+ bool enable_device_address_space_merge, bool from_back,
+ KMemoryManager::Pool pool, KProcessAddress code_address,
+ size_t code_size, KSystemResource* system_resource,
+ KResourceLimit* resource_limit, Core::Memory::Memory& memory,
+ KProcessAddress aslr_space_start);
+
+ void Finalize();
+
+ bool IsKernel() const {
+ return m_is_kernel;
+ }
+ bool IsAslrEnabled() const {
+ return m_enable_aslr;
+ }
+
+ bool Contains(KProcessAddress addr) const {
+ return m_address_space_start <= addr && addr <= m_address_space_end - 1;
+ }
+
+ bool Contains(KProcessAddress addr, size_t size) const {
+ return m_address_space_start <= addr && addr < addr + size &&
+ addr + size - 1 <= m_address_space_end - 1;
+ }
+
+ bool IsInAliasRegion(KProcessAddress addr, size_t size) const {
+ return this->Contains(addr, size) && m_alias_region_start <= addr &&
+ addr + size - 1 <= m_alias_region_end - 1;
+ }
+
+ bool IsInHeapRegion(KProcessAddress addr, size_t size) const {
+ return this->Contains(addr, size) && m_heap_region_start <= addr &&
+ addr + size - 1 <= m_heap_region_end - 1;
+ }
+
+ bool IsInUnsafeAliasRegion(KProcessAddress addr, size_t size) const {
+ // Even though Unsafe physical memory is KMemoryState_Normal, it must be mapped inside the
+ // alias code region.
+ return this->CanContain(addr, size, Svc::MemoryState::AliasCode);
+ }
+
+ KScopedLightLock AcquireDeviceMapLock() {
+ return KScopedLightLock(m_device_map_lock);
+ }
+
+ KProcessAddress GetRegionAddress(Svc::MemoryState state) const;
+ size_t GetRegionSize(Svc::MemoryState state) const;
+ bool CanContain(KProcessAddress addr, size_t size, Svc::MemoryState state) const;
+
+ KProcessAddress GetRegionAddress(KMemoryState state) const {
+ return this->GetRegionAddress(static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
+ }
+ size_t GetRegionSize(KMemoryState state) const {
+ return this->GetRegionSize(static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
+ }
+ bool CanContain(KProcessAddress addr, size_t size, KMemoryState state) const {
+ return this->CanContain(addr, size,
+ static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
+ }
+
+public:
+ Core::Memory::Memory& GetMemory() {
+ return *m_memory;
+ }
+
+ Core::Memory::Memory& GetMemory() const {
+ return *m_memory;
+ }
+
+ Common::PageTable& GetImpl() {
+ return *m_impl;
+ }
+
+ Common::PageTable& GetImpl() const {
+ return *m_impl;
+ }
+
+ size_t GetNumGuardPages() const {
+ return this->IsKernel() ? 1 : 4;
+ }
+
+protected:
+ // NOTE: These three functions (Operate, Operate, FinalizeUpdate) are virtual functions
+ // in Nintendo's kernel. We devirtualize them, since KPageTable is the only derived
+ // class, and this avoids unnecessary virtual function calls.
+ Result Operate(PageLinkedList* page_list, KProcessAddress virt_addr, size_t num_pages,
+ KPhysicalAddress phys_addr, bool is_pa_valid, const KPageProperties properties,
+ OperationType operation, bool reuse_ll);
+ Result Operate(PageLinkedList* page_list, KProcessAddress virt_addr, size_t num_pages,
+ const KPageGroup& page_group, const KPageProperties properties,
+ OperationType operation, bool reuse_ll);
+ void FinalizeUpdate(PageLinkedList* page_list);
+
+ bool IsLockedByCurrentThread() const {
+ return m_general_lock.IsLockedByCurrentThread();
+ }
+
+ bool IsLinearMappedPhysicalAddress(KPhysicalAddress phys_addr) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ return m_kernel.MemoryLayout().IsLinearMappedPhysicalAddress(
+ m_cached_physical_linear_region, phys_addr);
+ }
+
+ bool IsLinearMappedPhysicalAddress(KPhysicalAddress phys_addr, size_t size) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ return m_kernel.MemoryLayout().IsLinearMappedPhysicalAddress(
+ m_cached_physical_linear_region, phys_addr, size);
+ }
+
+ bool IsHeapPhysicalAddress(KPhysicalAddress phys_addr) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ return m_kernel.MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region,
+ phys_addr);
+ }
+
+ bool IsHeapPhysicalAddress(KPhysicalAddress phys_addr, size_t size) {
+ ASSERT(this->IsLockedByCurrentThread());
+
+ return m_kernel.MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region,
+ phys_addr, size);
+ }
+
+ bool IsHeapPhysicalAddressForFinalize(KPhysicalAddress phys_addr) {
+ ASSERT(!this->IsLockedByCurrentThread());
+
+ return m_kernel.MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region,
+ phys_addr);
+ }
+
+ bool ContainsPages(KProcessAddress addr, size_t num_pages) const {
+ return (m_address_space_start <= addr) &&
+ (num_pages <= (m_address_space_end - m_address_space_start) / PageSize) &&
+ (addr + num_pages * PageSize - 1 <= m_address_space_end - 1);
+ }
+
+private:
+ KProcessAddress FindFreeArea(KProcessAddress region_start, size_t region_num_pages,
+ size_t num_pages, size_t alignment, size_t offset,
+ size_t guard_pages) const;
+
+ Result CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr, size_t size,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
+ Result CheckMemoryStateContiguous(KProcessAddress addr, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask,
+ KMemoryPermission perm, KMemoryAttribute attr_mask,
+ KMemoryAttribute attr) const {
+ R_RETURN(this->CheckMemoryStateContiguous(nullptr, addr, size, state_mask, state, perm_mask,
+ perm, attr_mask, attr));
+ }
+
+ Result CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
+ Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
+ KMemoryAttribute* out_attr, size_t* out_blocks_needed,
+ KMemoryBlockManager::const_iterator it, KProcessAddress last_addr,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const;
+ Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
+ KMemoryAttribute* out_attr, size_t* out_blocks_needed,
+ KProcessAddress addr, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const;
+ Result CheckMemoryState(size_t* out_blocks_needed, KProcessAddress addr, size_t size,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const {
+ R_RETURN(this->CheckMemoryState(nullptr, nullptr, nullptr, out_blocks_needed, addr, size,
+ state_mask, state, perm_mask, perm, attr_mask, attr,
+ ignore_attr));
+ }
+ Result CheckMemoryState(KProcessAddress addr, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const {
+ R_RETURN(this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm,
+ attr_mask, attr, ignore_attr));
+ }
+
+ Result LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_paddr, KProcessAddress addr,
+ size_t size, KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryPermission new_perm, KMemoryAttribute lock_attr);
+ Result UnlockMemory(KProcessAddress addr, size_t size, KMemoryState state_mask,
+ KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr,
+ KMemoryPermission new_perm, KMemoryAttribute lock_attr,
+ const KPageGroup* pg);
+
+ Result QueryInfoImpl(KMemoryInfo* out_info, Svc::PageInfo* out_page,
+ KProcessAddress address) const;
+
+ Result QueryMappingImpl(KProcessAddress* out, KPhysicalAddress address, size_t size,
+ Svc::MemoryState state) const;
+
+ Result AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address,
+ size_t num_pages, KMemoryPermission perm);
+ Result MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address,
+ const KPageGroup& pg, const KPageProperties properties, bool reuse_ll);
+
+ void RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size,
+ const KPageGroup& pg);
+
+ Result MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_t num_pages);
+ bool IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr, size_t num_pages);
+
+ Result GetContiguousMemoryRangeWithState(MemoryRange* out, KProcessAddress address, size_t size,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr);
+
+ Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
+ KPhysicalAddress phys_addr, bool is_pa_valid, KProcessAddress region_start,
+ size_t region_num_pages, KMemoryState state, KMemoryPermission perm);
+
+ Result MapIoImpl(KProcessAddress* out, PageLinkedList* page_list, KPhysicalAddress phys_addr,
+ size_t size, KMemoryState state, KMemoryPermission perm);
+ Result ReadIoMemoryImpl(KProcessAddress dst_addr, KPhysicalAddress phys_addr, size_t size,
+ KMemoryState state);
+ Result WriteIoMemoryImpl(KPhysicalAddress phys_addr, KProcessAddress src_addr, size_t size,
+ KMemoryState state);
+
+ Result SetupForIpcClient(PageLinkedList* page_list, size_t* out_blocks_needed,
+ KProcessAddress address, size_t size, KMemoryPermission test_perm,
+ KMemoryState dst_state);
+ Result SetupForIpcServer(KProcessAddress* out_addr, size_t size, KProcessAddress src_addr,
+ KMemoryPermission test_perm, KMemoryState dst_state,
+ KPageTableBase& src_page_table, bool send);
+ void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, KProcessAddress address,
+ size_t size, KMemoryPermission prot_perm);
+
+ size_t GetSize(KMemoryState state) const;
+
+ bool GetPhysicalAddressLocked(KPhysicalAddress* out, KProcessAddress virt_addr) const {
+ // Validate pre-conditions.
+ ASSERT(this->IsLockedByCurrentThread());
+
+ return this->GetImpl().GetPhysicalAddress(out, virt_addr);
+ }
+
+public:
+ bool GetPhysicalAddress(KPhysicalAddress* out, KProcessAddress virt_addr) const {
+ // Validate pre-conditions.
+ ASSERT(!this->IsLockedByCurrentThread());
+
+ // Acquire exclusive access to the table while doing address translation.
+ KScopedLightLock lk(m_general_lock);
+
+ return this->GetPhysicalAddressLocked(out, virt_addr);
+ }
+
+ KBlockInfoManager* GetBlockInfoManager() const {
+ return m_block_info_manager;
+ }
+
+ Result SetMemoryPermission(KProcessAddress addr, size_t size, Svc::MemoryPermission perm);
+ Result SetProcessMemoryPermission(KProcessAddress addr, size_t size,
+ Svc::MemoryPermission perm);
+ Result SetMemoryAttribute(KProcessAddress addr, size_t size, KMemoryAttribute mask,
+ KMemoryAttribute attr);
+ Result SetHeapSize(KProcessAddress* out, size_t size);
+ Result SetMaxHeapSize(size_t size);
+ Result QueryInfo(KMemoryInfo* out_info, Svc::PageInfo* out_page_info,
+ KProcessAddress addr) const;
+ Result QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out, KProcessAddress address) const;
+ Result QueryStaticMapping(KProcessAddress* out, KPhysicalAddress address, size_t size) const {
+ R_RETURN(this->QueryMappingImpl(out, address, size, Svc::MemoryState::Static));
+ }
+ Result QueryIoMapping(KProcessAddress* out, KPhysicalAddress address, size_t size) const {
+ R_RETURN(this->QueryMappingImpl(out, address, size, Svc::MemoryState::Io));
+ }
+ Result MapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size);
+ Result UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size);
+ Result MapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size);
+ Result UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size);
+ Result MapIo(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm);
+ Result MapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size,
+ Svc::MemoryMapping mapping, Svc::MemoryPermission perm);
+ Result UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size,
+ Svc::MemoryMapping mapping);
+ Result MapStatic(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm);
+ Result MapRegion(KMemoryRegionType region_type, KMemoryPermission perm);
+ Result MapInsecureMemory(KProcessAddress address, size_t size);
+ Result UnmapInsecureMemory(KProcessAddress address, size_t size);
+
+ Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
+ KPhysicalAddress phys_addr, KProcessAddress region_start,
+ size_t region_num_pages, KMemoryState state, KMemoryPermission perm) {
+ R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, region_start,
+ region_num_pages, state, perm));
+ }
+
+ Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
+ KPhysicalAddress phys_addr, KMemoryState state, KMemoryPermission perm) {
+ R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true,
+ this->GetRegionAddress(state),
+ this->GetRegionSize(state) / PageSize, state, perm));
+ }
+
+ Result MapPages(KProcessAddress* out_addr, size_t num_pages, KMemoryState state,
+ KMemoryPermission perm) {
+ R_RETURN(this->MapPages(out_addr, num_pages, PageSize, 0, false,
+ this->GetRegionAddress(state),
+ this->GetRegionSize(state) / PageSize, state, perm));
+ }
+
+ Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state,
+ KMemoryPermission perm);
+ Result UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state);
+
+ Result MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg,
+ KProcessAddress region_start, size_t region_num_pages, KMemoryState state,
+ KMemoryPermission perm);
+ Result MapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state,
+ KMemoryPermission perm);
+ Result UnmapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state);
+
+ Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr);
+
+ Result InvalidateProcessDataCache(KProcessAddress address, size_t size);
+ Result InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size);
+
+ Result ReadDebugMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size);
+ Result ReadDebugIoMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size,
+ KMemoryState state);
+
+ Result WriteDebugMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size);
+ Result WriteDebugIoMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size,
+ KMemoryState state);
+
+ Result LockForMapDeviceAddressSpace(bool* out_is_io, KProcessAddress address, size_t size,
+ KMemoryPermission perm, bool is_aligned, bool check_heap);
+ Result LockForUnmapDeviceAddressSpace(KProcessAddress address, size_t size, bool check_heap);
+
+ Result UnlockForDeviceAddressSpace(KProcessAddress address, size_t size);
+ Result UnlockForDeviceAddressSpacePartialMap(KProcessAddress address, size_t size);
+
+ Result OpenMemoryRangeForMapDeviceAddressSpace(KPageTableBase::MemoryRange* out,
+ KProcessAddress address, size_t size,
+ KMemoryPermission perm, bool is_aligned);
+ Result OpenMemoryRangeForUnmapDeviceAddressSpace(MemoryRange* out, KProcessAddress address,
+ size_t size);
+
+ Result LockForIpcUserBuffer(KPhysicalAddress* out, KProcessAddress address, size_t size);
+ Result UnlockForIpcUserBuffer(KProcessAddress address, size_t size);
+
+ 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 address, size_t size);
+ Result UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup& pg);
+
+ Result OpenMemoryRangeForProcessCacheOperation(MemoryRange* out, KProcessAddress address,
+ size_t size);
+
+ Result CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size,
+ KProcessAddress src_addr, KMemoryState src_state_mask,
+ KMemoryState src_state, KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr);
+ Result CopyMemoryFromLinearToKernel(void* buffer, size_t size, KProcessAddress src_addr,
+ KMemoryState src_state_mask, KMemoryState src_state,
+ KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr);
+ Result CopyMemoryFromUserToLinear(KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask, KMemoryState dst_state,
+ KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr,
+ KProcessAddress src_addr);
+ Result CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask, KMemoryState dst_state,
+ KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr,
+ void* buffer);
+ Result CopyMemoryFromHeapToHeap(KPageTableBase& dst_page_table, KProcessAddress dst_addr,
+ size_t size, KMemoryState dst_state_mask,
+ KMemoryState dst_state, KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr,
+ KProcessAddress src_addr, KMemoryState src_state_mask,
+ KMemoryState src_state, KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr);
+ Result CopyMemoryFromHeapToHeapWithoutCheckDestination(
+ KPageTableBase& dst_page_table, KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask, KMemoryState dst_state, KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr, KProcessAddress src_addr,
+ KMemoryState src_state_mask, KMemoryState src_state, KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr);
+
+ Result SetupForIpc(KProcessAddress* out_dst_addr, size_t size, KProcessAddress src_addr,
+ KPageTableBase& src_page_table, KMemoryPermission test_perm,
+ KMemoryState dst_state, bool send);
+ Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state);
+ Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state);
+
+ Result MapPhysicalMemory(KProcessAddress address, size_t size);
+ Result UnmapPhysicalMemory(KProcessAddress address, size_t size);
+
+ Result MapPhysicalMemoryUnsafe(KProcessAddress address, size_t size);
+ Result UnmapPhysicalMemoryUnsafe(KProcessAddress address, size_t size);
+
+ Result UnmapProcessMemory(KProcessAddress dst_address, size_t size, KPageTableBase& src_pt,
+ KProcessAddress src_address);
+
+public:
+ KProcessAddress GetAddressSpaceStart() const {
+ return m_address_space_start;
+ }
+ KProcessAddress GetHeapRegionStart() const {
+ return m_heap_region_start;
+ }
+ KProcessAddress GetAliasRegionStart() const {
+ return m_alias_region_start;
+ }
+ KProcessAddress GetStackRegionStart() const {
+ return m_stack_region_start;
+ }
+ KProcessAddress GetKernelMapRegionStart() const {
+ return m_kernel_map_region_start;
+ }
+ KProcessAddress GetCodeRegionStart() const {
+ return m_code_region_start;
+ }
+ KProcessAddress GetAliasCodeRegionStart() const {
+ return m_alias_code_region_start;
+ }
+
+ size_t GetAddressSpaceSize() const {
+ return m_address_space_end - m_address_space_start;
+ }
+ size_t GetHeapRegionSize() const {
+ return m_heap_region_end - m_heap_region_start;
+ }
+ size_t GetAliasRegionSize() const {
+ return m_alias_region_end - m_alias_region_start;
+ }
+ size_t GetStackRegionSize() const {
+ return m_stack_region_end - m_stack_region_start;
+ }
+ size_t GetKernelMapRegionSize() const {
+ return m_kernel_map_region_end - m_kernel_map_region_start;
+ }
+ size_t GetCodeRegionSize() const {
+ return m_code_region_end - m_code_region_start;
+ }
+ size_t GetAliasCodeRegionSize() const {
+ return m_alias_code_region_end - m_alias_code_region_start;
+ }
+
+ size_t GetNormalMemorySize() const {
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ return (m_current_heap_end - m_heap_region_start) + m_mapped_physical_memory_size;
+ }
+
+ size_t GetCodeSize() const;
+ size_t GetCodeDataSize() const;
+ size_t GetAliasCodeSize() const;
+ size_t GetAliasCodeDataSize() const;
+
+ u32 GetAllocateOption() const {
+ return m_allocate_option;
+ }
+
+ u32 GetAddressSpaceWidth() const {
+ return m_address_space_width;
+ }
+
+public:
+ // Linear mapped
+ static u8* GetLinearMappedVirtualPointer(KernelCore& kernel, KPhysicalAddress addr) {
+ return kernel.System().DeviceMemory().GetPointer<u8>(addr);
+ }
+
+ static KPhysicalAddress GetLinearMappedPhysicalAddress(KernelCore& kernel,
+ KVirtualAddress addr) {
+ return kernel.MemoryLayout().GetLinearPhysicalAddress(addr);
+ }
+
+ static KVirtualAddress GetLinearMappedVirtualAddress(KernelCore& kernel,
+ KPhysicalAddress addr) {
+ return kernel.MemoryLayout().GetLinearVirtualAddress(addr);
+ }
+
+ // Heap
+ static u8* GetHeapVirtualPointer(KernelCore& kernel, KPhysicalAddress addr) {
+ return kernel.System().DeviceMemory().GetPointer<u8>(addr);
+ }
+
+ static KPhysicalAddress GetHeapPhysicalAddress(KernelCore& kernel, KVirtualAddress addr) {
+ return GetLinearMappedPhysicalAddress(kernel, addr);
+ }
+
+ static KVirtualAddress GetHeapVirtualAddress(KernelCore& kernel, KPhysicalAddress addr) {
+ return GetLinearMappedVirtualAddress(kernel, addr);
+ }
+
+ // Member heap
+ u8* GetHeapVirtualPointer(KPhysicalAddress addr) {
+ return GetHeapVirtualPointer(m_kernel, addr);
+ }
+
+ KPhysicalAddress GetHeapPhysicalAddress(KVirtualAddress addr) {
+ return GetHeapPhysicalAddress(m_kernel, addr);
+ }
+
+ KVirtualAddress GetHeapVirtualAddress(KPhysicalAddress addr) {
+ return GetHeapVirtualAddress(m_kernel, addr);
+ }
+
+ // TODO: GetPageTableVirtualAddress
+ // TODO: GetPageTablePhysicalAddress
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 7fa34d693..6c29eb72c 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -1,515 +1,598 @@
-// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
-#include <bitset>
-#include <ctime>
-#include <memory>
#include <random>
-#include "common/alignment.h"
-#include "common/assert.h"
-#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/core.h"
-#include "core/file_sys/program_metadata.h"
-#include "core/hle/kernel/code_set.h"
-#include "core/hle/kernel/k_memory_block_manager.h"
-#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
-#include "core/hle/kernel/k_resource_limit.h"
-#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_shared_memory_info.h"
-#include "core/hle/kernel/k_thread.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/svc_results.h"
-#include "core/memory.h"
+#include "core/hle/kernel/k_thread_local_page.h"
+#include "core/hle/kernel/k_thread_queue.h"
+#include "core/hle/kernel/k_worker_task_manager.h"
namespace Kernel {
-namespace {
-/**
- * Sets up the primary application thread
- *
- * @param system The system instance to create the main thread under.
- * @param owner_process The parent process for the main thread
- * @param priority The priority to give the main thread
- */
-void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority,
- KProcessAddress stack_top) {
- const KProcessAddress entry_point = owner_process.GetEntryPoint();
- ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1));
-
- KThread* thread = KThread::Create(system.Kernel());
- SCOPE_EXIT({ thread->Close(); });
-
- ASSERT(KThread::InitializeUserThread(system, thread, entry_point, 0, stack_top, priority,
- owner_process.GetIdealCoreId(),
- std::addressof(owner_process))
- .IsSuccess());
-
- // Register 1 must be a handle to the main thread
- Handle thread_handle{};
- owner_process.GetHandleTable().Add(std::addressof(thread_handle), thread);
-
- thread->GetContext32().cpu_registers[0] = 0;
- thread->GetContext64().cpu_registers[0] = 0;
- thread->GetContext32().cpu_registers[1] = thread_handle;
- thread->GetContext64().cpu_registers[1] = thread_handle;
-
- if (system.DebuggerEnabled()) {
- thread->RequestSuspend(SuspendType::Debug);
- }
- // Run our thread.
- void(thread->Run());
-}
-} // Anonymous namespace
+namespace {
-Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name,
- ProcessType type, KResourceLimit* res_limit) {
- auto& kernel = system.Kernel();
+Result TerminateChildren(KernelCore& kernel, KProcess* process,
+ const KThread* thread_to_not_terminate) {
+ // Request that all children threads terminate.
+ {
+ KScopedLightLock proc_lk(process->GetListLock());
+ KScopedSchedulerLock sl(kernel);
+
+ if (thread_to_not_terminate != nullptr &&
+ process->GetPinnedThread(GetCurrentCoreId(kernel)) == thread_to_not_terminate) {
+ // NOTE: Here Nintendo unpins the current thread instead of the thread_to_not_terminate.
+ // This is valid because the only caller which uses non-nullptr as argument uses
+ // GetCurrentThreadPointer(), but it's still notable because it seems incorrect at
+ // first glance.
+ process->UnpinCurrentThread();
+ }
- process->name = std::move(process_name);
- process->m_resource_limit = res_limit;
- process->m_system_resource_address = 0;
- process->m_state = State::Created;
- process->m_program_id = 0;
- process->m_process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID()
- : kernel.CreateNewUserProcessID();
- process->m_capabilities.InitializeForMetadatalessProcess();
- process->m_is_initialized = true;
+ auto& thread_list = process->GetThreadList();
+ for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
+ if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
+ if (thread->GetState() != ThreadState::Terminated) {
+ thread->RequestTerminate();
+ }
+ }
+ }
+ }
- std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
- : static_cast<u32>(std::time(nullptr)));
- std::uniform_int_distribution<u64> distribution;
- std::generate(process->m_random_entropy.begin(), process->m_random_entropy.end(),
- [&] { return distribution(rng); });
+ // Wait for all children threads to terminate.
+ while (true) {
+ // Get the next child.
+ KThread* cur_child = nullptr;
+ {
+ KScopedLightLock proc_lk(process->GetListLock());
+
+ auto& thread_list = process->GetThreadList();
+ for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
+ if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
+ if (thread->GetState() != ThreadState::Terminated) {
+ if (thread->Open()) {
+ cur_child = thread;
+ break;
+ }
+ }
+ }
+ }
+ }
- kernel.AppendNewProcess(process);
+ // If we didn't find any non-terminated children, we're done.
+ if (cur_child == nullptr) {
+ break;
+ }
- // Clear remaining fields.
- process->m_num_running_threads = 0;
- process->m_is_signaled = false;
- process->m_exception_thread = nullptr;
- process->m_is_suspended = false;
- process->m_schedule_count = 0;
- process->m_is_handle_table_initialized = false;
- process->m_is_hbl = false;
+ // Terminate and close the thread.
+ SCOPE_EXIT({ cur_child->Close(); });
- // Open a reference to the resource limit.
- process->m_resource_limit->Open();
+ if (const Result terminate_result = cur_child->Terminate();
+ ResultTerminationRequested == terminate_result) {
+ R_THROW(terminate_result);
+ }
+ }
R_SUCCEED();
}
-void KProcess::DoWorkerTaskImpl() {
- UNIMPLEMENTED();
-}
-
-KResourceLimit* KProcess::GetResourceLimit() const {
- return m_resource_limit;
-}
+class ThreadQueueImplForKProcessEnterUserException final : public KThreadQueue {
+private:
+ KThread** m_exception_thread;
-void KProcess::IncrementRunningThreadCount() {
- ASSERT(m_num_running_threads.load() >= 0);
- ++m_num_running_threads;
-}
+public:
+ explicit ThreadQueueImplForKProcessEnterUserException(KernelCore& kernel, KThread** t)
+ : KThreadQueue(kernel), m_exception_thread(t) {}
-void KProcess::DecrementRunningThreadCount() {
- ASSERT(m_num_running_threads.load() > 0);
+ virtual void EndWait(KThread* waiting_thread, Result wait_result) override {
+ // Set the exception thread.
+ *m_exception_thread = waiting_thread;
- if (const auto prev = m_num_running_threads--; prev == 1) {
- // TODO(bunnei): Process termination to be implemented when multiprocess is supported.
+ // Invoke the base end wait handler.
+ KThreadQueue::EndWait(waiting_thread, wait_result);
}
-}
-u64 KProcess::GetTotalPhysicalMemoryAvailable() {
- const u64 capacity{m_resource_limit->GetFreeValue(LimitableResource::PhysicalMemoryMax) +
- m_page_table.GetNormalMemorySize() + GetSystemResourceSize() + m_image_size +
- m_main_thread_stack_size};
- if (const auto pool_size = m_kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application);
- capacity != pool_size) {
- LOG_WARNING(Kernel, "capacity {} != application pool size {}", capacity, pool_size);
- }
- if (capacity < m_memory_usage_capacity) {
- return capacity;
+ virtual void CancelWait(KThread* waiting_thread, Result wait_result,
+ bool cancel_timer_task) override {
+ // Remove the thread as a waiter on its mutex owner.
+ waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread);
+
+ // Invoke the base cancel wait handler.
+ KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
}
- return m_memory_usage_capacity;
-}
+};
-u64 KProcess::GetTotalPhysicalMemoryAvailableWithoutSystemResource() {
- return this->GetTotalPhysicalMemoryAvailable() - this->GetSystemResourceSize();
+void GenerateRandom(std::span<u64> out_random) {
+ std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
+ : static_cast<u32>(std::time(nullptr)));
+ std::uniform_int_distribution<u64> distribution;
+ std::generate(out_random.begin(), out_random.end(), [&] { return distribution(rng); });
}
-u64 KProcess::GetTotalPhysicalMemoryUsed() {
- return m_image_size + m_main_thread_stack_size + m_page_table.GetNormalMemorySize() +
- this->GetSystemResourceSize();
-}
+} // namespace
-u64 KProcess::GetTotalPhysicalMemoryUsedWithoutSystemResource() {
- return this->GetTotalPhysicalMemoryUsed() - this->GetSystemResourceSize();
-}
+void KProcess::Finalize() {
+ // Delete the process local region.
+ this->DeleteThreadLocalRegion(m_plr_address);
-bool KProcess::ReleaseUserException(KThread* thread) {
- KScopedSchedulerLock sl{m_kernel};
+ // Get the used memory size.
+ const size_t used_memory_size = this->GetUsedNonSystemUserPhysicalMemorySize();
- if (m_exception_thread == thread) {
- m_exception_thread = nullptr;
+ // Finalize the page table.
+ m_page_table.Finalize();
- // Remove waiter thread.
- bool has_waiters{};
- if (KThread* next = thread->RemoveKernelWaiterByKey(
- std::addressof(has_waiters),
- reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)));
- next != nullptr) {
- next->EndWait(ResultSuccess);
+ // Finish using our system resource.
+ if (m_system_resource) {
+ if (m_system_resource->IsSecureResource()) {
+ // Finalize optimized memory. If memory wasn't optimized, this is a no-op.
+ m_kernel.MemoryManager().FinalizeOptimizedMemory(this->GetId(), m_memory_pool);
}
- KScheduler::SetSchedulerUpdateNeeded(m_kernel);
-
- return true;
- } else {
- return false;
+ m_system_resource->Close();
+ m_system_resource = nullptr;
}
-}
-
-void KProcess::PinCurrentThread(s32 core_id) {
- ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
- // Get the current thread.
- KThread* cur_thread =
- m_kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread();
+ // Free all shared memory infos.
+ {
+ auto it = m_shared_memory_list.begin();
+ while (it != m_shared_memory_list.end()) {
+ KSharedMemoryInfo* info = std::addressof(*it);
+ KSharedMemory* shmem = info->GetSharedMemory();
- // If the thread isn't terminated, pin it.
- if (!cur_thread->IsTerminationRequested()) {
- // Pin it.
- this->PinThread(core_id, cur_thread);
- cur_thread->Pin(core_id);
+ while (!info->Close()) {
+ shmem->Close();
+ }
+ shmem->Close();
- // An update is needed.
- KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+ it = m_shared_memory_list.erase(it);
+ KSharedMemoryInfo::Free(m_kernel, info);
+ }
}
-}
-void KProcess::UnpinCurrentThread(s32 core_id) {
- ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
-
- // Get the current thread.
- KThread* cur_thread =
- m_kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread();
+ // Our thread local page list must be empty at this point.
+ ASSERT(m_partially_used_tlp_tree.empty());
+ ASSERT(m_fully_used_tlp_tree.empty());
- // Unpin it.
- cur_thread->Unpin();
- this->UnpinThread(core_id, cur_thread);
+ // Release memory to the resource limit.
+ if (m_resource_limit != nullptr) {
+ ASSERT(used_memory_size >= m_memory_release_hint);
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, used_memory_size,
+ used_memory_size - m_memory_release_hint);
+ m_resource_limit->Close();
+ }
- // An update is needed.
- KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+ // Perform inherited finalization.
+ KSynchronizationObject::Finalize();
}
-void KProcess::UnpinThread(KThread* thread) {
- ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
-
- // Get the thread's core id.
- const auto core_id = thread->GetActiveCore();
+Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit,
+ bool is_real) {
+ // TODO: remove this special case
+ if (is_real) {
+ // Create and clear the process local region.
+ R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address)));
+ this->GetMemory().ZeroBlock(m_plr_address, Svc::ThreadLocalRegionSize);
+ }
- // Unpin it.
- this->UnpinThread(core_id, thread);
- thread->Unpin();
+ // Copy in the name from parameters.
+ static_assert(sizeof(params.name) < sizeof(m_name));
+ std::memcpy(m_name.data(), params.name.data(), sizeof(params.name));
+ m_name[sizeof(params.name)] = 0;
+
+ // Set misc fields.
+ m_state = State::Created;
+ m_main_thread_stack_size = 0;
+ m_used_kernel_memory_size = 0;
+ m_ideal_core_id = 0;
+ m_flags = params.flags;
+ m_version = params.version;
+ m_program_id = params.program_id;
+ m_code_address = params.code_address;
+ m_code_size = params.code_num_pages * PageSize;
+ m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication);
+
+ // Set thread fields.
+ for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ m_running_threads[i] = nullptr;
+ m_pinned_threads[i] = nullptr;
+ m_running_thread_idle_counts[i] = 0;
+ m_running_thread_switch_counts[i] = 0;
+ }
- // An update is needed.
- KScheduler::SetSchedulerUpdateNeeded(m_kernel);
-}
+ // Set max memory based on address space type.
+ switch ((params.flags & Svc::CreateProcessFlag::AddressSpaceMask)) {
+ case Svc::CreateProcessFlag::AddressSpace32Bit:
+ case Svc::CreateProcessFlag::AddressSpace64BitDeprecated:
+ case Svc::CreateProcessFlag::AddressSpace64Bit:
+ m_max_process_memory = m_page_table.GetHeapRegionSize();
+ break;
+ case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias:
+ m_max_process_memory = m_page_table.GetHeapRegionSize() + m_page_table.GetAliasRegionSize();
+ break;
+ default:
+ UNREACHABLE();
+ }
-Result KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] KProcessAddress address,
- [[maybe_unused]] size_t size) {
- // Lock ourselves, to prevent concurrent access.
- KScopedLightLock lk(m_state_lock);
+ // Generate random entropy.
+ GenerateRandom(m_entropy);
- // Try to find an existing info for the memory.
- KSharedMemoryInfo* shemen_info = nullptr;
- const auto iter = std::find_if(
- m_shared_memory_list.begin(), m_shared_memory_list.end(),
- [shmem](const KSharedMemoryInfo* info) { return info->GetSharedMemory() == shmem; });
- if (iter != m_shared_memory_list.end()) {
- shemen_info = *iter;
- }
+ // Clear remaining fields.
+ m_num_running_threads = 0;
+ m_num_process_switches = 0;
+ m_num_thread_switches = 0;
+ m_num_fpu_switches = 0;
+ m_num_supervisor_calls = 0;
+ m_num_ipc_messages = 0;
- if (shemen_info == nullptr) {
- shemen_info = KSharedMemoryInfo::Allocate(m_kernel);
- R_UNLESS(shemen_info != nullptr, ResultOutOfMemory);
+ m_is_signaled = false;
+ m_exception_thread = nullptr;
+ m_is_suspended = false;
+ m_memory_release_hint = 0;
+ m_schedule_count = 0;
+ m_is_handle_table_initialized = false;
- shemen_info->Initialize(shmem);
- m_shared_memory_list.push_back(shemen_info);
- }
+ // Open a reference to our resource limit.
+ m_resource_limit = res_limit;
+ m_resource_limit->Open();
- // Open a reference to the shared memory and its info.
- shmem->Open();
- shemen_info->Open();
+ // We're initialized!
+ m_is_initialized = true;
R_SUCCEED();
}
-void KProcess::RemoveSharedMemory(KSharedMemory* shmem, [[maybe_unused]] KProcessAddress address,
- [[maybe_unused]] size_t size) {
- // Lock ourselves, to prevent concurrent access.
- KScopedLightLock lk(m_state_lock);
+Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg,
+ std::span<const u32> caps, KResourceLimit* res_limit,
+ KMemoryManager::Pool pool, bool immortal) {
+ ASSERT(res_limit != nullptr);
+ ASSERT((params.code_num_pages * PageSize) / PageSize ==
+ static_cast<size_t>(params.code_num_pages));
+
+ // Set members.
+ m_memory_pool = pool;
+ m_is_default_application_system_resource = false;
+ m_is_immortal = immortal;
+
+ // Setup our system resource.
+ if (const size_t system_resource_num_pages = params.system_resource_num_pages;
+ system_resource_num_pages != 0) {
+ // Create a secure system resource.
+ KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel);
+ R_UNLESS(secure_resource != nullptr, ResultOutOfResource);
+
+ ON_RESULT_FAILURE {
+ secure_resource->Close();
+ };
+
+ // Initialize the secure resource.
+ R_TRY(secure_resource->Initialize(system_resource_num_pages * PageSize, res_limit,
+ m_memory_pool));
+
+ // Set our system resource.
+ m_system_resource = secure_resource;
+ } else {
+ // Use the system-wide system resource.
+ const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication);
+ m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource()
+ : m_kernel.GetSystemSystemResource());
+
+ m_is_default_application_system_resource = is_app;
- KSharedMemoryInfo* shemen_info = nullptr;
- const auto iter = std::find_if(
- m_shared_memory_list.begin(), m_shared_memory_list.end(),
- [shmem](const KSharedMemoryInfo* info) { return info->GetSharedMemory() == shmem; });
- if (iter != m_shared_memory_list.end()) {
- shemen_info = *iter;
+ // Open reference to the system resource.
+ m_system_resource->Open();
}
- ASSERT(shemen_info != nullptr);
+ // Ensure we clean up our secure resource, if we fail.
+ ON_RESULT_FAILURE {
+ m_system_resource->Close();
+ m_system_resource = nullptr;
+ };
- if (shemen_info->Close()) {
- m_shared_memory_list.erase(iter);
- KSharedMemoryInfo::Free(m_kernel, shemen_info);
+ // Setup page table.
+ {
+ const auto as_type = params.flags & Svc::CreateProcessFlag::AddressSpaceMask;
+ const bool enable_aslr = True(params.flags & Svc::CreateProcessFlag::EnableAslr);
+ const bool enable_das_merge =
+ False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
+ R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
+ params.code_address, params.code_num_pages * PageSize,
+ m_system_resource, res_limit, this->GetMemory(), 0));
}
+ ON_RESULT_FAILURE_2 {
+ m_page_table.Finalize();
+ };
- // Close a reference to the shared memory.
- shmem->Close();
-}
+ // Ensure we can insert the code region.
+ R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize,
+ KMemoryState::Code),
+ ResultInvalidMemoryRegion);
-void KProcess::RegisterThread(KThread* thread) {
- KScopedLightLock lk{m_list_lock};
+ // Map the code region.
+ R_TRY(m_page_table.MapPageGroup(params.code_address, pg, KMemoryState::Code,
+ KMemoryPermission::KernelRead));
- m_thread_list.push_back(thread);
-}
+ // Initialize capabilities.
+ R_TRY(m_capabilities.InitializeForKip(caps, std::addressof(m_page_table)));
-void KProcess::UnregisterThread(KThread* thread) {
- KScopedLightLock lk{m_list_lock};
+ // Initialize the process id.
+ m_process_id = m_kernel.CreateNewUserProcessID();
+ ASSERT(InitialProcessIdMin <= m_process_id);
+ ASSERT(m_process_id <= InitialProcessIdMax);
- m_thread_list.remove(thread);
-}
+ // Initialize the rest of the process.
+ R_TRY(this->Initialize(params, res_limit, true));
-u64 KProcess::GetFreeThreadCount() const {
- if (m_resource_limit != nullptr) {
- const auto current_value =
- m_resource_limit->GetCurrentValue(LimitableResource::ThreadCountMax);
- const auto limit_value = m_resource_limit->GetLimitValue(LimitableResource::ThreadCountMax);
- return limit_value - current_value;
- } else {
- return 0;
- }
+ // We succeeded!
+ R_SUCCEED();
}
-Result KProcess::Reset() {
- // Lock the process and the scheduler.
- KScopedLightLock lk(m_state_lock);
- KScopedSchedulerLock sl{m_kernel};
+Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
+ std::span<const u32> user_caps, KResourceLimit* res_limit,
+ KMemoryManager::Pool pool, KProcessAddress aslr_space_start) {
+ ASSERT(res_limit != nullptr);
- // Validate that we're in a state that we can reset.
- R_UNLESS(m_state != State::Terminated, ResultInvalidState);
- R_UNLESS(m_is_signaled, ResultInvalidState);
+ // Set members.
+ m_memory_pool = pool;
+ m_is_default_application_system_resource = false;
+ m_is_immortal = false;
- // Clear signaled.
- m_is_signaled = false;
- R_SUCCEED();
-}
+ // Get the memory sizes.
+ const size_t code_num_pages = params.code_num_pages;
+ const size_t system_resource_num_pages = params.system_resource_num_pages;
+ const size_t code_size = code_num_pages * PageSize;
+ const size_t system_resource_size = system_resource_num_pages * PageSize;
-Result KProcess::SetActivity(ProcessActivity activity) {
- // Lock ourselves and the scheduler.
- KScopedLightLock lk{m_state_lock};
- KScopedLightLock list_lk{m_list_lock};
- KScopedSchedulerLock sl{m_kernel};
+ // Reserve memory for our code resource.
+ KScopedResourceReservation memory_reservation(
+ res_limit, Svc::LimitableResource::PhysicalMemoryMax, code_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
- // Validate our state.
- R_UNLESS(m_state != State::Terminating, ResultInvalidState);
- R_UNLESS(m_state != State::Terminated, ResultInvalidState);
+ // Setup our system resource.
+ if (system_resource_num_pages != 0) {
+ // Create a secure system resource.
+ KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel);
+ R_UNLESS(secure_resource != nullptr, ResultOutOfResource);
- // Either pause or resume.
- if (activity == ProcessActivity::Paused) {
- // Verify that we're not suspended.
- R_UNLESS(!m_is_suspended, ResultInvalidState);
+ ON_RESULT_FAILURE {
+ secure_resource->Close();
+ };
- // Suspend all threads.
- for (auto* thread : this->GetThreadList()) {
- thread->RequestSuspend(SuspendType::Process);
- }
+ // Initialize the secure resource.
+ R_TRY(secure_resource->Initialize(system_resource_size, res_limit, m_memory_pool));
+
+ // Set our system resource.
+ m_system_resource = secure_resource;
- // Set ourselves as suspended.
- this->SetSuspended(true);
} else {
- ASSERT(activity == ProcessActivity::Runnable);
+ // Use the system-wide system resource.
+ const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication);
+ m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource()
+ : m_kernel.GetSystemSystemResource());
- // Verify that we're suspended.
- R_UNLESS(m_is_suspended, ResultInvalidState);
+ m_is_default_application_system_resource = is_app;
- // Resume all threads.
- for (auto* thread : this->GetThreadList()) {
- thread->Resume(SuspendType::Process);
- }
+ // Open reference to the system resource.
+ m_system_resource->Open();
+ }
- // Set ourselves as resumed.
- this->SetSuspended(false);
+ // Ensure we clean up our secure resource, if we fail.
+ ON_RESULT_FAILURE {
+ m_system_resource->Close();
+ m_system_resource = nullptr;
+ };
+
+ // Setup page table.
+ {
+ const auto as_type = params.flags & Svc::CreateProcessFlag::AddressSpaceMask;
+ const bool enable_aslr = True(params.flags & Svc::CreateProcessFlag::EnableAslr);
+ const bool enable_das_merge =
+ False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
+ R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
+ params.code_address, code_size, m_system_resource, res_limit,
+ this->GetMemory(), aslr_space_start));
+ }
+ ON_RESULT_FAILURE_2 {
+ m_page_table.Finalize();
+ };
+
+ // Ensure we can insert the code region.
+ R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState::Code),
+ ResultInvalidMemoryRegion);
+
+ // Map the code region.
+ R_TRY(m_page_table.MapPages(params.code_address, code_num_pages, KMemoryState::Code,
+ KMemoryPermission::KernelRead | KMemoryPermission::NotMapped));
+
+ // Initialize capabilities.
+ R_TRY(m_capabilities.InitializeForUser(user_caps, std::addressof(m_page_table)));
+
+ // Initialize the process id.
+ m_process_id = m_kernel.CreateNewUserProcessID();
+ ASSERT(ProcessIdMin <= m_process_id);
+ ASSERT(m_process_id <= ProcessIdMax);
+
+ // If we should optimize memory allocations, do so.
+ if (m_system_resource->IsSecureResource() &&
+ True(params.flags & Svc::CreateProcessFlag::OptimizeMemoryAllocation)) {
+ R_TRY(m_kernel.MemoryManager().InitializeOptimizedMemory(m_process_id, pool));
}
+ // Initialize the rest of the process.
+ R_TRY(this->Initialize(params, res_limit, true));
+
+ // We succeeded, so commit our memory reservation.
+ memory_reservation.Commit();
R_SUCCEED();
}
-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;
+void KProcess::DoWorkerTaskImpl() {
+ // Terminate child threads.
+ TerminateChildren(m_kernel, this, nullptr);
- 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;
- }
+ // Finalize the handle table, if we're not immortal.
+ if (!m_is_immortal && m_is_handle_table_initialized) {
+ this->FinalizeHandleTable();
}
- KScopedResourceReservation memory_reservation(
- m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size);
- if (!memory_reservation.Succeeded()) {
- LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes",
- code_size + m_system_resource_size);
- R_RETURN(ResultLimitReached);
- }
- // Initialize process address space
- if (const Result result{m_page_table.InitializeForProcess(
- metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application,
- 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(this->GetEntryPoint(), code_size / PageSize,
- KMemoryState::Code,
- KMemoryPermission::None)};
- result.IsError()) {
- R_RETURN(result);
- }
-
- // Initialize process capabilities
- const auto& caps{metadata.GetKernelCapabilities()};
- if (const Result result{
- m_capabilities.InitializeForUserProcess(caps.data(), caps.size(), m_page_table)};
- result.IsError()) {
- R_RETURN(result);
- }
-
- // Set memory usage capacity
- switch (metadata.GetAddressSpaceType()) {
- case FileSys::ProgramAddressSpaceType::Is32Bit:
- case FileSys::ProgramAddressSpaceType::Is36Bit:
- case FileSys::ProgramAddressSpaceType::Is39Bit:
- m_memory_usage_capacity =
- m_page_table.GetHeapRegionEnd() - m_page_table.GetHeapRegionStart();
- break;
+ // Finish termination.
+ this->FinishTermination();
+}
- case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
- m_memory_usage_capacity =
- (m_page_table.GetHeapRegionEnd() - m_page_table.GetHeapRegionStart()) +
- (m_page_table.GetAliasRegionEnd() - m_page_table.GetAliasRegionStart());
- break;
+Result KProcess::StartTermination() {
+ // Finalize the handle table when we're done, if the process isn't immortal.
+ SCOPE_EXIT({
+ if (!m_is_immortal) {
+ this->FinalizeHandleTable();
+ }
+ });
- default:
- ASSERT(false);
- break;
- }
+ // Terminate child threads other than the current one.
+ R_RETURN(TerminateChildren(m_kernel, this, GetCurrentThreadPointer(m_kernel)));
+}
- // Create TLS region
- R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address)));
- memory_reservation.Commit();
+void KProcess::FinishTermination() {
+ // Only allow termination to occur if the process isn't immortal.
+ if (!m_is_immortal) {
+ // Release resource limit hint.
+ if (m_resource_limit != nullptr) {
+ m_memory_release_hint = this->GetUsedNonSystemUserPhysicalMemorySize();
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, 0,
+ m_memory_release_hint);
+ }
+
+ // Change state.
+ {
+ KScopedSchedulerLock sl(m_kernel);
+ this->ChangeState(State::Terminated);
+ }
- R_RETURN(m_handle_table.Initialize(m_capabilities.GetHandleTableSize()));
+ // Close.
+ this->Close();
+ }
}
-void KProcess::Run(s32 main_thread_priority, u64 stack_size) {
- ASSERT(this->AllocateMainThreadStack(stack_size) == ResultSuccess);
- m_resource_limit->Reserve(LimitableResource::ThreadCountMax, 1);
+void KProcess::Exit() {
+ // Determine whether we need to start terminating
+ bool needs_terminate = false;
+ {
+ KScopedLightLock lk(m_state_lock);
+ KScopedSchedulerLock sl(m_kernel);
+
+ ASSERT(m_state != State::Created);
+ ASSERT(m_state != State::CreatedAttached);
+ ASSERT(m_state != State::Crashed);
+ ASSERT(m_state != State::Terminated);
+ if (m_state == State::Running || m_state == State::RunningAttached ||
+ m_state == State::DebugBreak) {
+ this->ChangeState(State::Terminating);
+ needs_terminate = true;
+ }
+ }
- const std::size_t heap_capacity{m_memory_usage_capacity -
- (m_main_thread_stack_size + m_image_size)};
- ASSERT(!m_page_table.SetMaxHeapSize(heap_capacity).IsError());
+ // If we need to start termination, do so.
+ if (needs_terminate) {
+ this->StartTermination();
- this->ChangeState(State::Running);
+ // Register the process as a work task.
+ m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, this);
+ }
- SetupMainThread(m_kernel.System(), *this, main_thread_priority, m_main_thread_stack_top);
+ // Exit the current thread.
+ GetCurrentThread(m_kernel).Exit();
}
-void KProcess::PrepareForTermination() {
- this->ChangeState(State::Terminating);
+Result KProcess::Terminate() {
+ // Determine whether we need to start terminating.
+ bool needs_terminate = false;
+ {
+ KScopedLightLock lk(m_state_lock);
- const auto stop_threads = [this](const std::vector<KThread*>& in_thread_list) {
- for (auto* thread : in_thread_list) {
- if (thread->GetOwnerProcess() != this)
- continue;
+ // Check whether we're allowed to terminate.
+ R_UNLESS(m_state != State::Created, ResultInvalidState);
+ R_UNLESS(m_state != State::CreatedAttached, ResultInvalidState);
- if (thread == GetCurrentThreadPointer(m_kernel))
- continue;
+ KScopedSchedulerLock sl(m_kernel);
- // TODO(Subv): When are the other running/ready threads terminated?
- ASSERT_MSG(thread->GetState() == ThreadState::Waiting,
- "Exiting processes with non-waiting threads is currently unimplemented");
+ if (m_state == State::Running || m_state == State::RunningAttached ||
+ m_state == State::Crashed || m_state == State::DebugBreak) {
+ this->ChangeState(State::Terminating);
+ needs_terminate = true;
+ }
+ }
- thread->Exit();
+ // If we need to terminate, do so.
+ if (needs_terminate) {
+ // Start termination.
+ if (R_SUCCEEDED(this->StartTermination())) {
+ // Finish termination.
+ this->FinishTermination();
+ } else {
+ // Register the process as a work task.
+ m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit,
+ this);
}
- };
+ }
- stop_threads(m_kernel.System().GlobalSchedulerContext().GetThreadList());
+ R_SUCCEED();
+}
- this->DeleteThreadLocalRegion(m_plr_address);
- m_plr_address = 0;
+Result KProcess::AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) {
+ // Lock ourselves, to prevent concurrent access.
+ KScopedLightLock lk(m_state_lock);
- if (m_resource_limit) {
- m_resource_limit->Release(LimitableResource::PhysicalMemoryMax,
- m_main_thread_stack_size + m_image_size);
+ // Try to find an existing info for the memory.
+ KSharedMemoryInfo* info = nullptr;
+ for (auto it = m_shared_memory_list.begin(); it != m_shared_memory_list.end(); ++it) {
+ if (it->GetSharedMemory() == shmem) {
+ info = std::addressof(*it);
+ break;
+ }
}
- this->ChangeState(State::Terminated);
-}
+ // If we didn't find an info, create one.
+ if (info == nullptr) {
+ // Allocate a new info.
+ info = KSharedMemoryInfo::Allocate(m_kernel);
+ R_UNLESS(info != nullptr, ResultOutOfResource);
-void KProcess::Finalize() {
- // Free all shared memory infos.
- {
- auto it = m_shared_memory_list.begin();
- while (it != m_shared_memory_list.end()) {
- KSharedMemoryInfo* info = *it;
- KSharedMemory* shmem = info->GetSharedMemory();
+ // Initialize the info and add it to our list.
+ info->Initialize(shmem);
+ m_shared_memory_list.push_back(*info);
+ }
- while (!info->Close()) {
- shmem->Close();
- }
+ // Open a reference to the shared memory and its info.
+ shmem->Open();
+ info->Open();
- shmem->Close();
+ R_SUCCEED();
+}
- it = m_shared_memory_list.erase(it);
- KSharedMemoryInfo::Free(m_kernel, info);
+void KProcess::RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) {
+ // Lock ourselves, to prevent concurrent access.
+ KScopedLightLock lk(m_state_lock);
+
+ // Find an existing info for the memory.
+ KSharedMemoryInfo* info = nullptr;
+ auto it = m_shared_memory_list.begin();
+ for (; it != m_shared_memory_list.end(); ++it) {
+ if (it->GetSharedMemory() == shmem) {
+ info = std::addressof(*it);
+ break;
}
}
+ ASSERT(info != nullptr);
- // Release memory to the resource limit.
- if (m_resource_limit != nullptr) {
- m_resource_limit->Close();
- m_resource_limit = nullptr;
+ // Close a reference to the info and its memory.
+ if (info->Close()) {
+ m_shared_memory_list.erase(it);
+ KSharedMemoryInfo::Free(m_kernel, info);
}
- // Finalize the page table.
- m_page_table.Finalize();
-
- // Perform inherited finalization.
- KSynchronizationObject::Finalize();
+ shmem->Close();
}
Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
@@ -518,7 +601,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
// See if we can get a region from a partially used TLP.
{
- KScopedSchedulerLock sl{m_kernel};
+ KScopedSchedulerLock sl(m_kernel);
if (auto it = m_partially_used_tlp_tree.begin(); it != m_partially_used_tlp_tree.end()) {
tlr = it->Reserve();
@@ -538,7 +621,9 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
// Allocate a new page.
tlp = KThreadLocalPage::Allocate(m_kernel);
R_UNLESS(tlp != nullptr, ResultOutOfMemory);
- auto tlp_guard = SCOPE_GUARD({ KThreadLocalPage::Free(m_kernel, tlp); });
+ ON_RESULT_FAILURE {
+ KThreadLocalPage::Free(m_kernel, tlp);
+ };
// Initialize the new page.
R_TRY(tlp->Initialize(m_kernel, this));
@@ -549,7 +634,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
// Insert into our tree.
{
- KScopedSchedulerLock sl{m_kernel};
+ KScopedSchedulerLock sl(m_kernel);
if (tlp->IsAllUsed()) {
m_fully_used_tlp_tree.insert(*tlp);
} else {
@@ -558,7 +643,6 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
}
// We succeeded!
- tlp_guard.Cancel();
*out = tlr;
R_SUCCEED();
}
@@ -568,7 +652,7 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) {
// Release the region.
{
- KScopedSchedulerLock sl{m_kernel};
+ KScopedSchedulerLock sl(m_kernel);
// Try to find the page in the partially used list.
auto it = m_partially_used_tlp_tree.find_key(Common::AlignDown(GetInteger(addr), PageSize));
@@ -611,95 +695,213 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) {
R_SUCCEED();
}
-bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
- const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
- return wp.type == DebugWatchpointType::None;
- })};
+bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value) {
+ if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
+ return rl->Reserve(which, value);
+ } else {
+ return true;
+ }
+}
- if (watch == m_watchpoints.end()) {
- return false;
+bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout) {
+ if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
+ return rl->Reserve(which, value, timeout);
+ } else {
+ return true;
}
+}
- watch->start_address = addr;
- watch->end_address = addr + size;
- watch->type = type;
+void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value) {
+ if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
+ rl->Release(which, value);
+ }
+}
- for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
- page += PageSize) {
- m_debug_page_refcounts[page]++;
- this->GetMemory().MarkRegionDebug(page, PageSize, true);
+void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint) {
+ if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
+ rl->Release(which, value, hint);
}
+}
- return true;
+void KProcess::IncrementRunningThreadCount() {
+ ASSERT(m_num_running_threads.load() >= 0);
+
+ ++m_num_running_threads;
}
-bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
- const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
- return wp.start_address == addr && wp.end_address == addr + size && wp.type == type;
- })};
+void KProcess::DecrementRunningThreadCount() {
+ ASSERT(m_num_running_threads.load() > 0);
- if (watch == m_watchpoints.end()) {
+ if (const auto prev = m_num_running_threads--; prev == 1) {
+ this->Terminate();
+ }
+}
+
+bool KProcess::EnterUserException() {
+ // Get the current thread.
+ KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
+ ASSERT(this == cur_thread->GetOwnerProcess());
+
+ // Check that we haven't already claimed the exception thread.
+ if (m_exception_thread == cur_thread) {
return false;
}
- watch->start_address = 0;
- watch->end_address = 0;
- watch->type = DebugWatchpointType::None;
+ // Create the wait queue we'll be using.
+ ThreadQueueImplForKProcessEnterUserException wait_queue(m_kernel,
+ std::addressof(m_exception_thread));
- for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
- page += PageSize) {
- m_debug_page_refcounts[page]--;
- if (!m_debug_page_refcounts[page]) {
- this->GetMemory().MarkRegionDebug(page, PageSize, false);
+ // Claim the exception thread.
+ {
+ // Lock the scheduler.
+ KScopedSchedulerLock sl(m_kernel);
+
+ // Check that we're not terminating.
+ if (cur_thread->IsTerminationRequested()) {
+ return false;
}
+
+ // If we don't have an exception thread, we can just claim it directly.
+ if (m_exception_thread == nullptr) {
+ m_exception_thread = cur_thread;
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+ return true;
+ }
+
+ // Otherwise, we need to wait until we don't have an exception thread.
+
+ // Add the current thread as a waiter on the current exception thread.
+ cur_thread->SetKernelAddressKey(
+ reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1);
+ m_exception_thread->AddWaiter(cur_thread);
+
+ // Wait to claim the exception thread.
+ cur_thread->BeginWait(std::addressof(wait_queue));
}
- return true;
+ // If our wait didn't end due to thread termination, we succeeded.
+ return ResultTerminationRequested != cur_thread->GetWaitResult();
}
-void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
- const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
- Svc::MemoryPermission permission) {
- m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
- };
+bool KProcess::LeaveUserException() {
+ return this->ReleaseUserException(GetCurrentThreadPointer(m_kernel));
+}
- this->GetMemory().WriteBlock(base_addr, code_set.memory.data(), code_set.memory.size());
+bool KProcess::ReleaseUserException(KThread* thread) {
+ KScopedSchedulerLock sl(m_kernel);
- ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute);
- ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read);
- ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
+ if (m_exception_thread == thread) {
+ m_exception_thread = nullptr;
+
+ // Remove waiter thread.
+ bool has_waiters;
+ if (KThread* next = thread->RemoveKernelWaiterByKey(
+ std::addressof(has_waiters),
+ reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1);
+ next != nullptr) {
+ next->EndWait(ResultSuccess);
+ }
+
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+
+ return true;
+ } else {
+ return false;
+ }
}
-bool KProcess::IsSignaled() const {
- ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
- return m_is_signaled;
+void KProcess::RegisterThread(KThread* thread) {
+ KScopedLightLock lk(m_list_lock);
+
+ m_thread_list.push_back(*thread);
}
-KProcess::KProcess(KernelCore& kernel)
- : KAutoObjectWithSlabHeapAndContainer{kernel}, m_page_table{m_kernel.System()},
- m_handle_table{m_kernel}, m_address_arbiter{m_kernel.System()},
- m_condition_var{m_kernel.System()}, m_state_lock{m_kernel}, m_list_lock{m_kernel} {}
+void KProcess::UnregisterThread(KThread* thread) {
+ KScopedLightLock lk(m_list_lock);
-KProcess::~KProcess() = default;
+ m_thread_list.erase(m_thread_list.iterator_to(*thread));
+}
+
+size_t KProcess::GetUsedUserPhysicalMemorySize() const {
+ const size_t norm_size = m_page_table.GetNormalMemorySize();
+ const size_t other_size = m_code_size + m_main_thread_stack_size;
+ const size_t sec_size = this->GetRequiredSecureMemorySizeNonDefault();
+
+ return norm_size + other_size + sec_size;
+}
-void KProcess::ChangeState(State new_state) {
- if (m_state == new_state) {
- return;
+size_t KProcess::GetTotalUserPhysicalMemorySize() const {
+ // Get the amount of free and used size.
+ const size_t free_size =
+ m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax);
+ const size_t max_size = m_max_process_memory;
+
+ // Determine used size.
+ // NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike
+ // GetUsedUserPhysicalMemorySize().
+ const size_t norm_size = m_page_table.GetNormalMemorySize();
+ const size_t other_size = m_code_size + m_main_thread_stack_size;
+ const size_t sec_size = this->GetRequiredSecureMemorySize();
+ const size_t used_size = norm_size + other_size + sec_size;
+
+ // NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo
+ // does it this way.
+ if (used_size + free_size > max_size) {
+ return max_size;
+ } else {
+ return free_size + this->GetUsedUserPhysicalMemorySize();
}
+}
+
+size_t KProcess::GetUsedNonSystemUserPhysicalMemorySize() const {
+ const size_t norm_size = m_page_table.GetNormalMemorySize();
+ const size_t other_size = m_code_size + m_main_thread_stack_size;
- m_state = new_state;
- m_is_signaled = true;
- this->NotifyAvailable();
+ return norm_size + other_size;
}
-Result KProcess::AllocateMainThreadStack(std::size_t stack_size) {
+size_t KProcess::GetTotalNonSystemUserPhysicalMemorySize() const {
+ // Get the amount of free and used size.
+ const size_t free_size =
+ m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax);
+ const size_t max_size = m_max_process_memory;
+
+ // Determine used size.
+ // NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike
+ // GetUsedUserPhysicalMemorySize().
+ const size_t norm_size = m_page_table.GetNormalMemorySize();
+ const size_t other_size = m_code_size + m_main_thread_stack_size;
+ const size_t sec_size = this->GetRequiredSecureMemorySize();
+ const size_t used_size = norm_size + other_size + sec_size;
+
+ // NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo
+ // does it this way.
+ if (used_size + free_size > max_size) {
+ return max_size - this->GetRequiredSecureMemorySizeNonDefault();
+ } else {
+ return free_size + this->GetUsedNonSystemUserPhysicalMemorySize();
+ }
+}
+
+Result KProcess::Run(s32 priority, size_t stack_size) {
+ // Lock ourselves, to prevent concurrent access.
+ KScopedLightLock lk(m_state_lock);
+
+ // Validate that we're in a state where we can initialize.
+ const auto state = m_state;
+ R_UNLESS(state == State::Created || state == State::CreatedAttached, ResultInvalidState);
+
+ // Place a tentative reservation of a thread for this process.
+ KScopedResourceReservation thread_reservation(this, Svc::LimitableResource::ThreadCountMax);
+ R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached);
+
// Ensure that we haven't already allocated stack.
ASSERT(m_main_thread_stack_size == 0);
// Ensure that we're allocating a valid stack.
stack_size = Common::AlignUp(stack_size, PageSize);
- // R_UNLESS(stack_size + image_size <= m_max_process_memory, ResultOutOfMemory);
- R_UNLESS(stack_size + m_image_size >= m_image_size, ResultOutOfMemory);
+ R_UNLESS(stack_size + m_code_size <= m_max_process_memory, ResultOutOfMemory);
+ R_UNLESS(stack_size + m_code_size >= m_code_size, ResultOutOfMemory);
// Place a tentative reservation of memory for our new stack.
KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax,
@@ -707,21 +909,370 @@ Result KProcess::AllocateMainThreadStack(std::size_t stack_size) {
R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached);
// Allocate and map our stack.
+ KProcessAddress stack_top = 0;
if (stack_size) {
KProcessAddress stack_bottom;
R_TRY(m_page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize,
KMemoryState::Stack, KMemoryPermission::UserReadWrite));
- m_main_thread_stack_top = stack_bottom + stack_size;
+ stack_top = stack_bottom + stack_size;
m_main_thread_stack_size = stack_size;
}
+ // Ensure our stack is safe to clean up on exit.
+ ON_RESULT_FAILURE {
+ if (m_main_thread_stack_size) {
+ ASSERT(R_SUCCEEDED(m_page_table.UnmapPages(stack_top - m_main_thread_stack_size,
+ m_main_thread_stack_size / PageSize,
+ KMemoryState::Stack)));
+ m_main_thread_stack_size = 0;
+ }
+ };
+
+ // Set our maximum heap size.
+ R_TRY(m_page_table.SetMaxHeapSize(m_max_process_memory -
+ (m_main_thread_stack_size + m_code_size)));
+
+ // Initialize our handle table.
+ R_TRY(this->InitializeHandleTable(m_capabilities.GetHandleTableSize()));
+ ON_RESULT_FAILURE_2 {
+ this->FinalizeHandleTable();
+ };
+
+ // Create a new thread for the process.
+ KThread* main_thread = KThread::Create(m_kernel);
+ R_UNLESS(main_thread != nullptr, ResultOutOfResource);
+ SCOPE_EXIT({ main_thread->Close(); });
+
+ // Initialize the thread.
+ R_TRY(KThread::InitializeUserThread(m_kernel.System(), main_thread, this->GetEntryPoint(), 0,
+ stack_top, priority, m_ideal_core_id, this));
+
+ // Register the thread, and commit our reservation.
+ KThread::Register(m_kernel, main_thread);
+ thread_reservation.Commit();
+
+ // Add the thread to our handle table.
+ Handle thread_handle;
+ R_TRY(m_handle_table.Add(std::addressof(thread_handle), main_thread));
+
+ // Set the thread arguments.
+ main_thread->GetContext32().cpu_registers[0] = 0;
+ main_thread->GetContext64().cpu_registers[0] = 0;
+ main_thread->GetContext32().cpu_registers[1] = thread_handle;
+ main_thread->GetContext64().cpu_registers[1] = thread_handle;
+
+ // Update our state.
+ this->ChangeState((state == State::Created) ? State::Running : State::RunningAttached);
+ ON_RESULT_FAILURE_2 {
+ this->ChangeState(state);
+ };
+
+ // Suspend for debug, if we should.
+ if (m_kernel.System().DebuggerEnabled()) {
+ main_thread->RequestSuspend(SuspendType::Debug);
+ }
+
+ // Run our thread.
+ R_TRY(main_thread->Run());
+
+ // Open a reference to represent that we're running.
+ this->Open();
+
// We succeeded! Commit our memory reservation.
mem_reservation.Commit();
R_SUCCEED();
}
+Result KProcess::Reset() {
+ // Lock the process and the scheduler.
+ KScopedLightLock lk(m_state_lock);
+ KScopedSchedulerLock sl(m_kernel);
+
+ // Validate that we're in a state that we can reset.
+ R_UNLESS(m_state != State::Terminated, ResultInvalidState);
+ R_UNLESS(m_is_signaled, ResultInvalidState);
+
+ // Clear signaled.
+ m_is_signaled = false;
+ R_SUCCEED();
+}
+
+Result KProcess::SetActivity(Svc::ProcessActivity activity) {
+ // Lock ourselves and the scheduler.
+ KScopedLightLock lk(m_state_lock);
+ KScopedLightLock list_lk(m_list_lock);
+ KScopedSchedulerLock sl(m_kernel);
+
+ // Validate our state.
+ R_UNLESS(m_state != State::Terminating, ResultInvalidState);
+ R_UNLESS(m_state != State::Terminated, ResultInvalidState);
+
+ // Either pause or resume.
+ if (activity == Svc::ProcessActivity::Paused) {
+ // Verify that we're not suspended.
+ R_UNLESS(!m_is_suspended, ResultInvalidState);
+
+ // Suspend all threads.
+ auto end = this->GetThreadList().end();
+ for (auto it = this->GetThreadList().begin(); it != end; ++it) {
+ it->RequestSuspend(SuspendType::Process);
+ }
+
+ // Set ourselves as suspended.
+ this->SetSuspended(true);
+ } else {
+ ASSERT(activity == Svc::ProcessActivity::Runnable);
+
+ // Verify that we're suspended.
+ R_UNLESS(m_is_suspended, ResultInvalidState);
+
+ // Resume all threads.
+ auto end = this->GetThreadList().end();
+ for (auto it = this->GetThreadList().begin(); it != end; ++it) {
+ it->Resume(SuspendType::Process);
+ }
+
+ // Set ourselves as resumed.
+ this->SetSuspended(false);
+ }
+
+ R_SUCCEED();
+}
+
+void KProcess::PinCurrentThread() {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
+
+ // Get the current thread.
+ const s32 core_id = GetCurrentCoreId(m_kernel);
+ KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
+
+ // If the thread isn't terminated, pin it.
+ if (!cur_thread->IsTerminationRequested()) {
+ // Pin it.
+ this->PinThread(core_id, cur_thread);
+ cur_thread->Pin(core_id);
+
+ // An update is needed.
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+ }
+}
+
+void KProcess::UnpinCurrentThread() {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
+
+ // Get the current thread.
+ const s32 core_id = GetCurrentCoreId(m_kernel);
+ KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
+
+ // Unpin it.
+ cur_thread->Unpin();
+ this->UnpinThread(core_id, cur_thread);
+
+ // An update is needed.
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+}
+
+void KProcess::UnpinThread(KThread* thread) {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
+
+ // Get the thread's core id.
+ const auto core_id = thread->GetActiveCore();
+
+ // Unpin it.
+ this->UnpinThread(core_id, thread);
+ thread->Unpin();
+
+ // An update is needed.
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+}
+
+Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids,
+ s32 max_out_count) {
+ // TODO: use current memory reference
+ auto& memory = m_kernel.System().ApplicationMemory();
+
+ // Lock the list.
+ KScopedLightLock lk(m_list_lock);
+
+ // Iterate over the list.
+ s32 count = 0;
+ auto end = this->GetThreadList().end();
+ for (auto it = this->GetThreadList().begin(); it != end; ++it) {
+ // If we're within array bounds, write the id.
+ if (count < max_out_count) {
+ // Get the thread id.
+ KThread* thread = std::addressof(*it);
+ const u64 id = thread->GetId();
+
+ // Copy the id to userland.
+ memory.Write64(out_thread_ids + count * sizeof(u64), id);
+ }
+
+ // Increment the count.
+ ++count;
+ }
+
+ // We successfully iterated the list.
+ *out_num_threads = count;
+ R_SUCCEED();
+}
+
+void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {}
+
+KProcess::KProcess(KernelCore& kernel)
+ : KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel}, m_state_lock{kernel},
+ m_list_lock{kernel}, m_cond_var{kernel.System()}, m_address_arbiter{kernel.System()},
+ m_handle_table{kernel} {}
+KProcess::~KProcess() = default;
+
+Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
+ KProcessAddress aslr_space_start, bool is_hbl) {
+ // Create a resource limit for the process.
+ const auto physical_memory_size =
+ m_kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
+ auto* res_limit =
+ Kernel::CreateResourceLimitForProcess(m_kernel.System(), physical_memory_size);
+
+ // Ensure we maintain a clean state on exit.
+ SCOPE_EXIT({ res_limit->Close(); });
+
+ // Declare flags and code address.
+ Svc::CreateProcessFlag flag{};
+ u64 code_address{};
+
+ // We are an application.
+ flag |= Svc::CreateProcessFlag::IsApplication;
+
+ // If we are 64-bit, create as such.
+ if (metadata.Is64BitProgram()) {
+ flag |= Svc::CreateProcessFlag::Is64Bit;
+ }
+
+ // Set the address space type and code address.
+ switch (metadata.GetAddressSpaceType()) {
+ case FileSys::ProgramAddressSpaceType::Is39Bit:
+ flag |= Svc::CreateProcessFlag::AddressSpace64Bit;
+
+ // 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.
+ code_address = 0x8000'0000;
+ break;
+ case FileSys::ProgramAddressSpaceType::Is36Bit:
+ flag |= Svc::CreateProcessFlag::AddressSpace64BitDeprecated;
+ code_address = 0x800'0000;
+ break;
+ case FileSys::ProgramAddressSpaceType::Is32Bit:
+ flag |= Svc::CreateProcessFlag::AddressSpace32Bit;
+ code_address = 0x20'0000;
+ break;
+ case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
+ flag |= Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias;
+ code_address = 0x20'0000;
+ break;
+ }
+
+ Svc::CreateProcessParameter params{
+ .name = {},
+ .version = {},
+ .program_id = metadata.GetTitleID(),
+ .code_address = code_address + GetInteger(aslr_space_start),
+ .code_num_pages = static_cast<s32>(code_size / PageSize),
+ .flags = flag,
+ .reslimit = Svc::InvalidHandle,
+ .system_resource_num_pages = static_cast<s32>(metadata.GetSystemResourceSize() / PageSize),
+ };
+
+ // Set the process name.
+ const auto& name = metadata.GetName();
+ static_assert(sizeof(params.name) <= sizeof(name));
+ std::memcpy(params.name.data(), name.data(), sizeof(params.name));
+
+ // Initialize for application process.
+ R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit,
+ KMemoryManager::Pool::Application, aslr_space_start));
+
+ // Assign remaining properties.
+ m_is_hbl = is_hbl;
+ m_ideal_core_id = metadata.GetMainThreadCore();
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
+ const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
+ Svc::MemoryPermission permission) {
+ m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
+ };
+
+ this->GetMemory().WriteBlock(base_addr, code_set.memory.data(), code_set.memory.size());
+
+ ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute);
+ ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read);
+ ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
+
+#ifdef HAS_NCE
+ if (Settings::IsNceEnabled()) {
+ auto& buffer = m_kernel.System().DeviceMemory().buffer;
+ const auto& code = code_set.CodeSegment();
+ const auto& patch = code_set.PatchSegment();
+ buffer.Protect(GetInteger(base_addr + code.addr), code.size, true, true, true);
+ buffer.Protect(GetInteger(base_addr + patch.addr), patch.size, true, true, true);
+ ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None);
+ }
+#endif
+}
+
+bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
+ const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
+ return wp.type == DebugWatchpointType::None;
+ })};
+
+ if (watch == m_watchpoints.end()) {
+ return false;
+ }
+
+ watch->start_address = addr;
+ watch->end_address = addr + size;
+ watch->type = type;
+
+ for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
+ page += PageSize) {
+ m_debug_page_refcounts[page]++;
+ this->GetMemory().MarkRegionDebug(page, PageSize, true);
+ }
+
+ return true;
+}
+
+bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
+ const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
+ return wp.start_address == addr && wp.end_address == addr + size && wp.type == type;
+ })};
+
+ if (watch == m_watchpoints.end()) {
+ return false;
+ }
+
+ watch->start_address = 0;
+ watch->end_address = 0;
+ watch->type = DebugWatchpointType::None;
+
+ for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
+ page += PageSize) {
+ m_debug_page_refcounts[page]--;
+ if (!m_debug_page_refcounts[page]) {
+ this->GetMemory().MarkRegionDebug(page, PageSize, false);
+ }
+ }
+
+ return true;
+}
+
Core::Memory::Memory& KProcess::GetMemory() const {
// TODO: per-process memory
return m_kernel.System().ApplicationMemory();
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 146e07a57..d8cd0fdde 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -1,59 +1,24 @@
-// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
-#include <cstddef>
-#include <list>
#include <map>
-#include <string>
+
+#include "core/file_sys/program_metadata.h"
+#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/k_address_arbiter.h"
-#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/k_capabilities.h"
#include "core/hle/kernel/k_condition_variable.h"
#include "core/hle/kernel/k_handle_table.h"
-#include "core/hle/kernel/k_page_table.h"
-#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_page_table_manager.h"
+#include "core/hle/kernel/k_process_page_table.h"
+#include "core/hle/kernel/k_system_resource.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_thread_local_page.h"
-#include "core/hle/kernel/k_typed_address.h"
-#include "core/hle/kernel/k_worker_task.h"
-#include "core/hle/kernel/process_capability.h"
-#include "core/hle/kernel/slab_helpers.h"
-#include "core/hle/result.h"
-
-namespace Core {
-namespace Memory {
-class Memory;
-};
-
-class System;
-} // namespace Core
-
-namespace FileSys {
-class ProgramMetadata;
-}
namespace Kernel {
-class KernelCore;
-class KResourceLimit;
-class KThread;
-class KSharedMemoryInfo;
-class TLSPage;
-
-struct CodeSet;
-
-enum class MemoryRegion : u16 {
- APPLICATION = 1,
- SYSTEM = 2,
- BASE = 3,
-};
-
-enum class ProcessActivity : u32 {
- Runnable,
- Paused,
-};
-
enum class DebugWatchpointType : u8 {
None = 0,
Read = 1 << 0,
@@ -72,9 +37,6 @@ class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWor
KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject);
public:
- explicit KProcess(KernelCore& kernel);
- ~KProcess() override;
-
enum class State {
Created = static_cast<u32>(Svc::ProcessState::Created),
CreatedAttached = static_cast<u32>(Svc::ProcessState::CreatedAttached),
@@ -86,470 +48,502 @@ public:
DebugBreak = static_cast<u32>(Svc::ProcessState::DebugBreak),
};
- enum : u64 {
- /// Lowest allowed process ID for a kernel initial process.
- InitialKIPIDMin = 1,
- /// Highest allowed process ID for a kernel initial process.
- InitialKIPIDMax = 80,
+ using ThreadList = Common::IntrusiveListMemberTraits<&KThread::m_process_list_node>::ListType;
- /// Lowest allowed process ID for a userland process.
- ProcessIDMin = 81,
- /// Highest allowed process ID for a userland process.
- ProcessIDMax = 0xFFFFFFFFFFFFFFFF,
- };
+ static constexpr size_t AslrAlignment = 2_MiB;
- // Used to determine how process IDs are assigned.
- enum class ProcessType {
- KernelInternal,
- Userland,
- };
-
- static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
+public:
+ static constexpr u64 InitialProcessIdMin = 1;
+ static constexpr u64 InitialProcessIdMax = 0x50;
- static Result Initialize(KProcess* process, Core::System& system, std::string process_name,
- ProcessType type, KResourceLimit* res_limit);
+ static constexpr u64 ProcessIdMin = InitialProcessIdMax + 1;
+ static constexpr u64 ProcessIdMax = std::numeric_limits<u64>::max();
- /// Gets a reference to the process' page table.
- KPageTable& GetPageTable() {
- return m_page_table;
- }
+private:
+ using SharedMemoryInfoList = Common::IntrusiveListBaseTraits<KSharedMemoryInfo>::ListType;
+ using TLPTree =
+ Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
+ using TLPIterator = TLPTree::iterator;
- /// Gets const a reference to the process' page table.
- const KPageTable& GetPageTable() const {
- return m_page_table;
- }
+private:
+ KProcessPageTable m_page_table;
+ std::atomic<size_t> m_used_kernel_memory_size{};
+ TLPTree m_fully_used_tlp_tree{};
+ TLPTree m_partially_used_tlp_tree{};
+ s32 m_ideal_core_id{};
+ KResourceLimit* m_resource_limit{};
+ KSystemResource* m_system_resource{};
+ size_t m_memory_release_hint{};
+ State m_state{};
+ KLightLock m_state_lock;
+ KLightLock m_list_lock;
+ KConditionVariable m_cond_var;
+ KAddressArbiter m_address_arbiter;
+ std::array<u64, 4> m_entropy{};
+ bool m_is_signaled{};
+ bool m_is_initialized{};
+ bool m_is_application{};
+ bool m_is_default_application_system_resource{};
+ bool m_is_hbl{};
+ std::array<char, 13> m_name{};
+ std::atomic<u16> m_num_running_threads{};
+ Svc::CreateProcessFlag m_flags{};
+ KMemoryManager::Pool m_memory_pool{};
+ s64 m_schedule_count{};
+ KCapabilities m_capabilities{};
+ u64 m_program_id{};
+ u64 m_process_id{};
+ KProcessAddress m_code_address{};
+ size_t m_code_size{};
+ size_t m_main_thread_stack_size{};
+ size_t m_max_process_memory{};
+ u32 m_version{};
+ KHandleTable m_handle_table;
+ KProcessAddress m_plr_address{};
+ KThread* m_exception_thread{};
+ ThreadList m_thread_list{};
+ SharedMemoryInfoList m_shared_memory_list{};
+ bool m_is_suspended{};
+ bool m_is_immortal{};
+ bool m_is_handle_table_initialized{};
+ std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{};
+ std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{};
+ std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_switch_counts{};
+ std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{};
+ std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
+ std::map<KProcessAddress, u64> m_debug_page_refcounts{};
+ std::atomic<s64> m_cpu_time{};
+ std::atomic<s64> m_num_process_switches{};
+ std::atomic<s64> m_num_thread_switches{};
+ std::atomic<s64> m_num_fpu_switches{};
+ std::atomic<s64> m_num_supervisor_calls{};
+ std::atomic<s64> m_num_ipc_messages{};
+ std::atomic<s64> m_num_ipc_replies{};
+ std::atomic<s64> m_num_ipc_receives{};
+#ifdef HAS_NCE
+ std::unordered_map<u64, u64> m_post_handlers{};
+#endif
- /// Gets a reference to the process' handle table.
- KHandleTable& GetHandleTable() {
- return m_handle_table;
- }
+private:
+ Result StartTermination();
+ void FinishTermination();
- /// Gets a const reference to the process' handle table.
- const KHandleTable& GetHandleTable() const {
- return m_handle_table;
+ void PinThread(s32 core_id, KThread* thread) {
+ ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+ ASSERT(thread != nullptr);
+ ASSERT(m_pinned_threads[core_id] == nullptr);
+ m_pinned_threads[core_id] = thread;
}
- /// Gets a reference to process's memory.
- Core::Memory::Memory& GetMemory() const;
-
- Result SignalToAddress(KProcessAddress address) {
- return m_condition_var.SignalToAddress(address);
+ void UnpinThread(s32 core_id, KThread* thread) {
+ ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+ ASSERT(thread != nullptr);
+ ASSERT(m_pinned_threads[core_id] == thread);
+ m_pinned_threads[core_id] = nullptr;
}
- Result WaitForAddress(Handle handle, KProcessAddress address, u32 tag) {
- return m_condition_var.WaitForAddress(handle, address, tag);
- }
+public:
+ explicit KProcess(KernelCore& kernel);
+ ~KProcess() override;
- void SignalConditionVariable(u64 cv_key, int32_t count) {
- return m_condition_var.Signal(cv_key, count);
- }
+ Result Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit,
+ bool is_real);
- Result WaitConditionVariable(KProcessAddress address, u64 cv_key, u32 tag, s64 ns) {
- R_RETURN(m_condition_var.Wait(address, cv_key, tag, ns));
- }
+ Result Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg,
+ std::span<const u32> caps, KResourceLimit* res_limit,
+ KMemoryManager::Pool pool, bool immortal);
+ Result Initialize(const Svc::CreateProcessParameter& params, std::span<const u32> user_caps,
+ KResourceLimit* res_limit, KMemoryManager::Pool pool,
+ KProcessAddress aslr_space_start);
+ void Exit();
- Result SignalAddressArbiter(uint64_t address, Svc::SignalType signal_type, s32 value,
- s32 count) {
- R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count));
+ const char* GetName() const {
+ return m_name.data();
}
- Result WaitAddressArbiter(uint64_t address, Svc::ArbitrationType arb_type, s32 value,
- s64 timeout) {
- R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout));
+ u64 GetProgramId() const {
+ return m_program_id;
}
- KProcessAddress GetProcessLocalRegionAddress() const {
- return m_plr_address;
+ u64 GetProcessId() const {
+ return m_process_id;
}
- /// Gets the current status of the process
State GetState() const {
return m_state;
}
- /// Gets the unique ID that identifies this particular process.
- u64 GetProcessId() const {
- return m_process_id;
+ u64 GetCoreMask() const {
+ return m_capabilities.GetCoreMask();
+ }
+ u64 GetPhysicalCoreMask() const {
+ return m_capabilities.GetPhysicalCoreMask();
+ }
+ u64 GetPriorityMask() const {
+ return m_capabilities.GetPriorityMask();
}
- /// Gets the program ID corresponding to this process.
- u64 GetProgramId() const {
- return m_program_id;
+ s32 GetIdealCoreId() const {
+ return m_ideal_core_id;
+ }
+ void SetIdealCoreId(s32 core_id) {
+ m_ideal_core_id = core_id;
}
- KProcessAddress GetEntryPoint() const {
- return m_code_address;
+ bool CheckThreadPriority(s32 prio) const {
+ return ((1ULL << prio) & this->GetPriorityMask()) != 0;
}
- /// Gets the resource limit descriptor for this process
- KResourceLimit* GetResourceLimit() const;
+ u32 GetCreateProcessFlags() const {
+ return static_cast<u32>(m_flags);
+ }
- /// Gets the ideal CPU core ID for this process
- u8 GetIdealCoreId() const {
- return m_ideal_core;
+ bool Is64Bit() const {
+ return True(m_flags & Svc::CreateProcessFlag::Is64Bit);
}
- /// Checks if the specified thread priority is valid.
- bool CheckThreadPriority(s32 prio) const {
- return ((1ULL << prio) & GetPriorityMask()) != 0;
+ KProcessAddress GetEntryPoint() const {
+ return m_code_address;
}
- /// Gets the bitmask of allowed cores that this process' threads can run on.
- u64 GetCoreMask() const {
- return m_capabilities.GetCoreMask();
+ size_t GetMainStackSize() const {
+ return m_main_thread_stack_size;
}
- /// Gets the bitmask of allowed thread priorities.
- u64 GetPriorityMask() const {
- return m_capabilities.GetPriorityMask();
+ KMemoryManager::Pool GetMemoryPool() const {
+ return m_memory_pool;
}
- /// Gets the amount of secure memory to allocate for memory management.
- u32 GetSystemResourceSize() const {
- return m_system_resource_size;
+ u64 GetRandomEntropy(size_t i) const {
+ return m_entropy[i];
}
- /// Gets the amount of secure memory currently in use for memory management.
- u32 GetSystemResourceUsage() const {
- // On hardware, this returns the amount of system resource memory that has
- // been used by the kernel. This is problematic for Yuzu to emulate, because
- // system resource memory is used for page tables -- and yuzu doesn't really
- // have a way to calculate how much memory is required for page tables for
- // the current process at any given time.
- // TODO: Is this even worth implementing? Games may retrieve this value via
- // an SDK function that gets used + available system resource size for debug
- // or diagnostic purposes. However, it seems unlikely that a game would make
- // decisions based on how much system memory is dedicated to its page tables.
- // Is returning a value other than zero wise?
- return 0;
+ bool IsApplication() const {
+ return m_is_application;
}
- /// Whether this process is an AArch64 or AArch32 process.
- bool Is64BitProcess() const {
- return m_is_64bit_process;
+ bool IsDefaultApplicationSystemResource() const {
+ return m_is_default_application_system_resource;
}
bool IsSuspended() const {
return m_is_suspended;
}
-
void SetSuspended(bool suspended) {
m_is_suspended = suspended;
}
- /// Gets the total running time of the process instance in ticks.
- u64 GetCPUTimeTicks() const {
- return m_total_process_running_time_ticks;
+ Result Terminate();
+
+ bool IsTerminated() const {
+ return m_state == State::Terminated;
}
- /// Updates the total running time, adding the given ticks to it.
- void UpdateCPUTimeTicks(u64 ticks) {
- m_total_process_running_time_ticks += ticks;
+ bool IsPermittedSvc(u32 svc_id) const {
+ return m_capabilities.IsPermittedSvc(svc_id);
}
- /// Gets the process schedule count, used for thread yielding
- s64 GetScheduledCount() const {
- return m_schedule_count;
+ bool IsPermittedInterrupt(s32 interrupt_id) const {
+ return m_capabilities.IsPermittedInterrupt(interrupt_id);
}
- /// Increments the process schedule count, used for thread yielding.
- void IncrementScheduledCount() {
- ++m_schedule_count;
+ bool IsPermittedDebug() const {
+ return m_capabilities.IsPermittedDebug();
}
- void IncrementRunningThreadCount();
- void DecrementRunningThreadCount();
+ bool CanForceDebug() const {
+ return m_capabilities.CanForceDebug();
+ }
- void SetRunningThread(s32 core, KThread* thread, u64 idle_count) {
- m_running_threads[core] = thread;
- m_running_thread_idle_counts[core] = idle_count;
+ bool IsHbl() const {
+ return m_is_hbl;
}
- void ClearRunningThread(KThread* thread) {
- for (size_t i = 0; i < m_running_threads.size(); ++i) {
- if (m_running_threads[i] == thread) {
- m_running_threads[i] = nullptr;
- }
- }
+ u32 GetAllocateOption() const {
+ return m_page_table.GetAllocateOption();
}
- [[nodiscard]] KThread* GetRunningThread(s32 core) const {
- return m_running_threads[core];
+ ThreadList& GetThreadList() {
+ return m_thread_list;
+ }
+ const ThreadList& GetThreadList() const {
+ return m_thread_list;
}
+ bool EnterUserException();
+ bool LeaveUserException();
bool ReleaseUserException(KThread* thread);
- [[nodiscard]] KThread* GetPinnedThread(s32 core_id) const {
+ KThread* GetPinnedThread(s32 core_id) const {
ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
return m_pinned_threads[core_id];
}
- /// Gets 8 bytes of random data for svcGetInfo RandomEntropy
- u64 GetRandomEntropy(std::size_t index) const {
- return m_random_entropy.at(index);
+ const Svc::SvcAccessFlagSet& GetSvcPermissions() const {
+ return m_capabilities.GetSvcPermissions();
}
- /// Retrieves the total physical memory available to this process in bytes.
- u64 GetTotalPhysicalMemoryAvailable();
-
- /// Retrieves the total physical memory available to this process in bytes,
- /// without the size of the personal system resource heap added to it.
- u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource();
-
- /// Retrieves the total physical memory used by this process in bytes.
- u64 GetTotalPhysicalMemoryUsed();
+ KResourceLimit* GetResourceLimit() const {
+ return m_resource_limit;
+ }
- /// Retrieves the total physical memory used by this process in bytes,
- /// without the size of the personal system resource heap added to it.
- u64 GetTotalPhysicalMemoryUsedWithoutSystemResource();
+ bool ReserveResource(Svc::LimitableResource which, s64 value);
+ bool ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout);
+ void ReleaseResource(Svc::LimitableResource which, s64 value);
+ void ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint);
- /// Gets the list of all threads created with this process as their owner.
- std::list<KThread*>& GetThreadList() {
- return m_thread_list;
+ KLightLock& GetStateLock() {
+ return m_state_lock;
+ }
+ KLightLock& GetListLock() {
+ return m_list_lock;
}
- /// Registers a thread as being created under this process,
- /// adding it to this process' thread list.
- void RegisterThread(KThread* thread);
+ KProcessPageTable& GetPageTable() {
+ return m_page_table;
+ }
+ const KProcessPageTable& GetPageTable() const {
+ return m_page_table;
+ }
- /// Unregisters a thread from this process, removing it
- /// from this process' thread list.
- void UnregisterThread(KThread* thread);
+ KHandleTable& GetHandleTable() {
+ return m_handle_table;
+ }
+ const KHandleTable& GetHandleTable() const {
+ return m_handle_table;
+ }
- /// Retrieves the number of available threads for this process.
- u64 GetFreeThreadCount() const;
-
- /// Clears the signaled state of the process if and only if it's signaled.
- ///
- /// @pre The process must not be already terminated. If this is called on a
- /// terminated process, then ResultInvalidState will be returned.
- ///
- /// @pre The process must be in a signaled state. If this is called on a
- /// process instance that is not signaled, ResultInvalidState will be
- /// returned.
- Result Reset();
+ size_t GetUsedUserPhysicalMemorySize() const;
+ size_t GetTotalUserPhysicalMemorySize() const;
+ size_t GetUsedNonSystemUserPhysicalMemorySize() const;
+ size_t GetTotalNonSystemUserPhysicalMemorySize() const;
- /**
- * Loads process-specifics configuration info with metadata provided
- * by an executable.
- *
- * @param metadata The provided metadata to load process specific info from.
- *
- * @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,
- bool is_hbl);
-
- /**
- * Starts the main application thread for this process.
- *
- * @param main_thread_priority The priority for the main thread.
- * @param stack_size The stack size for the main thread in bytes.
- */
- void Run(s32 main_thread_priority, u64 stack_size);
-
- /**
- * Prepares a process for termination by stopping all of its threads
- * and clearing any other resources.
- */
- void PrepareForTermination();
+ Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
+ void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
- void LoadModule(CodeSet code_set, KProcessAddress base_addr);
+ Result CreateThreadLocalRegion(KProcessAddress* out);
+ Result DeleteThreadLocalRegion(KProcessAddress addr);
- bool IsInitialized() const override {
- return m_is_initialized;
+ KProcessAddress GetProcessLocalRegionAddress() const {
+ return m_plr_address;
}
- static void PostDestroy(uintptr_t arg) {}
-
- void Finalize() override;
-
- u64 GetId() const override {
- return GetProcessId();
+ KThread* GetExceptionThread() const {
+ return m_exception_thread;
}
- bool IsHbl() const {
- return m_is_hbl;
+ void AddCpuTime(s64 diff) {
+ m_cpu_time += diff;
+ }
+ s64 GetCpuTime() {
+ return m_cpu_time.load();
}
- bool IsSignaled() const override;
-
- void DoWorkerTaskImpl();
+ s64 GetScheduledCount() const {
+ return m_schedule_count;
+ }
+ void IncrementScheduledCount() {
+ ++m_schedule_count;
+ }
- Result SetActivity(ProcessActivity activity);
+ void IncrementRunningThreadCount();
+ void DecrementRunningThreadCount();
- void PinCurrentThread(s32 core_id);
- void UnpinCurrentThread(s32 core_id);
- void UnpinThread(KThread* thread);
+ size_t GetRequiredSecureMemorySizeNonDefault() const {
+ if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
+ auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
+ return secure_system_resource->CalculateRequiredSecureMemorySize();
+ }
- KLightLock& GetStateLock() {
- return m_state_lock;
+ return 0;
}
- Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
- void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Thread-local storage management
-
- // Marks the next available region as used and returns the address of the slot.
- [[nodiscard]] Result CreateThreadLocalRegion(KProcessAddress* out);
+ size_t GetRequiredSecureMemorySize() const {
+ if (m_system_resource->IsSecureResource()) {
+ auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
+ return secure_system_resource->CalculateRequiredSecureMemorySize();
+ }
- // Frees a used TLS slot identified by the given address
- Result DeleteThreadLocalRegion(KProcessAddress addr);
+ return 0;
+ }
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Debug watchpoint management
+ size_t GetTotalSystemResourceSize() const {
+ if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
+ auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
+ return secure_system_resource->GetSize();
+ }
- // Attempts to insert a watchpoint into a free slot. Returns false if none are available.
- bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
+ return 0;
+ }
- // Attempts to remove the watchpoint specified by the given parameters.
- bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
+ size_t GetUsedSystemResourceSize() const {
+ if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
+ auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
+ return secure_system_resource->GetUsedSize();
+ }
- const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const {
- return m_watchpoints;
+ return 0;
}
- const std::string& GetName() {
- return name;
+ void SetRunningThread(s32 core, KThread* thread, u64 idle_count, u64 switch_count) {
+ m_running_threads[core] = thread;
+ m_running_thread_idle_counts[core] = idle_count;
+ m_running_thread_switch_counts[core] = switch_count;
}
-private:
- void PinThread(s32 core_id, KThread* thread) {
- ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
- ASSERT(thread != nullptr);
- ASSERT(m_pinned_threads[core_id] == nullptr);
- m_pinned_threads[core_id] = thread;
+ void ClearRunningThread(KThread* thread) {
+ for (size_t i = 0; i < m_running_threads.size(); ++i) {
+ if (m_running_threads[i] == thread) {
+ m_running_threads[i] = nullptr;
+ }
+ }
}
- void UnpinThread(s32 core_id, KThread* thread) {
- ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
- ASSERT(thread != nullptr);
- ASSERT(m_pinned_threads[core_id] == thread);
- m_pinned_threads[core_id] = nullptr;
+ const KSystemResource& GetSystemResource() const {
+ return *m_system_resource;
}
- void FinalizeHandleTable() {
- // Finalize the table.
- m_handle_table.Finalize();
-
- // Note that the table is finalized.
- m_is_handle_table_initialized = false;
+ const KMemoryBlockSlabManager& GetMemoryBlockSlabManager() const {
+ return m_system_resource->GetMemoryBlockSlabManager();
+ }
+ const KBlockInfoManager& GetBlockInfoManager() const {
+ return m_system_resource->GetBlockInfoManager();
+ }
+ const KPageTableManager& GetPageTableManager() const {
+ return m_system_resource->GetPageTableManager();
}
- void ChangeState(State new_state);
-
- /// Allocates the main thread stack for the process, given the stack size in bytes.
- Result AllocateMainThreadStack(std::size_t stack_size);
-
- /// Memory manager for this process
- KPageTable m_page_table;
+ KThread* GetRunningThread(s32 core) const {
+ return m_running_threads[core];
+ }
+ u64 GetRunningThreadIdleCount(s32 core) const {
+ return m_running_thread_idle_counts[core];
+ }
+ u64 GetRunningThreadSwitchCount(s32 core) const {
+ return m_running_thread_switch_counts[core];
+ }
- /// Current status of the process
- State m_state{};
+ void RegisterThread(KThread* thread);
+ void UnregisterThread(KThread* thread);
- /// The ID of this process
- u64 m_process_id = 0;
+ Result Run(s32 priority, size_t stack_size);
- /// Title ID corresponding to the process
- u64 m_program_id = 0;
+ Result Reset();
- /// Specifies additional memory to be reserved for the process's memory management by the
- /// system. When this is non-zero, secure memory is allocated and used for page table allocation
- /// instead of using the normal global page tables/memory block management.
- u32 m_system_resource_size = 0;
+ void SetDebugBreak() {
+ if (m_state == State::RunningAttached) {
+ this->ChangeState(State::DebugBreak);
+ }
+ }
- /// Resource limit descriptor for this process
- KResourceLimit* m_resource_limit{};
+ void SetAttached() {
+ if (m_state == State::DebugBreak) {
+ this->ChangeState(State::RunningAttached);
+ }
+ }
- KVirtualAddress m_system_resource_address{};
+ Result SetActivity(Svc::ProcessActivity activity);
- /// The ideal CPU core for this process, threads are scheduled on this core by default.
- u8 m_ideal_core = 0;
+ void PinCurrentThread();
+ void UnpinCurrentThread();
+ void UnpinThread(KThread* thread);
- /// Contains the parsed process capability descriptors.
- ProcessCapabilities m_capabilities;
+ void SignalConditionVariable(uintptr_t cv_key, int32_t count) {
+ return m_cond_var.Signal(cv_key, count);
+ }
- /// Whether or not this process is AArch64, or AArch32.
- /// By default, we currently assume this is true, unless otherwise
- /// specified by metadata provided to the process during loading.
- bool m_is_64bit_process = true;
+ Result WaitConditionVariable(KProcessAddress address, uintptr_t cv_key, u32 tag, s64 ns) {
+ R_RETURN(m_cond_var.Wait(address, cv_key, tag, ns));
+ }
- /// Total running time for the process in ticks.
- std::atomic<u64> m_total_process_running_time_ticks = 0;
+ Result SignalAddressArbiter(uintptr_t address, Svc::SignalType signal_type, s32 value,
+ s32 count) {
+ R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count));
+ }
- /// Per-process handle table for storing created object handles in.
- KHandleTable m_handle_table;
+ Result WaitAddressArbiter(uintptr_t address, Svc::ArbitrationType arb_type, s32 value,
+ s64 timeout) {
+ R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout));
+ }
- /// Per-process address arbiter.
- KAddressArbiter m_address_arbiter;
+ Result GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids, s32 max_out_count);
- /// The per-process mutex lock instance used for handling various
- /// forms of services, such as lock arbitration, and condition
- /// variable related facilities.
- KConditionVariable m_condition_var;
+ static void Switch(KProcess* cur_process, KProcess* next_process);
- /// Address indicating the location of the process' dedicated TLS region.
- KProcessAddress m_plr_address = 0;
+#ifdef HAS_NCE
+ std::unordered_map<u64, u64>& GetPostHandlers() noexcept {
+ return m_post_handlers;
+ }
+#endif
- /// Address indicating the location of the process's entry point.
- KProcessAddress m_code_address = 0;
+public:
+ // Attempts to insert a watchpoint into a free slot. Returns false if none are available.
+ bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
- /// Random values for svcGetInfo RandomEntropy
- std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{};
+ // Attempts to remove the watchpoint specified by the given parameters.
+ bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
- /// List of threads that are running with this process as their owner.
- std::list<KThread*> m_thread_list;
+ const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const {
+ return m_watchpoints;
+ }
- /// List of shared memory that are running with this process as their owner.
- std::list<KSharedMemoryInfo*> m_shared_memory_list;
+public:
+ Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
+ KProcessAddress aslr_space_start, bool is_hbl);
- /// Address of the top of the main thread's stack
- KProcessAddress m_main_thread_stack_top{};
+ void LoadModule(CodeSet code_set, KProcessAddress base_addr);
- /// Size of the main thread's stack
- std::size_t m_main_thread_stack_size{};
+ Core::Memory::Memory& GetMemory() const;
- /// Memory usage capacity for the process
- std::size_t m_memory_usage_capacity{};
+public:
+ // Overridden parent functions.
+ bool IsInitialized() const override {
+ return m_is_initialized;
+ }
- /// Process total image size
- std::size_t m_image_size{};
+ static void PostDestroy(uintptr_t arg) {}
- /// Schedule count of this process
- s64 m_schedule_count{};
+ void Finalize() override;
- size_t m_memory_release_hint{};
+ u64 GetIdImpl() const {
+ return this->GetProcessId();
+ }
+ u64 GetId() const override {
+ return this->GetIdImpl();
+ }
- std::string name{};
+ virtual bool IsSignaled() const override {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
+ return m_is_signaled;
+ }
- bool m_is_signaled{};
- bool m_is_suspended{};
- bool m_is_immortal{};
- bool m_is_handle_table_initialized{};
- bool m_is_initialized{};
- bool m_is_hbl{};
+ void DoWorkerTaskImpl();
- std::atomic<u16> m_num_running_threads{};
+private:
+ void ChangeState(State new_state) {
+ if (m_state != new_state) {
+ m_state = new_state;
+ m_is_signaled = true;
+ this->NotifyAvailable();
+ }
+ }
- std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{};
- std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{};
- std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{};
- std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
- std::map<KProcessAddress, u64> m_debug_page_refcounts;
+ Result InitializeHandleTable(s32 size) {
+ // Try to initialize the handle table.
+ R_TRY(m_handle_table.Initialize(size));
- KThread* m_exception_thread{};
+ // We succeeded, so note that we did.
+ m_is_handle_table_initialized = true;
+ R_SUCCEED();
+ }
- KLightLock m_state_lock;
- KLightLock m_list_lock;
+ void FinalizeHandleTable() {
+ // Finalize the table.
+ m_handle_table.Finalize();
- using TLPTree =
- Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
- using TLPIterator = TLPTree::iterator;
- TLPTree m_fully_used_tlp_tree;
- TLPTree m_partially_used_tlp_tree;
+ // Note that the table is finalized.
+ m_is_handle_table_initialized = false;
+ }
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_process_page_table.h b/src/core/hle/kernel/k_process_page_table.h
new file mode 100644
index 000000000..9e40f68bc
--- /dev/null
+++ b/src/core/hle/kernel/k_process_page_table.h
@@ -0,0 +1,481 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_scoped_lock.h"
+#include "core/hle/kernel/svc_types.h"
+
+namespace Core {
+class ARM_Interface;
+}
+
+namespace Kernel {
+
+class KProcessPageTable {
+private:
+ KPageTable m_page_table;
+
+public:
+ KProcessPageTable(KernelCore& kernel) : m_page_table(kernel) {}
+
+ Result Initialize(Svc::CreateProcessFlag as_type, bool enable_aslr, bool enable_das_merge,
+ bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address,
+ size_t code_size, KSystemResource* system_resource,
+ KResourceLimit* resource_limit, Core::Memory::Memory& memory,
+ KProcessAddress aslr_space_start) {
+ R_RETURN(m_page_table.InitializeForProcess(
+ as_type, enable_aslr, enable_das_merge, from_back, pool, code_address, code_size,
+ system_resource, resource_limit, memory, aslr_space_start));
+ }
+
+ void Finalize() {
+ m_page_table.Finalize();
+ }
+
+ Core::Memory::Memory& GetMemory() {
+ return m_page_table.GetMemory();
+ }
+
+ Core::Memory::Memory& GetMemory() const {
+ return m_page_table.GetMemory();
+ }
+
+ Common::PageTable& GetImpl() {
+ return m_page_table.GetImpl();
+ }
+
+ Common::PageTable& GetImpl() const {
+ return m_page_table.GetImpl();
+ }
+
+ size_t GetNumGuardPages() const {
+ return m_page_table.GetNumGuardPages();
+ }
+
+ KScopedLightLock AcquireDeviceMapLock() {
+ return m_page_table.AcquireDeviceMapLock();
+ }
+
+ Result SetMemoryPermission(KProcessAddress addr, size_t size, Svc::MemoryPermission perm) {
+ R_RETURN(m_page_table.SetMemoryPermission(addr, size, perm));
+ }
+
+ Result SetProcessMemoryPermission(KProcessAddress addr, size_t size,
+ Svc::MemoryPermission perm) {
+ R_RETURN(m_page_table.SetProcessMemoryPermission(addr, size, perm));
+ }
+
+ Result SetMemoryAttribute(KProcessAddress addr, size_t size, KMemoryAttribute mask,
+ KMemoryAttribute attr) {
+ R_RETURN(m_page_table.SetMemoryAttribute(addr, size, mask, attr));
+ }
+
+ Result SetHeapSize(KProcessAddress* out, size_t size) {
+ R_RETURN(m_page_table.SetHeapSize(out, size));
+ }
+
+ Result SetMaxHeapSize(size_t size) {
+ R_RETURN(m_page_table.SetMaxHeapSize(size));
+ }
+
+ Result QueryInfo(KMemoryInfo* out_info, Svc::PageInfo* out_page_info,
+ KProcessAddress addr) const {
+ R_RETURN(m_page_table.QueryInfo(out_info, out_page_info, addr));
+ }
+
+ Result QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out, KProcessAddress address) {
+ R_RETURN(m_page_table.QueryPhysicalAddress(out, address));
+ }
+
+ Result QueryStaticMapping(KProcessAddress* out, KPhysicalAddress address, size_t size) {
+ R_RETURN(m_page_table.QueryStaticMapping(out, address, size));
+ }
+
+ Result QueryIoMapping(KProcessAddress* out, KPhysicalAddress address, size_t size) {
+ R_RETURN(m_page_table.QueryIoMapping(out, address, size));
+ }
+
+ Result MapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) {
+ R_RETURN(m_page_table.MapMemory(dst_address, src_address, size));
+ }
+
+ Result UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) {
+ R_RETURN(m_page_table.UnmapMemory(dst_address, src_address, size));
+ }
+
+ Result MapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) {
+ R_RETURN(m_page_table.MapCodeMemory(dst_address, src_address, size));
+ }
+
+ Result UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) {
+ R_RETURN(m_page_table.UnmapCodeMemory(dst_address, src_address, size));
+ }
+
+ Result MapIo(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm) {
+ R_RETURN(m_page_table.MapIo(phys_addr, size, perm));
+ }
+
+ Result MapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size,
+ Svc::MemoryMapping mapping, Svc::MemoryPermission perm) {
+ R_RETURN(m_page_table.MapIoRegion(dst_address, phys_addr, size, mapping, perm));
+ }
+
+ Result UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size,
+ Svc::MemoryMapping mapping) {
+ R_RETURN(m_page_table.UnmapIoRegion(dst_address, phys_addr, size, mapping));
+ }
+
+ Result MapStatic(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm) {
+ R_RETURN(m_page_table.MapStatic(phys_addr, size, perm));
+ }
+
+ Result MapRegion(KMemoryRegionType region_type, KMemoryPermission perm) {
+ R_RETURN(m_page_table.MapRegion(region_type, perm));
+ }
+
+ Result MapInsecureMemory(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.MapInsecureMemory(address, size));
+ }
+
+ Result UnmapInsecureMemory(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.UnmapInsecureMemory(address, size));
+ }
+
+ Result MapPageGroup(KProcessAddress addr, const KPageGroup& pg, KMemoryState state,
+ KMemoryPermission perm) {
+ R_RETURN(m_page_table.MapPageGroup(addr, pg, state, perm));
+ }
+
+ Result UnmapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state) {
+ R_RETURN(m_page_table.UnmapPageGroup(address, pg, state));
+ }
+
+ Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
+ KPhysicalAddress phys_addr, KMemoryState state, KMemoryPermission perm) {
+ R_RETURN(m_page_table.MapPages(out_addr, num_pages, alignment, phys_addr, state, perm));
+ }
+
+ Result MapPages(KProcessAddress* out_addr, size_t num_pages, KMemoryState state,
+ KMemoryPermission perm) {
+ R_RETURN(m_page_table.MapPages(out_addr, num_pages, state, perm));
+ }
+
+ Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state,
+ KMemoryPermission perm) {
+ R_RETURN(m_page_table.MapPages(address, num_pages, state, perm));
+ }
+
+ Result UnmapPages(KProcessAddress addr, size_t num_pages, KMemoryState state) {
+ R_RETURN(m_page_table.UnmapPages(addr, num_pages, state));
+ }
+
+ Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
+ KMemoryState state_mask, KMemoryState state,
+ KMemoryPermission perm_mask, KMemoryPermission perm,
+ KMemoryAttribute attr_mask, KMemoryAttribute attr) {
+ R_RETURN(m_page_table.MakeAndOpenPageGroup(out, address, num_pages, state_mask, state,
+ perm_mask, perm, attr_mask, attr));
+ }
+
+ Result InvalidateProcessDataCache(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.InvalidateProcessDataCache(address, size));
+ }
+
+ Result ReadDebugMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) {
+ R_RETURN(m_page_table.ReadDebugMemory(dst_address, src_address, size));
+ }
+
+ Result ReadDebugIoMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size,
+ KMemoryState state) {
+ R_RETURN(m_page_table.ReadDebugIoMemory(dst_address, src_address, size, state));
+ }
+
+ Result WriteDebugMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) {
+ R_RETURN(m_page_table.WriteDebugMemory(dst_address, src_address, size));
+ }
+
+ Result WriteDebugIoMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size,
+ KMemoryState state) {
+ R_RETURN(m_page_table.WriteDebugIoMemory(dst_address, src_address, size, state));
+ }
+
+ Result LockForMapDeviceAddressSpace(bool* out_is_io, KProcessAddress address, size_t size,
+ KMemoryPermission perm, bool is_aligned, bool check_heap) {
+ R_RETURN(m_page_table.LockForMapDeviceAddressSpace(out_is_io, address, size, perm,
+ is_aligned, check_heap));
+ }
+
+ Result LockForUnmapDeviceAddressSpace(KProcessAddress address, size_t size, bool check_heap) {
+ R_RETURN(m_page_table.LockForUnmapDeviceAddressSpace(address, size, check_heap));
+ }
+
+ Result UnlockForDeviceAddressSpace(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.UnlockForDeviceAddressSpace(address, size));
+ }
+
+ Result UnlockForDeviceAddressSpacePartialMap(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.UnlockForDeviceAddressSpacePartialMap(address, size));
+ }
+
+ Result OpenMemoryRangeForMapDeviceAddressSpace(KPageTableBase::MemoryRange* out,
+ KProcessAddress address, size_t size,
+ KMemoryPermission perm, bool is_aligned) {
+ R_RETURN(m_page_table.OpenMemoryRangeForMapDeviceAddressSpace(out, address, size, perm,
+ is_aligned));
+ }
+
+ Result OpenMemoryRangeForUnmapDeviceAddressSpace(KPageTableBase::MemoryRange* out,
+ KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.OpenMemoryRangeForUnmapDeviceAddressSpace(out, address, size));
+ }
+
+ Result LockForIpcUserBuffer(KPhysicalAddress* out, KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.LockForIpcUserBuffer(out, address, size));
+ }
+
+ Result UnlockForIpcUserBuffer(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.UnlockForIpcUserBuffer(address, size));
+ }
+
+ Result LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
+ KMemoryPermission perm) {
+ R_RETURN(m_page_table.LockForTransferMemory(out, address, size, perm));
+ }
+
+ Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup& pg) {
+ R_RETURN(m_page_table.UnlockForTransferMemory(address, size, pg));
+ }
+
+ Result LockForCodeMemory(KPageGroup* out, KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.LockForCodeMemory(out, address, size));
+ }
+
+ Result UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup& pg) {
+ R_RETURN(m_page_table.UnlockForCodeMemory(address, size, pg));
+ }
+
+ Result OpenMemoryRangeForProcessCacheOperation(KPageTableBase::MemoryRange* out,
+ KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.OpenMemoryRangeForProcessCacheOperation(out, address, size));
+ }
+
+ Result CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size,
+ KProcessAddress src_addr, KMemoryState src_state_mask,
+ KMemoryState src_state, KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr) {
+ R_RETURN(m_page_table.CopyMemoryFromLinearToUser(dst_addr, size, src_addr, src_state_mask,
+ src_state, src_test_perm, src_attr_mask,
+ src_attr));
+ }
+
+ Result CopyMemoryFromLinearToKernel(void* dst_addr, size_t size, KProcessAddress src_addr,
+ KMemoryState src_state_mask, KMemoryState src_state,
+ KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr) {
+ R_RETURN(m_page_table.CopyMemoryFromLinearToKernel(dst_addr, size, src_addr, src_state_mask,
+ src_state, src_test_perm, src_attr_mask,
+ src_attr));
+ }
+
+ Result CopyMemoryFromUserToLinear(KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask, KMemoryState dst_state,
+ KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr,
+ KProcessAddress src_addr) {
+ R_RETURN(m_page_table.CopyMemoryFromUserToLinear(dst_addr, size, dst_state_mask, dst_state,
+ dst_test_perm, dst_attr_mask, dst_attr,
+ src_addr));
+ }
+
+ Result CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask, KMemoryState dst_state,
+ KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr,
+ void* src_addr) {
+ R_RETURN(m_page_table.CopyMemoryFromKernelToLinear(dst_addr, size, dst_state_mask,
+ dst_state, dst_test_perm, dst_attr_mask,
+ dst_attr, src_addr));
+ }
+
+ Result CopyMemoryFromHeapToHeap(KProcessPageTable& dst_page_table, KProcessAddress dst_addr,
+ size_t size, KMemoryState dst_state_mask,
+ KMemoryState dst_state, KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr,
+ KProcessAddress src_addr, KMemoryState src_state_mask,
+ KMemoryState src_state, KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr) {
+ R_RETURN(m_page_table.CopyMemoryFromHeapToHeap(
+ dst_page_table.m_page_table, dst_addr, size, dst_state_mask, dst_state, dst_test_perm,
+ dst_attr_mask, dst_attr, src_addr, src_state_mask, src_state, src_test_perm,
+ src_attr_mask, src_attr));
+ }
+
+ Result CopyMemoryFromHeapToHeapWithoutCheckDestination(
+ KProcessPageTable& dst_page_table, KProcessAddress dst_addr, size_t size,
+ KMemoryState dst_state_mask, KMemoryState dst_state, KMemoryPermission dst_test_perm,
+ KMemoryAttribute dst_attr_mask, KMemoryAttribute dst_attr, KProcessAddress src_addr,
+ KMemoryState src_state_mask, KMemoryState src_state, KMemoryPermission src_test_perm,
+ KMemoryAttribute src_attr_mask, KMemoryAttribute src_attr) {
+ R_RETURN(m_page_table.CopyMemoryFromHeapToHeapWithoutCheckDestination(
+ dst_page_table.m_page_table, dst_addr, size, dst_state_mask, dst_state, dst_test_perm,
+ dst_attr_mask, dst_attr, src_addr, src_state_mask, src_state, src_test_perm,
+ src_attr_mask, src_attr));
+ }
+
+ Result SetupForIpc(KProcessAddress* out_dst_addr, size_t size, KProcessAddress src_addr,
+ KProcessPageTable& src_page_table, KMemoryPermission test_perm,
+ KMemoryState dst_state, bool send) {
+ R_RETURN(m_page_table.SetupForIpc(out_dst_addr, size, src_addr, src_page_table.m_page_table,
+ test_perm, dst_state, send));
+ }
+
+ Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state) {
+ R_RETURN(m_page_table.CleanupForIpcServer(address, size, dst_state));
+ }
+
+ Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state) {
+ R_RETURN(m_page_table.CleanupForIpcClient(address, size, dst_state));
+ }
+
+ Result MapPhysicalMemory(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.MapPhysicalMemory(address, size));
+ }
+
+ Result UnmapPhysicalMemory(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.UnmapPhysicalMemory(address, size));
+ }
+
+ Result MapPhysicalMemoryUnsafe(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.MapPhysicalMemoryUnsafe(address, size));
+ }
+
+ Result UnmapPhysicalMemoryUnsafe(KProcessAddress address, size_t size) {
+ R_RETURN(m_page_table.UnmapPhysicalMemoryUnsafe(address, size));
+ }
+
+ Result UnmapProcessMemory(KProcessAddress dst_address, size_t size,
+ KProcessPageTable& src_page_table, KProcessAddress src_address) {
+ R_RETURN(m_page_table.UnmapProcessMemory(dst_address, size, src_page_table.m_page_table,
+ src_address));
+ }
+
+ bool GetPhysicalAddress(KPhysicalAddress* out, KProcessAddress address) {
+ return m_page_table.GetPhysicalAddress(out, address);
+ }
+
+ bool Contains(KProcessAddress addr, size_t size) const {
+ return m_page_table.Contains(addr, size);
+ }
+
+ bool IsInAliasRegion(KProcessAddress addr, size_t size) const {
+ return m_page_table.IsInAliasRegion(addr, size);
+ }
+ bool IsInHeapRegion(KProcessAddress addr, size_t size) const {
+ return m_page_table.IsInHeapRegion(addr, size);
+ }
+ bool IsInUnsafeAliasRegion(KProcessAddress addr, size_t size) const {
+ return m_page_table.IsInUnsafeAliasRegion(addr, size);
+ }
+
+ bool CanContain(KProcessAddress addr, size_t size, KMemoryState state) const {
+ return m_page_table.CanContain(addr, size, state);
+ }
+
+ KProcessAddress GetAddressSpaceStart() const {
+ return m_page_table.GetAddressSpaceStart();
+ }
+ KProcessAddress GetHeapRegionStart() const {
+ return m_page_table.GetHeapRegionStart();
+ }
+ KProcessAddress GetAliasRegionStart() const {
+ return m_page_table.GetAliasRegionStart();
+ }
+ KProcessAddress GetStackRegionStart() const {
+ return m_page_table.GetStackRegionStart();
+ }
+ KProcessAddress GetKernelMapRegionStart() const {
+ return m_page_table.GetKernelMapRegionStart();
+ }
+ KProcessAddress GetCodeRegionStart() const {
+ return m_page_table.GetCodeRegionStart();
+ }
+ KProcessAddress GetAliasCodeRegionStart() const {
+ return m_page_table.GetAliasCodeRegionStart();
+ }
+
+ size_t GetAddressSpaceSize() const {
+ return m_page_table.GetAddressSpaceSize();
+ }
+ size_t GetHeapRegionSize() const {
+ return m_page_table.GetHeapRegionSize();
+ }
+ size_t GetAliasRegionSize() const {
+ return m_page_table.GetAliasRegionSize();
+ }
+ size_t GetStackRegionSize() const {
+ return m_page_table.GetStackRegionSize();
+ }
+ size_t GetKernelMapRegionSize() const {
+ return m_page_table.GetKernelMapRegionSize();
+ }
+ size_t GetCodeRegionSize() const {
+ return m_page_table.GetCodeRegionSize();
+ }
+ size_t GetAliasCodeRegionSize() const {
+ return m_page_table.GetAliasCodeRegionSize();
+ }
+
+ size_t GetNormalMemorySize() const {
+ return m_page_table.GetNormalMemorySize();
+ }
+
+ size_t GetCodeSize() const {
+ return m_page_table.GetCodeSize();
+ }
+ size_t GetCodeDataSize() const {
+ return m_page_table.GetCodeDataSize();
+ }
+
+ size_t GetAliasCodeSize() const {
+ return m_page_table.GetAliasCodeSize();
+ }
+ size_t GetAliasCodeDataSize() const {
+ return m_page_table.GetAliasCodeDataSize();
+ }
+
+ u32 GetAllocateOption() const {
+ return m_page_table.GetAllocateOption();
+ }
+
+ u32 GetAddressSpaceWidth() const {
+ return m_page_table.GetAddressSpaceWidth();
+ }
+
+ KPhysicalAddress GetHeapPhysicalAddress(KVirtualAddress address) {
+ return m_page_table.GetHeapPhysicalAddress(address);
+ }
+
+ u8* GetHeapVirtualPointer(KPhysicalAddress address) {
+ return m_page_table.GetHeapVirtualPointer(address);
+ }
+
+ KVirtualAddress GetHeapVirtualAddress(KPhysicalAddress address) {
+ return m_page_table.GetHeapVirtualAddress(address);
+ }
+
+ KBlockInfoManager* GetBlockInfoManager() {
+ return m_page_table.GetBlockInfoManager();
+ }
+
+ KPageTable& GetBasePageTable() {
+ return m_page_table;
+ }
+
+ const KPageTable& GetBasePageTable() const {
+ return m_page_table;
+ }
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
index d8143c650..1bce63a56 100644
--- a/src/core/hle/kernel/k_scheduler.cpp
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -190,7 +190,7 @@ u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
if (m_state.should_count_idle) {
if (highest_thread != nullptr) [[likely]] {
if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) {
- process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count);
+ process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count, 0);
}
} else {
m_state.idle_count++;
@@ -356,7 +356,7 @@ void KScheduler::SwitchThread(KThread* next_thread) {
const s64 tick_diff = cur_tick - prev_tick;
cur_thread->AddCpuTime(m_core_id, tick_diff);
if (cur_process != nullptr) {
- cur_process->UpdateCPUTimeTicks(tick_diff);
+ cur_process->AddCpuTime(tick_diff);
}
m_last_context_switch_time = cur_tick;
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index c64ceb530..3ea653163 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -383,7 +383,7 @@ Result KServerSession::SendReply(bool is_hle) {
if (event != nullptr) {
// // Get the client process/page table.
// KProcess *client_process = client_thread->GetOwnerProcess();
- // KPageTable *client_page_table = std::addressof(client_process->PageTable());
+ // KProcessPageTable *client_page_table = std::addressof(client_process->PageTable());
// // If we need to, reply with an async error.
// if (R_FAILED(client_result)) {
diff --git a/src/core/hle/kernel/k_system_resource.cpp b/src/core/hle/kernel/k_system_resource.cpp
index e6c8d589a..b51941faf 100644
--- a/src/core/hle/kernel/k_system_resource.cpp
+++ b/src/core/hle/kernel/k_system_resource.cpp
@@ -1,25 +1,100 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/core.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_system_resource.h"
namespace Kernel {
Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_limit,
KMemoryManager::Pool pool) {
- // Unimplemented
- UNREACHABLE();
+ // Set members.
+ m_resource_limit = resource_limit;
+ m_resource_size = size;
+ m_resource_pool = pool;
+
+ // Determine required size for our secure resource.
+ const size_t secure_size = this->CalculateRequiredSecureMemorySize();
+
+ // Reserve memory for our secure resource.
+ KScopedResourceReservation memory_reservation(
+ m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, secure_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+
+ // Allocate secure memory.
+ R_TRY(KSystemControl::AllocateSecureMemory(m_kernel, std::addressof(m_resource_address),
+ m_resource_size, static_cast<u32>(m_resource_pool)));
+ ASSERT(m_resource_address != 0);
+
+ // Ensure we clean up the secure memory, if we fail past this point.
+ ON_RESULT_FAILURE {
+ KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size,
+ static_cast<u32>(m_resource_pool));
+ };
+
+ // Check that our allocation is bigger than the reference counts needed for it.
+ const size_t rc_size =
+ Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(m_resource_size), PageSize);
+ R_UNLESS(m_resource_size > rc_size, ResultOutOfMemory);
+
+ // Get resource pointer.
+ KPhysicalAddress resource_paddr =
+ KPageTable::GetHeapPhysicalAddress(m_kernel, m_resource_address);
+ auto* resource =
+ m_kernel.System().DeviceMemory().GetPointer<KPageTableManager::RefCount>(resource_paddr);
+
+ // Initialize slab heaps.
+ m_dynamic_page_manager.Initialize(m_resource_address + rc_size, m_resource_size - rc_size,
+ PageSize);
+ m_page_table_heap.Initialize(std::addressof(m_dynamic_page_manager), 0, resource);
+ m_memory_block_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
+ m_block_info_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
+
+ // Initialize managers.
+ m_page_table_manager.Initialize(std::addressof(m_dynamic_page_manager),
+ std::addressof(m_page_table_heap));
+ m_memory_block_slab_manager.Initialize(std::addressof(m_dynamic_page_manager),
+ std::addressof(m_memory_block_heap));
+ m_block_info_manager.Initialize(std::addressof(m_dynamic_page_manager),
+ std::addressof(m_block_info_heap));
+
+ // Set our managers.
+ this->SetManagers(m_memory_block_slab_manager, m_block_info_manager, m_page_table_manager);
+
+ // Commit the memory reservation.
+ memory_reservation.Commit();
+
+ // Open reference to our resource limit.
+ m_resource_limit->Open();
+
+ // Set ourselves as initialized.
+ m_is_initialized = true;
+
+ R_SUCCEED();
}
void KSecureSystemResource::Finalize() {
- // Unimplemented
- UNREACHABLE();
+ // Check that we have no outstanding allocations.
+ ASSERT(m_memory_block_slab_manager.GetUsed() == 0);
+ ASSERT(m_block_info_manager.GetUsed() == 0);
+ ASSERT(m_page_table_manager.GetUsed() == 0);
+
+ // Free our secure memory.
+ KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size,
+ static_cast<u32>(m_resource_pool));
+
+ // Release the memory reservation.
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
+ this->CalculateRequiredSecureMemorySize());
+
+ // Close reference to our resource limit.
+ m_resource_limit->Close();
}
size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size,
KMemoryManager::Pool pool) {
- // Unimplemented
- UNREACHABLE();
+ return KSystemControl::CalculateRequiredSecureMemorySize(size, static_cast<u32>(pool));
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index 7df8fd7f7..a6deb50ec 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -122,16 +122,15 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress
case ThreadType::Main:
ASSERT(arg == 0);
[[fallthrough]];
- case ThreadType::HighPriority:
- [[fallthrough]];
- case ThreadType::Dummy:
- [[fallthrough]];
case ThreadType::User:
ASSERT(((owner == nullptr) ||
(owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) ||
(owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask()));
break;
+ case ThreadType::HighPriority:
+ case ThreadType::Dummy:
+ break;
case ThreadType::Kernel:
UNIMPLEMENTED();
break;
@@ -216,6 +215,7 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress
// Setup the TLS, if needed.
if (type == ThreadType::User) {
R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address)));
+ owner->GetMemory().ZeroBlock(m_tls_address, Svc::ThreadLocalRegionSize);
}
m_parent = owner;
@@ -403,7 +403,7 @@ void KThread::StartTermination() {
if (m_parent != nullptr) {
m_parent->ReleaseUserException(this);
if (m_parent->GetPinnedThread(GetCurrentCoreId(m_kernel)) == this) {
- m_parent->UnpinCurrentThread(m_core_id);
+ m_parent->UnpinCurrentThread();
}
}
@@ -415,10 +415,6 @@ void KThread::StartTermination() {
m_parent->ClearRunningThread(this);
}
- // Signal.
- m_signaled = true;
- KSynchronizationObject::NotifyAvailable();
-
// Clear previous thread in KScheduler.
KScheduler::ClearPreviousThread(m_kernel, this);
@@ -437,6 +433,13 @@ void KThread::FinishTermination() {
}
}
+ // Acquire the scheduler lock.
+ KScopedSchedulerLock sl{m_kernel};
+
+ // Signal.
+ m_signaled = true;
+ KSynchronizationObject::NotifyAvailable();
+
// Close the thread.
this->Close();
}
@@ -820,7 +823,7 @@ void KThread::CloneFpuStatus() {
ASSERT(this->GetOwnerProcess() != nullptr);
ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(m_kernel));
- if (this->GetOwnerProcess()->Is64BitProcess()) {
+ if (this->GetOwnerProcess()->Is64Bit()) {
// Clone FPSR and FPCR.
ThreadContext64 cur_ctx{};
m_kernel.System().CurrentArmInterface().SaveContext(cur_ctx);
@@ -923,7 +926,7 @@ Result KThread::GetThreadContext3(Common::ScratchBuffer<u8>& out) {
// If we're not terminating, get the thread's user context.
if (!this->IsTerminationRequested()) {
- if (m_parent->Is64BitProcess()) {
+ if (m_parent->Is64Bit()) {
// Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
auto context = GetContext64();
context.pstate &= 0xFF0FFE20;
@@ -1174,6 +1177,9 @@ Result KThread::Run() {
owner->IncrementRunningThreadCount();
}
+ // Open a reference, now that we're running.
+ this->Open();
+
// Set our state and finish.
this->SetState(ThreadState::Runnable);
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index d178c2453..e9ca5dfca 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -655,6 +655,21 @@ public:
return m_stack_top;
}
+public:
+ // TODO: This shouldn't be defined in kernel namespace
+ struct NativeExecutionParameters {
+ u64 tpidr_el0{};
+ u64 tpidrro_el0{};
+ void* native_context{};
+ std::atomic<u32> lock{1};
+ bool is_running{};
+ u32 magic{Common::MakeMagic('Y', 'U', 'Z', 'U')};
+ };
+
+ NativeExecutionParameters& GetNativeExecutionParameters() {
+ return m_native_execution_parameters;
+ }
+
private:
KThread* RemoveWaiterByKey(bool* out_has_waiters, KProcessAddress key,
bool is_kernel_address_key);
@@ -721,6 +736,7 @@ private:
// For core KThread implementation
ThreadContext32 m_thread_context_32{};
ThreadContext64 m_thread_context_64{};
+ Common::IntrusiveListNode m_process_list_node;
Common::IntrusiveRedBlackTreeNode m_condvar_arbiter_tree_node{};
s32 m_priority{};
using ConditionVariableThreadTreeTraits =
@@ -913,6 +929,7 @@ private:
ThreadWaitReasonForDebugging m_wait_reason_for_debugging{};
uintptr_t m_argument{};
KProcessAddress m_stack_top{};
+ NativeExecutionParameters m_native_execution_parameters{};
public:
using ConditionVariableThreadTreeType = ConditionVariableThreadTree;
diff --git a/src/core/hle/kernel/k_thread_local_page.cpp b/src/core/hle/kernel/k_thread_local_page.cpp
index 2c45b4232..a632d1634 100644
--- a/src/core/hle/kernel/k_thread_local_page.cpp
+++ b/src/core/hle/kernel/k_thread_local_page.cpp
@@ -37,8 +37,8 @@ Result KThreadLocalPage::Initialize(KernelCore& kernel, KProcess* process) {
Result KThreadLocalPage::Finalize() {
// Get the physical address of the page.
- const KPhysicalAddress phys_addr = m_owner->GetPageTable().GetPhysicalAddr(m_virt_addr);
- ASSERT(phys_addr);
+ KPhysicalAddress phys_addr{};
+ ASSERT(m_owner->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), m_virt_addr));
// Unmap the page.
R_TRY(m_owner->GetPageTable().UnmapPages(this->GetAddress(), 1, KMemoryState::ThreadLocal));
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 24433d32b..4a1559291 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -101,35 +101,31 @@ struct KernelCore::Impl {
void InitializeCores() {
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
- cores[core_id]->Initialize((*application_process).Is64BitProcess());
+ cores[core_id]->Initialize((*application_process).Is64Bit());
system.ApplicationMemory().SetCurrentPageTable(*application_process, core_id);
}
}
- void CloseApplicationProcess() {
- KProcess* old_process = application_process.exchange(nullptr);
- if (old_process == nullptr) {
- return;
- }
-
- // old_process->Close();
- // TODO: The process should be destroyed based on accurate ref counting after
- // calling Close(). Adding a manual Destroy() call instead to avoid a memory leak.
- old_process->Finalize();
- old_process->Destroy();
+ void TerminateApplicationProcess() {
+ application_process.load()->Terminate();
}
void Shutdown() {
is_shutting_down.store(true, std::memory_order_relaxed);
SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); });
- process_list.clear();
-
CloseServices();
+ auto* old_process = application_process.exchange(nullptr);
+ if (old_process) {
+ old_process->Close();
+ }
+
+ process_list.clear();
+
next_object_id = 0;
- next_kernel_process_id = KProcess::InitialKIPIDMin;
- next_user_process_id = KProcess::ProcessIDMin;
+ next_kernel_process_id = KProcess::InitialProcessIdMin;
+ next_user_process_id = KProcess::ProcessIdMin;
next_thread_id = 1;
global_handle_table->Finalize();
@@ -176,8 +172,6 @@ struct KernelCore::Impl {
}
}
- CloseApplicationProcess();
-
// Track kernel objects that were not freed on shutdown
{
std::scoped_lock lk{registered_objects_lock};
@@ -344,6 +338,8 @@ struct KernelCore::Impl {
// Create the system page table managers.
app_system_resource = std::make_unique<KSystemResource>(kernel);
sys_system_resource = std::make_unique<KSystemResource>(kernel);
+ KAutoObject::Create(std::addressof(*app_system_resource));
+ KAutoObject::Create(std::addressof(*sys_system_resource));
// Set the managers for the system resources.
app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager,
@@ -792,8 +788,8 @@ struct KernelCore::Impl {
std::mutex registered_in_use_objects_lock;
std::atomic<u32> next_object_id{0};
- std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin};
- std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin};
+ std::atomic<u64> next_kernel_process_id{KProcess::InitialProcessIdMin};
+ std::atomic<u64> next_user_process_id{KProcess::ProcessIdMin};
std::atomic<u64> next_thread_id{1};
// Lists all processes that exist in the current session.
@@ -924,10 +920,6 @@ const KProcess* KernelCore::ApplicationProcess() const {
return impl->application_process;
}
-void KernelCore::CloseApplicationProcess() {
- impl->CloseApplicationProcess();
-}
-
const std::vector<KProcess*>& KernelCore::GetProcessList() const {
return impl->process_list;
}
@@ -1128,8 +1120,8 @@ std::jthread KernelCore::RunOnHostCoreProcess(std::string&& process_name,
std::function<void()> func) {
// Make a new process.
KProcess* process = KProcess::Create(*this);
- ASSERT(R_SUCCEEDED(KProcess::Initialize(process, System(), "", KProcess::ProcessType::Userland,
- GetSystemResourceLimit())));
+ ASSERT(R_SUCCEEDED(
+ process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false)));
// Ensure that we don't hold onto any extra references.
SCOPE_EXIT({ process->Close(); });
@@ -1156,8 +1148,8 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function
// Make a new process.
KProcess* process = KProcess::Create(*this);
- ASSERT(R_SUCCEEDED(KProcess::Initialize(process, System(), "", KProcess::ProcessType::Userland,
- GetSystemResourceLimit())));
+ ASSERT(R_SUCCEEDED(
+ process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false)));
// Ensure that we don't hold onto any extra references.
SCOPE_EXIT({ process->Close(); });
@@ -1266,7 +1258,8 @@ const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
void KernelCore::SuspendApplication(bool suspended) {
const bool should_suspend{exception_exited || suspended};
- const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable;
+ const auto activity =
+ should_suspend ? Svc::ProcessActivity::Paused : Svc::ProcessActivity::Runnable;
// Get the application process.
KScopedAutoObject<KProcess> process = ApplicationProcess();
@@ -1300,6 +1293,8 @@ void KernelCore::SuspendApplication(bool suspended) {
}
void KernelCore::ShutdownCores() {
+ impl->TerminateApplicationProcess();
+
KScopedSchedulerLock lk{*this};
for (auto* thread : impl->shutdown_threads) {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index d5b08eeb5..d8086c0ea 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -134,9 +134,6 @@ public:
/// Retrieves a const pointer to the application process.
const KProcess* ApplicationProcess() const;
- /// Closes the application process.
- void CloseApplicationProcess();
-
/// Retrieves the list of processes.
const std::vector<KProcess*>& GetProcessList() const;
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index 5ee869fa2..073039825 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/settings.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
+#ifdef HAS_NCE
+#include "core/arm/nce/arm_nce.h"
+#endif
#include "core/core.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
@@ -14,7 +18,8 @@ PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, KSchedu
: m_core_index{core_index}, m_system{system}, m_scheduler{scheduler} {
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
// TODO(bunnei): Initialization relies on a core being available. We may later replace this with
- // a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager.
+ // an NCE interface or a 32-bit instance of Dynarmic. This should be abstracted out to a CPU
+ // manager.
auto& kernel = system.Kernel();
m_arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
system, kernel.IsMulticore(),
@@ -28,6 +33,13 @@ PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, KSchedu
PhysicalCore::~PhysicalCore() = default;
void PhysicalCore::Initialize(bool is_64_bit) {
+#if defined(HAS_NCE)
+ if (Settings::IsNceEnabled()) {
+ m_arm_interface = std::make_unique<Core::ARM_NCE>(m_system, m_system.Kernel().IsMulticore(),
+ m_core_index);
+ return;
+ }
+#endif
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
auto& kernel = m_system.Kernel();
if (!is_64_bit) {
diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp
deleted file mode 100644
index 773319ad8..000000000
--- a/src/core/hle/kernel/process_capability.cpp
+++ /dev/null
@@ -1,389 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <bit>
-
-#include "common/bit_util.h"
-#include "common/logging/log.h"
-#include "core/hle/kernel/k_handle_table.h"
-#include "core/hle/kernel/k_page_table.h"
-#include "core/hle/kernel/process_capability.h"
-#include "core/hle/kernel/svc_results.h"
-
-namespace Kernel {
-namespace {
-
-// clang-format off
-
-// Shift offsets for kernel capability types.
-enum : u32 {
- CapabilityOffset_PriorityAndCoreNum = 3,
- CapabilityOffset_Syscall = 4,
- CapabilityOffset_MapPhysical = 6,
- CapabilityOffset_MapIO = 7,
- CapabilityOffset_MapRegion = 10,
- CapabilityOffset_Interrupt = 11,
- CapabilityOffset_ProgramType = 13,
- CapabilityOffset_KernelVersion = 14,
- CapabilityOffset_HandleTableSize = 15,
- CapabilityOffset_Debug = 16,
-};
-
-// Combined mask of all parameters that may be initialized only once.
-constexpr u32 InitializeOnceMask = (1U << CapabilityOffset_PriorityAndCoreNum) |
- (1U << CapabilityOffset_ProgramType) |
- (1U << CapabilityOffset_KernelVersion) |
- (1U << CapabilityOffset_HandleTableSize) |
- (1U << CapabilityOffset_Debug);
-
-// Packed kernel version indicating 10.4.0
-constexpr u32 PackedKernelVersion = 0x520000;
-
-// Indicates possible types of capabilities that can be specified.
-enum class CapabilityType : u32 {
- Unset = 0U,
- PriorityAndCoreNum = (1U << CapabilityOffset_PriorityAndCoreNum) - 1,
- Syscall = (1U << CapabilityOffset_Syscall) - 1,
- MapPhysical = (1U << CapabilityOffset_MapPhysical) - 1,
- MapIO = (1U << CapabilityOffset_MapIO) - 1,
- MapRegion = (1U << CapabilityOffset_MapRegion) - 1,
- Interrupt = (1U << CapabilityOffset_Interrupt) - 1,
- ProgramType = (1U << CapabilityOffset_ProgramType) - 1,
- KernelVersion = (1U << CapabilityOffset_KernelVersion) - 1,
- HandleTableSize = (1U << CapabilityOffset_HandleTableSize) - 1,
- Debug = (1U << CapabilityOffset_Debug) - 1,
- Ignorable = 0xFFFFFFFFU,
-};
-
-// clang-format on
-
-constexpr CapabilityType GetCapabilityType(u32 value) {
- return static_cast<CapabilityType>((~value & (value + 1)) - 1);
-}
-
-u32 GetFlagBitOffset(CapabilityType type) {
- const auto value = static_cast<u32>(type);
- return static_cast<u32>(Common::BitSize<u32>() - static_cast<u32>(std::countl_zero(value)));
-}
-
-} // Anonymous namespace
-
-Result ProcessCapabilities::InitializeForKernelProcess(const u32* capabilities,
- std::size_t num_capabilities,
- KPageTable& page_table) {
- Clear();
-
- // Allow all cores and priorities.
- core_mask = 0xF;
- priority_mask = 0xFFFFFFFFFFFFFFFF;
- kernel_version = PackedKernelVersion;
-
- return ParseCapabilities(capabilities, num_capabilities, page_table);
-}
-
-Result ProcessCapabilities::InitializeForUserProcess(const u32* capabilities,
- std::size_t num_capabilities,
- KPageTable& page_table) {
- Clear();
-
- return ParseCapabilities(capabilities, num_capabilities, page_table);
-}
-
-void ProcessCapabilities::InitializeForMetadatalessProcess() {
- // Allow all cores and priorities
- core_mask = 0xF;
- priority_mask = 0xFFFFFFFFFFFFFFFF;
- kernel_version = PackedKernelVersion;
-
- // Allow all system calls and interrupts.
- svc_capabilities.set();
- interrupt_capabilities.set();
-
- // Allow using the maximum possible amount of handles
- handle_table_size = static_cast<s32>(KHandleTable::MaxTableSize);
-
- // Allow all debugging capabilities.
- is_debuggable = true;
- can_force_debug = true;
-}
-
-Result ProcessCapabilities::ParseCapabilities(const u32* capabilities, std::size_t num_capabilities,
- KPageTable& page_table) {
- u32 set_flags = 0;
- u32 set_svc_bits = 0;
-
- for (std::size_t i = 0; i < num_capabilities; ++i) {
- const u32 descriptor = capabilities[i];
- const auto type = GetCapabilityType(descriptor);
-
- if (type == CapabilityType::MapPhysical) {
- i++;
-
- // The MapPhysical type uses two descriptor flags for its parameters.
- // If there's only one, then there's a problem.
- if (i >= num_capabilities) {
- LOG_ERROR(Kernel, "Invalid combination! i={}", i);
- return ResultInvalidCombination;
- }
-
- const auto size_flags = capabilities[i];
- if (GetCapabilityType(size_flags) != CapabilityType::MapPhysical) {
- LOG_ERROR(Kernel, "Invalid capability type! size_flags={}", size_flags);
- return ResultInvalidCombination;
- }
-
- const auto result = HandleMapPhysicalFlags(descriptor, size_flags, page_table);
- if (result.IsError()) {
- LOG_ERROR(Kernel, "Failed to map physical flags! descriptor={}, size_flags={}",
- descriptor, size_flags);
- return result;
- }
- } else {
- const auto result =
- ParseSingleFlagCapability(set_flags, set_svc_bits, descriptor, page_table);
- if (result.IsError()) {
- LOG_ERROR(
- Kernel,
- "Failed to parse capability flag! set_flags={}, set_svc_bits={}, descriptor={}",
- set_flags, set_svc_bits, descriptor);
- return result;
- }
- }
- }
-
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag,
- KPageTable& page_table) {
- const auto type = GetCapabilityType(flag);
-
- if (type == CapabilityType::Unset) {
- return ResultInvalidArgument;
- }
-
- // Bail early on ignorable entries, as one would expect,
- // ignorable descriptors can be ignored.
- if (type == CapabilityType::Ignorable) {
- return ResultSuccess;
- }
-
- // Ensure that the give flag hasn't already been initialized before.
- // If it has been, then bail.
- const u32 flag_length = GetFlagBitOffset(type);
- const u32 set_flag = 1U << flag_length;
- if ((set_flag & set_flags & InitializeOnceMask) != 0) {
- LOG_ERROR(Kernel,
- "Attempted to initialize flags that may only be initialized once. set_flags={}",
- set_flags);
- return ResultInvalidCombination;
- }
- set_flags |= set_flag;
-
- switch (type) {
- case CapabilityType::PriorityAndCoreNum:
- return HandlePriorityCoreNumFlags(flag);
- case CapabilityType::Syscall:
- return HandleSyscallFlags(set_svc_bits, flag);
- case CapabilityType::MapIO:
- return HandleMapIOFlags(flag, page_table);
- case CapabilityType::MapRegion:
- return HandleMapRegionFlags(flag, page_table);
- case CapabilityType::Interrupt:
- return HandleInterruptFlags(flag);
- case CapabilityType::ProgramType:
- return HandleProgramTypeFlags(flag);
- case CapabilityType::KernelVersion:
- return HandleKernelVersionFlags(flag);
- case CapabilityType::HandleTableSize:
- return HandleHandleTableFlags(flag);
- case CapabilityType::Debug:
- return HandleDebugFlags(flag);
- default:
- break;
- }
-
- LOG_ERROR(Kernel, "Invalid capability type! type={}", type);
- return ResultInvalidArgument;
-}
-
-void ProcessCapabilities::Clear() {
- svc_capabilities.reset();
- interrupt_capabilities.reset();
-
- core_mask = 0;
- priority_mask = 0;
-
- handle_table_size = 0;
- kernel_version = 0;
-
- program_type = ProgramType::SysModule;
-
- is_debuggable = false;
- can_force_debug = false;
-}
-
-Result ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
- if (priority_mask != 0 || core_mask != 0) {
- LOG_ERROR(Kernel, "Core or priority mask are not zero! priority_mask={}, core_mask={}",
- priority_mask, core_mask);
- return ResultInvalidArgument;
- }
-
- const u32 core_num_min = (flags >> 16) & 0xFF;
- const u32 core_num_max = (flags >> 24) & 0xFF;
- if (core_num_min > core_num_max) {
- LOG_ERROR(Kernel, "Core min is greater than core max! core_num_min={}, core_num_max={}",
- core_num_min, core_num_max);
- return ResultInvalidCombination;
- }
-
- const u32 priority_min = (flags >> 10) & 0x3F;
- const u32 priority_max = (flags >> 4) & 0x3F;
- if (priority_min > priority_max) {
- LOG_ERROR(Kernel,
- "Priority min is greater than priority max! priority_min={}, priority_max={}",
- core_num_min, priority_max);
- return ResultInvalidCombination;
- }
-
- // The switch only has 4 usable cores.
- if (core_num_max >= 4) {
- LOG_ERROR(Kernel, "Invalid max cores specified! core_num_max={}", core_num_max);
- return ResultInvalidCoreId;
- }
-
- const auto make_mask = [](u64 min, u64 max) {
- const u64 range = max - min + 1;
- const u64 mask = (1ULL << range) - 1;
-
- return mask << min;
- };
-
- core_mask = make_mask(core_num_min, core_num_max);
- priority_mask = make_mask(priority_min, priority_max);
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags) {
- const u32 index = flags >> 29;
- const u32 svc_bit = 1U << index;
-
- // If we've already set this svc before, bail.
- if ((set_svc_bits & svc_bit) != 0) {
- return ResultInvalidCombination;
- }
- set_svc_bits |= svc_bit;
-
- const u32 svc_mask = (flags >> 5) & 0xFFFFFF;
- for (u32 i = 0; i < 24; ++i) {
- const u32 svc_number = index * 24 + i;
-
- if ((svc_mask & (1U << i)) == 0) {
- continue;
- }
-
- svc_capabilities[svc_number] = true;
- }
-
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleMapPhysicalFlags(u32 flags, u32 size_flags,
- KPageTable& page_table) {
- // TODO(Lioncache): Implement once the memory manager can handle this.
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleMapIOFlags(u32 flags, KPageTable& page_table) {
- // TODO(Lioncache): Implement once the memory manager can handle this.
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleMapRegionFlags(u32 flags, KPageTable& page_table) {
- // TODO(Lioncache): Implement once the memory manager can handle this.
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleInterruptFlags(u32 flags) {
- constexpr u32 interrupt_ignore_value = 0x3FF;
- const u32 interrupt0 = (flags >> 12) & 0x3FF;
- const u32 interrupt1 = (flags >> 22) & 0x3FF;
-
- for (u32 interrupt : {interrupt0, interrupt1}) {
- if (interrupt == interrupt_ignore_value) {
- continue;
- }
-
- // NOTE:
- // This should be checking a generic interrupt controller value
- // as part of the calculation, however, given we don't currently
- // emulate that, it's sufficient to mark every interrupt as defined.
-
- if (interrupt >= interrupt_capabilities.size()) {
- LOG_ERROR(Kernel, "Process interrupt capability is out of range! svc_number={}",
- interrupt);
- return ResultOutOfRange;
- }
-
- interrupt_capabilities[interrupt] = true;
- }
-
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleProgramTypeFlags(u32 flags) {
- const u32 reserved = flags >> 17;
- if (reserved != 0) {
- LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
- return ResultReservedUsed;
- }
-
- program_type = static_cast<ProgramType>((flags >> 14) & 0b111);
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
- // Yes, the internal member variable is checked in the actual kernel here.
- // This might look odd for options that are only allowed to be initialized
- // just once, however the kernel has a separate initialization function for
- // kernel processes and userland processes. The kernel variant sets this
- // member variable ahead of time.
-
- const u32 major_version = kernel_version >> 19;
-
- if (major_version != 0 || flags < 0x80000) {
- LOG_ERROR(Kernel,
- "Kernel version is non zero or flags are too small! major_version={}, flags={}",
- major_version, flags);
- return ResultInvalidArgument;
- }
-
- kernel_version = flags;
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
- const u32 reserved = flags >> 26;
- if (reserved != 0) {
- LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
- return ResultReservedUsed;
- }
-
- handle_table_size = static_cast<s32>((flags >> 16) & 0x3FF);
- return ResultSuccess;
-}
-
-Result ProcessCapabilities::HandleDebugFlags(u32 flags) {
- const u32 reserved = flags >> 19;
- if (reserved != 0) {
- LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
- return ResultReservedUsed;
- }
-
- is_debuggable = (flags & 0x20000) != 0;
- can_force_debug = (flags & 0x40000) != 0;
- return ResultSuccess;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/process_capability.h b/src/core/hle/kernel/process_capability.h
deleted file mode 100644
index ff05dc5ff..000000000
--- a/src/core/hle/kernel/process_capability.h
+++ /dev/null
@@ -1,266 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <bitset>
-
-#include "common/common_types.h"
-
-union Result;
-
-namespace Kernel {
-
-class KPageTable;
-
-/// The possible types of programs that may be indicated
-/// by the program type capability descriptor.
-enum class ProgramType {
- SysModule,
- Application,
- Applet,
-};
-
-/// Handles kernel capability descriptors that are provided by
-/// application metadata. These descriptors provide information
-/// that alters certain parameters for kernel process instance
-/// that will run said application (or applet).
-///
-/// Capabilities are a sequence of flag descriptors, that indicate various
-/// configurations and constraints for a particular process.
-///
-/// Flag types are indicated by a sequence of set low bits. E.g. the
-/// types are indicated with the low bits as follows (where x indicates "don't care"):
-///
-/// - Priority and core mask : 0bxxxxxxxxxxxx0111
-/// - Allowed service call mask: 0bxxxxxxxxxxx01111
-/// - Map physical memory : 0bxxxxxxxxx0111111
-/// - Map IO memory : 0bxxxxxxxx01111111
-/// - Interrupts : 0bxxxx011111111111
-/// - Application type : 0bxx01111111111111
-/// - Kernel version : 0bx011111111111111
-/// - Handle table size : 0b0111111111111111
-/// - Debugger flags : 0b1111111111111111
-///
-/// These are essentially a bit offset subtracted by 1 to create a mask.
-/// e.g. The first entry in the above list is simply bit 3 (value 8 -> 0b1000)
-/// subtracted by one (7 -> 0b0111)
-///
-/// An example of a bit layout (using the map physical layout):
-/// <example>
-/// The MapPhysical type indicates a sequence entry pair of:
-///
-/// [initial, memory_flags], where:
-///
-/// initial:
-/// bits:
-/// 7-24: Starting page to map memory at.
-/// 25 : Indicates if the memory should be mapped as read only.
-///
-/// memory_flags:
-/// bits:
-/// 7-20 : Number of pages to map
-/// 21-25: Seems to be reserved (still checked against though)
-/// 26 : Whether or not the memory being mapped is IO memory, or physical memory
-/// </example>
-///
-class ProcessCapabilities {
-public:
- using InterruptCapabilities = std::bitset<1024>;
- using SyscallCapabilities = std::bitset<192>;
-
- ProcessCapabilities() = default;
- ProcessCapabilities(const ProcessCapabilities&) = delete;
- ProcessCapabilities(ProcessCapabilities&&) = default;
-
- ProcessCapabilities& operator=(const ProcessCapabilities&) = delete;
- ProcessCapabilities& operator=(ProcessCapabilities&&) = default;
-
- /// Initializes this process capabilities instance for a kernel process.
- ///
- /// @param capabilities The capabilities to parse
- /// @param num_capabilities The number of capabilities to parse.
- /// @param page_table The memory manager to use for handling any mapping-related
- /// operations (such as mapping IO memory, etc).
- ///
- /// @returns ResultSuccess if this capabilities instance was able to be initialized,
- /// otherwise, an error code upon failure.
- ///
- Result InitializeForKernelProcess(const u32* capabilities, std::size_t num_capabilities,
- KPageTable& page_table);
-
- /// Initializes this process capabilities instance for a userland process.
- ///
- /// @param capabilities The capabilities to parse.
- /// @param num_capabilities The total number of capabilities to parse.
- /// @param page_table The memory manager to use for handling any mapping-related
- /// operations (such as mapping IO memory, etc).
- ///
- /// @returns ResultSuccess if this capabilities instance was able to be initialized,
- /// otherwise, an error code upon failure.
- ///
- Result InitializeForUserProcess(const u32* capabilities, std::size_t num_capabilities,
- KPageTable& page_table);
-
- /// Initializes this process capabilities instance for a process that does not
- /// have any metadata to parse.
- ///
- /// This is necessary, as we allow running raw executables, and the internal
- /// kernel process capabilities also determine what CPU cores the process is
- /// allowed to run on, and what priorities are allowed for threads. It also
- /// determines the max handle table size, what the program type is, whether or
- /// not the process can be debugged, or whether it's possible for a process to
- /// forcibly debug another process.
- ///
- /// Given the above, this essentially enables all capabilities across the board
- /// for the process. It allows the process to:
- ///
- /// - Run on any core
- /// - Use any thread priority
- /// - Use the maximum amount of handles a process is allowed to.
- /// - Be debuggable
- /// - Forcibly debug other processes.
- ///
- /// Note that this is not a behavior that the kernel allows a process to do via
- /// a single function like this. This is yuzu-specific behavior to handle
- /// executables with no capability descriptors whatsoever to derive behavior from.
- /// It being yuzu-specific is why this is also not the default behavior and not
- /// done by default in the constructor.
- ///
- void InitializeForMetadatalessProcess();
-
- /// Gets the allowable core mask
- u64 GetCoreMask() const {
- return core_mask;
- }
-
- /// Gets the allowable priority mask
- u64 GetPriorityMask() const {
- return priority_mask;
- }
-
- /// Gets the SVC access permission bits
- const SyscallCapabilities& GetServiceCapabilities() const {
- return svc_capabilities;
- }
-
- /// Gets the valid interrupt bits.
- const InterruptCapabilities& GetInterruptCapabilities() const {
- return interrupt_capabilities;
- }
-
- /// Gets the program type for this process.
- ProgramType GetProgramType() const {
- return program_type;
- }
-
- /// Gets the number of total allowable handles for the process' handle table.
- s32 GetHandleTableSize() const {
- return handle_table_size;
- }
-
- /// Gets the kernel version value.
- u32 GetKernelVersion() const {
- return kernel_version;
- }
-
- /// Whether or not this process can be debugged.
- bool IsDebuggable() const {
- return is_debuggable;
- }
-
- /// Whether or not this process can forcibly debug another
- /// process, even if that process is not considered debuggable.
- bool CanForceDebug() const {
- return can_force_debug;
- }
-
-private:
- /// Attempts to parse a given sequence of capability descriptors.
- ///
- /// @param capabilities The sequence of capability descriptors to parse.
- /// @param num_capabilities The number of descriptors within the given sequence.
- /// @param page_table The memory manager that will perform any memory
- /// mapping if necessary.
- ///
- /// @return ResultSuccess if no errors occur, otherwise an error code.
- ///
- Result ParseCapabilities(const u32* capabilities, std::size_t num_capabilities,
- KPageTable& page_table);
-
- /// Attempts to parse a capability descriptor that is only represented by a
- /// single flag set.
- ///
- /// @param set_flags Running set of flags that are used to catch
- /// flags being initialized more than once when they shouldn't be.
- /// @param set_svc_bits Running set of bits representing the allowed supervisor calls mask.
- /// @param flag The flag to attempt to parse.
- /// @param page_table The memory manager that will perform any memory
- /// mapping if necessary.
- ///
- /// @return ResultSuccess if no errors occurred, otherwise an error code.
- ///
- Result ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag,
- KPageTable& page_table);
-
- /// Clears the internal state of this process capability instance. Necessary,
- /// to have a sane starting point due to us allowing running executables without
- /// configuration metadata. We assume a process is not going to have metadata,
- /// and if it turns out that the process does, in fact, have metadata, then
- /// we attempt to parse it. Thus, we need this to reset data members back to
- /// a good state.
- ///
- /// DO NOT ever make this a public member function. This isn't an invariant
- /// anything external should depend upon (and if anything comes to rely on it,
- /// you should immediately be questioning the design of that thing, not this
- /// class. If the kernel itself can run without depending on behavior like that,
- /// then so can yuzu).
- ///
- void Clear();
-
- /// Handles flags related to the priority and core number capability flags.
- Result HandlePriorityCoreNumFlags(u32 flags);
-
- /// Handles flags related to determining the allowable SVC mask.
- Result HandleSyscallFlags(u32& set_svc_bits, u32 flags);
-
- /// Handles flags related to mapping physical memory pages.
- Result HandleMapPhysicalFlags(u32 flags, u32 size_flags, KPageTable& page_table);
-
- /// Handles flags related to mapping IO pages.
- Result HandleMapIOFlags(u32 flags, KPageTable& page_table);
-
- /// Handles flags related to mapping physical memory regions.
- Result HandleMapRegionFlags(u32 flags, KPageTable& page_table);
-
- /// Handles flags related to the interrupt capability flags.
- Result HandleInterruptFlags(u32 flags);
-
- /// Handles flags related to the program type.
- Result HandleProgramTypeFlags(u32 flags);
-
- /// Handles flags related to the handle table size.
- Result HandleHandleTableFlags(u32 flags);
-
- /// Handles flags related to the kernel version capability flags.
- Result HandleKernelVersionFlags(u32 flags);
-
- /// Handles flags related to debug-specific capabilities.
- Result HandleDebugFlags(u32 flags);
-
- SyscallCapabilities svc_capabilities;
- InterruptCapabilities interrupt_capabilities;
-
- u64 core_mask = 0;
- u64 priority_mask = 0;
-
- s32 handle_table_size = 0;
- u32 kernel_version = 0;
-
- ProgramType program_type = ProgramType::SysModule;
-
- bool is_debuggable = false;
- bool can_force_debug = false;
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 871d541d4..b76683969 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -4426,7 +4426,7 @@ void Call(Core::System& system, u32 imm) {
auto& kernel = system.Kernel();
kernel.EnterSVCProfile();
- if (GetCurrentProcess(system.Kernel()).Is64BitProcess()) {
+ if (GetCurrentProcess(system.Kernel()).Is64Bit()) {
Call64(system, imm);
} else {
Call32(system, imm);
diff --git a/src/core/hle/kernel/svc/svc_info.cpp b/src/core/hle/kernel/svc/svc_info.cpp
index f99964028..ada998772 100644
--- a/src/core/hle/kernel/svc/svc_info.cpp
+++ b/src/core/hle/kernel/svc/svc_info.cpp
@@ -86,20 +86,19 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
R_SUCCEED();
case InfoType::TotalMemorySize:
- *result = process->GetTotalPhysicalMemoryAvailable();
+ *result = process->GetTotalUserPhysicalMemorySize();
R_SUCCEED();
case InfoType::UsedMemorySize:
- *result = process->GetTotalPhysicalMemoryUsed();
+ *result = process->GetUsedUserPhysicalMemorySize();
R_SUCCEED();
case InfoType::SystemResourceSizeTotal:
- *result = process->GetSystemResourceSize();
+ *result = process->GetTotalSystemResourceSize();
R_SUCCEED();
case InfoType::SystemResourceSizeUsed:
- LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage");
- *result = process->GetSystemResourceUsage();
+ *result = process->GetUsedSystemResourceSize();
R_SUCCEED();
case InfoType::ProgramId:
@@ -111,20 +110,29 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
R_SUCCEED();
case InfoType::TotalNonSystemMemorySize:
- *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource();
+ *result = process->GetTotalNonSystemUserPhysicalMemorySize();
R_SUCCEED();
case InfoType::UsedNonSystemMemorySize:
- *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource();
+ *result = process->GetUsedNonSystemUserPhysicalMemorySize();
R_SUCCEED();
case InfoType::IsApplication:
LOG_WARNING(Kernel_SVC, "(STUBBED) Assuming process is application");
- *result = true;
+ *result = process->IsApplication();
R_SUCCEED();
case InfoType::FreeThreadCount:
- *result = process->GetFreeThreadCount();
+ if (KResourceLimit* resource_limit = process->GetResourceLimit();
+ resource_limit != nullptr) {
+ const auto current_value =
+ resource_limit->GetCurrentValue(Svc::LimitableResource::ThreadCountMax);
+ const auto limit_value =
+ resource_limit->GetLimitValue(Svc::LimitableResource::ThreadCountMax);
+ *result = limit_value - current_value;
+ } else {
+ *result = 0;
+ }
R_SUCCEED();
default:
@@ -161,7 +169,7 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
case InfoType::RandomEntropy:
R_UNLESS(handle == 0, ResultInvalidHandle);
- R_UNLESS(info_sub_id < KProcess::RANDOM_ENTROPY_SIZE, ResultInvalidCombination);
+ R_UNLESS(info_sub_id < 4, ResultInvalidCombination);
*result = GetCurrentProcess(system.Kernel()).GetRandomEntropy(info_sub_id);
R_SUCCEED();
diff --git a/src/core/hle/kernel/svc/svc_lock.cpp b/src/core/hle/kernel/svc/svc_lock.cpp
index 1d7bc4246..5f0833fcb 100644
--- a/src/core/hle/kernel/svc/svc_lock.cpp
+++ b/src/core/hle/kernel/svc/svc_lock.cpp
@@ -17,7 +17,7 @@ Result ArbitrateLock(Core::System& system, Handle thread_handle, u64 address, u3
R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory);
R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress);
- R_RETURN(GetCurrentProcess(system.Kernel()).WaitForAddress(thread_handle, address, tag));
+ R_RETURN(KConditionVariable::WaitForAddress(system.Kernel(), thread_handle, address, tag));
}
/// Unlock a mutex
@@ -28,7 +28,7 @@ Result ArbitrateUnlock(Core::System& system, u64 address) {
R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory);
R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress);
- R_RETURN(GetCurrentProcess(system.Kernel()).SignalToAddress(address));
+ R_RETURN(KConditionVariable::SignalToAddress(system.Kernel(), address));
}
Result ArbitrateLock64(Core::System& system, Handle thread_handle, uint64_t address, uint32_t tag) {
diff --git a/src/core/hle/kernel/svc/svc_memory.cpp b/src/core/hle/kernel/svc/svc_memory.cpp
index 97f1210de..4ca62860d 100644
--- a/src/core/hle/kernel/svc/svc_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_memory.cpp
@@ -29,7 +29,8 @@ constexpr bool IsValidAddressRange(u64 address, u64 size) {
// Helper function that performs the common sanity checks for svcMapMemory
// and svcUnmapMemory. This is doable, as both functions perform their sanitizing
// in the same order.
-Result MapUnmapMemorySanityChecks(const KPageTable& manager, u64 dst_addr, u64 src_addr, u64 size) {
+Result MapUnmapMemorySanityChecks(const KProcessPageTable& manager, u64 dst_addr, u64 src_addr,
+ u64 size) {
if (!Common::Is4KBAligned(dst_addr)) {
LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr);
R_THROW(ResultInvalidAddress);
@@ -123,7 +124,8 @@ Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask,
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
// Set the memory attribute.
- R_RETURN(page_table.SetMemoryAttribute(address, size, mask, attr));
+ R_RETURN(page_table.SetMemoryAttribute(address, size, static_cast<KMemoryAttribute>(mask),
+ static_cast<KMemoryAttribute>(attr)));
}
/// Maps a memory range into a different range.
diff --git a/src/core/hle/kernel/svc/svc_physical_memory.cpp b/src/core/hle/kernel/svc/svc_physical_memory.cpp
index d3545f232..793e9f8d0 100644
--- a/src/core/hle/kernel/svc/svc_physical_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_physical_memory.cpp
@@ -16,7 +16,14 @@ Result SetHeapSize(Core::System& system, u64* out_address, u64 size) {
R_UNLESS(size < MainMemorySizeMax, ResultInvalidSize);
// Set the heap size.
- R_RETURN(GetCurrentProcess(system.Kernel()).GetPageTable().SetHeapSize(out_address, size));
+ KProcessAddress address{};
+ R_TRY(GetCurrentProcess(system.Kernel())
+ .GetPageTable()
+ .SetHeapSize(std::addressof(address), size));
+
+ // We succeeded.
+ *out_address = GetInteger(address);
+ R_SUCCEED();
}
/// Maps memory at a desired address
@@ -46,7 +53,7 @@ Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())};
auto& page_table{current_process->GetPageTable()};
- if (current_process->GetSystemResourceSize() == 0) {
+ if (current_process->GetTotalSystemResourceSize() == 0) {
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
R_THROW(ResultInvalidState);
}
@@ -95,7 +102,7 @@ Result UnmapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())};
auto& page_table{current_process->GetPageTable()};
- if (current_process->GetSystemResourceSize() == 0) {
+ if (current_process->GetTotalSystemResourceSize() == 0) {
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
R_THROW(ResultInvalidState);
}
diff --git a/src/core/hle/kernel/svc/svc_process_memory.cpp b/src/core/hle/kernel/svc/svc_process_memory.cpp
index 07cd48175..e1427947b 100644
--- a/src/core/hle/kernel/svc/svc_process_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_process_memory.cpp
@@ -247,8 +247,7 @@ Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 d
R_THROW(ResultInvalidCurrentMemory);
}
- R_RETURN(page_table.UnmapCodeMemory(dst_address, src_address, size,
- KPageTable::ICacheInvalidationStrategy::InvalidateAll));
+ R_RETURN(page_table.UnmapCodeMemory(dst_address, src_address, size));
}
Result SetProcessMemoryPermission64(Core::System& system, Handle process_handle, uint64_t address,
diff --git a/src/core/hle/kernel/svc/svc_query_memory.cpp b/src/core/hle/kernel/svc/svc_query_memory.cpp
index 51af06e97..816dcb8d0 100644
--- a/src/core/hle/kernel/svc/svc_query_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_query_memory.cpp
@@ -31,12 +31,12 @@ Result QueryProcessMemory(Core::System& system, uint64_t out_memory_info, PageIn
}
auto& current_memory{GetCurrentMemory(system.Kernel())};
- const auto memory_info{process->GetPageTable().QueryInfo(address).GetSvcMemoryInfo()};
- current_memory.WriteBlock(out_memory_info, std::addressof(memory_info), sizeof(memory_info));
+ KMemoryInfo mem_info;
+ R_TRY(process->GetPageTable().QueryInfo(std::addressof(mem_info), out_page_info, address));
- //! This is supposed to be part of the QueryInfo call.
- *out_page_info = {};
+ const auto svc_mem_info = mem_info.GetSvcMemoryInfo();
+ current_memory.WriteBlock(out_memory_info, std::addressof(svc_mem_info), sizeof(svc_mem_info));
R_SUCCEED();
}
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp
index 8ebc1bd1c..6c79cfd8d 100644
--- a/src/core/hle/kernel/svc/svc_synchronization.cpp
+++ b/src/core/hle/kernel/svc/svc_synchronization.cpp
@@ -132,7 +132,7 @@ void SynchronizePreemptionState(Core::System& system) {
GetCurrentThread(kernel).ClearInterruptFlag();
// Unpin the current thread.
- cur_process->UnpinCurrentThread(core_id);
+ cur_process->UnpinCurrentThread();
}
}
diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp
index 933b82e30..755fd62b5 100644
--- a/src/core/hle/kernel/svc/svc_thread.cpp
+++ b/src/core/hle/kernel/svc/svc_thread.cpp
@@ -85,10 +85,6 @@ Result StartThread(Core::System& system, Handle thread_handle) {
// Try to start the thread.
R_TRY(thread->Run());
- // If we succeeded, persist a reference to the thread.
- thread->Open();
- system.Kernel().RegisterInUseObject(thread.GetPointerUnsafe());
-
R_SUCCEED();
}
@@ -99,7 +95,6 @@ void ExitThread(Core::System& system) {
auto* const current_thread = GetCurrentThreadPointer(system.Kernel());
system.GlobalSchedulerContext().RemoveThread(current_thread);
current_thread->Exit();
- system.Kernel().UnregisterInUseObject(current_thread);
}
/// Sleep the current thread
@@ -260,7 +255,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_
auto list_iter = thread_list.cbegin();
for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) {
- memory.Write64(out_thread_ids, (*list_iter)->GetThreadId());
+ memory.Write64(out_thread_ids, list_iter->GetThreadId());
out_thread_ids += sizeof(u64);
}
diff --git a/src/core/hle/kernel/svc_generator.py b/src/core/hle/kernel/svc_generator.py
index 7fcbb1ba1..5531faac6 100644
--- a/src/core/hle/kernel/svc_generator.py
+++ b/src/core/hle/kernel/svc_generator.py
@@ -592,7 +592,7 @@ void Call(Core::System& system, u32 imm) {
auto& kernel = system.Kernel();
kernel.EnterSVCProfile();
- if (GetCurrentProcess(system.Kernel()).Is64BitProcess()) {
+ if (GetCurrentProcess(system.Kernel()).Is64Bit()) {
Call64(system, imm);
} else {
Call32(system, imm);
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h
index 251e6013c..50de02e36 100644
--- a/src/core/hle/kernel/svc_types.h
+++ b/src/core/hle/kernel/svc_types.h
@@ -604,13 +604,57 @@ enum class ProcessActivity : u32 {
Paused,
};
+enum class CreateProcessFlag : u32 {
+ // Is 64 bit?
+ Is64Bit = (1 << 0),
+
+ // What kind of address space?
+ AddressSpaceShift = 1,
+ AddressSpaceMask = (7 << AddressSpaceShift),
+ AddressSpace32Bit = (0 << AddressSpaceShift),
+ AddressSpace64BitDeprecated = (1 << AddressSpaceShift),
+ AddressSpace32BitWithoutAlias = (2 << AddressSpaceShift),
+ AddressSpace64Bit = (3 << AddressSpaceShift),
+
+ // Should JIT debug be done on crash?
+ EnableDebug = (1 << 4),
+
+ // Should ASLR be enabled for the process?
+ EnableAslr = (1 << 5),
+
+ // Is the process an application?
+ IsApplication = (1 << 6),
+
+ // 4.x deprecated: Should use secure memory?
+ DeprecatedUseSecureMemory = (1 << 7),
+
+ // 5.x+ Pool partition type.
+ PoolPartitionShift = 7,
+ PoolPartitionMask = (0xF << PoolPartitionShift),
+ PoolPartitionApplication = (0 << PoolPartitionShift),
+ PoolPartitionApplet = (1 << PoolPartitionShift),
+ PoolPartitionSystem = (2 << PoolPartitionShift),
+ PoolPartitionSystemNonSecure = (3 << PoolPartitionShift),
+
+ // 7.x+ Should memory allocation be optimized? This requires IsApplication.
+ OptimizeMemoryAllocation = (1 << 11),
+
+ // 11.x+ DisableDeviceAddressSpaceMerge.
+ DisableDeviceAddressSpaceMerge = (1 << 12),
+
+ // Mask of all flags.
+ All = Is64Bit | AddressSpaceMask | EnableDebug | EnableAslr | IsApplication |
+ PoolPartitionMask | OptimizeMemoryAllocation | DisableDeviceAddressSpaceMerge,
+};
+DECLARE_ENUM_FLAG_OPERATORS(CreateProcessFlag);
+
struct CreateProcessParameter {
std::array<char, 12> name;
u32 version;
u64 program_id;
u64 code_address;
s32 code_num_pages;
- u32 flags;
+ CreateProcessFlag flags;
Handle reslimit;
s32 system_resource_num_pages;
};
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index dd0b27f47..749f51f69 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -407,3 +407,34 @@ constexpr inline Result __TmpCurrentResultReference = ResultSuccess;
/// Evaluates a boolean expression, and succeeds if that expression is true.
#define R_SUCCEED_IF(expr) R_UNLESS(!(expr), ResultSuccess)
+
+#define R_TRY_CATCH(res_expr) \
+ { \
+ const auto R_CURRENT_RESULT = (res_expr); \
+ if (R_FAILED(R_CURRENT_RESULT)) { \
+ if (false)
+
+#define R_END_TRY_CATCH \
+ else if (R_FAILED(R_CURRENT_RESULT)) { \
+ R_THROW(R_CURRENT_RESULT); \
+ } \
+ } \
+ }
+
+#define R_CATCH_ALL() \
+ } \
+ else if (R_FAILED(R_CURRENT_RESULT)) { \
+ if (true)
+
+#define R_CATCH(res_expr) \
+ } \
+ else if ((res_expr) == (R_CURRENT_RESULT)) { \
+ if (true)
+
+#define R_CONVERT(catch_type, convert_type) \
+ R_CATCH(catch_type) { R_THROW(static_cast<Result>(convert_type)); }
+
+#define R_CONVERT_ALL(convert_type) \
+ R_CATCH_ALL() { R_THROW(static_cast<Result>(convert_type)); }
+
+#define R_ASSERT(res_expr) ASSERT(R_SUCCEEDED(res_expr))
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 1b1c8190e..f21553644 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -3,11 +3,13 @@
#include <algorithm>
#include <array>
+
#include "common/common_types.h"
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/polyfill_ranges.h"
+#include "common/stb.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/constants.h"
@@ -38,9 +40,36 @@ static std::filesystem::path GetImagePath(const Common::UUID& uuid) {
fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormattedString());
}
-static constexpr u32 SanitizeJPEGSize(std::size_t size) {
+static void JPGToMemory(void* context, void* data, int len) {
+ std::vector<u8>* jpg_image = static_cast<std::vector<u8>*>(context);
+ unsigned char* jpg = static_cast<unsigned char*>(data);
+ jpg_image->insert(jpg_image->end(), jpg, jpg + len);
+}
+
+static void SanitizeJPEGImageSize(std::vector<u8>& image) {
constexpr std::size_t max_jpeg_image_size = 0x20000;
- return static_cast<u32>(std::min(size, max_jpeg_image_size));
+ constexpr int profile_dimensions = 256;
+ int original_width, original_height, color_channels;
+
+ const auto plain_image =
+ stbi_load_from_memory(image.data(), static_cast<int>(image.size()), &original_width,
+ &original_height, &color_channels, STBI_rgb);
+
+ // Resize image to match 256*256
+ if (original_width != profile_dimensions || original_height != profile_dimensions) {
+ // Use vector instead of array to avoid overflowing the stack
+ std::vector<u8> out_image(profile_dimensions * profile_dimensions * STBI_rgb);
+ stbir_resize_uint8_srgb(plain_image, original_width, original_height, 0, out_image.data(),
+ profile_dimensions, profile_dimensions, 0, STBI_rgb, 0,
+ STBIR_FILTER_BOX);
+ image.clear();
+ if (!stbi_write_jpg_to_func(JPGToMemory, &image, profile_dimensions, profile_dimensions,
+ STBI_rgb, out_image.data(), 0)) {
+ LOG_ERROR(Service_ACC, "Failed to resize the user provided image.");
+ }
+ }
+
+ image.resize(std::min(image.size(), max_jpeg_image_size));
}
class IManagerForSystemService final : public ServiceFramework<IManagerForSystemService> {
@@ -339,19 +368,20 @@ protected:
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
ctx.WriteBuffer(Core::Constants::ACCOUNT_BACKUP_JPEG);
- rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
+ rb.Push(static_cast<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
return;
}
- const u32 size = SanitizeJPEGSize(image.GetSize());
- std::vector<u8> buffer(size);
+ std::vector<u8> buffer(image.GetSize());
if (image.Read(buffer) != buffer.size()) {
LOG_ERROR(Service_ACC, "Failed to read all the bytes in the user provided image.");
}
+ SanitizeJPEGImageSize(buffer);
+
ctx.WriteBuffer(buffer);
- rb.Push<u32>(size);
+ rb.Push(static_cast<u32>(buffer.size()));
}
void GetImageSize(HLERequestContext& ctx) {
@@ -365,10 +395,18 @@ protected:
if (!image.IsOpen()) {
LOG_WARNING(Service_ACC,
"Failed to load user provided image! Falling back to built-in backup...");
- rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
- } else {
- rb.Push(SanitizeJPEGSize(image.GetSize()));
+ rb.Push(static_cast<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
+ return;
}
+
+ std::vector<u8> buffer(image.GetSize());
+
+ if (image.Read(buffer) != buffer.size()) {
+ LOG_ERROR(Service_ACC, "Failed to read all the bytes in the user provided image.");
+ }
+
+ SanitizeJPEGImageSize(buffer);
+ rb.Push(static_cast<u32>(buffer.size()));
}
void Store(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 98765b81a..a266d7c21 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -13,6 +13,7 @@
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/savedata_factory.h"
+#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/result.h"
@@ -21,8 +22,10 @@
#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_controller.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_software_keyboard_types.h"
#include "core/hle/service/am/applets/applet_web_browser.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/am/idle.h"
@@ -31,8 +34,10 @@
#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_su.h"
#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/hid/controllers/npad.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"
@@ -71,7 +76,7 @@ IWindowController::IWindowController(Core::System& system_)
static const FunctionInfo functions[] = {
{0, nullptr, "CreateWindow"},
{1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"},
- {2, nullptr, "GetAppletResourceUserIdOfCallerApplet"},
+ {2, &IWindowController::GetAppletResourceUserIdOfCallerApplet, "GetAppletResourceUserIdOfCallerApplet"},
{10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"},
{11, nullptr, "ReleaseForegroundRights"},
{12, nullptr, "RejectToChangeIntoBackground"},
@@ -95,6 +100,16 @@ void IWindowController::GetAppletResourceUserId(HLERequestContext& ctx) {
rb.Push<u64>(process_id);
}
+void IWindowController::GetAppletResourceUserIdOfCallerApplet(HLERequestContext& ctx) {
+ const u64 process_id = 0;
+
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(process_id);
+}
+
void IWindowController::AcquireForegroundRights(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
@@ -702,9 +717,17 @@ void ISelfController::SetAlbumImageTakenNotificationEnabled(HLERequestContext& c
void ISelfController::SaveCurrentScreenshot(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto album_report_option = rp.PopEnum<Capture::AlbumReportOption>();
+ const auto report_option = rp.PopEnum<Capture::AlbumReportOption>();
+
+ LOG_INFO(Service_AM, "called, report_option={}", report_option);
+
+ const auto screenshot_service =
+ system.ServiceManager().GetService<Service::Capture::IScreenShotApplicationService>(
+ "caps:su");
- LOG_WARNING(Service_AM, "(STUBBED) called. album_report_option={}", album_report_option);
+ if (screenshot_service) {
+ screenshot_service->CaptureAndSaveScreenshot(report_option);
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -796,7 +819,9 @@ ILockAccessor::ILockAccessor(Core::System& system_)
lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
}
-ILockAccessor::~ILockAccessor() = default;
+ILockAccessor::~ILockAccessor() {
+ service_context.CloseEvent(lock_event);
+};
void ILockAccessor::TryLock(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -909,7 +934,9 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
}
-ICommonStateGetter::~ICommonStateGetter() = default;
+ICommonStateGetter::~ICommonStateGetter() {
+ service_context.CloseEvent(sleep_lock_event);
+};
void ICommonStateGetter::GetBootMode(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
@@ -1551,14 +1578,14 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
{6, nullptr, "GetPopInteractiveInDataEvent"},
{10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"},
{11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"},
- {12, nullptr, "GetMainAppletIdentityInfo"},
+ {12, &ILibraryAppletSelfAccessor::GetMainAppletIdentityInfo, "GetMainAppletIdentityInfo"},
{13, nullptr, "CanUseApplicationCore"},
{14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"},
{15, nullptr, "GetMainAppletApplicationControlProperty"},
{16, nullptr, "GetMainAppletStorageId"},
{17, nullptr, "GetCallerAppletIdentityInfoStack"},
{18, nullptr, "GetNextReturnDestinationAppletIdentityInfo"},
- {19, nullptr, "GetDesirableKeyboardLayout"},
+ {19, &ILibraryAppletSelfAccessor::GetDesirableKeyboardLayout, "GetDesirableKeyboardLayout"},
{20, nullptr, "PopExtraStorage"},
{25, nullptr, "GetPopExtraStorageEvent"},
{30, nullptr, "UnpopInData"},
@@ -1577,7 +1604,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
{120, nullptr, "GetLaunchStorageInfoForDebug"},
{130, nullptr, "GetGpuErrorDetectedSystemEvent"},
{140, nullptr, "SetApplicationMemoryReservation"},
- {150, nullptr, "ShouldSetGpuTimeSliceManually"},
+ {150, &ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually, "ShouldSetGpuTimeSliceManually"},
};
// clang-format on
RegisterHandlers(functions);
@@ -1592,6 +1619,12 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
case Applets::AppletId::PhotoViewer:
PushInShowAlbum();
break;
+ case Applets::AppletId::SoftwareKeyboard:
+ PushInShowSoftwareKeyboard();
+ break;
+ case Applets::AppletId::Controller:
+ PushInShowController();
+ break;
default:
break;
}
@@ -1649,13 +1682,33 @@ void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
rb.PushRaw(applet_info);
}
-void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
+void ILibraryAppletSelfAccessor::GetMainAppletIdentityInfo(HLERequestContext& ctx) {
struct AppletIdentityInfo {
Applets::AppletId applet_id;
INSERT_PADDING_BYTES(0x4);
u64 application_id;
};
+ static_assert(sizeof(AppletIdentityInfo) == 0x10, "AppletIdentityInfo has incorrect size.");
+
+ 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::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
+ struct AppletIdentityInfo {
+ Applets::AppletId applet_id;
+ INSERT_PADDING_BYTES(0x4);
+ u64 application_id;
+ };
+ static_assert(sizeof(AppletIdentityInfo) == 0x10, "AppletIdentityInfo has incorrect size.");
LOG_WARNING(Service_AM, "(STUBBED) called");
const AppletIdentityInfo applet_info{
@@ -1668,6 +1721,14 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
rb.PushRaw(applet_info);
}
+void ILibraryAppletSelfAccessor::GetDesirableKeyboardLayout(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(0);
+}
+
void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {
const Service::Account::ProfileManager manager{};
bool is_empty{true};
@@ -1687,6 +1748,14 @@ void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext&
rb.Push(user_count);
}
+void ILibraryAppletSelfAccessor::ShouldSetGpuTimeSliceManually(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(0);
+}
+
void ILibraryAppletSelfAccessor::PushInShowAlbum() {
const Applets::CommonArguments arguments{
.arguments_version = Applets::CommonArgumentVersion::Version3,
@@ -1704,6 +1773,55 @@ void ILibraryAppletSelfAccessor::PushInShowAlbum() {
queue_data.emplace_back(std::move(settings_data));
}
+void ILibraryAppletSelfAccessor::PushInShowController() {
+ const Applets::CommonArguments common_args = {
+ .arguments_version = Applets::CommonArgumentVersion::Version3,
+ .size = Applets::CommonArgumentSize::Version3,
+ .library_version = static_cast<u32>(Applets::ControllerAppletVersion::Version8),
+ .theme_color = Applets::ThemeColor::BasicBlack,
+ .play_startup_sound = true,
+ .system_tick = system.CoreTiming().GetClockTicks(),
+ };
+
+ Applets::ControllerSupportArgNew user_args = {
+ .header = {.player_count_min = 1,
+ .player_count_max = 4,
+ .enable_take_over_connection = true,
+ .enable_left_justify = false,
+ .enable_permit_joy_dual = true,
+ .enable_single_mode = false,
+ .enable_identification_color = false},
+ .identification_colors = {},
+ .enable_explain_text = false,
+ .explain_text = {},
+ };
+
+ Applets::ControllerSupportArgPrivate private_args = {
+ .arg_private_size = sizeof(Applets::ControllerSupportArgPrivate),
+ .arg_size = sizeof(Applets::ControllerSupportArgNew),
+ .is_home_menu = true,
+ .flag_1 = true,
+ .mode = Applets::ControllerSupportMode::ShowControllerSupport,
+ .caller = Applets::ControllerSupportCaller::
+ Application, // switchbrew: Always zero except with
+ // ShowControllerFirmwareUpdateForSystem/ShowControllerKeyRemappingForSystem,
+ // which sets this to the input param
+ .style_set = Core::HID::NpadStyleSet::None,
+ .joy_hold_type = 0,
+ };
+ std::vector<u8> common_args_data(sizeof(common_args));
+ std::vector<u8> private_args_data(sizeof(private_args));
+ std::vector<u8> user_args_data(sizeof(user_args));
+
+ std::memcpy(common_args_data.data(), &common_args, sizeof(common_args));
+ std::memcpy(private_args_data.data(), &private_args, sizeof(private_args));
+ std::memcpy(user_args_data.data(), &user_args, sizeof(user_args));
+
+ queue_data.emplace_back(std::move(common_args_data));
+ queue_data.emplace_back(std::move(private_args_data));
+ queue_data.emplace_back(std::move(user_args_data));
+}
+
void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
const Applets::CommonArguments arguments{
.arguments_version = Applets::CommonArgumentVersion::Version3,
@@ -1755,6 +1873,61 @@ void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
queue_data.emplace_back(std::move(argument_data));
}
+void ILibraryAppletSelfAccessor::PushInShowSoftwareKeyboard() {
+ const Applets::CommonArguments arguments{
+ .arguments_version = Applets::CommonArgumentVersion::Version3,
+ .size = Applets::CommonArgumentSize::Version3,
+ .library_version = static_cast<u32>(Applets::SwkbdAppletVersion::Version524301),
+ .theme_color = Applets::ThemeColor::BasicBlack,
+ .play_startup_sound = true,
+ .system_tick = system.CoreTiming().GetClockTicks(),
+ };
+
+ std::vector<char16_t> initial_string(0);
+
+ const Applets::SwkbdConfigCommon swkbd_config{
+ .type = Applets::SwkbdType::Qwerty,
+ .ok_text{},
+ .left_optional_symbol_key{},
+ .right_optional_symbol_key{},
+ .use_prediction = false,
+ .key_disable_flags{},
+ .initial_cursor_position = Applets::SwkbdInitialCursorPosition::Start,
+ .header_text{},
+ .sub_text{},
+ .guide_text{},
+ .max_text_length = 500,
+ .min_text_length = 0,
+ .password_mode = Applets::SwkbdPasswordMode::Disabled,
+ .text_draw_type = Applets::SwkbdTextDrawType::Box,
+ .enable_return_button = true,
+ .use_utf8 = false,
+ .use_blur_background = true,
+ .initial_string_offset{},
+ .initial_string_length = static_cast<u32>(initial_string.size()),
+ .user_dictionary_offset{},
+ .user_dictionary_entries{},
+ .use_text_check = false,
+ };
+
+ Applets::SwkbdConfigNew swkbd_config_new{};
+
+ std::vector<u8> argument_data(sizeof(arguments));
+ std::vector<u8> swkbd_data(sizeof(swkbd_config) + sizeof(swkbd_config_new));
+ std::vector<u8> work_buffer(swkbd_config.initial_string_length * sizeof(char16_t));
+
+ std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
+ std::memcpy(swkbd_data.data(), &swkbd_config, sizeof(swkbd_config));
+ std::memcpy(swkbd_data.data() + sizeof(swkbd_config), &swkbd_config_new,
+ sizeof(Applets::SwkbdConfigNew));
+ std::memcpy(work_buffer.data(), initial_string.data(),
+ swkbd_config.initial_string_length * sizeof(char16_t));
+
+ queue_data.emplace_back(std::move(argument_data));
+ queue_data.emplace_back(std::move(swkbd_data));
+ queue_data.emplace_back(std::move(work_buffer));
+}
+
IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_)
: ServiceFramework{system_, "IAppletCommonFunctions"} {
// clang-format off
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 64b3f3fe2..905a71b9f 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -87,6 +87,7 @@ public:
private:
void GetAppletResourceUserId(HLERequestContext& ctx);
+ void GetAppletResourceUserIdOfCallerApplet(HLERequestContext& ctx);
void AcquireForegroundRights(HLERequestContext& ctx);
};
@@ -345,13 +346,18 @@ private:
void PopInData(HLERequestContext& ctx);
void PushOutData(HLERequestContext& ctx);
void GetLibraryAppletInfo(HLERequestContext& ctx);
+ void GetMainAppletIdentityInfo(HLERequestContext& ctx);
void ExitProcessAndReturn(HLERequestContext& ctx);
void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
+ void GetDesirableKeyboardLayout(HLERequestContext& ctx);
void GetMainAppletAvailableUsers(HLERequestContext& ctx);
+ void ShouldSetGpuTimeSliceManually(HLERequestContext& ctx);
void PushInShowAlbum();
void PushInShowCabinetData();
void PushInShowMiiEditData();
+ void PushInShowSoftwareKeyboard();
+ void PushInShowController();
std::deque<std::vector<u8>> queue_data;
};
diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index 19ed184e8..3906c0fa4 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -25,7 +25,9 @@ Cabinet::Cabinet(Core::System& system_, LibraryAppletMode applet_mode_,
service_context.CreateEvent("CabinetApplet:AvailabilityChangeEvent");
}
-Cabinet::~Cabinet() = default;
+Cabinet::~Cabinet() {
+ service_context.CloseEvent(availability_change_event);
+};
void Cabinet::Initialize() {
Applet::Initialize();
@@ -120,7 +122,8 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
Service::NFP::RegisterInfoPrivate register_info{};
std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(),
std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1));
-
+ register_info.mii_store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
+ register_info.mii_store_data.SetNickname({u'y', u'u', u'z', u'u'});
nfp_device->SetRegisterInfoPrivate(register_info);
break;
}
@@ -128,7 +131,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
nfp_device->DeleteApplicationArea();
break;
case Service::NFP::CabinetMode::StartRestorer:
- nfp_device->RestoreAmiibo();
+ nfp_device->Restore();
break;
case Service::NFP::CabinetMode::StartFormatter:
nfp_device->Format();
diff --git a/src/core/hle/service/am/applets/applet_controller.h b/src/core/hle/service/am/applets/applet_controller.h
index f6c64f633..9f839f3d7 100644
--- a/src/core/hle/service/am/applets/applet_controller.h
+++ b/src/core/hle/service/am/applets/applet_controller.h
@@ -56,7 +56,7 @@ enum class ControllerSupportResult : u32 {
struct ControllerSupportArgPrivate {
u32 arg_private_size{};
u32 arg_size{};
- bool flag_0{};
+ bool is_home_menu{};
bool flag_1{};
ControllerSupportMode mode{};
ControllerSupportCaller caller{};
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp
index 1c9a1dc29..b0ea2b381 100644
--- a/src/core/hle/service/am/applets/applet_web_browser.cpp
+++ b/src/core/hle/service/am/applets/applet_web_browser.cpp
@@ -330,8 +330,7 @@ void WebBrowser::ExtractOfflineRomFS() {
LOG_DEBUG(Service_AM, "Extracting RomFS to {}",
Common::FS::PathToUTF8String(offline_cache_dir));
- const auto extracted_romfs_dir =
- FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
+ const auto extracted_romfs_dir = FileSys::ExtractRomFS(offline_romfs);
const auto temp_dir = system.GetFilesystem()->CreateDirectory(
Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite);
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index f02bbc450..0bf2598b7 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -69,6 +69,30 @@ enum class AppletId : u32 {
MyPage = 0x1A,
};
+enum class AppletProgramId : u64 {
+ QLaunch = 0x0100000000001000ull,
+ Auth = 0x0100000000001001ull,
+ Cabinet = 0x0100000000001002ull,
+ Controller = 0x0100000000001003ull,
+ DataErase = 0x0100000000001004ull,
+ Error = 0x0100000000001005ull,
+ NetConnect = 0x0100000000001006ull,
+ ProfileSelect = 0x0100000000001007ull,
+ SoftwareKeyboard = 0x0100000000001008ull,
+ MiiEdit = 0x0100000000001009ull,
+ Web = 0x010000000000100Aull,
+ Shop = 0x010000000000100Bull,
+ OverlayDisplay = 0x010000000000100Cull,
+ PhotoViewer = 0x010000000000100Dull,
+ Settings = 0x010000000000100Eull,
+ OfflineWeb = 0x010000000000100Full,
+ LoginShare = 0x0100000000001010ull,
+ WebAuth = 0x0100000000001011ull,
+ Starter = 0x0100000000001012ull,
+ MyPage = 0x0100000000001013ull,
+ MaxProgramId = 0x0100000000001FFFull,
+};
+
enum class LibraryAppletMode : u32 {
AllForeground = 0,
Background = 1,
diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp
index 8069f75b7..c65e32489 100644
--- a/src/core/hle/service/btm/btm.cpp
+++ b/src/core/hle/service/btm/btm.cpp
@@ -127,7 +127,7 @@ public:
private:
void GetCore(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BTM, "called");
+ LOG_WARNING(Service_BTM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
@@ -263,13 +263,13 @@ public:
explicit IBtmSystemCore(Core::System& system_) : ServiceFramework{system_, "IBtmSystemCore"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "StartGamepadPairing"},
- {1, nullptr, "CancelGamepadPairing"},
+ {0, &IBtmSystemCore::StartGamepadPairing, "StartGamepadPairing"},
+ {1, &IBtmSystemCore::CancelGamepadPairing, "CancelGamepadPairing"},
{2, nullptr, "ClearGamepadPairingDatabase"},
{3, nullptr, "GetPairedGamepadCount"},
{4, nullptr, "EnableRadio"},
{5, nullptr, "DisableRadio"},
- {6, nullptr, "GetRadioOnOff"},
+ {6, &IBtmSystemCore::IsRadioEnabled, "IsRadioEnabled"},
{7, nullptr, "AcquireRadioEvent"},
{8, nullptr, "AcquireGamepadPairingEvent"},
{9, nullptr, "IsGamepadPairingStarted"},
@@ -280,18 +280,58 @@ public:
{14, nullptr, "AcquireAudioDeviceConnectionEvent"},
{15, nullptr, "ConnectAudioDevice"},
{16, nullptr, "IsConnectingAudioDevice"},
- {17, nullptr, "GetConnectedAudioDevices"},
+ {17, &IBtmSystemCore::GetConnectedAudioDevices, "GetConnectedAudioDevices"},
{18, nullptr, "DisconnectAudioDevice"},
{19, nullptr, "AcquirePairedAudioDeviceInfoChangedEvent"},
{20, nullptr, "GetPairedAudioDevices"},
{21, nullptr, "RemoveAudioDevicePairing"},
- {22, nullptr, "RequestAudioDeviceConnectionRejection"},
- {23, nullptr, "CancelAudioDeviceConnectionRejection"}
+ {22, &IBtmSystemCore::RequestAudioDeviceConnectionRejection, "RequestAudioDeviceConnectionRejection"},
+ {23, &IBtmSystemCore::CancelAudioDeviceConnectionRejection, "CancelAudioDeviceConnectionRejection"}
};
// clang-format on
RegisterHandlers(functions);
}
+
+private:
+ void IsRadioEnabled(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BTM, "(STUBBED) called"); // Spams a lot when controller applet is running
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(true);
+ }
+
+ void StartGamepadPairing(HLERequestContext& ctx) {
+ LOG_WARNING(Service_BTM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void CancelGamepadPairing(HLERequestContext& ctx) {
+ LOG_WARNING(Service_BTM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void CancelAudioDeviceConnectionRejection(HLERequestContext& ctx) {
+ LOG_WARNING(Service_BTM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void GetConnectedAudioDevices(HLERequestContext& ctx) {
+ LOG_WARNING(Service_BTM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(0);
+ }
+
+ void RequestAudioDeviceConnectionRejection(HLERequestContext& ctx) {
+ LOG_WARNING(Service_BTM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
};
class BTM_SYS final : public ServiceFramework<BTM_SYS> {
@@ -308,7 +348,7 @@ public:
private:
void GetCore(HLERequestContext& ctx) {
- LOG_DEBUG(Service_BTM, "called");
+ LOG_WARNING(Service_BTM, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
index 7d733eb54..96b225d5f 100644
--- a/src/core/hle/service/caps/caps_manager.cpp
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -228,12 +228,14 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail(
Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
const ScreenShotAttribute& attribute,
- std::span<const u8> image_data, u64 aruid) {
- return SaveScreenShot(out_entry, attribute, {}, image_data, aruid);
+ AlbumReportOption report_option, std::span<const u8> image_data,
+ u64 aruid) {
+ return SaveScreenShot(out_entry, attribute, report_option, {}, image_data, aruid);
}
Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
const ScreenShotAttribute& attribute,
+ AlbumReportOption report_option,
const ApplicationData& app_data, std::span<const u8> image_data,
u64 aruid) {
const u64 title_id = system.GetApplicationProcessProgramID();
@@ -407,10 +409,14 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
return ResultSuccess;
}
-static void PNGToMemory(void* context, void* png, int len) {
+void AlbumManager::FlipVerticallyOnWrite(bool flip) {
+ stbi_flip_vertically_on_write(flip);
+}
+
+static void PNGToMemory(void* context, void* data, int len) {
std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context);
- png_image->reserve(len);
- std::memcpy(png_image->data(), png, len);
+ unsigned char* png = static_cast<unsigned char*>(data);
+ png_image->insert(png_image->end(), png, png + len);
}
Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image,
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h
index 44d85117f..e20c70c7b 100644
--- a/src/core/hle/service/caps/caps_manager.h
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -59,14 +59,17 @@ public:
const ScreenShotDecodeOption& decoder_options) const;
Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
- std::span<const u8> image_data, u64 aruid);
- Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
- const ApplicationData& app_data, std::span<const u8> image_data,
+ AlbumReportOption report_option, std::span<const u8> image_data,
u64 aruid);
+ Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
+ AlbumReportOption report_option, const ApplicationData& app_data,
+ std::span<const u8> image_data, u64 aruid);
Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
const ScreenShotAttribute& attribute, const AlbumFileId& file_id,
std::span<const u8> image_data);
+ void FlipVerticallyOnWrite(bool flip);
+
private:
static constexpr std::size_t NandAlbumFileLimit = 1000;
static constexpr std::size_t SdAlbumFileLimit = 10000;
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index 1ba2b7972..eab023568 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -34,7 +34,7 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
ScreenShotAttribute attribute{};
- u32 report_option{};
+ AlbumReportOption report_option{};
INSERT_PADDING_BYTES(0x4);
u64 applet_resource_user_id{};
};
@@ -49,13 +49,16 @@ void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
parameters.applet_resource_user_id);
ApplicationAlbumEntry entry{};
- const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
- parameters.applet_resource_user_id);
+ manager->FlipVerticallyOnWrite(false);
+ const auto result =
+ manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option,
+ image_data_buffer, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(result);
rb.PushRaw(entry);
}
+
void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
@@ -83,6 +86,7 @@ void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
image_data_buffer.size(), thumbnail_image_data_buffer.size());
ApplicationAlbumEntry entry{};
+ manager->FlipVerticallyOnWrite(false);
const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute,
parameters.file_id, image_data_buffer);
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index e85625ee4..296b07b00 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -2,10 +2,12 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/service/caps/caps_manager.h"
#include "core/hle/service/caps/caps_su.h"
#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/ipc_helpers.h"
+#include "video_core/renderer_base.h"
namespace Service::Capture {
@@ -58,8 +60,10 @@ void IScreenShotApplicationService::SaveScreenShotEx0(HLERequestContext& ctx) {
parameters.applet_resource_user_id);
ApplicationAlbumEntry entry{};
- const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
- parameters.applet_resource_user_id);
+ manager->FlipVerticallyOnWrite(false);
+ const auto result =
+ manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option,
+ image_data_buffer, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(result);
@@ -88,13 +92,43 @@ void IScreenShotApplicationService::SaveScreenShotEx1(HLERequestContext& ctx) {
ApplicationAlbumEntry entry{};
ApplicationData app_data{};
std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData));
+ manager->FlipVerticallyOnWrite(false);
const auto result =
- manager->SaveScreenShot(entry, parameters.attribute, app_data, image_data_buffer,
- parameters.applet_resource_user_id);
+ manager->SaveScreenShot(entry, parameters.attribute, parameters.report_option, app_data,
+ image_data_buffer, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(result);
rb.PushRaw(entry);
}
+void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption report_option) {
+ auto& renderer = system.Renderer();
+ Layout::FramebufferLayout layout =
+ Layout::DefaultFrameLayout(screenshot_width, screenshot_height);
+
+ const Capture::ScreenShotAttribute attribute{
+ .unknown_0{},
+ .orientation = Capture::AlbumImageOrientation::None,
+ .unknown_1{},
+ .unknown_2{},
+ };
+
+ renderer.RequestScreenshot(
+ image_data.data(),
+ [attribute, report_option, this](bool invert_y) {
+ // Convert from BGRA to RGBA
+ for (std::size_t i = 0; i < image_data.size(); i += bytes_per_pixel) {
+ const u8 temp = image_data[i];
+ image_data[i] = image_data[i + 2];
+ image_data[i + 2] = temp;
+ }
+
+ Capture::ApplicationAlbumEntry entry{};
+ manager->FlipVerticallyOnWrite(invert_y);
+ manager->SaveScreenShot(entry, attribute, report_option, image_data, {});
+ },
+ layout);
+}
+
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index 89e71f506..21912e95f 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -10,6 +10,7 @@ class System;
}
namespace Service::Capture {
+enum class AlbumReportOption : s32;
class AlbumManager;
class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
@@ -18,11 +19,19 @@ public:
std::shared_ptr<AlbumManager> album_manager);
~IScreenShotApplicationService() override;
+ void CaptureAndSaveScreenshot(AlbumReportOption report_option);
+
private:
+ static constexpr std::size_t screenshot_width = 1280;
+ static constexpr std::size_t screenshot_height = 720;
+ static constexpr std::size_t bytes_per_pixel = 4;
+
void SetShimLibraryVersion(HLERequestContext& ctx);
void SaveScreenShotEx0(HLERequestContext& ctx);
void SaveScreenShotEx1(HLERequestContext& ctx);
+ std::array<u8, screenshot_width * screenshot_height * bytes_per_pixel> image_data;
+
std::shared_ptr<AlbumManager> manager;
};
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index 9d05f9801..0507b14e7 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -32,7 +32,7 @@ public:
{10200, nullptr, "SendFriendRequestForApplication"},
{10211, nullptr, "AddFacedFriendRequestForApplication"},
{10400, &IFriendService::GetBlockedUserListIds, "GetBlockedUserListIds"},
- {10420, nullptr, "IsBlockedUserListCacheAvailable"},
+ {10420, &IFriendService::CheckBlockedUserListAvailability, "CheckBlockedUserListAvailability"},
{10421, nullptr, "EnsureBlockedUserListAvailable"},
{10500, nullptr, "GetProfileList"},
{10600, nullptr, "DeclareOpenOnlinePlaySession"},
@@ -206,6 +206,17 @@ private:
rb.Push(true);
}
+ void CheckBlockedUserListAvailability(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto uuid{rp.PopRaw<Common::UUID>()};
+
+ LOG_WARNING(Service_Friend, "(STUBBED) called, uuid=0x{}", uuid.RawString());
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(true);
+ }
+
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* completion_event;
diff --git a/src/core/hle/service/hid/controllers/console_six_axis.cpp b/src/core/hle/service/hid/controllers/console_six_axis.cpp
new file mode 100644
index 000000000..b2bf1d78d
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/console_six_axis.cpp
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hid/emulated_console.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/controllers/console_six_axis.h"
+#include "core/memory.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C200;
+
+ConsoleSixAxis::ConsoleSixAxis(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+ : ControllerBase{hid_core_} {
+ console = hid_core.GetEmulatedConsole();
+ static_assert(SHARED_MEMORY_OFFSET + sizeof(ConsoleSharedMemory) < shared_memory_size,
+ "ConsoleSharedMemory is bigger than the shared memory");
+ shared_memory = std::construct_at(
+ reinterpret_cast<ConsoleSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
+}
+
+ConsoleSixAxis::~ConsoleSixAxis() = default;
+
+void ConsoleSixAxis::OnInit() {}
+
+void ConsoleSixAxis::OnRelease() {}
+
+void ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+ if (!IsControllerActivated()) {
+ return;
+ }
+
+ const auto motion_status = console->GetMotion();
+
+ shared_memory->sampling_number++;
+ shared_memory->is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest;
+ shared_memory->verticalization_error = motion_status.verticalization_error;
+ shared_memory->gyro_bias = motion_status.gyro_bias;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/console_six_axis.h b/src/core/hle/service/hid/controllers/console_six_axis.h
new file mode 100644
index 000000000..5b7c6a29a
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/console_six_axis.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/vector_math.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Core::HID {
+class EmulatedConsole;
+} // namespace Core::HID
+
+namespace Service::HID {
+class ConsoleSixAxis final : public ControllerBase {
+public:
+ explicit ConsoleSixAxis(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
+ ~ConsoleSixAxis() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+
+private:
+ // This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat
+ struct ConsoleSharedMemory {
+ u64 sampling_number{};
+ bool is_seven_six_axis_sensor_at_rest{};
+ INSERT_PADDING_BYTES(3); // padding
+ f32 verticalization_error{};
+ Common::Vec3f gyro_bias{};
+ INSERT_PADDING_BYTES(4); // padding
+ };
+ static_assert(sizeof(ConsoleSharedMemory) == 0x20, "ConsoleSharedMemory is an invalid size");
+
+ ConsoleSharedMemory* shared_memory = nullptr;
+ Core::HID::EmulatedConsole* console = nullptr;
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/controller_base.cpp b/src/core/hle/service/hid/controllers/controller_base.cpp
index c58d67d7d..0bcd87062 100644
--- a/src/core/hle/service/hid/controllers/controller_base.cpp
+++ b/src/core/hle/service/hid/controllers/controller_base.cpp
@@ -8,12 +8,17 @@ namespace Service::HID {
ControllerBase::ControllerBase(Core::HID::HIDCore& hid_core_) : hid_core(hid_core_) {}
ControllerBase::~ControllerBase() = default;
-void ControllerBase::ActivateController() {
+Result ControllerBase::Activate() {
if (is_activated) {
- return;
+ return ResultSuccess;
}
is_activated = true;
OnInit();
+ return ResultSuccess;
+}
+
+Result ControllerBase::Activate(u64 aruid) {
+ return Activate();
}
void ControllerBase::DeactivateController() {
diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h
index d6f7a5073..9a44ee41e 100644
--- a/src/core/hle/service/hid/controllers/controller_base.h
+++ b/src/core/hle/service/hid/controllers/controller_base.h
@@ -4,6 +4,7 @@
#pragma once
#include "common/common_types.h"
+#include "core/hle/result.h"
namespace Core::Timing {
class CoreTiming;
@@ -31,7 +32,8 @@ public:
// When the controller is requesting a motion update for the shared memory
virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing) {}
- void ActivateController();
+ Result Activate();
+ Result Activate(u64 aruid);
void DeactivateController();
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
index 8ec9f4a95..9de19ebfc 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.cpp
+++ b/src/core/hle/service/hid/controllers/debug_pad.cpp
@@ -13,7 +13,7 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x00000;
-Controller_DebugPad::Controller_DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+DebugPad::DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
: ControllerBase{hid_core_} {
static_assert(SHARED_MEMORY_OFFSET + sizeof(DebugPadSharedMemory) < shared_memory_size,
"DebugPadSharedMemory is bigger than the shared memory");
@@ -22,13 +22,13 @@ Controller_DebugPad::Controller_DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
}
-Controller_DebugPad::~Controller_DebugPad() = default;
+DebugPad::~DebugPad() = default;
-void Controller_DebugPad::OnInit() {}
+void DebugPad::OnInit() {}
-void Controller_DebugPad::OnRelease() {}
+void DebugPad::OnRelease() {}
-void Controller_DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
shared_memory->debug_pad_lifo.buffer_count = 0;
shared_memory->debug_pad_lifo.buffer_tail = 0;
diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h
index 68ff0ea79..5566dba77 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.h
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -15,10 +15,10 @@ struct AnalogStickState;
} // namespace Core::HID
namespace Service::HID {
-class Controller_DebugPad final : public ControllerBase {
+class DebugPad final : public ControllerBase {
public:
- explicit Controller_DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
- ~Controller_DebugPad() override;
+ explicit DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
+ ~DebugPad() override;
// Called when the controller is initialized
void OnInit() override;
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index 63eecd42b..59b2ec73c 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -23,7 +23,7 @@ constexpr f32 Square(s32 num) {
return static_cast<f32>(num * num);
}
-Controller_Gesture::Controller_Gesture(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+Gesture::Gesture(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
: ControllerBase(hid_core_) {
static_assert(SHARED_MEMORY_OFFSET + sizeof(GestureSharedMemory) < shared_memory_size,
"GestureSharedMemory is bigger than the shared memory");
@@ -31,17 +31,17 @@ Controller_Gesture::Controller_Gesture(Core::HID::HIDCore& hid_core_, u8* raw_sh
reinterpret_cast<GestureSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
console = hid_core.GetEmulatedConsole();
}
-Controller_Gesture::~Controller_Gesture() = default;
+Gesture::~Gesture() = default;
-void Controller_Gesture::OnInit() {
+void Gesture::OnInit() {
shared_memory->gesture_lifo.buffer_count = 0;
shared_memory->gesture_lifo.buffer_tail = 0;
force_update = true;
}
-void Controller_Gesture::OnRelease() {}
+void Gesture::OnRelease() {}
-void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
shared_memory->gesture_lifo.buffer_count = 0;
shared_memory->gesture_lifo.buffer_tail = 0;
@@ -64,7 +64,7 @@ void Controller_Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
UpdateGestureSharedMemory(gesture, time_difference);
}
-void Controller_Gesture::ReadTouchInput() {
+void Gesture::ReadTouchInput() {
if (!Settings::values.touchscreen.enabled) {
fingers = {};
return;
@@ -76,8 +76,7 @@ void Controller_Gesture::ReadTouchInput() {
}
}
-bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
- f32 time_difference) {
+bool Gesture::ShouldUpdateGesture(const GestureProperties& gesture, f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
if (force_update) {
force_update = false;
@@ -100,8 +99,7 @@ bool Controller_Gesture::ShouldUpdateGesture(const GestureProperties& gesture,
return false;
}
-void Controller_Gesture::UpdateGestureSharedMemory(GestureProperties& gesture,
- f32 time_difference) {
+void Gesture::UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_difference) {
GestureType type = GestureType::Idle;
GestureAttribute attributes{};
@@ -138,8 +136,8 @@ void Controller_Gesture::UpdateGestureSharedMemory(GestureProperties& gesture,
shared_memory->gesture_lifo.WriteNextEntry(next_state);
}
-void Controller_Gesture::NewGesture(GestureProperties& gesture, GestureType& type,
- GestureAttribute& attributes) {
+void Gesture::NewGesture(GestureProperties& gesture, GestureType& type,
+ GestureAttribute& attributes) {
const auto& last_entry = GetLastGestureEntry();
gesture.detection_count++;
@@ -152,8 +150,8 @@ void Controller_Gesture::NewGesture(GestureProperties& gesture, GestureType& typ
}
}
-void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, GestureType& type,
- f32 time_difference) {
+void Gesture::UpdateExistingGesture(GestureProperties& gesture, GestureType& type,
+ f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
// Promote to pan type if touch moved
@@ -186,9 +184,8 @@ void Controller_Gesture::UpdateExistingGesture(GestureProperties& gesture, Gestu
}
}
-void Controller_Gesture::EndGesture(GestureProperties& gesture,
- GestureProperties& last_gesture_props, GestureType& type,
- GestureAttribute& attributes, f32 time_difference) {
+void Gesture::EndGesture(GestureProperties& gesture, GestureProperties& last_gesture_props,
+ GestureType& type, GestureAttribute& attributes, f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
if (last_gesture_props.active_points != 0) {
@@ -222,9 +219,8 @@ void Controller_Gesture::EndGesture(GestureProperties& gesture,
}
}
-void Controller_Gesture::SetTapEvent(GestureProperties& gesture,
- GestureProperties& last_gesture_props, GestureType& type,
- GestureAttribute& attributes) {
+void Gesture::SetTapEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
+ GestureType& type, GestureAttribute& attributes) {
type = GestureType::Tap;
gesture = last_gesture_props;
force_update = true;
@@ -236,9 +232,8 @@ void Controller_Gesture::SetTapEvent(GestureProperties& gesture,
}
}
-void Controller_Gesture::UpdatePanEvent(GestureProperties& gesture,
- GestureProperties& last_gesture_props, GestureType& type,
- f32 time_difference) {
+void Gesture::UpdatePanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
+ GestureType& type, f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
next_state.delta = gesture.mid_point - last_entry.pos;
@@ -263,9 +258,8 @@ void Controller_Gesture::UpdatePanEvent(GestureProperties& gesture,
}
}
-void Controller_Gesture::EndPanEvent(GestureProperties& gesture,
- GestureProperties& last_gesture_props, GestureType& type,
- f32 time_difference) {
+void Gesture::EndPanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
+ GestureType& type, f32 time_difference) {
const auto& last_entry = GetLastGestureEntry();
next_state.vel_x =
static_cast<f32>(last_entry.delta.x) / (last_pan_time_difference + time_difference);
@@ -287,8 +281,8 @@ void Controller_Gesture::EndPanEvent(GestureProperties& gesture,
force_update = true;
}
-void Controller_Gesture::SetSwipeEvent(GestureProperties& gesture,
- GestureProperties& last_gesture_props, GestureType& type) {
+void Gesture::SetSwipeEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
+ GestureType& type) {
const auto& last_entry = GetLastGestureEntry();
type = GestureType::Swipe;
@@ -311,11 +305,11 @@ void Controller_Gesture::SetSwipeEvent(GestureProperties& gesture,
next_state.direction = GestureDirection::Up;
}
-const Controller_Gesture::GestureState& Controller_Gesture::GetLastGestureEntry() const {
+const Gesture::GestureState& Gesture::GetLastGestureEntry() const {
return shared_memory->gesture_lifo.ReadCurrentEntry().state;
}
-Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() {
+Gesture::GestureProperties Gesture::GetGestureProperties() {
GestureProperties gesture;
std::array<Core::HID::TouchFinger, MAX_POINTS> active_fingers;
const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(),
diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h
index 0d6099ea0..4c6f8ee07 100644
--- a/src/core/hle/service/hid/controllers/gesture.h
+++ b/src/core/hle/service/hid/controllers/gesture.h
@@ -12,10 +12,10 @@
#include "core/hle/service/hid/ring_lifo.h"
namespace Service::HID {
-class Controller_Gesture final : public ControllerBase {
+class Gesture final : public ControllerBase {
public:
- explicit Controller_Gesture(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
- ~Controller_Gesture() override;
+ explicit Gesture(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
+ ~Gesture() override;
// Called when the controller is initialized
void OnInit() override;
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
index 117d87433..ddb1b0ba4 100644
--- a/src/core/hle/service/hid/controllers/keyboard.cpp
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -12,7 +12,7 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
-Controller_Keyboard::Controller_Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+Keyboard::Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
: ControllerBase{hid_core_} {
static_assert(SHARED_MEMORY_OFFSET + sizeof(KeyboardSharedMemory) < shared_memory_size,
"KeyboardSharedMemory is bigger than the shared memory");
@@ -21,13 +21,13 @@ Controller_Keyboard::Controller_Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_
emulated_devices = hid_core.GetEmulatedDevices();
}
-Controller_Keyboard::~Controller_Keyboard() = default;
+Keyboard::~Keyboard() = default;
-void Controller_Keyboard::OnInit() {}
+void Keyboard::OnInit() {}
-void Controller_Keyboard::OnRelease() {}
+void Keyboard::OnRelease() {}
-void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
shared_memory->keyboard_lifo.buffer_count = 0;
shared_memory->keyboard_lifo.buffer_tail = 0;
diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h
index 7532f53c6..172ec1309 100644
--- a/src/core/hle/service/hid/controllers/keyboard.h
+++ b/src/core/hle/service/hid/controllers/keyboard.h
@@ -14,10 +14,10 @@ struct KeyboardKey;
} // namespace Core::HID
namespace Service::HID {
-class Controller_Keyboard final : public ControllerBase {
+class Keyboard final : public ControllerBase {
public:
- explicit Controller_Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
- ~Controller_Keyboard() override;
+ explicit Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
+ ~Keyboard() override;
// Called when the controller is initialized
void OnInit() override;
diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp
index 0afc66681..6e5a04e34 100644
--- a/src/core/hle/service/hid/controllers/mouse.cpp
+++ b/src/core/hle/service/hid/controllers/mouse.cpp
@@ -12,8 +12,7 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
-Controller_Mouse::Controller_Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
- : ControllerBase{hid_core_} {
+Mouse::Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) : ControllerBase{hid_core_} {
static_assert(SHARED_MEMORY_OFFSET + sizeof(MouseSharedMemory) < shared_memory_size,
"MouseSharedMemory is bigger than the shared memory");
shared_memory = std::construct_at(
@@ -21,12 +20,12 @@ Controller_Mouse::Controller_Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared
emulated_devices = hid_core.GetEmulatedDevices();
}
-Controller_Mouse::~Controller_Mouse() = default;
+Mouse::~Mouse() = default;
-void Controller_Mouse::OnInit() {}
-void Controller_Mouse::OnRelease() {}
+void Mouse::OnInit() {}
+void Mouse::OnRelease() {}
-void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
shared_memory->mouse_lifo.buffer_count = 0;
shared_memory->mouse_lifo.buffer_tail = 0;
diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h
index 733d00577..a80f3823f 100644
--- a/src/core/hle/service/hid/controllers/mouse.h
+++ b/src/core/hle/service/hid/controllers/mouse.h
@@ -14,10 +14,10 @@ struct AnalogStickState;
} // namespace Core::HID
namespace Service::HID {
-class Controller_Mouse final : public ControllerBase {
+class Mouse final : public ControllerBase {
public:
- explicit Controller_Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
- ~Controller_Mouse() override;
+ explicit Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
+ ~Mouse() override;
// Called when the controller is initialized
void OnInit() override;
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index bc822f19e..08ee9de9c 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -18,6 +18,7 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/errors.h"
+#include "core/hle/service/hid/hid_util.h"
#include "core/hle/service/kernel_helpers.h"
namespace Service::HID {
@@ -29,60 +30,8 @@ constexpr std::array<Core::HID::NpadIdType, 10> npad_id_list{
Core::HID::NpadIdType::Handheld,
};
-bool Controller_NPad::IsNpadIdValid(Core::HID::NpadIdType npad_id) {
- switch (npad_id) {
- case Core::HID::NpadIdType::Player1:
- case Core::HID::NpadIdType::Player2:
- case Core::HID::NpadIdType::Player3:
- case Core::HID::NpadIdType::Player4:
- case Core::HID::NpadIdType::Player5:
- case Core::HID::NpadIdType::Player6:
- case Core::HID::NpadIdType::Player7:
- case Core::HID::NpadIdType::Player8:
- case Core::HID::NpadIdType::Other:
- case Core::HID::NpadIdType::Handheld:
- return true;
- default:
- LOG_ERROR(Service_HID, "Invalid npad id {}", npad_id);
- return false;
- }
-}
-
-Result Controller_NPad::IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle) {
- const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id));
- const bool npad_type = device_handle.npad_type < Core::HID::NpadStyleIndex::MaxNpadType;
- const bool device_index = device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
-
- if (!npad_type) {
- return VibrationInvalidStyleIndex;
- }
- if (!npad_id) {
- return VibrationInvalidNpadId;
- }
- if (!device_index) {
- return VibrationDeviceIndexOutOfRange;
- }
-
- return ResultSuccess;
-}
-
-Result Controller_NPad::VerifyValidSixAxisSensorHandle(
- const Core::HID::SixAxisSensorHandle& device_handle) {
- const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(device_handle.npad_id));
- const bool device_index = device_handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
-
- if (!npad_id) {
- return InvalidNpadId;
- }
- if (!device_index) {
- return NpadDeviceIndexOutOfRange;
- }
-
- return ResultSuccess;
-}
-
-Controller_NPad::Controller_NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
- KernelHelpers::ServiceContext& service_context_)
+NPad::NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_)
: ControllerBase{hid_core_}, service_context{service_context_} {
static_assert(NPAD_OFFSET + (NPAD_COUNT * sizeof(NpadInternalState)) < shared_memory_size);
for (std::size_t i = 0; i < controller_data.size(); ++i) {
@@ -103,7 +52,7 @@ Controller_NPad::Controller_NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_m
}
}
-Controller_NPad::~Controller_NPad() {
+NPad::~NPad() {
for (std::size_t i = 0; i < controller_data.size(); ++i) {
auto& controller = controller_data[i];
controller.device->DeleteCallback(controller.callback_key);
@@ -111,8 +60,7 @@ Controller_NPad::~Controller_NPad() {
OnRelease();
}
-void Controller_NPad::ControllerUpdate(Core::HID::ControllerTriggerType type,
- std::size_t controller_idx) {
+void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx) {
if (type == Core::HID::ControllerTriggerType::All) {
ControllerUpdate(Core::HID::ControllerTriggerType::Connected, controller_idx);
ControllerUpdate(Core::HID::ControllerTriggerType::Battery, controller_idx);
@@ -150,7 +98,7 @@ void Controller_NPad::ControllerUpdate(Core::HID::ControllerTriggerType type,
}
}
-void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
+void NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
auto& controller = GetControllerFromNpadIdType(npad_id);
if (!IsControllerSupported(controller.device->GetNpadStyleIndex())) {
return;
@@ -344,12 +292,13 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
Common::Input::PollingMode::Active);
}
+
SignalStyleSetChangedEvent(npad_id);
WriteEmptyEntry(controller.shared_memory);
hid_core.SetLastActiveController(npad_id);
}
-void Controller_NPad::OnInit() {
+void NPad::OnInit() {
if (!IsControllerActivated()) {
return;
}
@@ -383,7 +332,7 @@ void Controller_NPad::OnInit() {
}
}
-void Controller_NPad::WriteEmptyEntry(NpadInternalState* npad) {
+void NPad::WriteEmptyEntry(NpadInternalState* npad) {
NPadGenericState dummy_pad_state{};
NpadGcTriggerState dummy_gc_state{};
dummy_pad_state.sampling_number = npad->fullkey_lifo.ReadCurrentEntry().sampling_number + 1;
@@ -404,7 +353,7 @@ void Controller_NPad::WriteEmptyEntry(NpadInternalState* npad) {
npad->gc_trigger_lifo.WriteNextEntry(dummy_gc_state);
}
-void Controller_NPad::OnRelease() {
+void NPad::OnRelease() {
is_controller_initialized = false;
for (std::size_t i = 0; i < controller_data.size(); ++i) {
auto& controller = controller_data[i];
@@ -415,7 +364,7 @@ void Controller_NPad::OnRelease() {
}
}
-void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
+void NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
std::scoped_lock lock{mutex};
auto& controller = GetControllerFromNpadIdType(npad_id);
const auto controller_type = controller.device->GetNpadStyleIndex();
@@ -457,12 +406,14 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
pad_entry.l_stick = stick_state.left;
}
- if (controller_type == Core::HID::NpadStyleIndex::JoyconLeft) {
+ if (controller_type == Core::HID::NpadStyleIndex::JoyconLeft ||
+ controller_type == Core::HID::NpadStyleIndex::JoyconDual) {
pad_entry.npad_buttons.left_sl.Assign(button_state.left_sl);
pad_entry.npad_buttons.left_sr.Assign(button_state.left_sr);
}
- if (controller_type == Core::HID::NpadStyleIndex::JoyconRight) {
+ if (controller_type == Core::HID::NpadStyleIndex::JoyconRight ||
+ controller_type == Core::HID::NpadStyleIndex::JoyconDual) {
pad_entry.npad_buttons.right_sl.Assign(button_state.right_sl);
pad_entry.npad_buttons.right_sr.Assign(button_state.right_sr);
}
@@ -482,7 +433,7 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
}
}
-void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
return;
}
@@ -612,134 +563,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
}
}
-void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing) {
- if (!IsControllerActivated()) {
- return;
- }
-
- for (std::size_t i = 0; i < controller_data.size(); ++i) {
- auto& controller = controller_data[i];
-
- const auto& controller_type = controller.device->GetNpadStyleIndex();
-
- if (controller_type == Core::HID::NpadStyleIndex::None ||
- !controller.device->IsConnected()) {
- continue;
- }
-
- auto* npad = controller.shared_memory;
- const auto& motion_state = controller.device->GetMotions();
- auto& sixaxis_fullkey_state = controller.sixaxis_fullkey_state;
- auto& sixaxis_handheld_state = controller.sixaxis_handheld_state;
- auto& sixaxis_dual_left_state = controller.sixaxis_dual_left_state;
- auto& sixaxis_dual_right_state = controller.sixaxis_dual_right_state;
- auto& sixaxis_left_lifo_state = controller.sixaxis_left_lifo_state;
- auto& sixaxis_right_lifo_state = controller.sixaxis_right_lifo_state;
-
- // Clear previous state
- sixaxis_fullkey_state = {};
- sixaxis_handheld_state = {};
- sixaxis_dual_left_state = {};
- sixaxis_dual_right_state = {};
- sixaxis_left_lifo_state = {};
- sixaxis_right_lifo_state = {};
-
- if (controller.sixaxis_sensor_enabled && Settings::values.motion_enabled.GetValue()) {
- controller.sixaxis_at_rest = true;
- for (std::size_t e = 0; e < motion_state.size(); ++e) {
- controller.sixaxis_at_rest =
- controller.sixaxis_at_rest && motion_state[e].is_at_rest;
- }
- }
-
- const auto set_motion_state = [&](SixAxisSensorState& state,
- const Core::HID::ControllerMotion& hid_state) {
- using namespace std::literals::chrono_literals;
- static constexpr SixAxisSensorState default_motion_state = {
- .delta_time = std::chrono::nanoseconds(5ms).count(),
- .accel = {0, 0, -1.0f},
- .orientation =
- {
- Common::Vec3f{1.0f, 0, 0},
- Common::Vec3f{0, 1.0f, 0},
- Common::Vec3f{0, 0, 1.0f},
- },
- .attribute = {1},
- };
- if (!controller.sixaxis_sensor_enabled) {
- state = default_motion_state;
- return;
- }
- if (!Settings::values.motion_enabled.GetValue()) {
- state = default_motion_state;
- return;
- }
- state.attribute.is_connected.Assign(1);
- state.delta_time = std::chrono::nanoseconds(5ms).count();
- state.accel = hid_state.accel;
- state.gyro = hid_state.gyro;
- state.rotation = hid_state.rotation;
- state.orientation = hid_state.orientation;
- };
-
- switch (controller_type) {
- case Core::HID::NpadStyleIndex::None:
- ASSERT(false);
- break;
- case Core::HID::NpadStyleIndex::ProController:
- set_motion_state(sixaxis_fullkey_state, motion_state[0]);
- break;
- case Core::HID::NpadStyleIndex::Handheld:
- set_motion_state(sixaxis_handheld_state, motion_state[0]);
- break;
- case Core::HID::NpadStyleIndex::JoyconDual:
- set_motion_state(sixaxis_dual_left_state, motion_state[0]);
- set_motion_state(sixaxis_dual_right_state, motion_state[1]);
- break;
- case Core::HID::NpadStyleIndex::JoyconLeft:
- set_motion_state(sixaxis_left_lifo_state, motion_state[0]);
- break;
- case Core::HID::NpadStyleIndex::JoyconRight:
- set_motion_state(sixaxis_right_lifo_state, motion_state[1]);
- break;
- case Core::HID::NpadStyleIndex::Pokeball:
- using namespace std::literals::chrono_literals;
- set_motion_state(sixaxis_fullkey_state, motion_state[0]);
- sixaxis_fullkey_state.delta_time = std::chrono::nanoseconds(15ms).count();
- break;
- default:
- break;
- }
-
- sixaxis_fullkey_state.sampling_number =
- npad->sixaxis_fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1;
- sixaxis_handheld_state.sampling_number =
- npad->sixaxis_handheld_lifo.ReadCurrentEntry().state.sampling_number + 1;
- sixaxis_dual_left_state.sampling_number =
- npad->sixaxis_dual_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
- sixaxis_dual_right_state.sampling_number =
- npad->sixaxis_dual_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
- sixaxis_left_lifo_state.sampling_number =
- npad->sixaxis_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
- sixaxis_right_lifo_state.sampling_number =
- npad->sixaxis_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
-
- if (Core::HID::IndexToNpadIdType(i) == Core::HID::NpadIdType::Handheld) {
- // This buffer only is updated on handheld on HW
- npad->sixaxis_handheld_lifo.WriteNextEntry(sixaxis_handheld_state);
- } else {
- // Handheld doesn't update this buffer on HW
- npad->sixaxis_fullkey_lifo.WriteNextEntry(sixaxis_fullkey_state);
- }
-
- npad->sixaxis_dual_left_lifo.WriteNextEntry(sixaxis_dual_left_state);
- npad->sixaxis_dual_right_lifo.WriteNextEntry(sixaxis_dual_right_state);
- npad->sixaxis_left_lifo.WriteNextEntry(sixaxis_left_lifo_state);
- npad->sixaxis_right_lifo.WriteNextEntry(sixaxis_right_lifo_state);
- }
-}
-
-void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
+void NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
hid_core.SetSupportedStyleTag(style_set);
if (is_controller_initialized) {
@@ -750,14 +574,14 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
is_controller_initialized = true;
}
-Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
+Core::HID::NpadStyleTag NPad::GetSupportedStyleSet() const {
if (!is_controller_initialized) {
return {Core::HID::NpadStyleSet::None};
}
return hid_core.GetSupportedStyleTag();
}
-Result Controller_NPad::SetSupportedNpadIdTypes(std::span<const u8> data) {
+Result NPad::SetSupportedNpadIdTypes(std::span<const u8> data) {
constexpr std::size_t max_number_npad_ids = 0xa;
const auto length = data.size();
ASSERT(length > 0 && (length % sizeof(u32)) == 0);
@@ -773,17 +597,17 @@ Result Controller_NPad::SetSupportedNpadIdTypes(std::span<const u8> data) {
return ResultSuccess;
}
-void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) {
+void NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) {
const auto copy_amount = supported_npad_id_types.size() * sizeof(u32);
ASSERT(max_length <= copy_amount);
std::memcpy(data, supported_npad_id_types.data(), copy_amount);
}
-std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const {
+std::size_t NPad::GetSupportedNpadIdTypesSize() const {
return supported_npad_id_types.size();
}
-void Controller_NPad::SetHoldType(NpadJoyHoldType joy_hold_type) {
+void NPad::SetHoldType(NpadJoyHoldType joy_hold_type) {
if (joy_hold_type != NpadJoyHoldType::Horizontal &&
joy_hold_type != NpadJoyHoldType::Vertical) {
LOG_ERROR(Service_HID, "Npad joy hold type needs to be valid, joy_hold_type={}",
@@ -793,11 +617,11 @@ void Controller_NPad::SetHoldType(NpadJoyHoldType joy_hold_type) {
hold_type = joy_hold_type;
}
-Controller_NPad::NpadJoyHoldType Controller_NPad::GetHoldType() const {
+NPad::NpadJoyHoldType NPad::GetHoldType() const {
return hold_type;
}
-void Controller_NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode) {
+void NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode) {
if (activation_mode >= NpadHandheldActivationMode::MaxActivationMode) {
ASSERT_MSG(false, "Activation mode should be always None, Single or Dual");
return;
@@ -806,21 +630,20 @@ void Controller_NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode a
handheld_activation_mode = activation_mode;
}
-Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActivationMode() const {
+NPad::NpadHandheldActivationMode NPad::GetNpadHandheldActivationMode() const {
return handheld_activation_mode;
}
-void Controller_NPad::SetNpadCommunicationMode(NpadCommunicationMode communication_mode_) {
+void NPad::SetNpadCommunicationMode(NpadCommunicationMode communication_mode_) {
communication_mode = communication_mode_;
}
-Controller_NPad::NpadCommunicationMode Controller_NPad::GetNpadCommunicationMode() const {
+NPad::NpadCommunicationMode NPad::GetNpadCommunicationMode() const {
return communication_mode;
}
-bool Controller_NPad::SetNpadMode(Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id,
- NpadJoyDeviceType npad_device_type,
- NpadJoyAssignmentMode assignment_mode) {
+bool NPad::SetNpadMode(Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id,
+ NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode) {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
return false;
@@ -889,9 +712,8 @@ bool Controller_NPad::SetNpadMode(Core::HID::NpadIdType& new_npad_id, Core::HID:
return true;
}
-bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id,
- std::size_t device_index,
- const Core::HID::VibrationValue& vibration_value) {
+bool NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, std::size_t device_index,
+ const Core::HID::VibrationValue& vibration_value) {
auto& controller = GetControllerFromNpadIdType(npad_id);
if (!controller.device->IsConnected()) {
return false;
@@ -935,10 +757,9 @@ bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id,
return controller.device->SetVibration(device_index, vibration);
}
-void Controller_NPad::VibrateController(
- const Core::HID::VibrationDeviceHandle& vibration_device_handle,
- const Core::HID::VibrationValue& vibration_value) {
- if (IsDeviceHandleValid(vibration_device_handle).IsError()) {
+void NPad::VibrateController(const Core::HID::VibrationDeviceHandle& vibration_device_handle,
+ const Core::HID::VibrationValue& vibration_value) {
+ if (IsVibrationHandleValid(vibration_device_handle).IsError()) {
return;
}
@@ -982,7 +803,7 @@ void Controller_NPad::VibrateController(
}
}
-void Controller_NPad::VibrateControllers(
+void NPad::VibrateControllers(
std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles,
std::span<const Core::HID::VibrationValue> vibration_values) {
if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
@@ -999,9 +820,9 @@ void Controller_NPad::VibrateControllers(
}
}
-Core::HID::VibrationValue Controller_NPad::GetLastVibration(
+Core::HID::VibrationValue NPad::GetLastVibration(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) const {
- if (IsDeviceHandleValid(vibration_device_handle).IsError()) {
+ if (IsVibrationHandleValid(vibration_device_handle).IsError()) {
return {};
}
@@ -1010,9 +831,9 @@ Core::HID::VibrationValue Controller_NPad::GetLastVibration(
return controller.vibration[device_index].latest_vibration_value;
}
-void Controller_NPad::InitializeVibrationDevice(
+void NPad::InitializeVibrationDevice(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) {
- if (IsDeviceHandleValid(vibration_device_handle).IsError()) {
+ if (IsVibrationHandleValid(vibration_device_handle).IsError()) {
return;
}
@@ -1021,8 +842,8 @@ void Controller_NPad::InitializeVibrationDevice(
InitializeVibrationDeviceAtIndex(npad_index, device_index);
}
-void Controller_NPad::InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npad_id,
- std::size_t device_index) {
+void NPad::InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npad_id,
+ std::size_t device_index) {
auto& controller = GetControllerFromNpadIdType(npad_id);
if (!Settings::values.vibration_enabled.GetValue()) {
controller.vibration[device_index].device_mounted = false;
@@ -1033,13 +854,13 @@ void Controller_NPad::InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npa
controller.device->IsVibrationEnabled(device_index);
}
-void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
+void NPad::SetPermitVibrationSession(bool permit_vibration_session) {
permit_vibration_session_enabled = permit_vibration_session;
}
-bool Controller_NPad::IsVibrationDeviceMounted(
+bool NPad::IsVibrationDeviceMounted(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) const {
- if (IsDeviceHandleValid(vibration_device_handle).IsError()) {
+ if (IsVibrationHandleValid(vibration_device_handle).IsError()) {
return false;
}
@@ -1048,7 +869,7 @@ bool Controller_NPad::IsVibrationDeviceMounted(
return controller.vibration[device_index].device_mounted;
}
-Kernel::KReadableEvent& Controller_NPad::GetStyleSetChangedEvent(Core::HID::NpadIdType npad_id) {
+Kernel::KReadableEvent& NPad::GetStyleSetChangedEvent(Core::HID::NpadIdType npad_id) {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
// Fallback to player 1
@@ -1060,18 +881,17 @@ Kernel::KReadableEvent& Controller_NPad::GetStyleSetChangedEvent(Core::HID::Npad
return controller.styleset_changed_event->GetReadableEvent();
}
-void Controller_NPad::SignalStyleSetChangedEvent(Core::HID::NpadIdType npad_id) const {
+void NPad::SignalStyleSetChangedEvent(Core::HID::NpadIdType npad_id) const {
const auto& controller = GetControllerFromNpadIdType(npad_id);
controller.styleset_changed_event->Signal();
}
-void Controller_NPad::AddNewControllerAt(Core::HID::NpadStyleIndex controller,
- Core::HID::NpadIdType npad_id) {
+void NPad::AddNewControllerAt(Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id) {
UpdateControllerAt(controller, npad_id, true);
}
-void Controller_NPad::UpdateControllerAt(Core::HID::NpadStyleIndex type,
- Core::HID::NpadIdType npad_id, bool connected) {
+void NPad::UpdateControllerAt(Core::HID::NpadStyleIndex type, Core::HID::NpadIdType npad_id,
+ bool connected) {
auto& controller = GetControllerFromNpadIdType(npad_id);
if (!connected) {
DisconnectNpad(npad_id);
@@ -1082,7 +902,7 @@ void Controller_NPad::UpdateControllerAt(Core::HID::NpadStyleIndex type,
InitNewlyAddedController(npad_id);
}
-Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
+Result NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
return InvalidNpadId;
@@ -1108,9 +928,9 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
shared_memory->sixaxis_dual_right_properties.raw = 0;
shared_memory->sixaxis_left_properties.raw = 0;
shared_memory->sixaxis_right_properties.raw = 0;
- shared_memory->battery_level_dual = 0;
- shared_memory->battery_level_left = 0;
- shared_memory->battery_level_right = 0;
+ shared_memory->battery_level_dual = Core::HID::NpadBatteryLevel::Empty;
+ shared_memory->battery_level_left = Core::HID::NpadBatteryLevel::Empty;
+ shared_memory->battery_level_right = Core::HID::NpadBatteryLevel::Empty;
shared_memory->fullkey_color = {
.attribute = ColorAttribute::NoController,
.fullkey = {},
@@ -1131,54 +951,9 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
return ResultSuccess;
}
-Result Controller_NPad::SetGyroscopeZeroDriftMode(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::GyroscopeZeroDriftMode drift_mode) {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- auto& sixaxis = GetSixaxisState(sixaxis_handle);
- auto& controller = GetControllerFromHandle(sixaxis_handle);
- sixaxis.gyroscope_zero_drift_mode = drift_mode;
- controller.device->SetGyroscopeZeroDriftMode(drift_mode);
-
- return ResultSuccess;
-}
-
-Result Controller_NPad::GetGyroscopeZeroDriftMode(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::GyroscopeZeroDriftMode& drift_mode) const {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- const auto& sixaxis = GetSixaxisState(sixaxis_handle);
- drift_mode = sixaxis.gyroscope_zero_drift_mode;
-
- return ResultSuccess;
-}
-
-Result Controller_NPad::IsSixAxisSensorAtRest(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- bool& is_at_rest) const {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- const auto& controller = GetControllerFromHandle(sixaxis_handle);
- is_at_rest = controller.sixaxis_at_rest;
- return ResultSuccess;
-}
-
-Result Controller_NPad::IsFirmwareUpdateAvailableForSixAxisSensor(
+Result NPad::IsFirmwareUpdateAvailableForSixAxisSensor(
const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_firmware_available) const {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
if (is_valid.IsError()) {
LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
return is_valid;
@@ -1189,65 +964,9 @@ Result Controller_NPad::IsFirmwareUpdateAvailableForSixAxisSensor(
return ResultSuccess;
}
-Result Controller_NPad::EnableSixAxisSensorUnalteredPassthrough(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_enabled) {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- auto& sixaxis = GetSixaxisState(sixaxis_handle);
- sixaxis.unaltered_passtrough = is_enabled;
- return ResultSuccess;
-}
-
-Result Controller_NPad::IsSixAxisSensorUnalteredPassthroughEnabled(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_enabled) const {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- const auto& sixaxis = GetSixaxisState(sixaxis_handle);
- is_enabled = sixaxis.unaltered_passtrough;
- return ResultSuccess;
-}
-
-Result Controller_NPad::LoadSixAxisSensorCalibrationParameter(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::SixAxisSensorCalibrationParameter& calibration) const {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- // TODO: Request this data to the controller. On error return 0xd8ca
- const auto& sixaxis = GetSixaxisState(sixaxis_handle);
- calibration = sixaxis.calibration;
- return ResultSuccess;
-}
-
-Result Controller_NPad::GetSixAxisSensorIcInformation(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::SixAxisSensorIcInformation& ic_information) const {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- // TODO: Request this data to the controller. On error return 0xd8ca
- const auto& sixaxis = GetSixaxisState(sixaxis_handle);
- ic_information = sixaxis.ic_information;
- return ResultSuccess;
-}
-
-Result Controller_NPad::ResetIsSixAxisSensorDeviceNewlyAssigned(
+Result NPad::ResetIsSixAxisSensorDeviceNewlyAssigned(
const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
if (is_valid.IsError()) {
LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
return is_valid;
@@ -1259,83 +978,32 @@ Result Controller_NPad::ResetIsSixAxisSensorDeviceNewlyAssigned(
return ResultSuccess;
}
-Result Controller_NPad::SetSixAxisEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- bool sixaxis_status) {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- auto& controller = GetControllerFromHandle(sixaxis_handle);
- controller.sixaxis_sensor_enabled = sixaxis_status;
- return ResultSuccess;
+NPad::SixAxisLifo& NPad::GetSixAxisFullkeyLifo(Core::HID::NpadIdType npad_id) {
+ return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_fullkey_lifo;
}
-Result Controller_NPad::IsSixAxisSensorFusionEnabled(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_fusion_enabled) const {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- const auto& sixaxis = GetSixaxisState(sixaxis_handle);
- is_fusion_enabled = sixaxis.is_fusion_enabled;
-
- return ResultSuccess;
+NPad::SixAxisLifo& NPad::GetSixAxisHandheldLifo(Core::HID::NpadIdType npad_id) {
+ return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_handheld_lifo;
}
-Result Controller_NPad::SetSixAxisFusionEnabled(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_fusion_enabled) {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
- auto& sixaxis = GetSixaxisState(sixaxis_handle);
- sixaxis.is_fusion_enabled = is_fusion_enabled;
-
- return ResultSuccess;
+NPad::SixAxisLifo& NPad::GetSixAxisDualLeftLifo(Core::HID::NpadIdType npad_id) {
+ return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_dual_left_lifo;
}
-Result Controller_NPad::SetSixAxisFusionParameters(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- const auto param1 = sixaxis_fusion_parameters.parameter1;
- if (param1 < 0.0f || param1 > 1.0f) {
- return InvalidSixAxisFusionRange;
- }
-
- auto& sixaxis = GetSixaxisState(sixaxis_handle);
- sixaxis.fusion = sixaxis_fusion_parameters;
-
- return ResultSuccess;
+NPad::SixAxisLifo& NPad::GetSixAxisDualRightLifo(Core::HID::NpadIdType npad_id) {
+ return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_dual_right_lifo;
}
-Result Controller_NPad::GetSixAxisFusionParameters(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::SixAxisSensorFusionParameters& parameters) const {
- const auto is_valid = VerifyValidSixAxisSensorHandle(sixaxis_handle);
- if (is_valid.IsError()) {
- LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
- return is_valid;
- }
-
- const auto& sixaxis = GetSixaxisState(sixaxis_handle);
- parameters = sixaxis.fusion;
+NPad::SixAxisLifo& NPad::GetSixAxisLeftLifo(Core::HID::NpadIdType npad_id) {
+ return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_left_lifo;
+}
- return ResultSuccess;
+NPad::SixAxisLifo& NPad::GetSixAxisRightLifo(Core::HID::NpadIdType npad_id) {
+ return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_right_lifo;
}
-Result Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1,
- Core::HID::NpadIdType npad_id_2) {
+Result NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1,
+ Core::HID::NpadIdType npad_id_2) {
if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1,
npad_id_2);
@@ -1397,18 +1065,17 @@ Result Controller_NPad::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1,
return ResultSuccess;
}
-void Controller_NPad::StartLRAssignmentMode() {
+void NPad::StartLRAssignmentMode() {
// Nothing internally is used for lr assignment mode. Since we have the ability to set the
// controller types from boot, it doesn't really matter about showing a selection screen
is_in_lr_assignment_mode = true;
}
-void Controller_NPad::StopLRAssignmentMode() {
+void NPad::StopLRAssignmentMode() {
is_in_lr_assignment_mode = false;
}
-Result Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1,
- Core::HID::NpadIdType npad_id_2) {
+Result NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2) {
if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1,
npad_id_2);
@@ -1439,8 +1106,7 @@ Result Controller_NPad::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1,
return ResultSuccess;
}
-Result Controller_NPad::GetLedPattern(Core::HID::NpadIdType npad_id,
- Core::HID::LedPattern& pattern) const {
+Result NPad::GetLedPattern(Core::HID::NpadIdType npad_id, Core::HID::LedPattern& pattern) const {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
return InvalidNpadId;
@@ -1450,8 +1116,8 @@ Result Controller_NPad::GetLedPattern(Core::HID::NpadIdType npad_id,
return ResultSuccess;
}
-Result Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id,
- bool& is_valid) const {
+Result NPad::IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id,
+ bool& is_valid) const {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
return InvalidNpadId;
@@ -1461,8 +1127,8 @@ Result Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::
return ResultSuccess;
}
-Result Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(
- bool is_protection_enabled, Core::HID::NpadIdType npad_id) {
+Result NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled,
+ Core::HID::NpadIdType npad_id) {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
return InvalidNpadId;
@@ -1472,11 +1138,11 @@ Result Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(
return ResultSuccess;
}
-void Controller_NPad::SetAnalogStickUseCenterClamp(bool use_center_clamp) {
+void NPad::SetAnalogStickUseCenterClamp(bool use_center_clamp) {
analog_stick_use_center_clamp = use_center_clamp;
}
-void Controller_NPad::ClearAllConnectedControllers() {
+void NPad::ClearAllConnectedControllers() {
for (auto& controller : controller_data) {
if (controller.device->IsConnected() &&
controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::None) {
@@ -1486,13 +1152,13 @@ void Controller_NPad::ClearAllConnectedControllers() {
}
}
-void Controller_NPad::DisconnectAllConnectedControllers() {
+void NPad::DisconnectAllConnectedControllers() {
for (auto& controller : controller_data) {
controller.device->Disconnect();
}
}
-void Controller_NPad::ConnectAllDisconnectedControllers() {
+void NPad::ConnectAllDisconnectedControllers() {
for (auto& controller : controller_data) {
if (controller.device->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::None &&
!controller.device->IsConnected()) {
@@ -1501,18 +1167,18 @@ void Controller_NPad::ConnectAllDisconnectedControllers() {
}
}
-void Controller_NPad::ClearAllControllers() {
+void NPad::ClearAllControllers() {
for (auto& controller : controller_data) {
controller.device->Disconnect();
controller.device->SetNpadStyleIndex(Core::HID::NpadStyleIndex::None);
}
}
-Core::HID::NpadButton Controller_NPad::GetAndResetPressState() {
+Core::HID::NpadButton NPad::GetAndResetPressState() {
return static_cast<Core::HID::NpadButton>(press_state.exchange(0));
}
-void Controller_NPad::ApplyNpadSystemCommonPolicy() {
+void NPad::ApplyNpadSystemCommonPolicy() {
Core::HID::NpadStyleTag styletag{};
styletag.fullkey.Assign(1);
styletag.handheld.Assign(1);
@@ -1537,7 +1203,7 @@ void Controller_NPad::ApplyNpadSystemCommonPolicy() {
supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
}
-bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const {
+bool NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const {
if (controller == Core::HID::NpadStyleIndex::Handheld) {
const bool support_handheld =
std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(),
@@ -1588,51 +1254,50 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller
return false;
}
-Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromHandle(
- const Core::HID::SixAxisSensorHandle& device_handle) {
+NPad::NpadControllerData& NPad::GetControllerFromHandle(
+ const Core::HID::VibrationDeviceHandle& device_handle) {
const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id);
return GetControllerFromNpadIdType(npad_id);
}
-const Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromHandle(
- const Core::HID::SixAxisSensorHandle& device_handle) const {
+const NPad::NpadControllerData& NPad::GetControllerFromHandle(
+ const Core::HID::VibrationDeviceHandle& device_handle) const {
const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id);
return GetControllerFromNpadIdType(npad_id);
}
-Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromHandle(
- const Core::HID::VibrationDeviceHandle& device_handle) {
+NPad::NpadControllerData& NPad::GetControllerFromHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle) {
const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id);
return GetControllerFromNpadIdType(npad_id);
}
-const Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromHandle(
- const Core::HID::VibrationDeviceHandle& device_handle) const {
+const NPad::NpadControllerData& NPad::GetControllerFromHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle) const {
const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id);
return GetControllerFromNpadIdType(npad_id);
}
-Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromNpadIdType(
- Core::HID::NpadIdType npad_id) {
+NPad::NpadControllerData& NPad::GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id) {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
npad_id = Core::HID::NpadIdType::Player1;
}
- const auto npad_index = Core::HID::NpadIdTypeToIndex(npad_id);
+ const auto npad_index = NpadIdTypeToIndex(npad_id);
return controller_data[npad_index];
}
-const Controller_NPad::NpadControllerData& Controller_NPad::GetControllerFromNpadIdType(
+const NPad::NpadControllerData& NPad::GetControllerFromNpadIdType(
Core::HID::NpadIdType npad_id) const {
if (!IsNpadIdValid(npad_id)) {
LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
npad_id = Core::HID::NpadIdType::Player1;
}
- const auto npad_index = Core::HID::NpadIdTypeToIndex(npad_id);
+ const auto npad_index = NpadIdTypeToIndex(npad_id);
return controller_data[npad_index];
}
-Core::HID::SixAxisSensorProperties& Controller_NPad::GetSixaxisProperties(
+Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
auto& controller = GetControllerFromHandle(sixaxis_handle);
switch (sixaxis_handle.npad_type) {
@@ -1655,7 +1320,7 @@ Core::HID::SixAxisSensorProperties& Controller_NPad::GetSixaxisProperties(
}
}
-const Core::HID::SixAxisSensorProperties& Controller_NPad::GetSixaxisProperties(
+const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
const auto& controller = GetControllerFromHandle(sixaxis_handle);
switch (sixaxis_handle.npad_type) {
@@ -1678,50 +1343,13 @@ const Core::HID::SixAxisSensorProperties& Controller_NPad::GetSixaxisProperties(
}
}
-Controller_NPad::SixaxisParameters& Controller_NPad::GetSixaxisState(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
- auto& controller = GetControllerFromHandle(sixaxis_handle);
- switch (sixaxis_handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
- case Core::HID::NpadStyleIndex::Pokeball:
- return controller.sixaxis_fullkey;
- case Core::HID::NpadStyleIndex::Handheld:
- return controller.sixaxis_handheld;
- case Core::HID::NpadStyleIndex::JoyconDual:
- if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
- return controller.sixaxis_dual_left;
- }
- return controller.sixaxis_dual_right;
- case Core::HID::NpadStyleIndex::JoyconLeft:
- return controller.sixaxis_left;
- case Core::HID::NpadStyleIndex::JoyconRight:
- return controller.sixaxis_right;
- default:
- return controller.sixaxis_unknown;
- }
-}
+NPad::AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) {
+ const auto& shared_memory = GetControllerFromNpadIdType(npad_id).shared_memory;
-const Controller_NPad::SixaxisParameters& Controller_NPad::GetSixaxisState(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
- const auto& controller = GetControllerFromHandle(sixaxis_handle);
- switch (sixaxis_handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
- case Core::HID::NpadStyleIndex::Pokeball:
- return controller.sixaxis_fullkey;
- case Core::HID::NpadStyleIndex::Handheld:
- return controller.sixaxis_handheld;
- case Core::HID::NpadStyleIndex::JoyconDual:
- if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
- return controller.sixaxis_dual_left;
- }
- return controller.sixaxis_dual_right;
- case Core::HID::NpadStyleIndex::JoyconLeft:
- return controller.sixaxis_left;
- case Core::HID::NpadStyleIndex::JoyconRight:
- return controller.sixaxis_right;
- default:
- return controller.sixaxis_unknown;
- }
+ return {
+ .ui_variant = 0,
+ .footer = shared_memory->applet_footer_type,
+ };
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 949e58a4c..9167c93f0 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -10,7 +10,6 @@
#include "common/bit_field.h"
#include "common/common_types.h"
-#include "common/vector_math.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/controller_base.h"
@@ -34,11 +33,11 @@ union Result;
namespace Service::HID {
-class Controller_NPad final : public ControllerBase {
+class NPad final : public ControllerBase {
public:
- explicit Controller_NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
- KernelHelpers::ServiceContext& service_context_);
- ~Controller_NPad() override;
+ explicit NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~NPad() override;
// Called when the controller is initialized
void OnInit() override;
@@ -49,9 +48,6 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
- // When the controller is requesting a motion update for the shared memory
- void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing) override;
-
// This is nn::hid::NpadJoyHoldType
enum class NpadJoyHoldType : u64 {
Vertical = 0,
@@ -78,6 +74,46 @@ public:
MaxActivationMode = 3,
};
+ // This is nn::hid::system::AppletFooterUiAttributesSet
+ struct AppletFooterUiAttributes {
+ INSERT_PADDING_BYTES(0x4);
+ };
+
+ // This is nn::hid::system::AppletFooterUiType
+ enum class AppletFooterUiType : u8 {
+ None = 0,
+ HandheldNone = 1,
+ HandheldJoyConLeftOnly = 2,
+ HandheldJoyConRightOnly = 3,
+ HandheldJoyConLeftJoyConRight = 4,
+ JoyDual = 5,
+ JoyDualLeftOnly = 6,
+ JoyDualRightOnly = 7,
+ JoyLeftHorizontal = 8,
+ JoyLeftVertical = 9,
+ JoyRightHorizontal = 10,
+ JoyRightVertical = 11,
+ SwitchProController = 12,
+ CompatibleProController = 13,
+ CompatibleJoyCon = 14,
+ LarkHvc1 = 15,
+ LarkHvc2 = 16,
+ LarkNesLeft = 17,
+ LarkNesRight = 18,
+ Lucia = 19,
+ Verification = 20,
+ Lagon = 21,
+ };
+
+ using AppletFooterUiVariant = u8;
+
+ // This is "nn::hid::system::AppletDetailedUiType".
+ struct AppletDetailedUiType {
+ AppletFooterUiVariant ui_variant;
+ INSERT_PADDING_BYTES(0x2);
+ AppletFooterUiType footer;
+ };
+ static_assert(sizeof(AppletDetailedUiType) == 0x4, "AppletDetailedUiType is an invalid size");
// This is nn::hid::NpadCommunicationMode
enum class NpadCommunicationMode : u64 {
Mode_5ms = 0,
@@ -86,6 +122,15 @@ public:
Default = 3,
};
+ enum class NpadRevision : u32 {
+ Revision0 = 0,
+ Revision1 = 1,
+ Revision2 = 2,
+ Revision3 = 3,
+ };
+
+ using SixAxisLifo = Lifo<Core::HID::SixAxisSensorState, hid_entry_count>;
+
void SetSupportedStyleSet(Core::HID::NpadStyleTag style_set);
Core::HID::NpadStyleTag GetSupportedStyleSet() const;
@@ -138,37 +183,18 @@ public:
Result DisconnectNpad(Core::HID::NpadIdType npad_id);
- Result SetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::GyroscopeZeroDriftMode drift_mode);
- Result GetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::GyroscopeZeroDriftMode& drift_mode) const;
- Result IsSixAxisSensorAtRest(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- bool& is_at_rest) const;
Result IsFirmwareUpdateAvailableForSixAxisSensor(
const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_firmware_available) const;
- Result EnableSixAxisSensorUnalteredPassthrough(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_enabled);
- Result IsSixAxisSensorUnalteredPassthroughEnabled(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_enabled) const;
- Result LoadSixAxisSensorCalibrationParameter(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::SixAxisSensorCalibrationParameter& calibration) const;
- Result GetSixAxisSensorIcInformation(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::SixAxisSensorIcInformation& ic_information) const;
Result ResetIsSixAxisSensorDeviceNewlyAssigned(
const Core::HID::SixAxisSensorHandle& sixaxis_handle);
- Result SetSixAxisEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- bool sixaxis_status);
- Result IsSixAxisSensorFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- bool& is_fusion_enabled) const;
- Result SetSixAxisFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- bool is_fusion_enabled);
- Result SetSixAxisFusionParameters(
- const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters);
- Result GetSixAxisFusionParameters(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
- Core::HID::SixAxisSensorFusionParameters& parameters) const;
+
+ SixAxisLifo& GetSixAxisFullkeyLifo(Core::HID::NpadIdType npad_id);
+ SixAxisLifo& GetSixAxisHandheldLifo(Core::HID::NpadIdType npad_id);
+ SixAxisLifo& GetSixAxisDualLeftLifo(Core::HID::NpadIdType npad_id);
+ SixAxisLifo& GetSixAxisDualRightLifo(Core::HID::NpadIdType npad_id);
+ SixAxisLifo& GetSixAxisLeftLifo(Core::HID::NpadIdType npad_id);
+ SixAxisLifo& GetSixAxisRightLifo(Core::HID::NpadIdType npad_id);
+
Result GetLedPattern(Core::HID::NpadIdType npad_id, Core::HID::LedPattern& pattern) const;
Result IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id,
bool& is_enabled) const;
@@ -192,10 +218,7 @@ public:
void ApplyNpadSystemCommonPolicy();
- static bool IsNpadIdValid(Core::HID::NpadIdType npad_id);
- static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
- static Result VerifyValidSixAxisSensorHandle(
- const Core::HID::SixAxisSensorHandle& device_handle);
+ AppletDetailedUiType GetAppletDetailedUiType(Core::HID::NpadIdType npad_id);
private:
static constexpr std::size_t NPAD_COUNT = 10;
@@ -254,29 +277,6 @@ private:
};
static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size");
- // This is nn::hid::SixAxisSensorAttribute
- struct SixAxisSensorAttribute {
- union {
- u32 raw{};
- BitField<0, 1, u32> is_connected;
- BitField<1, 1, u32> is_interpolated;
- };
- };
- static_assert(sizeof(SixAxisSensorAttribute) == 4, "SixAxisSensorAttribute is an invalid size");
-
- // This is nn::hid::SixAxisSensorState
- struct SixAxisSensorState {
- s64 delta_time{};
- s64 sampling_number{};
- Common::Vec3f accel{};
- Common::Vec3f gyro{};
- Common::Vec3f rotation{};
- std::array<Common::Vec3f, 3> orientation{};
- SixAxisSensorAttribute attribute{};
- INSERT_PADDING_BYTES(4); // Reserved
- };
- static_assert(sizeof(SixAxisSensorState) == 0x60, "SixAxisSensorState is an invalid size");
-
// This is nn::hid::server::NpadGcTriggerState
struct NpadGcTriggerState {
s64 sampling_number{};
@@ -353,37 +353,6 @@ private:
static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18,
"NfcXcdDeviceHandleStateImpl is an invalid size");
- // This is nn::hid::system::AppletFooterUiAttributesSet
- struct AppletFooterUiAttributes {
- INSERT_PADDING_BYTES(0x4);
- };
-
- // This is nn::hid::system::AppletFooterUiType
- enum class AppletFooterUiType : u8 {
- None = 0,
- HandheldNone = 1,
- HandheldJoyConLeftOnly = 2,
- HandheldJoyConRightOnly = 3,
- HandheldJoyConLeftJoyConRight = 4,
- JoyDual = 5,
- JoyDualLeftOnly = 6,
- JoyDualRightOnly = 7,
- JoyLeftHorizontal = 8,
- JoyLeftVertical = 9,
- JoyRightHorizontal = 10,
- JoyRightVertical = 11,
- SwitchProController = 12,
- CompatibleProController = 13,
- CompatibleJoyCon = 14,
- LarkHvc1 = 15,
- LarkHvc2 = 16,
- LarkNesLeft = 17,
- LarkNesRight = 18,
- Lucia = 19,
- Verification = 20,
- Lagon = 21,
- };
-
// This is nn::hid::NpadLarkType
enum class NpadLarkType : u32 {
Invalid,
@@ -427,12 +396,12 @@ private:
Lifo<NPadGenericState, hid_entry_count> joy_right_lifo{};
Lifo<NPadGenericState, hid_entry_count> palma_lifo{};
Lifo<NPadGenericState, hid_entry_count> system_ext_lifo{};
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_fullkey_lifo{};
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_handheld_lifo{};
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_left_lifo{};
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_dual_right_lifo{};
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_left_lifo{};
- Lifo<SixAxisSensorState, hid_entry_count> sixaxis_right_lifo{};
+ Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_fullkey_lifo{};
+ Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_handheld_lifo{};
+ Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_dual_left_lifo{};
+ Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_dual_right_lifo{};
+ Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_left_lifo{};
+ Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_right_lifo{};
DeviceType device_type{};
INSERT_PADDING_BYTES(0x4); // Reserved
NPadSystemProperties system_properties{};
@@ -466,16 +435,6 @@ private:
std::chrono::steady_clock::time_point last_vibration_timepoint{};
};
- struct SixaxisParameters {
- bool is_fusion_enabled{true};
- bool unaltered_passtrough{false};
- Core::HID::SixAxisSensorFusionParameters fusion{};
- Core::HID::SixAxisSensorCalibrationParameter calibration{};
- Core::HID::SixAxisSensorIcInformation ic_information{};
- Core::HID::GyroscopeZeroDriftMode gyroscope_zero_drift_mode{
- Core::HID::GyroscopeZeroDriftMode::Standard};
- };
-
struct NpadControllerData {
Kernel::KEvent* styleset_changed_event{};
NpadInternalState* shared_memory = nullptr;
@@ -489,27 +448,10 @@ private:
bool is_dual_left_connected{true};
bool is_dual_right_connected{true};
- // Motion parameters
- bool sixaxis_at_rest{true};
- bool sixaxis_sensor_enabled{true};
- SixaxisParameters sixaxis_fullkey{};
- SixaxisParameters sixaxis_handheld{};
- SixaxisParameters sixaxis_dual_left{};
- SixaxisParameters sixaxis_dual_right{};
- SixaxisParameters sixaxis_left{};
- SixaxisParameters sixaxis_right{};
- SixaxisParameters sixaxis_unknown{};
-
// Current pad state
NPadGenericState npad_pad_state{};
NPadGenericState npad_libnx_state{};
NpadGcTriggerState npad_trigger_state{};
- SixAxisSensorState sixaxis_fullkey_state{};
- SixAxisSensorState sixaxis_handheld_state{};
- SixAxisSensorState sixaxis_dual_left_state{};
- SixAxisSensorState sixaxis_dual_right_state{};
- SixAxisSensorState sixaxis_left_lifo_state{};
- SixAxisSensorState sixaxis_right_lifo_state{};
int callback_key{};
};
@@ -520,13 +462,13 @@ private:
void WriteEmptyEntry(NpadInternalState* npad);
NpadControllerData& GetControllerFromHandle(
- const Core::HID::SixAxisSensorHandle& device_handle);
- const NpadControllerData& GetControllerFromHandle(
- const Core::HID::SixAxisSensorHandle& device_handle) const;
- NpadControllerData& GetControllerFromHandle(
const Core::HID::VibrationDeviceHandle& device_handle);
const NpadControllerData& GetControllerFromHandle(
const Core::HID::VibrationDeviceHandle& device_handle) const;
+ NpadControllerData& GetControllerFromHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle);
+ const NpadControllerData& GetControllerFromHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle) const;
NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id);
const NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id) const;
@@ -534,9 +476,6 @@ private:
const Core::HID::SixAxisSensorHandle& device_handle);
const Core::HID::SixAxisSensorProperties& GetSixaxisProperties(
const Core::HID::SixAxisSensorHandle& device_handle) const;
- SixaxisParameters& GetSixaxisState(const Core::HID::SixAxisSensorHandle& device_handle);
- const SixaxisParameters& GetSixaxisState(
- const Core::HID::SixAxisSensorHandle& device_handle) const;
std::atomic<u64> press_state{};
diff --git a/src/core/hle/service/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp
index 14c67e454..588ff9d62 100644
--- a/src/core/hle/service/hid/controllers/palma.cpp
+++ b/src/core/hle/service/hid/controllers/palma.cpp
@@ -12,41 +12,43 @@
namespace Service::HID {
-Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
- KernelHelpers::ServiceContext& service_context_)
+Palma::Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_)
: ControllerBase{hid_core_}, service_context{service_context_} {
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
}
-Controller_Palma::~Controller_Palma() = default;
+Palma::~Palma() {
+ service_context.CloseEvent(operation_complete_event);
+};
-void Controller_Palma::OnInit() {}
+void Palma::OnInit() {}
-void Controller_Palma::OnRelease() {}
+void Palma::OnRelease() {}
-void Controller_Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
return;
}
}
-Result Controller_Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
- PalmaConnectionHandle& handle) {
+Result Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
+ PalmaConnectionHandle& handle) {
active_handle.npad_id = npad_id;
handle = active_handle;
return ResultSuccess;
}
-Result Controller_Palma::InitializePalma(const PalmaConnectionHandle& handle) {
+Result Palma::InitializePalma(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
- ActivateController();
+ Activate();
return ResultSuccess;
}
-Kernel::KReadableEvent& Controller_Palma::AcquirePalmaOperationCompleteEvent(
+Kernel::KReadableEvent& Palma::AcquirePalmaOperationCompleteEvent(
const PalmaConnectionHandle& handle) const {
if (handle.npad_id != active_handle.npad_id) {
LOG_ERROR(Service_HID, "Invalid npad id {}", handle.npad_id);
@@ -54,9 +56,9 @@ Kernel::KReadableEvent& Controller_Palma::AcquirePalmaOperationCompleteEvent(
return operation_complete_event->GetReadableEvent();
}
-Result Controller_Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
- PalmaOperationType& operation_type,
- PalmaOperationData& data) const {
+Result Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
+ PalmaOperationType& operation_type,
+ PalmaOperationData& data) const {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -65,8 +67,7 @@ Result Controller_Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& hand
return ResultSuccess;
}
-Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
- u64 palma_activity) {
+Result Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -77,8 +78,7 @@ Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
return ResultSuccess;
}
-Result Controller_Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle,
- PalmaFrModeType fr_mode_) {
+Result Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -86,7 +86,7 @@ Result Controller_Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle,
return ResultSuccess;
}
-Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
+Result Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -97,25 +97,25 @@ Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
return ResultSuccess;
}
-Result Controller_Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
+Result Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return ResultSuccess;
}
-Result Controller_Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
+Result Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return ResultSuccess;
}
-void Controller_Palma::ReadPalmaApplicationSection() {}
+void Palma::ReadPalmaApplicationSection() {}
-void Controller_Palma::WritePalmaApplicationSection() {}
+void Palma::WritePalmaApplicationSection() {}
-Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
+Result Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -126,7 +126,7 @@ Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle
return ResultSuccess;
}
-Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
+Result Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -137,10 +137,9 @@ Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle&
return ResultSuccess;
}
-void Controller_Palma::WritePalmaActivityEntry() {}
+void Palma::WritePalmaActivityEntry() {}
-Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle,
- u64 unknown) {
+Result Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -151,8 +150,8 @@ Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandl
return ResultSuccess;
}
-Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
- Common::ProcessAddress t_mem, u64 size) {
+Result Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
+ Common::ProcessAddress t_mem, u64 size) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -163,8 +162,8 @@ Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle
return ResultSuccess;
}
-Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
- s32 database_id_version_) {
+Result Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
+ s32 database_id_version_) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -176,8 +175,7 @@ Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnec
return ResultSuccess;
}
-Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
- const PalmaConnectionHandle& handle) {
+Result Palma::GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -189,26 +187,26 @@ Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
return ResultSuccess;
}
-void Controller_Palma::SuspendPalmaFeature() {}
+void Palma::SuspendPalmaFeature() {}
-Result Controller_Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
+Result Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return operation.result;
}
-void Controller_Palma::ReadPalmaPlayLog() {}
+void Palma::ReadPalmaPlayLog() {}
-void Controller_Palma::ResetPalmaPlayLog() {}
+void Palma::ResetPalmaPlayLog() {}
-void Controller_Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
+void Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
// If true controllers are able to be paired
is_connectable = is_all_connectable;
}
-void Controller_Palma::SetIsPalmaPairedConnectable() {}
+void Palma::SetIsPalmaPairedConnectable() {}
-Result Controller_Palma::PairPalma(const PalmaConnectionHandle& handle) {
+Result Palma::PairPalma(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
@@ -216,14 +214,14 @@ Result Controller_Palma::PairPalma(const PalmaConnectionHandle& handle) {
return ResultSuccess;
}
-void Controller_Palma::SetPalmaBoostMode(bool boost_mode) {}
+void Palma::SetPalmaBoostMode(bool boost_mode) {}
-void Controller_Palma::CancelWritePalmaWaveEntry() {}
+void Palma::CancelWritePalmaWaveEntry() {}
-void Controller_Palma::EnablePalmaBoostMode() {}
+void Palma::EnablePalmaBoostMode() {}
-void Controller_Palma::GetPalmaBluetoothAddress() {}
+void Palma::GetPalmaBluetoothAddress() {}
-void Controller_Palma::SetDisallowedPalmaConnection() {}
+void Palma::SetDisallowedPalmaConnection() {}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/palma.h b/src/core/hle/service/hid/controllers/palma.h
index a0491a819..a6047f36a 100644
--- a/src/core/hle/service/hid/controllers/palma.h
+++ b/src/core/hle/service/hid/controllers/palma.h
@@ -23,7 +23,7 @@ class EmulatedController;
} // namespace Core::HID
namespace Service::HID {
-class Controller_Palma final : public ControllerBase {
+class Palma final : public ControllerBase {
public:
using PalmaOperationData = std::array<u8, 0x140>;
@@ -97,9 +97,9 @@ public:
static_assert(sizeof(PalmaConnectionHandle) == 0x8,
"PalmaConnectionHandle has incorrect size.");
- explicit Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
- KernelHelpers::ServiceContext& service_context_);
- ~Controller_Palma() override;
+ explicit Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~Palma() override;
// Called when the controller is initialized
void OnInit() override;
diff --git a/src/core/hle/service/hid/controllers/console_sixaxis.cpp b/src/core/hle/service/hid/controllers/seven_six_axis.cpp
index bcb272eaf..495568484 100644
--- a/src/core/hle/service/hid/controllers/console_sixaxis.cpp
+++ b/src/core/hle/service/hid/controllers/seven_six_axis.cpp
@@ -1,32 +1,29 @@
-// SPDX-FileCopyrightText: Copyright 2021 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 <cstring>
+#include "common/common_types.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/frontend/emu_window.h"
#include "core/hid/emulated_console.h"
+#include "core/hid/emulated_devices.h"
#include "core/hid/hid_core.h"
-#include "core/hle/service/hid/controllers/console_sixaxis.h"
+#include "core/hle/service/hid/controllers/seven_six_axis.h"
#include "core/memory.h"
namespace Service::HID {
-constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C200;
-
-Controller_ConsoleSixAxis::Controller_ConsoleSixAxis(Core::System& system_, u8* raw_shared_memory_)
+SevenSixAxis::SevenSixAxis(Core::System& system_)
: ControllerBase{system_.HIDCore()}, system{system_} {
console = hid_core.GetEmulatedConsole();
- static_assert(SHARED_MEMORY_OFFSET + sizeof(ConsoleSharedMemory) < shared_memory_size,
- "ConsoleSharedMemory is bigger than the shared memory");
- shared_memory = std::construct_at(
- reinterpret_cast<ConsoleSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
}
-Controller_ConsoleSixAxis::~Controller_ConsoleSixAxis() = default;
-
-void Controller_ConsoleSixAxis::OnInit() {}
+SevenSixAxis::~SevenSixAxis() = default;
-void Controller_ConsoleSixAxis::OnRelease() {}
+void SevenSixAxis::OnInit() {}
+void SevenSixAxis::OnRelease() {}
-void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void SevenSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated() || transfer_memory == 0) {
seven_sixaxis_lifo.buffer_count = 0;
seven_sixaxis_lifo.buffer_tail = 0;
@@ -53,22 +50,17 @@ void Controller_ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_ti
-motion_status.quaternion.xyz.z,
};
- shared_memory->sampling_number++;
- shared_memory->is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest;
- shared_memory->verticalization_error = motion_status.verticalization_error;
- shared_memory->gyro_bias = motion_status.gyro_bias;
-
- // Update seven six axis transfer memory
seven_sixaxis_lifo.WriteNextEntry(next_seven_sixaxis_state);
system.ApplicationMemory().WriteBlock(transfer_memory, &seven_sixaxis_lifo,
sizeof(seven_sixaxis_lifo));
}
-void Controller_ConsoleSixAxis::SetTransferMemoryAddress(Common::ProcessAddress t_mem) {
+void SevenSixAxis::SetTransferMemoryAddress(Common::ProcessAddress t_mem) {
transfer_memory = t_mem;
}
-void Controller_ConsoleSixAxis::ResetTimestamp() {
+void SevenSixAxis::ResetTimestamp() {
last_saved_timestamp = last_global_timestamp;
}
+
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/console_sixaxis.h b/src/core/hle/service/hid/controllers/seven_six_axis.h
index 7015d924c..40e3f5d12 100644
--- a/src/core/hle/service/hid/controllers/console_sixaxis.h
+++ b/src/core/hle/service/hid/controllers/seven_six_axis.h
@@ -1,10 +1,9 @@
-// SPDX-FileCopyrightText: Copyright 2021 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 <array>
-
+#include "common/common_types.h"
#include "common/quaternion.h"
#include "common/typed_address.h"
#include "core/hle/service/hid/controllers/controller_base.h"
@@ -19,10 +18,10 @@ class EmulatedConsole;
} // namespace Core::HID
namespace Service::HID {
-class Controller_ConsoleSixAxis final : public ControllerBase {
+class SevenSixAxis final : public ControllerBase {
public:
- explicit Controller_ConsoleSixAxis(Core::System& system_, u8* raw_shared_memory_);
- ~Controller_ConsoleSixAxis() override;
+ explicit SevenSixAxis(Core::System& system_);
+ ~SevenSixAxis() override;
// Called when the controller is initialized
void OnInit() override;
@@ -51,28 +50,16 @@ private:
};
static_assert(sizeof(SevenSixAxisState) == 0x48, "SevenSixAxisState is an invalid size");
- // This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat
- struct ConsoleSharedMemory {
- u64 sampling_number{};
- bool is_seven_six_axis_sensor_at_rest{};
- INSERT_PADDING_BYTES(3); // padding
- f32 verticalization_error{};
- Common::Vec3f gyro_bias{};
- INSERT_PADDING_BYTES(4); // padding
- };
- static_assert(sizeof(ConsoleSharedMemory) == 0x20, "ConsoleSharedMemory is an invalid size");
-
Lifo<SevenSixAxisState, 0x21> seven_sixaxis_lifo{};
static_assert(sizeof(seven_sixaxis_lifo) == 0xA70, "SevenSixAxisState is an invalid size");
+ u64 last_saved_timestamp{};
+ u64 last_global_timestamp{};
+
SevenSixAxisState next_seven_sixaxis_state{};
Common::ProcessAddress transfer_memory{};
- ConsoleSharedMemory* shared_memory = nullptr;
Core::HID::EmulatedConsole* console = nullptr;
- u64 last_saved_timestamp{};
- u64 last_global_timestamp{};
-
Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/six_axis.cpp b/src/core/hle/service/hid/controllers/six_axis.cpp
new file mode 100644
index 000000000..3d24a5c04
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/six_axis.cpp
@@ -0,0 +1,413 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/common_types.h"
+#include "core/core_timing.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/six_axis.h"
+#include "core/hle/service/hid/errors.h"
+#include "core/hle/service/hid/hid_util.h"
+
+namespace Service::HID {
+
+SixAxis::SixAxis(Core::HID::HIDCore& hid_core_, std::shared_ptr<NPad> npad_)
+ : ControllerBase{hid_core_}, npad{npad_} {
+ for (std::size_t i = 0; i < controller_data.size(); ++i) {
+ auto& controller = controller_data[i];
+ controller.device = hid_core.GetEmulatedControllerByIndex(i);
+ }
+}
+
+SixAxis::~SixAxis() = default;
+
+void SixAxis::OnInit() {}
+void SixAxis::OnRelease() {}
+
+void SixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+ if (!IsControllerActivated()) {
+ return;
+ }
+
+ for (std::size_t i = 0; i < controller_data.size(); ++i) {
+ auto& controller = controller_data[i];
+
+ const auto npad_id = IndexToNpadIdType(i);
+ const auto& controller_type = controller.device->GetNpadStyleIndex();
+
+ if (controller_type == Core::HID::NpadStyleIndex::None ||
+ !controller.device->IsConnected()) {
+ continue;
+ }
+
+ const auto& motion_state = controller.device->GetMotions();
+ auto& sixaxis_fullkey_state = controller.sixaxis_fullkey_state;
+ auto& sixaxis_handheld_state = controller.sixaxis_handheld_state;
+ auto& sixaxis_dual_left_state = controller.sixaxis_dual_left_state;
+ auto& sixaxis_dual_right_state = controller.sixaxis_dual_right_state;
+ auto& sixaxis_left_lifo_state = controller.sixaxis_left_lifo_state;
+ auto& sixaxis_right_lifo_state = controller.sixaxis_right_lifo_state;
+
+ auto& sixaxis_fullkey_lifo = npad->GetSixAxisFullkeyLifo(npad_id);
+ auto& sixaxis_handheld_lifo = npad->GetSixAxisHandheldLifo(npad_id);
+ auto& sixaxis_dual_left_lifo = npad->GetSixAxisDualLeftLifo(npad_id);
+ auto& sixaxis_dual_right_lifo = npad->GetSixAxisDualRightLifo(npad_id);
+ auto& sixaxis_left_lifo = npad->GetSixAxisLeftLifo(npad_id);
+ auto& sixaxis_right_lifo = npad->GetSixAxisRightLifo(npad_id);
+
+ // Clear previous state
+ sixaxis_fullkey_state = {};
+ sixaxis_handheld_state = {};
+ sixaxis_dual_left_state = {};
+ sixaxis_dual_right_state = {};
+ sixaxis_left_lifo_state = {};
+ sixaxis_right_lifo_state = {};
+
+ if (controller.sixaxis_sensor_enabled && Settings::values.motion_enabled.GetValue()) {
+ controller.sixaxis_at_rest = true;
+ for (std::size_t e = 0; e < motion_state.size(); ++e) {
+ controller.sixaxis_at_rest =
+ controller.sixaxis_at_rest && motion_state[e].is_at_rest;
+ }
+ }
+
+ const auto set_motion_state = [&](Core::HID::SixAxisSensorState& state,
+ const Core::HID::ControllerMotion& hid_state) {
+ using namespace std::literals::chrono_literals;
+ static constexpr Core::HID::SixAxisSensorState default_motion_state = {
+ .delta_time = std::chrono::nanoseconds(5ms).count(),
+ .accel = {0, 0, -1.0f},
+ .orientation =
+ {
+ Common::Vec3f{1.0f, 0, 0},
+ Common::Vec3f{0, 1.0f, 0},
+ Common::Vec3f{0, 0, 1.0f},
+ },
+ .attribute = {1},
+ };
+ if (!controller.sixaxis_sensor_enabled) {
+ state = default_motion_state;
+ return;
+ }
+ if (!Settings::values.motion_enabled.GetValue()) {
+ state = default_motion_state;
+ return;
+ }
+ state.attribute.is_connected.Assign(1);
+ state.delta_time = std::chrono::nanoseconds(5ms).count();
+ state.accel = hid_state.accel;
+ state.gyro = hid_state.gyro;
+ state.rotation = hid_state.rotation;
+ state.orientation = hid_state.orientation;
+ };
+
+ switch (controller_type) {
+ case Core::HID::NpadStyleIndex::None:
+ ASSERT(false);
+ break;
+ case Core::HID::NpadStyleIndex::ProController:
+ set_motion_state(sixaxis_fullkey_state, motion_state[0]);
+ break;
+ case Core::HID::NpadStyleIndex::Handheld:
+ set_motion_state(sixaxis_handheld_state, motion_state[0]);
+ break;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ set_motion_state(sixaxis_dual_left_state, motion_state[0]);
+ set_motion_state(sixaxis_dual_right_state, motion_state[1]);
+ break;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ set_motion_state(sixaxis_left_lifo_state, motion_state[0]);
+ break;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ set_motion_state(sixaxis_right_lifo_state, motion_state[1]);
+ break;
+ case Core::HID::NpadStyleIndex::Pokeball:
+ using namespace std::literals::chrono_literals;
+ set_motion_state(sixaxis_fullkey_state, motion_state[0]);
+ sixaxis_fullkey_state.delta_time = std::chrono::nanoseconds(15ms).count();
+ break;
+ default:
+ break;
+ }
+
+ sixaxis_fullkey_state.sampling_number =
+ sixaxis_fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ sixaxis_handheld_state.sampling_number =
+ sixaxis_handheld_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ sixaxis_dual_left_state.sampling_number =
+ sixaxis_dual_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ sixaxis_dual_right_state.sampling_number =
+ sixaxis_dual_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ sixaxis_left_lifo_state.sampling_number =
+ sixaxis_left_lifo.ReadCurrentEntry().state.sampling_number + 1;
+ sixaxis_right_lifo_state.sampling_number =
+ sixaxis_right_lifo.ReadCurrentEntry().state.sampling_number + 1;
+
+ if (IndexToNpadIdType(i) == Core::HID::NpadIdType::Handheld) {
+ // This buffer only is updated on handheld on HW
+ sixaxis_handheld_lifo.WriteNextEntry(sixaxis_handheld_state);
+ } else {
+ // Handheld doesn't update this buffer on HW
+ sixaxis_fullkey_lifo.WriteNextEntry(sixaxis_fullkey_state);
+ }
+
+ sixaxis_dual_left_lifo.WriteNextEntry(sixaxis_dual_left_state);
+ sixaxis_dual_right_lifo.WriteNextEntry(sixaxis_dual_right_state);
+ sixaxis_left_lifo.WriteNextEntry(sixaxis_left_lifo_state);
+ sixaxis_right_lifo.WriteNextEntry(sixaxis_right_lifo_state);
+ }
+}
+
+Result SixAxis::SetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::GyroscopeZeroDriftMode drift_mode) {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ auto& controller = GetControllerFromHandle(sixaxis_handle);
+ sixaxis.gyroscope_zero_drift_mode = drift_mode;
+ controller.device->SetGyroscopeZeroDriftMode(drift_mode);
+
+ return ResultSuccess;
+}
+
+Result SixAxis::GetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::GyroscopeZeroDriftMode& drift_mode) const {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ drift_mode = sixaxis.gyroscope_zero_drift_mode;
+
+ return ResultSuccess;
+}
+
+Result SixAxis::IsSixAxisSensorAtRest(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool& is_at_rest) const {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ const auto& controller = GetControllerFromHandle(sixaxis_handle);
+ is_at_rest = controller.sixaxis_at_rest;
+ return ResultSuccess;
+}
+
+Result SixAxis::LoadSixAxisSensorCalibrationParameter(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorCalibrationParameter& calibration) const {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ // TODO: Request this data to the controller. On error return 0xd8ca
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ calibration = sixaxis.calibration;
+ return ResultSuccess;
+}
+
+Result SixAxis::GetSixAxisSensorIcInformation(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorIcInformation& ic_information) const {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ // TODO: Request this data to the controller. On error return 0xd8ca
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ ic_information = sixaxis.ic_information;
+ return ResultSuccess;
+}
+
+Result SixAxis::EnableSixAxisSensorUnalteredPassthrough(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_enabled) {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ sixaxis.unaltered_passtrough = is_enabled;
+ return ResultSuccess;
+}
+
+Result SixAxis::IsSixAxisSensorUnalteredPassthroughEnabled(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_enabled) const {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ is_enabled = sixaxis.unaltered_passtrough;
+ return ResultSuccess;
+}
+
+Result SixAxis::SetSixAxisEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool sixaxis_status) {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ auto& controller = GetControllerFromHandle(sixaxis_handle);
+ controller.sixaxis_sensor_enabled = sixaxis_status;
+ return ResultSuccess;
+}
+
+Result SixAxis::IsSixAxisSensorFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool& is_fusion_enabled) const {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ is_fusion_enabled = sixaxis.is_fusion_enabled;
+
+ return ResultSuccess;
+}
+Result SixAxis::SetSixAxisFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool is_fusion_enabled) {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ sixaxis.is_fusion_enabled = is_fusion_enabled;
+
+ return ResultSuccess;
+}
+
+Result SixAxis::SetSixAxisFusionParameters(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters) {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ const auto param1 = sixaxis_fusion_parameters.parameter1;
+ if (param1 < 0.0f || param1 > 1.0f) {
+ return InvalidSixAxisFusionRange;
+ }
+
+ auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ sixaxis.fusion = sixaxis_fusion_parameters;
+
+ return ResultSuccess;
+}
+
+Result SixAxis::GetSixAxisFusionParameters(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters& parameters) const {
+ const auto is_valid = IsSixaxisHandleValid(sixaxis_handle);
+ if (is_valid.IsError()) {
+ LOG_ERROR(Service_HID, "Invalid handle, error_code={}", is_valid.raw);
+ return is_valid;
+ }
+
+ const auto& sixaxis = GetSixaxisState(sixaxis_handle);
+ parameters = sixaxis.fusion;
+
+ return ResultSuccess;
+}
+
+SixAxis::SixaxisParameters& SixAxis::GetSixaxisState(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
+ auto& controller = GetControllerFromHandle(sixaxis_handle);
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ return controller.sixaxis_fullkey;
+ case Core::HID::NpadStyleIndex::Handheld:
+ return controller.sixaxis_handheld;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ return controller.sixaxis_dual_left;
+ }
+ return controller.sixaxis_dual_right;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ return controller.sixaxis_left;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ return controller.sixaxis_right;
+ default:
+ return controller.sixaxis_unknown;
+ }
+}
+
+const SixAxis::SixaxisParameters& SixAxis::GetSixaxisState(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
+ const auto& controller = GetControllerFromHandle(sixaxis_handle);
+ switch (sixaxis_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Pokeball:
+ return controller.sixaxis_fullkey;
+ case Core::HID::NpadStyleIndex::Handheld:
+ return controller.sixaxis_handheld;
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ if (sixaxis_handle.device_index == Core::HID::DeviceIndex::Left) {
+ return controller.sixaxis_dual_left;
+ }
+ return controller.sixaxis_dual_right;
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ return controller.sixaxis_left;
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ return controller.sixaxis_right;
+ default:
+ return controller.sixaxis_unknown;
+ }
+}
+
+SixAxis::NpadControllerData& SixAxis::GetControllerFromHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle) {
+ const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id);
+ return GetControllerFromNpadIdType(npad_id);
+}
+
+const SixAxis::NpadControllerData& SixAxis::GetControllerFromHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle) const {
+ const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id);
+ return GetControllerFromNpadIdType(npad_id);
+}
+
+SixAxis::NpadControllerData& SixAxis::GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id) {
+ if (!IsNpadIdValid(npad_id)) {
+ LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
+ npad_id = Core::HID::NpadIdType::Player1;
+ }
+ const auto npad_index = NpadIdTypeToIndex(npad_id);
+ return controller_data[npad_index];
+}
+
+const SixAxis::NpadControllerData& SixAxis::GetControllerFromNpadIdType(
+ Core::HID::NpadIdType npad_id) const {
+ if (!IsNpadIdValid(npad_id)) {
+ LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id);
+ npad_id = Core::HID::NpadIdType::Player1;
+ }
+ const auto npad_index = NpadIdTypeToIndex(npad_id);
+ return controller_data[npad_index];
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/six_axis.h b/src/core/hle/service/hid/controllers/six_axis.h
new file mode 100644
index 000000000..4c4f5dc7b
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/six_axis.h
@@ -0,0 +1,111 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/hid/ring_lifo.h"
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+class NPad;
+
+class SixAxis final : public ControllerBase {
+public:
+ explicit SixAxis(Core::HID::HIDCore& hid_core_, std::shared_ptr<NPad> npad_);
+ ~SixAxis() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+
+ Result SetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::GyroscopeZeroDriftMode drift_mode);
+ Result GetGyroscopeZeroDriftMode(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::GyroscopeZeroDriftMode& drift_mode) const;
+ Result IsSixAxisSensorAtRest(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool& is_at_rest) const;
+ Result EnableSixAxisSensorUnalteredPassthrough(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool is_enabled);
+ Result IsSixAxisSensorUnalteredPassthroughEnabled(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle, bool& is_enabled) const;
+ Result LoadSixAxisSensorCalibrationParameter(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorCalibrationParameter& calibration) const;
+ Result GetSixAxisSensorIcInformation(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorIcInformation& ic_information) const;
+ Result SetSixAxisEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool sixaxis_status);
+ Result IsSixAxisSensorFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool& is_fusion_enabled) const;
+ Result SetSixAxisFusionEnabled(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ bool is_fusion_enabled);
+ Result SetSixAxisFusionParameters(
+ const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters sixaxis_fusion_parameters);
+ Result GetSixAxisFusionParameters(const Core::HID::SixAxisSensorHandle& sixaxis_handle,
+ Core::HID::SixAxisSensorFusionParameters& parameters) const;
+
+private:
+ static constexpr std::size_t NPAD_COUNT = 10;
+
+ struct SixaxisParameters {
+ bool is_fusion_enabled{true};
+ bool unaltered_passtrough{false};
+ Core::HID::SixAxisSensorFusionParameters fusion{};
+ Core::HID::SixAxisSensorCalibrationParameter calibration{};
+ Core::HID::SixAxisSensorIcInformation ic_information{};
+ Core::HID::GyroscopeZeroDriftMode gyroscope_zero_drift_mode{
+ Core::HID::GyroscopeZeroDriftMode::Standard};
+ };
+
+ struct NpadControllerData {
+ Core::HID::EmulatedController* device = nullptr;
+
+ // Motion parameters
+ bool sixaxis_at_rest{true};
+ bool sixaxis_sensor_enabled{true};
+ SixaxisParameters sixaxis_fullkey{};
+ SixaxisParameters sixaxis_handheld{};
+ SixaxisParameters sixaxis_dual_left{};
+ SixaxisParameters sixaxis_dual_right{};
+ SixaxisParameters sixaxis_left{};
+ SixaxisParameters sixaxis_right{};
+ SixaxisParameters sixaxis_unknown{};
+
+ // Current pad state
+ Core::HID::SixAxisSensorState sixaxis_fullkey_state{};
+ Core::HID::SixAxisSensorState sixaxis_handheld_state{};
+ Core::HID::SixAxisSensorState sixaxis_dual_left_state{};
+ Core::HID::SixAxisSensorState sixaxis_dual_right_state{};
+ Core::HID::SixAxisSensorState sixaxis_left_lifo_state{};
+ Core::HID::SixAxisSensorState sixaxis_right_lifo_state{};
+ int callback_key{};
+ };
+
+ SixaxisParameters& GetSixaxisState(const Core::HID::SixAxisSensorHandle& device_handle);
+ const SixaxisParameters& GetSixaxisState(
+ const Core::HID::SixAxisSensorHandle& device_handle) const;
+
+ NpadControllerData& GetControllerFromHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle);
+ const NpadControllerData& GetControllerFromHandle(
+ const Core::HID::SixAxisSensorHandle& device_handle) const;
+ NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id);
+ const NpadControllerData& GetControllerFromNpadIdType(Core::HID::NpadIdType npad_id) const;
+
+ std::shared_ptr<NPad> npad;
+ std::array<NpadControllerData, NPAD_COUNT> controller_data{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index 3ef91df4b..fcd973414 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -15,9 +15,9 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
-Controller_Touchscreen::Controller_Touchscreen(Core::HID::HIDCore& hid_core_,
- u8* raw_shared_memory_)
- : ControllerBase{hid_core_} {
+TouchScreen::TouchScreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
+ : ControllerBase{hid_core_}, touchscreen_width(Layout::ScreenUndocked::Width),
+ touchscreen_height(Layout::ScreenUndocked::Height) {
static_assert(SHARED_MEMORY_OFFSET + sizeof(TouchSharedMemory) < shared_memory_size,
"TouchSharedMemory is bigger than the shared memory");
shared_memory = std::construct_at(
@@ -25,13 +25,13 @@ Controller_Touchscreen::Controller_Touchscreen(Core::HID::HIDCore& hid_core_,
console = hid_core.GetEmulatedConsole();
}
-Controller_Touchscreen::~Controller_Touchscreen() = default;
+TouchScreen::~TouchScreen() = default;
-void Controller_Touchscreen::OnInit() {}
+void TouchScreen::OnInit() {}
-void Controller_Touchscreen::OnRelease() {}
+void TouchScreen::OnRelease() {}
-void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
shared_memory->touch_screen_lifo.timestamp = core_timing.GetGlobalTimeNs().count();
if (!IsControllerActivated()) {
@@ -96,8 +96,8 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
if (id < active_fingers_count) {
const auto& [active_x, active_y] = active_fingers[id].position;
touch_entry.position = {
- .x = static_cast<u16>(active_x * Layout::ScreenUndocked::Width),
- .y = static_cast<u16>(active_y * Layout::ScreenUndocked::Height),
+ .x = static_cast<u16>(active_x * static_cast<float>(touchscreen_width)),
+ .y = static_cast<u16>(active_y * static_cast<float>(touchscreen_height)),
};
touch_entry.diameter_x = Settings::values.touchscreen.diameter_x;
touch_entry.diameter_y = Settings::values.touchscreen.diameter_y;
@@ -121,4 +121,9 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
shared_memory->touch_screen_lifo.WriteNextEntry(next_state);
}
+void TouchScreen::SetTouchscreenDimensions(u32 width, u32 height) {
+ touchscreen_width = width;
+ touchscreen_height = height;
+}
+
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index dd00921fd..79f026a81 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -14,10 +14,10 @@ class EmulatedConsole;
} // namespace Core::HID
namespace Service::HID {
-class Controller_Touchscreen final : public ControllerBase {
+class TouchScreen final : public ControllerBase {
public:
- explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
- ~Controller_Touchscreen() override;
+ explicit TouchScreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
+ ~TouchScreen() override;
// Called when the controller is initialized
void OnInit() override;
@@ -28,6 +28,8 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+ void SetTouchscreenDimensions(u32 width, u32 height);
+
private:
static constexpr std::size_t MAX_FINGERS = 16;
@@ -53,5 +55,7 @@ private:
Core::HID::EmulatedConsole* console = nullptr;
std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers{};
+ u32 touchscreen_width;
+ u32 touchscreen_height;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp
index 62119e2c5..0aaed1fa7 100644
--- a/src/core/hle/service/hid/controllers/xpad.cpp
+++ b/src/core/hle/service/hid/controllers/xpad.cpp
@@ -10,20 +10,19 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
-Controller_XPad::Controller_XPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_)
- : ControllerBase{hid_core_} {
+XPad::XPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) : ControllerBase{hid_core_} {
static_assert(SHARED_MEMORY_OFFSET + sizeof(XpadSharedMemory) < shared_memory_size,
"XpadSharedMemory is bigger than the shared memory");
shared_memory = std::construct_at(
reinterpret_cast<XpadSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET));
}
-Controller_XPad::~Controller_XPad() = default;
+XPad::~XPad() = default;
-void Controller_XPad::OnInit() {}
+void XPad::OnInit() {}
-void Controller_XPad::OnRelease() {}
+void XPad::OnRelease() {}
-void Controller_XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+void XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
shared_memory->basic_xpad_lifo.buffer_count = 0;
shared_memory->basic_xpad_lifo.buffer_tail = 0;
diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h
index d01dee5fc..9e63a317a 100644
--- a/src/core/hle/service/hid/controllers/xpad.h
+++ b/src/core/hle/service/hid/controllers/xpad.h
@@ -10,10 +10,10 @@
#include "core/hle/service/hid/ring_lifo.h"
namespace Service::HID {
-class Controller_XPad final : public ControllerBase {
+class XPad final : public ControllerBase {
public:
- explicit Controller_XPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
- ~Controller_XPad() override;
+ explicit XPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_);
+ ~XPad() override;
// Called when the controller is initialized
void OnInit() override;
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 4d70006c1..1b7381d8d 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -1,2860 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <array>
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/hid/hid_core.h"
-#include "core/hle/kernel/k_readable_event.h"
-#include "core/hle/kernel/k_shared_memory.h"
-#include "core/hle/kernel/k_transfer_memory.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/hid/hid_debug_server.h"
+#include "core/hle/service/hid/hid_firmware_settings.h"
+#include "core/hle/service/hid/hid_server.h"
+#include "core/hle/service/hid/hid_system_server.h"
#include "core/hle/service/hid/hidbus.h"
#include "core/hle/service/hid/irs.h"
+#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/hid/xcd.h"
-#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/server_manager.h"
-#include "core/memory.h"
-
-#include "core/hle/service/hid/controllers/console_sixaxis.h"
-#include "core/hle/service/hid/controllers/controller_base.h"
-#include "core/hle/service/hid/controllers/debug_pad.h"
-#include "core/hle/service/hid/controllers/gesture.h"
-#include "core/hle/service/hid/controllers/keyboard.h"
-#include "core/hle/service/hid/controllers/mouse.h"
-#include "core/hle/service/hid/controllers/npad.h"
-#include "core/hle/service/hid/controllers/palma.h"
-#include "core/hle/service/hid/controllers/stubbed.h"
-#include "core/hle/service/hid/controllers/touchscreen.h"
-#include "core/hle/service/hid/controllers/xpad.h"
namespace Service::HID {
-// Updating period for each HID device.
-// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
-// Correct npad_update_ns is 4ms this is overclocked to lower input lag
-constexpr auto npad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
-constexpr auto default_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 1000Hz)
-constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
-constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
-
-IAppletResource::IAppletResource(Core::System& system_,
- KernelHelpers::ServiceContext& service_context_)
- : ServiceFramework{system_, "IAppletResource"}, service_context{service_context_} {
- static const FunctionInfo functions[] = {
- {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
- };
- RegisterHandlers(functions);
- u8* shared_memory = system.Kernel().GetHidSharedMem().GetPointer();
- MakeController<Controller_DebugPad>(HidController::DebugPad, shared_memory);
- MakeController<Controller_Touchscreen>(HidController::Touchscreen, shared_memory);
- MakeController<Controller_Mouse>(HidController::Mouse, shared_memory);
- MakeController<Controller_Keyboard>(HidController::Keyboard, shared_memory);
- MakeController<Controller_XPad>(HidController::XPad, shared_memory);
- MakeController<Controller_Stubbed>(HidController::HomeButton, shared_memory);
- MakeController<Controller_Stubbed>(HidController::SleepButton, shared_memory);
- MakeController<Controller_Stubbed>(HidController::CaptureButton, shared_memory);
- MakeController<Controller_Stubbed>(HidController::InputDetector, shared_memory);
- MakeController<Controller_Stubbed>(HidController::UniquePad, shared_memory);
- MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory);
- MakeController<Controller_Gesture>(HidController::Gesture, shared_memory);
- MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory);
- MakeController<Controller_Stubbed>(HidController::DebugMouse, shared_memory);
- MakeControllerWithServiceContext<Controller_Palma>(HidController::Palma, shared_memory);
-
- // Homebrew doesn't try to activate some controllers, so we activate them by default
- GetController<Controller_NPad>(HidController::NPad).ActivateController();
- GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController();
-
- GetController<Controller_Stubbed>(HidController::HomeButton).SetCommonHeaderOffset(0x4C00);
- GetController<Controller_Stubbed>(HidController::SleepButton).SetCommonHeaderOffset(0x4E00);
- GetController<Controller_Stubbed>(HidController::CaptureButton).SetCommonHeaderOffset(0x5000);
- GetController<Controller_Stubbed>(HidController::InputDetector).SetCommonHeaderOffset(0x5200);
- GetController<Controller_Stubbed>(HidController::UniquePad).SetCommonHeaderOffset(0x5A00);
- GetController<Controller_Stubbed>(HidController::DebugMouse).SetCommonHeaderOffset(0x3DC00);
-
- // Register update callbacks
- npad_update_event = Core::Timing::CreateEvent(
- "HID::UpdatePadCallback",
- [this](std::uintptr_t user_data, s64 time,
- std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
- const auto guard = LockService();
- UpdateNpad(user_data, ns_late);
- return std::nullopt;
- });
- default_update_event = Core::Timing::CreateEvent(
- "HID::UpdateDefaultCallback",
- [this](std::uintptr_t user_data, s64 time,
- std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
- const auto guard = LockService();
- UpdateControllers(user_data, ns_late);
- return std::nullopt;
- });
- mouse_keyboard_update_event = Core::Timing::CreateEvent(
- "HID::UpdateMouseKeyboardCallback",
- [this](std::uintptr_t user_data, s64 time,
- std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
- const auto guard = LockService();
- UpdateMouseKeyboard(user_data, ns_late);
- return std::nullopt;
- });
- motion_update_event = Core::Timing::CreateEvent(
- "HID::UpdateMotionCallback",
- [this](std::uintptr_t user_data, s64 time,
- std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
- const auto guard = LockService();
- UpdateMotion(user_data, ns_late);
- return std::nullopt;
- });
-
- system.CoreTiming().ScheduleLoopingEvent(npad_update_ns, npad_update_ns, npad_update_event);
- system.CoreTiming().ScheduleLoopingEvent(default_update_ns, default_update_ns,
- default_update_event);
- system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
- mouse_keyboard_update_event);
- system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
- motion_update_event);
-
- system.HIDCore().ReloadInputDevices();
-}
-
-void IAppletResource::ActivateController(HidController controller) {
- controllers[static_cast<size_t>(controller)]->ActivateController();
-}
-
-void IAppletResource::DeactivateController(HidController controller) {
- controllers[static_cast<size_t>(controller)]->DeactivateController();
-}
-
-IAppletResource::~IAppletResource() {
- system.CoreTiming().UnscheduleEvent(npad_update_event, 0);
- system.CoreTiming().UnscheduleEvent(default_update_event, 0);
- system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event, 0);
- system.CoreTiming().UnscheduleEvent(motion_update_event, 0);
-}
-
-void IAppletResource::GetSharedMemoryHandle(HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(&system.Kernel().GetHidSharedMem());
-}
-
-void IAppletResource::UpdateControllers(std::uintptr_t user_data,
- std::chrono::nanoseconds ns_late) {
- auto& core_timing = system.CoreTiming();
-
- for (const auto& controller : controllers) {
- // Keyboard has it's own update event
- if (controller == controllers[static_cast<size_t>(HidController::Keyboard)]) {
- continue;
- }
- // Mouse has it's own update event
- if (controller == controllers[static_cast<size_t>(HidController::Mouse)]) {
- continue;
- }
- // Npad has it's own update event
- if (controller == controllers[static_cast<size_t>(HidController::NPad)]) {
- continue;
- }
- controller->OnUpdate(core_timing);
- }
-}
-
-void IAppletResource::UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
- auto& core_timing = system.CoreTiming();
-
- controllers[static_cast<size_t>(HidController::NPad)]->OnUpdate(core_timing);
-}
-
-void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,
- std::chrono::nanoseconds ns_late) {
- auto& core_timing = system.CoreTiming();
-
- controllers[static_cast<size_t>(HidController::Mouse)]->OnUpdate(core_timing);
- controllers[static_cast<size_t>(HidController::Keyboard)]->OnUpdate(core_timing);
-}
-
-void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
- auto& core_timing = system.CoreTiming();
-
- controllers[static_cast<size_t>(HidController::NPad)]->OnMotionUpdate(core_timing);
-}
-
-class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
-public:
- explicit IActiveVibrationDeviceList(Core::System& system_,
- std::shared_ptr<IAppletResource> applet_resource_)
- : ServiceFramework{system_, "IActiveVibrationDeviceList"},
- applet_resource(applet_resource_) {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-
-private:
- void InitializeVibrationDevice(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
-
- if (applet_resource != nullptr) {
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .InitializeVibrationDevice(vibration_device_handle);
- }
-
- LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}",
- vibration_device_handle.npad_type, vibration_device_handle.npad_id,
- vibration_device_handle.device_index);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- std::shared_ptr<IAppletResource> applet_resource;
-};
-
-std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
- if (applet_resource == nullptr) {
- applet_resource = std::make_shared<IAppletResource>(system, service_context);
- }
-
- return applet_resource;
-}
-
-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"},
- {1, &Hid::ActivateDebugPad, "ActivateDebugPad"},
- {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"},
- {21, &Hid::ActivateMouse, "ActivateMouse"},
- {26, nullptr, "ActivateDebugMouse"},
- {31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
- {32, &Hid::SendKeyboardLockKeyEvent, "SendKeyboardLockKeyEvent"},
- {40, nullptr, "AcquireXpadIdEventHandle"},
- {41, nullptr, "ReleaseXpadIdEventHandle"},
- {51, &Hid::ActivateXpad, "ActivateXpad"},
- {55, &Hid::GetXpadIDs, "GetXpadIds"},
- {56, nullptr, "ActivateJoyXpad"},
- {58, nullptr, "GetJoyXpadLifoHandle"},
- {59, nullptr, "GetJoyXpadIds"},
- {60, &Hid::ActivateSixAxisSensor, "ActivateSixAxisSensor"},
- {61, &Hid::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"},
- {62, nullptr, "GetSixAxisSensorLifoHandle"},
- {63, nullptr, "ActivateJoySixAxisSensor"},
- {64, nullptr, "DeactivateJoySixAxisSensor"},
- {65, nullptr, "GetJoySixAxisSensorLifoHandle"},
- {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"},
- {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"},
- {68, &Hid::IsSixAxisSensorFusionEnabled, "IsSixAxisSensorFusionEnabled"},
- {69, &Hid::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"},
- {70, &Hid::SetSixAxisSensorFusionParameters, "SetSixAxisSensorFusionParameters"},
- {71, &Hid::GetSixAxisSensorFusionParameters, "GetSixAxisSensorFusionParameters"},
- {72, &Hid::ResetSixAxisSensorFusionParameters, "ResetSixAxisSensorFusionParameters"},
- {73, nullptr, "SetAccelerometerParameters"},
- {74, nullptr, "GetAccelerometerParameters"},
- {75, nullptr, "ResetAccelerometerParameters"},
- {76, nullptr, "SetAccelerometerPlayMode"},
- {77, nullptr, "GetAccelerometerPlayMode"},
- {78, nullptr, "ResetAccelerometerPlayMode"},
- {79, &Hid::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"},
- {80, &Hid::GetGyroscopeZeroDriftMode, "GetGyroscopeZeroDriftMode"},
- {81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"},
- {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
- {83, &Hid::IsFirmwareUpdateAvailableForSixAxisSensor, "IsFirmwareUpdateAvailableForSixAxisSensor"},
- {84, &Hid::EnableSixAxisSensorUnalteredPassthrough, "EnableSixAxisSensorUnalteredPassthrough"},
- {85, &Hid::IsSixAxisSensorUnalteredPassthroughEnabled, "IsSixAxisSensorUnalteredPassthroughEnabled"},
- {86, nullptr, "StoreSixAxisSensorCalibrationParameter"},
- {87, &Hid::LoadSixAxisSensorCalibrationParameter, "LoadSixAxisSensorCalibrationParameter"},
- {88, &Hid::GetSixAxisSensorIcInformation, "GetSixAxisSensorIcInformation"},
- {89, &Hid::ResetIsSixAxisSensorDeviceNewlyAssigned, "ResetIsSixAxisSensorDeviceNewlyAssigned"},
- {91, &Hid::ActivateGesture, "ActivateGesture"},
- {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
- {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
- {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
- {103, &Hid::ActivateNpad, "ActivateNpad"},
- {104, &Hid::DeactivateNpad, "DeactivateNpad"},
- {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"},
- {107, &Hid::DisconnectNpad, "DisconnectNpad"},
- {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"},
- {109, &Hid::ActivateNpadWithRevision, "ActivateNpadWithRevision"},
- {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"},
- {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
- {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"},
- {123, &Hid::SetNpadJoyAssignmentModeSingle, "SetNpadJoyAssignmentModeSingle"},
- {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
- {125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"},
- {126, &Hid::StartLrAssignmentMode, "StartLrAssignmentMode"},
- {127, &Hid::StopLrAssignmentMode, "StopLrAssignmentMode"},
- {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
- {129, &Hid::GetNpadHandheldActivationMode, "GetNpadHandheldActivationMode"},
- {130, &Hid::SwapNpadAssignment, "SwapNpadAssignment"},
- {131, &Hid::IsUnintendedHomeButtonInputProtectionEnabled, "IsUnintendedHomeButtonInputProtectionEnabled"},
- {132, &Hid::EnableUnintendedHomeButtonInputProtection, "EnableUnintendedHomeButtonInputProtection"},
- {133, &Hid::SetNpadJoyAssignmentModeSingleWithDestination, "SetNpadJoyAssignmentModeSingleWithDestination"},
- {134, &Hid::SetNpadAnalogStickUseCenterClamp, "SetNpadAnalogStickUseCenterClamp"},
- {135, &Hid::SetNpadCaptureButtonAssignment, "SetNpadCaptureButtonAssignment"},
- {136, &Hid::ClearNpadCaptureButtonAssignment, "ClearNpadCaptureButtonAssignment"},
- {200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
- {201, &Hid::SendVibrationValue, "SendVibrationValue"},
- {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
- {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
- {204, &Hid::PermitVibration, "PermitVibration"},
- {205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"},
- {206, &Hid::SendVibrationValues, "SendVibrationValues"},
- {207, &Hid::SendVibrationGcErmCommand, "SendVibrationGcErmCommand"},
- {208, &Hid::GetActualVibrationGcErmCommand, "GetActualVibrationGcErmCommand"},
- {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
- {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
- {211, &Hid::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"},
- {212, nullptr, "SendVibrationValueInBool"},
- {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
- {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
- {302, &Hid::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"},
- {303, &Hid::ActivateSevenSixAxisSensor, "ActivateSevenSixAxisSensor"},
- {304, &Hid::StartSevenSixAxisSensor, "StartSevenSixAxisSensor"},
- {305, &Hid::StopSevenSixAxisSensor, "StopSevenSixAxisSensor"},
- {306, &Hid::InitializeSevenSixAxisSensor, "InitializeSevenSixAxisSensor"},
- {307, &Hid::FinalizeSevenSixAxisSensor, "FinalizeSevenSixAxisSensor"},
- {308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
- {309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
- {310, &Hid::ResetSevenSixAxisSensorTimestamp, "ResetSevenSixAxisSensorTimestamp"},
- {400, &Hid::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
- {401, nullptr, "EnableUsbFullKeyController"},
- {402, nullptr, "IsUsbFullKeyControllerConnected"},
- {403, nullptr, "HasBattery"},
- {404, nullptr, "HasLeftRightBattery"},
- {405, nullptr, "GetNpadInterfaceType"},
- {406, nullptr, "GetNpadLeftRightInterfaceType"},
- {407, nullptr, "GetNpadOfHighestBatteryLevel"},
- {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
- {500, &Hid::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"},
- {501, &Hid::InitializePalma, "InitializePalma"},
- {502, &Hid::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"},
- {503, &Hid::GetPalmaOperationInfo, "GetPalmaOperationInfo"},
- {504, &Hid::PlayPalmaActivity, "PlayPalmaActivity"},
- {505, &Hid::SetPalmaFrModeType, "SetPalmaFrModeType"},
- {506, &Hid::ReadPalmaStep, "ReadPalmaStep"},
- {507, &Hid::EnablePalmaStep, "EnablePalmaStep"},
- {508, &Hid::ResetPalmaStep, "ResetPalmaStep"},
- {509, &Hid::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"},
- {510, &Hid::WritePalmaApplicationSection, "WritePalmaApplicationSection"},
- {511, &Hid::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"},
- {512, &Hid::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"},
- {513, &Hid::WritePalmaActivityEntry, "WritePalmaActivityEntry"},
- {514, &Hid::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"},
- {515, &Hid::WritePalmaWaveEntry, "WritePalmaWaveEntry"},
- {516, &Hid::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"},
- {517, &Hid::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"},
- {518, &Hid::SuspendPalmaFeature, "SuspendPalmaFeature"},
- {519, &Hid::GetPalmaOperationResult, "GetPalmaOperationResult"},
- {520, &Hid::ReadPalmaPlayLog, "ReadPalmaPlayLog"},
- {521, &Hid::ResetPalmaPlayLog, "ResetPalmaPlayLog"},
- {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
- {523, &Hid::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"},
- {524, &Hid::PairPalma, "PairPalma"},
- {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
- {526, &Hid::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"},
- {527, &Hid::EnablePalmaBoostMode, "EnablePalmaBoostMode"},
- {528, &Hid::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"},
- {529, &Hid::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"},
- {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
- {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
- {1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
- {1003, &Hid::IsFirmwareUpdateNeededForNotification, "IsFirmwareUpdateNeededForNotification"},
- {2000, nullptr, "ActivateDigitizer"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-}
-
-Hid::~Hid() = default;
-
-void Hid::CreateAppletResource(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- if (applet_resource == nullptr) {
- applet_resource = std::make_shared<IAppletResource>(system, service_context);
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IAppletResource>(applet_resource);
-}
-
-void Hid::ActivateDebugPad(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->ActivateController(HidController::DebugPad);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ActivateTouchScreen(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->ActivateController(HidController::Touchscreen);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ActivateMouse(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->ActivateController(HidController::Mouse);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ActivateKeyboard(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->ActivateController(HidController::Keyboard);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SendKeyboardLockKeyEvent(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto flags{rp.Pop<u32>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ActivateXpad(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- u32 basic_xpad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- applet_resource->ActivateController(HidController::XPad);
-
- LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}",
- parameters.basic_xpad_id, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetXpadIDs(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_DEBUG(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(0);
-}
-
-void Hid::ActivateSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- u32 basic_xpad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- // This function does nothing on 10.0.0+
-
- LOG_WARNING(Service_HID, "(STUBBED) called, basic_xpad_id={}, applet_resource_user_id={}",
- parameters.basic_xpad_id, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::DeactivateSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- u32 basic_xpad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- // This function does nothing on 10.0.0+
-
- LOG_WARNING(Service_HID, "(STUBBED) called, basic_xpad_id={}, applet_resource_user_id={}",
- parameters.basic_xpad_id, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::StartSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.SetSixAxisEnabled(parameters.sixaxis_handle, true);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::StopSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.SetSixAxisEnabled(parameters.sixaxis_handle, false);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::IsSixAxisSensorFusionEnabled(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- bool is_enabled{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result =
- controller.IsSixAxisSensorFusionEnabled(parameters.sixaxis_handle, is_enabled);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(result);
- rb.Push(is_enabled);
-}
-
-void Hid::EnableSixAxisSensorFusion(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- bool enable_sixaxis_sensor_fusion;
- INSERT_PADDING_BYTES_NOINIT(3);
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.SetSixAxisFusionEnabled(parameters.sixaxis_handle,
- parameters.enable_sixaxis_sensor_fusion);
-
- LOG_DEBUG(Service_HID,
- "called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, "
- "device_index={}, applet_resource_user_id={}",
- parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type,
- parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
- parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::SetSixAxisSensorFusionParameters(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- Core::HID::SixAxisSensorFusionParameters sixaxis_fusion;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result =
- controller.SetSixAxisFusionParameters(parameters.sixaxis_handle, parameters.sixaxis_fusion);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, parameter1={}, "
- "parameter2={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.sixaxis_fusion.parameter1,
- parameters.sixaxis_fusion.parameter2, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::GetSixAxisSensorFusionParameters(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- Core::HID::SixAxisSensorFusionParameters fusion_parameters{};
- const auto& controller =
- GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result =
- controller.GetSixAxisFusionParameters(parameters.sixaxis_handle, fusion_parameters);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(result);
- rb.PushRaw(fusion_parameters);
-}
-
-void Hid::ResetSixAxisSensorFusionParameters(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- // Since these parameters are unknown just use what HW outputs
- const Core::HID::SixAxisSensorFusionParameters fusion_parameters{
- .parameter1 = 0.03f,
- .parameter2 = 0.4f,
- };
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result1 =
- controller.SetSixAxisFusionParameters(parameters.sixaxis_handle, fusion_parameters);
- const auto result2 = controller.SetSixAxisFusionEnabled(parameters.sixaxis_handle, true);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- if (result1.IsError()) {
- rb.Push(result1);
- return;
- }
- rb.Push(result2);
-}
-
-void Hid::SetGyroscopeZeroDriftMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto sixaxis_handle{rp.PopRaw<Core::HID::SixAxisSensorHandle>()};
- const auto drift_mode{rp.PopEnum<Core::HID::GyroscopeZeroDriftMode>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.SetGyroscopeZeroDriftMode(sixaxis_handle, drift_mode);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, "
- "applet_resource_user_id={}",
- sixaxis_handle.npad_type, sixaxis_handle.npad_id, sixaxis_handle.device_index,
- drift_mode, applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::GetGyroscopeZeroDriftMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto drift_mode{Core::HID::GyroscopeZeroDriftMode::Standard};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.GetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(result);
- rb.PushEnum(drift_mode);
-}
-
-void Hid::ResetGyroscopeZeroDriftMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- const auto drift_mode{Core::HID::GyroscopeZeroDriftMode::Standard};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.SetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::IsSixAxisSensorAtRest(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- bool is_at_rest{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- controller.IsSixAxisSensorAtRest(parameters.sixaxis_handle, is_at_rest);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(is_at_rest);
-}
-
-void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- bool is_firmware_available{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- controller.IsFirmwareUpdateAvailableForSixAxisSensor(parameters.sixaxis_handle,
- is_firmware_available);
-
- LOG_WARNING(
- Service_HID,
- "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(is_firmware_available);
-}
-
-void Hid::EnableSixAxisSensorUnalteredPassthrough(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- bool enabled;
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.EnableSixAxisSensorUnalteredPassthrough(
- parameters.sixaxis_handle, parameters.enabled);
-
- LOG_DEBUG(Service_HID,
- "(STUBBED) called, enabled={}, npad_type={}, npad_id={}, device_index={}, "
- "applet_resource_user_id={}",
- parameters.enabled, parameters.sixaxis_handle.npad_type,
- parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
- parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::IsSixAxisSensorUnalteredPassthroughEnabled(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- bool is_unaltered_sisxaxis_enabled{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.IsSixAxisSensorUnalteredPassthroughEnabled(
- parameters.sixaxis_handle, is_unaltered_sisxaxis_enabled);
-
- LOG_DEBUG(
- Service_HID,
- "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(result);
- rb.Push(is_unaltered_sisxaxis_enabled);
-}
-
-void Hid::LoadSixAxisSensorCalibrationParameter(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- Core::HID::SixAxisSensorCalibrationParameter calibration{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result =
- controller.LoadSixAxisSensorCalibrationParameter(parameters.sixaxis_handle, calibration);
-
- LOG_WARNING(
- Service_HID,
- "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- if (result.IsSuccess()) {
- ctx.WriteBuffer(calibration);
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::GetSixAxisSensorIcInformation(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- Core::HID::SixAxisSensorIcInformation ic_information{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result =
- controller.GetSixAxisSensorIcInformation(parameters.sixaxis_handle, ic_information);
-
- LOG_WARNING(
- Service_HID,
- "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- if (result.IsSuccess()) {
- ctx.WriteBuffer(ic_information);
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::ResetIsSixAxisSensorDeviceNewlyAssigned(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::SixAxisSensorHandle sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result =
- controller.ResetIsSixAxisSensorDeviceNewlyAssigned(parameters.sixaxis_handle);
-
- LOG_WARNING(
- Service_HID,
- "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
- parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::ActivateGesture(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- u32 unknown;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- applet_resource->ActivateController(HidController::Gesture);
-
- LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}, applet_resource_user_id={}",
- parameters.unknown, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetSupportedNpadStyleSet(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadStyleSet supported_styleset;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSupportedStyleSet({parameters.supported_styleset});
-
- LOG_DEBUG(Service_HID, "called, supported_styleset={}, applet_resource_user_id={}",
- parameters.supported_styleset, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetSupportedNpadStyleSet(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetSupportedStyleSet()
- .raw);
-}
-
-void Hid::SetSupportedNpadIdType(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- const auto result = applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSupportedNpadIdTypes(ctx.ReadBuffer());
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::ActivateNpad(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->ActivateController(HidController::NPad);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::DeactivateNpad(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->DeactivateController(HidController::NPad);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::AcquireNpadStyleSetUpdateEventHandle(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadIdType npad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- u64 unknown;
- };
- static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}",
- parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown);
-
- // Games expect this event to be signaled after calling this function
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SignalStyleSetChangedEvent(parameters.npad_id);
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetStyleSetChangedEvent(parameters.npad_id));
-}
-
-void Hid::DisconnectNpad(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadIdType npad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- controller.DisconnectNpad(parameters.npad_id);
-
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
- parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetPlayerLedPattern(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id{rp.PopEnum<Core::HID::NpadIdType>()};
-
- Core::HID::LedPattern pattern{0, 0, 0, 0};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.GetLedPattern(npad_id, pattern);
-
- LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(result);
- rb.Push(pattern.raw);
-}
-
-void Hid::ActivateNpadWithRevision(HLERequestContext& ctx) {
- // Should have no effect with how our npad sets up the data
- IPC::RequestParser rp{ctx};
- struct Parameters {
- s32 revision;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- applet_resource->ActivateController(HidController::NPad);
-
- LOG_DEBUG(Service_HID, "called, revision={}, applet_resource_user_id={}", parameters.revision,
- parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetNpadJoyHoldType(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto hold_type{rp.PopEnum<Controller_NPad::NpadJoyHoldType>()};
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad).SetHoldType(hold_type);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}",
- applet_resource_user_id, hold_type);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetNpadJoyHoldType(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad).GetHoldType());
-}
-
-void Hid::SetNpadJoyAssignmentModeSingleByDefault(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadIdType npad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- Core::HID::NpadIdType new_npad_id{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(new_npad_id, parameters.npad_id,
- Controller_NPad::NpadJoyDeviceType::Left,
- Controller_NPad::NpadJoyAssignmentMode::Single);
-
- LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
- parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetNpadJoyAssignmentModeSingle(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadIdType npad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- Controller_NPad::NpadJoyDeviceType npad_joy_device_type;
- };
- static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- Core::HID::NpadIdType new_npad_id{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(new_npad_id, parameters.npad_id, parameters.npad_joy_device_type,
- Controller_NPad::NpadJoyAssignmentMode::Single);
-
- LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
- parameters.npad_id, parameters.applet_resource_user_id,
- parameters.npad_joy_device_type);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetNpadJoyAssignmentModeDual(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadIdType npad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- Core::HID::NpadIdType new_npad_id{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(new_npad_id, parameters.npad_id, {},
- Controller_NPad::NpadJoyAssignmentMode::Dual);
-
- LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
- parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::MergeSingleJoyAsDualJoy(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id_1{rp.PopEnum<Core::HID::NpadIdType>()};
- const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2);
-
- LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
- npad_id_1, npad_id_2, applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::StartLrAssignmentMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad).StartLRAssignmentMode();
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::StopLrAssignmentMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad).StopLRAssignmentMode();
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetNpadHandheldActivationMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto activation_mode{rp.PopEnum<Controller_NPad::NpadHandheldActivationMode>()};
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetNpadHandheldActivationMode(activation_mode);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, activation_mode={}",
- applet_resource_user_id, activation_mode);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetNpadHandheldActivationMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetNpadHandheldActivationMode());
-}
-
-void Hid::SwapNpadAssignment(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id_1{rp.PopEnum<Core::HID::NpadIdType>()};
- const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.SwapNpadAssignment(npad_id_1, npad_id_2);
-
- LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
- npad_id_1, npad_id_2, applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::IsUnintendedHomeButtonInputProtectionEnabled(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadIdType npad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- bool is_enabled = false;
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result =
- controller.IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id, is_enabled);
-
- LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
- parameters.npad_id, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(result);
- rb.Push(is_enabled);
-}
-
-void Hid::EnableUnintendedHomeButtonInputProtection(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- bool unintended_home_button_input_protection;
- INSERT_PADDING_BYTES_NOINIT(3);
- Core::HID::NpadIdType npad_id;
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto result = controller.SetUnintendedHomeButtonInputProtectionEnabled(
- parameters.unintended_home_button_input_protection, parameters.npad_id);
-
- LOG_WARNING(Service_HID,
- "(STUBBED) called, unintended_home_button_input_protection={}, npad_id={},"
- "applet_resource_user_id={}",
- parameters.unintended_home_button_input_protection, parameters.npad_id,
- parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::SetNpadJoyAssignmentModeSingleWithDestination(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadIdType npad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- Controller_NPad::NpadJoyDeviceType npad_joy_device_type;
- };
- static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- Core::HID::NpadIdType new_npad_id{};
- auto& controller = GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
- const auto is_reassigned =
- controller.SetNpadMode(new_npad_id, parameters.npad_id, parameters.npad_joy_device_type,
- Controller_NPad::NpadJoyAssignmentMode::Single);
-
- LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
- parameters.npad_id, parameters.applet_resource_user_id,
- parameters.npad_joy_device_type);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push(is_reassigned);
- rb.PushEnum(new_npad_id);
-}
-
-void Hid::SetNpadAnalogStickUseCenterClamp(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- bool analog_stick_use_center_clamp;
- INSERT_PADDING_BYTES_NOINIT(7);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- GetAppletResource()
- ->GetController<Controller_NPad>(HidController::NPad)
- .SetAnalogStickUseCenterClamp(parameters.analog_stick_use_center_clamp);
-
- LOG_WARNING(Service_HID,
- "(STUBBED) called, analog_stick_use_center_clamp={}, applet_resource_user_id={}",
- parameters.analog_stick_use_center_clamp, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetNpadCaptureButtonAssignment(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadStyleSet npad_styleset;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- Core::HID::NpadButton button;
- };
- static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_WARNING(Service_HID,
- "(STUBBED) called, npad_styleset={}, applet_resource_user_id={}, button={}",
- parameters.npad_styleset, parameters.applet_resource_user_id, parameters.button);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ClearNpadCaptureButtonAssignment(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
- applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetVibrationDeviceInfo(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
- const auto& controller =
- GetAppletResource()->GetController<Controller_NPad>(HidController::NPad);
-
- Core::HID::VibrationDeviceInfo vibration_device_info;
- bool check_device_index = false;
-
- switch (vibration_device_handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
- case Core::HID::NpadStyleIndex::Handheld:
- case Core::HID::NpadStyleIndex::JoyconDual:
- case Core::HID::NpadStyleIndex::JoyconLeft:
- case Core::HID::NpadStyleIndex::JoyconRight:
- vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
- check_device_index = true;
- break;
- case Core::HID::NpadStyleIndex::GameCube:
- vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm;
- break;
- case Core::HID::NpadStyleIndex::N64:
- vibration_device_info.type = Core::HID::VibrationDeviceType::N64;
- break;
- default:
- vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown;
- break;
- }
-
- vibration_device_info.position = Core::HID::VibrationDevicePosition::None;
- if (check_device_index) {
- switch (vibration_device_handle.device_index) {
- case Core::HID::DeviceIndex::Left:
- vibration_device_info.position = Core::HID::VibrationDevicePosition::Left;
- break;
- case Core::HID::DeviceIndex::Right:
- vibration_device_info.position = Core::HID::VibrationDevicePosition::Right;
- break;
- case Core::HID::DeviceIndex::None:
- default:
- ASSERT_MSG(false, "DeviceIndex should never be None!");
- break;
- }
- }
-
- LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}",
- vibration_device_info.type, vibration_device_info.position);
-
- const auto result = controller.IsDeviceHandleValid(vibration_device_handle);
- if (result.IsError()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.PushRaw(vibration_device_info);
-}
-
-void Hid::SendVibrationValue(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::VibrationDeviceHandle vibration_device_handle;
- Core::HID::VibrationValue vibration_value;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateController(parameters.vibration_device_handle, parameters.vibration_value);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.vibration_device_handle.npad_type,
- parameters.vibration_device_handle.npad_id,
- parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetActualVibrationValue(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::VibrationDeviceHandle vibration_device_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.vibration_device_handle.npad_type,
- parameters.vibration_device_handle.npad_id,
- parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 6};
- rb.Push(ResultSuccess);
- rb.PushRaw(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetLastVibration(parameters.vibration_device_handle));
-}
-
-void Hid::CreateActiveVibrationDeviceList(HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IActiveVibrationDeviceList>(system, applet_resource);
-}
-
-void Hid::PermitVibration(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto can_vibrate{rp.Pop<bool>()};
-
- // nnSDK saves this value as a float. Since it can only be 1.0f or 0.0f we simplify this value
- // by converting it to a bool
- Settings::values.vibration_enabled.SetValue(can_vibrate);
-
- LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::IsVibrationPermitted(HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
-
- // nnSDK checks if a float is greater than zero. We return the bool we stored earlier
- const auto is_enabled = Settings::values.vibration_enabled.GetValue();
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(is_enabled);
-}
-
-void Hid::SendVibrationValues(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- const auto handle_data = ctx.ReadBuffer(0);
- const auto handle_count = ctx.GetReadBufferNumElements<Core::HID::VibrationDeviceHandle>(0);
- const auto vibration_data = ctx.ReadBuffer(1);
- const auto vibration_count = ctx.GetReadBufferNumElements<Core::HID::VibrationValue>(1);
-
- auto vibration_device_handles =
- std::span(reinterpret_cast<const Core::HID::VibrationDeviceHandle*>(handle_data.data()),
- handle_count);
- auto vibration_values = std::span(
- reinterpret_cast<const Core::HID::VibrationValue*>(vibration_data.data()), vibration_count);
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateControllers(vibration_device_handles, vibration_values);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SendVibrationGcErmCommand(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::VibrationDeviceHandle vibration_device_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- Core::HID::VibrationGcErmCommand gc_erm_command;
- };
- static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- /**
- * Note: This uses yuzu-specific behavior such that the StopHard command produces
- * vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined below,
- * in order to differentiate between Stop and StopHard commands.
- * This is done to reuse the controller vibration functions made for regular controllers.
- */
- const auto vibration_value = [parameters] {
- switch (parameters.gc_erm_command) {
- case Core::HID::VibrationGcErmCommand::Stop:
- return Core::HID::VibrationValue{
- .low_amplitude = 0.0f,
- .low_frequency = 160.0f,
- .high_amplitude = 0.0f,
- .high_frequency = 320.0f,
- };
- case Core::HID::VibrationGcErmCommand::Start:
- return Core::HID::VibrationValue{
- .low_amplitude = 1.0f,
- .low_frequency = 160.0f,
- .high_amplitude = 1.0f,
- .high_frequency = 320.0f,
- };
- case Core::HID::VibrationGcErmCommand::StopHard:
- return Core::HID::VibrationValue{
- .low_amplitude = 0.0f,
- .low_frequency = 0.0f,
- .high_amplitude = 0.0f,
- .high_frequency = 0.0f,
- };
- default:
- return Core::HID::DEFAULT_VIBRATION_VALUE;
- }
- }();
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateController(parameters.vibration_device_handle, vibration_value);
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, "
- "gc_erm_command={}",
- parameters.vibration_device_handle.npad_type,
- parameters.vibration_device_handle.npad_id,
- parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id,
- parameters.gc_erm_command);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetActualVibrationGcErmCommand(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::VibrationDeviceHandle vibration_device_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- const auto last_vibration = applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetLastVibration(parameters.vibration_device_handle);
-
- const auto gc_erm_command = [last_vibration] {
- if (last_vibration.low_amplitude != 0.0f || last_vibration.high_amplitude != 0.0f) {
- return Core::HID::VibrationGcErmCommand::Start;
- }
-
- /**
- * Note: This uses yuzu-specific behavior such that the StopHard command produces
- * vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined in the HID function
- * SendVibrationGcErmCommand, in order to differentiate between Stop and StopHard commands.
- * This is done to reuse the controller vibration functions made for regular controllers.
- */
- if (last_vibration.low_frequency == 0.0f && last_vibration.high_frequency == 0.0f) {
- return Core::HID::VibrationGcErmCommand::StopHard;
- }
-
- return Core::HID::VibrationGcErmCommand::Stop;
- }();
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.vibration_device_handle.npad_type,
- parameters.vibration_device_handle.npad_id,
- parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.PushEnum(gc_erm_command);
-}
-
-void Hid::BeginPermitVibrationSession(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetPermitVibrationSession(true);
-
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::EndPermitVibrationSession(HLERequestContext& ctx) {
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetPermitVibrationSession(false);
-
- LOG_DEBUG(Service_HID, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::IsVibrationDeviceMounted(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::VibrationDeviceHandle vibration_device_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_DEBUG(Service_HID,
- "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
- parameters.vibration_device_handle.npad_type,
- parameters.vibration_device_handle.npad_id,
- parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .IsVibrationDeviceMounted(parameters.vibration_device_handle));
-}
-
-void Hid::ActivateConsoleSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->ActivateController(HidController::ConsoleSixAxisSensor);
-
- LOG_WARNING(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::StartConsoleSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_WARNING(Service_HID,
- "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
- parameters.console_sixaxis_handle.unknown_1,
- parameters.console_sixaxis_handle.unknown_2, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::StopConsoleSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_WARNING(Service_HID,
- "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
- parameters.console_sixaxis_handle.unknown_1,
- parameters.console_sixaxis_handle.unknown_2, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ActivateSevenSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->ActivateController(HidController::ConsoleSixAxisSensor);
-
- LOG_WARNING(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::StartSevenSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
- applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::StopSevenSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
- applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::InitializeSevenSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto t_mem_1_size{rp.Pop<u64>()};
- const auto t_mem_2_size{rp.Pop<u64>()};
- const auto t_mem_1_handle{ctx.GetCopyHandle(0)};
- const auto t_mem_2_handle{ctx.GetCopyHandle(1)};
-
- ASSERT_MSG(t_mem_1_size == 0x1000, "t_mem_1_size is not 0x1000 bytes");
- ASSERT_MSG(t_mem_2_size == 0x7F000, "t_mem_2_size is not 0x7F000 bytes");
-
- auto t_mem_1 = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
- t_mem_1_handle);
-
- if (t_mem_1.IsNull()) {
- LOG_ERROR(Service_HID, "t_mem_1 is a nullptr for handle=0x{:08X}", t_mem_1_handle);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultUnknown);
- return;
- }
-
- auto t_mem_2 = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
- t_mem_2_handle);
-
- if (t_mem_2.IsNull()) {
- LOG_ERROR(Service_HID, "t_mem_2 is a nullptr for handle=0x{:08X}", t_mem_2_handle);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultUnknown);
- return;
- }
-
- ASSERT_MSG(t_mem_1->GetSize() == 0x1000, "t_mem_1 has incorrect size");
- ASSERT_MSG(t_mem_2->GetSize() == 0x7F000, "t_mem_2 has incorrect size");
-
- // Activate console six axis controller
- applet_resource->GetController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor)
- .ActivateController();
-
- applet_resource->GetController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor)
- .SetTransferMemoryAddress(t_mem_1->GetSourceAddress());
-
- LOG_WARNING(Service_HID,
- "called, t_mem_1_handle=0x{:08X}, t_mem_2_handle=0x{:08X}, "
- "applet_resource_user_id={}",
- t_mem_1_handle, t_mem_2_handle, applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::FinalizeSevenSixAxisSensor(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
- applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ResetSevenSixAxisSensorTimestamp(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- applet_resource->GetController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor)
- .ResetTimestamp();
-
- LOG_WARNING(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(false);
-}
-
-void Hid::GetPalmaConnectionHandle(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- Core::HID::NpadIdType npad_id;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
- parameters.npad_id, parameters.applet_resource_user_id);
-
- Controller_Palma::PalmaConnectionHandle handle;
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
- const auto result = controller.GetPalmaConnectionHandle(parameters.npad_id, handle);
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(result);
- rb.PushRaw(handle);
-}
-
-void Hid::InitializePalma(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
- const auto result = controller.InitializePalma(connection_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::AcquirePalmaOperationCompleteEvent(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(controller.AcquirePalmaOperationCompleteEvent(connection_handle));
-}
-
-void Hid::GetPalmaOperationInfo(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- Controller_Palma::PalmaOperationType operation_type;
- Controller_Palma::PalmaOperationData data;
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
- const auto result = controller.GetPalmaOperationInfo(connection_handle, operation_type, data);
-
- if (result.IsError()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- }
-
- ctx.WriteBuffer(data);
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(result);
- rb.Push(static_cast<u64>(operation_type));
-}
-
-void Hid::PlayPalmaActivity(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
- const auto palma_activity{rp.Pop<u64>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}",
- connection_handle.npad_id, palma_activity);
-
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
- const auto result = controller.PlayPalmaActivity(connection_handle, palma_activity);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::SetPalmaFrModeType(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
- const auto fr_mode{rp.PopEnum<Controller_Palma::PalmaFrModeType>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}",
- connection_handle.npad_id, fr_mode);
-
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
- const auto result = controller.SetPalmaFrModeType(connection_handle, fr_mode);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::ReadPalmaStep(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
- const auto result = controller.ReadPalmaStep(connection_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::EnablePalmaStep(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- bool is_enabled;
- INSERT_PADDING_WORDS_NOINIT(1);
- Controller_Palma::PalmaConnectionHandle connection_handle;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}",
- parameters.connection_handle.npad_id, parameters.is_enabled);
-
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
- const auto result =
- controller.EnablePalmaStep(parameters.connection_handle, parameters.is_enabled);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::ResetPalmaStep(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
- const auto result = controller.ResetPalmaStep(connection_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::ReadPalmaApplicationSection(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::WritePalmaApplicationSection(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ReadPalmaUniqueCode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .ReadPalmaUniqueCode(connection_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetPalmaUniqueCodeInvalid(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .SetPalmaUniqueCodeInvalid(connection_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::WritePalmaActivityEntry(HLERequestContext& ctx) {
- LOG_CRITICAL(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::WritePalmaRgbLedPatternEntry(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
- const auto unknown{rp.Pop<u64>()};
-
- [[maybe_unused]] const auto buffer = ctx.ReadBuffer();
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}",
- connection_handle.npad_id, unknown);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .WritePalmaRgbLedPatternEntry(connection_handle, unknown);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::WritePalmaWaveEntry(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
- const auto wave_set{rp.PopEnum<Controller_Palma::PalmaWaveSet>()};
- const auto unknown{rp.Pop<u64>()};
- const auto t_mem_size{rp.Pop<u64>()};
- const auto t_mem_handle{ctx.GetCopyHandle(0)};
- const auto size{rp.Pop<u64>()};
-
- ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes");
-
- auto t_mem = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
- t_mem_handle);
-
- if (t_mem.IsNull()) {
- LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultUnknown);
- return;
- }
-
- ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size");
-
- LOG_WARNING(Service_HID,
- "(STUBBED) called, connection_handle={}, wave_set={}, unknown={}, "
- "t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
- connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .WritePalmaWaveEntry(connection_handle, wave_set, t_mem->GetSourceAddress(), t_mem_size);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetPalmaDataBaseIdentificationVersion(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- s32 database_id_version;
- INSERT_PADDING_WORDS_NOINIT(1);
- Controller_Palma::PalmaConnectionHandle connection_handle;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}",
- parameters.connection_handle.npad_id, parameters.database_id_version);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .SetPalmaDataBaseIdentificationVersion(parameters.connection_handle,
- parameters.database_id_version);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetPalmaDataBaseIdentificationVersion(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .GetPalmaDataBaseIdentificationVersion(connection_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SuspendPalmaFeature(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetPalmaOperationResult(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- const auto result = applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .GetPalmaOperationResult(connection_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Hid::ReadPalmaPlayLog(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::ResetPalmaPlayLog(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetIsPalmaAllConnectable(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- bool is_palma_all_connectable;
- INSERT_PADDING_BYTES_NOINIT(7);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_WARNING(Service_HID,
- "(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}",
- parameters.is_palma_all_connectable, parameters.applet_resource_user_id);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .SetIsPalmaAllConnectable(parameters.is_palma_all_connectable);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetIsPalmaPairedConnectable(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::PairPalma(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .PairPalma(connection_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetPalmaBoostMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto palma_boost_mode{rp.Pop<bool>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
-
- applet_resource->GetController<Controller_Palma>(HidController::Palma)
- .SetPalmaBoostMode(palma_boost_mode);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::CancelWritePalmaWaveEntry(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::EnablePalmaBoostMode(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetPalmaBluetoothAddress(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetDisallowedPalmaConnection(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::SetNpadCommunicationMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto communication_mode{rp.PopEnum<Controller_NPad::NpadCommunicationMode>()};
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetNpadCommunicationMode(communication_mode);
-
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, communication_mode={}",
- applet_resource_user_id, communication_mode);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetNpadCommunicationMode());
-}
-
-void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}",
- touchscreen_mode.mode, applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Hid::IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- s32 unknown;
- INSERT_PADDING_WORDS_NOINIT(1);
- u64 applet_resource_user_id;
- };
- static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}, applet_resource_user_id={}",
- parameters.unknown, parameters.applet_resource_user_id);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(false);
-}
-
-class HidDbg final : public ServiceFramework<HidDbg> {
-public:
- explicit HidDbg(Core::System& system_) : ServiceFramework{system_, "hid:dbg"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "DeactivateDebugPad"},
- {1, nullptr, "SetDebugPadAutoPilotState"},
- {2, nullptr, "UnsetDebugPadAutoPilotState"},
- {10, nullptr, "DeactivateTouchScreen"},
- {11, nullptr, "SetTouchScreenAutoPilotState"},
- {12, nullptr, "UnsetTouchScreenAutoPilotState"},
- {13, nullptr, "GetTouchScreenConfiguration"},
- {14, nullptr, "ProcessTouchScreenAutoTune"},
- {15, nullptr, "ForceStopTouchScreenManagement"},
- {16, nullptr, "ForceRestartTouchScreenManagement"},
- {17, nullptr, "IsTouchScreenManaged"},
- {20, nullptr, "DeactivateMouse"},
- {21, nullptr, "SetMouseAutoPilotState"},
- {22, nullptr, "UnsetMouseAutoPilotState"},
- {25, nullptr, "SetDebugMouseAutoPilotState"},
- {26, nullptr, "UnsetDebugMouseAutoPilotState"},
- {30, nullptr, "DeactivateKeyboard"},
- {31, nullptr, "SetKeyboardAutoPilotState"},
- {32, nullptr, "UnsetKeyboardAutoPilotState"},
- {50, nullptr, "DeactivateXpad"},
- {51, nullptr, "SetXpadAutoPilotState"},
- {52, nullptr, "UnsetXpadAutoPilotState"},
- {53, nullptr, "DeactivateJoyXpad"},
- {60, nullptr, "ClearNpadSystemCommonPolicy"},
- {61, nullptr, "DeactivateNpad"},
- {62, nullptr, "ForceDisconnectNpad"},
- {91, nullptr, "DeactivateGesture"},
- {110, nullptr, "DeactivateHomeButton"},
- {111, nullptr, "SetHomeButtonAutoPilotState"},
- {112, nullptr, "UnsetHomeButtonAutoPilotState"},
- {120, nullptr, "DeactivateSleepButton"},
- {121, nullptr, "SetSleepButtonAutoPilotState"},
- {122, nullptr, "UnsetSleepButtonAutoPilotState"},
- {123, nullptr, "DeactivateInputDetector"},
- {130, nullptr, "DeactivateCaptureButton"},
- {131, nullptr, "SetCaptureButtonAutoPilotState"},
- {132, nullptr, "UnsetCaptureButtonAutoPilotState"},
- {133, nullptr, "SetShiftAccelerometerCalibrationValue"},
- {134, nullptr, "GetShiftAccelerometerCalibrationValue"},
- {135, nullptr, "SetShiftGyroscopeCalibrationValue"},
- {136, nullptr, "GetShiftGyroscopeCalibrationValue"},
- {140, nullptr, "DeactivateConsoleSixAxisSensor"},
- {141, nullptr, "GetConsoleSixAxisSensorSamplingFrequency"},
- {142, nullptr, "DeactivateSevenSixAxisSensor"},
- {143, nullptr, "GetConsoleSixAxisSensorCountStates"},
- {144, nullptr, "GetAccelerometerFsr"},
- {145, nullptr, "SetAccelerometerFsr"},
- {146, nullptr, "GetAccelerometerOdr"},
- {147, nullptr, "SetAccelerometerOdr"},
- {148, nullptr, "GetGyroscopeFsr"},
- {149, nullptr, "SetGyroscopeFsr"},
- {150, nullptr, "GetGyroscopeOdr"},
- {151, nullptr, "SetGyroscopeOdr"},
- {152, nullptr, "GetWhoAmI"},
- {201, nullptr, "ActivateFirmwareUpdate"},
- {202, nullptr, "DeactivateFirmwareUpdate"},
- {203, nullptr, "StartFirmwareUpdate"},
- {204, nullptr, "GetFirmwareUpdateStage"},
- {205, nullptr, "GetFirmwareVersion"},
- {206, nullptr, "GetDestinationFirmwareVersion"},
- {207, nullptr, "DiscardFirmwareInfoCacheForRevert"},
- {208, nullptr, "StartFirmwareUpdateForRevert"},
- {209, nullptr, "GetAvailableFirmwareVersionForRevert"},
- {210, nullptr, "IsFirmwareUpdatingDevice"},
- {211, nullptr, "StartFirmwareUpdateIndividual"},
- {215, nullptr, "SetUsbFirmwareForceUpdateEnabled"},
- {216, nullptr, "SetAllKuinaDevicesToFirmwareUpdateMode"},
- {221, nullptr, "UpdateControllerColor"},
- {222, nullptr, "ConnectUsbPadsAsync"},
- {223, nullptr, "DisconnectUsbPadsAsync"},
- {224, nullptr, "UpdateDesignInfo"},
- {225, nullptr, "GetUniquePadDriverState"},
- {226, nullptr, "GetSixAxisSensorDriverStates"},
- {227, nullptr, "GetRxPacketHistory"},
- {228, nullptr, "AcquireOperationEventHandle"},
- {229, nullptr, "ReadSerialFlash"},
- {230, nullptr, "WriteSerialFlash"},
- {231, nullptr, "GetOperationResult"},
- {232, nullptr, "EnableShipmentMode"},
- {233, nullptr, "ClearPairingInfo"},
- {234, nullptr, "GetUniquePadDeviceTypeSetInternal"},
- {235, nullptr, "EnableAnalogStickPower"},
- {236, nullptr, "RequestKuinaUartClockCal"},
- {237, nullptr, "GetKuinaUartClockCal"},
- {238, nullptr, "SetKuinaUartClockTrim"},
- {239, nullptr, "KuinaLoopbackTest"},
- {240, nullptr, "RequestBatteryVoltage"},
- {241, nullptr, "GetBatteryVoltage"},
- {242, nullptr, "GetUniquePadPowerInfo"},
- {243, nullptr, "RebootUniquePad"},
- {244, nullptr, "RequestKuinaFirmwareVersion"},
- {245, nullptr, "GetKuinaFirmwareVersion"},
- {246, nullptr, "GetVidPid"},
- {247, nullptr, "GetAnalogStickCalibrationValue"},
- {248, nullptr, "GetUniquePadIdsFull"},
- {249, nullptr, "ConnectUniquePad"},
- {250, nullptr, "IsVirtual"},
- {251, nullptr, "GetAnalogStickModuleParam"},
- {301, nullptr, "GetAbstractedPadHandles"},
- {302, nullptr, "GetAbstractedPadState"},
- {303, nullptr, "GetAbstractedPadsState"},
- {321, nullptr, "SetAutoPilotVirtualPadState"},
- {322, nullptr, "UnsetAutoPilotVirtualPadState"},
- {323, nullptr, "UnsetAllAutoPilotVirtualPadState"},
- {324, nullptr, "AttachHdlsWorkBuffer"},
- {325, nullptr, "ReleaseHdlsWorkBuffer"},
- {326, nullptr, "DumpHdlsNpadAssignmentState"},
- {327, nullptr, "DumpHdlsStates"},
- {328, nullptr, "ApplyHdlsNpadAssignmentState"},
- {329, nullptr, "ApplyHdlsStateList"},
- {330, nullptr, "AttachHdlsVirtualDevice"},
- {331, nullptr, "DetachHdlsVirtualDevice"},
- {332, nullptr, "SetHdlsState"},
- {350, nullptr, "AddRegisteredDevice"},
- {400, nullptr, "DisableExternalMcuOnNxDevice"},
- {401, nullptr, "DisableRailDeviceFiltering"},
- {402, nullptr, "EnableWiredPairing"},
- {403, nullptr, "EnableShipmentModeAutoClear"},
- {404, nullptr, "SetRailEnabled"},
- {500, nullptr, "SetFactoryInt"},
- {501, nullptr, "IsFactoryBootEnabled"},
- {550, nullptr, "SetAnalogStickModelDataTemporarily"},
- {551, nullptr, "GetAnalogStickModelData"},
- {552, nullptr, "ResetAnalogStickModelData"},
- {600, nullptr, "ConvertPadState"},
- {650, nullptr, "AddButtonPlayData"},
- {651, nullptr, "StartButtonPlayData"},
- {652, nullptr, "StopButtonPlayData"},
- {2000, nullptr, "DeactivateDigitizer"},
- {2001, nullptr, "SetDigitizerAutoPilotState"},
- {2002, nullptr, "UnsetDigitizerAutoPilotState"},
- {2002, nullptr, "ReloadFirmwareDebugSettings"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-class HidSys final : public ServiceFramework<HidSys> {
-public:
- 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"},
- {101, nullptr, "AcquireHomeButtonEventHandle"},
- {111, nullptr, "ActivateHomeButton"},
- {121, nullptr, "AcquireSleepButtonEventHandle"},
- {131, nullptr, "ActivateSleepButton"},
- {141, nullptr, "AcquireCaptureButtonEventHandle"},
- {151, nullptr, "ActivateCaptureButton"},
- {161, nullptr, "GetPlatformConfig"},
- {210, nullptr, "AcquireNfcDeviceUpdateEventHandle"},
- {211, nullptr, "GetNpadsWithNfc"},
- {212, nullptr, "AcquireNfcActivateEventHandle"},
- {213, nullptr, "ActivateNfc"},
- {214, nullptr, "GetXcdHandleForNpadWithNfc"},
- {215, nullptr, "IsNfcActivated"},
- {230, nullptr, "AcquireIrSensorEventHandle"},
- {231, nullptr, "ActivateIrSensor"},
- {232, nullptr, "GetIrSensorState"},
- {233, nullptr, "GetXcdHandleForNpadWithIrSensor"},
- {301, nullptr, "ActivateNpadSystem"},
- {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"},
- {304, nullptr, "EnableAssigningSingleOnSlSrPress"},
- {305, nullptr, "DisableAssigningSingleOnSlSrPress"},
- {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"},
- {307, nullptr, "GetNpadSystemExtStyle"},
- {308, nullptr, "ApplyNpadSystemCommonPolicyFull"},
- {309, nullptr, "GetNpadFullKeyGripColor"},
- {310, nullptr, "GetMaskedSupportedNpadStyleSet"},
- {311, nullptr, "SetNpadPlayerLedBlinkingDevice"},
- {312, nullptr, "SetSupportedNpadStyleSetAll"},
- {313, nullptr, "GetNpadCaptureButtonAssignment"},
- {314, nullptr, "GetAppletFooterUiType"},
- {315, nullptr, "GetAppletDetailedUiType"},
- {316, nullptr, "GetNpadInterfaceType"},
- {317, nullptr, "GetNpadLeftRightInterfaceType"},
- {318, nullptr, "HasBattery"},
- {319, nullptr, "HasLeftRightBattery"},
- {321, &HidSys::GetUniquePadsFromNpad, "GetUniquePadsFromNpad"},
- {322, nullptr, "GetIrSensorState"},
- {323, nullptr, "GetXcdHandleForNpadWithIrSensor"},
- {324, nullptr, "GetUniquePadButtonSet"},
- {325, nullptr, "GetUniquePadColor"},
- {326, nullptr, "GetUniquePadAppletDetailedUiType"},
- {327, nullptr, "GetAbstractedPadIdDataFromNpad"},
- {328, nullptr, "AttachAbstractedPadToNpad"},
- {329, nullptr, "DetachAbstractedPadAll"},
- {330, nullptr, "CheckAbstractedPadConnection"},
- {500, nullptr, "SetAppletResourceUserId"},
- {501, nullptr, "RegisterAppletResourceUserId"},
- {502, nullptr, "UnregisterAppletResourceUserId"},
- {503, nullptr, "EnableAppletToGetInput"},
- {504, nullptr, "SetAruidValidForVibration"},
- {505, nullptr, "EnableAppletToGetSixAxisSensor"},
- {506, nullptr, "EnableAppletToGetPadInput"},
- {507, nullptr, "EnableAppletToGetTouchScreen"},
- {510, nullptr, "SetVibrationMasterVolume"},
- {511, nullptr, "GetVibrationMasterVolume"},
- {512, nullptr, "BeginPermitVibrationSession"},
- {513, nullptr, "EndPermitVibrationSession"},
- {514, nullptr, "Unknown514"},
- {520, nullptr, "EnableHandheldHids"},
- {521, nullptr, "DisableHandheldHids"},
- {522, nullptr, "SetJoyConRailEnabled"},
- {523, nullptr, "IsJoyConRailEnabled"},
- {524, nullptr, "IsHandheldHidsEnabled"},
- {525, nullptr, "IsJoyConAttachedOnAllRail"},
- {540, nullptr, "AcquirePlayReportControllerUsageUpdateEvent"},
- {541, nullptr, "GetPlayReportControllerUsages"},
- {542, nullptr, "AcquirePlayReportRegisteredDeviceUpdateEvent"},
- {543, nullptr, "GetRegisteredDevicesOld"},
- {544, nullptr, "AcquireConnectionTriggerTimeoutEvent"},
- {545, nullptr, "SendConnectionTrigger"},
- {546, nullptr, "AcquireDeviceRegisteredEventForControllerSupport"},
- {547, nullptr, "GetAllowedBluetoothLinksCount"},
- {548, nullptr, "GetRegisteredDevices"},
- {549, nullptr, "GetConnectableRegisteredDevices"},
- {700, nullptr, "ActivateUniquePad"},
- {702, nullptr, "AcquireUniquePadConnectionEventHandle"},
- {703, nullptr, "GetUniquePadIds"},
- {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"},
- {800, nullptr, "ListSixAxisSensorHandles"},
- {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"},
- {802, nullptr, "ResetSixAxisSensorCalibrationValues"},
- {803, nullptr, "StartSixAxisSensorUserCalibration"},
- {804, nullptr, "CancelSixAxisSensorUserCalibration"},
- {805, nullptr, "GetUniquePadBluetoothAddress"},
- {806, nullptr, "DisconnectUniquePad"},
- {807, nullptr, "GetUniquePadType"},
- {808, nullptr, "GetUniquePadInterface"},
- {809, nullptr, "GetUniquePadSerialNumber"},
- {810, nullptr, "GetUniquePadControllerNumber"},
- {811, nullptr, "GetSixAxisSensorUserCalibrationStage"},
- {812, nullptr, "GetConsoleUniqueSixAxisSensorHandle"},
- {821, nullptr, "StartAnalogStickManualCalibration"},
- {822, nullptr, "RetryCurrentAnalogStickManualCalibrationStage"},
- {823, nullptr, "CancelAnalogStickManualCalibration"},
- {824, nullptr, "ResetAnalogStickManualCalibration"},
- {825, nullptr, "GetAnalogStickState"},
- {826, nullptr, "GetAnalogStickManualCalibrationStage"},
- {827, nullptr, "IsAnalogStickButtonPressed"},
- {828, nullptr, "IsAnalogStickInReleasePosition"},
- {829, nullptr, "IsAnalogStickInCircumference"},
- {830, nullptr, "SetNotificationLedPattern"},
- {831, nullptr, "SetNotificationLedPatternWithTimeout"},
- {832, nullptr, "PrepareHidsForNotificationWake"},
- {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
- {851, nullptr, "EnableUsbFullKeyController"},
- {852, nullptr, "IsUsbConnected"},
- {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"},
- {900, nullptr, "ActivateInputDetector"},
- {901, nullptr, "NotifyInputDetector"},
- {1000, nullptr, "InitializeFirmwareUpdate"},
- {1001, nullptr, "GetFirmwareVersion"},
- {1002, nullptr, "GetAvailableFirmwareVersion"},
- {1003, nullptr, "IsFirmwareUpdateAvailable"},
- {1004, nullptr, "CheckFirmwareUpdateRequired"},
- {1005, nullptr, "StartFirmwareUpdate"},
- {1006, nullptr, "AbortFirmwareUpdate"},
- {1007, nullptr, "GetFirmwareUpdateState"},
- {1008, nullptr, "ActivateAudioControl"},
- {1009, nullptr, "AcquireAudioControlEventHandle"},
- {1010, nullptr, "GetAudioControlStates"},
- {1011, nullptr, "DeactivateAudioControl"},
- {1050, nullptr, "IsSixAxisSensorAccurateUserCalibrationSupported"},
- {1051, nullptr, "StartSixAxisSensorAccurateUserCalibration"},
- {1052, nullptr, "CancelSixAxisSensorAccurateUserCalibration"},
- {1053, nullptr, "GetSixAxisSensorAccurateUserCalibrationState"},
- {1100, nullptr, "GetHidbusSystemServiceObject"},
- {1120, nullptr, "SetFirmwareHotfixUpdateSkipEnabled"},
- {1130, nullptr, "InitializeUsbFirmwareUpdate"},
- {1131, nullptr, "FinalizeUsbFirmwareUpdate"},
- {1132, nullptr, "CheckUsbFirmwareUpdateRequired"},
- {1133, nullptr, "StartUsbFirmwareUpdate"},
- {1134, nullptr, "GetUsbFirmwareUpdateState"},
- {1150, nullptr, "SetTouchScreenMagnification"},
- {1151, nullptr, "GetTouchScreenFirmwareVersion"},
- {1152, nullptr, "SetTouchScreenDefaultConfiguration"},
- {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"},
- {1154, nullptr, "IsFirmwareAvailableForNotification"},
- {1155, nullptr, "SetForceHandheldStyleVibration"},
- {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"},
- {1157, nullptr, "CancelConnectionTrigger"},
- {1200, nullptr, "IsButtonConfigSupported"},
- {1201, nullptr, "IsButtonConfigEmbeddedSupported"},
- {1202, nullptr, "DeleteButtonConfig"},
- {1203, nullptr, "DeleteButtonConfigEmbedded"},
- {1204, nullptr, "SetButtonConfigEnabled"},
- {1205, nullptr, "SetButtonConfigEmbeddedEnabled"},
- {1206, nullptr, "IsButtonConfigEnabled"},
- {1207, nullptr, "IsButtonConfigEmbeddedEnabled"},
- {1208, nullptr, "SetButtonConfigEmbedded"},
- {1209, nullptr, "SetButtonConfigFull"},
- {1210, nullptr, "SetButtonConfigLeft"},
- {1211, nullptr, "SetButtonConfigRight"},
- {1212, nullptr, "GetButtonConfigEmbedded"},
- {1213, nullptr, "GetButtonConfigFull"},
- {1214, nullptr, "GetButtonConfigLeft"},
- {1215, nullptr, "GetButtonConfigRight"},
- {1250, nullptr, "IsCustomButtonConfigSupported"},
- {1251, nullptr, "IsDefaultButtonConfigEmbedded"},
- {1252, nullptr, "IsDefaultButtonConfigFull"},
- {1253, nullptr, "IsDefaultButtonConfigLeft"},
- {1254, nullptr, "IsDefaultButtonConfigRight"},
- {1255, nullptr, "IsButtonConfigStorageEmbeddedEmpty"},
- {1256, nullptr, "IsButtonConfigStorageFullEmpty"},
- {1257, nullptr, "IsButtonConfigStorageLeftEmpty"},
- {1258, nullptr, "IsButtonConfigStorageRightEmpty"},
- {1259, nullptr, "GetButtonConfigStorageEmbeddedDeprecated"},
- {1260, nullptr, "GetButtonConfigStorageFullDeprecated"},
- {1261, nullptr, "GetButtonConfigStorageLeftDeprecated"},
- {1262, nullptr, "GetButtonConfigStorageRightDeprecated"},
- {1263, nullptr, "SetButtonConfigStorageEmbeddedDeprecated"},
- {1264, nullptr, "SetButtonConfigStorageFullDeprecated"},
- {1265, nullptr, "SetButtonConfigStorageLeftDeprecated"},
- {1266, nullptr, "SetButtonConfigStorageRightDeprecated"},
- {1267, nullptr, "DeleteButtonConfigStorageEmbedded"},
- {1268, nullptr, "DeleteButtonConfigStorageFull"},
- {1269, nullptr, "DeleteButtonConfigStorageLeft"},
- {1270, nullptr, "DeleteButtonConfigStorageRight"},
- {1271, nullptr, "IsUsingCustomButtonConfig"},
- {1272, nullptr, "IsAnyCustomButtonConfigEnabled"},
- {1273, nullptr, "SetAllCustomButtonConfigEnabled"},
- {1274, nullptr, "SetDefaultButtonConfig"},
- {1275, nullptr, "SetAllDefaultButtonConfig"},
- {1276, nullptr, "SetHidButtonConfigEmbedded"},
- {1277, nullptr, "SetHidButtonConfigFull"},
- {1278, nullptr, "SetHidButtonConfigLeft"},
- {1279, nullptr, "SetHidButtonConfigRight"},
- {1280, nullptr, "GetHidButtonConfigEmbedded"},
- {1281, nullptr, "GetHidButtonConfigFull"},
- {1282, nullptr, "GetHidButtonConfigLeft"},
- {1283, nullptr, "GetHidButtonConfigRight"},
- {1284, nullptr, "GetButtonConfigStorageEmbedded"},
- {1285, nullptr, "GetButtonConfigStorageFull"},
- {1286, nullptr, "GetButtonConfigStorageLeft"},
- {1287, nullptr, "GetButtonConfigStorageRight"},
- {1288, nullptr, "SetButtonConfigStorageEmbedded"},
- {1289, nullptr, "SetButtonConfigStorageFull"},
- {1290, nullptr, "DeleteButtonConfigStorageRight"},
- {1291, nullptr, "DeleteButtonConfigStorageRight"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
-
- joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent");
- }
-
-private:
- void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "called");
-
- GetAppletResource()
- ->GetController<Controller_NPad>(HidController::NPad)
- .ApplyNpadSystemCommonPolicy();
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
- }
-
- void GetLastActiveNpad(HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(system.HIDCore().GetLastActiveController());
- }
-
- void GetUniquePadsFromNpad(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
-
- LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type);
-
- const std::vector<Core::HID::UniquePadId> unique_pads{};
-
- ctx.WriteBuffer(unique_pads);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(unique_pads.size()));
- }
-
- void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) {
- LOG_INFO(Service_AM, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(joy_detach_event->GetReadableEvent());
- }
-
- void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
- const bool is_enabled = false;
-
- LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(is_enabled);
- }
-
- void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
-
- Core::HID::TouchScreenConfigurationForNx touchscreen_config{
- .mode = Core::HID::TouchScreenModeForNx::Finger,
- };
-
- if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 &&
- touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) {
- touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting;
- }
-
- IPC::ResponseBuilder rb{ctx, 6};
- rb.Push(ResultSuccess);
- rb.PushRaw(touchscreen_config);
- }
-
- std::shared_ptr<IAppletResource> GetAppletResource() {
- if (applet_resource == nullptr) {
- applet_resource = std::make_shared<IAppletResource>(system, service_context);
- }
-
- return applet_resource;
- }
-
- Kernel::KEvent* joy_detach_event;
- KernelHelpers::ServiceContext service_context;
- std::shared_ptr<IAppletResource> applet_resource;
-};
-
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- std::shared_ptr<IAppletResource> applet_resource;
+ std::shared_ptr<ResourceManager> resouce_manager = std::make_shared<ResourceManager>(system);
+ std::shared_ptr<HidFirmwareSettings> firmware_settings =
+ std::make_shared<HidFirmwareSettings>();
+
+ server_manager->RegisterNamedService(
+ "hid", std::make_shared<IHidServer>(system, resouce_manager, firmware_settings));
+ server_manager->RegisterNamedService(
+ "hid:dbg", std::make_shared<IHidDebugServer>(system, resouce_manager));
+ server_manager->RegisterNamedService(
+ "hid:sys", std::make_shared<IHidSystemServer>(system, resouce_manager));
- 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, applet_resource));
- server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system));
- server_manager->RegisterNamedService("irs:sys",
- std::make_shared<Service::IRS::IRS_SYS>(system));
+ server_manager->RegisterNamedService("irs", std::make_shared<IRS::IRS>(system));
+ server_manager->RegisterNamedService("irs:sys", std::make_shared<IRS::IRS_SYS>(system));
server_manager->RegisterNamedService("xcd:sys", std::make_shared<XCD_SYS>(system));
+
system.RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 0ca43de93..ec5463f4e 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -3,220 +3,12 @@
#pragma once
-#include <chrono>
-
-#include "core/hle/service/hid/controllers/controller_base.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/service.h"
-
-namespace Core::Timing {
-struct EventType;
-}
-
-namespace Service::SM {
-class ServiceManager;
+namespace Core {
+class System;
}
namespace Service::HID {
-enum class HidController : std::size_t {
- DebugPad,
- Touchscreen,
- Mouse,
- Keyboard,
- XPad,
- HomeButton,
- SleepButton,
- CaptureButton,
- InputDetector,
- UniquePad,
- NPad,
- Gesture,
- ConsoleSixAxisSensor,
- DebugMouse,
- Palma,
-
- MaxControllers,
-};
-
-class IAppletResource final : public ServiceFramework<IAppletResource> {
-public:
- explicit IAppletResource(Core::System& system_,
- KernelHelpers::ServiceContext& service_context_);
- ~IAppletResource() override;
-
- void ActivateController(HidController controller);
- void DeactivateController(HidController controller);
-
- template <typename T>
- T& GetController(HidController controller) {
- return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
- }
-
- template <typename T>
- const T& GetController(HidController controller) const {
- return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
- }
-
-private:
- template <typename T>
- void MakeController(HidController controller, u8* shared_memory) {
- if constexpr (std::is_constructible_v<T, Core::System&, u8*>) {
- controllers[static_cast<std::size_t>(controller)] =
- std::make_unique<T>(system, shared_memory);
- } else {
- controllers[static_cast<std::size_t>(controller)] =
- std::make_unique<T>(system.HIDCore(), shared_memory);
- }
- }
-
- template <typename T>
- void MakeControllerWithServiceContext(HidController controller, u8* shared_memory) {
- controllers[static_cast<std::size_t>(controller)] =
- std::make_unique<T>(system.HIDCore(), shared_memory, service_context);
- }
-
- void GetSharedMemoryHandle(HLERequestContext& ctx);
- void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
- void UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
- void UpdateMouseKeyboard(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
- void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
-
- KernelHelpers::ServiceContext& service_context;
-
- std::shared_ptr<Core::Timing::EventType> npad_update_event;
- std::shared_ptr<Core::Timing::EventType> default_update_event;
- std::shared_ptr<Core::Timing::EventType> mouse_keyboard_update_event;
- std::shared_ptr<Core::Timing::EventType> motion_update_event;
-
- std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
- controllers{};
-};
-
-class Hid final : public ServiceFramework<Hid> {
-public:
- explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_);
- ~Hid() override;
-
- std::shared_ptr<IAppletResource> GetAppletResource();
-
-private:
- void CreateAppletResource(HLERequestContext& ctx);
- void ActivateDebugPad(HLERequestContext& ctx);
- void ActivateTouchScreen(HLERequestContext& ctx);
- void ActivateMouse(HLERequestContext& ctx);
- void ActivateKeyboard(HLERequestContext& ctx);
- void SendKeyboardLockKeyEvent(HLERequestContext& ctx);
- void ActivateXpad(HLERequestContext& ctx);
- void GetXpadIDs(HLERequestContext& ctx);
- void ActivateSixAxisSensor(HLERequestContext& ctx);
- void DeactivateSixAxisSensor(HLERequestContext& ctx);
- void StartSixAxisSensor(HLERequestContext& ctx);
- void StopSixAxisSensor(HLERequestContext& ctx);
- void IsSixAxisSensorFusionEnabled(HLERequestContext& ctx);
- void EnableSixAxisSensorFusion(HLERequestContext& ctx);
- void SetSixAxisSensorFusionParameters(HLERequestContext& ctx);
- void GetSixAxisSensorFusionParameters(HLERequestContext& ctx);
- void ResetSixAxisSensorFusionParameters(HLERequestContext& ctx);
- void SetGyroscopeZeroDriftMode(HLERequestContext& ctx);
- void GetGyroscopeZeroDriftMode(HLERequestContext& ctx);
- void ResetGyroscopeZeroDriftMode(HLERequestContext& ctx);
- void IsSixAxisSensorAtRest(HLERequestContext& ctx);
- void IsFirmwareUpdateAvailableForSixAxisSensor(HLERequestContext& ctx);
- void EnableSixAxisSensorUnalteredPassthrough(HLERequestContext& ctx);
- void IsSixAxisSensorUnalteredPassthroughEnabled(HLERequestContext& ctx);
- void LoadSixAxisSensorCalibrationParameter(HLERequestContext& ctx);
- void GetSixAxisSensorIcInformation(HLERequestContext& ctx);
- void ResetIsSixAxisSensorDeviceNewlyAssigned(HLERequestContext& ctx);
- void ActivateGesture(HLERequestContext& ctx);
- void SetSupportedNpadStyleSet(HLERequestContext& ctx);
- void GetSupportedNpadStyleSet(HLERequestContext& ctx);
- void SetSupportedNpadIdType(HLERequestContext& ctx);
- void ActivateNpad(HLERequestContext& ctx);
- void DeactivateNpad(HLERequestContext& ctx);
- void AcquireNpadStyleSetUpdateEventHandle(HLERequestContext& ctx);
- void DisconnectNpad(HLERequestContext& ctx);
- void GetPlayerLedPattern(HLERequestContext& ctx);
- void ActivateNpadWithRevision(HLERequestContext& ctx);
- void SetNpadJoyHoldType(HLERequestContext& ctx);
- void GetNpadJoyHoldType(HLERequestContext& ctx);
- void SetNpadJoyAssignmentModeSingleByDefault(HLERequestContext& ctx);
- void SetNpadJoyAssignmentModeSingle(HLERequestContext& ctx);
- void SetNpadJoyAssignmentModeDual(HLERequestContext& ctx);
- void MergeSingleJoyAsDualJoy(HLERequestContext& ctx);
- void StartLrAssignmentMode(HLERequestContext& ctx);
- void StopLrAssignmentMode(HLERequestContext& ctx);
- void SetNpadHandheldActivationMode(HLERequestContext& ctx);
- void GetNpadHandheldActivationMode(HLERequestContext& ctx);
- void SwapNpadAssignment(HLERequestContext& ctx);
- void IsUnintendedHomeButtonInputProtectionEnabled(HLERequestContext& ctx);
- void EnableUnintendedHomeButtonInputProtection(HLERequestContext& ctx);
- void SetNpadJoyAssignmentModeSingleWithDestination(HLERequestContext& ctx);
- void SetNpadAnalogStickUseCenterClamp(HLERequestContext& ctx);
- void SetNpadCaptureButtonAssignment(HLERequestContext& ctx);
- void ClearNpadCaptureButtonAssignment(HLERequestContext& ctx);
- void GetVibrationDeviceInfo(HLERequestContext& ctx);
- void SendVibrationValue(HLERequestContext& ctx);
- void GetActualVibrationValue(HLERequestContext& ctx);
- void CreateActiveVibrationDeviceList(HLERequestContext& ctx);
- void PermitVibration(HLERequestContext& ctx);
- void IsVibrationPermitted(HLERequestContext& ctx);
- void SendVibrationValues(HLERequestContext& ctx);
- void SendVibrationGcErmCommand(HLERequestContext& ctx);
- void GetActualVibrationGcErmCommand(HLERequestContext& ctx);
- void BeginPermitVibrationSession(HLERequestContext& ctx);
- void EndPermitVibrationSession(HLERequestContext& ctx);
- void IsVibrationDeviceMounted(HLERequestContext& ctx);
- void ActivateConsoleSixAxisSensor(HLERequestContext& ctx);
- void StartConsoleSixAxisSensor(HLERequestContext& ctx);
- void StopConsoleSixAxisSensor(HLERequestContext& ctx);
- void ActivateSevenSixAxisSensor(HLERequestContext& ctx);
- void StartSevenSixAxisSensor(HLERequestContext& ctx);
- void StopSevenSixAxisSensor(HLERequestContext& ctx);
- void InitializeSevenSixAxisSensor(HLERequestContext& ctx);
- void FinalizeSevenSixAxisSensor(HLERequestContext& ctx);
- void ResetSevenSixAxisSensorTimestamp(HLERequestContext& ctx);
- void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx);
- void GetPalmaConnectionHandle(HLERequestContext& ctx);
- void InitializePalma(HLERequestContext& ctx);
- void AcquirePalmaOperationCompleteEvent(HLERequestContext& ctx);
- void GetPalmaOperationInfo(HLERequestContext& ctx);
- void PlayPalmaActivity(HLERequestContext& ctx);
- void SetPalmaFrModeType(HLERequestContext& ctx);
- void ReadPalmaStep(HLERequestContext& ctx);
- void EnablePalmaStep(HLERequestContext& ctx);
- void ResetPalmaStep(HLERequestContext& ctx);
- void ReadPalmaApplicationSection(HLERequestContext& ctx);
- void WritePalmaApplicationSection(HLERequestContext& ctx);
- void ReadPalmaUniqueCode(HLERequestContext& ctx);
- void SetPalmaUniqueCodeInvalid(HLERequestContext& ctx);
- void WritePalmaActivityEntry(HLERequestContext& ctx);
- void WritePalmaRgbLedPatternEntry(HLERequestContext& ctx);
- void WritePalmaWaveEntry(HLERequestContext& ctx);
- void SetPalmaDataBaseIdentificationVersion(HLERequestContext& ctx);
- void GetPalmaDataBaseIdentificationVersion(HLERequestContext& ctx);
- void SuspendPalmaFeature(HLERequestContext& ctx);
- void GetPalmaOperationResult(HLERequestContext& ctx);
- void ReadPalmaPlayLog(HLERequestContext& ctx);
- void ResetPalmaPlayLog(HLERequestContext& ctx);
- void SetIsPalmaAllConnectable(HLERequestContext& ctx);
- void SetIsPalmaPairedConnectable(HLERequestContext& ctx);
- void PairPalma(HLERequestContext& ctx);
- void SetPalmaBoostMode(HLERequestContext& ctx);
- void CancelWritePalmaWaveEntry(HLERequestContext& ctx);
- void EnablePalmaBoostMode(HLERequestContext& ctx);
- void GetPalmaBluetoothAddress(HLERequestContext& ctx);
- void SetDisallowedPalmaConnection(HLERequestContext& ctx);
- void SetNpadCommunicationMode(HLERequestContext& ctx);
- void GetNpadCommunicationMode(HLERequestContext& ctx);
- void SetTouchScreenConfiguration(HLERequestContext& ctx);
- void IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx);
-
- std::shared_ptr<IAppletResource> applet_resource;
-
- KernelHelpers::ServiceContext service_context;
-};
-
void LoopProcess(Core::System& system);
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_debug_server.cpp b/src/core/hle/service/hid/hid_debug_server.cpp
new file mode 100644
index 000000000..6294f3dfb
--- /dev/null
+++ b/src/core/hle/service/hid/hid_debug_server.cpp
@@ -0,0 +1,159 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/hid_debug_server.h"
+#include "core/hle/service/hid/resource_manager.h"
+#include "core/hle/service/ipc_helpers.h"
+
+namespace Service::HID {
+
+IHidDebugServer::IHidDebugServer(Core::System& system_, std::shared_ptr<ResourceManager> resource)
+ : ServiceFramework{system_, "hid:dbg"}, resource_manager{resource} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "DeactivateDebugPad"},
+ {1, nullptr, "SetDebugPadAutoPilotState"},
+ {2, nullptr, "UnsetDebugPadAutoPilotState"},
+ {10, nullptr, "DeactivateTouchScreen"},
+ {11, nullptr, "SetTouchScreenAutoPilotState"},
+ {12, nullptr, "UnsetTouchScreenAutoPilotState"},
+ {13, nullptr, "GetTouchScreenConfiguration"},
+ {14, nullptr, "ProcessTouchScreenAutoTune"},
+ {15, nullptr, "ForceStopTouchScreenManagement"},
+ {16, nullptr, "ForceRestartTouchScreenManagement"},
+ {17, nullptr, "IsTouchScreenManaged"},
+ {20, nullptr, "DeactivateMouse"},
+ {21, nullptr, "SetMouseAutoPilotState"},
+ {22, nullptr, "UnsetMouseAutoPilotState"},
+ {25, nullptr, "SetDebugMouseAutoPilotState"},
+ {26, nullptr, "UnsetDebugMouseAutoPilotState"},
+ {30, nullptr, "DeactivateKeyboard"},
+ {31, nullptr, "SetKeyboardAutoPilotState"},
+ {32, nullptr, "UnsetKeyboardAutoPilotState"},
+ {50, nullptr, "DeactivateXpad"},
+ {51, nullptr, "SetXpadAutoPilotState"},
+ {52, nullptr, "UnsetXpadAutoPilotState"},
+ {53, nullptr, "DeactivateJoyXpad"},
+ {60, nullptr, "ClearNpadSystemCommonPolicy"},
+ {61, nullptr, "DeactivateNpad"},
+ {62, nullptr, "ForceDisconnectNpad"},
+ {91, nullptr, "DeactivateGesture"},
+ {110, nullptr, "DeactivateHomeButton"},
+ {111, nullptr, "SetHomeButtonAutoPilotState"},
+ {112, nullptr, "UnsetHomeButtonAutoPilotState"},
+ {120, nullptr, "DeactivateSleepButton"},
+ {121, nullptr, "SetSleepButtonAutoPilotState"},
+ {122, nullptr, "UnsetSleepButtonAutoPilotState"},
+ {123, nullptr, "DeactivateInputDetector"},
+ {130, nullptr, "DeactivateCaptureButton"},
+ {131, nullptr, "SetCaptureButtonAutoPilotState"},
+ {132, nullptr, "UnsetCaptureButtonAutoPilotState"},
+ {133, nullptr, "SetShiftAccelerometerCalibrationValue"},
+ {134, nullptr, "GetShiftAccelerometerCalibrationValue"},
+ {135, nullptr, "SetShiftGyroscopeCalibrationValue"},
+ {136, nullptr, "GetShiftGyroscopeCalibrationValue"},
+ {140, nullptr, "DeactivateConsoleSixAxisSensor"},
+ {141, nullptr, "GetConsoleSixAxisSensorSamplingFrequency"},
+ {142, nullptr, "DeactivateSevenSixAxisSensor"},
+ {143, nullptr, "GetConsoleSixAxisSensorCountStates"},
+ {144, nullptr, "GetAccelerometerFsr"},
+ {145, nullptr, "SetAccelerometerFsr"},
+ {146, nullptr, "GetAccelerometerOdr"},
+ {147, nullptr, "SetAccelerometerOdr"},
+ {148, nullptr, "GetGyroscopeFsr"},
+ {149, nullptr, "SetGyroscopeFsr"},
+ {150, nullptr, "GetGyroscopeOdr"},
+ {151, nullptr, "SetGyroscopeOdr"},
+ {152, nullptr, "GetWhoAmI"},
+ {201, nullptr, "ActivateFirmwareUpdate"},
+ {202, nullptr, "DeactivateFirmwareUpdate"},
+ {203, nullptr, "StartFirmwareUpdate"},
+ {204, nullptr, "GetFirmwareUpdateStage"},
+ {205, nullptr, "GetFirmwareVersion"},
+ {206, nullptr, "GetDestinationFirmwareVersion"},
+ {207, nullptr, "DiscardFirmwareInfoCacheForRevert"},
+ {208, nullptr, "StartFirmwareUpdateForRevert"},
+ {209, nullptr, "GetAvailableFirmwareVersionForRevert"},
+ {210, nullptr, "IsFirmwareUpdatingDevice"},
+ {211, nullptr, "StartFirmwareUpdateIndividual"},
+ {215, nullptr, "SetUsbFirmwareForceUpdateEnabled"},
+ {216, nullptr, "SetAllKuinaDevicesToFirmwareUpdateMode"},
+ {221, nullptr, "UpdateControllerColor"},
+ {222, nullptr, "ConnectUsbPadsAsync"},
+ {223, nullptr, "DisconnectUsbPadsAsync"},
+ {224, nullptr, "UpdateDesignInfo"},
+ {225, nullptr, "GetUniquePadDriverState"},
+ {226, nullptr, "GetSixAxisSensorDriverStates"},
+ {227, nullptr, "GetRxPacketHistory"},
+ {228, nullptr, "AcquireOperationEventHandle"},
+ {229, nullptr, "ReadSerialFlash"},
+ {230, nullptr, "WriteSerialFlash"},
+ {231, nullptr, "GetOperationResult"},
+ {232, nullptr, "EnableShipmentMode"},
+ {233, nullptr, "ClearPairingInfo"},
+ {234, nullptr, "GetUniquePadDeviceTypeSetInternal"},
+ {235, nullptr, "EnableAnalogStickPower"},
+ {236, nullptr, "RequestKuinaUartClockCal"},
+ {237, nullptr, "GetKuinaUartClockCal"},
+ {238, nullptr, "SetKuinaUartClockTrim"},
+ {239, nullptr, "KuinaLoopbackTest"},
+ {240, nullptr, "RequestBatteryVoltage"},
+ {241, nullptr, "GetBatteryVoltage"},
+ {242, nullptr, "GetUniquePadPowerInfo"},
+ {243, nullptr, "RebootUniquePad"},
+ {244, nullptr, "RequestKuinaFirmwareVersion"},
+ {245, nullptr, "GetKuinaFirmwareVersion"},
+ {246, nullptr, "GetVidPid"},
+ {247, nullptr, "GetAnalogStickCalibrationValue"},
+ {248, nullptr, "GetUniquePadIdsFull"},
+ {249, nullptr, "ConnectUniquePad"},
+ {250, nullptr, "IsVirtual"},
+ {251, nullptr, "GetAnalogStickModuleParam"},
+ {301, nullptr, "GetAbstractedPadHandles"},
+ {302, nullptr, "GetAbstractedPadState"},
+ {303, nullptr, "GetAbstractedPadsState"},
+ {321, nullptr, "SetAutoPilotVirtualPadState"},
+ {322, nullptr, "UnsetAutoPilotVirtualPadState"},
+ {323, nullptr, "UnsetAllAutoPilotVirtualPadState"},
+ {324, nullptr, "AttachHdlsWorkBuffer"},
+ {325, nullptr, "ReleaseHdlsWorkBuffer"},
+ {326, nullptr, "DumpHdlsNpadAssignmentState"},
+ {327, nullptr, "DumpHdlsStates"},
+ {328, nullptr, "ApplyHdlsNpadAssignmentState"},
+ {329, nullptr, "ApplyHdlsStateList"},
+ {330, nullptr, "AttachHdlsVirtualDevice"},
+ {331, nullptr, "DetachHdlsVirtualDevice"},
+ {332, nullptr, "SetHdlsState"},
+ {350, nullptr, "AddRegisteredDevice"},
+ {400, nullptr, "DisableExternalMcuOnNxDevice"},
+ {401, nullptr, "DisableRailDeviceFiltering"},
+ {402, nullptr, "EnableWiredPairing"},
+ {403, nullptr, "EnableShipmentModeAutoClear"},
+ {404, nullptr, "SetRailEnabled"},
+ {500, nullptr, "SetFactoryInt"},
+ {501, nullptr, "IsFactoryBootEnabled"},
+ {550, nullptr, "SetAnalogStickModelDataTemporarily"},
+ {551, nullptr, "GetAnalogStickModelData"},
+ {552, nullptr, "ResetAnalogStickModelData"},
+ {600, nullptr, "ConvertPadState"},
+ {650, nullptr, "AddButtonPlayData"},
+ {651, nullptr, "StartButtonPlayData"},
+ {652, nullptr, "StopButtonPlayData"},
+ {2000, nullptr, "DeactivateDigitizer"},
+ {2001, nullptr, "SetDigitizerAutoPilotState"},
+ {2002, nullptr, "UnsetDigitizerAutoPilotState"},
+ {2002, nullptr, "ReloadFirmwareDebugSettings"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IHidDebugServer::~IHidDebugServer() = default;
+
+std::shared_ptr<ResourceManager> IHidDebugServer::GetResourceManager() {
+ resource_manager->Initialize();
+ return resource_manager;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_debug_server.h b/src/core/hle/service/hid/hid_debug_server.h
new file mode 100644
index 000000000..406db2211
--- /dev/null
+++ b/src/core/hle/service/hid/hid_debug_server.h
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::HID {
+class ResourceManager;
+
+class IHidDebugServer final : public ServiceFramework<IHidDebugServer> {
+public:
+ explicit IHidDebugServer(Core::System& system_, std::shared_ptr<ResourceManager> resource);
+ ~IHidDebugServer() override;
+
+private:
+ std::shared_ptr<ResourceManager> GetResourceManager();
+
+ std::shared_ptr<ResourceManager> resource_manager;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_firmware_settings.cpp b/src/core/hle/service/hid/hid_firmware_settings.cpp
new file mode 100644
index 000000000..59bd6825c
--- /dev/null
+++ b/src/core/hle/service/hid/hid_firmware_settings.cpp
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/hid_firmware_settings.h"
+
+namespace Service::HID {
+
+HidFirmwareSettings::HidFirmwareSettings() {
+ LoadSettings(true);
+}
+
+void HidFirmwareSettings::Reload() {
+ LoadSettings(true);
+}
+
+void HidFirmwareSettings::LoadSettings(bool reload_config) {
+ if (is_initalized && !reload_config) {
+ return;
+ }
+
+ // TODO: Use nn::settings::fwdbg::GetSettingsItemValue to load config values
+
+ is_debug_pad_enabled = true;
+ is_device_managed = true;
+ is_touch_i2c_managed = is_device_managed;
+ is_future_devices_emulated = false;
+ is_mcu_hardware_error_emulated = false;
+ is_rail_enabled = true;
+ is_firmware_update_failure_emulated = false;
+ is_firmware_update_failure = {};
+ is_ble_disabled = false;
+ is_dscale_disabled = false;
+ is_handheld_forced = true;
+ features_per_id_disabled = {};
+ is_touch_firmware_auto_update_disabled = false;
+ is_initalized = true;
+}
+
+bool HidFirmwareSettings::IsDebugPadEnabled() {
+ LoadSettings(false);
+ return is_debug_pad_enabled;
+}
+
+bool HidFirmwareSettings::IsDeviceManaged() {
+ LoadSettings(false);
+ return is_device_managed;
+}
+
+bool HidFirmwareSettings::IsEmulateFutureDevice() {
+ LoadSettings(false);
+ return is_future_devices_emulated;
+}
+
+bool HidFirmwareSettings::IsTouchI2cManaged() {
+ LoadSettings(false);
+ return is_touch_i2c_managed;
+}
+
+bool HidFirmwareSettings::IsHandheldForced() {
+ LoadSettings(false);
+ return is_handheld_forced;
+}
+
+bool HidFirmwareSettings::IsRailEnabled() {
+ LoadSettings(false);
+ return is_rail_enabled;
+}
+
+bool HidFirmwareSettings::IsHardwareErrorEmulated() {
+ LoadSettings(false);
+ return is_mcu_hardware_error_emulated;
+}
+
+bool HidFirmwareSettings::IsBleDisabled() {
+ LoadSettings(false);
+ return is_ble_disabled;
+}
+
+bool HidFirmwareSettings::IsDscaleDisabled() {
+ LoadSettings(false);
+ return is_dscale_disabled;
+}
+
+bool HidFirmwareSettings::IsTouchAutoUpdateDisabled() {
+ LoadSettings(false);
+ return is_touch_firmware_auto_update_disabled;
+}
+
+HidFirmwareSettings::FirmwareSetting HidFirmwareSettings::GetFirmwareUpdateFailure() {
+ LoadSettings(false);
+ return is_firmware_update_failure;
+}
+
+HidFirmwareSettings::FeaturesPerId HidFirmwareSettings::FeaturesDisabledPerId() {
+ LoadSettings(false);
+ return features_per_id_disabled;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_firmware_settings.h b/src/core/hle/service/hid/hid_firmware_settings.h
new file mode 100644
index 000000000..6c10c440b
--- /dev/null
+++ b/src/core/hle/service/hid/hid_firmware_settings.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Service::HID {
+
+/// Loads firmware config from nn::settings::fwdbg
+class HidFirmwareSettings {
+public:
+ using FirmwareSetting = std::array<u8, 4>;
+ using FeaturesPerId = std::array<bool, 0xA8>;
+
+ HidFirmwareSettings();
+
+ void Reload();
+ void LoadSettings(bool reload_config);
+
+ bool IsDebugPadEnabled();
+ bool IsDeviceManaged();
+ bool IsEmulateFutureDevice();
+ bool IsTouchI2cManaged();
+ bool IsHandheldForced();
+ bool IsRailEnabled();
+ bool IsHardwareErrorEmulated();
+ bool IsBleDisabled();
+ bool IsDscaleDisabled();
+ bool IsTouchAutoUpdateDisabled();
+
+ FirmwareSetting GetFirmwareUpdateFailure();
+ FeaturesPerId FeaturesDisabledPerId();
+
+private:
+ bool is_initalized{};
+
+ // Debug settings
+ bool is_debug_pad_enabled{};
+ bool is_device_managed{};
+ bool is_touch_i2c_managed{};
+ bool is_future_devices_emulated{};
+ bool is_mcu_hardware_error_emulated{};
+ bool is_rail_enabled{};
+ bool is_firmware_update_failure_emulated{};
+ bool is_ble_disabled{};
+ bool is_dscale_disabled{};
+ bool is_handheld_forced{};
+ bool is_touch_firmware_auto_update_disabled{};
+ FirmwareSetting is_firmware_update_failure{};
+ FeaturesPerId features_per_id_disabled{};
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp
new file mode 100644
index 000000000..a7d1578d9
--- /dev/null
+++ b/src/core/hle/service/hid/hid_server.cpp
@@ -0,0 +1,2387 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <array>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/service/hid/errors.h"
+#include "core/hle/service/hid/hid_firmware_settings.h"
+#include "core/hle/service/hid/hid_server.h"
+#include "core/hle/service/hid/hid_util.h"
+#include "core/hle/service/hid/resource_manager.h"
+#include "core/hle/service/ipc_helpers.h"
+#include "core/memory.h"
+
+#include "core/hle/service/hid/controllers/console_six_axis.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/hid/controllers/debug_pad.h"
+#include "core/hle/service/hid/controllers/gesture.h"
+#include "core/hle/service/hid/controllers/keyboard.h"
+#include "core/hle/service/hid/controllers/mouse.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/palma.h"
+#include "core/hle/service/hid/controllers/seven_six_axis.h"
+#include "core/hle/service/hid/controllers/six_axis.h"
+#include "core/hle/service/hid/controllers/touchscreen.h"
+
+namespace Service::HID {
+
+class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
+public:
+ explicit IActiveVibrationDeviceList(Core::System& system_,
+ std::shared_ptr<ResourceManager> resource)
+ : ServiceFramework{system_, "IActiveVibrationDeviceList"}, resource_manager(resource) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void InitializeVibrationDevice(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
+
+ if (resource_manager != nullptr) {
+ resource_manager->GetNpad()->InitializeVibrationDevice(vibration_device_handle);
+ }
+
+ LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}",
+ vibration_device_handle.npad_type, vibration_device_handle.npad_id,
+ vibration_device_handle.device_index);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ std::shared_ptr<ResourceManager> resource_manager;
+};
+
+IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> resource,
+ std::shared_ptr<HidFirmwareSettings> settings)
+ : ServiceFramework{system_, "hid"}, resource_manager{resource}, firmware_settings{settings} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IHidServer::CreateAppletResource, "CreateAppletResource"},
+ {1, &IHidServer::ActivateDebugPad, "ActivateDebugPad"},
+ {11, &IHidServer::ActivateTouchScreen, "ActivateTouchScreen"},
+ {21, &IHidServer::ActivateMouse, "ActivateMouse"},
+ {26, nullptr, "ActivateDebugMouse"},
+ {31, &IHidServer::ActivateKeyboard, "ActivateKeyboard"},
+ {32, &IHidServer::SendKeyboardLockKeyEvent, "SendKeyboardLockKeyEvent"},
+ {40, &IHidServer::AcquireXpadIdEventHandle, "AcquireXpadIdEventHandle"},
+ {41, &IHidServer::ReleaseXpadIdEventHandle, "ReleaseXpadIdEventHandle"},
+ {51, &IHidServer::ActivateXpad, "ActivateXpad"},
+ {55, &IHidServer::GetXpadIds, "GetXpadIds"},
+ {56, &IHidServer::ActivateJoyXpad, "ActivateJoyXpad"},
+ {58, &IHidServer::GetJoyXpadLifoHandle, "GetJoyXpadLifoHandle"},
+ {59, &IHidServer::GetJoyXpadIds, "GetJoyXpadIds"},
+ {60, &IHidServer::ActivateSixAxisSensor, "ActivateSixAxisSensor"},
+ {61, &IHidServer::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"},
+ {62, &IHidServer::GetSixAxisSensorLifoHandle, "GetSixAxisSensorLifoHandle"},
+ {63, &IHidServer::ActivateJoySixAxisSensor, "ActivateJoySixAxisSensor"},
+ {64, &IHidServer::DeactivateJoySixAxisSensor, "DeactivateJoySixAxisSensor"},
+ {65, &IHidServer::GetJoySixAxisSensorLifoHandle, "GetJoySixAxisSensorLifoHandle"},
+ {66, &IHidServer::StartSixAxisSensor, "StartSixAxisSensor"},
+ {67, &IHidServer::StopSixAxisSensor, "StopSixAxisSensor"},
+ {68, &IHidServer::IsSixAxisSensorFusionEnabled, "IsSixAxisSensorFusionEnabled"},
+ {69, &IHidServer::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"},
+ {70, &IHidServer::SetSixAxisSensorFusionParameters, "SetSixAxisSensorFusionParameters"},
+ {71, &IHidServer::GetSixAxisSensorFusionParameters, "GetSixAxisSensorFusionParameters"},
+ {72, &IHidServer::ResetSixAxisSensorFusionParameters, "ResetSixAxisSensorFusionParameters"},
+ {73, nullptr, "SetAccelerometerParameters"},
+ {74, nullptr, "GetAccelerometerParameters"},
+ {75, nullptr, "ResetAccelerometerParameters"},
+ {76, nullptr, "SetAccelerometerPlayMode"},
+ {77, nullptr, "GetAccelerometerPlayMode"},
+ {78, nullptr, "ResetAccelerometerPlayMode"},
+ {79, &IHidServer::SetGyroscopeZeroDriftMode, "SetGyroscopeZeroDriftMode"},
+ {80, &IHidServer::GetGyroscopeZeroDriftMode, "GetGyroscopeZeroDriftMode"},
+ {81, &IHidServer::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"},
+ {82, &IHidServer::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
+ {83, &IHidServer::IsFirmwareUpdateAvailableForSixAxisSensor, "IsFirmwareUpdateAvailableForSixAxisSensor"},
+ {84, &IHidServer::EnableSixAxisSensorUnalteredPassthrough, "EnableSixAxisSensorUnalteredPassthrough"},
+ {85, &IHidServer::IsSixAxisSensorUnalteredPassthroughEnabled, "IsSixAxisSensorUnalteredPassthroughEnabled"},
+ {86, nullptr, "StoreSixAxisSensorCalibrationParameter"},
+ {87, &IHidServer::LoadSixAxisSensorCalibrationParameter, "LoadSixAxisSensorCalibrationParameter"},
+ {88, &IHidServer::GetSixAxisSensorIcInformation, "GetSixAxisSensorIcInformation"},
+ {89, &IHidServer::ResetIsSixAxisSensorDeviceNewlyAssigned, "ResetIsSixAxisSensorDeviceNewlyAssigned"},
+ {91, &IHidServer::ActivateGesture, "ActivateGesture"},
+ {100, &IHidServer::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
+ {101, &IHidServer::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
+ {102, &IHidServer::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
+ {103, &IHidServer::ActivateNpad, "ActivateNpad"},
+ {104, &IHidServer::DeactivateNpad, "DeactivateNpad"},
+ {106, &IHidServer::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"},
+ {107, &IHidServer::DisconnectNpad, "DisconnectNpad"},
+ {108, &IHidServer::GetPlayerLedPattern, "GetPlayerLedPattern"},
+ {109, &IHidServer::ActivateNpadWithRevision, "ActivateNpadWithRevision"},
+ {120, &IHidServer::SetNpadJoyHoldType, "SetNpadJoyHoldType"},
+ {121, &IHidServer::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
+ {122, &IHidServer::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"},
+ {123, &IHidServer::SetNpadJoyAssignmentModeSingle, "SetNpadJoyAssignmentModeSingle"},
+ {124, &IHidServer::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
+ {125, &IHidServer::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"},
+ {126, &IHidServer::StartLrAssignmentMode, "StartLrAssignmentMode"},
+ {127, &IHidServer::StopLrAssignmentMode, "StopLrAssignmentMode"},
+ {128, &IHidServer::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
+ {129, &IHidServer::GetNpadHandheldActivationMode, "GetNpadHandheldActivationMode"},
+ {130, &IHidServer::SwapNpadAssignment, "SwapNpadAssignment"},
+ {131, &IHidServer::IsUnintendedHomeButtonInputProtectionEnabled, "IsUnintendedHomeButtonInputProtectionEnabled"},
+ {132, &IHidServer::EnableUnintendedHomeButtonInputProtection, "EnableUnintendedHomeButtonInputProtection"},
+ {133, &IHidServer::SetNpadJoyAssignmentModeSingleWithDestination, "SetNpadJoyAssignmentModeSingleWithDestination"},
+ {134, &IHidServer::SetNpadAnalogStickUseCenterClamp, "SetNpadAnalogStickUseCenterClamp"},
+ {135, &IHidServer::SetNpadCaptureButtonAssignment, "SetNpadCaptureButtonAssignment"},
+ {136, &IHidServer::ClearNpadCaptureButtonAssignment, "ClearNpadCaptureButtonAssignment"},
+ {200, &IHidServer::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
+ {201, &IHidServer::SendVibrationValue, "SendVibrationValue"},
+ {202, &IHidServer::GetActualVibrationValue, "GetActualVibrationValue"},
+ {203, &IHidServer::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
+ {204, &IHidServer::PermitVibration, "PermitVibration"},
+ {205, &IHidServer::IsVibrationPermitted, "IsVibrationPermitted"},
+ {206, &IHidServer::SendVibrationValues, "SendVibrationValues"},
+ {207, &IHidServer::SendVibrationGcErmCommand, "SendVibrationGcErmCommand"},
+ {208, &IHidServer::GetActualVibrationGcErmCommand, "GetActualVibrationGcErmCommand"},
+ {209, &IHidServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
+ {210, &IHidServer::EndPermitVibrationSession, "EndPermitVibrationSession"},
+ {211, &IHidServer::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"},
+ {212, nullptr, "SendVibrationValueInBool"},
+ {300, &IHidServer::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
+ {301, &IHidServer::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
+ {302, &IHidServer::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"},
+ {303, &IHidServer::ActivateSevenSixAxisSensor, "ActivateSevenSixAxisSensor"},
+ {304, &IHidServer::StartSevenSixAxisSensor, "StartSevenSixAxisSensor"},
+ {305, &IHidServer::StopSevenSixAxisSensor, "StopSevenSixAxisSensor"},
+ {306, &IHidServer::InitializeSevenSixAxisSensor, "InitializeSevenSixAxisSensor"},
+ {307, &IHidServer::FinalizeSevenSixAxisSensor, "FinalizeSevenSixAxisSensor"},
+ {308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
+ {309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
+ {310, &IHidServer::ResetSevenSixAxisSensorTimestamp, "ResetSevenSixAxisSensorTimestamp"},
+ {400, &IHidServer::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
+ {401, nullptr, "EnableUsbFullKeyController"},
+ {402, nullptr, "IsUsbFullKeyControllerConnected"},
+ {403, nullptr, "HasBattery"},
+ {404, nullptr, "HasLeftRightBattery"},
+ {405, nullptr, "GetNpadInterfaceType"},
+ {406, nullptr, "GetNpadLeftRightInterfaceType"},
+ {407, nullptr, "GetNpadOfHighestBatteryLevel"},
+ {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
+ {500, &IHidServer::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"},
+ {501, &IHidServer::InitializePalma, "InitializePalma"},
+ {502, &IHidServer::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"},
+ {503, &IHidServer::GetPalmaOperationInfo, "GetPalmaOperationInfo"},
+ {504, &IHidServer::PlayPalmaActivity, "PlayPalmaActivity"},
+ {505, &IHidServer::SetPalmaFrModeType, "SetPalmaFrModeType"},
+ {506, &IHidServer::ReadPalmaStep, "ReadPalmaStep"},
+ {507, &IHidServer::EnablePalmaStep, "EnablePalmaStep"},
+ {508, &IHidServer::ResetPalmaStep, "ResetPalmaStep"},
+ {509, &IHidServer::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"},
+ {510, &IHidServer::WritePalmaApplicationSection, "WritePalmaApplicationSection"},
+ {511, &IHidServer::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"},
+ {512, &IHidServer::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"},
+ {513, &IHidServer::WritePalmaActivityEntry, "WritePalmaActivityEntry"},
+ {514, &IHidServer::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"},
+ {515, &IHidServer::WritePalmaWaveEntry, "WritePalmaWaveEntry"},
+ {516, &IHidServer::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"},
+ {517, &IHidServer::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"},
+ {518, &IHidServer::SuspendPalmaFeature, "SuspendPalmaFeature"},
+ {519, &IHidServer::GetPalmaOperationResult, "GetPalmaOperationResult"},
+ {520, &IHidServer::ReadPalmaPlayLog, "ReadPalmaPlayLog"},
+ {521, &IHidServer::ResetPalmaPlayLog, "ResetPalmaPlayLog"},
+ {522, &IHidServer::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
+ {523, &IHidServer::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"},
+ {524, &IHidServer::PairPalma, "PairPalma"},
+ {525, &IHidServer::SetPalmaBoostMode, "SetPalmaBoostMode"},
+ {526, &IHidServer::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"},
+ {527, &IHidServer::EnablePalmaBoostMode, "EnablePalmaBoostMode"},
+ {528, &IHidServer::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"},
+ {529, &IHidServer::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"},
+ {1000, &IHidServer::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
+ {1001, &IHidServer::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
+ {1002, &IHidServer::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
+ {1003, &IHidServer::IsFirmwareUpdateNeededForNotification, "IsFirmwareUpdateNeededForNotification"},
+ {1004, &IHidServer::SetTouchScreenResolution, "SetTouchScreenResolution"},
+ {2000, nullptr, "ActivateDigitizer"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+IHidServer::~IHidServer() = default;
+
+void IHidServer::CreateAppletResource(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IAppletResource>(system, resource_manager);
+}
+
+void IHidServer::ActivateDebugPad(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ Result result = ResultSuccess;
+ auto debug_pad = GetResourceManager()->GetDebugPad();
+
+ if (!firmware_settings->IsDeviceManaged()) {
+ result = debug_pad->Activate();
+ }
+
+ if (result.IsSuccess()) {
+ result = debug_pad->Activate(applet_resource_user_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ActivateTouchScreen(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ Result result = ResultSuccess;
+ auto touch_screen = GetResourceManager()->GetTouchScreen();
+
+ if (!firmware_settings->IsDeviceManaged()) {
+ result = touch_screen->Activate();
+ }
+
+ if (result.IsSuccess()) {
+ result = touch_screen->Activate(applet_resource_user_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ActivateMouse(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ Result result = ResultSuccess;
+ auto mouse = GetResourceManager()->GetMouse();
+
+ if (!firmware_settings->IsDeviceManaged()) {
+ result = mouse->Activate();
+ }
+
+ if (result.IsSuccess()) {
+ result = mouse->Activate(applet_resource_user_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ActivateKeyboard(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ Result result = ResultSuccess;
+ auto keyboard = GetResourceManager()->GetKeyboard();
+
+ if (!firmware_settings->IsDeviceManaged()) {
+ result = keyboard->Activate();
+ }
+
+ if (result.IsSuccess()) {
+ result = keyboard->Activate(applet_resource_user_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::SendKeyboardLockKeyEvent(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto flags{rp.Pop<u32>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::AcquireXpadIdEventHandle(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ // Handle returned is null here
+}
+
+void IHidServer::ReleaseXpadIdEventHandle(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::ActivateXpad(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ u32 basic_xpad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}",
+ parameters.basic_xpad_id, parameters.applet_resource_user_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetXpadIds(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ // This function has been hardcoded since 10.0.0+
+ const std::array<u32, 4> basic_xpad_id{0, 1, 2, 3};
+ ctx.WriteBuffer(basic_xpad_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<s64>(basic_xpad_id.size());
+}
+
+void IHidServer::ActivateJoyXpad(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto joy_xpad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, joy_xpad_id={}", joy_xpad_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetJoyXpadLifoHandle(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto joy_xpad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, joy_xpad_id={}", joy_xpad_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ // Handle returned is null here
+}
+
+void IHidServer::GetJoyXpadIds(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ // This function has been hardcoded since 10.0.0+
+ const s64 basic_xpad_id_count{};
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push(basic_xpad_id_count);
+}
+
+void IHidServer::ActivateSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto joy_xpad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, joy_xpad_id={}", joy_xpad_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::DeactivateSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto joy_xpad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, joy_xpad_id={}", joy_xpad_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetSixAxisSensorLifoHandle(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto joy_xpad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, joy_xpad_id={}", joy_xpad_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::ActivateJoySixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto joy_xpad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, joy_xpad_id={}", joy_xpad_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::DeactivateJoySixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto joy_xpad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, joy_xpad_id={}", joy_xpad_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetJoySixAxisSensorLifoHandle(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto joy_xpad_id{rp.Pop<u32>()};
+
+ LOG_DEBUG(Service_HID, "called, joy_xpad_id={}", joy_xpad_id);
+
+ // This function has been stubbed since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ // Handle returned is null here
+}
+
+void IHidServer::StartSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result = six_axis->SetSixAxisEnabled(parameters.sixaxis_handle, true);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::StopSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result = six_axis->SetSixAxisEnabled(parameters.sixaxis_handle, false);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::IsSixAxisSensorFusionEnabled(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ bool is_enabled{};
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result =
+ six_axis->IsSixAxisSensorFusionEnabled(parameters.sixaxis_handle, is_enabled);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(is_enabled);
+}
+
+void IHidServer::EnableSixAxisSensorFusion(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool enable_sixaxis_sensor_fusion;
+ INSERT_PADDING_BYTES_NOINIT(3);
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result = six_axis->SetSixAxisFusionEnabled(parameters.sixaxis_handle,
+ parameters.enable_sixaxis_sensor_fusion);
+
+ LOG_DEBUG(Service_HID,
+ "called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, "
+ "device_index={}, applet_resource_user_id={}",
+ parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type,
+ parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::SetSixAxisSensorFusionParameters(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ Core::HID::SixAxisSensorFusionParameters sixaxis_fusion;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result =
+ six_axis->SetSixAxisFusionParameters(parameters.sixaxis_handle, parameters.sixaxis_fusion);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, parameter1={}, "
+ "parameter2={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.sixaxis_fusion.parameter1,
+ parameters.sixaxis_fusion.parameter2, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::GetSixAxisSensorFusionParameters(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ Core::HID::SixAxisSensorFusionParameters fusion_parameters{};
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result =
+ six_axis->GetSixAxisFusionParameters(parameters.sixaxis_handle, fusion_parameters);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.PushRaw(fusion_parameters);
+}
+
+void IHidServer::ResetSixAxisSensorFusionParameters(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ // Since these parameters are unknown just use what HW outputs
+ const Core::HID::SixAxisSensorFusionParameters fusion_parameters{
+ .parameter1 = 0.03f,
+ .parameter2 = 0.4f,
+ };
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result1 =
+ six_axis->SetSixAxisFusionParameters(parameters.sixaxis_handle, fusion_parameters);
+ const auto result2 = six_axis->SetSixAxisFusionEnabled(parameters.sixaxis_handle, true);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ if (result1.IsError()) {
+ rb.Push(result1);
+ return;
+ }
+ rb.Push(result2);
+}
+
+void IHidServer::SetGyroscopeZeroDriftMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto sixaxis_handle{rp.PopRaw<Core::HID::SixAxisSensorHandle>()};
+ const auto drift_mode{rp.PopEnum<Core::HID::GyroscopeZeroDriftMode>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result = six_axis->SetGyroscopeZeroDriftMode(sixaxis_handle, drift_mode);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, "
+ "applet_resource_user_id={}",
+ sixaxis_handle.npad_type, sixaxis_handle.npad_id, sixaxis_handle.device_index,
+ drift_mode, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::GetGyroscopeZeroDriftMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto drift_mode{Core::HID::GyroscopeZeroDriftMode::Standard};
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result = six_axis->GetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.PushEnum(drift_mode);
+}
+
+void IHidServer::ResetGyroscopeZeroDriftMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ const auto drift_mode{Core::HID::GyroscopeZeroDriftMode::Standard};
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result = six_axis->SetGyroscopeZeroDriftMode(parameters.sixaxis_handle, drift_mode);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::IsSixAxisSensorAtRest(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ bool is_at_rest{};
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ six_axis->IsSixAxisSensorAtRest(parameters.sixaxis_handle, is_at_rest);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(is_at_rest);
+}
+
+void IHidServer::IsFirmwareUpdateAvailableForSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ bool is_firmware_available{};
+ auto controller = GetResourceManager()->GetNpad();
+ controller->IsFirmwareUpdateAvailableForSixAxisSensor(parameters.sixaxis_handle,
+ is_firmware_available);
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(is_firmware_available);
+}
+
+void IHidServer::EnableSixAxisSensorUnalteredPassthrough(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool enabled;
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result = six_axis->EnableSixAxisSensorUnalteredPassthrough(parameters.sixaxis_handle,
+ parameters.enabled);
+
+ LOG_DEBUG(Service_HID,
+ "(STUBBED) called, enabled={}, npad_type={}, npad_id={}, device_index={}, "
+ "applet_resource_user_id={}",
+ parameters.enabled, parameters.sixaxis_handle.npad_type,
+ parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::IsSixAxisSensorUnalteredPassthroughEnabled(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ bool is_unaltered_sisxaxis_enabled{};
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result = six_axis->IsSixAxisSensorUnalteredPassthroughEnabled(
+ parameters.sixaxis_handle, is_unaltered_sisxaxis_enabled);
+
+ LOG_DEBUG(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(is_unaltered_sisxaxis_enabled);
+}
+
+void IHidServer::LoadSixAxisSensorCalibrationParameter(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ Core::HID::SixAxisSensorCalibrationParameter calibration{};
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result =
+ six_axis->LoadSixAxisSensorCalibrationParameter(parameters.sixaxis_handle, calibration);
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(calibration);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::GetSixAxisSensorIcInformation(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ Core::HID::SixAxisSensorIcInformation ic_information{};
+ auto six_axis = GetResourceManager()->GetSixAxis();
+ const auto result =
+ six_axis->GetSixAxisSensorIcInformation(parameters.sixaxis_handle, ic_information);
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(ic_information);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ResetIsSixAxisSensorDeviceNewlyAssigned(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::SixAxisSensorHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto controller = GetResourceManager()->GetNpad();
+ const auto result =
+ controller->ResetIsSixAxisSensorDeviceNewlyAssigned(parameters.sixaxis_handle);
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ActivateGesture(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ u32 basic_gesture_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_INFO(Service_HID, "called, basic_gesture_id={}, applet_resource_user_id={}",
+ parameters.basic_gesture_id, parameters.applet_resource_user_id);
+
+ Result result = ResultSuccess;
+ auto gesture = GetResourceManager()->GetGesture();
+
+ if (!firmware_settings->IsDeviceManaged()) {
+ result = gesture->Activate();
+ }
+
+ if (result.IsSuccess()) {
+ // TODO: Use gesture id here
+ result = gesture->Activate(parameters.applet_resource_user_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::SetSupportedNpadStyleSet(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadStyleSet supported_styleset;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ GetResourceManager()->GetNpad()->SetSupportedStyleSet({parameters.supported_styleset});
+
+ LOG_DEBUG(Service_HID, "called, supported_styleset={}, applet_resource_user_id={}",
+ parameters.supported_styleset, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetSupportedNpadStyleSet(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(GetResourceManager()->GetNpad()->GetSupportedStyleSet().raw);
+}
+
+void IHidServer::SetSupportedNpadIdType(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ const auto result = GetResourceManager()->GetNpad()->SetSupportedNpadIdTypes(ctx.ReadBuffer());
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ActivateNpad(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ auto npad = GetResourceManager()->GetNpad();
+
+ // TODO: npad->SetRevision(applet_resource_user_id, NpadRevision::Revision0);
+ const Result result = npad->Activate(applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::DeactivateNpad(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ // This function does nothing since 10.0.0+
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::AcquireNpadStyleSetUpdateEventHandle(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ u64 unknown;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}",
+ parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown);
+
+ // Games expect this event to be signaled after calling this function
+ GetResourceManager()->GetNpad()->SignalStyleSetChangedEvent(parameters.npad_id);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(
+ GetResourceManager()->GetNpad()->GetStyleSetChangedEvent(parameters.npad_id));
+}
+
+void IHidServer::DisconnectNpad(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto controller = GetResourceManager()->GetNpad();
+ controller->DisconnectNpad(parameters.npad_id);
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetPlayerLedPattern(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ Core::HID::LedPattern pattern{0, 0, 0, 0};
+ auto controller = GetResourceManager()->GetNpad();
+ const auto result = controller->GetLedPattern(npad_id, pattern);
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}", npad_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(pattern.raw);
+}
+
+void IHidServer::ActivateNpadWithRevision(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ NPad::NpadRevision revision;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID, "called, revision={}, applet_resource_user_id={}", parameters.revision,
+ parameters.applet_resource_user_id);
+
+ auto npad = GetResourceManager()->GetNpad();
+
+ // TODO: npad->SetRevision(applet_resource_user_id, revision);
+ const auto result = npad->Activate(parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::SetNpadJoyHoldType(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto hold_type{rp.PopEnum<NPad::NpadJoyHoldType>()};
+
+ GetResourceManager()->GetNpad()->SetHoldType(hold_type);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}",
+ applet_resource_user_id, hold_type);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetNpadJoyHoldType(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(GetResourceManager()->GetNpad()->GetHoldType());
+}
+
+void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ Core::HID::NpadIdType new_npad_id{};
+ auto controller = GetResourceManager()->GetNpad();
+ controller->SetNpadMode(new_npad_id, parameters.npad_id, NPad::NpadJoyDeviceType::Left,
+ NPad::NpadJoyAssignmentMode::Single);
+
+ LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetNpadJoyAssignmentModeSingle(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ NPad::NpadJoyDeviceType npad_joy_device_type;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ Core::HID::NpadIdType new_npad_id{};
+ auto controller = GetResourceManager()->GetNpad();
+ controller->SetNpadMode(new_npad_id, parameters.npad_id, parameters.npad_joy_device_type,
+ NPad::NpadJoyAssignmentMode::Single);
+
+ LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
+ parameters.npad_id, parameters.applet_resource_user_id,
+ parameters.npad_joy_device_type);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetNpadJoyAssignmentModeDual(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ Core::HID::NpadIdType new_npad_id{};
+ auto controller = GetResourceManager()->GetNpad();
+ controller->SetNpadMode(new_npad_id, parameters.npad_id, {}, NPad::NpadJoyAssignmentMode::Dual);
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
+ parameters.applet_resource_user_id); // Spams a lot when controller applet is open
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::MergeSingleJoyAsDualJoy(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_1{rp.PopEnum<Core::HID::NpadIdType>()};
+ const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ auto controller = GetResourceManager()->GetNpad();
+ const auto result = controller->MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2);
+
+ LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
+ npad_id_1, npad_id_2, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::StartLrAssignmentMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ GetResourceManager()->GetNpad()->StartLRAssignmentMode();
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::StopLrAssignmentMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ GetResourceManager()->GetNpad()->StopLRAssignmentMode();
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetNpadHandheldActivationMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto activation_mode{rp.PopEnum<NPad::NpadHandheldActivationMode>()};
+
+ GetResourceManager()->GetNpad()->SetNpadHandheldActivationMode(activation_mode);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, activation_mode={}",
+ applet_resource_user_id, activation_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetNpadHandheldActivationMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(GetResourceManager()->GetNpad()->GetNpadHandheldActivationMode());
+}
+
+void IHidServer::SwapNpadAssignment(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_1{rp.PopEnum<Core::HID::NpadIdType>()};
+ const auto npad_id_2{rp.PopEnum<Core::HID::NpadIdType>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ auto controller = GetResourceManager()->GetNpad();
+ const auto result = controller->SwapNpadAssignment(npad_id_1, npad_id_2);
+
+ LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
+ npad_id_1, npad_id_2, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::IsUnintendedHomeButtonInputProtectionEnabled(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ bool is_enabled = false;
+ auto controller = GetResourceManager()->GetNpad();
+ const auto result =
+ controller->IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id, is_enabled);
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(is_enabled);
+}
+
+void IHidServer::EnableUnintendedHomeButtonInputProtection(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool is_enabled;
+ INSERT_PADDING_BYTES_NOINIT(3);
+ Core::HID::NpadIdType npad_id;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ auto controller = GetResourceManager()->GetNpad();
+ const auto result = controller->SetUnintendedHomeButtonInputProtectionEnabled(
+ parameters.is_enabled, parameters.npad_id);
+
+ LOG_DEBUG(Service_HID,
+ "(STUBBED) called, is_enabled={}, npad_id={}, applet_resource_user_id={}",
+ parameters.is_enabled, parameters.npad_id, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::SetNpadJoyAssignmentModeSingleWithDestination(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ NPad::NpadJoyDeviceType npad_joy_device_type;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ Core::HID::NpadIdType new_npad_id{};
+ auto controller = GetResourceManager()->GetNpad();
+ const auto is_reassigned =
+ controller->SetNpadMode(new_npad_id, parameters.npad_id, parameters.npad_joy_device_type,
+ NPad::NpadJoyAssignmentMode::Single);
+
+ LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
+ parameters.npad_id, parameters.applet_resource_user_id,
+ parameters.npad_joy_device_type);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push(is_reassigned);
+ rb.PushEnum(new_npad_id);
+}
+
+void IHidServer::SetNpadAnalogStickUseCenterClamp(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool analog_stick_use_center_clamp;
+ INSERT_PADDING_BYTES_NOINIT(7);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ GetResourceManager()->GetNpad()->SetAnalogStickUseCenterClamp(
+ parameters.analog_stick_use_center_clamp);
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, analog_stick_use_center_clamp={}, applet_resource_user_id={}",
+ parameters.analog_stick_use_center_clamp, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetNpadCaptureButtonAssignment(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadStyleSet npad_styleset;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ Core::HID::NpadButton button;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, npad_styleset={}, applet_resource_user_id={}, button={}",
+ parameters.npad_styleset, parameters.applet_resource_user_id, parameters.button);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::ClearNpadCaptureButtonAssignment(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
+ const auto controller = GetResourceManager()->GetNpad();
+
+ Core::HID::VibrationDeviceInfo vibration_device_info;
+ bool check_device_index = false;
+
+ switch (vibration_device_handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Handheld:
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
+ check_device_index = true;
+ break;
+ case Core::HID::NpadStyleIndex::GameCube:
+ vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm;
+ break;
+ case Core::HID::NpadStyleIndex::N64:
+ vibration_device_info.type = Core::HID::VibrationDeviceType::N64;
+ break;
+ default:
+ vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown;
+ break;
+ }
+
+ vibration_device_info.position = Core::HID::VibrationDevicePosition::None;
+ if (check_device_index) {
+ switch (vibration_device_handle.device_index) {
+ case Core::HID::DeviceIndex::Left:
+ vibration_device_info.position = Core::HID::VibrationDevicePosition::Left;
+ break;
+ case Core::HID::DeviceIndex::Right:
+ vibration_device_info.position = Core::HID::VibrationDevicePosition::Right;
+ break;
+ case Core::HID::DeviceIndex::None:
+ default:
+ ASSERT_MSG(false, "DeviceIndex should never be None!");
+ break;
+ }
+ }
+
+ LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}",
+ vibration_device_info.type, vibration_device_info.position);
+
+ const auto result = IsVibrationHandleValid(vibration_device_handle);
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(vibration_device_info);
+}
+
+void IHidServer::SendVibrationValue(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::VibrationDeviceHandle vibration_device_handle;
+ Core::HID::VibrationValue vibration_value;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ GetResourceManager()->GetNpad()->VibrateController(parameters.vibration_device_handle,
+ parameters.vibration_value);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetActualVibrationValue(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::VibrationDeviceHandle vibration_device_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(
+ GetResourceManager()->GetNpad()->GetLastVibration(parameters.vibration_device_handle));
+}
+
+void IHidServer::CreateActiveVibrationDeviceList(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IActiveVibrationDeviceList>(system, GetResourceManager());
+}
+
+void IHidServer::PermitVibration(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto can_vibrate{rp.Pop<bool>()};
+
+ // nnSDK saves this value as a float. Since it can only be 1.0f or 0.0f we simplify this value
+ // by converting it to a bool
+ Settings::values.vibration_enabled.SetValue(can_vibrate);
+
+ LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::IsVibrationPermitted(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ // nnSDK checks if a float is greater than zero. We return the bool we stored earlier
+ const auto is_enabled = Settings::values.vibration_enabled.GetValue();
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(is_enabled);
+}
+
+void IHidServer::SendVibrationValues(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ const auto handle_data = ctx.ReadBuffer(0);
+ const auto handle_count = ctx.GetReadBufferNumElements<Core::HID::VibrationDeviceHandle>(0);
+ const auto vibration_data = ctx.ReadBuffer(1);
+ const auto vibration_count = ctx.GetReadBufferNumElements<Core::HID::VibrationValue>(1);
+
+ auto vibration_device_handles =
+ std::span(reinterpret_cast<const Core::HID::VibrationDeviceHandle*>(handle_data.data()),
+ handle_count);
+ auto vibration_values = std::span(
+ reinterpret_cast<const Core::HID::VibrationValue*>(vibration_data.data()), vibration_count);
+
+ GetResourceManager()->GetNpad()->VibrateControllers(vibration_device_handles, vibration_values);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::VibrationDeviceHandle vibration_device_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ Core::HID::VibrationGcErmCommand gc_erm_command;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ /**
+ * Note: This uses yuzu-specific behavior such that the StopHard command produces
+ * vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined below,
+ * in order to differentiate between Stop and StopHard commands.
+ * This is done to reuse the controller vibration functions made for regular controllers.
+ */
+ const auto vibration_value = [parameters] {
+ switch (parameters.gc_erm_command) {
+ case Core::HID::VibrationGcErmCommand::Stop:
+ return Core::HID::VibrationValue{
+ .low_amplitude = 0.0f,
+ .low_frequency = 160.0f,
+ .high_amplitude = 0.0f,
+ .high_frequency = 320.0f,
+ };
+ case Core::HID::VibrationGcErmCommand::Start:
+ return Core::HID::VibrationValue{
+ .low_amplitude = 1.0f,
+ .low_frequency = 160.0f,
+ .high_amplitude = 1.0f,
+ .high_frequency = 320.0f,
+ };
+ case Core::HID::VibrationGcErmCommand::StopHard:
+ return Core::HID::VibrationValue{
+ .low_amplitude = 0.0f,
+ .low_frequency = 0.0f,
+ .high_amplitude = 0.0f,
+ .high_frequency = 0.0f,
+ };
+ default:
+ return Core::HID::DEFAULT_VIBRATION_VALUE;
+ }
+ }();
+
+ GetResourceManager()->GetNpad()->VibrateController(parameters.vibration_device_handle,
+ vibration_value);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, "
+ "gc_erm_command={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id,
+ parameters.gc_erm_command);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::VibrationDeviceHandle vibration_device_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ const auto last_vibration =
+ GetResourceManager()->GetNpad()->GetLastVibration(parameters.vibration_device_handle);
+
+ const auto gc_erm_command = [last_vibration] {
+ if (last_vibration.low_amplitude != 0.0f || last_vibration.high_amplitude != 0.0f) {
+ return Core::HID::VibrationGcErmCommand::Start;
+ }
+
+ /**
+ * Note: This uses yuzu-specific behavior such that the StopHard command produces
+ * vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined in the HID function
+ * SendVibrationGcErmCommand, in order to differentiate between Stop and StopHard commands.
+ * This is done to reuse the controller vibration functions made for regular controllers.
+ */
+ if (last_vibration.low_frequency == 0.0f && last_vibration.high_frequency == 0.0f) {
+ return Core::HID::VibrationGcErmCommand::StopHard;
+ }
+
+ return Core::HID::VibrationGcErmCommand::Stop;
+ }();
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(gc_erm_command);
+}
+
+void IHidServer::BeginPermitVibrationSession(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ GetResourceManager()->GetNpad()->SetPermitVibrationSession(true);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::EndPermitVibrationSession(HLERequestContext& ctx) {
+ GetResourceManager()->GetNpad()->SetPermitVibrationSession(false);
+
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::VibrationDeviceHandle vibration_device_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(GetResourceManager()->GetNpad()->IsVibrationDeviceMounted(
+ parameters.vibration_device_handle));
+}
+
+void IHidServer::ActivateConsoleSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_INFO(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ Result result = ResultSuccess;
+ auto console_sixaxis = GetResourceManager()->GetConsoleSixAxis();
+
+ if (!firmware_settings->IsDeviceManaged()) {
+ result = console_sixaxis->Activate();
+ }
+
+ if (result.IsSuccess()) {
+ result = console_sixaxis->Activate(applet_resource_user_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::StartConsoleSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
+ parameters.console_sixaxis_handle.unknown_1,
+ parameters.console_sixaxis_handle.unknown_2, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::StopConsoleSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
+ parameters.console_sixaxis_handle.unknown_1,
+ parameters.console_sixaxis_handle.unknown_2, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::ActivateSevenSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_INFO(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ Result result = ResultSuccess;
+ auto seven_sixaxis = GetResourceManager()->GetSevenSixAxis();
+
+ if (!firmware_settings->IsDeviceManaged()) {
+ result = seven_sixaxis->Activate();
+ }
+
+ if (result.IsSuccess()) {
+ seven_sixaxis->Activate(applet_resource_user_id);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::StartSevenSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::StopSevenSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::InitializeSevenSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto t_mem_1_size{rp.Pop<u64>()};
+ const auto t_mem_2_size{rp.Pop<u64>()};
+ const auto t_mem_1_handle{ctx.GetCopyHandle(0)};
+ const auto t_mem_2_handle{ctx.GetCopyHandle(1)};
+
+ ASSERT_MSG(t_mem_1_size == 0x1000, "t_mem_1_size is not 0x1000 bytes");
+ ASSERT_MSG(t_mem_2_size == 0x7F000, "t_mem_2_size is not 0x7F000 bytes");
+
+ auto t_mem_1 = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ t_mem_1_handle);
+
+ if (t_mem_1.IsNull()) {
+ LOG_ERROR(Service_HID, "t_mem_1 is a nullptr for handle=0x{:08X}", t_mem_1_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ auto t_mem_2 = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ t_mem_2_handle);
+
+ if (t_mem_2.IsNull()) {
+ LOG_ERROR(Service_HID, "t_mem_2 is a nullptr for handle=0x{:08X}", t_mem_2_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ ASSERT_MSG(t_mem_1->GetSize() == 0x1000, "t_mem_1 has incorrect size");
+ ASSERT_MSG(t_mem_2->GetSize() == 0x7F000, "t_mem_2 has incorrect size");
+
+ // Activate console six axis controller
+ GetResourceManager()->GetConsoleSixAxis()->Activate();
+ GetResourceManager()->GetSevenSixAxis()->Activate();
+
+ GetResourceManager()->GetSevenSixAxis()->SetTransferMemoryAddress(t_mem_1->GetSourceAddress());
+
+ LOG_WARNING(Service_HID,
+ "called, t_mem_1_handle=0x{:08X}, t_mem_2_handle=0x{:08X}, "
+ "applet_resource_user_id={}",
+ t_mem_1_handle, t_mem_2_handle, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::FinalizeSevenSixAxisSensor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::ResetSevenSixAxisSensorTimestamp(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ GetResourceManager()->GetSevenSixAxis()->ResetTimestamp();
+
+ LOG_WARNING(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(false);
+}
+
+void IHidServer::GetPalmaConnectionHandle(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
+
+ Palma::PalmaConnectionHandle handle;
+ auto controller = GetResourceManager()->GetPalma();
+ const auto result = controller->GetPalmaConnectionHandle(parameters.npad_id, handle);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.PushRaw(handle);
+}
+
+void IHidServer::InitializePalma(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto controller = GetResourceManager()->GetPalma();
+ const auto result = controller->InitializePalma(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::AcquirePalmaOperationCompleteEvent(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto controller = GetResourceManager()->GetPalma();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(controller->AcquirePalmaOperationCompleteEvent(connection_handle));
+}
+
+void IHidServer::GetPalmaOperationInfo(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ Palma::PalmaOperationType operation_type;
+ Palma::PalmaOperationData data;
+ auto controller = GetResourceManager()->GetPalma();
+ const auto result = controller->GetPalmaOperationInfo(connection_handle, operation_type, data);
+
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ ctx.WriteBuffer(data);
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(static_cast<u64>(operation_type));
+}
+
+void IHidServer::PlayPalmaActivity(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+ const auto palma_activity{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}",
+ connection_handle.npad_id, palma_activity);
+
+ auto controller = GetResourceManager()->GetPalma();
+ const auto result = controller->PlayPalmaActivity(connection_handle, palma_activity);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::SetPalmaFrModeType(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+ const auto fr_mode{rp.PopEnum<Palma::PalmaFrModeType>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}",
+ connection_handle.npad_id, fr_mode);
+
+ auto controller = GetResourceManager()->GetPalma();
+ const auto result = controller->SetPalmaFrModeType(connection_handle, fr_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ReadPalmaStep(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto controller = GetResourceManager()->GetPalma();
+ const auto result = controller->ReadPalmaStep(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::EnablePalmaStep(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool is_enabled;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ Palma::PalmaConnectionHandle connection_handle;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}",
+ parameters.connection_handle.npad_id, parameters.is_enabled);
+
+ auto controller = GetResourceManager()->GetPalma();
+ const auto result =
+ controller->EnablePalmaStep(parameters.connection_handle, parameters.is_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ResetPalmaStep(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto controller = GetResourceManager()->GetPalma();
+ const auto result = controller->ResetPalmaStep(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ReadPalmaApplicationSection(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::WritePalmaApplicationSection(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::ReadPalmaUniqueCode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ GetResourceManager()->GetPalma()->ReadPalmaUniqueCode(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetPalmaUniqueCodeInvalid(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ GetResourceManager()->GetPalma()->SetPalmaUniqueCodeInvalid(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::WritePalmaActivityEntry(HLERequestContext& ctx) {
+ LOG_CRITICAL(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::WritePalmaRgbLedPatternEntry(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+ const auto unknown{rp.Pop<u64>()};
+
+ [[maybe_unused]] const auto buffer = ctx.ReadBuffer();
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}",
+ connection_handle.npad_id, unknown);
+
+ GetResourceManager()->GetPalma()->WritePalmaRgbLedPatternEntry(connection_handle, unknown);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::WritePalmaWaveEntry(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+ const auto wave_set{rp.PopEnum<Palma::PalmaWaveSet>()};
+ const auto unknown{rp.Pop<u64>()};
+ const auto t_mem_size{rp.Pop<u64>()};
+ const auto t_mem_handle{ctx.GetCopyHandle(0)};
+ const auto size{rp.Pop<u64>()};
+
+ ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes");
+
+ auto t_mem = system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
+ t_mem_handle);
+
+ if (t_mem.IsNull()) {
+ LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size");
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, connection_handle={}, wave_set={}, unknown={}, "
+ "t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
+ connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
+
+ GetResourceManager()->GetPalma()->WritePalmaWaveEntry(connection_handle, wave_set,
+ t_mem->GetSourceAddress(), t_mem_size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetPalmaDataBaseIdentificationVersion(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ s32 database_id_version;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ Palma::PalmaConnectionHandle connection_handle;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}",
+ parameters.connection_handle.npad_id, parameters.database_id_version);
+
+ GetResourceManager()->GetPalma()->SetPalmaDataBaseIdentificationVersion(
+ parameters.connection_handle, parameters.database_id_version);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetPalmaDataBaseIdentificationVersion(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ GetResourceManager()->GetPalma()->GetPalmaDataBaseIdentificationVersion(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SuspendPalmaFeature(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetPalmaOperationResult(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ const auto result =
+ GetResourceManager()->GetPalma()->GetPalmaOperationResult(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidServer::ReadPalmaPlayLog(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::ResetPalmaPlayLog(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetIsPalmaAllConnectable(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool is_palma_all_connectable;
+ INSERT_PADDING_BYTES_NOINIT(7);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}",
+ parameters.is_palma_all_connectable, parameters.applet_resource_user_id);
+
+ GetResourceManager()->GetPalma()->SetIsPalmaAllConnectable(parameters.is_palma_all_connectable);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetIsPalmaPairedConnectable(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::PairPalma(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ GetResourceManager()->GetPalma()->PairPalma(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetPalmaBoostMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto palma_boost_mode{rp.Pop<bool>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
+
+ GetResourceManager()->GetPalma()->SetPalmaBoostMode(palma_boost_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::CancelWritePalmaWaveEntry(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::EnablePalmaBoostMode(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetPalmaBluetoothAddress(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetDisallowedPalmaConnection(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::SetNpadCommunicationMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto communication_mode{rp.PopEnum<NPad::NpadCommunicationMode>()};
+
+ GetResourceManager()->GetNpad()->SetNpadCommunicationMode(communication_mode);
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, communication_mode={}",
+ applet_resource_user_id, communication_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::GetNpadCommunicationMode(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(GetResourceManager()->GetNpad()->GetNpadCommunicationMode());
+}
+
+void IHidServer::SetTouchScreenConfiguration(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}",
+ touchscreen_mode.mode, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidServer::IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ s32 unknown;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, unknown={}, applet_resource_user_id={}",
+ parameters.unknown, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(false);
+}
+
+void IHidServer::SetTouchScreenResolution(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto width{rp.Pop<u32>()};
+ const auto height{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ GetResourceManager()->GetTouchScreen()->SetTouchscreenDimensions(width, height);
+
+ LOG_INFO(Service_HID, "called, width={}, height={}, applet_resource_user_id={}", width, height,
+ applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+std::shared_ptr<ResourceManager> IHidServer::GetResourceManager() {
+ resource_manager->Initialize();
+ return resource_manager;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h
new file mode 100644
index 000000000..cc7c4ebdd
--- /dev/null
+++ b/src/core/hle/service/hid/hid_server.h
@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::HID {
+class ResourceManager;
+class HidFirmwareSettings;
+
+class IHidServer final : public ServiceFramework<IHidServer> {
+public:
+ explicit IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> resource,
+ std::shared_ptr<HidFirmwareSettings> settings);
+ ~IHidServer() override;
+
+ std::shared_ptr<ResourceManager> GetResourceManager();
+
+private:
+ void CreateAppletResource(HLERequestContext& ctx);
+ void ActivateDebugPad(HLERequestContext& ctx);
+ void ActivateTouchScreen(HLERequestContext& ctx);
+ void ActivateMouse(HLERequestContext& ctx);
+ void ActivateKeyboard(HLERequestContext& ctx);
+ void SendKeyboardLockKeyEvent(HLERequestContext& ctx);
+ void AcquireXpadIdEventHandle(HLERequestContext& ctx);
+ void ReleaseXpadIdEventHandle(HLERequestContext& ctx);
+ void ActivateXpad(HLERequestContext& ctx);
+ void GetXpadIds(HLERequestContext& ctx);
+ void ActivateJoyXpad(HLERequestContext& ctx);
+ void GetJoyXpadLifoHandle(HLERequestContext& ctx);
+ void GetJoyXpadIds(HLERequestContext& ctx);
+ void ActivateSixAxisSensor(HLERequestContext& ctx);
+ void DeactivateSixAxisSensor(HLERequestContext& ctx);
+ void GetSixAxisSensorLifoHandle(HLERequestContext& ctx);
+ void ActivateJoySixAxisSensor(HLERequestContext& ctx);
+ void DeactivateJoySixAxisSensor(HLERequestContext& ctx);
+ void GetJoySixAxisSensorLifoHandle(HLERequestContext& ctx);
+ void StartSixAxisSensor(HLERequestContext& ctx);
+ void StopSixAxisSensor(HLERequestContext& ctx);
+ void IsSixAxisSensorFusionEnabled(HLERequestContext& ctx);
+ void EnableSixAxisSensorFusion(HLERequestContext& ctx);
+ void SetSixAxisSensorFusionParameters(HLERequestContext& ctx);
+ void GetSixAxisSensorFusionParameters(HLERequestContext& ctx);
+ void ResetSixAxisSensorFusionParameters(HLERequestContext& ctx);
+ void SetGyroscopeZeroDriftMode(HLERequestContext& ctx);
+ void GetGyroscopeZeroDriftMode(HLERequestContext& ctx);
+ void ResetGyroscopeZeroDriftMode(HLERequestContext& ctx);
+ void IsSixAxisSensorAtRest(HLERequestContext& ctx);
+ void IsFirmwareUpdateAvailableForSixAxisSensor(HLERequestContext& ctx);
+ void EnableSixAxisSensorUnalteredPassthrough(HLERequestContext& ctx);
+ void IsSixAxisSensorUnalteredPassthroughEnabled(HLERequestContext& ctx);
+ void LoadSixAxisSensorCalibrationParameter(HLERequestContext& ctx);
+ void GetSixAxisSensorIcInformation(HLERequestContext& ctx);
+ void ResetIsSixAxisSensorDeviceNewlyAssigned(HLERequestContext& ctx);
+ void ActivateGesture(HLERequestContext& ctx);
+ void SetSupportedNpadStyleSet(HLERequestContext& ctx);
+ void GetSupportedNpadStyleSet(HLERequestContext& ctx);
+ void SetSupportedNpadIdType(HLERequestContext& ctx);
+ void ActivateNpad(HLERequestContext& ctx);
+ void DeactivateNpad(HLERequestContext& ctx);
+ void AcquireNpadStyleSetUpdateEventHandle(HLERequestContext& ctx);
+ void DisconnectNpad(HLERequestContext& ctx);
+ void GetPlayerLedPattern(HLERequestContext& ctx);
+ void ActivateNpadWithRevision(HLERequestContext& ctx);
+ void SetNpadJoyHoldType(HLERequestContext& ctx);
+ void GetNpadJoyHoldType(HLERequestContext& ctx);
+ void SetNpadJoyAssignmentModeSingleByDefault(HLERequestContext& ctx);
+ void SetNpadJoyAssignmentModeSingle(HLERequestContext& ctx);
+ void SetNpadJoyAssignmentModeDual(HLERequestContext& ctx);
+ void MergeSingleJoyAsDualJoy(HLERequestContext& ctx);
+ void StartLrAssignmentMode(HLERequestContext& ctx);
+ void StopLrAssignmentMode(HLERequestContext& ctx);
+ void SetNpadHandheldActivationMode(HLERequestContext& ctx);
+ void GetNpadHandheldActivationMode(HLERequestContext& ctx);
+ void SwapNpadAssignment(HLERequestContext& ctx);
+ void IsUnintendedHomeButtonInputProtectionEnabled(HLERequestContext& ctx);
+ void EnableUnintendedHomeButtonInputProtection(HLERequestContext& ctx);
+ void SetNpadJoyAssignmentModeSingleWithDestination(HLERequestContext& ctx);
+ void SetNpadAnalogStickUseCenterClamp(HLERequestContext& ctx);
+ void SetNpadCaptureButtonAssignment(HLERequestContext& ctx);
+ void ClearNpadCaptureButtonAssignment(HLERequestContext& ctx);
+ void GetVibrationDeviceInfo(HLERequestContext& ctx);
+ void SendVibrationValue(HLERequestContext& ctx);
+ void GetActualVibrationValue(HLERequestContext& ctx);
+ void CreateActiveVibrationDeviceList(HLERequestContext& ctx);
+ void PermitVibration(HLERequestContext& ctx);
+ void IsVibrationPermitted(HLERequestContext& ctx);
+ void SendVibrationValues(HLERequestContext& ctx);
+ void SendVibrationGcErmCommand(HLERequestContext& ctx);
+ void GetActualVibrationGcErmCommand(HLERequestContext& ctx);
+ void BeginPermitVibrationSession(HLERequestContext& ctx);
+ void EndPermitVibrationSession(HLERequestContext& ctx);
+ void IsVibrationDeviceMounted(HLERequestContext& ctx);
+ void ActivateConsoleSixAxisSensor(HLERequestContext& ctx);
+ void StartConsoleSixAxisSensor(HLERequestContext& ctx);
+ void StopConsoleSixAxisSensor(HLERequestContext& ctx);
+ void ActivateSevenSixAxisSensor(HLERequestContext& ctx);
+ void StartSevenSixAxisSensor(HLERequestContext& ctx);
+ void StopSevenSixAxisSensor(HLERequestContext& ctx);
+ void InitializeSevenSixAxisSensor(HLERequestContext& ctx);
+ void FinalizeSevenSixAxisSensor(HLERequestContext& ctx);
+ void ResetSevenSixAxisSensorTimestamp(HLERequestContext& ctx);
+ void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx);
+ void GetPalmaConnectionHandle(HLERequestContext& ctx);
+ void InitializePalma(HLERequestContext& ctx);
+ void AcquirePalmaOperationCompleteEvent(HLERequestContext& ctx);
+ void GetPalmaOperationInfo(HLERequestContext& ctx);
+ void PlayPalmaActivity(HLERequestContext& ctx);
+ void SetPalmaFrModeType(HLERequestContext& ctx);
+ void ReadPalmaStep(HLERequestContext& ctx);
+ void EnablePalmaStep(HLERequestContext& ctx);
+ void ResetPalmaStep(HLERequestContext& ctx);
+ void ReadPalmaApplicationSection(HLERequestContext& ctx);
+ void WritePalmaApplicationSection(HLERequestContext& ctx);
+ void ReadPalmaUniqueCode(HLERequestContext& ctx);
+ void SetPalmaUniqueCodeInvalid(HLERequestContext& ctx);
+ void WritePalmaActivityEntry(HLERequestContext& ctx);
+ void WritePalmaRgbLedPatternEntry(HLERequestContext& ctx);
+ void WritePalmaWaveEntry(HLERequestContext& ctx);
+ void SetPalmaDataBaseIdentificationVersion(HLERequestContext& ctx);
+ void GetPalmaDataBaseIdentificationVersion(HLERequestContext& ctx);
+ void SuspendPalmaFeature(HLERequestContext& ctx);
+ void GetPalmaOperationResult(HLERequestContext& ctx);
+ void ReadPalmaPlayLog(HLERequestContext& ctx);
+ void ResetPalmaPlayLog(HLERequestContext& ctx);
+ void SetIsPalmaAllConnectable(HLERequestContext& ctx);
+ void SetIsPalmaPairedConnectable(HLERequestContext& ctx);
+ void PairPalma(HLERequestContext& ctx);
+ void SetPalmaBoostMode(HLERequestContext& ctx);
+ void CancelWritePalmaWaveEntry(HLERequestContext& ctx);
+ void EnablePalmaBoostMode(HLERequestContext& ctx);
+ void GetPalmaBluetoothAddress(HLERequestContext& ctx);
+ void SetDisallowedPalmaConnection(HLERequestContext& ctx);
+ void SetNpadCommunicationMode(HLERequestContext& ctx);
+ void GetNpadCommunicationMode(HLERequestContext& ctx);
+ void SetTouchScreenConfiguration(HLERequestContext& ctx);
+ void IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx);
+ void SetTouchScreenResolution(HLERequestContext& ctx);
+
+ std::shared_ptr<ResourceManager> resource_manager;
+ std::shared_ptr<HidFirmwareSettings> firmware_settings;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_system_server.cpp b/src/core/hle/service/hid/hid_system_server.cpp
new file mode 100644
index 000000000..b56d0347a
--- /dev/null
+++ b/src/core/hle/service/hid/hid_system_server.cpp
@@ -0,0 +1,539 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/touchscreen.h"
+#include "core/hle/service/hid/errors.h"
+#include "core/hle/service/hid/hid_system_server.h"
+#include "core/hle/service/hid/resource_manager.h"
+#include "core/hle/service/ipc_helpers.h"
+
+namespace Service::HID {
+
+IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<ResourceManager> resource)
+ : ServiceFramework{system_, "hid:sys"}, service_context{system_, service_name},
+ resource_manager{resource} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {31, nullptr, "SendKeyboardLockKeyEvent"},
+ {101, nullptr, "AcquireHomeButtonEventHandle"},
+ {111, nullptr, "ActivateHomeButton"},
+ {121, nullptr, "AcquireSleepButtonEventHandle"},
+ {131, nullptr, "ActivateSleepButton"},
+ {141, nullptr, "AcquireCaptureButtonEventHandle"},
+ {151, nullptr, "ActivateCaptureButton"},
+ {161, nullptr, "GetPlatformConfig"},
+ {210, nullptr, "AcquireNfcDeviceUpdateEventHandle"},
+ {211, nullptr, "GetNpadsWithNfc"},
+ {212, nullptr, "AcquireNfcActivateEventHandle"},
+ {213, nullptr, "ActivateNfc"},
+ {214, nullptr, "GetXcdHandleForNpadWithNfc"},
+ {215, nullptr, "IsNfcActivated"},
+ {230, nullptr, "AcquireIrSensorEventHandle"},
+ {231, nullptr, "ActivateIrSensor"},
+ {232, nullptr, "GetIrSensorState"},
+ {233, nullptr, "GetXcdHandleForNpadWithIrSensor"},
+ {301, nullptr, "ActivateNpadSystem"},
+ {303, &IHidSystemServer::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"},
+ {304, &IHidSystemServer::EnableAssigningSingleOnSlSrPress, "EnableAssigningSingleOnSlSrPress"},
+ {305, &IHidSystemServer::DisableAssigningSingleOnSlSrPress, "DisableAssigningSingleOnSlSrPress"},
+ {306, &IHidSystemServer::GetLastActiveNpad, "GetLastActiveNpad"},
+ {307, nullptr, "GetNpadSystemExtStyle"},
+ {308, &IHidSystemServer::ApplyNpadSystemCommonPolicyFull, "ApplyNpadSystemCommonPolicyFull"},
+ {309, &IHidSystemServer::GetNpadFullKeyGripColor, "GetNpadFullKeyGripColor"},
+ {310, &IHidSystemServer::GetMaskedSupportedNpadStyleSet, "GetMaskedSupportedNpadStyleSet"},
+ {311, nullptr, "SetNpadPlayerLedBlinkingDevice"},
+ {312, &IHidSystemServer::SetSupportedNpadStyleSetAll, "SetSupportedNpadStyleSetAll"},
+ {313, nullptr, "GetNpadCaptureButtonAssignment"},
+ {314, nullptr, "GetAppletFooterUiType"},
+ {315, &IHidSystemServer::GetAppletDetailedUiType, "GetAppletDetailedUiType"},
+ {316, &IHidSystemServer::GetNpadInterfaceType, "GetNpadInterfaceType"},
+ {317, &IHidSystemServer::GetNpadLeftRightInterfaceType, "GetNpadLeftRightInterfaceType"},
+ {318, &IHidSystemServer::HasBattery, "HasBattery"},
+ {319, &IHidSystemServer::HasLeftRightBattery, "HasLeftRightBattery"},
+ {321, &IHidSystemServer::GetUniquePadsFromNpad, "GetUniquePadsFromNpad"},
+ {322, &IHidSystemServer::GetIrSensorState, "GetIrSensorState"},
+ {323, nullptr, "GetXcdHandleForNpadWithIrSensor"},
+ {324, nullptr, "GetUniquePadButtonSet"},
+ {325, nullptr, "GetUniquePadColor"},
+ {326, nullptr, "GetUniquePadAppletDetailedUiType"},
+ {327, nullptr, "GetAbstractedPadIdDataFromNpad"},
+ {328, nullptr, "AttachAbstractedPadToNpad"},
+ {329, nullptr, "DetachAbstractedPadAll"},
+ {330, nullptr, "CheckAbstractedPadConnection"},
+ {500, nullptr, "SetAppletResourceUserId"},
+ {501, nullptr, "RegisterAppletResourceUserId"},
+ {502, nullptr, "UnregisterAppletResourceUserId"},
+ {503, nullptr, "EnableAppletToGetInput"},
+ {504, nullptr, "SetAruidValidForVibration"},
+ {505, nullptr, "EnableAppletToGetSixAxisSensor"},
+ {506, nullptr, "EnableAppletToGetPadInput"},
+ {507, nullptr, "EnableAppletToGetTouchScreen"},
+ {510, nullptr, "SetVibrationMasterVolume"},
+ {511, nullptr, "GetVibrationMasterVolume"},
+ {512, nullptr, "BeginPermitVibrationSession"},
+ {513, nullptr, "EndPermitVibrationSession"},
+ {514, nullptr, "Unknown514"},
+ {520, nullptr, "EnableHandheldHids"},
+ {521, nullptr, "DisableHandheldHids"},
+ {522, nullptr, "SetJoyConRailEnabled"},
+ {523, nullptr, "IsJoyConRailEnabled"},
+ {524, nullptr, "IsHandheldHidsEnabled"},
+ {525, nullptr, "IsJoyConAttachedOnAllRail"},
+ {540, nullptr, "AcquirePlayReportControllerUsageUpdateEvent"},
+ {541, nullptr, "GetPlayReportControllerUsages"},
+ {542, nullptr, "AcquirePlayReportRegisteredDeviceUpdateEvent"},
+ {543, nullptr, "GetRegisteredDevicesOld"},
+ {544, &IHidSystemServer::AcquireConnectionTriggerTimeoutEvent, "AcquireConnectionTriggerTimeoutEvent"},
+ {545, nullptr, "SendConnectionTrigger"},
+ {546, &IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport, "AcquireDeviceRegisteredEventForControllerSupport"},
+ {547, nullptr, "GetAllowedBluetoothLinksCount"},
+ {548, &IHidSystemServer::GetRegisteredDevices, "GetRegisteredDevices"},
+ {549, nullptr, "GetConnectableRegisteredDevices"},
+ {700, nullptr, "ActivateUniquePad"},
+ {702, &IHidSystemServer::AcquireUniquePadConnectionEventHandle, "AcquireUniquePadConnectionEventHandle"},
+ {703, &IHidSystemServer::GetUniquePadIds, "GetUniquePadIds"},
+ {751, &IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"},
+ {800, nullptr, "ListSixAxisSensorHandles"},
+ {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"},
+ {802, nullptr, "ResetSixAxisSensorCalibrationValues"},
+ {803, nullptr, "StartSixAxisSensorUserCalibration"},
+ {804, nullptr, "CancelSixAxisSensorUserCalibration"},
+ {805, nullptr, "GetUniquePadBluetoothAddress"},
+ {806, nullptr, "DisconnectUniquePad"},
+ {807, nullptr, "GetUniquePadType"},
+ {808, nullptr, "GetUniquePadInterface"},
+ {809, nullptr, "GetUniquePadSerialNumber"},
+ {810, nullptr, "GetUniquePadControllerNumber"},
+ {811, nullptr, "GetSixAxisSensorUserCalibrationStage"},
+ {812, nullptr, "GetConsoleUniqueSixAxisSensorHandle"},
+ {821, nullptr, "StartAnalogStickManualCalibration"},
+ {822, nullptr, "RetryCurrentAnalogStickManualCalibrationStage"},
+ {823, nullptr, "CancelAnalogStickManualCalibration"},
+ {824, nullptr, "ResetAnalogStickManualCalibration"},
+ {825, nullptr, "GetAnalogStickState"},
+ {826, nullptr, "GetAnalogStickManualCalibrationStage"},
+ {827, nullptr, "IsAnalogStickButtonPressed"},
+ {828, nullptr, "IsAnalogStickInReleasePosition"},
+ {829, nullptr, "IsAnalogStickInCircumference"},
+ {830, nullptr, "SetNotificationLedPattern"},
+ {831, nullptr, "SetNotificationLedPatternWithTimeout"},
+ {832, nullptr, "PrepareHidsForNotificationWake"},
+ {850, &IHidSystemServer::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
+ {851, nullptr, "EnableUsbFullKeyController"},
+ {852, nullptr, "IsUsbConnected"},
+ {870, &IHidSystemServer::IsHandheldButtonPressedOnConsoleMode, "IsHandheldButtonPressedOnConsoleMode"},
+ {900, nullptr, "ActivateInputDetector"},
+ {901, nullptr, "NotifyInputDetector"},
+ {1000, &IHidSystemServer::InitializeFirmwareUpdate, "InitializeFirmwareUpdate"},
+ {1001, nullptr, "GetFirmwareVersion"},
+ {1002, nullptr, "GetAvailableFirmwareVersion"},
+ {1003, nullptr, "IsFirmwareUpdateAvailable"},
+ {1004, nullptr, "CheckFirmwareUpdateRequired"},
+ {1005, nullptr, "StartFirmwareUpdate"},
+ {1006, nullptr, "AbortFirmwareUpdate"},
+ {1007, nullptr, "GetFirmwareUpdateState"},
+ {1008, nullptr, "ActivateAudioControl"},
+ {1009, nullptr, "AcquireAudioControlEventHandle"},
+ {1010, nullptr, "GetAudioControlStates"},
+ {1011, nullptr, "DeactivateAudioControl"},
+ {1050, nullptr, "IsSixAxisSensorAccurateUserCalibrationSupported"},
+ {1051, nullptr, "StartSixAxisSensorAccurateUserCalibration"},
+ {1052, nullptr, "CancelSixAxisSensorAccurateUserCalibration"},
+ {1053, nullptr, "GetSixAxisSensorAccurateUserCalibrationState"},
+ {1100, nullptr, "GetHidbusSystemServiceObject"},
+ {1120, nullptr, "SetFirmwareHotfixUpdateSkipEnabled"},
+ {1130, nullptr, "InitializeUsbFirmwareUpdate"},
+ {1131, nullptr, "FinalizeUsbFirmwareUpdate"},
+ {1132, nullptr, "CheckUsbFirmwareUpdateRequired"},
+ {1133, nullptr, "StartUsbFirmwareUpdate"},
+ {1134, nullptr, "GetUsbFirmwareUpdateState"},
+ {1135, &IHidSystemServer::InitializeUsbFirmwareUpdateWithoutMemory, "InitializeUsbFirmwareUpdateWithoutMemory"},
+ {1150, nullptr, "SetTouchScreenMagnification"},
+ {1151, nullptr, "GetTouchScreenFirmwareVersion"},
+ {1152, nullptr, "SetTouchScreenDefaultConfiguration"},
+ {1153, &IHidSystemServer::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"},
+ {1154, nullptr, "IsFirmwareAvailableForNotification"},
+ {1155, nullptr, "SetForceHandheldStyleVibration"},
+ {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"},
+ {1157, nullptr, "CancelConnectionTrigger"},
+ {1200, nullptr, "IsButtonConfigSupported"},
+ {1201, nullptr, "IsButtonConfigEmbeddedSupported"},
+ {1202, nullptr, "DeleteButtonConfig"},
+ {1203, nullptr, "DeleteButtonConfigEmbedded"},
+ {1204, nullptr, "SetButtonConfigEnabled"},
+ {1205, nullptr, "SetButtonConfigEmbeddedEnabled"},
+ {1206, nullptr, "IsButtonConfigEnabled"},
+ {1207, nullptr, "IsButtonConfigEmbeddedEnabled"},
+ {1208, nullptr, "SetButtonConfigEmbedded"},
+ {1209, nullptr, "SetButtonConfigFull"},
+ {1210, nullptr, "SetButtonConfigLeft"},
+ {1211, nullptr, "SetButtonConfigRight"},
+ {1212, nullptr, "GetButtonConfigEmbedded"},
+ {1213, nullptr, "GetButtonConfigFull"},
+ {1214, nullptr, "GetButtonConfigLeft"},
+ {1215, nullptr, "GetButtonConfigRight"},
+ {1250, nullptr, "IsCustomButtonConfigSupported"},
+ {1251, nullptr, "IsDefaultButtonConfigEmbedded"},
+ {1252, nullptr, "IsDefaultButtonConfigFull"},
+ {1253, nullptr, "IsDefaultButtonConfigLeft"},
+ {1254, nullptr, "IsDefaultButtonConfigRight"},
+ {1255, nullptr, "IsButtonConfigStorageEmbeddedEmpty"},
+ {1256, nullptr, "IsButtonConfigStorageFullEmpty"},
+ {1257, nullptr, "IsButtonConfigStorageLeftEmpty"},
+ {1258, nullptr, "IsButtonConfigStorageRightEmpty"},
+ {1259, nullptr, "GetButtonConfigStorageEmbeddedDeprecated"},
+ {1260, nullptr, "GetButtonConfigStorageFullDeprecated"},
+ {1261, nullptr, "GetButtonConfigStorageLeftDeprecated"},
+ {1262, nullptr, "GetButtonConfigStorageRightDeprecated"},
+ {1263, nullptr, "SetButtonConfigStorageEmbeddedDeprecated"},
+ {1264, nullptr, "SetButtonConfigStorageFullDeprecated"},
+ {1265, nullptr, "SetButtonConfigStorageLeftDeprecated"},
+ {1266, nullptr, "SetButtonConfigStorageRightDeprecated"},
+ {1267, nullptr, "DeleteButtonConfigStorageEmbedded"},
+ {1268, nullptr, "DeleteButtonConfigStorageFull"},
+ {1269, nullptr, "DeleteButtonConfigStorageLeft"},
+ {1270, nullptr, "DeleteButtonConfigStorageRight"},
+ {1271, nullptr, "IsUsingCustomButtonConfig"},
+ {1272, nullptr, "IsAnyCustomButtonConfigEnabled"},
+ {1273, nullptr, "SetAllCustomButtonConfigEnabled"},
+ {1274, nullptr, "SetDefaultButtonConfig"},
+ {1275, nullptr, "SetAllDefaultButtonConfig"},
+ {1276, nullptr, "SetHidButtonConfigEmbedded"},
+ {1277, nullptr, "SetHidButtonConfigFull"},
+ {1278, nullptr, "SetHidButtonConfigLeft"},
+ {1279, nullptr, "SetHidButtonConfigRight"},
+ {1280, nullptr, "GetHidButtonConfigEmbedded"},
+ {1281, nullptr, "GetHidButtonConfigFull"},
+ {1282, nullptr, "GetHidButtonConfigLeft"},
+ {1283, nullptr, "GetHidButtonConfigRight"},
+ {1284, nullptr, "GetButtonConfigStorageEmbedded"},
+ {1285, nullptr, "GetButtonConfigStorageFull"},
+ {1286, nullptr, "GetButtonConfigStorageLeft"},
+ {1287, nullptr, "GetButtonConfigStorageRight"},
+ {1288, nullptr, "SetButtonConfigStorageEmbedded"},
+ {1289, nullptr, "SetButtonConfigStorageFull"},
+ {1290, nullptr, "DeleteButtonConfigStorageRight"},
+ {1291, nullptr, "DeleteButtonConfigStorageRight"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ joy_detach_event = service_context.CreateEvent("IHidSystemServer::JoyDetachEvent");
+ acquire_device_registered_event =
+ service_context.CreateEvent("IHidSystemServer::AcquireDeviceRegisteredEvent");
+ acquire_connection_trigger_timeout_event =
+ service_context.CreateEvent("IHidSystemServer::AcquireConnectionTriggerTimeoutEvent");
+ unique_pad_connection_event =
+ service_context.CreateEvent("IHidSystemServer::AcquireUniquePadConnectionEventHandle");
+}
+
+IHidSystemServer::~IHidSystemServer() {
+ service_context.CloseEvent(joy_detach_event);
+ service_context.CloseEvent(acquire_device_registered_event);
+ service_context.CloseEvent(acquire_connection_trigger_timeout_event);
+ service_context.CloseEvent(unique_pad_connection_event);
+};
+
+void IHidSystemServer::ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "called");
+
+ GetResourceManager()->GetNpad()->ApplyNpadSystemCommonPolicy();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidSystemServer::EnableAssigningSingleOnSlSrPress(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidSystemServer::DisableAssigningSingleOnSlSrPress(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidSystemServer::GetLastActiveNpad(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "(STUBBED) called"); // Spams a lot when controller applet is running
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(system.HIDCore().GetLastActiveController());
+}
+
+void IHidSystemServer::ApplyNpadSystemCommonPolicyFull(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "called");
+
+ GetResourceManager()->GetNpad()->ApplyNpadSystemCommonPolicy();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidSystemServer::GetNpadFullKeyGripColor(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
+ npad_id_type); // Spams a lot when controller applet is running
+
+ Core::HID::NpadColor left_color{};
+ Core::HID::NpadColor right_color{};
+ // TODO: Get colors from Npad
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(left_color);
+ rb.PushRaw(right_color);
+}
+
+void IHidSystemServer::GetMaskedSupportedNpadStyleSet(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_INFO(Service_HID, "(STUBBED) called");
+
+ Core::HID::NpadStyleSet supported_styleset =
+ GetResourceManager()->GetNpad()->GetSupportedStyleSet().raw;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(supported_styleset);
+}
+
+void IHidSystemServer::SetSupportedNpadStyleSetAll(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_INFO(Service_HID, "(STUBBED) called");
+
+ Core::HID::NpadStyleSet supported_styleset =
+ GetResourceManager()->GetNpad()->GetSupportedStyleSet().raw;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(supported_styleset);
+}
+
+void IHidSystemServer::GetAppletDetailedUiType(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ LOG_DEBUG(Service_HID, "called, npad_id_type={}",
+ npad_id_type); // Spams a lot when controller applet is running
+
+ const NPad::AppletDetailedUiType detailed_ui_type =
+ GetResourceManager()->GetNpad()->GetAppletDetailedUiType(npad_id_type);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(detailed_ui_type);
+}
+
+void IHidSystemServer::GetNpadInterfaceType(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
+ npad_id_type); // Spams a lot when controller applet is running
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(Core::HID::NpadInterfaceType::Bluetooth);
+}
+
+void IHidSystemServer::GetNpadLeftRightInterfaceType(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
+ npad_id_type); // Spams a lot when controller applet is running
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(Core::HID::NpadInterfaceType::Bluetooth);
+ rb.PushEnum(Core::HID::NpadInterfaceType::Bluetooth);
+}
+
+void IHidSystemServer::HasBattery(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
+ npad_id_type); // Spams a lot when controller applet is running
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(false);
+}
+
+void IHidSystemServer::HasLeftRightBattery(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
+ npad_id_type); // Spams a lot when controller applet is running
+
+ struct LeftRightBattery {
+ bool left;
+ bool right;
+ };
+
+ LeftRightBattery left_right_battery{
+ .left = false,
+ .right = false,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(left_right_battery);
+}
+
+void IHidSystemServer::GetUniquePadsFromNpad(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
+
+ LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
+ npad_id_type); // Spams a lot when controller applet is running
+
+ const std::vector<Core::HID::UniquePadId> unique_pads{};
+
+ if (!unique_pads.empty()) {
+ ctx.WriteBuffer(unique_pads);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(unique_pads.size()));
+}
+
+void IHidSystemServer::GetIrSensorState(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidSystemServer::AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(acquire_device_registered_event->GetReadableEvent());
+}
+
+void IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx) {
+ LOG_INFO(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(acquire_device_registered_event->GetReadableEvent());
+}
+
+void IHidSystemServer::GetRegisteredDevices(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ struct RegisterData {
+ std::array<u8, 0x68> data;
+ };
+ static_assert(sizeof(RegisterData) == 0x68, "RegisterData is an invalid size");
+ std::vector<RegisterData> registered_devices{};
+
+ if (!registered_devices.empty()) {
+ ctx.WriteBuffer(registered_devices);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(registered_devices.size());
+}
+
+void IHidSystemServer::AcquireUniquePadConnectionEventHandle(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.PushCopyObjects(unique_pad_connection_event->GetReadableEvent());
+ rb.Push(ResultSuccess);
+}
+
+void IHidSystemServer::GetUniquePadIds(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(0);
+}
+
+void IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(joy_detach_event->GetReadableEvent());
+}
+
+void IHidSystemServer::IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
+ const bool is_enabled = false;
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(is_enabled);
+}
+
+void IHidSystemServer::IsHandheldButtonPressedOnConsoleMode(HLERequestContext& ctx) {
+ const bool button_pressed = false;
+
+ LOG_DEBUG(Service_HID, "(STUBBED) called, is_enabled={}",
+ button_pressed); // Spams a lot when controller applet is open
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(button_pressed);
+}
+
+void IHidSystemServer::InitializeFirmwareUpdate(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidSystemServer::InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IHidSystemServer::GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ Core::HID::TouchScreenConfigurationForNx touchscreen_config{
+ .mode = Core::HID::TouchScreenModeForNx::Finger,
+ };
+
+ if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 &&
+ touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) {
+ touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(touchscreen_config);
+}
+
+std::shared_ptr<ResourceManager> IHidSystemServer::GetResourceManager() {
+ resource_manager->Initialize();
+ return resource_manager;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_system_server.h b/src/core/hle/service/hid/hid_system_server.h
new file mode 100644
index 000000000..822d5e5b9
--- /dev/null
+++ b/src/core/hle/service/hid/hid_system_server.h
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KEvent;
+}
+
+namespace Service::HID {
+class ResourceManager;
+
+class IHidSystemServer final : public ServiceFramework<IHidSystemServer> {
+public:
+ explicit IHidSystemServer(Core::System& system_, std::shared_ptr<ResourceManager> resource);
+ ~IHidSystemServer() override;
+
+private:
+ void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx);
+ void EnableAssigningSingleOnSlSrPress(HLERequestContext& ctx);
+ void DisableAssigningSingleOnSlSrPress(HLERequestContext& ctx);
+ void GetLastActiveNpad(HLERequestContext& ctx);
+ void ApplyNpadSystemCommonPolicyFull(HLERequestContext& ctx);
+ void GetNpadFullKeyGripColor(HLERequestContext& ctx);
+ void GetMaskedSupportedNpadStyleSet(HLERequestContext& ctx);
+ void SetSupportedNpadStyleSetAll(HLERequestContext& ctx);
+ void GetAppletDetailedUiType(HLERequestContext& ctx);
+ void GetNpadInterfaceType(HLERequestContext& ctx);
+ void GetNpadLeftRightInterfaceType(HLERequestContext& ctx);
+ void HasBattery(HLERequestContext& ctx);
+ void HasLeftRightBattery(HLERequestContext& ctx);
+ void GetUniquePadsFromNpad(HLERequestContext& ctx);
+ void GetIrSensorState(HLERequestContext& ctx);
+ void AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx);
+ void AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx);
+ void GetRegisteredDevices(HLERequestContext& ctx);
+ void AcquireUniquePadConnectionEventHandle(HLERequestContext& ctx);
+ void GetUniquePadIds(HLERequestContext& ctx);
+ void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx);
+ void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx);
+ void IsHandheldButtonPressedOnConsoleMode(HLERequestContext& ctx);
+ void InitializeFirmwareUpdate(HLERequestContext& ctx);
+ void InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx);
+ void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx);
+
+ std::shared_ptr<ResourceManager> GetResourceManager();
+
+ Kernel::KEvent* acquire_connection_trigger_timeout_event;
+ Kernel::KEvent* acquire_device_registered_event;
+ Kernel::KEvent* joy_detach_event;
+ Kernel::KEvent* unique_pad_connection_event;
+ KernelHelpers::ServiceContext service_context;
+ std::shared_ptr<ResourceManager> resource_manager;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_util.h b/src/core/hle/service/hid/hid_util.h
new file mode 100644
index 000000000..b87cc10e3
--- /dev/null
+++ b/src/core/hle/service/hid/hid_util.h
@@ -0,0 +1,146 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hid/hid_types.h"
+#include "core/hle/service/hid/errors.h"
+
+namespace Service::HID {
+
+constexpr bool IsNpadIdValid(const Core::HID::NpadIdType npad_id) {
+ switch (npad_id) {
+ case Core::HID::NpadIdType::Player1:
+ case Core::HID::NpadIdType::Player2:
+ case Core::HID::NpadIdType::Player3:
+ case Core::HID::NpadIdType::Player4:
+ case Core::HID::NpadIdType::Player5:
+ case Core::HID::NpadIdType::Player6:
+ case Core::HID::NpadIdType::Player7:
+ case Core::HID::NpadIdType::Player8:
+ case Core::HID::NpadIdType::Other:
+ case Core::HID::NpadIdType::Handheld:
+ return true;
+ default:
+ return false;
+ }
+}
+
+constexpr Result IsSixaxisHandleValid(const Core::HID::SixAxisSensorHandle& handle) {
+ const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(handle.npad_id));
+ const bool device_index = handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
+
+ if (!npad_id) {
+ return InvalidNpadId;
+ }
+ if (!device_index) {
+ return NpadDeviceIndexOutOfRange;
+ }
+
+ return ResultSuccess;
+}
+
+constexpr Result IsVibrationHandleValid(const Core::HID::VibrationDeviceHandle& handle) {
+ switch (handle.npad_type) {
+ case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Handheld:
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ case Core::HID::NpadStyleIndex::GameCube:
+ case Core::HID::NpadStyleIndex::N64:
+ case Core::HID::NpadStyleIndex::SystemExt:
+ case Core::HID::NpadStyleIndex::System:
+ // These support vibration
+ break;
+ default:
+ return VibrationInvalidStyleIndex;
+ }
+
+ if (!IsNpadIdValid(static_cast<Core::HID::NpadIdType>(handle.npad_id))) {
+ return VibrationInvalidNpadId;
+ }
+
+ if (handle.device_index >= Core::HID::DeviceIndex::MaxDeviceIndex) {
+ return VibrationDeviceIndexOutOfRange;
+ }
+
+ return ResultSuccess;
+}
+
+/// Converts a Core::HID::NpadIdType to an array index.
+constexpr size_t NpadIdTypeToIndex(Core::HID::NpadIdType npad_id_type) {
+ switch (npad_id_type) {
+ case Core::HID::NpadIdType::Player1:
+ return 0;
+ case Core::HID::NpadIdType::Player2:
+ return 1;
+ case Core::HID::NpadIdType::Player3:
+ return 2;
+ case Core::HID::NpadIdType::Player4:
+ return 3;
+ case Core::HID::NpadIdType::Player5:
+ return 4;
+ case Core::HID::NpadIdType::Player6:
+ return 5;
+ case Core::HID::NpadIdType::Player7:
+ return 6;
+ case Core::HID::NpadIdType::Player8:
+ return 7;
+ case Core::HID::NpadIdType::Handheld:
+ return 8;
+ case Core::HID::NpadIdType::Other:
+ return 9;
+ default:
+ return 8;
+ }
+}
+
+/// Converts an array index to a Core::HID::NpadIdType
+constexpr Core::HID::NpadIdType IndexToNpadIdType(size_t index) {
+ switch (index) {
+ case 0:
+ return Core::HID::NpadIdType::Player1;
+ case 1:
+ return Core::HID::NpadIdType::Player2;
+ case 2:
+ return Core::HID::NpadIdType::Player3;
+ case 3:
+ return Core::HID::NpadIdType::Player4;
+ case 4:
+ return Core::HID::NpadIdType::Player5;
+ case 5:
+ return Core::HID::NpadIdType::Player6;
+ case 6:
+ return Core::HID::NpadIdType::Player7;
+ case 7:
+ return Core::HID::NpadIdType::Player8;
+ case 8:
+ return Core::HID::NpadIdType::Handheld;
+ case 9:
+ return Core::HID::NpadIdType::Other;
+ default:
+ return Core::HID::NpadIdType::Invalid;
+ }
+}
+
+constexpr Core::HID::NpadStyleSet GetStylesetByIndex(std::size_t index) {
+ switch (index) {
+ case 0:
+ return Core::HID::NpadStyleSet::Fullkey;
+ case 1:
+ return Core::HID::NpadStyleSet::Handheld;
+ case 2:
+ return Core::HID::NpadStyleSet::JoyDual;
+ case 3:
+ return Core::HID::NpadStyleSet::JoyLeft;
+ case 4:
+ return Core::HID::NpadStyleSet::JoyRight;
+ case 5:
+ return Core::HID::NpadStyleSet::Palma;
+ default:
+ return Core::HID::NpadStyleSet::None;
+ }
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
index ee522c36e..8c44f93e8 100644
--- a/src/core/hle/service/hid/hidbus/hidbus_base.cpp
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
@@ -13,7 +13,10 @@ HidbusBase::HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& ser
: system(system_), service_context(service_context_) {
send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
}
-HidbusBase::~HidbusBase() = default;
+
+HidbusBase::~HidbusBase() {
+ service_context.CloseEvent(send_command_async_event);
+};
void HidbusBase::ActivateDevice() {
if (is_activated) {
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 221c33b86..39b9a4474 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -12,6 +12,7 @@
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hid/errors.h"
+#include "core/hle/service/hid/hid_util.h"
#include "core/hle/service/hid/irs.h"
#include "core/hle/service/hid/irsensor/clustering_processor.h"
#include "core/hle/service/hid/irsensor/image_transfer_processor.h"
@@ -138,7 +139,7 @@ void IRS::RunMomentProcessor(HLERequestContext& ctx) {
if (result.IsSuccess()) {
auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
- MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
+ MakeProcessorWithCoreContext<MomentProcessor>(parameters.camera_handle, device);
auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
image_transfer_processor.SetConfig(parameters.processor_config);
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
@@ -320,7 +321,7 @@ void IRS::GetNpadIrCameraHandle(HLERequestContext& ctx) {
}
Core::IrSensor::IrCameraHandle camera_handle{
- .npad_id = static_cast<u8>(NpadIdTypeToIndex(npad_id)),
+ .npad_id = static_cast<u8>(HID::NpadIdTypeToIndex(npad_id)),
.npad_type = Core::HID::NpadStyleIndex::None,
};
@@ -545,7 +546,7 @@ void IRS::ActivateIrsensorWithFunctionLevel(HLERequestContext& ctx) {
Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const {
if (camera_handle.npad_id >
- static_cast<u8>(NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) {
+ static_cast<u8>(HID::NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) {
return InvalidIrCameraHandle;
}
if (camera_handle.npad_type != Core::HID::NpadStyleIndex::None) {
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index a8fa19025..c8e6dab17 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -3,15 +3,12 @@
#pragma once
+#include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hid/irs_types.h"
#include "core/hle/service/hid/irsensor/processor_base.h"
#include "core/hle/service/service.h"
-namespace Core {
-class System;
-}
-
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.cpp b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
index e2f4ae876..c559eb0d5 100644
--- a/src/core/hle/service/hid/irsensor/clustering_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
@@ -3,16 +3,18 @@
#include <queue>
+#include "core/core.h"
+#include "core/core_timing.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/irsensor/clustering_processor.h"
namespace Service::IRS {
-ClusteringProcessor::ClusteringProcessor(Core::HID::HIDCore& hid_core_,
+ClusteringProcessor::ClusteringProcessor(Core::System& system_,
Core::IrSensor::DeviceFormat& device_format,
std::size_t npad_index)
- : device{device_format} {
- npad_device = hid_core_.GetEmulatedControllerByIndex(npad_index);
+ : device{device_format}, system{system_} {
+ npad_device = system.HIDCore().GetEmulatedControllerByIndex(npad_index);
device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor;
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
@@ -48,7 +50,7 @@ void ClusteringProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType ty
}
next_state = {};
- const auto camera_data = npad_device->GetCamera();
+ const auto& camera_data = npad_device->GetCamera();
auto filtered_image = camera_data.data;
RemoveLowIntensityData(filtered_image);
@@ -83,7 +85,7 @@ void ClusteringProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType ty
}
next_state.sampling_number = camera_data.sample;
- next_state.timestamp = next_state.timestamp + 131;
+ next_state.timestamp = system.CoreTiming().GetGlobalTimeNs().count();
next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
shared_memory->clustering_lifo.WriteNextEntry(next_state);
@@ -202,14 +204,14 @@ ClusteringProcessor::ClusteringData ClusteringProcessor::MergeCluster(
}
u8 ClusteringProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const {
- if ((y * width) + x > data.size()) {
+ if ((y * width) + x >= data.size()) {
return 0;
}
return data[(y * width) + x];
}
void ClusteringProcessor::SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value) {
- if ((y * width) + x > data.size()) {
+ if ((y * width) + x >= data.size()) {
return;
}
data[(y * width) + x] = value;
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.h b/src/core/hle/service/hid/irsensor/clustering_processor.h
index dc01a8ea7..83f34734a 100644
--- a/src/core/hle/service/hid/irsensor/clustering_processor.h
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.h
@@ -8,6 +8,10 @@
#include "core/hle/service/hid/irs_ring_lifo.h"
#include "core/hle/service/hid/irsensor/processor_base.h"
+namespace Core {
+class System;
+}
+
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
@@ -15,8 +19,7 @@ class EmulatedController;
namespace Service::IRS {
class ClusteringProcessor final : public ProcessorBase {
public:
- explicit ClusteringProcessor(Core::HID::HIDCore& hid_core_,
- Core::IrSensor::DeviceFormat& device_format,
+ explicit ClusteringProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
std::size_t npad_index);
~ClusteringProcessor() override;
@@ -106,5 +109,7 @@ private:
Core::IrSensor::DeviceFormat& device;
Core::HID::EmulatedController* npad_device;
int callback_key{};
+
+ Core::System& system;
};
} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
index 803a6277c..22067a591 100644
--- a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
@@ -49,7 +49,7 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType
return;
}
- const auto camera_data = npad_device->GetCamera();
+ const auto& camera_data = npad_device->GetCamera();
// This indicates how much ambient light is present
processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.cpp b/src/core/hle/service/hid/irsensor/moment_processor.cpp
index dbaca420a..cf045bda7 100644
--- a/src/core/hle/service/hid/irsensor/moment_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/moment_processor.cpp
@@ -1,24 +1,137 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
#include "core/hle/service/hid/irsensor/moment_processor.h"
namespace Service::IRS {
-MomentProcessor::MomentProcessor(Core::IrSensor::DeviceFormat& device_format)
- : device(device_format) {
+static constexpr auto format = Core::IrSensor::ImageTransferProcessorFormat::Size40x30;
+static constexpr std::size_t ImageWidth = 40;
+static constexpr std::size_t ImageHeight = 30;
+
+MomentProcessor::MomentProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
+ std::size_t npad_index)
+ : device(device_format), system{system_} {
+ npad_device = system.HIDCore().GetEmulatedControllerByIndex(npad_index);
+
device.mode = Core::IrSensor::IrSensorMode::MomentProcessor;
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+
+ shared_memory = std::construct_at(
+ reinterpret_cast<MomentSharedMemory*>(&device_format.state.processor_raw_data));
+
+ Core::HID::ControllerUpdateCallback engine_callback{
+ .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
+ .is_npad_service = true,
+ };
+ callback_key = npad_device->SetCallback(engine_callback);
}
-MomentProcessor::~MomentProcessor() = default;
+MomentProcessor::~MomentProcessor() {
+ npad_device->DeleteCallback(callback_key);
+};
-void MomentProcessor::StartProcessor() {}
+void MomentProcessor::StartProcessor() {
+ device.camera_status = Core::IrSensor::IrCameraStatus::Available;
+ device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
+}
void MomentProcessor::SuspendProcessor() {}
void MomentProcessor::StopProcessor() {}
+void MomentProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
+ if (type != Core::HID::ControllerTriggerType::IrSensor) {
+ return;
+ }
+
+ next_state = {};
+ const auto& camera_data = npad_device->GetCamera();
+
+ const auto window_width = static_cast<std::size_t>(current_config.window_of_interest.width);
+ const auto window_height = static_cast<std::size_t>(current_config.window_of_interest.height);
+ const auto window_start_x = static_cast<std::size_t>(current_config.window_of_interest.x);
+ const auto window_start_y = static_cast<std::size_t>(current_config.window_of_interest.y);
+
+ const std::size_t block_width = window_width / Columns;
+ const std::size_t block_height = window_height / Rows;
+
+ for (std::size_t row = 0; row < Rows; row++) {
+ for (std::size_t column = 0; column < Columns; column++) {
+ const size_t x_pos = (column * block_width) + window_start_x;
+ const size_t y_pos = (row * block_height) + window_start_y;
+ auto& statistic = next_state.statistic[column + (row * Columns)];
+ statistic = GetStatistic(camera_data.data, x_pos, y_pos, block_width, block_height);
+ }
+ }
+
+ next_state.sampling_number = camera_data.sample;
+ next_state.timestamp = system.CoreTiming().GetGlobalTimeNs().count();
+ next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
+ shared_memory->moment_lifo.WriteNextEntry(next_state);
+
+ if (!IsProcessorActive()) {
+ StartProcessor();
+ }
+}
+
+u8 MomentProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const {
+ if ((y * ImageWidth) + x >= data.size()) {
+ return 0;
+ }
+ return data[(y * ImageWidth) + x];
+}
+
+MomentProcessor::MomentStatistic MomentProcessor::GetStatistic(const std::vector<u8>& data,
+ std::size_t start_x,
+ std::size_t start_y,
+ std::size_t width,
+ std::size_t height) const {
+ // The actual implementation is always 320x240
+ static constexpr std::size_t RealWidth = 320;
+ static constexpr std::size_t RealHeight = 240;
+ static constexpr std::size_t Threshold = 30;
+ MomentStatistic statistic{};
+ std::size_t active_points{};
+
+ // Sum all data points on the block that meet with the threshold
+ for (std::size_t y = 0; y < width; y++) {
+ for (std::size_t x = 0; x < height; x++) {
+ const size_t x_pos = x + start_x;
+ const size_t y_pos = y + start_y;
+ const auto pixel =
+ GetPixel(data, x_pos * ImageWidth / RealWidth, y_pos * ImageHeight / RealHeight);
+
+ if (pixel < Threshold) {
+ continue;
+ }
+
+ statistic.average_intensity += pixel;
+
+ statistic.centroid.x += static_cast<float>(x_pos);
+ statistic.centroid.y += static_cast<float>(y_pos);
+
+ active_points++;
+ }
+ }
+
+ // Return an empty field if no points were available
+ if (active_points == 0) {
+ return {};
+ }
+
+ // Finally calculate the actual centroid and average intensity
+ statistic.centroid.x /= static_cast<float>(active_points);
+ statistic.centroid.y /= static_cast<float>(active_points);
+ statistic.average_intensity /= static_cast<f32>(width * height);
+
+ return statistic;
+}
+
void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig config) {
current_config.camera_config.exposure_time = config.camera_config.exposure_time;
current_config.camera_config.gain = config.camera_config.gain;
@@ -29,6 +142,8 @@ void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig conf
current_config.preprocess =
static_cast<Core::IrSensor::MomentProcessorPreprocess>(config.preprocess);
current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold;
+
+ npad_device->SetCameraFormat(format);
}
} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.h b/src/core/hle/service/hid/irsensor/moment_processor.h
index d4bd22e0f..398cfbdc1 100644
--- a/src/core/hle/service/hid/irsensor/moment_processor.h
+++ b/src/core/hle/service/hid/irsensor/moment_processor.h
@@ -6,12 +6,22 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irs_ring_lifo.h"
#include "core/hle/service/hid/irsensor/processor_base.h"
+namespace Core {
+class System;
+}
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
namespace Service::IRS {
class MomentProcessor final : public ProcessorBase {
public:
- explicit MomentProcessor(Core::IrSensor::DeviceFormat& device_format);
+ explicit MomentProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
+ std::size_t npad_index);
~MomentProcessor() override;
// Called when the processor is initialized
@@ -27,6 +37,9 @@ public:
void SetConfig(Core::IrSensor::PackedMomentProcessorConfig config);
private:
+ static constexpr std::size_t Columns = 8;
+ static constexpr std::size_t Rows = 6;
+
// This is nn::irsensor::MomentProcessorConfig
struct MomentProcessorConfig {
Core::IrSensor::CameraConfig camera_config;
@@ -50,12 +63,29 @@ private:
u64 timestamp;
Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
INSERT_PADDING_BYTES(4);
- std::array<MomentStatistic, 0x30> stadistic;
+ std::array<MomentStatistic, Columns * Rows> statistic;
};
static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size");
+ struct MomentSharedMemory {
+ Service::IRS::Lifo<MomentProcessorState, 6> moment_lifo;
+ };
+ static_assert(sizeof(MomentSharedMemory) == 0xE20, "MomentSharedMemory is an invalid size");
+
+ void OnControllerUpdate(Core::HID::ControllerTriggerType type);
+ u8 GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const;
+ MomentStatistic GetStatistic(const std::vector<u8>& data, std::size_t start_x,
+ std::size_t start_y, std::size_t width, std::size_t height) const;
+
+ MomentSharedMemory* shared_memory = nullptr;
+ MomentProcessorState next_state{};
+
MomentProcessorConfig current_config{};
Core::IrSensor::DeviceFormat& device;
+ Core::HID::EmulatedController* npad_device;
+ int callback_key{};
+
+ Core::System& system;
};
} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/resource_manager.cpp b/src/core/hle/service/hid/resource_manager.cpp
new file mode 100644
index 000000000..e76d4eea9
--- /dev/null
+++ b/src/core/hle/service/hid/resource_manager.cpp
@@ -0,0 +1,241 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/service/hid/resource_manager.h"
+#include "core/hle/service/ipc_helpers.h"
+
+#include "core/hle/service/hid/controllers/console_six_axis.h"
+#include "core/hle/service/hid/controllers/debug_pad.h"
+#include "core/hle/service/hid/controllers/gesture.h"
+#include "core/hle/service/hid/controllers/keyboard.h"
+#include "core/hle/service/hid/controllers/mouse.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/palma.h"
+#include "core/hle/service/hid/controllers/seven_six_axis.h"
+#include "core/hle/service/hid/controllers/six_axis.h"
+#include "core/hle/service/hid/controllers/stubbed.h"
+#include "core/hle/service/hid/controllers/touchscreen.h"
+#include "core/hle/service/hid/controllers/xpad.h"
+
+namespace Service::HID {
+
+// Updating period for each HID device.
+// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
+// Correct npad_update_ns is 4ms this is overclocked to lower input lag
+constexpr auto npad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
+constexpr auto default_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 1000Hz)
+constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
+constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
+
+ResourceManager::ResourceManager(Core::System& system_)
+ : system{system_}, service_context{system_, "hid"} {}
+
+ResourceManager::~ResourceManager() = default;
+
+void ResourceManager::Initialize() {
+ if (is_initialized) {
+ return;
+ }
+
+ u8* shared_memory = system.Kernel().GetHidSharedMem().GetPointer();
+ debug_pad = std::make_shared<DebugPad>(system.HIDCore(), shared_memory);
+ mouse = std::make_shared<Mouse>(system.HIDCore(), shared_memory);
+ debug_mouse = std::make_shared<DebugMouse>(system.HIDCore(), shared_memory);
+ keyboard = std::make_shared<Keyboard>(system.HIDCore(), shared_memory);
+ unique_pad = std::make_shared<UniquePad>(system.HIDCore(), shared_memory);
+ npad = std::make_shared<NPad>(system.HIDCore(), shared_memory, service_context);
+ gesture = std::make_shared<Gesture>(system.HIDCore(), shared_memory);
+ touch_screen = std::make_shared<TouchScreen>(system.HIDCore(), shared_memory);
+ xpad = std::make_shared<XPad>(system.HIDCore(), shared_memory);
+
+ palma = std::make_shared<Palma>(system.HIDCore(), shared_memory, service_context);
+
+ home_button = std::make_shared<HomeButton>(system.HIDCore(), shared_memory);
+ sleep_button = std::make_shared<SleepButton>(system.HIDCore(), shared_memory);
+ capture_button = std::make_shared<CaptureButton>(system.HIDCore(), shared_memory);
+
+ six_axis = std::make_shared<SixAxis>(system.HIDCore(), npad);
+ console_six_axis = std::make_shared<ConsoleSixAxis>(system.HIDCore(), shared_memory);
+ seven_six_axis = std::make_shared<SevenSixAxis>(system);
+
+ home_button->SetCommonHeaderOffset(0x4C00);
+ sleep_button->SetCommonHeaderOffset(0x4E00);
+ capture_button->SetCommonHeaderOffset(0x5000);
+ unique_pad->SetCommonHeaderOffset(0x5A00);
+ debug_mouse->SetCommonHeaderOffset(0x3DC00);
+
+ // Homebrew doesn't try to activate some controllers, so we activate them by default
+ npad->Activate();
+ six_axis->Activate();
+ touch_screen->Activate();
+
+ system.HIDCore().ReloadInputDevices();
+ is_initialized = true;
+}
+std::shared_ptr<CaptureButton> ResourceManager::GetCaptureButton() const {
+ return capture_button;
+}
+
+std::shared_ptr<ConsoleSixAxis> ResourceManager::GetConsoleSixAxis() const {
+ return console_six_axis;
+}
+
+std::shared_ptr<DebugMouse> ResourceManager::GetDebugMouse() const {
+ return debug_mouse;
+}
+
+std::shared_ptr<DebugPad> ResourceManager::GetDebugPad() const {
+ return debug_pad;
+}
+
+std::shared_ptr<Gesture> ResourceManager::GetGesture() const {
+ return gesture;
+}
+
+std::shared_ptr<HomeButton> ResourceManager::GetHomeButton() const {
+ return home_button;
+}
+
+std::shared_ptr<Keyboard> ResourceManager::GetKeyboard() const {
+ return keyboard;
+}
+
+std::shared_ptr<Mouse> ResourceManager::GetMouse() const {
+ return mouse;
+}
+
+std::shared_ptr<NPad> ResourceManager::GetNpad() const {
+ return npad;
+}
+
+std::shared_ptr<Palma> ResourceManager::GetPalma() const {
+ return palma;
+}
+
+std::shared_ptr<SevenSixAxis> ResourceManager::GetSevenSixAxis() const {
+ return seven_six_axis;
+}
+
+std::shared_ptr<SixAxis> ResourceManager::GetSixAxis() const {
+ return six_axis;
+}
+
+std::shared_ptr<SleepButton> ResourceManager::GetSleepButton() const {
+ return sleep_button;
+}
+
+std::shared_ptr<TouchScreen> ResourceManager::GetTouchScreen() const {
+ return touch_screen;
+}
+
+std::shared_ptr<UniquePad> ResourceManager::GetUniquePad() const {
+ return unique_pad;
+}
+
+void ResourceManager::UpdateControllers(std::uintptr_t user_data,
+ std::chrono::nanoseconds ns_late) {
+ auto& core_timing = system.CoreTiming();
+ debug_pad->OnUpdate(core_timing);
+ unique_pad->OnUpdate(core_timing);
+ gesture->OnUpdate(core_timing);
+ touch_screen->OnUpdate(core_timing);
+ palma->OnUpdate(core_timing);
+ home_button->OnUpdate(core_timing);
+ sleep_button->OnUpdate(core_timing);
+ capture_button->OnUpdate(core_timing);
+ xpad->OnUpdate(core_timing);
+}
+
+void ResourceManager::UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ auto& core_timing = system.CoreTiming();
+ npad->OnUpdate(core_timing);
+}
+
+void ResourceManager::UpdateMouseKeyboard(std::uintptr_t user_data,
+ std::chrono::nanoseconds ns_late) {
+ auto& core_timing = system.CoreTiming();
+ mouse->OnUpdate(core_timing);
+ debug_mouse->OnUpdate(core_timing);
+ keyboard->OnUpdate(core_timing);
+}
+
+void ResourceManager::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ auto& core_timing = system.CoreTiming();
+ six_axis->OnUpdate(core_timing);
+ seven_six_axis->OnUpdate(core_timing);
+ console_six_axis->OnUpdate(core_timing);
+}
+
+IAppletResource::IAppletResource(Core::System& system_, std::shared_ptr<ResourceManager> resource)
+ : ServiceFramework{system_, "IAppletResource"} {
+ static const FunctionInfo functions[] = {
+ {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
+ };
+ RegisterHandlers(functions);
+
+ resource->Initialize();
+
+ // Register update callbacks
+ npad_update_event = Core::Timing::CreateEvent(
+ "HID::UpdatePadCallback",
+ [this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)
+ -> std::optional<std::chrono::nanoseconds> {
+ const auto guard = LockService();
+ resource->UpdateNpad(user_data, ns_late);
+ return std::nullopt;
+ });
+ default_update_event = Core::Timing::CreateEvent(
+ "HID::UpdateDefaultCallback",
+ [this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)
+ -> std::optional<std::chrono::nanoseconds> {
+ const auto guard = LockService();
+ resource->UpdateControllers(user_data, ns_late);
+ return std::nullopt;
+ });
+ mouse_keyboard_update_event = Core::Timing::CreateEvent(
+ "HID::UpdateMouseKeyboardCallback",
+ [this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)
+ -> std::optional<std::chrono::nanoseconds> {
+ const auto guard = LockService();
+ resource->UpdateMouseKeyboard(user_data, ns_late);
+ return std::nullopt;
+ });
+ motion_update_event = Core::Timing::CreateEvent(
+ "HID::UpdateMotionCallback",
+ [this, resource](std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)
+ -> std::optional<std::chrono::nanoseconds> {
+ const auto guard = LockService();
+ resource->UpdateMotion(user_data, ns_late);
+ return std::nullopt;
+ });
+
+ system.CoreTiming().ScheduleLoopingEvent(npad_update_ns, npad_update_ns, npad_update_event);
+ system.CoreTiming().ScheduleLoopingEvent(default_update_ns, default_update_ns,
+ default_update_event);
+ system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
+ mouse_keyboard_update_event);
+ system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
+ motion_update_event);
+}
+
+IAppletResource::~IAppletResource() {
+ system.CoreTiming().UnscheduleEvent(npad_update_event, 0);
+ system.CoreTiming().UnscheduleEvent(default_update_event, 0);
+ system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event, 0);
+ system.CoreTiming().UnscheduleEvent(motion_update_event, 0);
+}
+
+void IAppletResource::GetSharedMemoryHandle(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(&system.Kernel().GetHidSharedMem());
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/resource_manager.h b/src/core/hle/service/hid/resource_manager.h
new file mode 100644
index 000000000..2b6a9b5e6
--- /dev/null
+++ b/src/core/hle/service/hid/resource_manager.h
@@ -0,0 +1,111 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/service.h"
+
+namespace Core::Timing {
+struct EventType;
+}
+
+namespace Service::HID {
+class Controller_Stubbed;
+class ConsoleSixAxis;
+class DebugPad;
+class Gesture;
+class Keyboard;
+class Mouse;
+class NPad;
+class Palma;
+class SevenSixAxis;
+class SixAxis;
+class TouchScreen;
+class XPad;
+
+using CaptureButton = Controller_Stubbed;
+using DebugMouse = Controller_Stubbed;
+using HomeButton = Controller_Stubbed;
+using SleepButton = Controller_Stubbed;
+using UniquePad = Controller_Stubbed;
+
+class ResourceManager {
+
+public:
+ explicit ResourceManager(Core::System& system_);
+ ~ResourceManager();
+
+ void Initialize();
+
+ std::shared_ptr<CaptureButton> GetCaptureButton() const;
+ std::shared_ptr<ConsoleSixAxis> GetConsoleSixAxis() const;
+ std::shared_ptr<DebugMouse> GetDebugMouse() const;
+ std::shared_ptr<DebugPad> GetDebugPad() const;
+ std::shared_ptr<Gesture> GetGesture() const;
+ std::shared_ptr<HomeButton> GetHomeButton() const;
+ std::shared_ptr<Keyboard> GetKeyboard() const;
+ std::shared_ptr<Mouse> GetMouse() const;
+ std::shared_ptr<NPad> GetNpad() const;
+ std::shared_ptr<Palma> GetPalma() const;
+ std::shared_ptr<SevenSixAxis> GetSevenSixAxis() const;
+ std::shared_ptr<SixAxis> GetSixAxis() const;
+ std::shared_ptr<SleepButton> GetSleepButton() const;
+ std::shared_ptr<TouchScreen> GetTouchScreen() const;
+ std::shared_ptr<UniquePad> GetUniquePad() const;
+
+ void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
+ void UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
+ void UpdateMouseKeyboard(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
+ void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
+
+private:
+ bool is_initialized{false};
+
+ std::shared_ptr<CaptureButton> capture_button = nullptr;
+ std::shared_ptr<ConsoleSixAxis> console_six_axis = nullptr;
+ std::shared_ptr<DebugMouse> debug_mouse = nullptr;
+ std::shared_ptr<DebugPad> debug_pad = nullptr;
+ std::shared_ptr<Gesture> gesture = nullptr;
+ std::shared_ptr<HomeButton> home_button = nullptr;
+ std::shared_ptr<Keyboard> keyboard = nullptr;
+ std::shared_ptr<Mouse> mouse = nullptr;
+ std::shared_ptr<NPad> npad = nullptr;
+ std::shared_ptr<Palma> palma = nullptr;
+ std::shared_ptr<SevenSixAxis> seven_six_axis = nullptr;
+ std::shared_ptr<SixAxis> six_axis = nullptr;
+ std::shared_ptr<SleepButton> sleep_button = nullptr;
+ std::shared_ptr<TouchScreen> touch_screen = nullptr;
+ std::shared_ptr<UniquePad> unique_pad = nullptr;
+ std::shared_ptr<XPad> xpad = nullptr;
+
+ // TODO: Create these resources
+ // std::shared_ptr<AudioControl> audio_control = nullptr;
+ // std::shared_ptr<ButtonConfig> button_config = nullptr;
+ // std::shared_ptr<Config> config = nullptr;
+ // std::shared_ptr<Connection> connection = nullptr;
+ // std::shared_ptr<CustomConfig> custom_config = nullptr;
+ // std::shared_ptr<Digitizer> digitizer = nullptr;
+ // std::shared_ptr<Hdls> hdls = nullptr;
+ // std::shared_ptr<PlayReport> play_report = nullptr;
+ // std::shared_ptr<Rail> rail = nullptr;
+
+ Core::System& system;
+ KernelHelpers::ServiceContext service_context;
+};
+
+class IAppletResource final : public ServiceFramework<IAppletResource> {
+public:
+ explicit IAppletResource(Core::System& system_, std::shared_ptr<ResourceManager> resource);
+ ~IAppletResource() override;
+
+private:
+ void GetSharedMemoryHandle(HLERequestContext& ctx);
+
+ std::shared_ptr<Core::Timing::EventType> npad_update_event;
+ std::shared_ptr<Core::Timing::EventType> default_update_event;
+ std::shared_ptr<Core::Timing::EventType> mouse_keyboard_update_event;
+ std::shared_ptr<Core::Timing::EventType> motion_update_event;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/ring_lifo.h b/src/core/hle/service/hid/ring_lifo.h
index 65eb7ea02..0816784e0 100644
--- a/src/core/hle/service/hid/ring_lifo.h
+++ b/src/core/hle/service/hid/ring_lifo.h
@@ -32,15 +32,15 @@ struct Lifo {
}
std::size_t GetPreviousEntryIndex() const {
- return static_cast<size_t>((buffer_tail + total_buffer_count - 1) % total_buffer_count);
+ return static_cast<size_t>((buffer_tail + max_buffer_size - 1) % max_buffer_size);
}
std::size_t GetNextEntryIndex() const {
- return static_cast<size_t>((buffer_tail + 1) % total_buffer_count);
+ return static_cast<size_t>((buffer_tail + 1) % max_buffer_size);
}
void WriteNextEntry(const State& new_state) {
- if (buffer_count < total_buffer_count - 1) {
+ if (buffer_count < static_cast<s64>(max_buffer_size) - 1) {
buffer_count++;
}
buffer_tail = GetNextEntryIndex();
diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp
index 6a313a03b..f51e63564 100644
--- a/src/core/hle/service/kernel_helpers.cpp
+++ b/src/core/hle/service/kernel_helpers.cpp
@@ -21,10 +21,8 @@ ServiceContext::ServiceContext(Core::System& system_, std::string name_)
// Create the process.
process = Kernel::KProcess::Create(kernel);
- ASSERT(Kernel::KProcess::Initialize(process, system_, std::move(name_),
- Kernel::KProcess::ProcessType::KernelInternal,
- kernel.GetSystemResourceLimit())
- .IsSuccess());
+ ASSERT(R_SUCCEEDED(process->Initialize(Kernel::Svc::CreateProcessParameter{},
+ kernel.GetSystemResourceLimit(), false)));
// Register the process.
Kernel::KProcess::Register(kernel, process);
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 7927f8264..961f89a14 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -115,12 +115,20 @@ public:
{400, nullptr, "InitializeSystem"},
{401, nullptr, "FinalizeSystem"},
{402, nullptr, "SetOperationMode"},
- {403, nullptr, "InitializeSystem2"},
+ {403, &ISystemLocalCommunicationService::InitializeSystem2, "InitializeSystem2"},
};
// clang-format on
RegisterHandlers(functions);
}
+
+private:
+ void InitializeSystem2(HLERequestContext& ctx) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
};
class IUserLocalCommunicationService final
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index c73035c77..97b6a9385 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -286,9 +286,14 @@ public:
rb.Push(ResultSuccess);
}
- bool ValidateRegionForMap(Kernel::KPageTable& page_table, VAddr start, std::size_t size) const {
+ bool ValidateRegionForMap(Kernel::KProcessPageTable& page_table, VAddr start,
+ std::size_t size) const {
const std::size_t padding_size{page_table.GetNumGuardPages() * Kernel::PageSize};
- const auto start_info{page_table.QueryInfo(start - 1)};
+
+ Kernel::KMemoryInfo start_info;
+ Kernel::Svc::PageInfo page_info;
+ R_ASSERT(
+ page_table.QueryInfo(std::addressof(start_info), std::addressof(page_info), start - 1));
if (start_info.GetState() != Kernel::KMemoryState::Free) {
return {};
@@ -298,7 +303,9 @@ public:
return {};
}
- const auto end_info{page_table.QueryInfo(start + size)};
+ Kernel::KMemoryInfo end_info;
+ R_ASSERT(page_table.QueryInfo(std::addressof(end_info), std::addressof(page_info),
+ start + size));
if (end_info.GetState() != Kernel::KMemoryState::Free) {
return {};
@@ -307,7 +314,7 @@ public:
return (start + size + padding_size) <= (end_info.GetAddress() + end_info.GetSize());
}
- Result GetAvailableMapRegion(Kernel::KPageTable& page_table, u64 size, VAddr& out_addr) {
+ Result GetAvailableMapRegion(Kernel::KProcessPageTable& page_table, u64 size, VAddr& out_addr) {
size = Common::AlignUp(size, Kernel::PageSize);
size += page_table.GetNumGuardPages() * Kernel::PageSize * 4;
@@ -391,12 +398,8 @@ public:
if (bss_size) {
auto block_guard = detail::ScopeExit([&] {
- page_table.UnmapCodeMemory(
- addr + nro_size, bss_addr, bss_size,
- Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange);
- page_table.UnmapCodeMemory(
- addr, nro_addr, nro_size,
- Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange);
+ page_table.UnmapCodeMemory(addr + nro_size, bss_addr, bss_size);
+ page_table.UnmapCodeMemory(addr, nro_addr, nro_size);
});
const Result result{page_table.MapCodeMemory(addr + nro_size, bss_addr, bss_size)};
@@ -578,21 +581,17 @@ public:
auto& page_table{system.ApplicationProcess()->GetPageTable()};
if (info.bss_size != 0) {
- R_TRY(page_table.UnmapCodeMemory(
- info.nro_address + info.text_size + info.ro_size + info.data_size, info.bss_address,
- info.bss_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
+ R_TRY(page_table.UnmapCodeMemory(info.nro_address + info.text_size + info.ro_size +
+ info.data_size,
+ info.bss_address, info.bss_size));
}
- R_TRY(page_table.UnmapCodeMemory(
- info.nro_address + info.text_size + info.ro_size,
- info.src_addr + info.text_size + info.ro_size, info.data_size,
- Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
- R_TRY(page_table.UnmapCodeMemory(
- info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size,
- Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
- R_TRY(page_table.UnmapCodeMemory(
- info.nro_address, info.src_addr, info.text_size,
- Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange));
+ R_TRY(page_table.UnmapCodeMemory(info.nro_address + info.text_size + info.ro_size,
+ info.src_addr + info.text_size + info.ro_size,
+ info.data_size));
+ R_TRY(page_table.UnmapCodeMemory(info.nro_address + info.text_size,
+ info.src_addr + info.text_size, info.ro_size));
+ R_TRY(page_table.UnmapCodeMemory(info.nro_address, info.src_addr, info.text_size));
return ResultSuccess;
}
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp
index a019cc9f7..c27646fcf 100644
--- a/src/core/hle/service/mii/types/ver3_store_data.cpp
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -98,7 +98,7 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
}
void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
- version = 1;
+ version = 3;
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();
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index e7a00deb3..47516f883 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -401,6 +401,12 @@ Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& time
}
Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) {
+ bool is_corrupted = false;
+
+ if (model_type != NFP::ModelType::Amiibo) {
+ return ResultInvalidArgument;
+ }
+
if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ResultWrongDeviceState;
@@ -420,26 +426,32 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
if (is_plain_amiibo) {
std::vector<u8> data(sizeof(NFP::NTAG215File));
memcpy(data.data(), &tag_data, sizeof(tag_data));
- WriteBackupData(tag_data.uid, data);
+ }
- device_state = DeviceState::TagMounted;
- mount_target = mount_target_;
- return ResultSuccess;
+ if (!is_plain_amiibo && !NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
+ LOG_ERROR(Service_NFP, "Can't decode amiibo");
+ is_corrupted = true;
}
- if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
- bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
- LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
- return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
+ if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
+ LOG_ERROR(Service_NFP, "Invalid mii data");
+ is_corrupted = true;
}
- std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
- memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
- WriteBackupData(encrypted_tag_data.uuid, data);
+ if (!is_corrupted) {
+ std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
+ memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+ WriteBackupData(encrypted_tag_data.uuid, data);
+ }
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
+ if (is_corrupted) {
+ bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
+ return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
+ }
+
return ResultSuccess;
}
@@ -606,6 +618,17 @@ Result NfcDevice::Restore() {
}
}
+ // Restore mii data in case is corrupted by previous instances of yuzu
+ if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
+ LOG_ERROR(Service_NFP, "Regenerating mii data");
+ Mii::StoreData new_mii{};
+ new_mii.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
+ new_mii.SetNickname({u'y', u'u', u'z', u'u', u'\0'});
+
+ tag_data.owner_mii.BuildFromStoreData(new_mii);
+ tag_data.mii_extension.SetFromStoreData(new_mii);
+ }
+
// Overwrite tag contents with backup and mount the tag
tag_data = temporary_tag_data;
encrypted_tag_data = temporary_encrypted_tag_data;
@@ -851,25 +874,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
return Flush();
}
-Result NfcDevice::RestoreAmiibo() {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- if (device_state == DeviceState::TagRemoved) {
- return ResultTagRemoved;
- }
- return ResultWrongDeviceState;
- }
-
- if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
- LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return ResultWrongDeviceState;
- }
-
- // TODO: Load amiibo from backup on system
- LOG_ERROR(Service_NFP, "Not Implemented");
- return ResultSuccess;
-}
-
Result NfcDevice::Format() {
Result result = ResultSuccess;
@@ -877,7 +881,9 @@ Result NfcDevice::Format() {
result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
}
- if (result.IsError()) {
+ // We are formatting all data. Corruption is not an issue.
+ if (result.IsError() &&
+ (result != ResultCorruptedData && result != ResultCorruptedDataWithBackup)) {
return result;
}
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 0ed1ff34c..d8efe25ec 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -68,7 +68,6 @@ public:
Result DeleteRegisterInfo();
Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info);
- Result RestoreAmiibo();
Result Format();
Result OpenApplicationArea(u32 access_id);
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp
index a71d26157..ad534177d 100644
--- a/src/core/hle/service/nfc/common/device_manager.cpp
+++ b/src/core/hle/service/nfc/common/device_manager.cpp
@@ -7,6 +7,7 @@
#include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/hid/hid_util.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nfc/common/device.h"
#include "core/hle/service/nfc/common/device_manager.h"
@@ -24,7 +25,7 @@ DeviceManager::DeviceManager(Core::System& system_, KernelHelpers::ServiceContex
for (u32 device_index = 0; device_index < devices.size(); device_index++) {
devices[device_index] =
- std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
+ std::make_shared<NfcDevice>(HID::IndexToNpadIdType(device_index), system,
service_context, availability_change_event);
}
diff --git a/src/core/hle/service/nvdrv/devices/ioctl_serialization.h b/src/core/hle/service/nvdrv/devices/ioctl_serialization.h
new file mode 100644
index 000000000..b12bcd138
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/ioctl_serialization.h
@@ -0,0 +1,159 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+#include <vector>
+
+#include "common/concepts.h"
+#include "core/hle/service/nvdrv/devices/nvdevice.h"
+
+namespace Service::Nvidia::Devices {
+
+struct IoctlOneArgTraits {
+ template <typename T, typename R, typename A, typename... B>
+ static A GetFirstArgImpl(R (T::*)(A, B...));
+};
+
+struct IoctlTwoArgTraits {
+ template <typename T, typename R, typename A, typename B, typename... C>
+ static A GetFirstArgImpl(R (T::*)(A, B, C...));
+
+ template <typename T, typename R, typename A, typename B, typename... C>
+ static B GetSecondArgImpl(R (T::*)(A, B, C...));
+};
+
+struct Null {};
+
+// clang-format off
+
+template <typename FixedArg, typename VarArg, typename InlInVarArg, typename InlOutVarArg, typename F>
+NvResult WrapGeneric(F&& callable, std::span<const u8> input, std::span<const u8> inline_input, std::span<u8> output, std::span<u8> inline_output) {
+ constexpr bool HasFixedArg = !std::is_same_v<FixedArg, Null>;
+ constexpr bool HasVarArg = !std::is_same_v<VarArg, Null>;
+ constexpr bool HasInlInVarArg = !std::is_same_v<InlInVarArg, Null>;
+ constexpr bool HasInlOutVarArg = !std::is_same_v<InlOutVarArg, Null>;
+
+ // Declare the fixed-size input value.
+ FixedArg fixed{};
+ size_t var_offset = 0;
+
+ if constexpr (HasFixedArg) {
+ // Read the fixed-size input value.
+ var_offset = std::min(sizeof(FixedArg), input.size());
+ if (var_offset > 0) {
+ std::memcpy(&fixed, input.data(), var_offset);
+ }
+ }
+
+ // Read the variable-sized inputs.
+ const size_t num_var_args = HasVarArg ? ((input.size() - var_offset) / sizeof(VarArg)) : 0;
+ std::vector<VarArg> var_args(num_var_args);
+ if constexpr (HasVarArg) {
+ if (num_var_args > 0) {
+ std::memcpy(var_args.data(), input.data() + var_offset, num_var_args * sizeof(VarArg));
+ }
+ }
+
+ const size_t num_inl_in_var_args = HasInlInVarArg ? (inline_input.size() / sizeof(InlInVarArg)) : 0;
+ std::vector<InlInVarArg> inl_in_var_args(num_inl_in_var_args);
+ if constexpr (HasInlInVarArg) {
+ if (num_inl_in_var_args > 0) {
+ std::memcpy(inl_in_var_args.data(), inline_input.data(), num_inl_in_var_args * sizeof(InlInVarArg));
+ }
+ }
+
+ // Construct inline output data.
+ const size_t num_inl_out_var_args = HasInlOutVarArg ? (inline_output.size() / sizeof(InlOutVarArg)) : 0;
+ std::vector<InlOutVarArg> inl_out_var_args(num_inl_out_var_args);
+
+ // Perform the call.
+ NvResult result = callable(fixed, var_args, inl_in_var_args, inl_out_var_args);
+
+ // Copy outputs.
+ if constexpr (HasFixedArg) {
+ if (output.size() > 0) {
+ std::memcpy(output.data(), &fixed, std::min(output.size(), sizeof(FixedArg)));
+ }
+ }
+
+ if constexpr (HasVarArg) {
+ if (num_var_args > 0 && output.size() > var_offset) {
+ const size_t max_var_size = output.size() - var_offset;
+ std::memcpy(output.data() + var_offset, var_args.data(), std::min(max_var_size, num_var_args * sizeof(VarArg)));
+ }
+ }
+
+ // Copy inline outputs.
+ if constexpr (HasInlOutVarArg) {
+ if (num_inl_out_var_args > 0) {
+ std::memcpy(inline_output.data(), inl_out_var_args.data(), num_inl_out_var_args * sizeof(InlOutVarArg));
+ }
+ }
+
+ // We're done.
+ return result;
+}
+
+template <typename Self, typename F, typename... Rest>
+NvResult WrapFixed(Self* self, F&& callable, std::span<const u8> input, std::span<u8> output, Rest&&... rest) {
+ using FixedArg = typename std::remove_reference_t<decltype(IoctlOneArgTraits::GetFirstArgImpl(callable))>;
+
+ const auto Callable = [&](auto& fixed, auto& var, auto& inl_in, auto& inl_out) -> NvResult {
+ return (self->*callable)(fixed, std::forward<Rest>(rest)...);
+ };
+
+ return WrapGeneric<FixedArg, Null, Null, Null>(std::move(Callable), input, {}, output, {});
+}
+
+template <typename Self, typename F, typename... Rest>
+NvResult WrapFixedInlOut(Self* self, F&& callable, std::span<const u8> input, std::span<u8> output, std::span<u8> inline_output, Rest&&... rest) {
+ using FixedArg = typename std::remove_reference_t<decltype(IoctlTwoArgTraits::GetFirstArgImpl(callable))>;
+ using InlOutVarArg = typename std::remove_reference_t<decltype(IoctlTwoArgTraits::GetSecondArgImpl(callable))>::value_type;
+
+ const auto Callable = [&](auto& fixed, auto& var, auto& inl_in, auto& inl_out) -> NvResult {
+ return (self->*callable)(fixed, inl_out, std::forward<Rest>(rest)...);
+ };
+
+ return WrapGeneric<FixedArg, Null, Null, InlOutVarArg>(std::move(Callable), input, {}, output, inline_output);
+}
+
+template <typename Self, typename F, typename... Rest>
+NvResult WrapVariable(Self* self, F&& callable, std::span<const u8> input, std::span<u8> output, Rest&&... rest) {
+ using VarArg = typename std::remove_reference_t<decltype(IoctlOneArgTraits::GetFirstArgImpl(callable))>::value_type;
+
+ const auto Callable = [&](auto& fixed, auto& var, auto& inl_in, auto& inl_out) -> NvResult {
+ return (self->*callable)(var, std::forward<Rest>(rest)...);
+ };
+
+ return WrapGeneric<Null, VarArg, Null, Null>(std::move(Callable), input, {}, output, {});
+}
+
+template <typename Self, typename F, typename... Rest>
+NvResult WrapFixedVariable(Self* self, F&& callable, std::span<const u8> input, std::span<u8> output, Rest&&... rest) {
+ using FixedArg = typename std::remove_reference_t<decltype(IoctlTwoArgTraits::GetFirstArgImpl(callable))>;
+ using VarArg = typename std::remove_reference_t<decltype(IoctlTwoArgTraits::GetSecondArgImpl(callable))>::value_type;
+
+ const auto Callable = [&](auto& fixed, auto& var, auto& inl_in, auto& inl_out) -> NvResult {
+ return (self->*callable)(fixed, var, std::forward<Rest>(rest)...);
+ };
+
+ return WrapGeneric<FixedArg, VarArg, Null, Null>(std::move(Callable), input, {}, output, {});
+}
+
+template <typename Self, typename F, typename... Rest>
+NvResult WrapFixedInlIn(Self* self, F&& callable, std::span<const u8> input, std::span<const u8> inline_input, std::span<u8> output, Rest&&... rest) {
+ using FixedArg = typename std::remove_reference_t<decltype(IoctlTwoArgTraits::GetFirstArgImpl(callable))>;
+ using InlInVarArg = typename std::remove_reference_t<decltype(IoctlTwoArgTraits::GetSecondArgImpl(callable))>::value_type;
+
+ const auto Callable = [&](auto& fixed, auto& var, auto& inl_in, auto& inl_out) -> NvResult {
+ return (self->*callable)(fixed, inl_in, std::forward<Rest>(rest)...);
+ };
+
+ return WrapGeneric<FixedArg, Null, InlInVarArg, Null>(std::move(Callable), input, inline_input, output, {});
+}
+
+// clang-format on
+
+} // namespace Service::Nvidia::Devices
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 7d7bb8687..6b3639008 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -11,6 +11,7 @@
#include "core/core.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
#include "core/hle/service/nvdrv/nvdrv.h"
@@ -33,21 +34,21 @@ NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> i
case 'A':
switch (command.cmd) {
case 0x1:
- return BindChannel(input, output);
+ return WrapFixed(this, &nvhost_as_gpu::BindChannel, input, output);
case 0x2:
- return AllocateSpace(input, output);
+ return WrapFixed(this, &nvhost_as_gpu::AllocateSpace, input, output);
case 0x3:
- return FreeSpace(input, output);
+ return WrapFixed(this, &nvhost_as_gpu::FreeSpace, input, output);
case 0x5:
- return UnmapBuffer(input, output);
+ return WrapFixed(this, &nvhost_as_gpu::UnmapBuffer, input, output);
case 0x6:
- return MapBufferEx(input, output);
+ return WrapFixed(this, &nvhost_as_gpu::MapBufferEx, input, output);
case 0x8:
- return GetVARegions(input, output);
+ return WrapFixed(this, &nvhost_as_gpu::GetVARegions1, input, output);
case 0x9:
- return AllocAsEx(input, output);
+ return WrapFixed(this, &nvhost_as_gpu::AllocAsEx, input, output);
case 0x14:
- return Remap(input, output);
+ return WrapVariable(this, &nvhost_as_gpu::Remap, input, output);
default:
break;
}
@@ -72,7 +73,8 @@ NvResult nvhost_as_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> i
case 'A':
switch (command.cmd) {
case 0x8:
- return GetVARegions(input, output, inline_output);
+ return WrapFixedInlOut(this, &nvhost_as_gpu::GetVARegions3, input, output,
+ inline_output);
default:
break;
}
@@ -87,10 +89,7 @@ NvResult nvhost_as_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> i
void nvhost_as_gpu::OnOpen(DeviceFD fd) {}
void nvhost_as_gpu::OnClose(DeviceFD fd) {}
-NvResult nvhost_as_gpu::AllocAsEx(std::span<const u8> input, std::span<u8> output) {
- IoctlAllocAsEx params{};
- std::memcpy(&params, input.data(), input.size());
-
+NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {
LOG_DEBUG(Service_NVDRV, "called, big_page_size=0x{:X}", params.big_page_size);
std::scoped_lock lock(mutex);
@@ -141,10 +140,7 @@ NvResult nvhost_as_gpu::AllocAsEx(std::span<const u8> input, std::span<u8> outpu
return NvResult::Success;
}
-NvResult nvhost_as_gpu::AllocateSpace(std::span<const u8> input, std::span<u8> output) {
- IoctlAllocSpace params{};
- std::memcpy(&params, input.data(), input.size());
-
+NvResult nvhost_as_gpu::AllocateSpace(IoctlAllocSpace& params) {
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
params.page_size, params.flags);
@@ -194,7 +190,6 @@ NvResult nvhost_as_gpu::AllocateSpace(std::span<const u8> input, std::span<u8> o
.big_pages = params.page_size != VM::YUZU_PAGESIZE,
};
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
@@ -222,10 +217,7 @@ void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
mapping_map.erase(offset);
}
-NvResult nvhost_as_gpu::FreeSpace(std::span<const u8> input, std::span<u8> output) {
- IoctlFreeSpace params{};
- std::memcpy(&params, input.data(), input.size());
-
+NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) {
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
params.pages, params.page_size);
@@ -264,18 +256,11 @@ NvResult nvhost_as_gpu::FreeSpace(std::span<const u8> input, std::span<u8> outpu
return NvResult::BadValue;
}
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_as_gpu::Remap(std::span<const u8> input, std::span<u8> output) {
- const auto num_entries = input.size() / sizeof(IoctlRemapEntry);
-
- LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
-
- std::scoped_lock lock(mutex);
- entries.resize_destructive(num_entries);
- std::memcpy(entries.data(), input.data(), input.size());
+NvResult nvhost_as_gpu::Remap(std::span<IoctlRemapEntry> entries) {
+ LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", entries.size());
if (!vm.initialised) {
return NvResult::BadValue;
@@ -317,14 +302,10 @@ NvResult nvhost_as_gpu::Remap(std::span<const u8> input, std::span<u8> output) {
}
}
- std::memcpy(output.data(), entries.data(), output.size());
return NvResult::Success;
}
-NvResult nvhost_as_gpu::MapBufferEx(std::span<const u8> input, std::span<u8> output) {
- IoctlMapBufferEx params{};
- std::memcpy(&params, input.data(), input.size());
-
+NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
LOG_DEBUG(Service_NVDRV,
"called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
", offset={}",
@@ -421,14 +402,10 @@ NvResult nvhost_as_gpu::MapBufferEx(std::span<const u8> input, std::span<u8> out
mapping_map[params.offset] = mapping;
}
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_as_gpu::UnmapBuffer(std::span<const u8> input, std::span<u8> output) {
- IoctlUnmapBuffer params{};
- std::memcpy(&params, input.data(), input.size());
-
+NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
std::scoped_lock lock(mutex);
@@ -464,9 +441,7 @@ NvResult nvhost_as_gpu::UnmapBuffer(std::span<const u8> input, std::span<u8> out
return NvResult::Success;
}
-NvResult nvhost_as_gpu::BindChannel(std::span<const u8> input, std::span<u8> output) {
- IoctlBindChannel params{};
- std::memcpy(&params, input.data(), input.size());
+NvResult nvhost_as_gpu::BindChannel(IoctlBindChannel& params) {
LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
auto gpu_channel_device = module.GetDevice<nvhost_gpu>(params.fd);
@@ -493,10 +468,7 @@ void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
};
}
-NvResult nvhost_as_gpu::GetVARegions(std::span<const u8> input, std::span<u8> output) {
- IoctlGetVaRegions params{};
- std::memcpy(&params, input.data(), input.size());
-
+NvResult nvhost_as_gpu::GetVARegions1(IoctlGetVaRegions& params) {
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
params.buf_size);
@@ -508,15 +480,10 @@ NvResult nvhost_as_gpu::GetVARegions(std::span<const u8> input, std::span<u8> ou
GetVARegionsImpl(params);
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_as_gpu::GetVARegions(std::span<const u8> input, std::span<u8> output,
- std::span<u8> inline_output) {
- IoctlGetVaRegions params{};
- std::memcpy(&params, input.data(), input.size());
-
+NvResult nvhost_as_gpu::GetVARegions3(IoctlGetVaRegions& params, std::span<VaRegion> regions) {
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
params.buf_size);
@@ -528,9 +495,10 @@ NvResult nvhost_as_gpu::GetVARegions(std::span<const u8> input, std::span<u8> ou
GetVARegionsImpl(params);
- std::memcpy(output.data(), &params, output.size());
- std::memcpy(inline_output.data(), &params.regions[0], sizeof(VaRegion));
- std::memcpy(inline_output.data() + sizeof(VaRegion), &params.regions[1], sizeof(VaRegion));
+ const size_t num_regions = std::min(params.regions.size(), regions.size());
+ for (size_t i = 0; i < num_regions; i++) {
+ regions[i] = params.regions[i];
+ }
return NvResult::Success;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index 2af3e1260..932997e75 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -139,18 +139,17 @@ private:
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(VaRegion) * 2,
"IoctlGetVaRegions is incorrect size");
- NvResult AllocAsEx(std::span<const u8> input, std::span<u8> output);
- NvResult AllocateSpace(std::span<const u8> input, std::span<u8> output);
- NvResult Remap(std::span<const u8> input, std::span<u8> output);
- NvResult MapBufferEx(std::span<const u8> input, std::span<u8> output);
- NvResult UnmapBuffer(std::span<const u8> input, std::span<u8> output);
- NvResult FreeSpace(std::span<const u8> input, std::span<u8> output);
- NvResult BindChannel(std::span<const u8> input, std::span<u8> output);
+ NvResult AllocAsEx(IoctlAllocAsEx& params);
+ NvResult AllocateSpace(IoctlAllocSpace& params);
+ NvResult Remap(std::span<IoctlRemapEntry> params);
+ NvResult MapBufferEx(IoctlMapBufferEx& params);
+ NvResult UnmapBuffer(IoctlUnmapBuffer& params);
+ NvResult FreeSpace(IoctlFreeSpace& params);
+ NvResult BindChannel(IoctlBindChannel& params);
void GetVARegionsImpl(IoctlGetVaRegions& params);
- NvResult GetVARegions(std::span<const u8> input, std::span<u8> output);
- NvResult GetVARegions(std::span<const u8> input, std::span<u8> output,
- std::span<u8> inline_output);
+ NvResult GetVARegions1(IoctlGetVaRegions& params);
+ NvResult GetVARegions3(IoctlGetVaRegions& params, std::span<VaRegion> regions);
void FreeMappingLocked(u64 offset);
@@ -213,7 +212,6 @@ private:
bool initialised{};
} vm;
std::shared_ptr<Tegra::MemoryManager> gmmu;
- Common::ScratchBuffer<IoctlRemapEntry> entries;
// s32 channel{};
// u32 big_page_size{VM::DEFAULT_BIG_PAGE_SIZE};
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index 4d55554b4..b8dd34e24 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -14,6 +14,7 @@
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
+#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
#include "video_core/gpu.h"
#include "video_core/host1x/host1x.h"
@@ -40,19 +41,19 @@ NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inp
case 0x0:
switch (command.cmd) {
case 0x1b:
- return NvOsGetConfigU32(input, output);
+ return WrapFixed(this, &nvhost_ctrl::NvOsGetConfigU32, input, output);
case 0x1c:
- return IocCtrlClearEventWait(input, output);
+ return WrapFixed(this, &nvhost_ctrl::IocCtrlClearEventWait, input, output);
case 0x1d:
- return IocCtrlEventWait(input, output, true);
+ return WrapFixed(this, &nvhost_ctrl::IocCtrlEventWait, input, output, true);
case 0x1e:
- return IocCtrlEventWait(input, output, false);
+ return WrapFixed(this, &nvhost_ctrl::IocCtrlEventWait, input, output, false);
case 0x1f:
- return IocCtrlEventRegister(input, output);
+ return WrapFixed(this, &nvhost_ctrl::IocCtrlEventRegister, input, output);
case 0x20:
- return IocCtrlEventUnregister(input, output);
+ return WrapFixed(this, &nvhost_ctrl::IocCtrlEventUnregister, input, output);
case 0x21:
- return IocCtrlEventUnregisterBatch(input, output);
+ return WrapFixed(this, &nvhost_ctrl::IocCtrlEventUnregisterBatch, input, output);
}
break;
default:
@@ -79,25 +80,19 @@ void nvhost_ctrl::OnOpen(DeviceFD fd) {}
void nvhost_ctrl::OnClose(DeviceFD fd) {}
-NvResult nvhost_ctrl::NvOsGetConfigU32(std::span<const u8> input, std::span<u8> output) {
- IocGetConfigParams params{};
- std::memcpy(&params, input.data(), sizeof(params));
+NvResult nvhost_ctrl::NvOsGetConfigU32(IocGetConfigParams& params) {
LOG_TRACE(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(),
params.param_str.data());
return NvResult::ConfigVarNotFound; // Returns error on production mode
}
-NvResult nvhost_ctrl::IocCtrlEventWait(std::span<const u8> input, std::span<u8> output,
- bool is_allocation) {
- IocCtrlEventWaitParams params{};
- std::memcpy(&params, input.data(), sizeof(params));
+NvResult nvhost_ctrl::IocCtrlEventWait(IocCtrlEventWaitParams& params, bool is_allocation) {
LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
params.fence.id, params.fence.value, params.timeout, is_allocation);
bool must_unmark_fail = !is_allocation;
const u32 event_id = params.value.raw;
SCOPE_EXIT({
- std::memcpy(output.data(), &params, sizeof(params));
if (must_unmark_fail) {
events[event_id].fails = 0;
}
@@ -231,9 +226,7 @@ NvResult nvhost_ctrl::FreeEvent(u32 slot) {
return NvResult::Success;
}
-NvResult nvhost_ctrl::IocCtrlEventRegister(std::span<const u8> input, std::span<u8> output) {
- IocCtrlEventRegisterParams params{};
- std::memcpy(&params, input.data(), sizeof(params));
+NvResult nvhost_ctrl::IocCtrlEventRegister(IocCtrlEventRegisterParams& params) {
const u32 event_id = params.user_event_id;
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
if (event_id >= MaxNvEvents) {
@@ -252,9 +245,7 @@ NvResult nvhost_ctrl::IocCtrlEventRegister(std::span<const u8> input, std::span<
return NvResult::Success;
}
-NvResult nvhost_ctrl::IocCtrlEventUnregister(std::span<const u8> input, std::span<u8> output) {
- IocCtrlEventUnregisterParams params{};
- std::memcpy(&params, input.data(), sizeof(params));
+NvResult nvhost_ctrl::IocCtrlEventUnregister(IocCtrlEventUnregisterParams& params) {
const u32 event_id = params.user_event_id & 0x00FF;
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
@@ -262,9 +253,7 @@ NvResult nvhost_ctrl::IocCtrlEventUnregister(std::span<const u8> input, std::spa
return FreeEvent(event_id);
}
-NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(std::span<const u8> input, std::span<u8> output) {
- IocCtrlEventUnregisterBatchParams params{};
- std::memcpy(&params, input.data(), sizeof(params));
+NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(IocCtrlEventUnregisterBatchParams& params) {
u64 event_mask = params.user_events;
LOG_DEBUG(Service_NVDRV, " called, event_mask: {:X}", event_mask);
@@ -280,10 +269,7 @@ NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(std::span<const u8> input, std
return NvResult::Success;
}
-NvResult nvhost_ctrl::IocCtrlClearEventWait(std::span<const u8> input, std::span<u8> output) {
- IocCtrlEventClearParams params{};
- std::memcpy(&params, input.data(), sizeof(params));
-
+NvResult nvhost_ctrl::IocCtrlClearEventWait(IocCtrlEventClearParams& params) {
u32 event_id = params.event_id.slot;
LOG_DEBUG(Service_NVDRV, "called, event_id: {:X}", event_id);
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index 2efed4862..992124b60 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -186,12 +186,12 @@ private:
static_assert(sizeof(IocCtrlEventUnregisterBatchParams) == 8,
"IocCtrlEventKill is incorrect size");
- NvResult NvOsGetConfigU32(std::span<const u8> input, std::span<u8> output);
- NvResult IocCtrlEventWait(std::span<const u8> input, std::span<u8> output, bool is_allocation);
- NvResult IocCtrlEventRegister(std::span<const u8> input, std::span<u8> output);
- NvResult IocCtrlEventUnregister(std::span<const u8> input, std::span<u8> output);
- NvResult IocCtrlEventUnregisterBatch(std::span<const u8> input, std::span<u8> output);
- NvResult IocCtrlClearEventWait(std::span<const u8> input, std::span<u8> output);
+ NvResult NvOsGetConfigU32(IocGetConfigParams& params);
+ NvResult IocCtrlEventRegister(IocCtrlEventRegisterParams& params);
+ NvResult IocCtrlEventUnregister(IocCtrlEventUnregisterParams& params);
+ NvResult IocCtrlEventUnregisterBatch(IocCtrlEventUnregisterBatchParams& params);
+ NvResult IocCtrlEventWait(IocCtrlEventWaitParams& params, bool is_allocation);
+ NvResult IocCtrlClearEventWait(IocCtrlEventClearParams& params);
NvResult FreeEvent(u32 slot);
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index 6081d92e9..61a2df121 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -6,6 +6,7 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
#include "core/hle/service/nvdrv/nvdrv.h"
@@ -27,23 +28,23 @@ NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8>
case 'G':
switch (command.cmd) {
case 0x1:
- return ZCullGetCtxSize(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::ZCullGetCtxSize, input, output);
case 0x2:
- return ZCullGetInfo(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::ZCullGetInfo, input, output);
case 0x3:
- return ZBCSetTable(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::ZBCSetTable, input, output);
case 0x4:
- return ZBCQueryTable(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::ZBCQueryTable, input, output);
case 0x5:
- return GetCharacteristics(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::GetCharacteristics1, input, output);
case 0x6:
- return GetTPCMasks(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::GetTPCMasks1, input, output);
case 0x7:
- return FlushL2(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::FlushL2, input, output);
case 0x14:
- return GetActiveSlotMask(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::GetActiveSlotMask, input, output);
case 0x1c:
- return GetGpuTime(input, output);
+ return WrapFixed(this, &nvhost_ctrl_gpu::GetGpuTime, input, output);
default:
break;
}
@@ -65,9 +66,11 @@ NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8>
case 'G':
switch (command.cmd) {
case 0x5:
- return GetCharacteristics(input, output, inline_output);
+ return WrapFixedInlOut(this, &nvhost_ctrl_gpu::GetCharacteristics3, input, output,
+ inline_output);
case 0x6:
- return GetTPCMasks(input, output, inline_output);
+ return WrapFixedInlOut(this, &nvhost_ctrl_gpu::GetTPCMasks3, input, output,
+ inline_output);
default:
break;
}
@@ -82,10 +85,8 @@ NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8>
void nvhost_ctrl_gpu::OnOpen(DeviceFD fd) {}
void nvhost_ctrl_gpu::OnClose(DeviceFD fd) {}
-NvResult nvhost_ctrl_gpu::GetCharacteristics(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_ctrl_gpu::GetCharacteristics1(IoctlCharacteristics& params) {
LOG_DEBUG(Service_NVDRV, "called");
- IoctlCharacteristics params{};
- std::memcpy(&params, input.data(), input.size());
params.gc.arch = 0x120;
params.gc.impl = 0xb;
params.gc.rev = 0xa1;
@@ -123,15 +124,13 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics(std::span<const u8> input, std::spa
params.gc.gr_compbit_store_base_hw = 0x0;
params.gpu_characteristics_buf_size = 0xA0;
params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::GetCharacteristics(std::span<const u8> input, std::span<u8> output,
- std::span<u8> inline_output) {
+NvResult nvhost_ctrl_gpu::GetCharacteristics3(
+ IoctlCharacteristics& params, std::span<IoctlGpuCharacteristics> gpu_characteristics) {
LOG_DEBUG(Service_NVDRV, "called");
- IoctlCharacteristics params{};
- std::memcpy(&params, input.data(), input.size());
+
params.gc.arch = 0x120;
params.gc.impl = 0xb;
params.gc.rev = 0xa1;
@@ -169,70 +168,47 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics(std::span<const u8> input, std::spa
params.gc.gr_compbit_store_base_hw = 0x0;
params.gpu_characteristics_buf_size = 0xA0;
params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
-
- std::memcpy(output.data(), &params, output.size());
- std::memcpy(inline_output.data(), &params.gc, inline_output.size());
+ if (!gpu_characteristics.empty()) {
+ gpu_characteristics.front() = params.gc;
+ }
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::GetTPCMasks(std::span<const u8> input, std::span<u8> output) {
- IoctlGpuGetTpcMasksArgs params{};
- std::memcpy(&params, input.data(), input.size());
+NvResult nvhost_ctrl_gpu::GetTPCMasks1(IoctlGpuGetTpcMasksArgs& params) {
LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size);
if (params.mask_buffer_size != 0) {
params.tcp_mask = 3;
}
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::GetTPCMasks(std::span<const u8> input, std::span<u8> output,
- std::span<u8> inline_output) {
- IoctlGpuGetTpcMasksArgs params{};
- std::memcpy(&params, input.data(), input.size());
+NvResult nvhost_ctrl_gpu::GetTPCMasks3(IoctlGpuGetTpcMasksArgs& params, std::span<u32> tpc_mask) {
LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size);
if (params.mask_buffer_size != 0) {
params.tcp_mask = 3;
}
- std::memcpy(output.data(), &params, output.size());
- std::memcpy(inline_output.data(), &params.tcp_mask, inline_output.size());
+ if (!tpc_mask.empty()) {
+ tpc_mask.front() = params.tcp_mask;
+ }
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::GetActiveSlotMask(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_ctrl_gpu::GetActiveSlotMask(IoctlActiveSlotMask& params) {
LOG_DEBUG(Service_NVDRV, "called");
- IoctlActiveSlotMask params{};
- if (input.size() > 0) {
- std::memcpy(&params, input.data(), input.size());
- }
params.slot = 0x07;
params.mask = 0x01;
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(IoctlZcullGetCtxSize& params) {
LOG_DEBUG(Service_NVDRV, "called");
-
- IoctlZcullGetCtxSize params{};
- if (input.size() > 0) {
- std::memcpy(&params, input.data(), input.size());
- }
params.size = 0x1;
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::ZCullGetInfo(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_ctrl_gpu::ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params) {
LOG_DEBUG(Service_NVDRV, "called");
-
- IoctlNvgpuGpuZcullGetInfoArgs params{};
-
- if (input.size() > 0) {
- std::memcpy(&params, input.data(), input.size());
- }
-
params.width_align_pixels = 0x20;
params.height_align_pixels = 0x20;
params.pixel_squares_by_aliquots = 0x400;
@@ -243,53 +219,28 @@ NvResult nvhost_ctrl_gpu::ZCullGetInfo(std::span<const u8> input, std::span<u8>
params.subregion_width_align_pixels = 0x20;
params.subregion_height_align_pixels = 0x40;
params.subregion_count = 0x10;
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::ZBCSetTable(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
-
- IoctlZbcSetTable params{};
- std::memcpy(&params, input.data(), input.size());
// TODO(ogniK): What does this even actually do?
-
- // Prevent null pointer being passed as arg 1
- if (output.empty()) {
- LOG_WARNING(Service_NVDRV, "Avoiding passing null pointer to memcpy");
- } else {
- std::memcpy(output.data(), &params, output.size());
- }
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::ZBCQueryTable(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
-
- IoctlZbcQueryTable params{};
- std::memcpy(&params, input.data(), input.size());
- // TODO : To implement properly
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::FlushL2(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_ctrl_gpu::FlushL2(IoctlFlushL2& params) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
-
- IoctlFlushL2 params{};
- std::memcpy(&params, input.data(), input.size());
- // TODO : To implement properly
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_ctrl_gpu::GetGpuTime(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_ctrl_gpu::GetGpuTime(IoctlGetGpuTime& params) {
LOG_DEBUG(Service_NVDRV, "called");
-
- IoctlGetGpuTime params{};
- std::memcpy(&params, input.data(), input.size());
params.gpu_time = static_cast<u64_le>(system.CoreTiming().GetGlobalTimeNs().count());
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index 97995551c..d170299bd 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -151,21 +151,20 @@ private:
};
static_assert(sizeof(IoctlGetGpuTime) == 0x10, "IoctlGetGpuTime is incorrect size");
- NvResult GetCharacteristics(std::span<const u8> input, std::span<u8> output);
- NvResult GetCharacteristics(std::span<const u8> input, std::span<u8> output,
- std::span<u8> inline_output);
-
- NvResult GetTPCMasks(std::span<const u8> input, std::span<u8> output);
- NvResult GetTPCMasks(std::span<const u8> input, std::span<u8> output,
- std::span<u8> inline_output);
-
- NvResult GetActiveSlotMask(std::span<const u8> input, std::span<u8> output);
- NvResult ZCullGetCtxSize(std::span<const u8> input, std::span<u8> output);
- NvResult ZCullGetInfo(std::span<const u8> input, std::span<u8> output);
- NvResult ZBCSetTable(std::span<const u8> input, std::span<u8> output);
- NvResult ZBCQueryTable(std::span<const u8> input, std::span<u8> output);
- NvResult FlushL2(std::span<const u8> input, std::span<u8> output);
- NvResult GetGpuTime(std::span<const u8> input, std::span<u8> output);
+ NvResult GetCharacteristics1(IoctlCharacteristics& params);
+ NvResult GetCharacteristics3(IoctlCharacteristics& params,
+ std::span<IoctlGpuCharacteristics> gpu_characteristics);
+
+ NvResult GetTPCMasks1(IoctlGpuGetTpcMasksArgs& params);
+ NvResult GetTPCMasks3(IoctlGpuGetTpcMasksArgs& params, std::span<u32> tpc_mask);
+
+ NvResult GetActiveSlotMask(IoctlActiveSlotMask& params);
+ NvResult ZCullGetCtxSize(IoctlZcullGetCtxSize& params);
+ NvResult ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params);
+ NvResult ZBCSetTable(IoctlZbcSetTable& params);
+ NvResult ZBCQueryTable(IoctlZbcQueryTable& params);
+ NvResult FlushL2(IoctlFlushL2& params);
+ NvResult GetGpuTime(IoctlGetGpuTime& params);
EventInterface& events_interface;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 46a25fcab..b0395c2f0 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -8,6 +8,7 @@
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
+#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/memory.h"
@@ -52,7 +53,7 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inpu
case 0x0:
switch (command.cmd) {
case 0x3:
- return GetWaitbase(input, output);
+ return WrapFixed(this, &nvhost_gpu::GetWaitbase, input, output);
default:
break;
}
@@ -60,25 +61,25 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inpu
case 'H':
switch (command.cmd) {
case 0x1:
- return SetNVMAPfd(input, output);
+ return WrapFixed(this, &nvhost_gpu::SetNVMAPfd, input, output);
case 0x3:
- return ChannelSetTimeout(input, output);
+ return WrapFixed(this, &nvhost_gpu::ChannelSetTimeout, input, output);
case 0x8:
- return SubmitGPFIFOBase(input, output, false);
+ return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, false);
case 0x9:
- return AllocateObjectContext(input, output);
+ return WrapFixed(this, &nvhost_gpu::AllocateObjectContext, input, output);
case 0xb:
- return ZCullBind(input, output);
+ return WrapFixed(this, &nvhost_gpu::ZCullBind, input, output);
case 0xc:
- return SetErrorNotifier(input, output);
+ return WrapFixed(this, &nvhost_gpu::SetErrorNotifier, input, output);
case 0xd:
- return SetChannelPriority(input, output);
+ return WrapFixed(this, &nvhost_gpu::SetChannelPriority, input, output);
case 0x1a:
- return AllocGPFIFOEx2(input, output);
+ return WrapFixed(this, &nvhost_gpu::AllocGPFIFOEx2, input, output);
case 0x1b:
- return SubmitGPFIFOBase(input, output, true);
+ return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, true);
case 0x1d:
- return ChannelSetTimeslice(input, output);
+ return WrapFixed(this, &nvhost_gpu::ChannelSetTimeslice, input, output);
default:
break;
}
@@ -86,9 +87,9 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inpu
case 'G':
switch (command.cmd) {
case 0x14:
- return SetClientData(input, output);
+ return WrapFixed(this, &nvhost_gpu::SetClientData, input, output);
case 0x15:
- return GetClientData(input, output);
+ return WrapFixed(this, &nvhost_gpu::GetClientData, input, output);
default:
break;
}
@@ -104,7 +105,8 @@ NvResult nvhost_gpu::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> inpu
case 'H':
switch (command.cmd) {
case 0x1b:
- return SubmitGPFIFOBase(input, inline_input, output);
+ return WrapFixedInlIn(this, &nvhost_gpu::SubmitGPFIFOBase2, input, inline_input,
+ output);
}
break;
}
@@ -121,63 +123,45 @@ NvResult nvhost_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> inpu
void nvhost_gpu::OnOpen(DeviceFD fd) {}
void nvhost_gpu::OnClose(DeviceFD fd) {}
-NvResult nvhost_gpu::SetNVMAPfd(std::span<const u8> input, std::span<u8> output) {
- IoctlSetNvmapFD params{};
- std::memcpy(&params, input.data(), input.size());
+NvResult nvhost_gpu::SetNVMAPfd(IoctlSetNvmapFD& params) {
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
nvmap_fd = params.nvmap_fd;
return NvResult::Success;
}
-NvResult nvhost_gpu::SetClientData(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_gpu::SetClientData(IoctlClientData& params) {
LOG_DEBUG(Service_NVDRV, "called");
-
- IoctlClientData params{};
- std::memcpy(&params, input.data(), input.size());
user_data = params.data;
return NvResult::Success;
}
-NvResult nvhost_gpu::GetClientData(std::span<const u8> input, std::span<u8> output) {
+NvResult nvhost_gpu::GetClientData(IoctlClientData& params) {
LOG_DEBUG(Service_NVDRV, "called");
-
- IoctlClientData params{};
- std::memcpy(&params, input.data(), input.size());
params.data = user_data;
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_gpu::ZCullBind(std::span<const u8> input, std::span<u8> output) {
- std::memcpy(&zcull_params, input.data(), input.size());
+NvResult nvhost_gpu::ZCullBind(IoctlZCullBind& params) {
+ zcull_params = params;
LOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va,
zcull_params.mode);
-
- std::memcpy(output.data(), &zcull_params, output.size());
return NvResult::Success;
}
-NvResult nvhost_gpu::SetErrorNotifier(std::span<const u8> input, std::span<u8> output) {
- IoctlSetErrorNotifier params{};
- std::memcpy(&params, input.data(), input.size());
+NvResult nvhost_gpu::SetErrorNotifier(IoctlSetErrorNotifier& params) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}", params.offset,
params.size, params.mem);
-
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_gpu::SetChannelPriority(std::span<const u8> input, std::span<u8> output) {
- std::memcpy(&channel_priority, input.data(), input.size());
+NvResult nvhost_gpu::SetChannelPriority(IoctlChannelSetPriority& params) {
+ channel_priority = params.priority;
LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority);
-
return NvResult::Success;
}
-NvResult nvhost_gpu::AllocGPFIFOEx2(std::span<const u8> input, std::span<u8> output) {
- IoctlAllocGpfifoEx2 params{};
- std::memcpy(&params, input.data(), input.size());
+NvResult nvhost_gpu::AllocGPFIFOEx2(IoctlAllocGpfifoEx2& params) {
LOG_WARNING(Service_NVDRV,
"(STUBBED) called, num_entries={:X}, flags={:X}, unk0={:X}, "
"unk1={:X}, unk2={:X}, unk3={:X}",
@@ -193,18 +177,14 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(std::span<const u8> input, std::span<u8> out
params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint);
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_gpu::AllocateObjectContext(std::span<const u8> input, std::span<u8> output) {
- IoctlAllocObjCtx params{};
- std::memcpy(&params, input.data(), input.size());
+NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num,
params.flags);
params.obj_id = 0x0;
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
@@ -248,8 +228,7 @@ static boost::container::small_vector<Tegra::CommandHeader, 512> BuildIncrementW
return result;
}
-NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::span<u8> output,
- Tegra::CommandList&& entries) {
+NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandList&& entries) {
LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
params.num_entries, params.flags.raw);
@@ -290,65 +269,55 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::span<u8> o
flags.raw = 0;
- std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
return NvResult::Success;
}
-NvResult nvhost_gpu::SubmitGPFIFOBase(std::span<const u8> input, std::span<u8> output,
- bool kickoff) {
- if (input.size() < sizeof(IoctlSubmitGpfifo)) {
+NvResult nvhost_gpu::SubmitGPFIFOBase1(IoctlSubmitGpfifo& params,
+ std::span<Tegra::CommandListHeader> commands, bool kickoff) {
+ if (params.num_entries > commands.size()) {
UNIMPLEMENTED();
return NvResult::InvalidSize;
}
- IoctlSubmitGpfifo params{};
- std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
- Tegra::CommandList entries(params.num_entries);
+ Tegra::CommandList entries(params.num_entries);
if (kickoff) {
system.ApplicationMemory().ReadBlock(params.address, entries.command_lists.data(),
params.num_entries * sizeof(Tegra::CommandListHeader));
} else {
- std::memcpy(entries.command_lists.data(), &input[sizeof(IoctlSubmitGpfifo)],
+ std::memcpy(entries.command_lists.data(), commands.data(),
params.num_entries * sizeof(Tegra::CommandListHeader));
}
- return SubmitGPFIFOImpl(params, output, std::move(entries));
+ return SubmitGPFIFOImpl(params, std::move(entries));
}
-NvResult nvhost_gpu::SubmitGPFIFOBase(std::span<const u8> input, std::span<const u8> input_inline,
- std::span<u8> output) {
- if (input.size() < sizeof(IoctlSubmitGpfifo)) {
+NvResult nvhost_gpu::SubmitGPFIFOBase2(IoctlSubmitGpfifo& params,
+ std::span<const Tegra::CommandListHeader> commands) {
+ if (params.num_entries > commands.size()) {
UNIMPLEMENTED();
return NvResult::InvalidSize;
}
- IoctlSubmitGpfifo params{};
- std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
+
Tegra::CommandList entries(params.num_entries);
- std::memcpy(entries.command_lists.data(), input_inline.data(), input_inline.size());
- return SubmitGPFIFOImpl(params, output, std::move(entries));
+ std::memcpy(entries.command_lists.data(), commands.data(),
+ params.num_entries * sizeof(Tegra::CommandListHeader));
+ return SubmitGPFIFOImpl(params, std::move(entries));
}
-NvResult nvhost_gpu::GetWaitbase(std::span<const u8> input, std::span<u8> output) {
- IoctlGetWaitbase params{};
- std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
+NvResult nvhost_gpu::GetWaitbase(IoctlGetWaitbase& params) {
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
params.value = 0; // Seems to be hard coded at 0
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
-NvResult nvhost_gpu::ChannelSetTimeout(std::span<const u8> input, std::span<u8> output) {
- IoctlChannelSetTimeout params{};
- std::memcpy(&params, input.data(), sizeof(IoctlChannelSetTimeout));
+NvResult nvhost_gpu::ChannelSetTimeout(IoctlChannelSetTimeout& params) {
LOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout);
return NvResult::Success;
}
-NvResult nvhost_gpu::ChannelSetTimeslice(std::span<const u8> input, std::span<u8> output) {
- IoctlSetTimeslice params{};
- std::memcpy(&params, input.data(), sizeof(IoctlSetTimeslice));
+NvResult nvhost_gpu::ChannelSetTimeslice(IoctlSetTimeslice& params) {
LOG_INFO(Service_NVDRV, "called, timeslice=0x{:X}", params.timeslice);
channel_timeslice = params.timeslice;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index 529c20526..88fd228ff 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -186,23 +186,24 @@ private:
u32_le channel_priority{};
u32_le channel_timeslice{};
- NvResult SetNVMAPfd(std::span<const u8> input, std::span<u8> output);
- NvResult SetClientData(std::span<const u8> input, std::span<u8> output);
- NvResult GetClientData(std::span<const u8> input, std::span<u8> output);
- NvResult ZCullBind(std::span<const u8> input, std::span<u8> output);
- NvResult SetErrorNotifier(std::span<const u8> input, std::span<u8> output);
- NvResult SetChannelPriority(std::span<const u8> input, std::span<u8> output);
- NvResult AllocGPFIFOEx2(std::span<const u8> input, std::span<u8> output);
- NvResult AllocateObjectContext(std::span<const u8> input, std::span<u8> output);
- NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::span<u8> output,
- Tegra::CommandList&& entries);
- NvResult SubmitGPFIFOBase(std::span<const u8> input, std::span<u8> output,
- bool kickoff = false);
- NvResult SubmitGPFIFOBase(std::span<const u8> input, std::span<const u8> input_inline,
- std::span<u8> output);
- NvResult GetWaitbase(std::span<const u8> input, std::span<u8> output);
- NvResult ChannelSetTimeout(std::span<const u8> input, std::span<u8> output);
- NvResult ChannelSetTimeslice(std::span<const u8> input, std::span<u8> output);
+ NvResult SetNVMAPfd(IoctlSetNvmapFD& params);
+ NvResult SetClientData(IoctlClientData& params);
+ NvResult GetClientData(IoctlClientData& params);
+ NvResult ZCullBind(IoctlZCullBind& params);
+ NvResult SetErrorNotifier(IoctlSetErrorNotifier& params);
+ NvResult SetChannelPriority(IoctlChannelSetPriority& params);
+ NvResult AllocGPFIFOEx2(IoctlAllocGpfifoEx2& params);
+ NvResult AllocateObjectContext(IoctlAllocObjCtx& params);
+
+ NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandList&& entries);
+ NvResult SubmitGPFIFOBase1(IoctlSubmitGpfifo& params,
+ std::span<Tegra::CommandListHeader> commands, bool kickoff = false);
+ NvResult SubmitGPFIFOBase2(IoctlSubmitGpfifo& params,
+ std::span<const Tegra::CommandListHeader> commands);
+
+ NvResult GetWaitbase(IoctlGetWaitbase& params);
+ NvResult ChannelSetTimeout(IoctlChannelSetTimeout& params);
+ NvResult ChannelSetTimeslice(IoctlSetTimeslice& params);
EventInterface& events_interface;
NvCore::Container& core;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index a174442a6..f43914e1b 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -6,6 +6,7 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
#include "video_core/renderer_base.h"
@@ -25,18 +26,18 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> in
if (!host1x_file.fd_to_id.contains(fd)) {
host1x_file.fd_to_id[fd] = host1x_file.nvdec_next_id++;
}
- return Submit(fd, input, output);
+ return WrapFixedVariable(this, &nvhost_nvdec::Submit, input, output, fd);
}
case 0x2:
- return GetSyncpoint(input, output);
+ return WrapFixed(this, &nvhost_nvdec::GetSyncpoint, input, output);
case 0x3:
- return GetWaitbase(input, output);
+ return WrapFixed(this, &nvhost_nvdec::GetWaitbase, input, output);
case 0x7:
- return SetSubmitTimeout(input, output);
+ return WrapFixed(this, &nvhost_nvdec::SetSubmitTimeout, input, output);
case 0x9:
- return MapBuffer(input, output);
+ return WrapFixedVariable(this, &nvhost_nvdec::MapBuffer, input, output);
case 0xa:
- return UnmapBuffer(input, output);
+ return WrapFixedVariable(this, &nvhost_nvdec::UnmapBuffer, input, output);
default:
break;
}
@@ -44,7 +45,7 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> in
case 'H':
switch (command.cmd) {
case 0x1:
- return SetNVMAPfd(input);
+ return WrapFixed(this, &nvhost_nvdec::SetNVMAPfd, input, output);
default:
break;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
index 61649aa4a..74c701b95 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
@@ -29,6 +29,9 @@ std::size_t SliceVectors(std::span<const u8> input, std::vector<T>& dst, std::si
return 0;
}
const size_t bytes_copied = count * sizeof(T);
+ if (input.size() < offset + bytes_copied) {
+ return 0;
+ }
std::memcpy(dst.data(), input.data() + offset, bytes_copied);
return bytes_copied;
}
@@ -41,6 +44,9 @@ std::size_t WriteVectors(std::span<u8> dst, const std::vector<T>& src, std::size
return 0;
}
const size_t bytes_copied = src.size() * sizeof(T);
+ if (dst.size() < offset + bytes_copied) {
+ return 0;
+ }
std::memcpy(dst.data() + offset, src.data(), bytes_copied);
return bytes_copied;
}
@@ -63,18 +69,14 @@ nvhost_nvdec_common::~nvhost_nvdec_common() {
core.Host1xDeviceFile().syncpts_accumulated.push_back(channel_syncpoint);
}
-NvResult nvhost_nvdec_common::SetNVMAPfd(std::span<const u8> input) {
- IoctlSetNvmapFD params{};
- std::memcpy(&params, input.data(), sizeof(IoctlSetNvmapFD));
+NvResult nvhost_nvdec_common::SetNVMAPfd(IoctlSetNvmapFD& params) {
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
nvmap_fd = params.nvmap_fd;
return NvResult::Success;
}
-NvResult nvhost_nvdec_common::Submit(DeviceFD fd, std::span<const u8> input, std::span<u8> output) {
- IoctlSubmit params{};
- std::memcpy(&params, input.data(), sizeof(IoctlSubmit));
+NvResult nvhost_nvdec_common::Submit(IoctlSubmit& params, std::span<u8> data, DeviceFD fd) {
LOG_DEBUG(Service_NVDRV, "called NVDEC Submit, cmd_buffer_count={}", params.cmd_buffer_count);
// Instantiate param buffers
@@ -85,12 +87,12 @@ NvResult nvhost_nvdec_common::Submit(DeviceFD fd, std::span<const u8> input, std
std::vector<u32> fence_thresholds(params.fence_count);
// Slice input into their respective buffers
- std::size_t offset = sizeof(IoctlSubmit);
- offset += SliceVectors(input, command_buffers, params.cmd_buffer_count, offset);
- offset += SliceVectors(input, relocs, params.relocation_count, offset);
- offset += SliceVectors(input, reloc_shifts, params.relocation_count, offset);
- offset += SliceVectors(input, syncpt_increments, params.syncpoint_count, offset);
- offset += SliceVectors(input, fence_thresholds, params.fence_count, offset);
+ std::size_t offset = 0;
+ offset += SliceVectors(data, command_buffers, params.cmd_buffer_count, offset);
+ offset += SliceVectors(data, relocs, params.relocation_count, offset);
+ offset += SliceVectors(data, reloc_shifts, params.relocation_count, offset);
+ offset += SliceVectors(data, syncpt_increments, params.syncpoint_count, offset);
+ offset += SliceVectors(data, fence_thresholds, params.fence_count, offset);
auto& gpu = system.GPU();
if (gpu.UseNvdec()) {
@@ -108,72 +110,51 @@ NvResult nvhost_nvdec_common::Submit(DeviceFD fd, std::span<const u8> input, std
cmdlist.size() * sizeof(u32));
gpu.PushCommandBuffer(core.Host1xDeviceFile().fd_to_id[fd], cmdlist);
}
- std::memcpy(output.data(), &params, sizeof(IoctlSubmit));
// Some games expect command_buffers to be written back
- offset = sizeof(IoctlSubmit);
- offset += WriteVectors(output, command_buffers, offset);
- offset += WriteVectors(output, relocs, offset);
- offset += WriteVectors(output, reloc_shifts, offset);
- offset += WriteVectors(output, syncpt_increments, offset);
- offset += WriteVectors(output, fence_thresholds, offset);
+ offset = 0;
+ offset += WriteVectors(data, command_buffers, offset);
+ offset += WriteVectors(data, relocs, offset);
+ offset += WriteVectors(data, reloc_shifts, offset);
+ offset += WriteVectors(data, syncpt_increments, offset);
+ offset += WriteVectors(data, fence_thresholds, offset);
return NvResult::Success;
}
-NvResult nvhost_nvdec_common::GetSyncpoint(std::span<const u8> input, std::span<u8> output) {
- IoctlGetSyncpoint params{};
- std::memcpy(&params, input.data(), sizeof(IoctlGetSyncpoint));
+NvResult nvhost_nvdec_common::GetSyncpoint(IoctlGetSyncpoint& params) {
LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
-
- // const u32 id{NvCore::SyncpointManager::channel_syncpoints[static_cast<u32>(channel_type)]};
params.value = channel_syncpoint;
- std::memcpy(output.data(), &params, sizeof(IoctlGetSyncpoint));
-
return NvResult::Success;
}
-NvResult nvhost_nvdec_common::GetWaitbase(std::span<const u8> input, std::span<u8> output) {
- IoctlGetWaitbase params{};
+NvResult nvhost_nvdec_common::GetWaitbase(IoctlGetWaitbase& params) {
LOG_CRITICAL(Service_NVDRV, "called WAITBASE");
- std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
params.value = 0; // Seems to be hard coded at 0
- std::memcpy(output.data(), &params, sizeof(IoctlGetWaitbase));
return NvResult::Success;
}
-NvResult nvhost_nvdec_common::MapBuffer(std::span<const u8> input, std::span<u8> output) {
- IoctlMapBuffer params{};
- std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
- std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
-
- SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
-
- for (auto& cmd_buffer : cmd_buffer_handles) {
- cmd_buffer.map_address = nvmap.PinHandle(cmd_buffer.map_handle);
+NvResult nvhost_nvdec_common::MapBuffer(IoctlMapBuffer& params, std::span<MapBufferEntry> entries) {
+ const size_t num_entries = std::min(params.num_entries, static_cast<u32>(entries.size()));
+ for (size_t i = 0; i < num_entries; i++) {
+ entries[i].map_address = nvmap.PinHandle(entries[i].map_handle);
}
- std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer));
- std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
- cmd_buffer_handles.size() * sizeof(MapBufferEntry));
return NvResult::Success;
}
-NvResult nvhost_nvdec_common::UnmapBuffer(std::span<const u8> input, std::span<u8> output) {
- IoctlMapBuffer params{};
- std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
- std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
-
- SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
- for (auto& cmd_buffer : cmd_buffer_handles) {
- nvmap.UnpinHandle(cmd_buffer.map_handle);
+NvResult nvhost_nvdec_common::UnmapBuffer(IoctlMapBuffer& params,
+ std::span<MapBufferEntry> entries) {
+ const size_t num_entries = std::min(params.num_entries, static_cast<u32>(entries.size()));
+ for (size_t i = 0; i < num_entries; i++) {
+ nvmap.UnpinHandle(entries[i].map_handle);
+ entries[i] = {};
}
- std::memset(output.data(), 0, output.size());
+ params = {};
return NvResult::Success;
}
-NvResult nvhost_nvdec_common::SetSubmitTimeout(std::span<const u8> input, std::span<u8> output) {
- std::memcpy(&submit_timeout, input.data(), input.size());
+NvResult nvhost_nvdec_common::SetSubmitTimeout(u32 timeout) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
return NvResult::Success;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
index 9bb573bfe..7ce748e18 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
@@ -107,13 +107,13 @@ protected:
static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size");
/// Ioctl command implementations
- NvResult SetNVMAPfd(std::span<const u8> input);
- NvResult Submit(DeviceFD fd, std::span<const u8> input, std::span<u8> output);
- NvResult GetSyncpoint(std::span<const u8> input, std::span<u8> output);
- NvResult GetWaitbase(std::span<const u8> input, std::span<u8> output);
- NvResult MapBuffer(std::span<const u8> input, std::span<u8> output);
- NvResult UnmapBuffer(std::span<const u8> input, std::span<u8> output);
- NvResult SetSubmitTimeout(std::span<const u8> input, std::span<u8> output);
+ NvResult SetNVMAPfd(IoctlSetNvmapFD&);
+ NvResult Submit(IoctlSubmit& params, std::span<u8> input, DeviceFD fd);
+ NvResult GetSyncpoint(IoctlGetSyncpoint& params);
+ NvResult GetWaitbase(IoctlGetWaitbase& params);
+ NvResult MapBuffer(IoctlMapBuffer& params, std::span<MapBufferEntry> entries);
+ NvResult UnmapBuffer(IoctlMapBuffer& params, std::span<MapBufferEntry> entries);
+ NvResult SetSubmitTimeout(u32 timeout);
Kernel::KEvent* QueryEvent(u32 event_id) override;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
index a05c8cdae..9e6b86458 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
@@ -5,6 +5,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h"
namespace Service::Nvidia::Devices {
@@ -18,7 +19,7 @@ NvResult nvhost_nvjpg::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> in
case 'H':
switch (command.cmd) {
case 0x1:
- return SetNVMAPfd(input, output);
+ return WrapFixed(this, &nvhost_nvjpg::SetNVMAPfd, input, output);
default:
break;
}
@@ -46,9 +47,7 @@ NvResult nvhost_nvjpg::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
void nvhost_nvjpg::OnOpen(DeviceFD fd) {}
void nvhost_nvjpg::OnClose(DeviceFD fd) {}
-NvResult nvhost_nvjpg::SetNVMAPfd(std::span<const u8> input, std::span<u8> output) {
- IoctlSetNvmapFD params{};
- std::memcpy(&params, input.data(), input.size());
+NvResult nvhost_nvjpg::SetNVMAPfd(IoctlSetNvmapFD& params) {
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
nvmap_fd = params.nvmap_fd;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
index 5623e0d47..790c97f6a 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
@@ -33,7 +33,7 @@ private:
s32_le nvmap_fd{};
- NvResult SetNVMAPfd(std::span<const u8> input, std::span<u8> output);
+ NvResult SetNVMAPfd(IoctlSetNvmapFD& params);
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
index c0b8684c3..87f8d7c22 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -5,6 +5,7 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
#include "video_core/renderer_base.h"
@@ -25,16 +26,16 @@ NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inpu
if (!host1x_file.fd_to_id.contains(fd)) {
host1x_file.fd_to_id[fd] = host1x_file.vic_next_id++;
}
- return Submit(fd, input, output);
+ return WrapFixedVariable(this, &nvhost_vic::Submit, input, output, fd);
}
case 0x2:
- return GetSyncpoint(input, output);
+ return WrapFixed(this, &nvhost_vic::GetSyncpoint, input, output);
case 0x3:
- return GetWaitbase(input, output);
+ return WrapFixed(this, &nvhost_vic::GetWaitbase, input, output);
case 0x9:
- return MapBuffer(input, output);
+ return WrapFixedVariable(this, &nvhost_vic::MapBuffer, input, output);
case 0xa:
- return UnmapBuffer(input, output);
+ return WrapFixedVariable(this, &nvhost_vic::UnmapBuffer, input, output);
default:
break;
}
@@ -42,7 +43,7 @@ NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inpu
case 'H':
switch (command.cmd) {
case 0x1:
- return SetNVMAPfd(input);
+ return WrapFixed(this, &nvhost_vic::SetNVMAPfd, input, output);
default:
break;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 968eaa175..71b2e62ec 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -13,6 +13,7 @@
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/memory.h"
@@ -31,17 +32,17 @@ NvResult nvmap::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
case 0x1:
switch (command.cmd) {
case 0x1:
- return IocCreate(input, output);
+ return WrapFixed(this, &nvmap::IocCreate, input, output);
case 0x3:
- return IocFromId(input, output);
+ return WrapFixed(this, &nvmap::IocFromId, input, output);
case 0x4:
- return IocAlloc(input, output);
+ return WrapFixed(this, &nvmap::IocAlloc, input, output);
case 0x5:
- return IocFree(input, output);
+ return WrapFixed(this, &nvmap::IocFree, input, output);
case 0x9:
- return IocParam(input, output);
+ return WrapFixed(this, &nvmap::IocParam, input, output);
case 0xe:
- return IocGetId(input, output);
+ return WrapFixed(this, &nvmap::IocGetId, input, output);
default:
break;
}
@@ -69,9 +70,7 @@ NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, st
void nvmap::OnOpen(DeviceFD fd) {}
void nvmap::OnClose(DeviceFD fd) {}
-NvResult nvmap::IocCreate(std::span<const u8> input, std::span<u8> output) {
- IocCreateParams params;
- std::memcpy(&params, input.data(), sizeof(params));
+NvResult nvmap::IocCreate(IocCreateParams& params) {
LOG_DEBUG(Service_NVDRV, "called, size=0x{:08X}", params.size);
std::shared_ptr<NvCore::NvMap::Handle> handle_description{};
@@ -85,13 +84,10 @@ NvResult nvmap::IocCreate(std::span<const u8> input, std::span<u8> output) {
params.handle = handle_description->id;
LOG_DEBUG(Service_NVDRV, "handle: {}, size: 0x{:X}", handle_description->id, params.size);
- std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
-NvResult nvmap::IocAlloc(std::span<const u8> input, std::span<u8> output) {
- IocAllocParams params;
- std::memcpy(&params, input.data(), sizeof(params));
+NvResult nvmap::IocAlloc(IocAllocParams& params) {
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.address);
if (!params.handle) {
@@ -133,14 +129,10 @@ NvResult nvmap::IocAlloc(std::span<const u8> input, std::span<u8> output) {
handle_description->size,
Kernel::KMemoryPermission::None, true, false)
.IsSuccess());
- std::memcpy(output.data(), &params, sizeof(params));
return result;
}
-NvResult nvmap::IocGetId(std::span<const u8> input, std::span<u8> output) {
- IocGetIdParams params;
- std::memcpy(&params, input.data(), sizeof(params));
-
+NvResult nvmap::IocGetId(IocGetIdParams& params) {
LOG_DEBUG(Service_NVDRV, "called");
// See the comment in FromId for extra info on this function
@@ -157,14 +149,10 @@ NvResult nvmap::IocGetId(std::span<const u8> input, std::span<u8> output) {
}
params.id = handle_description->id;
- std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
-NvResult nvmap::IocFromId(std::span<const u8> input, std::span<u8> output) {
- IocFromIdParams params;
- std::memcpy(&params, input.data(), sizeof(params));
-
+NvResult nvmap::IocFromId(IocFromIdParams& params) {
LOG_DEBUG(Service_NVDRV, "called, id:{}", params.id);
// Handles and IDs are always the same value in nvmap however IDs can be used globally given the
@@ -188,16 +176,12 @@ NvResult nvmap::IocFromId(std::span<const u8> input, std::span<u8> output) {
return result;
}
params.handle = handle_description->id;
- std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
-NvResult nvmap::IocParam(std::span<const u8> input, std::span<u8> output) {
+NvResult nvmap::IocParam(IocParamParams& params) {
enum class ParamTypes { Size = 1, Alignment = 2, Base = 3, Heap = 4, Kind = 5, Compr = 6 };
- IocParamParams params;
- std::memcpy(&params, input.data(), sizeof(params));
-
LOG_DEBUG(Service_NVDRV, "called type={}", params.param);
if (!params.handle) {
@@ -237,14 +221,10 @@ NvResult nvmap::IocParam(std::span<const u8> input, std::span<u8> output) {
return NvResult::BadValue;
}
- std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
-NvResult nvmap::IocFree(std::span<const u8> input, std::span<u8> output) {
- IocFreeParams params;
- std::memcpy(&params, input.data(), sizeof(params));
-
+NvResult nvmap::IocFree(IocFreeParams& params) {
LOG_DEBUG(Service_NVDRV, "called");
if (!params.handle) {
@@ -267,7 +247,6 @@ NvResult nvmap::IocFree(std::span<const u8> input, std::span<u8> output) {
// This is possible when there's internal dups or other duplicates.
}
- std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 4c0cc71cd..049c11028 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -99,12 +99,12 @@ public:
};
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
- NvResult IocCreate(std::span<const u8> input, std::span<u8> output);
- NvResult IocAlloc(std::span<const u8> input, std::span<u8> output);
- NvResult IocGetId(std::span<const u8> input, std::span<u8> output);
- NvResult IocFromId(std::span<const u8> input, std::span<u8> output);
- NvResult IocParam(std::span<const u8> input, std::span<u8> output);
- NvResult IocFree(std::span<const u8> input, std::span<u8> output);
+ NvResult IocCreate(IocCreateParams& params);
+ NvResult IocAlloc(IocAllocParams& params);
+ NvResult IocGetId(IocGetIdParams& params);
+ NvResult IocFromId(IocFromIdParams& params);
+ NvResult IocParam(IocParamParams& params);
+ NvResult IocFree(IocFreeParams& params);
private:
/// Id to use for the next handle that is created.
diff --git a/src/core/hle/service/nvnflinger/buffer_item.h b/src/core/hle/service/nvnflinger/buffer_item.h
index 3da8cc3aa..7fd808f54 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 {
-struct GraphicBuffer;
+class GraphicBuffer;
class BufferItem final {
public:
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
index 51291539d..d91886bed 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp
@@ -5,7 +5,6 @@
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
#include "common/logging/log.h"
-#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
@@ -14,9 +13,8 @@
namespace Service::android {
-BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
- Service::Nvidia::NvCore::NvMap& nvmap_)
- : core{std::move(core_)}, slots{core->slots}, nvmap(nvmap_) {}
+BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_)
+ : core{std::move(core_)}, slots{core->slots} {}
BufferQueueConsumer::~BufferQueueConsumer() = default;
@@ -136,8 +134,6 @@ Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fenc
slots[slot].buffer_state = BufferState::Free;
- nvmap.FreeHandle(slots[slot].graphic_buffer->BufferId(), true);
-
listener = core->connected_producer_listener;
LOG_DEBUG(Service_Nvnflinger, "releasing slot {}", slot);
@@ -175,6 +171,25 @@ Status BufferQueueConsumer::Connect(std::shared_ptr<IConsumerListener> consumer_
return Status::NoError;
}
+Status BufferQueueConsumer::Disconnect() {
+ LOG_DEBUG(Service_Nvnflinger, "called");
+
+ std::scoped_lock lock{core->mutex};
+
+ if (core->consumer_listener == nullptr) {
+ LOG_ERROR(Service_Nvnflinger, "no consumer is connected");
+ return Status::BadValue;
+ }
+
+ core->is_abandoned = true;
+ core->consumer_listener = nullptr;
+ core->queue.clear();
+ core->FreeAllBuffersLocked();
+ core->SignalDequeueCondition();
+
+ return Status::NoError;
+}
+
Status BufferQueueConsumer::GetReleasedBuffers(u64* out_slot_mask) {
if (out_slot_mask == nullptr) {
LOG_ERROR(Service_Nvnflinger, "out_slot_mask may not be nullptr");
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.h b/src/core/hle/service/nvnflinger/buffer_queue_consumer.h
index 50ed0bb5f..0a61e8dbd 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.h
+++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.h
@@ -13,10 +13,6 @@
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/status.h"
-namespace Service::Nvidia::NvCore {
-class NvMap;
-} // namespace Service::Nvidia::NvCore
-
namespace Service::android {
class BufferItem;
@@ -25,19 +21,18 @@ class IConsumerListener;
class BufferQueueConsumer final {
public:
- explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
- Service::Nvidia::NvCore::NvMap& nvmap_);
+ explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_);
~BufferQueueConsumer();
Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence);
Status Connect(std::shared_ptr<IConsumerListener> consumer_listener, bool controlled_by_app);
+ Status Disconnect();
Status GetReleasedBuffers(u64* out_slot_mask);
private:
std::shared_ptr<BufferQueueCore> core;
BufferQueueDefs::SlotsType& slots;
- Service::Nvidia::NvCore::NvMap& nvmap;
};
} // namespace Service::android
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
index 2dbe29616..4ed5e5978 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
@@ -14,24 +14,12 @@ BufferQueueCore::BufferQueueCore() = default;
BufferQueueCore::~BufferQueueCore() = default;
-void BufferQueueCore::NotifyShutdown() {
- std::scoped_lock lock{mutex};
-
- is_shutting_down = true;
-
- SignalDequeueCondition();
-}
-
void BufferQueueCore::SignalDequeueCondition() {
dequeue_possible.store(true);
dequeue_condition.notify_all();
}
bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk) {
- if (is_shutting_down) {
- return false;
- }
-
dequeue_condition.wait(lk, [&] { return dequeue_possible.load(); });
dequeue_possible.store(false);
@@ -41,7 +29,7 @@ bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk)
s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
// If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
if (!use_async_buffer) {
- return max_acquired_buffer_count;
+ return 0;
}
if (dequeue_buffer_cannot_block || async) {
@@ -52,7 +40,7 @@ s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
}
s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const {
- return GetMinUndequeuedBufferCountLocked(async) + 1;
+ return GetMinUndequeuedBufferCountLocked(async);
}
s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
@@ -61,7 +49,7 @@ s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
if (override_max_buffer_count != 0) {
ASSERT(override_max_buffer_count >= min_buffer_count);
- max_buffer_count = override_max_buffer_count;
+ return override_max_buffer_count;
}
// Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.h b/src/core/hle/service/nvnflinger/buffer_queue_core.h
index 9164f08a0..e513d183b 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_core.h
+++ b/src/core/hle/service/nvnflinger/buffer_queue_core.h
@@ -34,8 +34,6 @@ public:
BufferQueueCore();
~BufferQueueCore();
- void NotifyShutdown();
-
private:
void SignalDequeueCondition();
bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);
@@ -74,7 +72,6 @@ private:
u32 transform_hint{};
bool is_allocating{};
mutable std::condition_variable_any is_allocating_condition;
- bool is_shutting_down{};
};
} // namespace Service::android
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
index dc6917d5d..5d8762d25 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
@@ -13,7 +13,6 @@
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
#include "core/hle/service/nvnflinger/consumer_listener.h"
@@ -134,7 +133,7 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, St
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
if (async && core->override_max_buffer_count) {
if (core->override_max_buffer_count < max_buffer_count) {
- LOG_ERROR(Service_Nvnflinger, "async mode is invalid with buffer count override");
+ *found = BufferQueueCore::INVALID_BUFFER_SLOT;
return Status::BadValue;
}
}
@@ -142,7 +141,8 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, St
// Free up any buffers that are in slots beyond the max buffer count
for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
ASSERT(slots[s].buffer_state == BufferState::Free);
- if (slots[s].graphic_buffer != nullptr) {
+ if (slots[s].graphic_buffer != nullptr && slots[s].buffer_state == BufferState::Free &&
+ !slots[s].is_preallocated) {
core->FreeBufferLocked(s);
*return_flags |= Status::ReleaseAllBuffers;
}
@@ -532,8 +532,6 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
item.is_droppable = core->dequeue_buffer_cannot_block || async;
item.swap_interval = swap_interval;
- nvmap.DuplicateHandle(item.graphic_buffer->BufferId(), true);
-
sticky_transform = sticky_transform_;
if (core->queue.empty()) {
@@ -743,19 +741,13 @@ Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
return Status::NoError;
}
- // HACK: We are not Android. Remove handle for items in queue, and clear queue.
- // Allows synchronous destruction of nvmap handles.
- for (auto& item : core->queue) {
- nvmap.FreeHandle(item.graphic_buffer->BufferId(), true);
- }
- core->queue.clear();
-
switch (api) {
case NativeWindowApi::Egl:
case NativeWindowApi::Cpu:
case NativeWindowApi::Media:
case NativeWindowApi::Camera:
if (core->connected_api == api) {
+ core->queue.clear();
core->FreeAllBuffersLocked();
core->connected_producer_listener = nullptr;
core->connected_api = NativeWindowApi::NoConnectedApi;
@@ -784,7 +776,7 @@ Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
}
Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
- const std::shared_ptr<GraphicBuffer>& buffer) {
+ const std::shared_ptr<NvGraphicBuffer>& buffer) {
LOG_DEBUG(Service_Nvnflinger, "slot {}", slot);
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
@@ -795,7 +787,7 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
slots[slot] = {};
slots[slot].fence = Fence::NoFence();
- slots[slot].graphic_buffer = buffer;
+ slots[slot].graphic_buffer = std::make_shared<GraphicBuffer>(nvmap, buffer);
slots[slot].frame_number = 0;
// Most games preallocate a buffer and pass a valid buffer here. However, it is possible for
@@ -838,7 +830,7 @@ void BufferQueueProducer::Transact(HLERequestContext& ctx, TransactionId code, u
}
case TransactionId::SetPreallocatedBuffer: {
const auto slot = parcel_in.Read<s32>();
- const auto buffer = parcel_in.ReadObject<GraphicBuffer>();
+ const auto buffer = parcel_in.ReadObject<NvGraphicBuffer>();
status = SetPreallocatedBuffer(slot, buffer);
break;
@@ -866,7 +858,7 @@ void BufferQueueProducer::Transact(HLERequestContext& ctx, TransactionId code, u
status = RequestBuffer(slot, &buf);
- parcel_out.WriteFlattenedObject(buf);
+ parcel_out.WriteFlattenedObject<NvGraphicBuffer>(buf.get());
break;
}
case TransactionId::QueueBuffer: {
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.h b/src/core/hle/service/nvnflinger/buffer_queue_producer.h
index d4201c104..64c17d56c 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.h
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.h
@@ -38,6 +38,7 @@ namespace Service::android {
class BufferQueueCore;
class IProducerListener;
+struct NvGraphicBuffer;
class BufferQueueProducer final : public IBinder {
public:
@@ -65,7 +66,7 @@ public:
bool producer_controlled_by_app, QueueBufferOutput* output);
Status Disconnect(NativeWindowApi api);
- Status SetPreallocatedBuffer(s32 slot, const std::shared_ptr<GraphicBuffer>& buffer);
+ Status SetPreallocatedBuffer(s32 slot, const std::shared_ptr<NvGraphicBuffer>& buffer);
private:
BufferQueueProducer(const BufferQueueProducer&) = delete;
diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h
index d8c9dec3b..d25bca049 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 {
-struct GraphicBuffer;
+class GraphicBuffer;
enum class BufferState : u32 {
Free = 0,
diff --git a/src/core/hle/service/nvnflinger/buffer_transform_flags.h b/src/core/hle/service/nvnflinger/buffer_transform_flags.h
index 67aa5dad6..ffe579718 100644
--- a/src/core/hle/service/nvnflinger/buffer_transform_flags.h
+++ b/src/core/hle/service/nvnflinger/buffer_transform_flags.h
@@ -3,6 +3,7 @@
#pragma once
+#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Service::android {
@@ -21,5 +22,6 @@ enum class BufferTransformFlags : u32 {
/// Rotate source image 270 degrees clockwise
Rotate270 = 0x07,
};
+DECLARE_ENUM_FLAG_OPERATORS(BufferTransformFlags);
} // namespace Service::android
diff --git a/src/core/hle/service/nvnflinger/consumer_base.cpp b/src/core/hle/service/nvnflinger/consumer_base.cpp
index 4dcda8dac..1059e72bf 100644
--- a/src/core/hle/service/nvnflinger/consumer_base.cpp
+++ b/src/core/hle/service/nvnflinger/consumer_base.cpp
@@ -27,6 +27,26 @@ void ConsumerBase::Connect(bool controlled_by_app) {
consumer->Connect(shared_from_this(), controlled_by_app);
}
+void ConsumerBase::Abandon() {
+ LOG_DEBUG(Service_Nvnflinger, "called");
+
+ std::scoped_lock lock{mutex};
+
+ if (!is_abandoned) {
+ this->AbandonLocked();
+ is_abandoned = true;
+ }
+}
+
+void ConsumerBase::AbandonLocked() {
+ for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+ this->FreeBufferLocked(i);
+ }
+ // disconnect from the BufferQueue
+ consumer->Disconnect();
+ consumer = nullptr;
+}
+
void ConsumerBase::FreeBufferLocked(s32 slot_index) {
LOG_DEBUG(Service_Nvnflinger, "slot_index={}", slot_index);
diff --git a/src/core/hle/service/nvnflinger/consumer_base.h b/src/core/hle/service/nvnflinger/consumer_base.h
index 264829414..ea3e9e97a 100644
--- a/src/core/hle/service/nvnflinger/consumer_base.h
+++ b/src/core/hle/service/nvnflinger/consumer_base.h
@@ -24,6 +24,7 @@ class BufferQueueConsumer;
class ConsumerBase : public IConsumerListener, public std::enable_shared_from_this<ConsumerBase> {
public:
void Connect(bool controlled_by_app);
+ void Abandon();
protected:
explicit ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_);
@@ -34,6 +35,7 @@ protected:
void OnBuffersReleased() override;
void OnSidebandStreamChanged() override;
+ void AbandonLocked();
void FreeBufferLocked(s32 slot_index);
Status AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when);
Status ReleaseBufferLocked(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer);
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
index 2e29bc848..d7db24f42 100644
--- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
@@ -71,24 +71,17 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
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{
+ Nvidia::Devices::nvmap::IocCreateParams create_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,
+ R_UNLESS(nvmap.IocCreate(create_params) == Nvidia::NvResult::Success,
VI::ResultOperationFailed);
// Assign the output handle.
- *out_nv_map_handle = create_out_params.handle;
+ *out_nv_map_handle = create_params.handle;
// We succeeded.
R_SUCCEED();
@@ -96,13 +89,10 @@ Result CreateNvMapHandle(u32* out_nv_map_handle, Nvidia::Devices::nvmap& nvmap,
Result FreeNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle) {
// Free the handle.
- Nvidia::Devices::nvmap::IocFreeParams free_in_params{
+ Nvidia::Devices::nvmap::IocFreeParams free_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);
+ R_UNLESS(nvmap.IocFree(free_params) == Nvidia::NvResult::Success, VI::ResultOperationFailed);
// We succeeded.
R_SUCCEED();
@@ -111,7 +101,7 @@ Result FreeNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle) {
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{
+ Nvidia::Devices::nvmap::IocAllocParams alloc_params{
.handle = handle,
.heap_mask = 0,
.flags = {},
@@ -119,10 +109,7 @@ Result AllocNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle, Common::Proce
.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);
+ R_UNLESS(nvmap.IocAlloc(alloc_params) == Nvidia::NvResult::Success, VI::ResultOperationFailed);
// We succeeded.
R_SUCCEED();
@@ -179,7 +166,7 @@ constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] {
}();
void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) {
- auto buffer = std::make_shared<android::GraphicBuffer>();
+ auto buffer = std::make_shared<android::NvGraphicBuffer>();
buffer->width = SharedBufferWidth;
buffer->height = SharedBufferHeight;
buffer->stride = SharedBufferBlockLinearStride;
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index a07c621d9..0745434c5 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -47,7 +47,10 @@ void Nvnflinger::SplitVSync(std::stop_token stop_token) {
vsync_signal.Wait();
const auto lock_guard = Lock();
- Compose();
+
+ if (!is_abandoned) {
+ Compose();
+ }
}
}
@@ -66,7 +69,6 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
- { const auto lock_guard = Lock(); }
vsync_signal.Set();
return std::chrono::nanoseconds(GetNextTicks());
});
@@ -106,11 +108,20 @@ Nvnflinger::~Nvnflinger() {
}
void Nvnflinger::ShutdownLayers() {
- for (auto& display : displays) {
- for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
- display.GetLayer(layer).Core().NotifyShutdown();
+ // Abandon consumers.
+ {
+ const auto lock_guard = Lock();
+ for (auto& display : displays) {
+ for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
+ display.GetLayer(layer).GetConsumer().Abandon();
+ }
}
+
+ is_abandoned = true;
}
+
+ // Join the vsync thread, if it exists.
+ vsync_thread = {};
}
void Nvnflinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
@@ -229,16 +240,6 @@ VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) {
return display->FindLayer(layer_id);
}
-const VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) const {
- const auto* const display = FindDisplay(display_id);
-
- if (display == nullptr) {
- return nullptr;
- }
-
- return display->FindLayer(layer_id);
-}
-
VI::Layer* Nvnflinger::FindOrCreateLayer(u64 display_id, u64 layer_id) {
auto* const display = FindDisplay(display_id);
@@ -288,7 +289,6 @@ void Nvnflinger::Compose() {
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
ASSERT(nvdisp);
- guard->unlock();
Common::Rectangle<int> crop_rect{
static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
@@ -299,7 +299,6 @@ void Nvnflinger::Compose() {
buffer.fence.fences, buffer.fence.num_fences);
MicroProfileFlip();
- guard->lock();
swap_interval = buffer.swap_interval;
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index 14c783582..f5d73acdb 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -117,9 +117,6 @@ private:
/// Finds the layer identified by the specified ID in the desired display.
[[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
- /// Finds the layer identified by the specified ID in the desired display.
- [[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
-
/// Finds the layer identified by the specified ID in the desired display,
/// or creates the layer if it is not found.
/// To be used when the system expects the specified ID to already exist.
@@ -143,6 +140,8 @@ private:
s32 swap_interval = 1;
+ bool is_abandoned = false;
+
/// Event that handles screen composition.
std::shared_ptr<Core::Timing::EventType> multi_composition_event;
std::shared_ptr<Core::Timing::EventType> single_composition_event;
diff --git a/src/core/hle/service/nvnflinger/status.h b/src/core/hle/service/nvnflinger/status.h
index 7af166c40..3fa0fe15b 100644
--- a/src/core/hle/service/nvnflinger/status.h
+++ b/src/core/hle/service/nvnflinger/status.h
@@ -19,7 +19,7 @@ enum class Status : s32 {
Busy = -16,
NoInit = -19,
BadValue = -22,
- InvalidOperation = -37,
+ InvalidOperation = -38,
BufferNeedsReallocation = 1,
ReleaseAllBuffers = 2,
};
diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.cpp b/src/core/hle/service/nvnflinger/ui/graphic_buffer.cpp
new file mode 100644
index 000000000..ce70946ec
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.cpp
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
+
+namespace Service::android {
+
+static NvGraphicBuffer GetBuffer(std::shared_ptr<NvGraphicBuffer>& buffer) {
+ if (buffer) {
+ return *buffer;
+ } else {
+ return {};
+ }
+}
+
+GraphicBuffer::GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
+ : NvGraphicBuffer(width_, height_, format_, usage_), m_nvmap(nullptr) {}
+
+GraphicBuffer::GraphicBuffer(Service::Nvidia::NvCore::NvMap& nvmap,
+ std::shared_ptr<NvGraphicBuffer> buffer)
+ : NvGraphicBuffer(GetBuffer(buffer)), m_nvmap(std::addressof(nvmap)) {
+ if (this->BufferId() > 0) {
+ m_nvmap->DuplicateHandle(this->BufferId(), true);
+ }
+}
+
+GraphicBuffer::~GraphicBuffer() {
+ if (m_nvmap != nullptr && this->BufferId() > 0) {
+ m_nvmap->FreeHandle(this->BufferId(), true);
+ }
+}
+
+} // namespace Service::android
diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
index 3eac5cedd..da430aa75 100644
--- a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
+++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
@@ -6,16 +6,22 @@
#pragma once
+#include <memory>
+
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/service/nvnflinger/pixel_format.h"
+namespace Service::Nvidia::NvCore {
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
namespace Service::android {
-struct GraphicBuffer final {
- constexpr GraphicBuffer() = default;
+struct NvGraphicBuffer {
+ constexpr NvGraphicBuffer() = default;
- constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
+ constexpr NvGraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
: width{static_cast<s32>(width_)}, height{static_cast<s32>(height_)}, format{format_},
usage{static_cast<s32>(usage_)} {}
@@ -93,6 +99,17 @@ struct GraphicBuffer final {
u32 offset{};
INSERT_PADDING_WORDS(60);
};
-static_assert(sizeof(GraphicBuffer) == 0x16C, "GraphicBuffer has wrong size");
+static_assert(sizeof(NvGraphicBuffer) == 0x16C, "NvGraphicBuffer has wrong size");
+
+class GraphicBuffer final : public NvGraphicBuffer {
+public:
+ explicit GraphicBuffer(u32 width, u32 height, PixelFormat format, u32 usage);
+ explicit GraphicBuffer(Service::Nvidia::NvCore::NvMap& nvmap,
+ std::shared_ptr<NvGraphicBuffer> buffer);
+ ~GraphicBuffer();
+
+private:
+ Service::Nvidia::NvCore::NvMap* m_nvmap{};
+};
} // namespace Service::android
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index 938330dd0..6a7fd72bc 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.cpp
@@ -141,6 +141,12 @@ public:
service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent");
}
+ ~IParentalControlService() {
+ service_context.CloseEvent(synchronization_event);
+ service_context.CloseEvent(unlinked_event);
+ service_context.CloseEvent(request_suspension_event);
+ };
+
private:
bool CheckFreeCommunicationPermissionImpl() const {
if (states.temporary_unlocked) {
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index f9cf2dda3..d92499f05 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -37,7 +37,7 @@ std::optional<Kernel::KProcess*> SearchProcessList(
void GetApplicationPidGeneric(HLERequestContext& ctx,
const std::vector<Kernel::KProcess*>& process_list) {
const auto process = SearchProcessList(process_list, [](const auto& proc) {
- return proc->GetProcessId() == Kernel::KProcess::ProcessIDMin;
+ return proc->GetProcessId() == Kernel::KProcess::ProcessIdMin;
});
IPC::ResponseBuilder rb{ctx, 4};
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index ec3af80af..48304e6d1 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -19,19 +19,8 @@
namespace Service::Set {
-namespace {
-constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05;
-
-enum class GetFirmwareVersionType {
- Version1,
- Version2,
-};
-
-void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
- GetFirmwareVersionType type) {
- ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
- "FirmwareVersion output buffer must be 0x100 bytes in size!");
-
+Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
+ GetFirmwareVersionType type) {
constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809;
auto& fsc = system.GetFileSystemController();
@@ -45,46 +34,43 @@ void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
nca = bis_system->GetEntry(FirmwareVersionSystemDataId, FileSys::ContentRecordType::Data);
}
if (nca) {
- romfs = FileSys::ExtractRomFS(nca->GetRomFS());
+ if (auto nca_romfs = nca->GetRomFS(); nca_romfs) {
+ romfs = FileSys::ExtractRomFS(nca_romfs);
+ }
}
if (!romfs) {
romfs = FileSys::ExtractRomFS(
FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId));
}
- const auto early_exit_failure = [&ctx](std::string_view desc, Result code) {
+ const auto early_exit_failure = [](std::string_view desc, Result code) {
LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
desc);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(code);
+ return code;
};
const auto ver_file = romfs->GetFile("file");
if (ver_file == nullptr) {
- early_exit_failure("The system version archive didn't contain the file 'file'.",
- FileSys::ERROR_INVALID_ARGUMENT);
- return;
+ return early_exit_failure("The system version archive didn't contain the file 'file'.",
+ FileSys::ERROR_INVALID_ARGUMENT);
}
auto data = ver_file->ReadAllBytes();
- if (data.size() != 0x100) {
- early_exit_failure("The system version file 'file' was not the correct size.",
- FileSys::ERROR_OUT_OF_BOUNDS);
- return;
+ if (data.size() != sizeof(FirmwareVersionFormat)) {
+ return early_exit_failure("The system version file 'file' was not the correct size.",
+ FileSys::ERROR_OUT_OF_BOUNDS);
}
+ std::memcpy(&out_firmware, data.data(), sizeof(FirmwareVersionFormat));
+
// If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
// zero out the REVISION_MINOR field.
if (type == GetFirmwareVersionType::Version1) {
- data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0;
+ out_firmware.revision_minor = 0;
}
- ctx.WriteBuffer(data);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ return ResultSuccess;
}
-} // Anonymous namespace
void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -98,12 +84,32 @@ void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
- GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version1);
+
+ FirmwareVersionFormat firmware_data{};
+ const auto result =
+ GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version1);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(firmware_data);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
- GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version2);
+
+ FirmwareVersionFormat firmware_data{};
+ const auto result =
+ GetFirmwareVersionImpl(firmware_data, system, GetFirmwareVersionType::Version2);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(firmware_data);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {
@@ -431,8 +437,7 @@ void SET_SYS::GetAutoUpdateEnableFlag(HLERequestContext& ctx) {
void SET_SYS::GetBatteryPercentageFlag(HLERequestContext& ctx) {
u8 battery_percentage_flag{1};
- LOG_WARNING(Service_SET, "(STUBBED) called, battery_percentage_flag={}",
- battery_percentage_flag);
+ LOG_DEBUG(Service_SET, "(STUBBED) called, battery_percentage_flag={}", battery_percentage_flag);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -492,6 +497,29 @@ void SET_SYS::GetChineseTraditionalInputMethod(HLERequestContext& ctx) {
rb.PushEnum(ChineseTraditionalInputMethod::Unknown0);
}
+void SET_SYS::GetHomeMenuScheme(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "(STUBBED) called");
+
+ const HomeMenuScheme default_color = {
+ .main = 0xFF323232,
+ .back = 0xFF323232,
+ .sub = 0xFFFFFFFF,
+ .bezel = 0xFFFFFFFF,
+ .extra = 0xFF000000,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 7};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(default_color);
+}
+
+void SET_SYS::GetHomeMenuSchemeModel(HLERequestContext& ctx) {
+ LOG_WARNING(Service_SET, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(0);
+}
void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) {
LOG_WARNING(Service_SET, "(STUBBED) called");
@@ -674,7 +702,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{171, nullptr, "SetChineseTraditionalInputMethod"},
{172, nullptr, "GetPtmCycleCountReliability"},
{173, nullptr, "SetPtmCycleCountReliability"},
- {174, nullptr, "GetHomeMenuScheme"},
+ {174, &SET_SYS::GetHomeMenuScheme, "GetHomeMenuScheme"},
{175, nullptr, "GetThemeSettings"},
{176, nullptr, "SetThemeSettings"},
{177, nullptr, "GetThemeKey"},
@@ -685,7 +713,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{182, nullptr, "SetT"},
{183, nullptr, "GetPlatformRegion"},
{184, nullptr, "SetPlatformRegion"},
- {185, nullptr, "GetHomeMenuSchemeModel"},
+ {185, &SET_SYS::GetHomeMenuSchemeModel, "GetHomeMenuSchemeModel"},
{186, nullptr, "GetMemoryUsageRateFlag"},
{187, nullptr, "GetTouchScreenMode"},
{188, nullptr, "SetTouchScreenMode"},
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index c7dba2a9e..5f770fd32 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -4,6 +4,7 @@
#pragma once
#include "common/uuid.h"
+#include "core/hle/result.h"
#include "core/hle/service/service.h"
#include "core/hle/service/time/clock_types.h"
@@ -12,6 +13,29 @@ class System;
}
namespace Service::Set {
+enum class LanguageCode : u64;
+enum class GetFirmwareVersionType {
+ Version1,
+ Version2,
+};
+
+struct FirmwareVersionFormat {
+ u8 major;
+ u8 minor;
+ u8 micro;
+ INSERT_PADDING_BYTES(1);
+ u8 revision_major;
+ u8 revision_minor;
+ INSERT_PADDING_BYTES(2);
+ std::array<char, 0x20> platform;
+ std::array<u8, 0x40> version_hash;
+ std::array<char, 0x18> display_version;
+ std::array<char, 0x80> display_title;
+};
+static_assert(sizeof(FirmwareVersionFormat) == 0x100, "FirmwareVersionFormat is an invalid size");
+
+Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
+ GetFirmwareVersionType type);
class SET_SYS final : public ServiceFramework<SET_SYS> {
public:
@@ -269,6 +293,16 @@ private:
};
static_assert(sizeof(EulaVersion) == 0x30, "EulaVersion is incorrect size");
+ /// This is nn::settings::system::HomeMenuScheme
+ struct HomeMenuScheme {
+ u32 main;
+ u32 back;
+ u32 sub;
+ u32 bezel;
+ u32 extra;
+ };
+ static_assert(sizeof(HomeMenuScheme) == 0x14, "HomeMenuScheme is incorrect size");
+
void SetLanguageCode(HLERequestContext& ctx);
void GetFirmwareVersion(HLERequestContext& ctx);
void GetFirmwareVersion2(HLERequestContext& ctx);
@@ -305,6 +339,8 @@ private:
void GetKeyboardLayout(HLERequestContext& ctx);
void GetChineseTraditionalInputMethod(HLERequestContext& ctx);
void GetFieldTestingFlag(HLERequestContext& ctx);
+ void GetHomeMenuScheme(HLERequestContext& ctx);
+ void GetHomeMenuSchemeModel(HLERequestContext& ctx);
AccountSettings account_settings{
.flags = {},
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 85849d5f3..dd652ca42 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -39,6 +39,18 @@ bool IsConnectionBased(Type type) {
}
}
+template <typename T>
+T GetValue(std::span<const u8> buffer) {
+ T t{};
+ std::memcpy(&t, buffer.data(), std::min(sizeof(T), buffer.size()));
+ return t;
+}
+
+template <typename T>
+void PutValue(std::span<u8> buffer, const T& t) {
+ std::memcpy(buffer.data(), &t, std::min(sizeof(T), buffer.size()));
+}
+
} // Anonymous namespace
void BSD::PollWork::Execute(BSD* bsd) {
@@ -316,22 +328,12 @@ void BSD::SetSockOpt(HLERequestContext& ctx) {
const s32 fd = rp.Pop<s32>();
const u32 level = rp.Pop<u32>();
const OptName optname = static_cast<OptName>(rp.Pop<u32>());
-
- const auto buffer = ctx.ReadBuffer();
- const u8* optval = buffer.empty() ? nullptr : buffer.data();
- size_t optlen = buffer.size();
-
- std::array<u64, 2> values;
- if ((optname == OptName::SNDTIMEO || optname == OptName::RCVTIMEO) && buffer.size() == 8) {
- std::memcpy(values.data(), buffer.data(), sizeof(values));
- optlen = sizeof(values);
- optval = reinterpret_cast<const u8*>(values.data());
- }
+ const auto optval = ctx.ReadBuffer();
LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level,
- static_cast<u32>(optname), optlen);
+ static_cast<u32>(optname), optval.size());
- BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optlen, optval));
+ BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optval));
}
void BSD::Shutdown(HLERequestContext& ctx) {
@@ -521,18 +523,19 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco
std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer,
s32 nfds, s32 timeout) {
- if (write_buffer.size() < nfds * sizeof(PollFD)) {
- return {-1, Errno::INVAL};
- }
-
- if (nfds == 0) {
+ if (nfds <= 0) {
// When no entries are provided, -1 is returned with errno zero
return {-1, Errno::SUCCESS};
}
+ if (read_buffer.size() < nfds * sizeof(PollFD)) {
+ return {-1, Errno::INVAL};
+ }
+ if (write_buffer.size() < nfds * sizeof(PollFD)) {
+ return {-1, Errno::INVAL};
+ }
- const size_t length = std::min(read_buffer.size(), write_buffer.size());
std::vector<PollFD> fds(nfds);
- std::memcpy(fds.data(), read_buffer.data(), length);
+ std::memcpy(fds.data(), read_buffer.data(), nfds * sizeof(PollFD));
if (timeout >= 0) {
const s64 seconds = timeout / 1000;
@@ -580,7 +583,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<con
for (size_t i = 0; i < num; ++i) {
fds[i].revents = Translate(host_pollfds[i].revents);
}
- std::memcpy(write_buffer.data(), fds.data(), length);
+ std::memcpy(write_buffer.data(), fds.data(), nfds * sizeof(PollFD));
return Translate(result);
}
@@ -608,8 +611,7 @@ std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) {
new_descriptor.is_connection_based = descriptor.is_connection_based;
const SockAddrIn guest_addr_in = Translate(result.sockaddr_in);
- const size_t length = std::min(sizeof(guest_addr_in), write_buffer.size());
- std::memcpy(write_buffer.data(), &guest_addr_in, length);
+ PutValue(write_buffer, guest_addr_in);
return {new_fd, Errno::SUCCESS};
}
@@ -619,8 +621,7 @@ Errno BSD::BindImpl(s32 fd, std::span<const u8> addr) {
return Errno::BADF;
}
ASSERT(addr.size() == sizeof(SockAddrIn));
- SockAddrIn addr_in;
- std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
+ auto addr_in = GetValue<SockAddrIn>(addr);
return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
}
@@ -631,8 +632,7 @@ Errno BSD::ConnectImpl(s32 fd, std::span<const u8> addr) {
}
UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn));
- SockAddrIn addr_in;
- std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
+ auto addr_in = GetValue<SockAddrIn>(addr);
return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
}
@@ -650,7 +650,7 @@ Errno BSD::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
ASSERT(write_buffer.size() >= sizeof(guest_addrin));
write_buffer.resize(sizeof(guest_addrin));
- std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
+ PutValue(write_buffer, guest_addrin);
return Translate(bsd_errno);
}
@@ -667,7 +667,7 @@ Errno BSD::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
ASSERT(write_buffer.size() >= sizeof(guest_addrin));
write_buffer.resize(sizeof(guest_addrin));
- std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
+ PutValue(write_buffer, guest_addrin);
return Translate(bsd_errno);
}
@@ -725,7 +725,7 @@ Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& o
optval.size() == sizeof(Errno), { return Errno::INVAL; },
"Incorrect getsockopt option size");
optval.resize(sizeof(Errno));
- memcpy(optval.data(), &translated_pending_err, sizeof(Errno));
+ PutValue(optval, translated_pending_err);
}
return Translate(getsockopt_err);
}
@@ -735,7 +735,7 @@ Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& o
}
}
-Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) {
+Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8> optval) {
if (!IsFileDescriptorValid(fd)) {
return Errno::BADF;
}
@@ -748,17 +748,15 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
if (optname == OptName::LINGER) {
- ASSERT(optlen == sizeof(Linger));
- Linger linger;
- std::memcpy(&linger, optval, sizeof(linger));
+ ASSERT(optval.size() == sizeof(Linger));
+ auto linger = GetValue<Linger>(optval);
ASSERT(linger.onoff == 0 || linger.onoff == 1);
return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
}
- ASSERT(optlen == sizeof(u32));
- u32 value;
- std::memcpy(&value, optval, sizeof(value));
+ ASSERT(optval.size() == sizeof(u32));
+ auto value = GetValue<u32>(optval);
switch (optname) {
case OptName::REUSEADDR:
@@ -862,7 +860,7 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess
} else {
ASSERT(addr.size() == sizeof(SockAddrIn));
const SockAddrIn result = Translate(addr_in);
- std::memcpy(addr.data(), &result, sizeof(result));
+ PutValue(addr, result);
}
}
@@ -886,8 +884,7 @@ std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, std::span<const u8> mes
Network::SockAddrIn* p_addr_in = nullptr;
if (!addr.empty()) {
ASSERT(addr.size() == sizeof(SockAddrIn));
- SockAddrIn guest_addr_in;
- std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in));
+ auto guest_addr_in = GetValue<SockAddrIn>(addr);
addr_in = Translate(guest_addr_in);
p_addr_in = &addr_in;
}
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 161f22b9b..4f69d382c 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -163,7 +163,7 @@ private:
Errno ListenImpl(s32 fd, s32 backlog);
std::pair<s32, Errno> FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg);
Errno GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& optval);
- Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval);
+ Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8> optval);
Errno ShutdownImpl(s32 fd, s32 how);
std::pair<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message);
std::pair<s32, Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
index 9fc01ea90..7149fffeb 100644
--- a/src/core/hle/service/time/clock_types.h
+++ b/src/core/hle/service/time/clock_types.h
@@ -11,6 +11,11 @@
#include "core/hle/service/time/errors.h"
#include "core/hle/service/time/time_zone_types.h"
+// Defined by WinBase.h on Windows
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
namespace Service::Time::Clock {
enum class TimeType : u8 {
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index f0b5eff8a..d30f49877 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -35,7 +35,7 @@ static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_cont
return {
buffer_queue_core,
std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core, nvmap),
- std::make_unique<android::BufferQueueConsumer>(buffer_queue_core, nvmap)};
+ std::make_unique<android::BufferQueueConsumer>(buffer_queue_core)};
}
Display::Display(u64 id, std::string name_,
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 5c36b71e5..60ee78e89 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -3,6 +3,7 @@
#include <cstring>
#include "common/logging/log.h"
+#include "common/settings.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
@@ -14,6 +15,10 @@
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nso.h"
+#ifdef HAS_NCE
+#include "core/arm/nce/patcher.h"
+#endif
+
namespace Loader {
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
@@ -124,21 +129,43 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
metadata.Print();
- const auto static_modules = {"rtld", "main", "subsdk0", "subsdk1", "subsdk2",
- "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7",
- "subsdk8", "subsdk9", "sdk"};
+ // Enable NCE only for programs with 39-bit address space.
+ const bool is_39bit =
+ metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit;
+ Settings::SetNceEnabled(is_39bit);
+
+ const std::array static_modules = {"rtld", "main", "subsdk0", "subsdk1", "subsdk2",
+ "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7",
+ "subsdk8", "subsdk9", "sdk"};
- // Use the NSO module loader to figure out the code layout
std::size_t code_size{};
- for (const auto& module : static_modules) {
+
+ // Define an nce patch context for each potential module.
+#ifdef HAS_NCE
+ std::array<Core::NCE::Patcher, 13> module_patchers;
+#endif
+
+ const auto GetPatcher = [&](size_t i) -> Core::NCE::Patcher* {
+#ifdef HAS_NCE
+ if (Settings::IsNceEnabled()) {
+ return &module_patchers[i];
+ }
+#endif
+ return nullptr;
+ };
+
+ // Use the NSO module loader to figure out the code layout
+ for (size_t i = 0; i < static_modules.size(); i++) {
+ const auto& module = static_modules[i];
const FileSys::VirtualFile module_file{dir->GetFile(module)};
if (!module_file) {
continue;
}
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
- const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
- process, system, *module_file, code_size, should_pass_arguments, false);
+ const auto tentative_next_load_addr =
+ AppLoader_NSO::LoadModule(process, system, *module_file, code_size,
+ should_pass_arguments, false, {}, GetPatcher(i));
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -146,8 +173,18 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
code_size = *tentative_next_load_addr;
}
+ // Enable direct memory mapping in case of NCE.
+ const u64 fastmem_base = [&]() -> size_t {
+ if (Settings::IsNceEnabled()) {
+ auto& buffer = system.DeviceMemory().buffer;
+ buffer.EnableDirectMappedAddress();
+ return reinterpret_cast<u64>(buffer.VirtualBasePointer());
+ }
+ return 0;
+ }();
+
// Setup the process code layout
- if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) {
+ if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) {
return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
}
@@ -157,7 +194,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
VAddr next_load_addr{base_address};
const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
system.GetContentProvider()};
- for (const auto& module : static_modules) {
+ for (size_t i = 0; i < static_modules.size(); i++) {
+ const auto& module = static_modules[i];
const FileSys::VirtualFile module_file{dir->GetFile(module)};
if (!module_file) {
continue;
@@ -165,15 +203,16 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
const VAddr load_addr{next_load_addr};
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
- const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
- process, system, *module_file, load_addr, should_pass_arguments, true, pm);
+ const auto tentative_next_load_addr =
+ AppLoader_NSO::LoadModule(process, system, *module_file, load_addr,
+ should_pass_arguments, true, pm, GetPatcher(i));
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
next_load_addr = *tentative_next_load_addr;
modules.insert_or_assign(load_addr, module);
- LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
+ LOG_DEBUG(Loader, "loaded module {} @ {:#X}", module, load_addr);
}
// Find the RomFS by searching for a ".romfs" file in this directory
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index bf56a08b4..cd6982921 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -91,7 +91,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
// Setup the process code layout
if (process
- .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
+ .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), 0,
+ false)
.IsError()) {
return {ResultStatus::ErrorNotInitialized, {}};
}
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 69f1a54ed..e74697cda 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -22,6 +22,10 @@
#include "core/loader/nso.h"
#include "core/memory.h"
+#ifdef HAS_NCE
+#include "core/arm/nce/patcher.h"
+#endif
+
namespace Loader {
struct NroSegmentHeader {
@@ -139,7 +143,8 @@ static constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
-static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) {
+static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
+ const std::vector<u8>& data) {
if (data.size() < sizeof(NroHeader)) {
return {};
}
@@ -194,14 +199,61 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
codeset.DataSegment().size += bss_size;
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
+ size_t image_size = program_image.size();
+
+#ifdef HAS_NCE
+ const auto& code = codeset.CodeSegment();
+
+ // NROs always have a 39-bit address space.
+ Settings::SetNceEnabled(true);
+
+ // Create NCE patcher
+ Core::NCE::Patcher patch{};
+
+ if (Settings::IsNceEnabled()) {
+ // Patch SVCs and MRS calls in the guest code
+ patch.PatchText(program_image, code);
+
+ // We only support PostData patching for NROs.
+ ASSERT(patch.GetPatchMode() == Core::NCE::PatchMode::PostData);
+
+ // Update patch section.
+ auto& patch_segment = codeset.PatchSegment();
+ patch_segment.addr = image_size;
+ patch_segment.size = static_cast<u32>(patch.GetSectionSize());
+
+ // Add patch section size to the module size.
+ image_size += patch_segment.size;
+ }
+#endif
+
+ // Enable direct memory mapping in case of NCE.
+ const u64 fastmem_base = [&]() -> size_t {
+ if (Settings::IsNceEnabled()) {
+ auto& buffer = system.DeviceMemory().buffer;
+ buffer.EnableDirectMappedAddress();
+ return reinterpret_cast<u64>(buffer.VirtualBasePointer());
+ }
+ return 0;
+ }();
// Setup the process code layout
if (process
- .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
+ .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), image_size, fastmem_base,
+ false)
.IsError()) {
return false;
}
+ // Relocate code patch and copy to the program_image if running under NCE.
+ // This needs to be after LoadFromMetadata so we can use the process entry point.
+#ifdef HAS_NCE
+ if (Settings::IsNceEnabled()) {
+ patch.RelocateAndCopy(process.GetEntryPoint(), code, program_image,
+ &process.GetPostHandlers());
+ }
+#endif
+
// Load codeset for current process
codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), process.GetEntryPoint());
@@ -209,8 +261,9 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
return true;
}
-bool AppLoader_NRO::LoadNro(Kernel::KProcess& process, const FileSys::VfsFile& nro_file) {
- return LoadNroImpl(process, nro_file.ReadAllBytes());
+bool AppLoader_NRO::LoadNro(Core::System& system, Kernel::KProcess& process,
+ const FileSys::VfsFile& nro_file) {
+ return LoadNroImpl(system, process, nro_file.ReadAllBytes());
}
AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::System& system) {
@@ -218,7 +271,7 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
- if (!LoadNro(process, *file)) {
+ if (!LoadNro(system, process, *file)) {
return {ResultStatus::ErrorLoadingNRO, {}};
}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 8de6eebc6..d2928cba0 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -54,7 +54,7 @@ public:
bool IsRomFSUpdatable() const override;
private:
- bool LoadNro(Kernel::KProcess& process, const FileSys::VfsFile& nro_file);
+ bool LoadNro(Core::System& system, Kernel::KProcess& process, const FileSys::VfsFile& nro_file);
std::vector<u8> icon_data;
std::unique_ptr<FileSys::NACP> nacp;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 1350da8dc..b053a0d14 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -20,6 +20,10 @@
#include "core/loader/nso.h"
#include "core/memory.h"
+#ifdef HAS_NCE
+#include "core/arm/nce/patcher.h"
+#endif
+
namespace Loader {
namespace {
struct MODHeader {
@@ -72,7 +76,8 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& in_file) {
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system,
const FileSys::VfsFile& nso_file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
- std::optional<FileSys::PatchManager> pm) {
+ std::optional<FileSys::PatchManager> pm,
+ Core::NCE::Patcher* patch) {
if (nso_file.GetSize() < sizeof(NSOHeader)) {
return std::nullopt;
}
@@ -86,6 +91,16 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
return std::nullopt;
}
+ // Allocate some space at the beginning if we are patching in PreText mode.
+ const size_t module_start = [&]() -> size_t {
+#ifdef HAS_NCE
+ if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
+ return patch->GetSectionSize();
+ }
+#endif
+ return 0;
+ }();
+
// Build program image
Kernel::CodeSet codeset;
Kernel::PhysicalMemory program_image;
@@ -95,11 +110,12 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
if (nso_header.IsSegmentCompressed(i)) {
data = DecompressSegment(data, nso_header.segments[i]);
}
- program_image.resize(nso_header.segments[i].location + static_cast<u32>(data.size()));
- std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(),
- data.size());
- codeset.segments[i].addr = nso_header.segments[i].location;
- codeset.segments[i].offset = nso_header.segments[i].location;
+ program_image.resize(module_start + nso_header.segments[i].location +
+ static_cast<u32>(data.size()));
+ std::memcpy(program_image.data() + module_start + nso_header.segments[i].location,
+ data.data(), data.size());
+ codeset.segments[i].addr = module_start + nso_header.segments[i].location;
+ codeset.segments[i].offset = module_start + nso_header.segments[i].location;
codeset.segments[i].size = nso_header.segments[i].size;
}
@@ -118,7 +134,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
}
codeset.DataSegment().size += nso_header.segments[2].bss_size;
- const u32 image_size{
+ u32 image_size{
PageAlignSize(static_cast<u32>(program_image.size()) + nso_header.segments[2].bss_size)};
program_image.resize(image_size);
@@ -129,15 +145,44 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
// Apply patches if necessary
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::span<u8> patchable_section(program_image.data() + module_start,
+ program_image.size() - module_start);
+ std::vector<u8> pi_header(sizeof(NSOHeader) + patchable_section.size());
std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
- std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
- program_image.size());
+ std::memcpy(pi_header.data() + sizeof(NSOHeader), patchable_section.data(),
+ patchable_section.size());
pi_header = pm->PatchNSO(pi_header, name);
- std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
+ std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), patchable_section.data());
+ }
+
+#ifdef HAS_NCE
+ // If we are computing the process code layout and using nce backend, patch.
+ const auto& code = codeset.CodeSegment();
+ if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::None) {
+ // Patch SVCs and MRS calls in the guest code
+ patch->PatchText(program_image, code);
+
+ // Add patch section size to the module size.
+ image_size += static_cast<u32>(patch->GetSectionSize());
+ } else if (patch) {
+ // Relocate code patch and copy to the program_image.
+ patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers());
+
+ // Update patch section.
+ auto& patch_segment = codeset.PatchSegment();
+ patch_segment.addr =
+ patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
+ patch_segment.size = static_cast<u32>(patch->GetSectionSize());
+
+ // Add patch section size to the module size. In PreText mode image_size
+ // already contains the patch segment as part of module_start.
+ if (patch->GetPatchMode() == Core::NCE::PatchMode::PostData) {
+ image_size += patch_segment.size;
+ }
}
+#endif
// If we aren't actually loading (i.e. just computing the process code layout), we are done
if (!load_into_process) {
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 0b53b4ecd..29b86ed4c 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -15,6 +15,10 @@ namespace Core {
class System;
}
+namespace Core::NCE {
+class Patcher;
+}
+
namespace Kernel {
class KProcess;
}
@@ -88,7 +92,8 @@ public:
static std::optional<VAddr> LoadModule(Kernel::KProcess& process, Core::System& system,
const FileSys::VfsFile& nso_file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
- std::optional<FileSys::PatchManager> pm = {});
+ std::optional<FileSys::PatchManager> pm = {},
+ Core::NCE::Patcher* patch = nullptr);
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index fa5273402..5b376b202 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -1,8 +1,10 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-FileCopyrightText: 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
+#include <mutex>
#include <span>
#include "common/assert.h"
@@ -10,6 +12,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/page_table.h"
+#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/core.h"
@@ -41,7 +44,7 @@ struct Memory::Impl {
explicit Impl(Core::System& system_) : system{system_} {}
void SetCurrentPageTable(Kernel::KProcess& process, u32 core_id) {
- current_page_table = &process.GetPageTable().PageTableImpl();
+ current_page_table = &process.GetPageTable().GetImpl();
current_page_table->fastmem_arena = system.DeviceMemory().buffer.VirtualBasePointer();
const std::size_t address_space_width = process.GetPageTable().GetAddressSpaceWidth();
@@ -50,7 +53,7 @@ struct Memory::Impl {
}
void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
- Common::PhysicalAddress target) {
+ Common::PhysicalAddress target, Common::MemoryPermission perms) {
ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base));
ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}",
@@ -60,7 +63,7 @@ struct Memory::Impl {
if (Settings::IsFastmemEnabled()) {
system.DeviceMemory().buffer.Map(GetInteger(base),
- GetInteger(target) - DramMemoryMap::Base, size);
+ GetInteger(target) - DramMemoryMap::Base, size, perms);
}
}
@@ -75,6 +78,51 @@ struct Memory::Impl {
}
}
+ void ProtectRegion(Common::PageTable& page_table, VAddr vaddr, u64 size,
+ Common::MemoryPermission perms) {
+ ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
+ ASSERT_MSG((vaddr & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", vaddr);
+
+ if (!Settings::IsFastmemEnabled()) {
+ return;
+ }
+
+ const bool is_r = True(perms & Common::MemoryPermission::Read);
+ const bool is_w = True(perms & Common::MemoryPermission::Write);
+ const bool is_x =
+ True(perms & Common::MemoryPermission::Execute) && Settings::IsNceEnabled();
+
+ if (!current_page_table) {
+ system.DeviceMemory().buffer.Protect(vaddr, size, is_r, is_w, is_x);
+ return;
+ }
+
+ u64 protect_bytes{};
+ u64 protect_begin{};
+ for (u64 addr = vaddr; addr < vaddr + size; addr += YUZU_PAGESIZE) {
+ const Common::PageType page_type{
+ current_page_table->pointers[addr >> YUZU_PAGEBITS].Type()};
+ switch (page_type) {
+ case Common::PageType::RasterizerCachedMemory:
+ if (protect_bytes > 0) {
+ system.DeviceMemory().buffer.Protect(protect_begin, protect_bytes, is_r, is_w,
+ is_x);
+ protect_bytes = 0;
+ }
+ break;
+ default:
+ if (protect_bytes == 0) {
+ protect_begin = addr;
+ }
+ protect_bytes += YUZU_PAGESIZE;
+ }
+ }
+
+ if (protect_bytes > 0) {
+ system.DeviceMemory().buffer.Protect(protect_begin, protect_bytes, is_r, is_w, is_x);
+ }
+ }
+
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
const Common::PhysicalAddress paddr{
current_page_table->backing_addr[vaddr >> YUZU_PAGEBITS]};
@@ -195,7 +243,7 @@ struct Memory::Impl {
bool WalkBlock(const Common::ProcessAddress addr, const std::size_t size, auto on_unmapped,
auto on_memory, auto on_rasterizer, auto increment) {
- const auto& page_table = system.ApplicationProcess()->GetPageTable().PageTableImpl();
+ const auto& page_table = system.ApplicationProcess()->GetPageTable().GetImpl();
std::size_t remaining_size = size;
std::size_t page_index = addr >> YUZU_PAGEBITS;
std::size_t page_offset = addr & YUZU_PAGEMASK;
@@ -318,7 +366,7 @@ struct Memory::Impl {
[&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount,
u8* const host_ptr) {
if constexpr (!UNSAFE) {
- system.GPU().InvalidateRegion(GetInteger(current_vaddr), copy_amount);
+ HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount);
}
std::memcpy(host_ptr, src_buffer, copy_amount);
},
@@ -351,7 +399,7 @@ struct Memory::Impl {
},
[&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount,
u8* const host_ptr) {
- system.GPU().InvalidateRegion(GetInteger(current_vaddr), copy_amount);
+ HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount);
std::memset(host_ptr, 0, copy_amount);
},
[](const std::size_t copy_amount) {});
@@ -420,7 +468,7 @@ struct Memory::Impl {
const std::size_t block_size) {
// dc cvac: Store to point of coherency
// CPU flush -> GPU invalidate
- system.GPU().InvalidateRegion(GetInteger(current_vaddr), block_size);
+ HandleRasterizerWrite(GetInteger(current_vaddr), block_size);
};
return PerformCacheOperation(dest_addr, size, on_rasterizer);
}
@@ -430,7 +478,7 @@ struct Memory::Impl {
const std::size_t block_size) {
// dc civac: Store to point of coherency, and invalidate from cache
// CPU flush -> GPU invalidate
- system.GPU().InvalidateRegion(GetInteger(current_vaddr), block_size);
+ HandleRasterizerWrite(GetInteger(current_vaddr), block_size);
};
return PerformCacheOperation(dest_addr, size, on_rasterizer);
}
@@ -767,7 +815,18 @@ struct Memory::Impl {
}
void HandleRasterizerWrite(VAddr address, size_t size) {
- const size_t core = system.GetCurrentHostThreadID();
+ constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1;
+ const size_t core = std::min(system.GetCurrentHostThreadID(),
+ sys_core); // any other calls threads go to syscore.
+ // Guard on sys_core;
+ if (core == sys_core) [[unlikely]] {
+ sys_core_guard.lock();
+ }
+ SCOPE_EXIT({
+ if (core == sys_core) [[unlikely]] {
+ sys_core_guard.unlock();
+ }
+ });
auto& current_area = rasterizer_write_areas[core];
VAddr subaddress = address >> YUZU_PAGEBITS;
bool do_collection = current_area.last_address == subaddress;
@@ -799,6 +858,7 @@ struct Memory::Impl {
rasterizer_read_areas{};
std::array<GPUDirtyState, Core::Hardware::NUM_CPU_CORES> rasterizer_write_areas{};
std::span<Core::GPUDirtyMemoryManager> gpu_dirty_managers;
+ std::mutex sys_core_guard;
};
Memory::Memory(Core::System& system_) : system{system_} {
@@ -816,17 +876,22 @@ void Memory::SetCurrentPageTable(Kernel::KProcess& process, u32 core_id) {
}
void Memory::MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
- Common::PhysicalAddress target) {
- impl->MapMemoryRegion(page_table, base, size, target);
+ Common::PhysicalAddress target, Common::MemoryPermission perms) {
+ impl->MapMemoryRegion(page_table, base, size, target, perms);
}
void Memory::UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size) {
impl->UnmapRegion(page_table, base, size);
}
+void Memory::ProtectRegion(Common::PageTable& page_table, Common::ProcessAddress vaddr, u64 size,
+ Common::MemoryPermission perms) {
+ impl->ProtectRegion(page_table, GetInteger(vaddr), size, perms);
+}
+
bool Memory::IsValidVirtualAddress(const Common::ProcessAddress vaddr) const {
const Kernel::KProcess& process = *system.ApplicationProcess();
- const auto& page_table = process.GetPageTable().PageTableImpl();
+ const auto& page_table = process.GetPageTable().GetImpl();
const size_t page = vaddr >> YUZU_PAGEBITS;
if (page >= page_table.pointers.size()) {
return false;
@@ -986,4 +1051,17 @@ void Memory::FlushRegion(Common::ProcessAddress dest_addr, size_t size) {
impl->FlushRegion(dest_addr, size);
}
+bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
+ bool mapped = true;
+ u8* const ptr = impl->GetPointerImpl(
+ GetInteger(vaddr),
+ [&] {
+ LOG_ERROR(HW_Memory, "Unmapped InvalidateNCE for {} bytes @ {:#x}", size,
+ GetInteger(vaddr));
+ mapped = false;
+ },
+ [&] { impl->system.GPU().InvalidateRegion(GetInteger(vaddr), size); });
+ return mapped && ptr != nullptr;
+}
+
} // namespace Core::Memory
diff --git a/src/core/memory.h b/src/core/memory.h
index 13047a545..ed8ebb5eb 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -15,8 +15,9 @@
#include "core/hle/result.h"
namespace Common {
+enum class MemoryPermission : u32;
struct PageTable;
-}
+} // namespace Common
namespace Core {
class System;
@@ -82,9 +83,10 @@ public:
* @param size The amount of bytes to map. Must be page-aligned.
* @param target Buffer with the memory backing the mapping. Must be of length at least
* `size`.
+ * @param perms The permissions to map the memory with.
*/
void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
- Common::PhysicalAddress target);
+ Common::PhysicalAddress target, Common::MemoryPermission perms);
/**
* Unmaps a region of the emulated process address space.
@@ -96,6 +98,17 @@ public:
void UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size);
/**
+ * Protects a region of the emulated process address space with the new permissions.
+ *
+ * @param page_table The page table of the emulated process.
+ * @param base The start address to re-protect. Must be page-aligned.
+ * @param size The amount of bytes to protect. Must be page-aligned.
+ * @param perms The permissions the address range is mapped.
+ */
+ void ProtectRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
+ Common::MemoryPermission perms);
+
+ /**
* Checks whether or not the supplied address is a valid virtual
* address for the current process.
*
@@ -472,6 +485,7 @@ public:
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
void InvalidateRegion(Common::ProcessAddress dest_addr, size_t size);
+ bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
void FlushRegion(Common::ProcessAddress dest_addr, size_t size);
private:
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 53a89cc8f..db30ba598 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -10,7 +10,8 @@
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/hid/controllers/npad.h"
-#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/hid/hid_server.h"
+#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/sm/sm.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
@@ -54,23 +55,20 @@ void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size)
}
u64 StandardVmCallbacks::HidKeysDown() {
- const auto hid = system.ServiceManager().GetService<Service::HID::Hid>("hid");
+ const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
if (hid == nullptr) {
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
return 0;
}
- const auto applet_resource = hid->GetAppletResource();
+ const auto applet_resource = hid->GetResourceManager();
if (applet_resource == nullptr) {
LOG_WARNING(CheatEngine,
"Attempted to read input state, but applet resource is not initialized!");
return 0;
}
- const auto press_state =
- applet_resource
- ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
- .GetAndResetPressState();
+ const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
return static_cast<u64>(press_state & HID::NpadButton::All);
}
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index ed875d444..5d168cbc1 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -116,7 +116,7 @@ json GetProcessorStateDataAuto(Core::System& system) {
Core::ARM_Interface::ThreadContext64 context{};
arm.SaveContext(context);
- return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
+ return GetProcessorStateData(process->Is64Bit() ? "AArch64" : "AArch32",
GetInteger(process->GetEntryPoint()), context.sp, context.pc,
context.pstate, context.cpu_registers);
}
diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt
new file mode 100644
index 000000000..22e9337c4
--- /dev/null
+++ b/src/frontend_common/CMakeLists.txt
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+add_library(frontend_common STATIC
+ config.cpp
+ config.h
+)
+
+create_target_directory_groups(frontend_common)
+target_link_libraries(frontend_common PUBLIC core SimpleIni::SimpleIni PRIVATE common Boost::headers)
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
new file mode 100644
index 000000000..1a0491c2c
--- /dev/null
+++ b/src/frontend_common/config.cpp
@@ -0,0 +1,1010 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <array>
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/settings.h"
+#include "common/settings_common.h"
+#include "common/settings_enums.h"
+#include "config.h"
+#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "network/network.h"
+
+#include <boost/algorithm/string/replace.hpp>
+
+#include "common/string_util.h"
+
+namespace FS = Common::FS;
+
+Config::Config(const ConfigType config_type)
+ : type(config_type), global{config_type == ConfigType::GlobalConfig} {}
+
+void Config::Initialize(const std::string& config_name) {
+ const std::filesystem::path 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);
+ void(FS::CreateParentDir(config_loc));
+ SetUpIni();
+ Reload();
+ break;
+ case ConfigType::PerGameConfig:
+ config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
+ void(FS::CreateParentDir(config_loc));
+ SetUpIni();
+ Reload();
+ break;
+ case ConfigType::InputProfile:
+ config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
+ void(FS::CreateParentDir(config_loc));
+ SetUpIni();
+ break;
+ }
+}
+
+void Config::Initialize(const std::optional<std::string> config_path) {
+ const std::filesystem::path default_sdl_config_path =
+ FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini";
+ config_loc = config_path.value_or(FS::PathToUTF8String(default_sdl_config_path));
+ void(FS::CreateParentDir(config_loc));
+ SetUpIni();
+ Reload();
+}
+
+void Config::WriteToIni() const {
+ FILE* fp = nullptr;
+#ifdef _WIN32
+ fp = _wfopen(Common::UTF8ToUTF16W(config_loc).data(), L"wb");
+#else
+ fp = fopen(config_loc.c_str(), "wb");
+#endif
+
+ if (fp == nullptr) {
+ LOG_ERROR(Frontend, "Config file could not be saved!");
+ return;
+ }
+
+ CSimpleIniA::FileWriter writer(fp);
+ const SI_Error rc = config->Save(writer, false);
+ if (rc < 0) {
+ LOG_ERROR(Frontend, "Config file could not be saved!");
+ }
+ fclose(fp);
+}
+
+void Config::SetUpIni() {
+ config = std::make_unique<CSimpleIniA>();
+ config->SetUnicode(true);
+ config->SetSpaces(false);
+
+ FILE* fp = nullptr;
+#ifdef _WIN32
+ _wfopen_s(&fp, Common::UTF8ToUTF16W(config_loc).data(), L"rb, ccs=UTF-8");
+ if (fp == nullptr) {
+ fp = _wfopen(Common::UTF8ToUTF16W(config_loc).data(), L"wb, ccs=UTF-8");
+ }
+#else
+ fp = fopen(config_loc.c_str(), "rb");
+ if (fp == nullptr) {
+ fp = fopen(config_loc.c_str(), "wb");
+ }
+#endif
+
+ if (fp == nullptr) {
+ LOG_ERROR(Frontend, "Config file could not be loaded!");
+ return;
+ }
+
+ if (SI_Error rc = config->LoadFile(fp); rc < 0) {
+ LOG_ERROR(Frontend, "Config file could not be loaded!");
+ }
+ fclose(fp);
+}
+
+bool Config::IsCustomConfig() const {
+ return type == ConfigType::PerGameConfig;
+}
+
+void Config::ReadPlayerValues(const std::size_t player_index) {
+ std::string player_prefix;
+ if (type != ConfigType::InputProfile) {
+ player_prefix.append("player_").append(ToString(player_index)).append("_");
+ }
+
+ auto& player = Settings::values.players.GetValue()[player_index];
+ if (IsCustomConfig()) {
+ const auto profile_name =
+ ReadStringSetting(std::string(player_prefix).append("profile_name"));
+ if (profile_name.empty()) {
+ // Use the global input config
+ player = Settings::values.players.GetValue(true)[player_index];
+ return;
+ }
+ player.profile_name = profile_name;
+ }
+
+ if (player_prefix.empty() && Settings::IsConfiguringGlobal()) {
+ const auto controller = static_cast<Settings::ControllerType>(
+ ReadIntegerSetting(std::string(player_prefix).append("type"),
+ static_cast<u8>(Settings::ControllerType::ProController)));
+
+ if (controller == Settings::ControllerType::LeftJoycon ||
+ controller == Settings::ControllerType::RightJoycon) {
+ player.controller_type = controller;
+ }
+ } else {
+ std::string connected_key = player_prefix;
+ player.connected = ReadBooleanSetting(connected_key.append("connected"),
+ std::make_optional(player_index == 0));
+
+ player.controller_type = static_cast<Settings::ControllerType>(
+ ReadIntegerSetting(std::string(player_prefix).append("type"),
+ static_cast<u8>(Settings::ControllerType::ProController)));
+
+ player.vibration_enabled = ReadBooleanSetting(
+ std::string(player_prefix).append("vibration_enabled"), std::make_optional(true));
+
+ player.vibration_strength = static_cast<int>(
+ ReadIntegerSetting(std::string(player_prefix).append("vibration_strength"), 100));
+
+ player.body_color_left = static_cast<u32>(ReadIntegerSetting(
+ std::string(player_prefix).append("body_color_left"), Settings::JOYCON_BODY_NEON_BLUE));
+ player.body_color_right = static_cast<u32>(ReadIntegerSetting(
+ std::string(player_prefix).append("body_color_right"), Settings::JOYCON_BODY_NEON_RED));
+ player.button_color_left = static_cast<u32>(
+ ReadIntegerSetting(std::string(player_prefix).append("button_color_left"),
+ Settings::JOYCON_BUTTONS_NEON_BLUE));
+ player.button_color_right = static_cast<u32>(
+ ReadIntegerSetting(std::string(player_prefix).append("button_color_right"),
+ Settings::JOYCON_BUTTONS_NEON_RED));
+ }
+}
+
+void Config::ReadTouchscreenValues() {
+ Settings::values.touchscreen.enabled =
+ ReadBooleanSetting(std::string("touchscreen_enabled"), std::make_optional(true));
+ Settings::values.touchscreen.rotation_angle =
+ static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_angle"), 0));
+ Settings::values.touchscreen.diameter_x =
+ static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_diameter_x"), 15));
+ Settings::values.touchscreen.diameter_y =
+ static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_diameter_y"), 15));
+}
+
+void Config::ReadAudioValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Audio));
+
+ ReadCategory(Settings::Category::Audio);
+ ReadCategory(Settings::Category::UiAudio);
+
+ EndGroup();
+}
+
+void Config::ReadControlValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ ReadCategory(Settings::Category::Controls);
+
+ Settings::values.players.SetGlobal(!IsCustomConfig());
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ ReadPlayerValues(p);
+ }
+
+ // Disable docked mode if handheld is selected
+ const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
+ if (controller_type == Settings::ControllerType::Handheld) {
+ Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
+ Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
+ }
+
+ if (IsCustomConfig()) {
+ EndGroup();
+ return;
+ }
+ ReadTouchscreenValues();
+ ReadMotionTouchValues();
+
+ EndGroup();
+}
+
+void Config::ReadMotionTouchValues() {
+ int num_touch_from_button_maps = BeginArray(std::string("touch_from_button_maps"));
+
+ if (num_touch_from_button_maps > 0) {
+ for (int i = 0; i < num_touch_from_button_maps; ++i) {
+ SetArrayIndex(i);
+
+ Settings::TouchFromButtonMap map;
+ map.name = ReadStringSetting(std::string("name"), std::string("default"));
+
+ const int num_touch_maps = BeginArray(std::string("entries"));
+ map.buttons.reserve(num_touch_maps);
+ for (int j = 0; j < num_touch_maps; j++) {
+ SetArrayIndex(j);
+ std::string touch_mapping = ReadStringSetting(std::string("bind"));
+ map.buttons.emplace_back(std::move(touch_mapping));
+ }
+ EndArray(); // entries
+ Settings::values.touch_from_button_maps.emplace_back(std::move(map));
+ }
+ } else {
+ Settings::values.touch_from_button_maps.emplace_back(
+ Settings::TouchFromButtonMap{"default", {}});
+ num_touch_from_button_maps = 1;
+ }
+ EndArray(); // touch_from_button_maps
+
+ Settings::values.touch_from_button_map_index = std::clamp(
+ Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
+}
+
+void Config::ReadCoreValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Core));
+
+ ReadCategory(Settings::Category::Core);
+
+ EndGroup();
+}
+
+void Config::ReadDataStorageValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
+
+ FS::SetYuzuPath(FS::YuzuPath::NANDDir, ReadStringSetting(std::string("nand_directory")));
+ FS::SetYuzuPath(FS::YuzuPath::SDMCDir, ReadStringSetting(std::string("sdmc_directory")));
+ FS::SetYuzuPath(FS::YuzuPath::LoadDir, ReadStringSetting(std::string("load_directory")));
+ FS::SetYuzuPath(FS::YuzuPath::DumpDir, ReadStringSetting(std::string("dump_directory")));
+ FS::SetYuzuPath(FS::YuzuPath::TASDir, ReadStringSetting(std::string("tas_directory")));
+
+ ReadCategory(Settings::Category::DataStorage);
+
+ EndGroup();
+}
+
+void Config::ReadDebuggingValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
+
+ // Intentionally not using the QT default setting as this is intended to be changed in the ini
+ Settings::values.record_frame_times =
+ ReadBooleanSetting(std::string("record_frame_times"), std::make_optional(false));
+
+ ReadCategory(Settings::Category::Debugging);
+ ReadCategory(Settings::Category::DebuggingGraphics);
+
+ EndGroup();
+}
+
+void Config::ReadServiceValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
+
+ ReadCategory(Settings::Category::Services);
+
+ EndGroup();
+}
+
+void Config::ReadDisabledAddOnValues() {
+ // Custom config section
+ BeginGroup(std::string("DisabledAddOns"));
+
+ const int size = BeginArray(std::string(""));
+ for (int i = 0; i < size; ++i) {
+ SetArrayIndex(i);
+ const auto title_id = ReadUnsignedIntegerSetting(std::string("title_id"), 0);
+ std::vector<std::string> out;
+ const int d_size = BeginArray("disabled");
+ for (int j = 0; j < d_size; ++j) {
+ SetArrayIndex(j);
+ out.push_back(ReadStringSetting(std::string("d"), std::string("")));
+ }
+ EndArray(); // d
+ Settings::values.disabled_addons.insert_or_assign(title_id, out);
+ }
+ EndArray(); // Base disabled addons array - Has no base key
+
+ EndGroup();
+}
+
+void Config::ReadMiscellaneousValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous));
+
+ ReadCategory(Settings::Category::Miscellaneous);
+
+ EndGroup();
+}
+
+void Config::ReadCpuValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Cpu));
+
+ ReadCategory(Settings::Category::Cpu);
+ ReadCategory(Settings::Category::CpuDebug);
+ ReadCategory(Settings::Category::CpuUnsafe);
+
+ EndGroup();
+}
+
+void Config::ReadRendererValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Renderer));
+
+ ReadCategory(Settings::Category::Renderer);
+ ReadCategory(Settings::Category::RendererAdvanced);
+ ReadCategory(Settings::Category::RendererDebug);
+
+ EndGroup();
+}
+
+void Config::ReadScreenshotValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
+
+ ReadCategory(Settings::Category::Screenshots);
+ FS::SetYuzuPath(FS::YuzuPath::ScreenshotsDir,
+ ReadStringSetting(std::string("screenshot_path")));
+
+ EndGroup();
+}
+
+void Config::ReadSystemValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::System));
+
+ ReadCategory(Settings::Category::System);
+ ReadCategory(Settings::Category::SystemAudio);
+
+ EndGroup();
+}
+
+void Config::ReadWebServiceValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::WebService));
+
+ ReadCategory(Settings::Category::WebService);
+
+ EndGroup();
+}
+
+void Config::ReadNetworkValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
+
+ ReadCategory(Settings::Category::Network);
+
+ EndGroup();
+}
+
+void Config::ReadValues() {
+ if (global) {
+ ReadDataStorageValues();
+ ReadDebuggingValues();
+ ReadDisabledAddOnValues();
+ ReadNetworkValues();
+ ReadServiceValues();
+ ReadWebServiceValues();
+ ReadMiscellaneousValues();
+ }
+ ReadControlValues();
+ ReadCoreValues();
+ ReadCpuValues();
+ ReadRendererValues();
+ ReadAudioValues();
+ ReadSystemValues();
+}
+
+void Config::SavePlayerValues(const std::size_t player_index) {
+ std::string player_prefix;
+ if (type != ConfigType::InputProfile) {
+ player_prefix = std::string("player_").append(ToString(player_index)).append("_");
+ }
+
+ const auto& player = Settings::values.players.GetValue()[player_index];
+ if (IsCustomConfig()) {
+ if (player.profile_name.empty()) {
+ // No custom profile selected
+ return;
+ }
+ WriteSetting(std::string(player_prefix).append("profile_name"), player.profile_name,
+ std::make_optional(std::string("")));
+ }
+
+ WriteSetting(std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type),
+ std::make_optional(static_cast<u8>(Settings::ControllerType::ProController)));
+
+ if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) {
+ WriteSetting(std::string(player_prefix).append("connected"), player.connected,
+ std::make_optional(player_index == 0));
+ WriteSetting(std::string(player_prefix).append("vibration_enabled"),
+ player.vibration_enabled, std::make_optional(true));
+ WriteSetting(std::string(player_prefix).append("vibration_strength"),
+ player.vibration_strength, std::make_optional(100));
+ WriteSetting(std::string(player_prefix).append("body_color_left"), player.body_color_left,
+ std::make_optional(Settings::JOYCON_BODY_NEON_BLUE));
+ WriteSetting(std::string(player_prefix).append("body_color_right"), player.body_color_right,
+ std::make_optional(Settings::JOYCON_BODY_NEON_RED));
+ WriteSetting(std::string(player_prefix).append("button_color_left"),
+ player.button_color_left,
+ std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE));
+ WriteSetting(std::string(player_prefix).append("button_color_right"),
+ player.button_color_right,
+ std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED));
+ }
+}
+
+void Config::SaveTouchscreenValues() {
+ const auto& touchscreen = Settings::values.touchscreen;
+
+ WriteSetting(std::string("touchscreen_enabled"), touchscreen.enabled, std::make_optional(true));
+
+ WriteSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle,
+ std::make_optional(static_cast<u32>(0)));
+ WriteSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x,
+ std::make_optional(static_cast<u32>(15)));
+ WriteSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y,
+ std::make_optional(static_cast<u32>(15)));
+}
+
+void Config::SaveMotionTouchValues() {
+ BeginArray(std::string("touch_from_button_maps"));
+ for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
+ SetArrayIndex(static_cast<int>(p));
+ WriteSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name,
+ std::make_optional(std::string("default")));
+
+ BeginArray(std::string("entries"));
+ for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
+ ++q) {
+ SetArrayIndex(static_cast<int>(q));
+ WriteSetting(std::string("bind"),
+ Settings::values.touch_from_button_maps[p].buttons[q]);
+ }
+ EndArray(); // entries
+ }
+ EndArray(); // touch_from_button_maps
+}
+
+void Config::SaveValues() {
+ if (global) {
+ SaveDataStorageValues();
+ SaveDebuggingValues();
+ SaveDisabledAddOnValues();
+ SaveNetworkValues();
+ SaveWebServiceValues();
+ SaveMiscellaneousValues();
+ }
+ SaveControlValues();
+ SaveCoreValues();
+ SaveCpuValues();
+ SaveRendererValues();
+ SaveAudioValues();
+ SaveSystemValues();
+
+ WriteToIni();
+}
+
+void Config::SaveAudioValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Audio));
+
+ WriteCategory(Settings::Category::Audio);
+ WriteCategory(Settings::Category::UiAudio);
+
+ EndGroup();
+}
+
+void Config::SaveControlValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ WriteCategory(Settings::Category::Controls);
+
+ Settings::values.players.SetGlobal(!IsCustomConfig());
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ SavePlayerValues(p);
+ }
+ if (IsCustomConfig()) {
+ EndGroup();
+ return;
+ }
+ SaveTouchscreenValues();
+ SaveMotionTouchValues();
+
+ EndGroup();
+}
+
+void Config::SaveCoreValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Core));
+
+ WriteCategory(Settings::Category::Core);
+
+ EndGroup();
+}
+
+void Config::SaveDataStorageValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
+
+ WriteSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir),
+ std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
+ WriteSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir),
+ std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
+ WriteSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir),
+ std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
+ WriteSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir),
+ std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
+ WriteSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir),
+ std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
+
+ WriteCategory(Settings::Category::DataStorage);
+
+ EndGroup();
+}
+
+void Config::SaveDebuggingValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
+
+ // Intentionally not using the QT default setting as this is intended to be changed in the ini
+ WriteSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
+
+ WriteCategory(Settings::Category::Debugging);
+ WriteCategory(Settings::Category::DebuggingGraphics);
+
+ EndGroup();
+}
+
+void Config::SaveNetworkValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
+
+ WriteCategory(Settings::Category::Network);
+
+ EndGroup();
+}
+
+void Config::SaveDisabledAddOnValues() {
+ // Custom config section
+ BeginGroup(std::string("DisabledAddOns"));
+
+ int i = 0;
+ BeginArray(std::string(""));
+ for (const auto& elem : Settings::values.disabled_addons) {
+ SetArrayIndex(i);
+ WriteSetting(std::string("title_id"), elem.first, std::make_optional(static_cast<u64>(0)));
+ BeginArray(std::string("disabled"));
+ for (std::size_t j = 0; j < elem.second.size(); ++j) {
+ SetArrayIndex(static_cast<int>(j));
+ WriteSetting(std::string("d"), elem.second[j], std::make_optional(std::string("")));
+ }
+ EndArray(); // disabled
+ ++i;
+ }
+ EndArray(); // Base disabled addons array - Has no base key
+
+ EndGroup();
+}
+
+void Config::SaveMiscellaneousValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous));
+
+ WriteCategory(Settings::Category::Miscellaneous);
+
+ EndGroup();
+}
+
+void Config::SaveCpuValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Cpu));
+
+ WriteCategory(Settings::Category::Cpu);
+ WriteCategory(Settings::Category::CpuDebug);
+ WriteCategory(Settings::Category::CpuUnsafe);
+
+ EndGroup();
+}
+
+void Config::SaveRendererValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Renderer));
+
+ WriteCategory(Settings::Category::Renderer);
+ WriteCategory(Settings::Category::RendererAdvanced);
+ WriteCategory(Settings::Category::RendererDebug);
+
+ EndGroup();
+}
+
+void Config::SaveScreenshotValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
+
+ WriteSetting(std::string("screenshot_path"),
+ FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir));
+ WriteCategory(Settings::Category::Screenshots);
+
+ EndGroup();
+}
+
+void Config::SaveSystemValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::System));
+
+ WriteCategory(Settings::Category::System);
+ WriteCategory(Settings::Category::SystemAudio);
+
+ EndGroup();
+}
+
+void Config::SaveWebServiceValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::WebService));
+
+ WriteCategory(Settings::Category::WebService);
+
+ EndGroup();
+}
+
+bool Config::ReadBooleanSetting(const std::string& key, const std::optional<bool> default_value) {
+ std::string full_key = GetFullKey(key, false);
+ if (!default_value.has_value()) {
+ return config->GetBoolValue(GetSection().c_str(), full_key.c_str(), false);
+ }
+
+ if (config->GetBoolValue(GetSection().c_str(),
+ std::string(full_key).append("\\default").c_str(), false)) {
+ return static_cast<bool>(default_value.value());
+ } else {
+ return config->GetBoolValue(GetSection().c_str(), full_key.c_str(),
+ static_cast<bool>(default_value.value()));
+ }
+}
+
+s64 Config::ReadIntegerSetting(const std::string& key, const std::optional<s64> default_value) {
+ std::string full_key = GetFullKey(key, false);
+ if (!default_value.has_value()) {
+ try {
+ return std::stoll(
+ std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), "0")));
+ } catch (...) {
+ return 0;
+ }
+ }
+
+ s64 result = 0;
+ if (config->GetBoolValue(GetSection().c_str(),
+ std::string(full_key).append("\\default").c_str(), true)) {
+ result = default_value.value();
+ } else {
+ try {
+ result = std::stoll(std::string(config->GetValue(
+ GetSection().c_str(), full_key.c_str(), ToString(default_value.value()).c_str())));
+ } catch (...) {
+ result = default_value.value();
+ }
+ }
+ return result;
+}
+
+u64 Config::ReadUnsignedIntegerSetting(const std::string& key,
+ const std::optional<u64> default_value) {
+ std::string full_key = GetFullKey(key, false);
+ if (!default_value.has_value()) {
+ try {
+ return std::stoull(
+ std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), "0")));
+ } catch (...) {
+ return 0;
+ }
+ }
+
+ u64 result = 0;
+ if (config->GetBoolValue(GetSection().c_str(),
+ std::string(full_key).append("\\default").c_str(), true)) {
+ result = default_value.value();
+ } else {
+ try {
+ result = std::stoull(std::string(config->GetValue(
+ GetSection().c_str(), full_key.c_str(), ToString(default_value.value()).c_str())));
+ } catch (...) {
+ result = default_value.value();
+ }
+ }
+ return result;
+}
+
+double Config::ReadDoubleSetting(const std::string& key,
+ const std::optional<double> default_value) {
+ std::string full_key = GetFullKey(key, false);
+ if (!default_value.has_value()) {
+ return config->GetDoubleValue(GetSection().c_str(), full_key.c_str(), 0);
+ }
+
+ double result;
+ if (config->GetBoolValue(GetSection().c_str(),
+ std::string(full_key).append("\\default").c_str(), true)) {
+ result = default_value.value();
+ } else {
+ result =
+ config->GetDoubleValue(GetSection().c_str(), full_key.c_str(), default_value.value());
+ }
+ return result;
+}
+
+std::string Config::ReadStringSetting(const std::string& key,
+ const std::optional<std::string> default_value) {
+ std::string result;
+ std::string full_key = GetFullKey(key, false);
+ if (!default_value.has_value()) {
+ result = config->GetValue(GetSection().c_str(), full_key.c_str(), "");
+ boost::replace_all(result, "\"", "");
+ return result;
+ }
+
+ if (config->GetBoolValue(GetSection().c_str(),
+ std::string(full_key).append("\\default").c_str(), true)) {
+ result = default_value.value();
+ } else {
+ result =
+ config->GetValue(GetSection().c_str(), full_key.c_str(), default_value.value().c_str());
+ }
+ boost::replace_all(result, "\"", "");
+ boost::replace_all(result, "//", "/");
+ return result;
+}
+
+bool Config::Exists(const std::string& section, const std::string& key) const {
+ const std::string value = config->GetValue(section.c_str(), key.c_str(), "");
+ return !value.empty();
+}
+
+template <typename Type>
+void Config::WriteSetting(const std::string& key, const Type& value,
+ const std::optional<Type>& default_value,
+ const std::optional<bool>& use_global) {
+ std::string full_key = GetFullKey(key, false);
+
+ std::string saved_value;
+ std::string string_default;
+ if constexpr (std::is_same_v<Type, std::string>) {
+ saved_value.append(AdjustOutputString(value));
+ if (default_value.has_value()) {
+ string_default.append(AdjustOutputString(default_value.value()));
+ }
+ } else {
+ saved_value.append(AdjustOutputString(ToString(value)));
+ if (default_value.has_value()) {
+ string_default.append(ToString(default_value.value()));
+ }
+ }
+
+ if (default_value.has_value() && use_global.has_value()) {
+ if (!global) {
+ WriteSettingInternal(std::string(full_key).append("\\global"),
+ ToString(use_global.value()));
+ }
+ if (global || use_global.value() == false) {
+ WriteSettingInternal(std::string(full_key).append("\\default"),
+ ToString(string_default == saved_value));
+ WriteSettingInternal(full_key, saved_value);
+ }
+ } else if (default_value.has_value() && !use_global.has_value()) {
+ WriteSettingInternal(std::string(full_key).append("\\default"),
+ ToString(string_default == saved_value));
+ WriteSettingInternal(full_key, saved_value);
+ } else {
+ WriteSettingInternal(full_key, saved_value);
+ }
+}
+
+void Config::WriteSettingInternal(const std::string& key, const std::string& value) {
+ config->SetValue(GetSection().c_str(), key.c_str(), value.c_str());
+}
+
+void Config::Reload() {
+ ReadValues();
+ // To apply default value changes
+ SaveValues();
+}
+
+void Config::Save() {
+ SaveValues();
+}
+
+void Config::ClearControlPlayerValues() const {
+ // If key is an empty string, all keys in the current group() are removed.
+ const char* section = Settings::TranslateCategory(Settings::Category::Controls);
+ CSimpleIniA::TNamesDepend keys;
+ config->GetAllKeys(section, keys);
+ for (const auto& key : keys) {
+ if (std::string(config->GetValue(section, key.pItem)).empty()) {
+ config->Delete(section, key.pItem);
+ }
+ }
+}
+
+const std::string& Config::GetConfigFilePath() const {
+ return config_loc;
+}
+
+void Config::ReadCategory(const Settings::Category category) {
+ const auto& settings = FindRelevantList(category);
+ std::ranges::for_each(settings, [&](const auto& setting) { ReadSettingGeneric(setting); });
+}
+
+void Config::WriteCategory(const Settings::Category category) {
+ const auto& settings = FindRelevantList(category);
+ std::ranges::for_each(settings, [&](const auto& setting) { WriteSettingGeneric(setting); });
+}
+
+void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) {
+ if (!setting->Save() || (!setting->Switchable() && !global)) {
+ return;
+ }
+
+ const std::string key = AdjustKey(setting->GetLabel());
+ const std::string default_value(setting->DefaultToString());
+
+ bool use_global = true;
+ if (setting->Switchable() && !global) {
+ use_global =
+ ReadBooleanSetting(std::string(key).append("\\use_global"), std::make_optional(true));
+ setting->SetGlobal(use_global);
+ }
+
+ if (global || !use_global) {
+ const bool is_default =
+ ReadBooleanSetting(std::string(key).append("\\default"), std::make_optional(true));
+ if (!is_default) {
+ const std::string setting_string = ReadStringSetting(key, default_value);
+ setting->LoadString(setting_string);
+ } else {
+ // Empty string resets the Setting to default
+ setting->LoadString("");
+ }
+ }
+}
+
+void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) {
+ if (!setting->Save()) {
+ return;
+ }
+
+ std::string key = AdjustKey(setting->GetLabel());
+ if (setting->Switchable()) {
+ if (!global) {
+ WriteSetting(std::string(key).append("\\use_global"), setting->UsingGlobal());
+ }
+ if (global || !setting->UsingGlobal()) {
+ WriteSetting(std::string(key).append("\\default"),
+ setting->ToString() == setting->DefaultToString());
+ WriteSetting(key, setting->ToString());
+ }
+ } else if (global) {
+ WriteSetting(std::string(key).append("\\default"),
+ setting->ToString() == setting->DefaultToString());
+ WriteSetting(key, setting->ToString());
+ }
+}
+
+void Config::BeginGroup(const std::string& group) {
+ // You can't begin a group while reading/writing from a config array
+ ASSERT(array_stack.empty());
+
+ key_stack.push_back(AdjustKey(group));
+}
+
+void Config::EndGroup() {
+ // You can't end a group if you haven't started one yet
+ ASSERT(!key_stack.empty());
+
+ // You can't end a group when reading/writing from a config array
+ ASSERT(array_stack.empty());
+
+ key_stack.pop_back();
+}
+
+std::string Config::GetSection() {
+ if (key_stack.empty()) {
+ return std::string{""};
+ }
+
+ return key_stack.front();
+}
+
+std::string Config::GetGroup() const {
+ if (key_stack.size() <= 1) {
+ return std::string{""};
+ }
+
+ std::string key;
+ for (size_t i = 1; i < key_stack.size(); ++i) {
+ key.append(key_stack[i]).append("\\");
+ }
+ return key;
+}
+
+std::string Config::AdjustKey(const std::string& key) {
+ std::string adjusted_key(key);
+ boost::replace_all(adjusted_key, "/", "\\");
+ boost::replace_all(adjusted_key, " ", "%20");
+ return adjusted_key;
+}
+
+std::string Config::AdjustOutputString(const std::string& string) {
+ std::string adjusted_string(string);
+ boost::replace_all(adjusted_string, "\\", "/");
+
+ // Windows requires that two forward slashes are used at the start of a path for unmapped
+ // network drives so we have to watch for that here
+#ifndef ANDROID
+ if (string.substr(0, 2) == "//") {
+ boost::replace_all(adjusted_string, "//", "/");
+ adjusted_string.insert(0, "/");
+ } else {
+ boost::replace_all(adjusted_string, "//", "/");
+ }
+#endif
+
+ // Needed for backwards compatibility with QSettings deserialization
+ for (const auto& special_character : special_characters) {
+ if (adjusted_string.find(special_character) != std::string::npos) {
+ adjusted_string.insert(0, "\"");
+ adjusted_string.append("\"");
+ break;
+ }
+ }
+ return adjusted_string;
+}
+
+std::string Config::GetFullKey(const std::string& key, bool skipArrayIndex) {
+ if (array_stack.empty()) {
+ return std::string(GetGroup()).append(AdjustKey(key));
+ }
+
+ std::string array_key;
+ for (size_t i = 0; i < array_stack.size(); ++i) {
+ if (!array_stack[i].name.empty()) {
+ array_key.append(array_stack[i].name).append("\\");
+ }
+
+ if (!skipArrayIndex || (array_stack.size() - 1 != i && array_stack.size() > 1)) {
+ array_key.append(ToString(array_stack[i].index)).append("\\");
+ }
+ }
+ std::string final_key = std::string(GetGroup()).append(array_key).append(AdjustKey(key));
+ return final_key;
+}
+
+int Config::BeginArray(const std::string& array) {
+ array_stack.push_back(ConfigArray{AdjustKey(array), 0, 0});
+ const int size = config->GetLongValue(GetSection().c_str(),
+ GetFullKey(std::string("size"), true).c_str(), 0);
+ array_stack.back().size = size;
+ return size;
+}
+
+void Config::EndArray() {
+ // You can't end a config array before starting one
+ ASSERT(!array_stack.empty());
+
+ // Set the array size to 0 if the array is ended without changing the index
+ int size = 0;
+ if (array_stack.back().index != 0) {
+ size = array_stack.back().size;
+ }
+
+ // Write out the size to config
+ if (key_stack.size() == 1 && array_stack.back().name.empty()) {
+ // Edge-case where the first array created doesn't have a name
+ config->SetValue(GetSection().c_str(), std::string("size").c_str(), ToString(size).c_str());
+ } else {
+ const auto key = GetFullKey(std::string("size"), true);
+ config->SetValue(GetSection().c_str(), key.c_str(), ToString(size).c_str());
+ }
+
+ array_stack.pop_back();
+}
+
+void Config::SetArrayIndex(const int index) {
+ // You can't set the array index if you haven't started one yet
+ ASSERT(!array_stack.empty());
+
+ const int array_index = index + 1;
+
+ // You can't exceed the known max size of the array by more than 1
+ ASSERT(array_stack.front().size + 1 >= array_index);
+
+ // Change the config array size to the current index since you may want
+ // to reduce the number of elements that you read back from the config
+ // in the future.
+ array_stack.back().size = array_index;
+ array_stack.back().index = array_index;
+}
diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h
new file mode 100644
index 000000000..b3812af17
--- /dev/null
+++ b/src/frontend_common/config.h
@@ -0,0 +1,211 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "common/settings.h"
+
+#define SI_NO_CONVERSION
+#include <SimpleIni.h>
+#include <boost/algorithm/string/replace.hpp>
+
+// Workaround for conflicting definition in libloaderapi.h caused by SimpleIni
+#undef LoadString
+#undef CreateFile
+#undef DeleteFile
+#undef CopyFile
+#undef CreateDirectory
+#undef MoveFile
+
+namespace Core {
+class System;
+}
+
+class Config {
+public:
+ enum class ConfigType {
+ GlobalConfig,
+ PerGameConfig,
+ InputProfile,
+ };
+
+ virtual ~Config() = default;
+
+ void ClearControlPlayerValues() const;
+
+ [[nodiscard]] const std::string& GetConfigFilePath() const;
+
+ [[nodiscard]] bool Exists(const std::string& section, const std::string& key) const;
+
+protected:
+ explicit Config(ConfigType config_type = ConfigType::GlobalConfig);
+
+ void Initialize(const std::string& config_name = "config");
+ void Initialize(std::optional<std::string> config_path);
+
+ void WriteToIni() const;
+
+ void SetUpIni();
+ [[nodiscard]] bool IsCustomConfig() const;
+
+ void Reload();
+ void Save();
+
+ /**
+ * Derived config classes must implement this so they can reload all platform-specific
+ * values and global ones.
+ */
+ virtual void ReloadAllValues() = 0;
+
+ /**
+ * Derived config classes must implement this so they can save all platform-specific
+ * and global values.
+ */
+ virtual void SaveAllValues() = 0;
+
+ void ReadValues();
+ void ReadPlayerValues(std::size_t player_index);
+
+ void ReadTouchscreenValues();
+ void ReadMotionTouchValues();
+
+ // Read functions bases off the respective config section names.
+ void ReadAudioValues();
+ void ReadControlValues();
+ void ReadCoreValues();
+ void ReadDataStorageValues();
+ void ReadDebuggingValues();
+ void ReadServiceValues();
+ void ReadDisabledAddOnValues();
+ void ReadMiscellaneousValues();
+ void ReadCpuValues();
+ void ReadRendererValues();
+ void ReadScreenshotValues();
+ void ReadSystemValues();
+ void ReadWebServiceValues();
+ void ReadNetworkValues();
+
+ // Read platform specific sections
+ virtual void ReadHidbusValues() = 0;
+ virtual void ReadDebugControlValues() = 0;
+ virtual void ReadPathValues() = 0;
+ virtual void ReadShortcutValues() = 0;
+ virtual void ReadUIValues() = 0;
+ virtual void ReadUIGamelistValues() = 0;
+ virtual void ReadUILayoutValues() = 0;
+ virtual void ReadMultiplayerValues() = 0;
+
+ void SaveValues();
+ void SavePlayerValues(std::size_t player_index);
+ void SaveTouchscreenValues();
+ void SaveMotionTouchValues();
+
+ // Save functions based off the respective config section names.
+ void SaveAudioValues();
+ void SaveControlValues();
+ void SaveCoreValues();
+ void SaveDataStorageValues();
+ void SaveDebuggingValues();
+ void SaveNetworkValues();
+ void SaveDisabledAddOnValues();
+ void SaveMiscellaneousValues();
+ void SaveCpuValues();
+ void SaveRendererValues();
+ void SaveScreenshotValues();
+ void SaveSystemValues();
+ void SaveWebServiceValues();
+
+ // Save platform specific sections
+ virtual void SaveHidbusValues() = 0;
+ virtual void SaveDebugControlValues() = 0;
+ virtual void SavePathValues() = 0;
+ virtual void SaveShortcutValues() = 0;
+ virtual void SaveUIValues() = 0;
+ virtual void SaveUIGamelistValues() = 0;
+ virtual void SaveUILayoutValues() = 0;
+ virtual void SaveMultiplayerValues() = 0;
+
+ virtual std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) = 0;
+
+ /**
+ * Reads a setting from the qt_config.
+ *
+ * @param key The setting's identifier
+ * @param default_value The value to use when the setting is not already present in the config
+ */
+ bool ReadBooleanSetting(const std::string& key,
+ std::optional<bool> default_value = std::nullopt);
+ s64 ReadIntegerSetting(const std::string& key, std::optional<s64> default_value = std::nullopt);
+ u64 ReadUnsignedIntegerSetting(const std::string& key,
+ std::optional<u64> default_value = std::nullopt);
+ double ReadDoubleSetting(const std::string& key,
+ std::optional<double> default_value = std::nullopt);
+ std::string ReadStringSetting(const std::string& key,
+ std::optional<std::string> default_value = std::nullopt);
+
+ /**
+ * Writes a setting to the qt_config.
+ *
+ * @param key The setting's idetentifier
+ * @param value Value of the setting
+ * @param default_value Default of the setting if not present in config
+ * @param use_global Specifies if the custom or global config should be in use, for custom
+ * configs
+ */
+ template <typename Type = int>
+ void WriteSetting(const std::string& key, const Type& value,
+ const std::optional<Type>& default_value = std::nullopt,
+ const std::optional<bool>& use_global = std::nullopt);
+ void WriteSettingInternal(const std::string& key, const std::string& value);
+
+ void ReadCategory(Settings::Category category);
+ void WriteCategory(Settings::Category category);
+ void ReadSettingGeneric(Settings::BasicSetting* setting);
+ void WriteSettingGeneric(const Settings::BasicSetting* setting);
+
+ template <typename T>
+ [[nodiscard]] std::string ToString(const T& value_) {
+ if constexpr (std::is_same_v<T, std::string>) {
+ return value_;
+ } else if constexpr (std::is_same_v<T, std::optional<u32>>) {
+ return value_.has_value() ? std::to_string(*value_) : "none";
+ } else if constexpr (std::is_same_v<T, bool>) {
+ return value_ ? "true" : "false";
+ } else if constexpr (std::is_same_v<T, u64>) {
+ return std::to_string(static_cast<u64>(value_));
+ } else {
+ return std::to_string(static_cast<s64>(value_));
+ }
+ }
+
+ void BeginGroup(const std::string& group);
+ void EndGroup();
+ std::string GetSection();
+ [[nodiscard]] std::string GetGroup() const;
+ static std::string AdjustKey(const std::string& key);
+ static std::string AdjustOutputString(const std::string& string);
+ std::string GetFullKey(const std::string& key, bool skipArrayIndex);
+ int BeginArray(const std::string& array);
+ void EndArray();
+ void SetArrayIndex(int index);
+
+ const ConfigType type;
+ std::unique_ptr<CSimpleIniA> config;
+ std::string config_loc;
+ const bool global;
+
+private:
+ inline static std::array<char, 19> special_characters = {'!', '#', '$', '%', '^', '&', '*',
+ '|', ';', '\'', '\"', ',', '<', '.',
+ '>', '?', '`', '~', '='};
+
+ struct ConfigArray {
+ std::string name;
+ int size;
+ int index;
+ };
+ std::vector<ConfigArray> array_stack;
+ std::vector<std::string> key_stack;
+};
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index 3ad34884d..1ff296af5 100644
--- a/src/input_common/drivers/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -415,7 +415,7 @@ ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& p
// This list is missing ZL/ZR since those are not considered buttons.
// We will add those afterwards
// This list also excludes any button that can't be really mapped
- static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
+ static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 14>
switch_to_gcadapter_button = {
std::pair{Settings::NativeButton::A, PadButton::ButtonA},
{Settings::NativeButton::B, PadButton::ButtonB},
@@ -426,8 +426,10 @@ ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& p
{Settings::NativeButton::DUp, PadButton::ButtonUp},
{Settings::NativeButton::DRight, PadButton::ButtonRight},
{Settings::NativeButton::DDown, PadButton::ButtonDown},
- {Settings::NativeButton::SL, PadButton::TriggerL},
- {Settings::NativeButton::SR, PadButton::TriggerR},
+ {Settings::NativeButton::SLLeft, PadButton::TriggerL},
+ {Settings::NativeButton::SRLeft, PadButton::TriggerR},
+ {Settings::NativeButton::SLRight, PadButton::TriggerL},
+ {Settings::NativeButton::SRRight, PadButton::TriggerR},
{Settings::NativeButton::R, PadButton::TriggerZ},
};
if (!params.Has("port")) {
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index 0aca5a3a3..72d2951f3 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -680,8 +680,8 @@ ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& par
Common::ParamPackage sr_button_params = button_params;
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL));
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR));
- mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
- mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
+ mapping.insert_or_assign(Settings::NativeButton::SLLeft, std::move(sl_button_params));
+ mapping.insert_or_assign(Settings::NativeButton::SRLeft, std::move(sr_button_params));
}
// Map SL and SR buttons for right joycons
@@ -693,8 +693,8 @@ ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& par
Common::ParamPackage sr_button_params = button_params;
sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL));
sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR));
- mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
- mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
+ mapping.insert_or_assign(Settings::NativeButton::SLRight, std::move(sl_button_params));
+ mapping.insert_or_assign(Settings::NativeButton::SRRight, std::move(sr_button_params));
}
return mapping;
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 66e3ae9af..78f458afe 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -828,16 +828,18 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
ButtonBindings SDLDriver::GetDefaultButtonBinding(
const std::shared_ptr<SDLJoystick>& joystick) const {
// Default SL/SR mapping for other controllers
- auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
- auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
+ auto sll_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
+ auto srl_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
+ auto slr_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
+ auto srr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
if (joystick->IsJoyconLeft()) {
- sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
- sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
+ sll_button = SDL_CONTROLLER_BUTTON_PADDLE2;
+ srl_button = SDL_CONTROLLER_BUTTON_PADDLE4;
}
if (joystick->IsJoyconRight()) {
- sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
- sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
+ slr_button = SDL_CONTROLLER_BUTTON_PADDLE3;
+ srr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
}
return {
@@ -855,8 +857,10 @@ ButtonBindings SDLDriver::GetDefaultButtonBinding(
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
- {Settings::NativeButton::SL, sl_button},
- {Settings::NativeButton::SR, sr_button},
+ {Settings::NativeButton::SLLeft, sll_button},
+ {Settings::NativeButton::SRLeft, srl_button},
+ {Settings::NativeButton::SLRight, slr_button},
+ {Settings::NativeButton::SRRight, srr_button},
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
{Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
};
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index fcba4e3c6..08e49a0da 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -24,7 +24,7 @@ namespace InputCommon {
class SDLJoystick;
using ButtonBindings =
- std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 18>;
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 20>;
using ZButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 77db60e92..60821b31a 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -396,7 +396,7 @@ std::vector<Common::ParamPackage> UDPClient::GetInputDevices() const {
ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) {
// This list excludes any button that can't be really mapped
- static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 20>
+ static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 22>
switch_to_dsu_button = {
std::pair{Settings::NativeButton::A, PadButton::Circle},
{Settings::NativeButton::B, PadButton::Cross},
@@ -412,8 +412,10 @@ ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& p
{Settings::NativeButton::R, PadButton::R1},
{Settings::NativeButton::ZL, PadButton::L2},
{Settings::NativeButton::ZR, PadButton::R2},
- {Settings::NativeButton::SL, PadButton::L2},
- {Settings::NativeButton::SR, PadButton::R2},
+ {Settings::NativeButton::SLLeft, PadButton::L2},
+ {Settings::NativeButton::SRLeft, PadButton::R2},
+ {Settings::NativeButton::SLRight, PadButton::L2},
+ {Settings::NativeButton::SRRight, PadButton::R2},
{Settings::NativeButton::LStick, PadButton::L3},
{Settings::NativeButton::RStick, PadButton::R3},
{Settings::NativeButton::Home, PadButton::Home},
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index cf51f3481..c9f903213 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -139,7 +139,7 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
input_thread_running = true;
// Max update rate is 5ms, ensure we are always able to read a bit faster
- constexpr int ThreadDelay = 2;
+ constexpr int ThreadDelay = 3;
std::vector<u8> buffer(MaxBufferSize);
while (!stop_token.stop_requested()) {
@@ -163,6 +163,17 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
OnNewData(buffer);
}
+ if (!vibration_queue.Empty()) {
+ VibrationValue vibration_value;
+ vibration_queue.Pop(vibration_value);
+ last_vibration_result = rumble_protocol->SendVibration(vibration_value);
+ }
+
+ // We can't keep up with vibrations. Start skipping.
+ while (vibration_queue.Size() > 6) {
+ vibration_queue.Pop();
+ }
+
std::this_thread::yield();
}
@@ -402,7 +413,8 @@ Common::Input::DriverResult JoyconDriver::SetVibration(const VibrationValue& vib
if (disable_input_thread) {
return Common::Input::DriverResult::HandleInUse;
}
- return rumble_protocol->SendVibration(vibration);
+ vibration_queue.Push(vibration);
+ return last_vibration_result;
}
Common::Input::DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 335e12cc3..5355780fb 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -9,6 +9,7 @@
#include <span>
#include <thread>
+#include "common/threadsafe_queue.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace Common::Input {
@@ -152,6 +153,10 @@ private:
SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
SupportedFeatures supported_features{};
+ /// Queue of vibration request to controllers
+ Common::Input::DriverResult last_vibration_result{Common::Input::DriverResult::Success};
+ Common::SPSCQueue<VibrationValue> vibration_queue;
+
// Thread related
mutable std::mutex mutex;
std::jthread input_thread;
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt
index 83b763447..19db17c6d 100644
--- a/src/shader_recompiler/CMakeLists.txt
+++ b/src/shader_recompiler/CMakeLists.txt
@@ -231,6 +231,7 @@ add_library(shader_recompiler STATIC
ir_opt/rescaling_pass.cpp
ir_opt/ssa_rewrite_pass.cpp
ir_opt/texture_pass.cpp
+ ir_opt/vendor_workaround_pass.cpp
ir_opt/verification_pass.cpp
object_pool.h
precompiled_headers.h
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
index d0e308124..64e7bad75 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_image.cpp
@@ -559,12 +559,12 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
const IR::Value& offset, const IR::Value& lod_clamp) {
const auto info{inst.Flags<IR::TextureInstInfo>()};
ScopedRegister dpdx, dpdy, coords;
- const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp};
+ const bool multi_component{info.num_derivatives > 1 || info.has_lod_clamp};
if (multi_component) {
// Allocate this early to avoid aliasing other registers
dpdx = ScopedRegister{ctx.reg_alloc};
dpdy = ScopedRegister{ctx.reg_alloc};
- if (info.num_derivates >= 3) {
+ if (info.num_derivatives >= 3) {
coords = ScopedRegister{ctx.reg_alloc};
}
}
@@ -584,7 +584,7 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
dpdx.reg, derivatives_vec, dpdx.reg, derivatives_vec, dpdy.reg, derivatives_vec,
dpdy.reg, derivatives_vec);
Register final_coord;
- if (info.num_derivates >= 3) {
+ if (info.num_derivatives >= 3) {
ctx.Add("MOV.F {}.z,{}.x;"
"MOV.F {}.z,{}.y;",
dpdx.reg, coord_vec, dpdy.reg, coord_vec);
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
index 2705ab140..9319ea007 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
@@ -5,6 +5,7 @@
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/frontend/ir/value.h"
+#include "shader_recompiler/profile.h"
#include "shader_recompiler/runtime_info.h"
namespace Shader::Backend::GLASM {
@@ -35,7 +36,9 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
continue;
}
const auto& ssbo{ctx.info.storage_buffers_descriptors[index]};
- ctx.Add("LDC.U64 DC.x,c{}[{}];" // ssbo_addr
+ const u64 ssbo_align_mask{~(ctx.profile.min_ssbo_alignment - 1U)};
+ ctx.Add("LDC.U64 DC.x,c{}[{}];" // unaligned_ssbo_addr
+ "AND.U64 DC.x,DC.x,{};" // ssbo_addr = unaligned_ssbo_addr & ssbo_align_mask
"LDC.U32 RC.x,c{}[{}];" // ssbo_size_u32
"CVT.U64.U32 DC.y,RC.x;" // ssbo_size = ssbo_size_u32
"ADD.U64 DC.y,DC.y,DC.x;" // ssbo_end = ssbo_addr + ssbo_size
@@ -44,8 +47,8 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
"AND.U.CC RC.x,RC.x,RC.y;" // cond = a && b
"IF NE.x;" // if cond
"SUB.U64 DC.x,{}.x,DC.x;", // offset = input_addr - ssbo_addr
- ssbo.cbuf_index, ssbo.cbuf_offset, ssbo.cbuf_index, ssbo.cbuf_offset + 8, address,
- address, address);
+ ssbo.cbuf_index, ssbo.cbuf_offset, ssbo_align_mask, ssbo.cbuf_index,
+ ssbo.cbuf_offset + 8, address, address, address);
if (pointer_based) {
ctx.Add("PK64.U DC.y,c[{}];" // host_ssbo = cbuf
"ADD.U64 DC.x,DC.x,DC.y;" // host_addr = host_ssbo + offset
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
index d9872ecc2..6e940bd5a 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_image.cpp
@@ -548,15 +548,15 @@ void EmitImageGradient(EmitContext& ctx, IR::Inst& inst, const IR::Value& index,
if (sparse_inst) {
throw NotImplementedException("EmitImageGradient Sparse");
}
- if (!offset.IsEmpty() && info.num_derivates <= 2) {
+ if (!offset.IsEmpty() && info.num_derivatives <= 2) {
throw NotImplementedException("EmitImageGradient offset");
}
const auto texture{Texture(ctx, info, index)};
const auto texel{ctx.var_alloc.Define(inst, GlslVarType::F32x4)};
- const bool multi_component{info.num_derivates > 1 || info.has_lod_clamp};
+ const bool multi_component{info.num_derivatives > 1 || info.has_lod_clamp};
const auto derivatives_vec{ctx.var_alloc.Consume(derivatives)};
if (multi_component) {
- if (info.num_derivates >= 3) {
+ if (info.num_derivatives >= 3) {
const auto offset_vec{ctx.var_alloc.Consume(offset)};
ctx.Add("{}=textureGrad({},{},vec3({}.xz, {}.x),vec3({}.yw, {}.y));", texel, texture,
coords, derivatives_vec, offset_vec, derivatives_vec, offset_vec);
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index 9ff4028c2..fd9a99449 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -601,7 +601,10 @@ std::string EmitContext::DefineGlobalMemoryFunctions() {
addr_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, addr_loc / 16, Swizzle(addr_loc));
size_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, size_loc / 16, Swizzle(size_loc));
}
- const auto addr_pack{fmt::format("packUint2x32(uvec2({},{}))", addr_xy[0], addr_xy[1])};
+ const u32 ssbo_align_mask{~(static_cast<u32>(profile.min_ssbo_alignment) - 1U)};
+ const auto aligned_low_addr{fmt::format("{}&{}", addr_xy[0], ssbo_align_mask)};
+ const auto aligned_addr{fmt::format("uvec2({},{})", aligned_low_addr, addr_xy[1])};
+ const auto addr_pack{fmt::format("packUint2x32({})", aligned_addr)};
const auto addr_statment{fmt::format("uint64_t {}={};", ssbo_addr, addr_pack)};
func += addr_statment;
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
index 34592a01f..0031fa5fb 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
@@ -407,7 +407,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
}
ctx.AddCapability(spv::Capability::DemoteToHelperInvocation);
}
- if (info.stores[IR::Attribute::ViewportIndex]) {
+ if (info.stores[IR::Attribute::ViewportIndex] && profile.support_multi_viewport) {
ctx.AddCapability(spv::Capability::MultiViewport);
}
if (info.stores[IR::Attribute::ViewportMask] && profile.support_viewport_mask) {
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 1d77426e0..e5a78a914 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -84,6 +84,10 @@ std::optional<OutAttr> OutputAttrPointer(EmitContext& ctx, IR::Attribute attr) {
}
return std::nullopt;
case IR::Attribute::ViewportIndex:
+ if (!ctx.profile.support_multi_viewport) {
+ LOG_WARNING(Shader, "Ignoring viewport index store on non-supporting driver");
+ return std::nullopt;
+ }
if (ctx.profile.support_viewport_index_layer_non_geometry ||
ctx.stage == Shader::Stage::Geometry) {
return OutAttr{ctx.viewport_index, ctx.U32[1]};
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index 8decdf399..22ceca19c 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -67,22 +67,22 @@ public:
}
}
- explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivates, u32 num_derivates,
- Id offset, Id lod_clamp) {
- if (!Sirit::ValidId(derivates)) {
- throw LogicError("Derivates must be present");
+ explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivatives,
+ u32 num_derivatives, Id offset, Id lod_clamp) {
+ if (!Sirit::ValidId(derivatives)) {
+ throw LogicError("Derivatives must be present");
}
boost::container::static_vector<Id, 3> deriv_x_accum;
boost::container::static_vector<Id, 3> deriv_y_accum;
- for (u32 i = 0; i < num_derivates; ++i) {
- deriv_x_accum.push_back(ctx.OpCompositeExtract(ctx.F32[1], derivates, i * 2));
- deriv_y_accum.push_back(ctx.OpCompositeExtract(ctx.F32[1], derivates, i * 2 + 1));
+ for (u32 i = 0; i < num_derivatives; ++i) {
+ deriv_x_accum.push_back(ctx.OpCompositeExtract(ctx.F32[1], derivatives, i * 2));
+ deriv_y_accum.push_back(ctx.OpCompositeExtract(ctx.F32[1], derivatives, i * 2 + 1));
}
- const Id derivates_X{ctx.OpCompositeConstruct(
- ctx.F32[num_derivates], std::span{deriv_x_accum.data(), deriv_x_accum.size()})};
- const Id derivates_Y{ctx.OpCompositeConstruct(
- ctx.F32[num_derivates], std::span{deriv_y_accum.data(), deriv_y_accum.size()})};
- Add(spv::ImageOperandsMask::Grad, derivates_X, derivates_Y);
+ const Id derivatives_X{ctx.OpCompositeConstruct(
+ ctx.F32[num_derivatives], std::span{deriv_x_accum.data(), deriv_x_accum.size()})};
+ const Id derivatives_Y{ctx.OpCompositeConstruct(
+ ctx.F32[num_derivatives], std::span{deriv_y_accum.data(), deriv_y_accum.size()})};
+ Add(spv::ImageOperandsMask::Grad, derivatives_X, derivatives_Y);
if (Sirit::ValidId(offset)) {
Add(spv::ImageOperandsMask::Offset, offset);
}
@@ -91,26 +91,26 @@ public:
}
}
- explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivates_1, Id derivates_2,
+ explicit ImageOperands(EmitContext& ctx, bool has_lod_clamp, Id derivatives_1, Id derivatives_2,
Id offset, Id lod_clamp) {
- if (!Sirit::ValidId(derivates_1) || !Sirit::ValidId(derivates_2)) {
- throw LogicError("Derivates must be present");
+ if (!Sirit::ValidId(derivatives_1) || !Sirit::ValidId(derivatives_2)) {
+ throw LogicError("Derivatives 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),
+ ctx.OpCompositeExtract(ctx.F32[1], derivatives_1, 0),
+ ctx.OpCompositeExtract(ctx.F32[1], derivatives_1, 2),
+ ctx.OpCompositeExtract(ctx.F32[1], derivatives_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),
+ ctx.OpCompositeExtract(ctx.F32[1], derivatives_1, 1),
+ ctx.OpCompositeExtract(ctx.F32[1], derivatives_1, 3),
+ ctx.OpCompositeExtract(ctx.F32[1], derivatives_2, 1),
};
- const Id derivates_id1{ctx.OpCompositeConstruct(
+ const Id derivatives_id1{ctx.OpCompositeConstruct(
ctx.F32[3], std::span{deriv_1_accum.data(), deriv_1_accum.size()})};
- const Id derivates_id2{ctx.OpCompositeConstruct(
+ const Id derivatives_id2{ctx.OpCompositeConstruct(
ctx.F32[3], std::span{deriv_2_accum.data(), deriv_2_accum.size()})};
- Add(spv::ImageOperandsMask::Grad, derivates_id1, derivates_id2);
+ Add(spv::ImageOperandsMask::Grad, derivatives_id1, derivatives_id2);
if (Sirit::ValidId(offset)) {
Add(spv::ImageOperandsMask::Offset, offset);
}
@@ -548,12 +548,12 @@ 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) {
+ Id derivatives, Id offset, Id lod_clamp) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
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,
+ info.num_derivatives == 3
+ ? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, offset, {}, lod_clamp)
+ : ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, info.num_derivatives, offset,
lod_clamp);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index a440b557d..7d34575c8 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -543,7 +543,7 @@ Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& i
const IR::Value& skip_mips);
Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords);
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
- Id derivates, Id offset, Id lod_clamp);
+ Id derivatives, Id offset, Id lod_clamp);
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords);
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id color);
Id EmitIsTextureScaled(EmitContext& ctx, const IR::Value& index);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 57df6fc34..3350f1f85 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -891,7 +891,9 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32,
zero, ssbo_size_cbuf_offset)};
- const Id ssbo_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
+ const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
+ const Id unaligned_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
+ const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))};
const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)};
const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr),
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
index b7caa4246..49171c470 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
@@ -1864,11 +1864,11 @@ Value IREmitter::ImageQueryLod(const Value& handle, const Value& coords, Texture
return Inst(op, Flags{info}, handle, coords);
}
-Value IREmitter::ImageGradient(const Value& handle, const Value& coords, const Value& derivates,
+Value IREmitter::ImageGradient(const Value& handle, const Value& coords, const Value& derivatives,
const Value& offset, const F32& lod_clamp, TextureInstInfo info) {
const Opcode op{handle.IsImmediate() ? Opcode::BoundImageGradient
: Opcode::BindlessImageGradient};
- return Inst(op, Flags{info}, handle, coords, derivates, offset, lod_clamp);
+ return Inst(op, Flags{info}, handle, coords, derivatives, offset, lod_clamp);
}
Value IREmitter::ImageRead(const Value& handle, const Value& coords, TextureInstInfo info) {
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h
index f3c81dbe1..6c30897f4 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.h
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.h
@@ -335,7 +335,7 @@ public:
[[nodiscard]] Value ImageFetch(const Value& handle, const Value& coords, const Value& offset,
const U32& lod, const U32& multisampling, TextureInstInfo info);
[[nodiscard]] Value ImageGradient(const Value& handle, const Value& coords,
- const Value& derivates, const Value& offset,
+ const Value& derivatives, const Value& offset,
const F32& lod_clamp, TextureInstInfo info);
[[nodiscard]] Value ImageRead(const Value& handle, const Value& coords, TextureInstInfo info);
void ImageWrite(const Value& handle, const Value& coords, const Value& color,
diff --git a/src/shader_recompiler/frontend/ir/modifiers.h b/src/shader_recompiler/frontend/ir/modifiers.h
index 1e9e8c8f5..c20c2401f 100644
--- a/src/shader_recompiler/frontend/ir/modifiers.h
+++ b/src/shader_recompiler/frontend/ir/modifiers.h
@@ -40,7 +40,7 @@ union TextureInstInfo {
BitField<21, 1, u32> has_lod_clamp;
BitField<22, 1, u32> relaxed_precision;
BitField<23, 2, u32> gather_component;
- BitField<25, 2, u32> num_derivates;
+ BitField<25, 2, u32> num_derivatives;
BitField<27, 3, ImageFormat> image_format;
BitField<30, 1, u32> ndv_is_active;
};
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp
index dd34507bc..4ce3dd0cd 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_gradient.cpp
@@ -59,7 +59,7 @@ void Impl(TranslatorVisitor& v, u64 insn, bool is_bindless) {
BitField<51, 3, IR::Pred> sparse_pred;
BitField<0, 8, IR::Reg> dest_reg;
BitField<8, 8, IR::Reg> coord_reg;
- BitField<20, 8, IR::Reg> derivate_reg;
+ BitField<20, 8, IR::Reg> derivative_reg;
BitField<28, 3, TextureType> type;
BitField<31, 4, u64> mask;
BitField<36, 13, u64> cbuf_offset;
@@ -71,7 +71,7 @@ void Impl(TranslatorVisitor& v, u64 insn, bool is_bindless) {
}
IR::Value coords;
- u32 num_derivates{};
+ u32 num_derivatives{};
IR::Reg base_reg{txd.coord_reg};
IR::Reg last_reg;
IR::Value handle;
@@ -90,42 +90,42 @@ void Impl(TranslatorVisitor& v, u64 insn, bool is_bindless) {
switch (txd.type) {
case TextureType::_1D: {
coords = v.F(base_reg);
- num_derivates = 1;
+ num_derivatives = 1;
last_reg = base_reg + 1;
break;
}
case TextureType::ARRAY_1D: {
last_reg = base_reg + 1;
coords = v.ir.CompositeConstruct(v.F(base_reg), read_array());
- num_derivates = 1;
+ num_derivatives = 1;
break;
}
case TextureType::_2D: {
last_reg = base_reg + 2;
coords = v.ir.CompositeConstruct(v.F(base_reg), v.F(base_reg + 1));
- num_derivates = 2;
+ num_derivatives = 2;
break;
}
case TextureType::ARRAY_2D: {
last_reg = base_reg + 2;
coords = v.ir.CompositeConstruct(v.F(base_reg), v.F(base_reg + 1), read_array());
- num_derivates = 2;
+ num_derivatives = 2;
break;
}
default:
throw NotImplementedException("Invalid texture type");
}
- const IR::Reg derivate_reg{txd.derivate_reg};
- IR::Value derivates;
- switch (num_derivates) {
+ const IR::Reg derivative_reg{txd.derivative_reg};
+ IR::Value derivatives;
+ switch (num_derivatives) {
case 1: {
- derivates = v.ir.CompositeConstruct(v.F(derivate_reg), v.F(derivate_reg + 1));
+ derivatives = v.ir.CompositeConstruct(v.F(derivative_reg), v.F(derivative_reg + 1));
break;
}
case 2: {
- derivates = v.ir.CompositeConstruct(v.F(derivate_reg), v.F(derivate_reg + 1),
- v.F(derivate_reg + 2), v.F(derivate_reg + 3));
+ derivatives = v.ir.CompositeConstruct(v.F(derivative_reg), v.F(derivative_reg + 1),
+ v.F(derivative_reg + 2), v.F(derivative_reg + 3));
break;
}
default:
@@ -150,9 +150,10 @@ void Impl(TranslatorVisitor& v, u64 insn, bool is_bindless) {
IR::TextureInstInfo info{};
info.type.Assign(GetType(txd.type));
- info.num_derivates.Assign(num_derivates);
+ info.num_derivatives.Assign(num_derivatives);
info.has_lod_clamp.Assign(has_lod_clamp ? 1 : 0);
- const IR::Value sample{v.ir.ImageGradient(handle, coords, derivates, offset, lod_clamp, info)};
+ const IR::Value sample{
+ v.ir.ImageGradient(handle, coords, derivatives, offset, lod_clamp, info)};
IR::Reg dest_reg{txd.dest_reg};
for (size_t element = 0; element < 4; ++element) {
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
index 928b35561..321ea625b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
@@ -298,7 +298,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::PositionPass(env, program);
- Optimization::GlobalMemoryToStorageBufferPass(program);
+ Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
Optimization::TexturePass(env, program, host_info);
if (Settings::values.resolution_info.active) {
@@ -310,6 +310,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
}
Optimization::CollectShaderInfoPass(env, program);
Optimization::LayerPass(program, host_info);
+ Optimization::VendorWorkaroundPass(program);
CollectInterpolationInfo(env, program);
AddNVNStorageBuffers(program);
diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h
index 7d2ded907..1b53404fc 100644
--- a/src/shader_recompiler/host_translate_info.h
+++ b/src/shader_recompiler/host_translate_info.h
@@ -16,6 +16,7 @@ struct HostTranslateInfo {
bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered
bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers
bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS
+ u32 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs
bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry
///< passthrough shaders
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
index f46e55122..ec12c843a 100644
--- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
+++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp
@@ -428,7 +428,7 @@ void FoldFPAdd32(IR::Inst& inst) {
}
}
-bool FoldDerivateYFromCorrection(IR::Inst& inst) {
+bool FoldDerivativeYFromCorrection(IR::Inst& inst) {
const IR::Value lhs_value{inst.Arg(0)};
const IR::Value rhs_value{inst.Arg(1)};
IR::Inst* const lhs_op{lhs_value.InstRecursive()};
@@ -464,7 +464,7 @@ void FoldFPMul32(IR::Inst& inst) {
if (lhs_value.IsImmediate() || rhs_value.IsImmediate()) {
return;
}
- if (FoldDerivateYFromCorrection(inst)) {
+ if (FoldDerivativeYFromCorrection(inst)) {
return;
}
IR::Inst* const lhs_op{lhs_value.InstRecursive()};
@@ -699,7 +699,7 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) {
}
}
-bool FindGradient3DDerivates(std::array<IR::Value, 3>& results, IR::Value coord) {
+bool FindGradient3DDerivatives(std::array<IR::Value, 3>& results, IR::Value coord) {
if (coord.IsImmediate()) {
return false;
}
@@ -834,7 +834,7 @@ void FoldImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
IR::Inst* const inst2 = coords.InstRecursive();
std::array<std::array<IR::Value, 3>, 3> results_matrix;
for (size_t i = 0; i < 3; i++) {
- if (!FindGradient3DDerivates(results_matrix[i], inst2->Arg(i).Resolve())) {
+ if (!FindGradient3DDerivatives(results_matrix[i], inst2->Arg(i).Resolve())) {
return;
}
}
@@ -852,7 +852,7 @@ void FoldImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
IR::Value derivatives_1 = ir.CompositeConstruct(results_matrix[0][1], results_matrix[0][2],
results_matrix[1][1], results_matrix[1][2]);
IR::Value derivatives_2 = ir.CompositeConstruct(results_matrix[2][1], results_matrix[2][2]);
- info.num_derivates.Assign(3);
+ info.num_derivatives.Assign(3);
IR::Value new_gradient_instruction =
ir.ImageGradient(handle, new_coords, derivatives_1, derivatives_2, lod_clamp, info);
IR::Inst* const new_inst = new_gradient_instruction.InstRecursive();
diff --git a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
index d1e59f22e..0cea79945 100644
--- a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
+++ b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
@@ -11,6 +11,7 @@
#include "shader_recompiler/frontend/ir/breadth_first_search.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/frontend/ir/value.h"
+#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/ir_opt/passes.h"
namespace Shader::Optimization {
@@ -408,7 +409,7 @@ void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info)
}
/// Returns the offset in indices (not bytes) for an equivalent storage instruction
-IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer) {
+IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer, u32 alignment) {
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
IR::U32 offset;
if (const std::optional<LowAddrInfo> low_addr{TrackLowAddress(&inst)}) {
@@ -421,7 +422,10 @@ IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer
}
// Subtract the least significant 32 bits from the guest offset. The result is the storage
// buffer offset in bytes.
- const IR::U32 low_cbuf{ir.GetCbuf(ir.Imm32(buffer.index), ir.Imm32(buffer.offset))};
+ IR::U32 low_cbuf{ir.GetCbuf(ir.Imm32(buffer.index), ir.Imm32(buffer.offset))};
+
+ // Align the offset base to match the host alignment requirements
+ low_cbuf = ir.BitwiseAnd(low_cbuf, ir.Imm32(~(alignment - 1U)));
return ir.ISub(offset, low_cbuf);
}
@@ -516,7 +520,7 @@ void Replace(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index,
}
} // Anonymous namespace
-void GlobalMemoryToStorageBufferPass(IR::Program& program) {
+void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info) {
StorageInfo info;
for (IR::Block* const block : program.post_order_blocks) {
for (IR::Inst& inst : block->Instructions()) {
@@ -540,7 +544,8 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program) {
const IR::U32 index{IR::Value{static_cast<u32>(info.set.index_of(it))}};
IR::Block* const block{storage_inst.block};
IR::Inst* const inst{storage_inst.inst};
- const IR::U32 offset{StorageOffset(*block, *inst, storage_buffer)};
+ const IR::U32 offset{
+ StorageOffset(*block, *inst, storage_buffer, host_info.min_ssbo_alignment)};
Replace(*block, *inst, index, offset);
}
}
diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h
index 629d18fa1..1e637cb23 100644
--- a/src/shader_recompiler/ir_opt/passes.h
+++ b/src/shader_recompiler/ir_opt/passes.h
@@ -16,7 +16,7 @@ void CollectShaderInfoPass(Environment& env, IR::Program& program);
void ConditionalBarrierPass(IR::Program& program);
void ConstantPropagationPass(Environment& env, IR::Program& program);
void DeadCodeEliminationPass(IR::Program& program);
-void GlobalMemoryToStorageBufferPass(IR::Program& program);
+void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info);
void IdentityRemovalPass(IR::Program& program);
void LowerFp64ToFp32(IR::Program& program);
void LowerFp16ToFp32(IR::Program& program);
@@ -26,6 +26,7 @@ void SsaRewritePass(IR::Program& program);
void PositionPass(Environment& env, IR::Program& program);
void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info);
void LayerPass(IR::Program& program, const HostTranslateInfo& host_info);
+void VendorWorkaroundPass(IR::Program& program);
void VerificationPass(const IR::Program& program);
// Dual Vertex
diff --git a/src/shader_recompiler/ir_opt/vendor_workaround_pass.cpp b/src/shader_recompiler/ir_opt/vendor_workaround_pass.cpp
new file mode 100644
index 000000000..08c658cb8
--- /dev/null
+++ b/src/shader_recompiler/ir_opt/vendor_workaround_pass.cpp
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/frontend/ir/value.h"
+#include "shader_recompiler/ir_opt/passes.h"
+
+namespace Shader::Optimization {
+
+namespace {
+void AddingByteSwapsWorkaround(IR::Block& block, IR::Inst& inst) {
+ /*
+ * Workaround for an NVIDIA bug seen in Super Mario RPG
+ *
+ * We are looking for this pattern:
+ * %lhs_bfe = BitFieldUExtract %factor_a, #0, #16
+ * %lhs_mul = IMul32 %lhs_bfe, %factor_b // potentially optional?
+ * %lhs_shl = ShiftLeftLogical32 %lhs_mul, #16
+ * %rhs_bfe = BitFieldUExtract %factor_a, #16, #16
+ * %result = IAdd32 %lhs_shl, %rhs_bfe
+ *
+ * And replacing the IAdd32 with a BitwiseOr32
+ * %result = BitwiseOr32 %lhs_shl, %rhs_bfe
+ *
+ */
+ IR::Inst* const lhs_shl{inst.Arg(0).TryInstRecursive()};
+ IR::Inst* const rhs_bfe{inst.Arg(1).TryInstRecursive()};
+ if (!lhs_shl || !rhs_bfe) {
+ return;
+ }
+ if (lhs_shl->GetOpcode() != IR::Opcode::ShiftLeftLogical32 ||
+ lhs_shl->Arg(1) != IR::Value{16U}) {
+ return;
+ }
+ if (rhs_bfe->GetOpcode() != IR::Opcode::BitFieldUExtract || rhs_bfe->Arg(1) != IR::Value{16U} ||
+ rhs_bfe->Arg(2) != IR::Value{16U}) {
+ return;
+ }
+ IR::Inst* const lhs_mul{lhs_shl->Arg(0).TryInstRecursive()};
+ if (!lhs_mul) {
+ return;
+ }
+ const bool lhs_mul_optional{lhs_mul->GetOpcode() == IR::Opcode::BitFieldUExtract};
+ if (lhs_mul->GetOpcode() != IR::Opcode::IMul32 &&
+ lhs_mul->GetOpcode() != IR::Opcode::BitFieldUExtract) {
+ return;
+ }
+ IR::Inst* const lhs_bfe{lhs_mul_optional ? lhs_mul : lhs_mul->Arg(0).TryInstRecursive()};
+ if (!lhs_bfe) {
+ return;
+ }
+ if (lhs_bfe->GetOpcode() != IR::Opcode::BitFieldUExtract) {
+ return;
+ }
+ if (lhs_bfe->Arg(1) != IR::Value{0U} || lhs_bfe->Arg(2) != IR::Value{16U}) {
+ return;
+ }
+ IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
+ inst.ReplaceUsesWith(ir.BitwiseOr(IR::U32{inst.Arg(0)}, IR::U32{inst.Arg(1)}));
+}
+
+} // Anonymous namespace
+
+void VendorWorkaroundPass(IR::Program& program) {
+ for (IR::Block* const block : program.post_order_blocks) {
+ for (IR::Inst& inst : block->Instructions()) {
+ switch (inst.GetOpcode()) {
+ case IR::Opcode::IAdd32:
+ AddingByteSwapsWorkaround(*block, inst);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+} // namespace Shader::Optimization
diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h
index 38d820db2..66901a965 100644
--- a/src/shader_recompiler/profile.h
+++ b/src/shader_recompiler/profile.h
@@ -43,6 +43,7 @@ struct Profile {
bool support_gl_sparse_textures{};
bool support_gl_derivative_control{};
bool support_scaled_attributes{};
+ bool support_multi_viewport{};
bool warp_size_potentially_larger_than_guest{};
@@ -84,6 +85,8 @@ struct Profile {
/// Maxwell and earlier nVidia architectures have broken robust support
bool has_broken_robust{};
+
+ u64 min_ssbo_alignment{};
};
} // namespace Shader
diff --git a/src/tests/common/host_memory.cpp b/src/tests/common/host_memory.cpp
index 1b014b632..1a28e862b 100644
--- a/src/tests/common/host_memory.cpp
+++ b/src/tests/common/host_memory.cpp
@@ -11,6 +11,7 @@ using namespace Common::Literals;
static constexpr size_t VIRTUAL_SIZE = 1ULL << 39;
static constexpr size_t BACKING_SIZE = 4_GiB;
+static constexpr auto PERMS = Common::MemoryPermission::ReadWrite;
TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
@@ -19,7 +20,7 @@ TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
TEST_CASE("HostMemory: Simple map", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x5000, 0x8000, 0x1000);
+ mem.Map(0x5000, 0x8000, 0x1000, PERMS);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[0] = 50;
@@ -28,8 +29,8 @@ TEST_CASE("HostMemory: Simple map", "[common]") {
TEST_CASE("HostMemory: Simple mirror map", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x5000, 0x3000, 0x2000);
- mem.Map(0x8000, 0x4000, 0x1000);
+ mem.Map(0x5000, 0x3000, 0x2000, PERMS);
+ mem.Map(0x8000, 0x4000, 0x1000, PERMS);
volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000;
volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000;
@@ -39,7 +40,7 @@ TEST_CASE("HostMemory: Simple mirror map", "[common]") {
TEST_CASE("HostMemory: Simple unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x5000, 0x3000, 0x2000);
+ mem.Map(0x5000, 0x3000, 0x2000, PERMS);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[75] = 50;
@@ -50,7 +51,7 @@ TEST_CASE("HostMemory: Simple unmap", "[common]") {
TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x5000, 0x3000, 0x2000);
+ mem.Map(0x5000, 0x3000, 0x2000, PERMS);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[0] = 50;
@@ -58,79 +59,79 @@ TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
mem.Unmap(0x5000, 0x2000);
- mem.Map(0x5000, 0x3000, 0x2000);
+ mem.Map(0x5000, 0x3000, 0x2000, PERMS);
REQUIRE(data[0] == 50);
- mem.Map(0x7000, 0x2000, 0x5000);
+ mem.Map(0x7000, 0x2000, 0x5000, PERMS);
REQUIRE(data[0x3000] == 50);
}
TEST_CASE("HostMemory: Nieche allocation", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x0000, 0, 0x20000);
+ mem.Map(0x0000, 0, 0x20000, PERMS);
mem.Unmap(0x0000, 0x4000);
- mem.Map(0x1000, 0, 0x2000);
- mem.Map(0x3000, 0, 0x1000);
- mem.Map(0, 0, 0x1000);
+ mem.Map(0x1000, 0, 0x2000, PERMS);
+ mem.Map(0x3000, 0, 0x1000, PERMS);
+ mem.Map(0, 0, 0x1000, PERMS);
}
TEST_CASE("HostMemory: Full unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
mem.Unmap(0x8000, 0x4000);
- mem.Map(0x6000, 0, 0x16000);
+ mem.Map(0x6000, 0, 0x16000, PERMS);
}
TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x0000, 0, 0x4000);
+ mem.Map(0x0000, 0, 0x4000, PERMS);
mem.Unmap(0x2000, 0x4000);
- mem.Map(0x2000, 0x80000, 0x4000);
+ mem.Map(0x2000, 0x80000, 0x4000, PERMS);
}
TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
mem.Unmap(0x6000, 0x4000);
- mem.Map(0x8000, 0, 0x2000);
+ mem.Map(0x8000, 0, 0x2000, PERMS);
}
TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x0000, 0, 0x4000);
- mem.Map(0x4000, 0, 0x1b000);
+ mem.Map(0x0000, 0, 0x4000, PERMS);
+ mem.Map(0x4000, 0, 0x1b000, PERMS);
mem.Unmap(0x3000, 0x1c000);
- mem.Map(0x3000, 0, 0x20000);
+ mem.Map(0x3000, 0, 0x20000, PERMS);
}
TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x0000, 0, 0x4000);
- mem.Map(0x4000, 0, 0x4000);
+ mem.Map(0x0000, 0, 0x4000, PERMS);
+ mem.Map(0x4000, 0, 0x4000, PERMS);
mem.Unmap(0x2000, 0x4000);
- mem.Map(0x2000, 0, 0x4000);
+ mem.Map(0x2000, 0, 0x4000, PERMS);
}
TEST_CASE("HostMemory: Unmap to origin", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0, 0x4000);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x4000, 0, 0x4000, PERMS);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
mem.Unmap(0x4000, 0x4000);
- mem.Map(0, 0, 0x4000);
- mem.Map(0x4000, 0, 0x4000);
+ mem.Map(0, 0, 0x4000, PERMS);
+ mem.Map(0x4000, 0, 0x4000, PERMS);
}
TEST_CASE("HostMemory: Unmap to right", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0, 0x4000);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x4000, 0, 0x4000, PERMS);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
mem.Unmap(0x8000, 0x4000);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
}
TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0x10000, 0x4000);
+ mem.Map(0x4000, 0x10000, 0x4000, PERMS);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x1000] = 17;
@@ -142,7 +143,7 @@ TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0x10000, 0x4000);
+ mem.Map(0x4000, 0x10000, 0x4000, PERMS);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x3000] = 19;
@@ -156,7 +157,7 @@ TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0x10000, 0x4000);
+ mem.Map(0x4000, 0x10000, 0x4000, PERMS);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x0000] = 19;
@@ -170,8 +171,8 @@ TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0x10000, 0x2000);
- mem.Map(0x6000, 0x20000, 0x2000);
+ mem.Map(0x4000, 0x10000, 0x2000, PERMS);
+ mem.Map(0x6000, 0x20000, 0x2000, PERMS);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x0000] = 19;
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index cf9266d54..c22c7631c 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -4,7 +4,7 @@
add_subdirectory(host_shaders)
if(LIBVA_FOUND)
- set_source_files_properties(host1x/codecs/codec.cpp
+ set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
endif()
@@ -15,6 +15,7 @@ add_library(video_core STATIC
buffer_cache/buffer_cache.cpp
buffer_cache/buffer_cache.h
buffer_cache/memory_tracker_base.h
+ buffer_cache/usage_tracker.h
buffer_cache/word_manager.h
cache_types.h
cdma_pusher.cpp
@@ -66,6 +67,8 @@ add_library(video_core STATIC
host1x/codecs/vp9.cpp
host1x/codecs/vp9.h
host1x/codecs/vp9_types.h
+ host1x/ffmpeg/ffmpeg.cpp
+ host1x/ffmpeg/ffmpeg.h
host1x/control.cpp
host1x/control.h
host1x/host1x.cpp
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 9b2698fad..6d1fc3887 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -67,6 +67,7 @@ void BufferCache<P>::TickFrame() {
if (!channel_state) {
return;
}
+ runtime.TickFrame(slot_buffers);
// Calculate hits and shots and move hit bits to the right
const u32 hits = std::reduce(channel_state->uniform_cache_hits.begin(),
@@ -230,7 +231,10 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
for (const IntervalType& add_interval : tmp_intervals) {
common_ranges.add(add_interval);
}
- runtime.CopyBuffer(dest_buffer, src_buffer, copies);
+ const auto& copy = copies[0];
+ src_buffer.MarkUsage(copy.src_offset, copy.size);
+ dest_buffer.MarkUsage(copy.dst_offset, copy.size);
+ runtime.CopyBuffer(dest_buffer, src_buffer, copies, true);
if (has_new_downloads) {
memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount);
}
@@ -258,9 +262,10 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
common_ranges.subtract(subtract_interval);
const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size));
- auto& dest_buffer = slot_buffers[buffer];
+ Buffer& dest_buffer = slot_buffers[buffer];
const u32 offset = dest_buffer.Offset(*cpu_dst_address);
runtime.ClearBuffer(dest_buffer, offset, size, value);
+ dest_buffer.MarkUsage(offset, size);
return true;
}
@@ -603,6 +608,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
VAddr orig_cpu_addr = static_cast<VAddr>(second_copy.src_offset);
const IntervalType base_interval{orig_cpu_addr, orig_cpu_addr + copy.size};
async_downloads += std::make_pair(base_interval, 1);
+ buffer.MarkUsage(copy.src_offset, copy.size);
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
normalized_copies.push_back(second_copy);
}
@@ -621,8 +627,9 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
// Have in mind the staging buffer offset for the copy
copy.dst_offset += download_staging.offset;
const std::array copies{copy};
- runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies,
- false);
+ Buffer& buffer = slot_buffers[buffer_id];
+ buffer.MarkUsage(copy.src_offset, copy.size);
+ runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
}
runtime.PostCopyBarrier();
runtime.Finish();
@@ -742,7 +749,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
std::memcpy(upload_staging.mapped_span.data(),
draw_state.inline_index_draw_indexes.data(), size);
- runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
+ runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true);
} else {
buffer.ImmediateUpload(0, draw_state.inline_index_draw_indexes);
}
@@ -754,6 +761,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
runtime.BindIndexBuffer(buffer, new_offset, size);
} else {
+ buffer.MarkUsage(offset, size);
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
draw_state.index_buffer.first, draw_state.index_buffer.count,
buffer, offset, size);
@@ -790,6 +798,7 @@ void BufferCache<P>::BindHostVertexBuffers() {
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
const u32 offset = buffer.Offset(binding.cpu_addr);
+ buffer.MarkUsage(offset, binding.size);
host_bindings.buffers.push_back(&buffer);
host_bindings.offsets.push_back(offset);
@@ -895,6 +904,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
}
+ buffer.MarkUsage(offset, size);
if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size);
} else {
@@ -913,6 +923,7 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
SynchronizeBuffer(buffer, binding.cpu_addr, size);
const u32 offset = buffer.Offset(binding.cpu_addr);
+ buffer.MarkUsage(offset, size);
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (is_written) {
@@ -943,6 +954,7 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
const u32 offset = buffer.Offset(binding.cpu_addr);
const PixelFormat format = binding.format;
+ buffer.MarkUsage(offset, size);
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
if (((channel_state->image_texture_buffers[stage] >> index) & 1) != 0) {
runtime.BindImageBuffer(buffer, offset, size, format);
@@ -975,9 +987,10 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
const u32 offset = buffer.Offset(binding.cpu_addr);
+ buffer.MarkUsage(offset, size);
host_bindings.buffers.push_back(&buffer);
host_bindings.offsets.push_back(offset);
- host_bindings.sizes.push_back(binding.size);
+ host_bindings.sizes.push_back(size);
}
if (host_bindings.buffers.size() > 0) {
runtime.BindTransformFeedbackBuffers(host_bindings);
@@ -1001,6 +1014,7 @@ void BufferCache<P>::BindHostComputeUniformBuffers() {
SynchronizeBuffer(buffer, binding.cpu_addr, size);
const u32 offset = buffer.Offset(binding.cpu_addr);
+ buffer.MarkUsage(offset, size);
if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
runtime.BindComputeUniformBuffer(binding_index, buffer, offset, size);
++binding_index;
@@ -1021,6 +1035,7 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
SynchronizeBuffer(buffer, binding.cpu_addr, size);
const u32 offset = buffer.Offset(binding.cpu_addr);
+ buffer.MarkUsage(offset, size);
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
@@ -1053,6 +1068,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
const u32 offset = buffer.Offset(binding.cpu_addr);
const PixelFormat format = binding.format;
+ buffer.MarkUsage(offset, size);
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
if (((channel_state->image_compute_texture_buffers >> index) & 1) != 0) {
runtime.BindImageBuffer(buffer, offset, size, format);
@@ -1067,8 +1083,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
template <class P>
void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
- do {
- channel_state->has_deleted_buffers = false;
+ BufferOperations([&]() {
if (is_indexed) {
UpdateIndexBuffer();
}
@@ -1082,14 +1097,16 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
if (current_draw_indirect) {
UpdateDrawIndirect();
}
- } while (channel_state->has_deleted_buffers);
+ });
}
template <class P>
void BufferCache<P>::DoUpdateComputeBuffers() {
- UpdateComputeUniformBuffers();
- UpdateComputeStorageBuffers();
- UpdateComputeTextureBuffers();
+ BufferOperations([&]() {
+ UpdateComputeUniformBuffers();
+ UpdateComputeStorageBuffers();
+ UpdateComputeTextureBuffers();
+ });
}
template <class P>
@@ -1171,10 +1188,11 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
}
+ const BufferId buffer_id = FindBuffer(*cpu_addr, size);
channel_state->vertex_buffers[index] = Binding{
.cpu_addr = *cpu_addr,
.size = size,
- .buffer_id = FindBuffer(*cpu_addr, size),
+ .buffer_id = buffer_id,
};
}
@@ -1191,11 +1209,6 @@ 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),
@@ -1405,7 +1418,8 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
.dst_offset = dst_base_offset,
.size = overlap.SizeBytes(),
});
- runtime.CopyBuffer(new_buffer, overlap, copies);
+ new_buffer.MarkUsage(copies[0].dst_offset, copies[0].size);
+ runtime.CopyBuffer(new_buffer, overlap, copies, true);
DeleteBuffer(overlap_id, true);
}
@@ -1418,7 +1432,9 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
auto& new_buffer = slot_buffers[new_buffer_id];
- runtime.ClearBuffer(new_buffer, 0, new_buffer.SizeBytes(), 0);
+ const size_t size_bytes = new_buffer.SizeBytes();
+ runtime.ClearBuffer(new_buffer, 0, size_bytes, 0);
+ new_buffer.MarkUsage(0, size_bytes);
for (const BufferId overlap_id : overlap.ids) {
JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
}
@@ -1471,11 +1487,6 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
template <class P>
bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) {
- return SynchronizeBufferImpl(buffer, cpu_addr, size);
-}
-
-template <class P>
-bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size) {
boost::container::small_vector<BufferCopy, 4> copies;
u64 total_size_bytes = 0;
u64 largest_copy = 0;
@@ -1498,51 +1509,6 @@ bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 s
}
template <class P>
-bool BufferCache<P>::SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size) {
- boost::container::small_vector<BufferCopy, 4> copies;
- u64 total_size_bytes = 0;
- u64 largest_copy = 0;
- IntervalSet found_sets{};
- auto make_copies = [&] {
- for (auto& interval : found_sets) {
- const std::size_t sub_size = interval.upper() - interval.lower();
- const VAddr cpu_addr_ = interval.lower();
- copies.push_back(BufferCopy{
- .src_offset = total_size_bytes,
- .dst_offset = cpu_addr_ - buffer.CpuAddr(),
- .size = sub_size,
- });
- total_size_bytes += sub_size;
- largest_copy = std::max<u64>(largest_copy, sub_size);
- }
- const std::span<BufferCopy> copies_span(copies.data(), copies.size());
- UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
- };
- memory_tracker.ForEachUploadRange(cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) {
- const VAddr base_adr = cpu_addr_out;
- const VAddr end_adr = base_adr + range_size;
- const IntervalType add_interval{base_adr, end_adr};
- found_sets.add(add_interval);
- });
- if (found_sets.empty()) {
- return true;
- }
- const IntervalType search_interval{cpu_addr, cpu_addr + size};
- auto it = common_ranges.lower_bound(search_interval);
- auto it_end = common_ranges.upper_bound(search_interval);
- if (it == common_ranges.end()) {
- make_copies();
- return false;
- }
- while (it != it_end) {
- found_sets.subtract(*it);
- it++;
- }
- make_copies();
- return false;
-}
-
-template <class P>
void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
std::span<BufferCopy> copies) {
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
@@ -1590,7 +1556,8 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
// Apply the staging offset
copy.src_offset += upload_staging.offset;
}
- runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
+ const bool can_reorder = runtime.CanReorderUpload(buffer, copies);
+ runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true, can_reorder);
}
}
@@ -1632,7 +1599,8 @@ void BufferCache<P>::InlineMemoryImplementation(VAddr dest_address, size_t copy_
}};
u8* const src_pointer = upload_staging.mapped_span.data();
std::memcpy(src_pointer, inlined_buffer.data(), copy_size);
- runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
+ const bool can_reorder = runtime.CanReorderUpload(buffer, copies);
+ runtime.CopyBuffer(buffer, upload_staging.buffer, copies, true, can_reorder);
} else {
buffer.ImmediateUpload(buffer.Offset(dest_address), inlined_buffer.first(copy_size));
}
@@ -1685,8 +1653,9 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
for (BufferCopy& copy : copies) {
// Modify copies to have the staging offset in mind
copy.dst_offset += download_staging.offset;
+ buffer.MarkUsage(copy.src_offset, copy.size);
}
- runtime.CopyBuffer(download_staging.buffer, buffer, copies_span);
+ runtime.CopyBuffer(download_staging.buffer, buffer, copies_span, true);
runtime.Finish();
for (const BufferCopy& copy : copies) {
const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
@@ -1784,15 +1753,25 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr));
return std::min(memory_layout_size, static_cast<u32>(8_MiB));
}();
- const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
- if (!cpu_addr || size == 0) {
+ // Alignment only applies to the offset of the buffer
+ const u32 alignment = runtime.GetStorageBufferAlignment();
+ const GPUVAddr aligned_gpu_addr = Common::AlignDown(gpu_addr, alignment);
+ const u32 aligned_size = static_cast<u32>(gpu_addr - aligned_gpu_addr) + size;
+
+ const std::optional<VAddr> aligned_cpu_addr = gpu_memory->GpuToCpuAddress(aligned_gpu_addr);
+ if (!aligned_cpu_addr || size == 0) {
LOG_WARNING(HW_GPU, "Failed to find storage buffer for cbuf index {}", cbuf_index);
return NULL_BINDING;
}
- const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, YUZU_PAGESIZE);
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
+ ASSERT_MSG(cpu_addr, "Unaligned storage buffer address not found for cbuf index {}",
+ cbuf_index);
+ // The end address used for size calculation does not need to be aligned
+ const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
+
const Binding binding{
- .cpu_addr = *cpu_addr,
- .size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr),
+ .cpu_addr = *aligned_cpu_addr,
+ .size = is_written ? aligned_size : static_cast<u32>(cpu_end - *aligned_cpu_addr),
.buffer_id = BufferId{},
};
return binding;
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index eed267361..d6d696d8c 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -529,10 +529,6 @@ private:
bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);
- bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size);
-
- bool SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size);
-
void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
std::span<BufferCopy> copies);
diff --git a/src/video_core/buffer_cache/usage_tracker.h b/src/video_core/buffer_cache/usage_tracker.h
new file mode 100644
index 000000000..5f8688d31
--- /dev/null
+++ b/src/video_core/buffer_cache/usage_tracker.h
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/alignment.h"
+#include "common/common_types.h"
+
+namespace VideoCommon {
+
+class UsageTracker {
+ static constexpr size_t BYTES_PER_BIT_SHIFT = 6;
+ static constexpr size_t PAGE_SHIFT = 6 + BYTES_PER_BIT_SHIFT;
+ static constexpr size_t PAGE_BYTES = 1 << PAGE_SHIFT;
+
+public:
+ explicit UsageTracker(size_t size) {
+ const size_t num_pages = (size >> PAGE_SHIFT) + 1;
+ pages.resize(num_pages, 0ULL);
+ }
+
+ void Reset() noexcept {
+ std::ranges::fill(pages, 0ULL);
+ }
+
+ void Track(u64 offset, u64 size) noexcept {
+ const size_t page = offset >> PAGE_SHIFT;
+ const size_t page_end = (offset + size) >> PAGE_SHIFT;
+ TrackPage(page, offset, size);
+ if (page == page_end) {
+ return;
+ }
+ for (size_t i = page + 1; i < page_end; i++) {
+ pages[i] = ~u64{0};
+ }
+ const size_t offset_end = offset + size;
+ const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES);
+ TrackPage(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned);
+ }
+
+ [[nodiscard]] bool IsUsed(u64 offset, u64 size) const noexcept {
+ const size_t page = offset >> PAGE_SHIFT;
+ const size_t page_end = (offset + size) >> PAGE_SHIFT;
+ if (IsPageUsed(page, offset, size)) {
+ return true;
+ }
+ for (size_t i = page + 1; i < page_end; i++) {
+ if (pages[i] != 0) {
+ return true;
+ }
+ }
+ const size_t offset_end = offset + size;
+ const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES);
+ return IsPageUsed(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned);
+ }
+
+private:
+ void TrackPage(u64 page, u64 offset, u64 size) noexcept {
+ const size_t offset_in_page = offset % PAGE_BYTES;
+ const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
+ const size_t num_bits = std::min<size_t>(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
+ const size_t mask = ~u64{0} >> (64 - num_bits);
+ pages[page] |= (~u64{0} & mask) << first_bit;
+ }
+
+ bool IsPageUsed(u64 page, u64 offset, u64 size) const noexcept {
+ const size_t offset_in_page = offset % PAGE_BYTES;
+ const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
+ const size_t num_bits = std::min<size_t>(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
+ const size_t mask = ~u64{0} >> (64 - num_bits);
+ const size_t mask2 = (~u64{0} & mask) << first_bit;
+ return (pages[page] & mask2) != 0;
+ }
+
+private:
+ std::vector<u64> pages;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 02e161270..91f10aec2 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -72,7 +72,7 @@ void Fermi2D::Blit() {
UNIMPLEMENTED_IF_MSG(regs.clip_enable != 0, "Clipped blit enabled");
const auto& args = regs.pixels_from_memory;
- constexpr s64 null_derivate = 1ULL << 32;
+ constexpr s64 null_derivative = 1ULL << 32;
Surface src = regs.src;
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const bool delegate_to_gpu = src.width > 512 && src.height > 512 && bytes_per_pixel <= 8 &&
@@ -89,7 +89,7 @@ void Fermi2D::Blit() {
.operation = regs.operation,
.filter = args.sample_mode.filter,
.must_accelerate =
- args.du_dx != null_derivate || args.dv_dy != null_derivate || delegate_to_gpu,
+ args.du_dx != null_derivative || args.dv_dy != null_derivative || delegate_to_gpu,
.dst_x0 = args.dst_x0,
.dst_y0 = args.dst_y0,
.dst_x1 = args.dst_x0 + args.dst_width,
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 32d767d85..592c28ba3 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -268,7 +268,7 @@ 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 cap{GetMaxCurrentVertices() * 4 * 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>(
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index c0e6471fe..805a89900 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -86,10 +86,7 @@ public:
uncommitted_operations.emplace_back(std::move(func));
}
pending_operations.emplace_back(std::move(uncommitted_operations));
- {
- std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
- QueueFence(new_fence);
- }
+ QueueFence(new_fence);
if (!delay_fence) {
func();
}
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index dbcf508e5..1030db681 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -1,11 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
-#include <fstream>
-#include <vector>
#include "common/assert.h"
-#include "common/scope_exit.h"
#include "common/settings.h"
#include "video_core/host1x/codecs/codec.h"
#include "video_core/host1x/codecs/h264.h"
@@ -14,242 +10,17 @@
#include "video_core/host1x/host1x.h"
#include "video_core/memory_manager.h"
-extern "C" {
-#include <libavfilter/buffersink.h>
-#include <libavfilter/buffersrc.h>
-#include <libavutil/opt.h>
-#ifdef LIBVA_FOUND
-// for querying VAAPI driver information
-#include <libavutil/hwcontext_vaapi.h>
-#endif
-}
-
namespace Tegra {
-namespace {
-constexpr AVPixelFormat PREFERRED_GPU_FMT = AV_PIX_FMT_NV12;
-constexpr AVPixelFormat PREFERRED_CPU_FMT = AV_PIX_FMT_YUV420P;
-constexpr std::array PREFERRED_GPU_DECODERS = {
- AV_HWDEVICE_TYPE_CUDA,
-#ifdef _WIN32
- AV_HWDEVICE_TYPE_D3D11VA,
- AV_HWDEVICE_TYPE_DXVA2,
-#elif defined(__unix__)
- AV_HWDEVICE_TYPE_VAAPI,
- AV_HWDEVICE_TYPE_VDPAU,
-#endif
- // last resort for Linux Flatpak (w/ NVIDIA)
- AV_HWDEVICE_TYPE_VULKAN,
-};
-
-void AVPacketDeleter(AVPacket* ptr) {
- av_packet_free(&ptr);
-}
-
-using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&AVPacketDeleter)>;
-
-AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pix_fmts) {
- for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
- if (*p == av_codec_ctx->pix_fmt) {
- return av_codec_ctx->pix_fmt;
- }
- }
- LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
- av_buffer_unref(&av_codec_ctx->hw_device_ctx);
- av_codec_ctx->pix_fmt = PREFERRED_CPU_FMT;
- return PREFERRED_CPU_FMT;
-}
-
-// List all the currently available hwcontext in ffmpeg
-std::vector<AVHWDeviceType> ListSupportedContexts() {
- std::vector<AVHWDeviceType> contexts{};
- AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE;
- do {
- current_device_type = av_hwdevice_iterate_types(current_device_type);
- contexts.push_back(current_device_type);
- } while (current_device_type != AV_HWDEVICE_TYPE_NONE);
- return contexts;
-}
-
-} // namespace
-
-void AVFrameDeleter(AVFrame* ptr) {
- av_frame_free(&ptr);
-}
Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs)
: host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
-Codec::~Codec() {
- if (!initialized) {
- return;
- }
- // Free libav memory
- avcodec_free_context(&av_codec_ctx);
- av_buffer_unref(&av_gpu_decoder);
-
- if (filters_initialized) {
- avfilter_graph_free(&av_filter_graph);
- }
-}
-
-bool Codec::CreateGpuAvDevice() {
- static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX;
- static const auto supported_contexts = ListSupportedContexts();
- for (const auto& type : PREFERRED_GPU_DECODERS) {
- if (std::none_of(supported_contexts.begin(), supported_contexts.end(),
- [&type](const auto& context) { return context == type; })) {
- LOG_DEBUG(Service_NVDRV, "{} explicitly unsupported", av_hwdevice_get_type_name(type));
- continue;
- }
- // Avoid memory leak from not cleaning up after av_hwdevice_ctx_create
- av_buffer_unref(&av_gpu_decoder);
- const int hwdevice_res = av_hwdevice_ctx_create(&av_gpu_decoder, type, nullptr, nullptr, 0);
- if (hwdevice_res < 0) {
- LOG_DEBUG(Service_NVDRV, "{} av_hwdevice_ctx_create failed {}",
- av_hwdevice_get_type_name(type), hwdevice_res);
- continue;
- }
-#ifdef LIBVA_FOUND
- if (type == AV_HWDEVICE_TYPE_VAAPI) {
- // we need to determine if this is an impersonated VAAPI driver
- AVHWDeviceContext* hwctx =
- static_cast<AVHWDeviceContext*>(static_cast<void*>(av_gpu_decoder->data));
- AVVAAPIDeviceContext* vactx = static_cast<AVVAAPIDeviceContext*>(hwctx->hwctx);
- const char* vendor_name = vaQueryVendorString(vactx->display);
- if (strstr(vendor_name, "VDPAU backend")) {
- // VDPAU impersonated VAAPI impl's are super buggy, we need to skip them
- LOG_DEBUG(Service_NVDRV, "Skipping vdapu impersonated VAAPI driver");
- continue;
- } else {
- // according to some user testing, certain vaapi driver (Intel?) could be buggy
- // so let's log the driver name which may help the developers/supporters
- LOG_DEBUG(Service_NVDRV, "Using VAAPI driver: {}", vendor_name);
- }
- }
-#endif
- for (int i = 0;; i++) {
- const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i);
- if (!config) {
- LOG_DEBUG(Service_NVDRV, "{} decoder does not support device type {}.",
- av_codec->name, av_hwdevice_get_type_name(type));
- break;
- }
- if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) {
- LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
- av_codec_ctx->pix_fmt = config->pix_fmt;
- return true;
- }
- }
- }
- return false;
-}
-
-void Codec::InitializeAvCodecContext() {
- av_codec_ctx = avcodec_alloc_context3(av_codec);
- av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
- av_codec_ctx->thread_count = 0;
- av_codec_ctx->thread_type &= ~FF_THREAD_FRAME;
-}
-
-void Codec::InitializeGpuDecoder() {
- if (!CreateGpuAvDevice()) {
- av_buffer_unref(&av_gpu_decoder);
- return;
- }
- auto* hw_device_ctx = av_buffer_ref(av_gpu_decoder);
- ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
- av_codec_ctx->hw_device_ctx = hw_device_ctx;
- av_codec_ctx->get_format = GetGpuFormat;
-}
-
-void Codec::InitializeAvFilters(AVFrame* frame) {
- const AVFilter* buffer_src = avfilter_get_by_name("buffer");
- const AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
- AVFilterInOut* inputs = avfilter_inout_alloc();
- AVFilterInOut* outputs = avfilter_inout_alloc();
- SCOPE_EXIT({
- avfilter_inout_free(&inputs);
- avfilter_inout_free(&outputs);
- });
-
- // Don't know how to get the accurate time_base but it doesn't matter for yadif filter
- // so just use 1/1 to make buffer filter happy
- std::string args = fmt::format("video_size={}x{}:pix_fmt={}:time_base=1/1", frame->width,
- frame->height, frame->format);
-
- av_filter_graph = avfilter_graph_alloc();
- int ret = avfilter_graph_create_filter(&av_filter_src_ctx, buffer_src, "in", args.c_str(),
- nullptr, av_filter_graph);
- if (ret < 0) {
- LOG_ERROR(Service_NVDRV, "avfilter_graph_create_filter source error: {}", ret);
- return;
- }
-
- ret = avfilter_graph_create_filter(&av_filter_sink_ctx, buffer_sink, "out", nullptr, nullptr,
- av_filter_graph);
- if (ret < 0) {
- LOG_ERROR(Service_NVDRV, "avfilter_graph_create_filter sink error: {}", ret);
- return;
- }
-
- inputs->name = av_strdup("out");
- inputs->filter_ctx = av_filter_sink_ctx;
- inputs->pad_idx = 0;
- inputs->next = nullptr;
-
- outputs->name = av_strdup("in");
- outputs->filter_ctx = av_filter_src_ctx;
- outputs->pad_idx = 0;
- outputs->next = nullptr;
-
- const char* description = "yadif=1:-1:0";
- ret = avfilter_graph_parse_ptr(av_filter_graph, description, &inputs, &outputs, nullptr);
- if (ret < 0) {
- LOG_ERROR(Service_NVDRV, "avfilter_graph_parse_ptr error: {}", ret);
- return;
- }
-
- ret = avfilter_graph_config(av_filter_graph, nullptr);
- if (ret < 0) {
- LOG_ERROR(Service_NVDRV, "avfilter_graph_config error: {}", ret);
- return;
- }
-
- filters_initialized = true;
-}
+Codec::~Codec() = default;
void Codec::Initialize() {
- const AVCodecID codec = [&] {
- switch (current_codec) {
- case Host1x::NvdecCommon::VideoCodec::H264:
- return AV_CODEC_ID_H264;
- case Host1x::NvdecCommon::VideoCodec::VP8:
- return AV_CODEC_ID_VP8;
- case Host1x::NvdecCommon::VideoCodec::VP9:
- return AV_CODEC_ID_VP9;
- default:
- UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
- return AV_CODEC_ID_NONE;
- }
- }();
- av_codec = avcodec_find_decoder(codec);
-
- InitializeAvCodecContext();
- if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) {
- InitializeGpuDecoder();
- }
- if (const int res = avcodec_open2(av_codec_ctx, av_codec, nullptr); res < 0) {
- LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed with result {}", res);
- avcodec_free_context(&av_codec_ctx);
- av_buffer_unref(&av_gpu_decoder);
- return;
- }
- if (!av_codec_ctx->hw_device_ctx) {
- LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
- }
- initialized = true;
+ initialized = decode_api.Initialize(current_codec);
}
void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) {
@@ -264,14 +35,18 @@ void Codec::Decode() {
if (is_first_frame) {
Initialize();
}
+
if (!initialized) {
return;
}
+
+ // Assemble bitstream.
bool vp9_hidden_frame = false;
- const auto& frame_data = [&]() {
+ size_t configuration_size = 0;
+ const auto packet_data = [&]() {
switch (current_codec) {
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
- return h264_decoder->ComposeFrame(state, is_first_frame);
+ return h264_decoder->ComposeFrame(state, &configuration_size, is_first_frame);
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
return vp8_decoder->ComposeFrame(state);
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
@@ -283,89 +58,35 @@ void Codec::Decode() {
return std::span<const u8>{};
}
}();
- AVPacketPtr packet{av_packet_alloc(), AVPacketDeleter};
- if (!packet) {
- LOG_ERROR(Service_NVDRV, "av_packet_alloc failed");
- return;
- }
- packet->data = const_cast<u8*>(frame_data.data());
- packet->size = static_cast<s32>(frame_data.size());
- if (const int res = avcodec_send_packet(av_codec_ctx, packet.get()); res != 0) {
- LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", res);
+
+ // Send assembled bitstream to decoder.
+ if (!decode_api.SendPacket(packet_data, configuration_size)) {
return;
}
- // Only receive/store visible frames
+
+ // Only receive/store visible frames.
if (vp9_hidden_frame) {
return;
}
- AVFramePtr initial_frame{av_frame_alloc(), AVFrameDeleter};
- AVFramePtr final_frame{nullptr, AVFrameDeleter};
- ASSERT_MSG(initial_frame, "av_frame_alloc initial_frame failed");
- if (const int ret = avcodec_receive_frame(av_codec_ctx, initial_frame.get()); ret) {
- LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
- return;
- }
- if (initial_frame->width == 0 || initial_frame->height == 0) {
- 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");
- // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
- // because Intel drivers crash unless using AV_PIX_FMT_NV12
- final_frame->format = PREFERRED_GPU_FMT;
- const int ret = av_hwframe_transfer_data(final_frame.get(), initial_frame.get(), 0);
- ASSERT_MSG(!ret, "av_hwframe_transfer_data error {}", ret);
- } else {
- final_frame = std::move(initial_frame);
- }
- if (final_frame->format != PREFERRED_CPU_FMT && final_frame->format != PREFERRED_GPU_FMT) {
- UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
- return;
- }
- if (!is_interlaced) {
- av_frames.push(std::move(final_frame));
- } else {
- if (!filters_initialized) {
- InitializeAvFilters(final_frame.get());
- }
- if (const int ret = av_buffersrc_add_frame_flags(av_filter_src_ctx, final_frame.get(),
- AV_BUFFERSRC_FLAG_KEEP_REF);
- ret) {
- LOG_DEBUG(Service_NVDRV, "av_buffersrc_add_frame_flags error {}", ret);
- return;
- }
- while (true) {
- auto filter_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
- int ret = av_buffersink_get_frame(av_filter_sink_ctx, filter_frame.get());
+ // Receive output frames from decoder.
+ decode_api.ReceiveFrames(frames);
- if (ret == AVERROR(EAGAIN) || ret == AVERROR(AVERROR_EOF))
- break;
- if (ret < 0) {
- LOG_DEBUG(Service_NVDRV, "av_buffersink_get_frame error {}", ret);
- return;
- }
-
- av_frames.push(std::move(filter_frame));
- }
- }
- while (av_frames.size() > 10) {
- LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
- av_frames.pop();
+ while (frames.size() > 10) {
+ LOG_DEBUG(HW_GPU, "ReceiveFrames overflow, dropped frame");
+ frames.pop();
}
}
-AVFramePtr Codec::GetCurrentFrame() {
+std::unique_ptr<FFmpeg::Frame> Codec::GetCurrentFrame() {
// Sometimes VIC will request more frames than have been decoded.
- // in this case, return a nullptr and don't overwrite previous frame data
- if (av_frames.empty()) {
- return AVFramePtr{nullptr, AVFrameDeleter};
+ // in this case, return a blank frame and don't overwrite previous data.
+ if (frames.empty()) {
+ return {};
}
- AVFramePtr frame = std::move(av_frames.front());
- av_frames.pop();
+
+ auto frame = std::move(frames.front());
+ frames.pop();
return frame;
}
diff --git a/src/video_core/host1x/codecs/codec.h b/src/video_core/host1x/codecs/codec.h
index 06fe00a4b..f700ae129 100644
--- a/src/video_core/host1x/codecs/codec.h
+++ b/src/video_core/host1x/codecs/codec.h
@@ -4,28 +4,15 @@
#pragma once
#include <memory>
+#include <optional>
#include <string_view>
#include <queue>
#include "common/common_types.h"
+#include "video_core/host1x/ffmpeg/ffmpeg.h"
#include "video_core/host1x/nvdec_common.h"
-extern "C" {
-#if defined(__GNUC__) || defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wconversion"
-#endif
-#include <libavcodec/avcodec.h>
-#include <libavfilter/avfilter.h>
-#if defined(__GNUC__) || defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
-}
-
namespace Tegra {
-void AVFrameDeleter(AVFrame* ptr);
-using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
-
namespace Decoder {
class H264;
class VP8;
@@ -51,7 +38,7 @@ public:
void Decode();
/// Returns next decoded frame
- [[nodiscard]] AVFramePtr GetCurrentFrame();
+ [[nodiscard]] std::unique_ptr<FFmpeg::Frame> GetCurrentFrame();
/// Returns the value of current_codec
[[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const;
@@ -60,25 +47,9 @@ public:
[[nodiscard]] std::string_view GetCurrentCodecName() const;
private:
- void InitializeAvCodecContext();
-
- void InitializeAvFilters(AVFrame* frame);
-
- void InitializeGpuDecoder();
-
- bool CreateGpuAvDevice();
-
bool initialized{};
- bool filters_initialized{};
Host1x::NvdecCommon::VideoCodec current_codec{Host1x::NvdecCommon::VideoCodec::None};
-
- const AVCodec* av_codec{nullptr};
- AVCodecContext* av_codec_ctx{nullptr};
- AVBufferRef* av_gpu_decoder{nullptr};
-
- AVFilterContext* av_filter_src_ctx{nullptr};
- AVFilterContext* av_filter_sink_ctx{nullptr};
- AVFilterGraph* av_filter_graph{nullptr};
+ FFmpeg::DecodeApi decode_api;
Host1x::Host1x& host1x;
const Host1x::NvdecCommon::NvdecRegisters& state;
@@ -86,7 +57,7 @@ private:
std::unique_ptr<Decoder::VP8> vp8_decoder;
std::unique_ptr<Decoder::VP9> vp9_decoder;
- std::queue<AVFramePtr> av_frames{};
+ std::queue<std::unique_ptr<FFmpeg::Frame>> frames{};
};
} // namespace Tegra
diff --git a/src/video_core/host1x/codecs/h264.cpp b/src/video_core/host1x/codecs/h264.cpp
index ece79b1e2..309a7f1d5 100644
--- a/src/video_core/host1x/codecs/h264.cpp
+++ b/src/video_core/host1x/codecs/h264.cpp
@@ -30,7 +30,7 @@ H264::H264(Host1x::Host1x& host1x_) : host1x{host1x_} {}
H264::~H264() = default;
std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state,
- bool is_first_frame) {
+ size_t* out_configuration_size, bool is_first_frame) {
H264DecoderContext context;
host1x.MemoryManager().ReadBlock(state.picture_info_offset, &context,
sizeof(H264DecoderContext));
@@ -39,6 +39,7 @@ std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters
if (!is_first_frame && frame_number != 0) {
frame.resize_destructive(context.stream_len);
host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
+ *out_configuration_size = 0;
return frame;
}
@@ -157,6 +158,7 @@ std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters
frame.resize(encoded_header.size() + context.stream_len);
std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
+ *out_configuration_size = encoded_header.size();
host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset,
frame.data() + encoded_header.size(), context.stream_len);
diff --git a/src/video_core/host1x/codecs/h264.h b/src/video_core/host1x/codecs/h264.h
index d6b556322..1deaf4632 100644
--- a/src/video_core/host1x/codecs/h264.h
+++ b/src/video_core/host1x/codecs/h264.h
@@ -67,6 +67,7 @@ public:
/// Compose the H264 frame for FFmpeg decoding
[[nodiscard]] std::span<const u8> ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state,
+ size_t* out_configuration_size,
bool is_first_frame = false);
private:
diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.cpp b/src/video_core/host1x/ffmpeg/ffmpeg.cpp
new file mode 100644
index 000000000..dcd07e6d2
--- /dev/null
+++ b/src/video_core/host1x/ffmpeg/ffmpeg.cpp
@@ -0,0 +1,419 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "common/settings.h"
+#include "video_core/host1x/ffmpeg/ffmpeg.h"
+
+extern "C" {
+#ifdef LIBVA_FOUND
+// for querying VAAPI driver information
+#include <libavutil/hwcontext_vaapi.h>
+#endif
+}
+
+namespace FFmpeg {
+
+namespace {
+
+constexpr AVPixelFormat PreferredGpuFormat = AV_PIX_FMT_NV12;
+constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P;
+constexpr std::array PreferredGpuDecoders = {
+ AV_HWDEVICE_TYPE_CUDA,
+#ifdef _WIN32
+ AV_HWDEVICE_TYPE_D3D11VA,
+ AV_HWDEVICE_TYPE_DXVA2,
+#elif defined(__unix__)
+ AV_HWDEVICE_TYPE_VAAPI,
+ AV_HWDEVICE_TYPE_VDPAU,
+#endif
+ // last resort for Linux Flatpak (w/ NVIDIA)
+ AV_HWDEVICE_TYPE_VULKAN,
+};
+
+AVPixelFormat GetGpuFormat(AVCodecContext* codec_context, const AVPixelFormat* pix_fmts) {
+ for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
+ if (*p == codec_context->pix_fmt) {
+ return codec_context->pix_fmt;
+ }
+ }
+
+ LOG_INFO(HW_GPU, "Could not find compatible GPU AV format, falling back to CPU");
+ av_buffer_unref(&codec_context->hw_device_ctx);
+
+ codec_context->pix_fmt = PreferredCpuFormat;
+ return codec_context->pix_fmt;
+}
+
+std::string AVError(int errnum) {
+ char errbuf[AV_ERROR_MAX_STRING_SIZE] = {};
+ av_make_error_string(errbuf, sizeof(errbuf) - 1, errnum);
+ return errbuf;
+}
+
+} // namespace
+
+Packet::Packet(std::span<const u8> data) {
+ m_packet = av_packet_alloc();
+ m_packet->data = const_cast<u8*>(data.data());
+ m_packet->size = static_cast<s32>(data.size());
+}
+
+Packet::~Packet() {
+ av_packet_free(&m_packet);
+}
+
+Frame::Frame() {
+ m_frame = av_frame_alloc();
+}
+
+Frame::~Frame() {
+ av_frame_free(&m_frame);
+}
+
+Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
+ const AVCodecID av_codec = [&] {
+ switch (codec) {
+ case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
+ return AV_CODEC_ID_H264;
+ case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
+ return AV_CODEC_ID_VP8;
+ case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
+ return AV_CODEC_ID_VP9;
+ default:
+ UNIMPLEMENTED_MSG("Unknown codec {}", codec);
+ return AV_CODEC_ID_NONE;
+ }
+ }();
+
+ m_codec = avcodec_find_decoder(av_codec);
+}
+
+bool Decoder::SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceType type) const {
+ for (int i = 0;; i++) {
+ const AVCodecHWConfig* config = avcodec_get_hw_config(m_codec, i);
+ if (!config) {
+ LOG_DEBUG(HW_GPU, "{} decoder does not support device type {}", m_codec->name,
+ av_hwdevice_get_type_name(type));
+ break;
+ }
+ if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0 &&
+ config->device_type == type) {
+ LOG_INFO(HW_GPU, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
+ *out_pix_fmt = config->pix_fmt;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::vector<AVHWDeviceType> HardwareContext::GetSupportedDeviceTypes() {
+ std::vector<AVHWDeviceType> types;
+ AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE;
+
+ while (true) {
+ current_device_type = av_hwdevice_iterate_types(current_device_type);
+ if (current_device_type == AV_HWDEVICE_TYPE_NONE) {
+ return types;
+ }
+
+ types.push_back(current_device_type);
+ }
+}
+
+HardwareContext::~HardwareContext() {
+ av_buffer_unref(&m_gpu_decoder);
+}
+
+bool HardwareContext::InitializeForDecoder(DecoderContext& decoder_context,
+ const Decoder& decoder) {
+ const auto supported_types = GetSupportedDeviceTypes();
+ for (const auto type : PreferredGpuDecoders) {
+ AVPixelFormat hw_pix_fmt;
+
+ if (std::ranges::find(supported_types, type) == supported_types.end()) {
+ LOG_DEBUG(HW_GPU, "{} explicitly unsupported", av_hwdevice_get_type_name(type));
+ continue;
+ }
+
+ if (!this->InitializeWithType(type)) {
+ continue;
+ }
+
+ if (decoder.SupportsDecodingOnDevice(&hw_pix_fmt, type)) {
+ decoder_context.InitializeHardwareDecoder(*this, hw_pix_fmt);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool HardwareContext::InitializeWithType(AVHWDeviceType type) {
+ av_buffer_unref(&m_gpu_decoder);
+
+ if (const int ret = av_hwdevice_ctx_create(&m_gpu_decoder, type, nullptr, nullptr, 0);
+ ret < 0) {
+ LOG_DEBUG(HW_GPU, "av_hwdevice_ctx_create({}) failed: {}", av_hwdevice_get_type_name(type),
+ AVError(ret));
+ return false;
+ }
+
+#ifdef LIBVA_FOUND
+ if (type == AV_HWDEVICE_TYPE_VAAPI) {
+ // We need to determine if this is an impersonated VAAPI driver.
+ auto* hwctx = reinterpret_cast<AVHWDeviceContext*>(m_gpu_decoder->data);
+ auto* vactx = static_cast<AVVAAPIDeviceContext*>(hwctx->hwctx);
+ const char* vendor_name = vaQueryVendorString(vactx->display);
+ if (strstr(vendor_name, "VDPAU backend")) {
+ // VDPAU impersonated VAAPI impls are super buggy, we need to skip them.
+ LOG_DEBUG(HW_GPU, "Skipping VDPAU impersonated VAAPI driver");
+ return false;
+ } else {
+ // According to some user testing, certain VAAPI drivers (Intel?) could be buggy.
+ // Log the driver name just in case.
+ LOG_DEBUG(HW_GPU, "Using VAAPI driver: {}", vendor_name);
+ }
+ }
+#endif
+
+ return true;
+}
+
+DecoderContext::DecoderContext(const Decoder& decoder) {
+ m_codec_context = avcodec_alloc_context3(decoder.GetCodec());
+ av_opt_set(m_codec_context->priv_data, "tune", "zerolatency", 0);
+ m_codec_context->thread_count = 0;
+ m_codec_context->thread_type &= ~FF_THREAD_FRAME;
+}
+
+DecoderContext::~DecoderContext() {
+ av_buffer_unref(&m_codec_context->hw_device_ctx);
+ avcodec_free_context(&m_codec_context);
+}
+
+void DecoderContext::InitializeHardwareDecoder(const HardwareContext& context,
+ AVPixelFormat hw_pix_fmt) {
+ m_codec_context->hw_device_ctx = av_buffer_ref(context.GetBufferRef());
+ m_codec_context->get_format = GetGpuFormat;
+ m_codec_context->pix_fmt = hw_pix_fmt;
+}
+
+bool DecoderContext::OpenContext(const Decoder& decoder) {
+ if (const int ret = avcodec_open2(m_codec_context, decoder.GetCodec(), nullptr); ret < 0) {
+ LOG_ERROR(HW_GPU, "avcodec_open2 error: {}", AVError(ret));
+ return false;
+ }
+
+ if (!m_codec_context->hw_device_ctx) {
+ LOG_INFO(HW_GPU, "Using FFmpeg software decoding");
+ }
+
+ return true;
+}
+
+bool DecoderContext::SendPacket(const Packet& packet) {
+ if (const int ret = avcodec_send_packet(m_codec_context, packet.GetPacket()); ret < 0) {
+ LOG_ERROR(HW_GPU, "avcodec_send_packet error: {}", AVError(ret));
+ return false;
+ }
+
+ return true;
+}
+
+std::unique_ptr<Frame> DecoderContext::ReceiveFrame(bool* out_is_interlaced) {
+ auto dst_frame = std::make_unique<Frame>();
+
+ const auto ReceiveImpl = [&](AVFrame* frame) {
+ if (const int ret = avcodec_receive_frame(m_codec_context, frame); ret < 0) {
+ LOG_ERROR(HW_GPU, "avcodec_receive_frame error: {}", AVError(ret));
+ return false;
+ }
+
+ *out_is_interlaced = frame->interlaced_frame != 0;
+ return true;
+ };
+
+ if (m_codec_context->hw_device_ctx) {
+ // If we have a hardware context, make a separate frame here to receive the
+ // hardware result before sending it to the output.
+ Frame intermediate_frame;
+
+ if (!ReceiveImpl(intermediate_frame.GetFrame())) {
+ return {};
+ }
+
+ dst_frame->SetFormat(PreferredGpuFormat);
+ if (const int ret =
+ av_hwframe_transfer_data(dst_frame->GetFrame(), intermediate_frame.GetFrame(), 0);
+ ret < 0) {
+ LOG_ERROR(HW_GPU, "av_hwframe_transfer_data error: {}", AVError(ret));
+ return {};
+ }
+ } else {
+ // Otherwise, decode the frame as normal.
+ if (!ReceiveImpl(dst_frame->GetFrame())) {
+ return {};
+ }
+ }
+
+ return dst_frame;
+}
+
+DeinterlaceFilter::DeinterlaceFilter(const Frame& frame) {
+ const AVFilter* buffer_src = avfilter_get_by_name("buffer");
+ const AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
+ AVFilterInOut* inputs = avfilter_inout_alloc();
+ AVFilterInOut* outputs = avfilter_inout_alloc();
+ SCOPE_EXIT({
+ avfilter_inout_free(&inputs);
+ avfilter_inout_free(&outputs);
+ });
+
+ // Don't know how to get the accurate time_base but it doesn't matter for yadif filter
+ // so just use 1/1 to make buffer filter happy
+ std::string args = fmt::format("video_size={}x{}:pix_fmt={}:time_base=1/1", frame.GetWidth(),
+ frame.GetHeight(), static_cast<int>(frame.GetPixelFormat()));
+
+ m_filter_graph = avfilter_graph_alloc();
+ int ret = avfilter_graph_create_filter(&m_source_context, buffer_src, "in", args.c_str(),
+ nullptr, m_filter_graph);
+ if (ret < 0) {
+ LOG_ERROR(HW_GPU, "avfilter_graph_create_filter source error: {}", AVError(ret));
+ return;
+ }
+
+ ret = avfilter_graph_create_filter(&m_sink_context, buffer_sink, "out", nullptr, nullptr,
+ m_filter_graph);
+ if (ret < 0) {
+ LOG_ERROR(HW_GPU, "avfilter_graph_create_filter sink error: {}", AVError(ret));
+ return;
+ }
+
+ inputs->name = av_strdup("out");
+ inputs->filter_ctx = m_sink_context;
+ inputs->pad_idx = 0;
+ inputs->next = nullptr;
+
+ outputs->name = av_strdup("in");
+ outputs->filter_ctx = m_source_context;
+ outputs->pad_idx = 0;
+ outputs->next = nullptr;
+
+ const char* description = "yadif=1:-1:0";
+ ret = avfilter_graph_parse_ptr(m_filter_graph, description, &inputs, &outputs, nullptr);
+ if (ret < 0) {
+ LOG_ERROR(HW_GPU, "avfilter_graph_parse_ptr error: {}", AVError(ret));
+ return;
+ }
+
+ ret = avfilter_graph_config(m_filter_graph, nullptr);
+ if (ret < 0) {
+ LOG_ERROR(HW_GPU, "avfilter_graph_config error: {}", AVError(ret));
+ return;
+ }
+
+ m_initialized = true;
+}
+
+bool DeinterlaceFilter::AddSourceFrame(const Frame& frame) {
+ if (const int ret = av_buffersrc_add_frame_flags(m_source_context, frame.GetFrame(),
+ AV_BUFFERSRC_FLAG_KEEP_REF);
+ ret < 0) {
+ LOG_ERROR(HW_GPU, "av_buffersrc_add_frame_flags error: {}", AVError(ret));
+ return false;
+ }
+
+ return true;
+}
+
+std::unique_ptr<Frame> DeinterlaceFilter::DrainSinkFrame() {
+ auto dst_frame = std::make_unique<Frame>();
+ const int ret = av_buffersink_get_frame(m_sink_context, dst_frame->GetFrame());
+
+ if (ret == AVERROR(EAGAIN) || ret == AVERROR(AVERROR_EOF)) {
+ return {};
+ }
+
+ if (ret < 0) {
+ LOG_ERROR(HW_GPU, "av_buffersink_get_frame error: {}", AVError(ret));
+ return {};
+ }
+
+ return dst_frame;
+}
+
+DeinterlaceFilter::~DeinterlaceFilter() {
+ avfilter_graph_free(&m_filter_graph);
+}
+
+void DecodeApi::Reset() {
+ m_deinterlace_filter.reset();
+ m_hardware_context.reset();
+ m_decoder_context.reset();
+ m_decoder.reset();
+}
+
+bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
+ this->Reset();
+ m_decoder.emplace(codec);
+ m_decoder_context.emplace(*m_decoder);
+
+ // Enable GPU decoding if requested.
+ if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) {
+ m_hardware_context.emplace();
+ m_hardware_context->InitializeForDecoder(*m_decoder_context, *m_decoder);
+ }
+
+ // Open the decoder context.
+ if (!m_decoder_context->OpenContext(*m_decoder)) {
+ this->Reset();
+ return false;
+ }
+
+ return true;
+}
+
+bool DecodeApi::SendPacket(std::span<const u8> packet_data, size_t configuration_size) {
+ FFmpeg::Packet packet(packet_data);
+ return m_decoder_context->SendPacket(packet);
+}
+
+void DecodeApi::ReceiveFrames(std::queue<std::unique_ptr<Frame>>& frame_queue) {
+ // Receive raw frame from decoder.
+ bool is_interlaced;
+ auto frame = m_decoder_context->ReceiveFrame(&is_interlaced);
+ if (!frame) {
+ return;
+ }
+
+ if (!is_interlaced) {
+ // If the frame is not interlaced, we can pend it now.
+ frame_queue.push(std::move(frame));
+ } else {
+ // Create the deinterlacer if needed.
+ if (!m_deinterlace_filter) {
+ m_deinterlace_filter.emplace(*frame);
+ }
+
+ // Add the frame we just received.
+ if (!m_deinterlace_filter->AddSourceFrame(*frame)) {
+ return;
+ }
+
+ // Pend output fields.
+ while (true) {
+ auto filter_frame = m_deinterlace_filter->DrainSinkFrame();
+ if (!filter_frame) {
+ break;
+ }
+
+ frame_queue.push(std::move(filter_frame));
+ }
+ }
+}
+
+} // namespace FFmpeg
diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.h b/src/video_core/host1x/ffmpeg/ffmpeg.h
new file mode 100644
index 000000000..1de0bbd83
--- /dev/null
+++ b/src/video_core/host1x/ffmpeg/ffmpeg.h
@@ -0,0 +1,213 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <span>
+#include <vector>
+#include <queue>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/host1x/nvdec_common.h"
+
+extern "C" {
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#endif
+
+#include <libavcodec/avcodec.h>
+#include <libavfilter/avfilter.h>
+#include <libavfilter/buffersink.h>
+#include <libavfilter/buffersrc.h>
+#include <libavutil/avutil.h>
+#include <libavutil/opt.h>
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+}
+
+namespace FFmpeg {
+
+class Packet;
+class Frame;
+class Decoder;
+class HardwareContext;
+class DecoderContext;
+class DeinterlaceFilter;
+
+// Wraps an AVPacket, a container for compressed bitstream data.
+class Packet {
+public:
+ YUZU_NON_COPYABLE(Packet);
+ YUZU_NON_MOVEABLE(Packet);
+
+ explicit Packet(std::span<const u8> data);
+ ~Packet();
+
+ AVPacket* GetPacket() const {
+ return m_packet;
+ }
+
+private:
+ AVPacket* m_packet{};
+};
+
+// Wraps an AVFrame, a container for audio and video stream data.
+class Frame {
+public:
+ YUZU_NON_COPYABLE(Frame);
+ YUZU_NON_MOVEABLE(Frame);
+
+ explicit Frame();
+ ~Frame();
+
+ int GetWidth() const {
+ return m_frame->width;
+ }
+
+ int GetHeight() const {
+ return m_frame->height;
+ }
+
+ AVPixelFormat GetPixelFormat() const {
+ return static_cast<AVPixelFormat>(m_frame->format);
+ }
+
+ int GetStride(int plane) const {
+ return m_frame->linesize[plane];
+ }
+
+ int* GetStrides() const {
+ return m_frame->linesize;
+ }
+
+ u8* GetData(int plane) const {
+ return m_frame->data[plane];
+ }
+
+ u8** GetPlanes() const {
+ return m_frame->data;
+ }
+
+ void SetFormat(int format) {
+ m_frame->format = format;
+ }
+
+ AVFrame* GetFrame() const {
+ return m_frame;
+ }
+
+private:
+ AVFrame* m_frame{};
+};
+
+// Wraps an AVCodec, a type containing information about a codec.
+class Decoder {
+public:
+ YUZU_NON_COPYABLE(Decoder);
+ YUZU_NON_MOVEABLE(Decoder);
+
+ explicit Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec);
+ ~Decoder() = default;
+
+ bool SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceType type) const;
+
+ const AVCodec* GetCodec() const {
+ return m_codec;
+ }
+
+private:
+ const AVCodec* m_codec{};
+};
+
+// Wraps AVBufferRef for an accelerated decoder.
+class HardwareContext {
+public:
+ YUZU_NON_COPYABLE(HardwareContext);
+ YUZU_NON_MOVEABLE(HardwareContext);
+
+ static std::vector<AVHWDeviceType> GetSupportedDeviceTypes();
+
+ explicit HardwareContext() = default;
+ ~HardwareContext();
+
+ bool InitializeForDecoder(DecoderContext& decoder_context, const Decoder& decoder);
+
+ AVBufferRef* GetBufferRef() const {
+ return m_gpu_decoder;
+ }
+
+private:
+ bool InitializeWithType(AVHWDeviceType type);
+
+ AVBufferRef* m_gpu_decoder{};
+};
+
+// Wraps an AVCodecContext.
+class DecoderContext {
+public:
+ YUZU_NON_COPYABLE(DecoderContext);
+ YUZU_NON_MOVEABLE(DecoderContext);
+
+ explicit DecoderContext(const Decoder& decoder);
+ ~DecoderContext();
+
+ void InitializeHardwareDecoder(const HardwareContext& context, AVPixelFormat hw_pix_fmt);
+ bool OpenContext(const Decoder& decoder);
+ bool SendPacket(const Packet& packet);
+ std::unique_ptr<Frame> ReceiveFrame(bool* out_is_interlaced);
+
+ AVCodecContext* GetCodecContext() const {
+ return m_codec_context;
+ }
+
+private:
+ AVCodecContext* m_codec_context{};
+};
+
+// Wraps an AVFilterGraph.
+class DeinterlaceFilter {
+public:
+ YUZU_NON_COPYABLE(DeinterlaceFilter);
+ YUZU_NON_MOVEABLE(DeinterlaceFilter);
+
+ explicit DeinterlaceFilter(const Frame& frame);
+ ~DeinterlaceFilter();
+
+ bool AddSourceFrame(const Frame& frame);
+ std::unique_ptr<Frame> DrainSinkFrame();
+
+private:
+ AVFilterGraph* m_filter_graph{};
+ AVFilterContext* m_source_context{};
+ AVFilterContext* m_sink_context{};
+ bool m_initialized{};
+};
+
+class DecodeApi {
+public:
+ YUZU_NON_COPYABLE(DecodeApi);
+ YUZU_NON_MOVEABLE(DecodeApi);
+
+ DecodeApi() = default;
+ ~DecodeApi() = default;
+
+ bool Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec);
+ void Reset();
+
+ bool SendPacket(std::span<const u8> packet_data, size_t configuration_size);
+ void ReceiveFrames(std::queue<std::unique_ptr<Frame>>& frame_queue);
+
+private:
+ std::optional<FFmpeg::Decoder> m_decoder;
+ std::optional<FFmpeg::DecoderContext> m_decoder_context;
+ std::optional<FFmpeg::HardwareContext> m_hardware_context;
+ std::optional<FFmpeg::DeinterlaceFilter> m_deinterlace_filter;
+};
+
+} // namespace FFmpeg
diff --git a/src/video_core/host1x/nvdec.cpp b/src/video_core/host1x/nvdec.cpp
index a4bd5b79f..b8f5866d3 100644
--- a/src/video_core/host1x/nvdec.cpp
+++ b/src/video_core/host1x/nvdec.cpp
@@ -28,7 +28,7 @@ void Nvdec::ProcessMethod(u32 method, u32 argument) {
}
}
-AVFramePtr Nvdec::GetFrame() {
+std::unique_ptr<FFmpeg::Frame> Nvdec::GetFrame() {
return codec->GetCurrentFrame();
}
diff --git a/src/video_core/host1x/nvdec.h b/src/video_core/host1x/nvdec.h
index 3949d5181..ddddb8d28 100644
--- a/src/video_core/host1x/nvdec.h
+++ b/src/video_core/host1x/nvdec.h
@@ -23,7 +23,7 @@ public:
void ProcessMethod(u32 method, u32 argument);
/// Return most recently decoded frame
- [[nodiscard]] AVFramePtr GetFrame();
+ [[nodiscard]] std::unique_ptr<FFmpeg::Frame> GetFrame();
private:
/// Invoke codec to decode a frame
diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp
index 10d7ef884..2a5eba415 100644
--- a/src/video_core/host1x/vic.cpp
+++ b/src/video_core/host1x/vic.cpp
@@ -82,27 +82,26 @@ void Vic::Execute() {
return;
}
const VicConfig config{host1x.MemoryManager().Read<u64>(config_struct_address + 0x20)};
- const AVFramePtr frame_ptr = nvdec_processor->GetFrame();
- const auto* frame = frame_ptr.get();
+ auto frame = nvdec_processor->GetFrame();
if (!frame) {
return;
}
const u64 surface_width = config.surface_width_minus1 + 1;
const u64 surface_height = config.surface_height_minus1 + 1;
- if (static_cast<u64>(frame->width) != surface_width ||
- static_cast<u64>(frame->height) != surface_height) {
+ if (static_cast<u64>(frame->GetWidth()) != surface_width ||
+ static_cast<u64>(frame->GetHeight()) != surface_height) {
// TODO: Properly support multiple video streams with differing frame dimensions
LOG_WARNING(Service_NVDRV, "Frame dimensions {}x{} don't match surface dimensions {}x{}",
- frame->width, frame->height, surface_width, surface_height);
+ frame->GetWidth(), frame->GetHeight(), surface_width, surface_height);
}
switch (config.pixel_format) {
case VideoPixelFormat::RGBA8:
case VideoPixelFormat::BGRA8:
case VideoPixelFormat::RGBX8:
- WriteRGBFrame(frame, config);
+ WriteRGBFrame(std::move(frame), config);
break;
case VideoPixelFormat::YUV420:
- WriteYUVFrame(frame, config);
+ WriteYUVFrame(std::move(frame), config);
break;
default:
UNIMPLEMENTED_MSG("Unknown video pixel format {:X}", config.pixel_format.Value());
@@ -110,10 +109,14 @@ void Vic::Execute() {
}
}
-void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
+void Vic::WriteRGBFrame(std::unique_ptr<FFmpeg::Frame> frame, const VicConfig& config) {
LOG_TRACE(Service_NVDRV, "Writing RGB Frame");
- if (!scaler_ctx || frame->width != scaler_width || frame->height != scaler_height) {
+ const auto frame_width = frame->GetWidth();
+ const auto frame_height = frame->GetHeight();
+ const auto frame_format = frame->GetPixelFormat();
+
+ if (!scaler_ctx || frame_width != scaler_width || frame_height != scaler_height) {
const AVPixelFormat target_format = [pixel_format = config.pixel_format]() {
switch (pixel_format) {
case VideoPixelFormat::RGBA8:
@@ -129,27 +132,26 @@ void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
sws_freeContext(scaler_ctx);
// Frames are decoded into either YUV420 or NV12 formats. Convert to desired RGB format
- scaler_ctx = sws_getContext(frame->width, frame->height,
- static_cast<AVPixelFormat>(frame->format), frame->width,
- frame->height, target_format, 0, nullptr, nullptr, nullptr);
- scaler_width = frame->width;
- scaler_height = frame->height;
+ scaler_ctx = sws_getContext(frame_width, frame_height, frame_format, frame_width,
+ frame_height, target_format, 0, nullptr, nullptr, nullptr);
+ scaler_width = frame_width;
+ scaler_height = frame_height;
converted_frame_buffer.reset();
}
if (!converted_frame_buffer) {
- const size_t frame_size = frame->width * frame->height * 4;
+ const size_t frame_size = frame_width * frame_height * 4;
converted_frame_buffer = AVMallocPtr{static_cast<u8*>(av_malloc(frame_size)), av_free};
}
- const std::array<int, 4> converted_stride{frame->width * 4, frame->height * 4, 0, 0};
+ const std::array<int, 4> converted_stride{frame_width * 4, frame_height * 4, 0, 0};
u8* const converted_frame_buf_addr{converted_frame_buffer.get()};
- sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height, &converted_frame_buf_addr,
- converted_stride.data());
+ sws_scale(scaler_ctx, frame->GetPlanes(), frame->GetStrides(), 0, frame_height,
+ &converted_frame_buf_addr, converted_stride.data());
// Use the minimum of surface/frame dimensions to avoid buffer overflow.
const u32 surface_width = static_cast<u32>(config.surface_width_minus1) + 1;
const u32 surface_height = static_cast<u32>(config.surface_height_minus1) + 1;
- const u32 width = std::min(surface_width, static_cast<u32>(frame->width));
- const u32 height = std::min(surface_height, static_cast<u32>(frame->height));
+ const u32 width = std::min(surface_width, static_cast<u32>(frame_width));
+ const u32 height = std::min(surface_height, static_cast<u32>(frame_height));
const u32 blk_kind = static_cast<u32>(config.block_linear_kind);
if (blk_kind != 0) {
// swizzle pitch linear to block linear
@@ -169,23 +171,23 @@ void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
}
}
-void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
+void Vic::WriteYUVFrame(std::unique_ptr<FFmpeg::Frame> frame, const VicConfig& config) {
LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame");
const std::size_t surface_width = config.surface_width_minus1 + 1;
const std::size_t surface_height = config.surface_height_minus1 + 1;
const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL;
// Use the minimum of surface/frame dimensions to avoid buffer overflow.
- const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width));
- const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height));
+ const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->GetWidth()));
+ const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->GetHeight()));
- const auto stride = static_cast<size_t>(frame->linesize[0]);
+ const auto stride = static_cast<size_t>(frame->GetStride(0));
luma_buffer.resize_destructive(aligned_width * surface_height);
chroma_buffer.resize_destructive(aligned_width * surface_height / 2);
// Populate luma buffer
- const u8* luma_src = frame->data[0];
+ const u8* luma_src = frame->GetData(0);
for (std::size_t y = 0; y < frame_height; ++y) {
const std::size_t src = y * stride;
const std::size_t dst = y * aligned_width;
@@ -196,16 +198,16 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
// Chroma
const std::size_t half_height = frame_height / 2;
- const auto half_stride = static_cast<size_t>(frame->linesize[1]);
+ const auto half_stride = static_cast<size_t>(frame->GetStride(1));
- switch (frame->format) {
+ switch (frame->GetPixelFormat()) {
case AV_PIX_FMT_YUV420P: {
// Frame from FFmpeg software
// Populate chroma buffer from both channels with interleaving.
const std::size_t half_width = frame_width / 2;
u8* chroma_buffer_data = chroma_buffer.data();
- const u8* chroma_b_src = frame->data[1];
- const u8* chroma_r_src = frame->data[2];
+ const u8* chroma_b_src = frame->GetData(1);
+ const u8* chroma_r_src = frame->GetData(2);
for (std::size_t y = 0; y < half_height; ++y) {
const std::size_t src = y * half_stride;
const std::size_t dst = y * aligned_width;
@@ -219,7 +221,7 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
case AV_PIX_FMT_NV12: {
// Frame from VA-API hardware
// This is already interleaved so just copy
- const u8* chroma_src = frame->data[1];
+ const u8* chroma_src = frame->GetData(1);
for (std::size_t y = 0; y < half_height; ++y) {
const std::size_t src = y * stride;
const std::size_t dst = y * aligned_width;
diff --git a/src/video_core/host1x/vic.h b/src/video_core/host1x/vic.h
index 3d9753047..6c868f062 100644
--- a/src/video_core/host1x/vic.h
+++ b/src/video_core/host1x/vic.h
@@ -39,9 +39,9 @@ public:
private:
void Execute();
- void WriteRGBFrame(const AVFrame* frame, const VicConfig& config);
+ void WriteRGBFrame(std::unique_ptr<FFmpeg::Frame> frame, const VicConfig& config);
- void WriteYUVFrame(const AVFrame* frame, const VicConfig& config);
+ void WriteYUVFrame(std::unique_ptr<FFmpeg::Frame> frame, const VicConfig& config);
Host1x& host1x;
std::shared_ptr<Tegra::Host1x::Nvdec> nvdec_processor;
diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h
index 78b42b518..efa9adf7a 100644
--- a/src/video_core/query_cache/query_cache.h
+++ b/src/video_core/query_cache/query_cache.h
@@ -266,7 +266,7 @@ void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type
return;
}
if (False(query_base->flags & QueryFlagBits::IsFinalValueSynced)) [[unlikely]] {
- UNREACHABLE();
+ ASSERT(false);
return;
}
query_base->value += streamer->GetAmmendValue();
diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp
index 65cd5aa06..4f1d5b548 100644
--- a/src/video_core/renderer_null/null_rasterizer.cpp
+++ b/src/video_core/renderer_null/null_rasterizer.cpp
@@ -3,6 +3,7 @@
#include "common/alignment.h"
#include "core/memory.h"
+#include "video_core/control/channel_state.h"
#include "video_core/host1x/host1x.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_null/null_rasterizer.h"
@@ -99,8 +100,14 @@ bool RasterizerNull::AccelerateDisplay(const Tegra::FramebufferConfig& config,
}
void RasterizerNull::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {}
-void RasterizerNull::InitializeChannel(Tegra::Control::ChannelState& channel) {}
-void RasterizerNull::BindChannel(Tegra::Control::ChannelState& channel) {}
-void RasterizerNull::ReleaseChannel(s32 channel_id) {}
+void RasterizerNull::InitializeChannel(Tegra::Control::ChannelState& channel) {
+ CreateChannel(channel);
+}
+void RasterizerNull::BindChannel(Tegra::Control::ChannelState& channel) {
+ BindToChannel(channel.bind_id);
+}
+void RasterizerNull::ReleaseChannel(s32 channel_id) {
+ EraseChannel(channel_id);
+}
} // namespace Null
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index d1284e62f..ed188b435 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -182,13 +182,14 @@ void BufferCacheRuntime::CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
}
void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
- std::span<const VideoCommon::BufferCopy> copies, bool barrier) {
+ std::span<const VideoCommon::BufferCopy> copies, bool barrier,
+ bool) {
CopyBuffer(dst_buffer.Handle(), src_buffer, copies, barrier);
}
void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
- std::span<const VideoCommon::BufferCopy> copies) {
- CopyBuffer(dst_buffer.Handle(), src_buffer.Handle(), copies);
+ std::span<const VideoCommon::BufferCopy> copies, bool) {
+ CopyBuffer(dst_buffer.Handle(), src_buffer.Handle(), copies, true);
}
void BufferCacheRuntime::PreCopyBarrier() {
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 1b87954d7..1e8708f59 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -30,6 +30,8 @@ public:
void MakeResident(GLenum access) noexcept;
+ void MarkUsage(u64 offset, u64 size) {}
+
[[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
[[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept {
@@ -68,22 +70,29 @@ public:
void FreeDeferredStagingBuffer(StagingBufferMap& buffer);
+ bool CanReorderUpload(const Buffer&, std::span<const VideoCommon::BufferCopy>) {
+ return false;
+ }
+
void CopyBuffer(GLuint dst_buffer, GLuint src_buffer,
- std::span<const VideoCommon::BufferCopy> copies, bool barrier = true);
+ std::span<const VideoCommon::BufferCopy> copies, bool barrier);
void CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
- std::span<const VideoCommon::BufferCopy> copies, bool barrier = true);
+ std::span<const VideoCommon::BufferCopy> copies, bool barrier);
void CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
- std::span<const VideoCommon::BufferCopy> copies, bool barrier = true);
+ std::span<const VideoCommon::BufferCopy> copies, bool barrier,
+ bool can_reorder_upload = false);
void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
- std::span<const VideoCommon::BufferCopy> copies);
+ std::span<const VideoCommon::BufferCopy> copies, bool);
void PreCopyBarrier();
void PostCopyBarrier();
void Finish();
+ void TickFrame(VideoCommon::SlotVector<Buffer>&) noexcept {}
+
void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value);
void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
@@ -184,6 +193,10 @@ public:
return device.CanReportMemoryUsage();
}
+ u32 GetStorageBufferAlignment() const {
+ return static_cast<u32>(device.GetShaderStorageBufferAlignment());
+ }
+
private:
static constexpr std::array PABO_LUT{
GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 94258ccd0..993438a27 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -265,33 +265,33 @@ std::string Device::GetVendorName() const {
if (vendor_name == "Intel") {
// For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris.
// Simply return `INTEL` for those as well as the Windows driver.
- return "INTEL";
+ return "Intel";
}
if (vendor_name == "Intel Open Source Technology Center") {
- return "I965";
+ return "i965";
}
if (vendor_name == "Mesa Project") {
- return "I915";
+ return "i915";
}
if (vendor_name == "Mesa/X.org") {
// This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return
// MESA instead of one of those driver names.
- return "MESA";
+ return "Mesa";
}
if (vendor_name == "AMD") {
- return "RADEONSI";
+ return "RadeonSI";
}
if (vendor_name == "nouveau") {
- return "NOUVEAU";
+ return "Nouveau";
}
if (vendor_name == "X.Org") {
return "R600";
}
if (vendor_name == "Collabora Ltd") {
- return "ZINK";
+ return "Zink";
}
if (vendor_name == "Intel Corporation") {
- return "OPENSWR";
+ return "OpenSWR";
}
if (vendor_name == "Microsoft Corporation") {
return "D3D12";
@@ -300,7 +300,7 @@ std::string Device::GetVendorName() const {
// Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default
// strategy would have returned `NVIDIA` here for this driver, the same result as the
// proprietary driver.
- return "TEGRA";
+ return "Tegra";
}
return vendor_name;
}
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index 44a771d65..af0a453ee 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -559,7 +559,9 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
}
void GraphicsPipeline::ConfigureTransformFeedbackImpl() const {
- glTransformFeedbackAttribsNV(num_xfb_attribs, xfb_attribs.data(), GL_SEPARATE_ATTRIBS);
+ const GLenum buffer_mode =
+ num_xfb_buffers_active == 1 ? GL_INTERLEAVED_ATTRIBS : GL_SEPARATE_ATTRIBS;
+ glTransformFeedbackAttribsNV(num_xfb_attribs, xfb_attribs.data(), buffer_mode);
}
void GraphicsPipeline::GenerateTransformFeedbackState() {
@@ -567,12 +569,14 @@ void GraphicsPipeline::GenerateTransformFeedbackState() {
// when this is required.
GLint* cursor{xfb_attribs.data()};
+ num_xfb_buffers_active = 0;
for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) {
const auto& layout = key.xfb_state.layouts[feedback];
UNIMPLEMENTED_IF_MSG(layout.stride != layout.varying_count * 4, "Stride padding");
if (layout.varying_count == 0) {
continue;
}
+ num_xfb_buffers_active++;
const auto& locations = key.xfb_state.varyings[feedback];
std::optional<u32> current_index;
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
index 74fc9cc3d..2f70c1ae9 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h
@@ -154,6 +154,7 @@ private:
static constexpr std::size_t XFB_ENTRY_STRIDE = 3;
GLsizei num_xfb_attribs{};
+ u32 num_xfb_buffers_active{};
std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{};
std::mutex built_mutex;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 27e2de1bf..9995b6dd4 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -555,7 +555,7 @@ void RasterizerOpenGL::OnCacheInvalidation(VAddr addr, u64 size) {
}
{
std::scoped_lock lock{buffer_cache.mutex};
- buffer_cache.CachedWriteMemory(addr, size);
+ buffer_cache.WriteMemory(addr, size);
}
shader_cache.InvalidateRegion(addr, size);
}
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 2888e0238..26f2d0ea7 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -232,6 +232,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
.has_gl_bool_ref_bug = device.HasBoolRefBug(),
.ignore_nan_fp_comparisons = true,
.gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
+ .min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
},
host_info{
.support_float64 = true,
@@ -240,6 +241,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
.needs_demote_reorder = device.IsAmd(),
.support_snorm_render_buffer = false,
.support_viewport_index_layer = device.HasVertexViewportLayer(),
+ .min_ssbo_alignment = static_cast<u32>(device.GetShaderStorageBufferAlignment()),
.support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
.support_conditional_barrier = device.SupportsConditionalBarriers(),
} {
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 7e7a80740..c4c30d807 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -132,16 +132,12 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
const bool use_accelerated =
rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
const bool is_srgb = use_accelerated && screen_info.is_srgb;
+ RenderScreenshot(*framebuffer, use_accelerated);
- {
- std::scoped_lock lock{rasterizer.LockCaches()};
- RenderScreenshot(*framebuffer, use_accelerated);
-
- Frame* frame = present_manager.GetRenderFrame();
- blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb);
- scheduler.Flush(*frame->render_ready);
- present_manager.Present(frame);
- }
+ Frame* frame = present_manager.GetRenderFrame();
+ blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb);
+ scheduler.Flush(*frame->render_ready);
+ present_manager.Present(frame);
gpu.RendererFrameEndNotify();
rasterizer.TickFrame();
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 52fc142d1..66483a900 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -137,6 +137,56 @@ BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWin
BlitScreen::~BlitScreen() = default;
+static Common::Rectangle<f32> NormalizeCrop(const Tegra::FramebufferConfig& framebuffer,
+ const ScreenInfo& screen_info) {
+ f32 left, top, right, bottom;
+
+ if (!framebuffer.crop_rect.IsEmpty()) {
+ // If crop rectangle is not empty, apply properties from rectangle.
+ left = static_cast<f32>(framebuffer.crop_rect.left);
+ top = static_cast<f32>(framebuffer.crop_rect.top);
+ right = static_cast<f32>(framebuffer.crop_rect.right);
+ bottom = static_cast<f32>(framebuffer.crop_rect.bottom);
+ } else {
+ // Otherwise, fall back to framebuffer dimensions.
+ left = 0;
+ top = 0;
+ right = static_cast<f32>(framebuffer.width);
+ bottom = static_cast<f32>(framebuffer.height);
+ }
+
+ // Apply transformation flags.
+ auto framebuffer_transform_flags = framebuffer.transform_flags;
+
+ if (True(framebuffer_transform_flags & Service::android::BufferTransformFlags::FlipH)) {
+ // Switch left and right.
+ std::swap(left, right);
+ }
+ if (True(framebuffer_transform_flags & Service::android::BufferTransformFlags::FlipV)) {
+ // Switch top and bottom.
+ std::swap(top, bottom);
+ }
+
+ framebuffer_transform_flags &= ~Service::android::BufferTransformFlags::FlipH;
+ framebuffer_transform_flags &= ~Service::android::BufferTransformFlags::FlipV;
+ if (True(framebuffer_transform_flags)) {
+ UNIMPLEMENTED_MSG("Unsupported framebuffer_transform_flags={}",
+ static_cast<u32>(framebuffer_transform_flags));
+ }
+
+ // Get the screen properties.
+ const f32 screen_width = static_cast<f32>(screen_info.width);
+ const f32 screen_height = static_cast<f32>(screen_info.height);
+
+ // Normalize coordinate space.
+ left /= screen_width;
+ top /= screen_height;
+ right /= screen_width;
+ bottom /= screen_height;
+
+ return Common::Rectangle<f32>(left, top, right, bottom);
+}
+
void BlitScreen::Recreate() {
present_manager.WaitPresent();
scheduler.Finish();
@@ -354,17 +404,10 @@ void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
source_image_view = smaa->Draw(scheduler, image_index, source_image, source_image_view);
}
if (fsr) {
- auto crop_rect = framebuffer.crop_rect;
- if (crop_rect.GetWidth() == 0) {
- crop_rect.right = framebuffer.width;
- }
- if (crop_rect.GetHeight() == 0) {
- crop_rect.bottom = framebuffer.height;
- }
- crop_rect = crop_rect.Scale(Settings::values.resolution_info.up_factor);
- VkExtent2D fsr_input_size{
- .width = Settings::values.resolution_info.ScaleUp(framebuffer.width),
- .height = Settings::values.resolution_info.ScaleUp(framebuffer.height),
+ const auto crop_rect = NormalizeCrop(framebuffer, screen_info);
+ const VkExtent2D fsr_input_size{
+ .width = Settings::values.resolution_info.ScaleUp(screen_info.width),
+ .height = Settings::values.resolution_info.ScaleUp(screen_info.height),
};
VkImageView fsr_image_view =
fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect);
@@ -1397,61 +1440,37 @@ void BlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayou
void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
const Layout::FramebufferLayout layout) const {
- const auto& framebuffer_transform_flags = framebuffer.transform_flags;
- const auto& framebuffer_crop_rect = framebuffer.crop_rect;
-
- static constexpr Common::Rectangle<f32> texcoords{0.f, 0.f, 1.f, 1.f};
- auto left = texcoords.left;
- auto right = texcoords.right;
-
- switch (framebuffer_transform_flags) {
- case Service::android::BufferTransformFlags::Unset:
- break;
- case Service::android::BufferTransformFlags::FlipV:
- // Flip the framebuffer vertically
- left = texcoords.right;
- right = texcoords.left;
- break;
- default:
- UNIMPLEMENTED_MSG("Unsupported framebuffer_transform_flags={}",
- static_cast<u32>(framebuffer_transform_flags));
- break;
- }
+ f32 left, top, right, bottom;
- UNIMPLEMENTED_IF(framebuffer_crop_rect.left != 0);
-
- f32 left_start{};
- if (framebuffer_crop_rect.Top() > 0) {
- left_start = static_cast<f32>(framebuffer_crop_rect.Top()) /
- static_cast<f32>(framebuffer_crop_rect.Bottom());
- }
- f32 scale_u = static_cast<f32>(framebuffer.width) / static_cast<f32>(screen_info.width);
- f32 scale_v = static_cast<f32>(framebuffer.height) / static_cast<f32>(screen_info.height);
- // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering
- // (e.g. handheld mode) on a 1920x1080 framebuffer.
- if (!fsr) {
- if (framebuffer_crop_rect.GetWidth() > 0) {
- scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) /
- static_cast<f32>(screen_info.width);
- }
- if (framebuffer_crop_rect.GetHeight() > 0) {
- scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) /
- static_cast<f32>(screen_info.height);
- }
+ if (fsr) {
+ // FSR has already applied the crop, so we just want to render the image
+ // it has produced.
+ left = 0;
+ top = 0;
+ right = 1;
+ bottom = 1;
+ } else {
+ // Get the normalized crop rectangle.
+ const auto crop = NormalizeCrop(framebuffer, screen_info);
+
+ // Apply the crop.
+ left = crop.left;
+ top = crop.top;
+ right = crop.right;
+ bottom = crop.bottom;
}
+ // Map the coordinates to the screen.
const auto& screen = layout.screen;
const auto x = static_cast<f32>(screen.left);
const auto y = static_cast<f32>(screen.top);
const auto w = static_cast<f32>(screen.GetWidth());
const auto h = static_cast<f32>(screen.GetHeight());
- data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left_start + left * scale_v);
- data.vertices[1] =
- ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left_start + left * scale_v);
- data.vertices[2] =
- ScreenRectVertex(x, y + h, texcoords.top * scale_u, left_start + right * scale_v);
- data.vertices[3] =
- ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, left_start + right * scale_v);
+
+ data.vertices[0] = ScreenRectVertex(x, y, left, top);
+ data.vertices[1] = ScreenRectVertex(x + w, y, right, top);
+ data.vertices[2] = ScreenRectVertex(x, y + h, left, bottom);
+ data.vertices[3] = ScreenRectVertex(x + w, y + h, right, bottom);
}
void BlitScreen::CreateSMAA(VkExtent2D smaa_size) {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index d8148e89a..5958f52f7 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -79,13 +79,13 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
} // Anonymous namespace
Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
- : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {}
+ : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params), tracker{4096} {}
Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
VAddr cpu_addr_, u64 size_bytes_)
: VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_),
- device{&runtime.device}, buffer{
- CreateBuffer(*device, runtime.memory_allocator, SizeBytes())} {
+ device{&runtime.device}, buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())},
+ tracker{SizeBytes()} {
if (runtime.device.HasDebuggingToolAttached()) {
buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
}
@@ -355,12 +355,35 @@ bool BufferCacheRuntime::CanReportMemoryUsage() const {
return device.CanReportMemoryUsage();
}
+u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
+ return static_cast<u32>(device.GetStorageBufferAlignment());
+}
+
+void BufferCacheRuntime::TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept {
+ for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
+ it->ResetUsageTracking();
+ }
+}
+
void BufferCacheRuntime::Finish() {
scheduler.Finish();
}
+bool BufferCacheRuntime::CanReorderUpload(const Buffer& buffer,
+ std::span<const VideoCommon::BufferCopy> copies) {
+ if (Settings::values.disable_buffer_reorder) {
+ return false;
+ }
+ const bool can_use_upload_cmdbuf =
+ std::ranges::all_of(copies, [&](const VideoCommon::BufferCopy& copy) {
+ return !buffer.IsRegionUsed(copy.dst_offset, copy.size);
+ });
+ return can_use_upload_cmdbuf;
+}
+
void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
- std::span<const VideoCommon::BufferCopy> copies, bool barrier) {
+ std::span<const VideoCommon::BufferCopy> copies, bool barrier,
+ bool can_reorder_upload) {
if (dst_buffer == VK_NULL_HANDLE || src_buffer == VK_NULL_HANDLE) {
return;
}
@@ -376,9 +399,18 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
+
// Measuring a popular game, this number never exceeds the specified size once data is warmed up
boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size());
std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy);
+ if (src_buffer == staging_pool.StreamBuf() && can_reorder_upload) {
+ scheduler.RecordWithUploadBuffer([src_buffer, dst_buffer, vk_copies](
+ vk::CommandBuffer, vk::CommandBuffer upload_cmdbuf) {
+ upload_cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies);
+ });
+ return;
+ }
+
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) {
if (barrier) {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 95446c732..0b3fbd6d0 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -5,6 +5,7 @@
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/buffer_cache/memory_tracker_base.h"
+#include "video_core/buffer_cache/usage_tracker.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -34,6 +35,18 @@ public:
return *buffer;
}
+ [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept {
+ return tracker.IsUsed(offset, size);
+ }
+
+ void MarkUsage(u64 offset, u64 size) noexcept {
+ tracker.Track(offset, size);
+ }
+
+ void ResetUsageTracking() noexcept {
+ tracker.Reset();
+ }
+
operator VkBuffer() const noexcept {
return *buffer;
}
@@ -49,6 +62,7 @@ private:
const Device* device{};
vk::Buffer buffer;
std::vector<BufferView> views;
+ VideoCommon::UsageTracker tracker;
};
class QuadArrayIndexBuffer;
@@ -67,6 +81,8 @@ public:
ComputePassDescriptorQueue& compute_pass_descriptor_queue,
DescriptorPool& descriptor_pool);
+ void TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept;
+
void Finish();
u64 GetDeviceLocalMemory() const;
@@ -75,16 +91,21 @@ public:
bool CanReportMemoryUsage() const;
+ u32 GetStorageBufferAlignment() const;
+
[[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
[[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false);
+ bool CanReorderUpload(const Buffer& buffer, std::span<const VideoCommon::BufferCopy> copies);
+
void FreeDeferredStagingBuffer(StagingBufferRef& ref);
void PreCopyBarrier();
void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
- std::span<const VideoCommon::BufferCopy> copies, bool barrier = true);
+ std::span<const VideoCommon::BufferCopy> copies, bool barrier,
+ bool can_reorder_upload = false);
void PostCopyBarrier();
diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp
index ce8f3f3c2..f7a05fbc0 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.cpp
+++ b/src/video_core/renderer_vulkan/vk_fsr.cpp
@@ -34,7 +34,7 @@ FSR::FSR(const Device& device_, MemoryAllocator& memory_allocator_, size_t image
}
VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view,
- VkExtent2D input_image_extent, const Common::Rectangle<int>& crop_rect) {
+ VkExtent2D input_image_extent, const Common::Rectangle<f32>& crop_rect) {
UpdateDescriptorSet(image_index, image_view);
@@ -61,15 +61,21 @@ VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImageView imag
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *easu_pipeline);
+ const f32 input_image_width = static_cast<f32>(input_image_extent.width);
+ const f32 input_image_height = static_cast<f32>(input_image_extent.height);
+ const f32 output_image_width = static_cast<f32>(output_size.width);
+ const f32 output_image_height = static_cast<f32>(output_size.height);
+ const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width;
+ const f32 viewport_x = crop_rect.left * input_image_width;
+ const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
+ const f32 viewport_y = crop_rect.top * input_image_height;
+
std::array<u32, 4 * 4> push_constants;
- FsrEasuConOffset(
- push_constants.data() + 0, push_constants.data() + 4, push_constants.data() + 8,
- push_constants.data() + 12,
-
- static_cast<f32>(crop_rect.GetWidth()), static_cast<f32>(crop_rect.GetHeight()),
- static_cast<f32>(input_image_extent.width), static_cast<f32>(input_image_extent.height),
- static_cast<f32>(output_size.width), static_cast<f32>(output_size.height),
- static_cast<f32>(crop_rect.left), static_cast<f32>(crop_rect.top));
+ FsrEasuConOffset(push_constants.data() + 0, push_constants.data() + 4,
+ push_constants.data() + 8, push_constants.data() + 12,
+
+ viewport_width, viewport_height, input_image_width, input_image_height,
+ output_image_width, output_image_height, viewport_x, viewport_y);
cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, push_constants);
{
diff --git a/src/video_core/renderer_vulkan/vk_fsr.h b/src/video_core/renderer_vulkan/vk_fsr.h
index 8bb9fc23a..3505c1416 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.h
+++ b/src/video_core/renderer_vulkan/vk_fsr.h
@@ -17,7 +17,7 @@ public:
explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count,
VkExtent2D output_size);
VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view,
- VkExtent2D input_image_extent, const Common::Rectangle<int>& crop_rect);
+ VkExtent2D input_image_extent, const Common::Rectangle<f32>& crop_rect);
private:
void CreateDescriptorPool();
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
index 6b288b994..ac8b6e838 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -100,12 +100,14 @@ void MasterSemaphore::Wait(u64 tick) {
Refresh();
}
-VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
- VkSemaphore wait_semaphore, u64 host_tick) {
+VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
+ VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
+ u64 host_tick) {
if (semaphore) {
- return SubmitQueueTimeline(cmdbuf, signal_semaphore, wait_semaphore, host_tick);
+ return SubmitQueueTimeline(cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore,
+ host_tick);
} else {
- return SubmitQueueFence(cmdbuf, signal_semaphore, wait_semaphore, host_tick);
+ return SubmitQueueFence(cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, host_tick);
}
}
@@ -115,6 +117,7 @@ static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
};
VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
+ vk::CommandBuffer& upload_cmdbuf,
VkSemaphore signal_semaphore,
VkSemaphore wait_semaphore, u64 host_tick) {
const VkSemaphore timeline_semaphore = *semaphore;
@@ -123,6 +126,8 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
const std::array signal_values{host_tick, u64(0)};
const std::array signal_semaphores{timeline_semaphore, signal_semaphore};
+ const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
+
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
const VkTimelineSemaphoreSubmitInfo timeline_si{
.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
@@ -138,8 +143,8 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
.waitSemaphoreCount = num_wait_semaphores,
.pWaitSemaphores = &wait_semaphore,
.pWaitDstStageMask = wait_stage_masks.data(),
- .commandBufferCount = 1,
- .pCommandBuffers = cmdbuf.address(),
+ .commandBufferCount = static_cast<u32>(cmdbuffers.size()),
+ .pCommandBuffers = cmdbuffers.data(),
.signalSemaphoreCount = num_signal_semaphores,
.pSignalSemaphores = signal_semaphores.data(),
};
@@ -147,19 +152,23 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
return device.GetGraphicsQueue().Submit(submit_info);
}
-VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
- VkSemaphore wait_semaphore, u64 host_tick) {
+VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf,
+ vk::CommandBuffer& upload_cmdbuf,
+ VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
+ u64 host_tick) {
const u32 num_signal_semaphores = signal_semaphore ? 1 : 0;
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
+ const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
+
const VkSubmitInfo submit_info{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr,
.waitSemaphoreCount = num_wait_semaphores,
.pWaitSemaphores = &wait_semaphore,
.pWaitDstStageMask = wait_stage_masks.data(),
- .commandBufferCount = 1,
- .pCommandBuffers = cmdbuf.address(),
+ .commandBufferCount = static_cast<u32>(cmdbuffers.size()),
+ .pCommandBuffers = cmdbuffers.data(),
.signalSemaphoreCount = num_signal_semaphores,
.pSignalSemaphores = &signal_semaphore,
};
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
index 3f599d7bd..7dfb93ffb 100644
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.h
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -52,14 +52,16 @@ public:
void Wait(u64 tick);
/// Submits the device graphics queue, updating the tick as necessary
- VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
- VkSemaphore wait_semaphore, u64 host_tick);
+ VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
+ VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, u64 host_tick);
private:
- VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
- VkSemaphore wait_semaphore, u64 host_tick);
- VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
- VkSemaphore wait_semaphore, u64 host_tick);
+ VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
+ VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
+ u64 host_tick);
+ VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf,
+ VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
+ u64 host_tick);
void WaitThread(std::stop_token token);
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 804b95989..2a13b2a72 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -263,6 +263,22 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
info.y_negate = key.state.y_negate != 0;
return info;
}
+
+size_t GetTotalPipelineWorkers() {
+ const size_t max_core_threads =
+ std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL;
+#ifdef ANDROID
+ // Leave at least a few cores free in android
+ constexpr size_t free_cores = 3ULL;
+ if (max_core_threads <= free_cores) {
+ return 1ULL;
+ }
+ return max_core_threads - free_cores;
+#else
+ return max_core_threads;
+#endif
+}
+
} // Anonymous namespace
size_t ComputePipelineCacheKey::Hash() const noexcept {
@@ -294,11 +310,8 @@ 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()},
-#ifdef ANDROID
- workers(1, "VkPipelineBuilder"),
-#else
- workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"),
-#endif
+ workers(device.HasBrokenParallelShaderCompiling() ? 1ULL : GetTotalPipelineWorkers(),
+ "VkPipelineBuilder"),
serialization_thread(1, "VkPipelineSerialization") {
const auto& float_control{device.FloatControlProperties()};
const VkDriverId driver_id{device.GetDriverID()};
@@ -338,6 +351,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
.support_native_ndc = device.IsExtDepthClipControlSupported(),
.support_scaled_attributes = !device.MustEmulateScaledFormats(),
+ .support_multi_viewport = device.SupportsMultiViewport(),
.warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(),
@@ -358,7 +372,8 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
.has_broken_spirv_subgroup_mask_vector_extract_dynamic =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.has_broken_robust =
- device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Maxwell,
+ device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
+ .min_ssbo_alignment = device.GetStorageBufferAlignment(),
};
host_info = Shader::HostTranslateInfo{
@@ -369,6 +384,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE,
.support_snorm_render_buffer = true,
.support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
+ .min_ssbo_alignment = static_cast<u32>(device.GetStorageBufferAlignment()),
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
.support_conditional_barrier = device.SupportsConditionalBarriers(),
};
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 66c03bf17..078777cdd 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -211,6 +211,13 @@ public:
return;
}
PauseCounter();
+ const auto driver_id = device.GetDriverID();
+ if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
+ driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) {
+ pending_sync.clear();
+ sync_values_stash.clear();
+ return;
+ }
sync_values_stash.clear();
sync_values_stash.emplace_back();
std::vector<HostSyncValues>* sync_values = &sync_values_stash.back();
@@ -1378,6 +1385,12 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku
return true;
}
+ auto driver_id = impl->device.GetDriverID();
+ if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
+ driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) {
+ return true;
+ }
+
for (size_t i = 0; i < 2; i++) {
is_null[i] = !is_in_ac[i] && check_value(objects[i]->address);
}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 465eac37e..07222e603 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -13,6 +13,7 @@
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/settings.h"
+#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/control/channel_state.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/engines/kepler_compute.h"
@@ -81,7 +82,7 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in
}
if (y_negate) {
- y += height;
+ y += conv(static_cast<f32>(regs.surface_clip.height));
height = -height;
}
@@ -198,7 +199,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
if (!pipeline) {
return;
}
- std::scoped_lock lock{LockCaches()};
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
// update engine as channel may be different.
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
@@ -285,6 +286,7 @@ void RasterizerVulkan::DrawTexture() {
query_cache.NotifySegment(true);
+ std::scoped_lock l{texture_cache.mutex};
texture_cache.SynchronizeGraphicsDescriptors();
texture_cache.UpdateRenderTargets(false);
@@ -619,7 +621,7 @@ void RasterizerVulkan::OnCacheInvalidation(VAddr addr, u64 size) {
}
{
std::scoped_lock lock{buffer_cache.mutex};
- buffer_cache.CachedWriteMemory(addr, size);
+ buffer_cache.WriteMemory(addr, size);
}
pipeline_cache.InvalidateRegion(addr, size);
}
@@ -708,7 +710,6 @@ void RasterizerVulkan::TiledCacheBarrier() {
}
void RasterizerVulkan::FlushCommands() {
- std::scoped_lock lock{LockCaches()};
if (draw_counter == 0) {
return;
}
@@ -806,7 +807,6 @@ void RasterizerVulkan::FlushWork() {
if ((++draw_counter & 7) != 7) {
return;
}
- std::scoped_lock lock{LockCaches()};
if (draw_counter < DRAWS_TO_DISPATCH) {
// Send recorded tasks to the worker thread
scheduler.DispatchWork();
@@ -892,10 +892,6 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateFrontFace(regs);
UpdateStencilOp(regs);
- if (device.IsExtVertexInputDynamicStateSupported()) {
- UpdateVertexInput(regs);
- }
-
if (state_tracker.TouchStateEnable()) {
UpdateDepthBoundsTestEnable(regs);
UpdateDepthTestEnable(regs);
@@ -918,12 +914,19 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateBlending(regs);
}
}
+ if (device.IsExtVertexInputDynamicStateSupported()) {
+ UpdateVertexInput(regs);
+ }
}
void RasterizerVulkan::HandleTransformFeedback() {
+ static std::once_flag warn_unsupported;
+
const auto& regs = maxwell3d->regs;
if (!device.IsExtTransformFeedbackSupported()) {
- LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported");
+ std::call_once(warn_unsupported, [&] {
+ LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported");
+ });
return;
}
query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount,
@@ -1501,7 +1504,7 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel) {
CreateChannel(channel);
{
- std::scoped_lock lock{LockCaches()};
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
texture_cache.CreateChannel(channel);
buffer_cache.CreateChannel(channel);
}
@@ -1514,7 +1517,7 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) {
const s32 channel_id = channel.bind_id;
BindToChannel(channel_id);
{
- std::scoped_lock lock{LockCaches()};
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
texture_cache.BindToChannel(channel_id);
buffer_cache.BindToChannel(channel_id);
}
@@ -1527,7 +1530,7 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) {
void RasterizerVulkan::ReleaseChannel(s32 channel_id) {
EraseChannel(channel_id);
{
- std::scoped_lock lock{LockCaches()};
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
texture_cache.EraseChannel(channel_id);
buffer_cache.EraseChannel(channel_id);
}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index ce3dfbaab..ad069556c 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -133,10 +133,6 @@ public:
void ReleaseChannel(s32 channel_id) override;
- std::scoped_lock<std::recursive_mutex, std::recursive_mutex> LockCaches() {
- return std::scoped_lock{buffer_cache.mutex, texture_cache.mutex};
- }
-
private:
static constexpr size_t MAX_TEXTURES = 192;
static constexpr size_t MAX_IMAGES = 48;
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 3be7837f4..146923db4 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -22,11 +22,12 @@ namespace Vulkan {
MICROPROFILE_DECLARE(Vulkan_WaitForWorker);
-void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
+void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf,
+ vk::CommandBuffer upload_cmdbuf) {
auto command = first;
while (command != nullptr) {
auto next = command->GetNext();
- command->Execute(cmdbuf);
+ command->Execute(cmdbuf, upload_cmdbuf);
command->~Command();
command = next;
}
@@ -180,7 +181,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) {
// Perform the work, tracking whether the chunk was a submission
// before executing.
const bool has_submit = work->HasSubmit();
- work->ExecuteAll(current_cmdbuf);
+ work->ExecuteAll(current_cmdbuf, current_upload_cmdbuf);
// If the chunk was a submission, reallocate the command buffer.
if (has_submit) {
@@ -205,6 +206,13 @@ void Scheduler::AllocateWorkerCommandBuffer() {
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
.pInheritanceInfo = nullptr,
});
+ current_upload_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
+ current_upload_cmdbuf.Begin({
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .pNext = nullptr,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ .pInheritanceInfo = nullptr,
+ });
}
u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
@@ -212,7 +220,17 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
InvalidateState();
const u64 signal_value = master_semaphore->NextTick();
- Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) {
+ RecordWithUploadBuffer([signal_semaphore, wait_semaphore, signal_value,
+ this](vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) {
+ 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,
+ };
+ upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
+ upload_cmdbuf.End();
cmdbuf.End();
if (on_submit) {
@@ -221,7 +239,7 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
std::scoped_lock lock{submit_mutex};
switch (const VkResult result = master_semaphore->SubmitQueue(
- cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {
+ cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {
case VK_SUCCESS:
break;
case VK_ERROR_DEVICE_LOST:
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index da03803aa..f8d8ca80a 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -80,7 +80,8 @@ public:
/// Send work to a separate thread.
template <typename T>
- void Record(T&& command) {
+ requires std::is_invocable_v<T, vk::CommandBuffer, vk::CommandBuffer>
+ void RecordWithUploadBuffer(T&& command) {
if (chunk->Record(command)) {
return;
}
@@ -88,6 +89,15 @@ public:
(void)chunk->Record(command);
}
+ template <typename T>
+ requires std::is_invocable_v<T, vk::CommandBuffer>
+ void Record(T&& c) {
+ this->RecordWithUploadBuffer(
+ [command = std::move(c)](vk::CommandBuffer cmdbuf, vk::CommandBuffer) {
+ command(cmdbuf);
+ });
+ }
+
/// Returns the current command buffer tick.
[[nodiscard]] u64 CurrentTick() const noexcept {
return master_semaphore->CurrentTick();
@@ -119,7 +129,7 @@ private:
public:
virtual ~Command() = default;
- virtual void Execute(vk::CommandBuffer cmdbuf) const = 0;
+ virtual void Execute(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) const = 0;
Command* GetNext() const {
return next;
@@ -142,8 +152,8 @@ private:
TypedCommand(TypedCommand&&) = delete;
TypedCommand& operator=(TypedCommand&&) = delete;
- void Execute(vk::CommandBuffer cmdbuf) const override {
- command(cmdbuf);
+ void Execute(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) const override {
+ command(cmdbuf, upload_cmdbuf);
}
private:
@@ -152,7 +162,7 @@ private:
class CommandChunk final {
public:
- void ExecuteAll(vk::CommandBuffer cmdbuf);
+ void ExecuteAll(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf);
template <typename T>
bool Record(T& command) {
@@ -228,6 +238,7 @@ private:
VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr;
vk::CommandBuffer current_cmdbuf;
+ vk::CommandBuffer current_upload_cmdbuf;
std::unique_ptr<CommandChunk> chunk;
std::function<void()> on_submit;
diff --git a/src/video_core/renderer_vulkan/vk_smaa.cpp b/src/video_core/renderer_vulkan/vk_smaa.cpp
index 5efd7d66e..70644ea82 100644
--- a/src/video_core/renderer_vulkan/vk_smaa.cpp
+++ b/src/video_core/renderer_vulkan/vk_smaa.cpp
@@ -672,7 +672,7 @@ void SMAA::UploadImages(Scheduler& scheduler) {
UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent,
VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes));
- scheduler.Record([&](vk::CommandBuffer& cmdbuf) {
+ scheduler.Record([&](vk::CommandBuffer cmdbuf) {
for (auto& images : m_dynamic_images) {
for (size_t i = 0; i < MaxDynamicImage; i++) {
ClearColorImage(cmdbuf, *images.images[i]);
@@ -707,7 +707,7 @@ VkImageView SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage source_
UpdateDescriptorSets(source_image_view, image_index);
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([=, this](vk::CommandBuffer& cmdbuf) {
+ scheduler.Record([=, this](vk::CommandBuffer cmdbuf) {
TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL);
TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL);
BeginRenderPass(cmdbuf, m_renderpasses[EdgeDetection], edge_detection_framebuffer,
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 d3deb9072..f63a20327 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -36,6 +36,10 @@ public:
StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false);
void FreeDeferred(StagingBufferRef& ref);
+ [[nodiscard]] VkBuffer StreamBuf() const noexcept {
+ return *stream_buffer;
+ }
+
void TickFrame();
private:
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index d56558a83..daaea2979 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -190,7 +190,7 @@ void SetupDirtySpecialOps(Tables& tables) {
void SetupDirtyViewportSwizzles(Tables& tables) {
static constexpr size_t swizzle_offset = 6;
for (size_t index = 0; index < Regs::NumViewports; ++index) {
- tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
+ tables[1][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
ViewportSwizzles;
}
}
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 81ef98f61..821f44f1a 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -147,6 +147,9 @@ bool Swapchain::AcquireNextImage() {
case VK_ERROR_OUT_OF_DATE_KHR:
is_outdated = true;
break;
+ case VK_ERROR_SURFACE_LOST_KHR:
+ vk::Check(result);
+ break;
default:
LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result));
break;
@@ -180,6 +183,9 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
case VK_ERROR_OUT_OF_DATE_KHR:
is_outdated = true;
break;
+ case VK_ERROR_SURFACE_LOST_KHR:
+ vk::Check(result);
+ break;
default:
LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", vk::ToString(result));
break;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index de34f6d49..5dbec2e62 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1785,8 +1785,22 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
: VideoCommon::ImageViewBase{info, view_info, gpu_addr_},
buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
-ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params)
- : VideoCommon::ImageViewBase{params} {}
+ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params)
+ : VideoCommon::ImageViewBase{params}, device{&runtime.device} {
+ if (device->HasNullDescriptor()) {
+ return;
+ }
+
+ // Handle fallback for devices without nullDescriptor
+ ImageInfo info{};
+ info.format = PixelFormat::A8B8G8R8_UNORM;
+
+ null_image = MakeImage(*device, runtime.memory_allocator, info, {});
+ image_handle = *null_image;
+ for (u32 i = 0; i < Shader::NUM_TEXTURE_TYPES; i++) {
+ image_views[i] = MakeView(VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_ASPECT_COLOR_BIT);
+ }
+}
ImageView::~ImageView() = default;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 7a0807709..edf5d7635 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -267,6 +267,7 @@ private:
vk::ImageView depth_view;
vk::ImageView stencil_view;
vk::ImageView color_view;
+ vk::Image null_image;
VkImage image_handle = VK_NULL_HANDLE;
VkImageView render_target = VK_NULL_HANDLE;
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h
index 9df6a2903..3ffa2a661 100644
--- a/src/video_core/texture_cache/slot_vector.h
+++ b/src/video_core/texture_cache/slot_vector.h
@@ -138,6 +138,10 @@ public:
return Iterator(this, SlotId{SlotId::INVALID_INDEX});
}
+ [[nodiscard]] size_t size() const noexcept {
+ return values_capacity - free_list.size();
+ }
+
private:
struct NonTrivialDummy {
NonTrivialDummy() noexcept {}
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index e518756d2..1fda0042d 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -519,10 +519,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
-
- LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2");
- RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
- VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
}
if (is_nvidia) {
@@ -611,17 +607,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
}
if (extensions.vertex_input_dynamic_state && is_qualcomm) {
- const u32 version = (properties.properties.driverVersion << 3) >> 3;
- if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
- version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
- // Qualcomm Adreno 7xx drivers do not properly support vertex_input_dynamic_state.
- LOG_WARNING(
- Render_Vulkan,
- "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state");
- RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
- features.vertex_input_dynamic_state,
- VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
- }
+ // Qualcomm drivers do not properly support vertex_input_dynamic_state.
+ LOG_WARNING(Render_Vulkan,
+ "Qualcomm drivers have broken VK_EXT_vertex_input_dynamic_state");
+ RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
+ features.vertex_input_dynamic_state,
+ VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
sets_per_pool = 64;
@@ -635,6 +626,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
has_broken_cube_compatibility = true;
}
}
+ if (is_qualcomm) {
+ const u32 version = (properties.properties.driverVersion << 3) >> 3;
+ if (version < VK_MAKE_API_VERSION(0, 255, 615, 512)) {
+ has_broken_parallel_compiling = true;
+ }
+ }
if (extensions.sampler_filter_minmax && is_amd) {
// Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken.
if (!features.shader_float16_int8.shaderFloat16) {
@@ -698,6 +695,22 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
std::min(properties.properties.limits.maxVertexInputBindings, 16U);
}
+ if (!extensions.extended_dynamic_state && extensions.extended_dynamic_state2) {
+ LOG_INFO(Render_Vulkan,
+ "Removing extendedDynamicState2 due to missing extendedDynamicState");
+ RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
+ }
+
+ if (!extensions.extended_dynamic_state2 && extensions.extended_dynamic_state3) {
+ LOG_INFO(Render_Vulkan,
+ "Removing extendedDynamicState3 due to missing extendedDynamicState2");
+ RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
+ dynamic_state3_blending = false;
+ dynamic_state3_enables = false;
+ }
+
logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions),
first_next, dld);
@@ -841,11 +854,41 @@ std::string Device::GetDriverName() const {
case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
return "NVIDIA";
case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
- return "INTEL";
+ return "Intel";
case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
return "ANV";
+ case VK_DRIVER_ID_IMAGINATION_PROPRIETARY:
+ return "PowerVR";
+ case VK_DRIVER_ID_QUALCOMM_PROPRIETARY:
+ return "Qualcomm";
+ case VK_DRIVER_ID_ARM_PROPRIETARY:
+ return "Mali";
+ case VK_DRIVER_ID_GOOGLE_SWIFTSHADER:
+ return "SwiftShader";
+ case VK_DRIVER_ID_BROADCOM_PROPRIETARY:
+ return "Broadcom";
case VK_DRIVER_ID_MESA_LLVMPIPE:
- return "LAVAPIPE";
+ return "Lavapipe";
+ case VK_DRIVER_ID_MOLTENVK:
+ return "MoltenVK";
+ case VK_DRIVER_ID_VERISILICON_PROPRIETARY:
+ return "Vivante";
+ case VK_DRIVER_ID_MESA_TURNIP:
+ return "Turnip";
+ case VK_DRIVER_ID_MESA_V3DV:
+ return "V3DV";
+ case VK_DRIVER_ID_MESA_PANVK:
+ return "PanVK";
+ case VK_DRIVER_ID_MESA_VENUS:
+ return "Venus";
+ case VK_DRIVER_ID_MESA_DOZEN:
+ return "Dozen";
+ case VK_DRIVER_ID_MESA_NVK:
+ return "NVK";
+ case VK_DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA:
+ return "PVR";
+ // case VK_DRIVER_ID_MESA_AGXV:
+ // return "Asahi";
default:
return properties.driver.driverName;
}
@@ -863,7 +906,8 @@ bool Device::ShouldBoostClocks() const {
driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA ||
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP;
- const bool is_steam_deck = vendor_id == 0x1002 && device_id == 0x163F;
+ const bool is_steam_deck = (vendor_id == 0x1002 && device_id == 0x163F) ||
+ (vendor_id == 0x1002 && device_id == 0x1435);
const bool is_debugging = this->HasDebuggingToolAttached();
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index b213ed7dd..4f3846345 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -102,6 +102,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
+ EXTENSION_NAME(VK_EXT_EXTERNAL_MEMORY_HOST_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) \
@@ -599,6 +600,11 @@ public:
return has_broken_cube_compatibility;
}
+ /// Returns true if parallel shader compiling has issues with the current driver.
+ bool HasBrokenParallelShaderCompiling() const {
+ return has_broken_parallel_compiling;
+ }
+
/// Returns the vendor name reported from Vulkan.
std::string_view GetVendorName() const {
return properties.driver.driverName;
@@ -663,6 +669,10 @@ public:
return supports_conditional_barriers;
}
+ bool SupportsMultiViewport() const {
+ return features2.features.multiViewport;
+ }
+
[[nodiscard]] static constexpr bool CheckBrokenCompute(VkDriverId driver_id,
u32 driver_version) {
if (driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
@@ -794,6 +804,7 @@ private:
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_broken_parallel_compiling{}; ///< Has broken parallel shader compiling.
bool has_renderdoc{}; ///< Has RenderDoc attached
bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
bool supports_d24_depth{}; ///< Supports D24 depth buffers.
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 0487cd3b6..a0c70797f 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -1101,6 +1101,10 @@ public:
return &handle;
}
+ VkCommandBuffer operator*() const noexcept {
+ return handle;
+ }
+
void Begin(const VkCommandBufferBeginInfo& begin_info) const {
Check(dld->vkBeginCommandBuffer(handle, &begin_info));
}
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 34208ed74..90278052a 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -38,8 +38,6 @@ add_executable(yuzu
compatdb.ui
compatibility_list.cpp
compatibility_list.h
- configuration/config.cpp
- configuration/config.h
configuration/configuration_shared.cpp
configuration/configuration_shared.h
configuration/configure.ui
@@ -147,6 +145,8 @@ add_executable(yuzu
configuration/shared_translation.h
configuration/shared_widget.cpp
configuration/shared_widget.h
+ configuration/qt_config.cpp
+ configuration/qt_config.h
debugger/console.cpp
debugger/console.h
debugger/controller.cpp
@@ -227,14 +227,14 @@ add_executable(yuzu
yuzu.rc
)
-if (WIN32 AND YUZU_CRASH_DUMPS)
+if (YUZU_CRASH_DUMPS)
target_sources(yuzu PRIVATE
- mini_dump.cpp
- mini_dump.h
+ breakpad.cpp
+ breakpad.h
)
- target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
- target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
+ target_link_libraries(yuzu PRIVATE libbreakpad_client)
+ target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
@@ -252,6 +252,7 @@ file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
if (ENABLE_QT_TRANSLATION)
set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend")
option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF)
+ option(WORKAROUND_BROKEN_LUPDATE "Run lupdate directly through CMake if Qt's convenience wrappers don't work" OFF)
# Update source TS file if enabled
if (GENERATE_QT_TRANSLATION)
@@ -259,19 +260,51 @@ if (ENABLE_QT_TRANSLATION)
# these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals
# so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm
set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations")
- qt_create_translation(QM_FILES
- ${SRCS}
- ${UIS}
- ${YUZU_QT_LANGUAGES}/en.ts
- OPTIONS
- -source-language en_US
- -target-language en_US
- )
+ if (WORKAROUND_BROKEN_LUPDATE)
+ add_custom_command(OUTPUT ${YUZU_QT_LANGUAGES}/en.ts
+ COMMAND lupdate
+ -source-language en_US
+ -target-language en_US
+ ${SRCS}
+ ${UIS}
+ -ts ${YUZU_QT_LANGUAGES}/en.ts
+ DEPENDS
+ ${SRCS}
+ ${UIS}
+ WORKING_DIRECTORY
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ else()
+ qt_create_translation(QM_FILES
+ ${SRCS}
+ ${UIS}
+ ${YUZU_QT_LANGUAGES}/en.ts
+ OPTIONS
+ -source-language en_US
+ -target-language en_US
+ )
+ endif()
# Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts
set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts)
set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals")
- qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US)
+ if (WORKAROUND_BROKEN_LUPDATE)
+ add_custom_command(OUTPUT ${GENERATED_PLURALS_FILE}
+ COMMAND lupdate
+ -source-language en_US
+ -target-language en_US
+ ${SRCS}
+ ${UIS}
+ -ts ${GENERATED_PLURALS_FILE}
+ DEPENDS
+ ${SRCS}
+ ${UIS}
+ WORKING_DIRECTORY
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ else()
+ qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US)
+ endif()
add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE})
endif()
@@ -344,7 +377,7 @@ endif()
create_target_directory_groups(yuzu)
-target_link_libraries(yuzu PRIVATE common core input_common network video_core)
+target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core)
target_link_libraries(yuzu PRIVATE Boost::headers glad Qt${QT_MAJOR_VERSION}::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index ca0e14fad..9e5319716 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -13,7 +13,6 @@
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/hid/controllers/npad.h"
-#include "core/hle/service/hid/hid.h"
#include "core/hle/service/sm/sm.h"
#include "ui_qt_controller.h"
#include "yuzu/applets/qt_controller.h"
@@ -155,18 +154,27 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
UpdateBorderColor(i);
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);
- }
- } else {
- for (std::size_t index = i; index < NUM_PLAYERS; ++index) {
- connected_controller_checkboxes[index]->setChecked(checked);
- }
+ // Reconnect current controller if it was the last one checked
+ // (player number was reduced by more than one)
+ const bool reconnect_first = !checked && i < player_groupboxes.size() - 1 &&
+ player_groupboxes[i + 1]->isChecked();
+
+ // Ensures that connecting a controller changes the number of players
+ if (connected_controller_checkboxes[i]->isChecked() != checked) {
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked, reconnect_first);
}
});
+ connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
+ // Reconnect current controller if it was the last one checked
+ // (player number was reduced by more than one)
+ const bool reconnect_first = !checked &&
+ i < connected_controller_checkboxes.size() - 1 &&
+ connected_controller_checkboxes[i + 1]->isChecked();
+
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked, reconnect_first);
+ });
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
[this, i](int) {
@@ -668,6 +676,29 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
}
}
+void QtControllerSelectorDialog::PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current) {
+ connected_controller_checkboxes[player_index]->setChecked(checked);
+ // Hide eventual error message about number of controllers
+ ui->labelError->setVisible(false);
+
+ if (checked) {
+ // Check all previous buttons when checked
+ if (player_index > 0) {
+ PropagatePlayerNumberChanged(player_index - 1, checked);
+ }
+ } else {
+ // Unchecked all following buttons when unchecked
+ if (player_index < connected_controller_checkboxes.size() - 1) {
+ PropagatePlayerNumberChanged(player_index + 1, checked);
+ }
+ }
+
+ if (reconnect_current) {
+ connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
+ }
+}
+
void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 7f0673d06..e5372495d 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -100,6 +100,10 @@ private:
// Updates the console mode.
void UpdateDockedState(bool is_handheld);
+ // Enable preceding controllers or disable following ones
+ void PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current = false);
+
// Disables and disconnects unsupported players based on the given parameters.
void DisableUnsupportedPlayers();
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 2afa72140..ed5750155 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -30,7 +30,6 @@
#include <QSize>
#include <QStringLiteral>
#include <QSurfaceFormat>
-#include <QTimer>
#include <QWindow>
#include <QtCore/qobjectdefs.h>
@@ -66,6 +65,8 @@ class QObject;
class QPaintEngine;
class QSurface;
+constexpr int default_mouse_constrain_timeout = 10;
+
EmuThread::EmuThread(Core::System& system) : m_system{system} {}
EmuThread::~EmuThread() = default;
@@ -304,6 +305,9 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
Qt::QueuedConnection);
connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection);
connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged);
+
+ mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
+ connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);
}
void GRenderWindow::ExecuteProgram(std::size_t program_index) {
@@ -393,6 +397,22 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event);
}
+void GRenderWindow::leaveEvent(QEvent* event) {
+ if (Settings::values.mouse_panning) {
+ const QRect& rect = QWidget::geometry();
+ QPoint position = QCursor::pos();
+
+ qint32 x = qBound(rect.left(), position.x(), rect.right());
+ qint32 y = qBound(rect.top(), position.y(), rect.bottom());
+ // Only start the timer if the mouse has left the window bound.
+ // The leave event is also triggered when the window looses focus.
+ if (x != position.x() || y != position.y()) {
+ mouse_constrain_timer.start();
+ }
+ event->accept();
+ }
+}
+
int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) {
static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = {
std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A},
@@ -658,10 +678,19 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
+ // Center mouse for mouse panning
if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
}
+ // Constrain mouse for mouse emulation with mouse panning
+ if (Settings::values.mouse_panning && Settings::values.mouse_enabled) {
+ const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y);
+ QCursor::setPos(mapToGlobal(
+ QPoint{static_cast<int>(clamped_mouse_x), static_cast<int>(clamped_mouse_y)}));
+ }
+
+ mouse_constrain_timer.stop();
emit MouseActivity();
}
@@ -675,6 +704,31 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
input_subsystem->GetMouse()->ReleaseButton(button);
}
+void GRenderWindow::ConstrainMouse() {
+ if (emu_thread == nullptr || !Settings::values.mouse_panning) {
+ mouse_constrain_timer.stop();
+ return;
+ }
+ if (!this->isActiveWindow()) {
+ mouse_constrain_timer.stop();
+ return;
+ }
+
+ if (Settings::values.mouse_enabled) {
+ const auto pos = mapFromGlobal(QCursor::pos());
+ const int new_pos_x = std::clamp(pos.x(), 0, width());
+ const int new_pos_y = std::clamp(pos.y(), 0, height());
+
+ QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y}));
+ return;
+ }
+
+ const int center_x = width() / 2;
+ const int center_y = height() / 2;
+
+ QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
+}
+
void GRenderWindow::wheelEvent(QWheelEvent* event) {
const int x = event->angleDelta().x();
const int y = event->angleDelta().y();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 87b23df12..60edd464c 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -17,6 +17,7 @@
#include <QString>
#include <QStringList>
#include <QThread>
+#include <QTimer>
#include <QWidget>
#include <qglobal.h>
#include <qnamespace.h>
@@ -38,7 +39,6 @@ class QMouseEvent;
class QObject;
class QResizeEvent;
class QShowEvent;
-class QTimer;
class QTouchEvent;
class QWheelEvent;
@@ -166,6 +166,7 @@ public:
std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
void closeEvent(QCloseEvent* event) override;
+ void leaveEvent(QEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
@@ -229,6 +230,7 @@ private:
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
+ void ConstrainMouse();
void RequestCameraCapture();
void OnCameraCapture(int requestId, const QImage& img);
@@ -268,6 +270,8 @@ private:
std::unique_ptr<QTimer> camera_timer;
#endif
+ QTimer mouse_constrain_timer;
+
Core::System& system;
protected:
diff --git a/src/yuzu/breakpad.cpp b/src/yuzu/breakpad.cpp
new file mode 100644
index 000000000..0f6a71ab0
--- /dev/null
+++ b/src/yuzu/breakpad.cpp
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <ranges>
+
+#if defined(_WIN32)
+#include <client/windows/handler/exception_handler.h>
+#elif defined(__linux__)
+#include <client/linux/handler/exception_handler.h>
+#else
+#error Minidump creation not supported on this platform
+#endif
+
+#include "common/fs/fs_paths.h"
+#include "common/fs/path_util.h"
+#include "yuzu/breakpad.h"
+
+namespace Breakpad {
+
+static void PruneDumpDirectory(const std::filesystem::path& dump_path) {
+ // Code in this function should be exception-safe.
+ struct Entry {
+ std::filesystem::path path;
+ std::filesystem::file_time_type last_write_time;
+ };
+ std::vector<Entry> existing_dumps;
+
+ // Get existing entries.
+ std::error_code ec;
+ std::filesystem::directory_iterator dir(dump_path, ec);
+ for (auto& entry : dir) {
+ if (entry.is_regular_file()) {
+ existing_dumps.push_back(Entry{
+ .path = entry.path(),
+ .last_write_time = entry.last_write_time(ec),
+ });
+ }
+ }
+
+ // Sort descending by creation date.
+ std::ranges::stable_sort(existing_dumps, [](const auto& a, const auto& b) {
+ return a.last_write_time > b.last_write_time;
+ });
+
+ // Delete older dumps.
+ for (size_t i = 5; i < existing_dumps.size(); i++) {
+ std::filesystem::remove(existing_dumps[i].path, ec);
+ }
+}
+
+#if defined(__linux__)
+[[noreturn]] bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context,
+ bool succeeded) {
+ // Prevent time- and space-consuming core dumps from being generated, as we have
+ // already generated a minidump and a core file will not be useful anyway.
+ _exit(1);
+}
+#endif
+
+void InstallCrashHandler() {
+ // Write crash dumps to profile directory.
+ const auto dump_path = GetYuzuPath(Common::FS::YuzuPath::CrashDumpsDir);
+ PruneDumpDirectory(dump_path);
+
+#if defined(_WIN32)
+ // TODO: If we switch to MinGW builds for Windows, this needs to be wrapped in a C API.
+ static google_breakpad::ExceptionHandler eh{dump_path, nullptr, nullptr, nullptr,
+ google_breakpad::ExceptionHandler::HANDLER_ALL};
+#elif defined(__linux__)
+ static google_breakpad::MinidumpDescriptor descriptor{dump_path};
+ static google_breakpad::ExceptionHandler eh{descriptor, nullptr, DumpCallback,
+ nullptr, true, -1};
+#endif
+}
+
+} // namespace Breakpad
diff --git a/src/yuzu/breakpad.h b/src/yuzu/breakpad.h
new file mode 100644
index 000000000..0f911aa9c
--- /dev/null
+++ b/src/yuzu/breakpad.h
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+namespace Breakpad {
+
+void InstallCrashHandler();
+
+}
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
deleted file mode 100644
index baa3e55f3..000000000
--- a/src/yuzu/configuration/config.cpp
+++ /dev/null
@@ -1,1306 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <algorithm>
-#include <array>
-#include <QKeySequence>
-#include <QSettings>
-#include "common/fs/fs.h"
-#include "common/fs/path_util.h"
-#include "common/settings.h"
-#include "common/settings_common.h"
-#include "common/settings_enums.h"
-#include "core/core.h"
-#include "core/hle/service/acc/profile_manager.h"
-#include "core/hle/service/hid/controllers/npad.h"
-#include "input_common/main.h"
-#include "network/network.h"
-#include "yuzu/configuration/config.h"
-
-namespace FS = Common::FS;
-
-Config::Config(const std::string& config_name, ConfigType config_type)
- : type(config_type), global{config_type == ConfigType::GlobalConfig} {
- Initialize(config_name);
-}
-
-Config::~Config() {
- if (global) {
- Save();
- }
-}
-
-const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
- Qt::Key_C, Qt::Key_X, Qt::Key_V, Qt::Key_Z, Qt::Key_F,
- Qt::Key_G, Qt::Key_Q, Qt::Key_E, Qt::Key_R, Qt::Key_T,
- Qt::Key_M, Qt::Key_N, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right,
- Qt::Key_Down, Qt::Key_Q, Qt::Key_E, 0, 0,
-};
-
-const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = {
- Qt::Key_7,
- Qt::Key_8,
-};
-
-const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
- {
- Qt::Key_W,
- Qt::Key_S,
- Qt::Key_A,
- Qt::Key_D,
- },
- {
- Qt::Key_I,
- Qt::Key_K,
- Qt::Key_J,
- Qt::Key_L,
- },
-}};
-
-const std::array<int, 2> Config::default_stick_mod = {
- Qt::Key_Shift,
- 0,
-};
-
-const std::array<int, 2> Config::default_ringcon_analogs{{
- Qt::Key_A,
- Qt::Key_D,
-}};
-
-const std::map<Settings::AntiAliasing, QString> Config::anti_aliasing_texts_map = {
- {Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))},
- {Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))},
- {Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))},
-};
-
-const std::map<Settings::ScalingFilter, QString> Config::scaling_filter_texts_map = {
- {Settings::ScalingFilter::NearestNeighbor,
- QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Nearest"))},
- {Settings::ScalingFilter::Bilinear,
- QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))},
- {Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))},
- {Settings::ScalingFilter::Gaussian,
- QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))},
- {Settings::ScalingFilter::ScaleForce,
- QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))},
- {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
-};
-
-const std::map<Settings::ConsoleMode, QString> Config::use_docked_mode_texts_map = {
- {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
- {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
-};
-
-const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = {
- {Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
- {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
- {Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
-};
-
-const std::map<Settings::RendererBackend, QString> Config::renderer_backend_texts_map = {
- {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))},
- {Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))},
- {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
-};
-
-const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
- {Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
- {Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
- {Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
-};
-
-// This shouldn't have anything except static initializers (no functions). So
-// QKeySequence(...).toString() is NOT ALLOWED HERE.
-// This must be in alphabetical order according to action name as it must have the same order as
-// UISetting::values.shortcuts, which is alphabetically ordered.
-// clang-format off
-const std::array<UISettings::Shortcut, 23> Config::default_hotkeys{{
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"), QStringLiteral("Home+R"), Qt::ApplicationShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut, false}},
- {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("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}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut, false}},
-}};
-// clang-format on
-
-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:
- qt_config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
- void(FS::CreateParentDir(qt_config_loc));
- qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
- QSettings::IniFormat);
- Reload();
- break;
- case ConfigType::PerGameConfig:
- qt_config_loc =
- FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
- void(FS::CreateParentDir(qt_config_loc));
- qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
- QSettings::IniFormat);
- Reload();
- break;
- case ConfigType::InputProfile:
- qt_config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
- void(FS::CreateParentDir(qt_config_loc));
- qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
- QSettings::IniFormat);
- break;
- }
-}
-
-bool Config::IsCustomConfig() {
- return type == ConfigType::PerGameConfig;
-}
-
-void Config::ReadPlayerValue(std::size_t player_index) {
- const QString player_prefix = [this, player_index] {
- if (type == ConfigType::InputProfile) {
- return QString{};
- } else {
- return QStringLiteral("player_%1_").arg(player_index);
- }
- }();
-
- auto& player = Settings::values.players.GetValue()[player_index];
- if (IsCustomConfig()) {
- const auto profile_name =
- qt_config->value(QStringLiteral("%1profile_name").arg(player_prefix), QString{})
- .toString()
- .toStdString();
- if (profile_name.empty()) {
- // Use the global input config
- player = Settings::values.players.GetValue(true)[player_index];
- return;
- }
- player.profile_name = profile_name;
- }
-
- if (player_prefix.isEmpty() && Settings::IsConfiguringGlobal()) {
- const auto controller = static_cast<Settings::ControllerType>(
- qt_config
- ->value(QStringLiteral("%1type").arg(player_prefix),
- static_cast<u8>(Settings::ControllerType::ProController))
- .toUInt());
-
- if (controller == Settings::ControllerType::LeftJoycon ||
- controller == Settings::ControllerType::RightJoycon) {
- player.controller_type = controller;
- }
- } else {
- player.connected =
- ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0)
- .toBool();
-
- player.controller_type = static_cast<Settings::ControllerType>(
- qt_config
- ->value(QStringLiteral("%1type").arg(player_prefix),
- static_cast<u8>(Settings::ControllerType::ProController))
- .toUInt());
-
- player.vibration_enabled =
- qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true)
- .toBool();
-
- player.vibration_strength =
- qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100)
- .toInt();
-
- player.body_color_left = qt_config
- ->value(QStringLiteral("%1body_color_left").arg(player_prefix),
- Settings::JOYCON_BODY_NEON_BLUE)
- .toUInt();
- player.body_color_right =
- qt_config
- ->value(QStringLiteral("%1body_color_right").arg(player_prefix),
- Settings::JOYCON_BODY_NEON_RED)
- .toUInt();
- player.button_color_left =
- qt_config
- ->value(QStringLiteral("%1button_color_left").arg(player_prefix),
- Settings::JOYCON_BUTTONS_NEON_BLUE)
- .toUInt();
- player.button_color_right =
- qt_config
- ->value(QStringLiteral("%1button_color_right").arg(player_prefix),
- Settings::JOYCON_BUTTONS_NEON_RED)
- .toUInt();
- }
-
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- auto& player_buttons = player.buttons[i];
-
- player_buttons = qt_config
- ->value(QStringLiteral("%1").arg(player_prefix) +
- QString::fromUtf8(Settings::NativeButton::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player_buttons.empty()) {
- player_buttons = default_param;
- }
- }
-
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_stick_mod[i], 0.5f);
- auto& player_analogs = player.analogs[i];
-
- player_analogs = qt_config
- ->value(QStringLiteral("%1").arg(player_prefix) +
- QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player_analogs.empty()) {
- player_analogs = default_param;
- }
- }
-
- for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
- const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
- auto& player_motions = player.motions[i];
-
- player_motions = qt_config
- ->value(QStringLiteral("%1").arg(player_prefix) +
- QString::fromUtf8(Settings::NativeMotion::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player_motions.empty()) {
- player_motions = default_param;
- }
- }
-}
-
-void Config::ReadDebugValues() {
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
-
- debug_pad_buttons = qt_config
- ->value(QStringLiteral("debug_pad_") +
- QString::fromUtf8(Settings::NativeButton::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (debug_pad_buttons.empty()) {
- debug_pad_buttons = default_param;
- }
- }
-
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_stick_mod[i], 0.5f);
- auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
-
- debug_pad_analogs = qt_config
- ->value(QStringLiteral("debug_pad_") +
- QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (debug_pad_analogs.empty()) {
- debug_pad_analogs = default_param;
- }
- }
-}
-
-void Config::ReadTouchscreenValues() {
- Settings::values.touchscreen.enabled =
- ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool();
-
- Settings::values.touchscreen.rotation_angle =
- ReadSetting(QStringLiteral("touchscreen_angle"), 0).toUInt();
- Settings::values.touchscreen.diameter_x =
- ReadSetting(QStringLiteral("touchscreen_diameter_x"), 15).toUInt();
- Settings::values.touchscreen.diameter_y =
- ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
-}
-
-void Config::ReadHidbusValues() {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
- auto& ringcon_analogs = Settings::values.ringcon_analogs;
-
- ringcon_analogs =
- qt_config->value(QStringLiteral("ring_controller"), QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (ringcon_analogs.empty()) {
- ringcon_analogs = default_param;
- }
-}
-
-void Config::ReadAudioValues() {
- qt_config->beginGroup(QStringLiteral("Audio"));
-
- ReadCategory(Settings::Category::Audio);
-
- qt_config->endGroup();
-}
-
-void Config::ReadControlValues() {
- qt_config->beginGroup(QStringLiteral("Controls"));
-
- ReadCategory(Settings::Category::Controls);
-
- Settings::values.players.SetGlobal(!IsCustomConfig());
- for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
- ReadPlayerValue(p);
- }
-
- // Disable docked mode if handheld is selected
- const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
- if (controller_type == Settings::ControllerType::Handheld) {
- Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
- Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
- }
-
- if (IsCustomConfig()) {
- qt_config->endGroup();
- return;
- }
- ReadDebugValues();
- ReadTouchscreenValues();
- ReadMotionTouchValues();
- ReadHidbusValues();
-
- qt_config->endGroup();
-}
-
-void Config::ReadMotionTouchValues() {
- int num_touch_from_button_maps =
- qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
-
- if (num_touch_from_button_maps > 0) {
- const auto append_touch_from_button_map = [this] {
- Settings::TouchFromButtonMap map;
- map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default"))
- .toString()
- .toStdString();
- const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries"));
- map.buttons.reserve(num_touch_maps);
- for (int i = 0; i < num_touch_maps; i++) {
- qt_config->setArrayIndex(i);
- std::string touch_mapping =
- ReadSetting(QStringLiteral("bind")).toString().toStdString();
- map.buttons.emplace_back(std::move(touch_mapping));
- }
- qt_config->endArray(); // entries
- Settings::values.touch_from_button_maps.emplace_back(std::move(map));
- };
-
- for (int i = 0; i < num_touch_from_button_maps; ++i) {
- qt_config->setArrayIndex(i);
- append_touch_from_button_map();
- }
- } else {
- Settings::values.touch_from_button_maps.emplace_back(
- Settings::TouchFromButtonMap{"default", {}});
- num_touch_from_button_maps = 1;
- }
- qt_config->endArray();
-
- Settings::values.touch_from_button_map_index = std::clamp(
- Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
-}
-
-void Config::ReadCoreValues() {
- qt_config->beginGroup(QStringLiteral("Core"));
-
- ReadCategory(Settings::Category::Core);
-
- qt_config->endGroup();
-}
-
-void Config::ReadDataStorageValues() {
- qt_config->beginGroup(QStringLiteral("Data Storage"));
-
- FS::SetYuzuPath(
- FS::YuzuPath::NANDDir,
- qt_config
- ->value(QStringLiteral("nand_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)))
- .toString()
- .toStdString());
- FS::SetYuzuPath(
- FS::YuzuPath::SDMCDir,
- qt_config
- ->value(QStringLiteral("sdmc_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)))
- .toString()
- .toStdString());
- FS::SetYuzuPath(
- FS::YuzuPath::LoadDir,
- qt_config
- ->value(QStringLiteral("load_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)))
- .toString()
- .toStdString());
- FS::SetYuzuPath(
- FS::YuzuPath::DumpDir,
- qt_config
- ->value(QStringLiteral("dump_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)))
- .toString()
- .toStdString());
- FS::SetYuzuPath(FS::YuzuPath::TASDir,
- qt_config
- ->value(QStringLiteral("tas_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)))
- .toString()
- .toStdString());
-
- ReadCategory(Settings::Category::DataStorage);
-
- qt_config->endGroup();
-}
-
-void Config::ReadDebuggingValues() {
- qt_config->beginGroup(QStringLiteral("Debugging"));
-
- // Intentionally not using the QT default setting as this is intended to be changed in the ini
- Settings::values.record_frame_times =
- qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
-
- ReadCategory(Settings::Category::Debugging);
- ReadCategory(Settings::Category::DebuggingGraphics);
-
- qt_config->endGroup();
-}
-
-void Config::ReadServiceValues() {
- qt_config->beginGroup(QStringLiteral("Services"));
-
- ReadCategory(Settings::Category::Services);
-
- qt_config->endGroup();
-}
-
-void Config::ReadDisabledAddOnValues() {
- const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
-
- for (int i = 0; i < size; ++i) {
- qt_config->setArrayIndex(i);
- const auto title_id = ReadSetting(QStringLiteral("title_id"), 0).toULongLong();
- std::vector<std::string> out;
- const auto d_size = qt_config->beginReadArray(QStringLiteral("disabled"));
- for (int j = 0; j < d_size; ++j) {
- qt_config->setArrayIndex(j);
- out.push_back(ReadSetting(QStringLiteral("d"), QString{}).toString().toStdString());
- }
- qt_config->endArray();
- Settings::values.disabled_addons.insert_or_assign(title_id, out);
- }
-
- qt_config->endArray();
-}
-
-void Config::ReadMiscellaneousValues() {
- qt_config->beginGroup(QStringLiteral("Miscellaneous"));
-
- ReadCategory(Settings::Category::Miscellaneous);
-
- qt_config->endGroup();
-}
-
-void Config::ReadPathValues() {
- qt_config->beginGroup(QStringLiteral("Paths"));
-
- UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
- UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
- UISettings::values.game_dir_deprecated =
- ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
- UISettings::values.game_dir_deprecated_deepscan =
- ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
- const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs"));
- for (int i = 0; i < gamedirs_size; ++i) {
- qt_config->setArrayIndex(i);
- UISettings::GameDir game_dir;
- game_dir.path = ReadSetting(QStringLiteral("path")).toString();
- game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool();
- game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool();
- UISettings::values.game_dirs.append(game_dir);
- }
- qt_config->endArray();
- // create NAND and SD card directories if empty, these are not removable through the UI,
- // also carries over old game list settings if present
- if (UISettings::values.game_dirs.isEmpty()) {
- UISettings::GameDir game_dir;
- game_dir.path = QStringLiteral("SDMC");
- game_dir.expanded = true;
- UISettings::values.game_dirs.append(game_dir);
- game_dir.path = QStringLiteral("UserNAND");
- UISettings::values.game_dirs.append(game_dir);
- game_dir.path = QStringLiteral("SysNAND");
- UISettings::values.game_dirs.append(game_dir);
- if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) {
- game_dir.path = UISettings::values.game_dir_deprecated;
- game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
- UISettings::values.game_dirs.append(game_dir);
- }
- }
- UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
- UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
-
- qt_config->endGroup();
-}
-
-void Config::ReadCpuValues() {
- qt_config->beginGroup(QStringLiteral("Cpu"));
-
- ReadCategory(Settings::Category::Cpu);
- ReadCategory(Settings::Category::CpuDebug);
- ReadCategory(Settings::Category::CpuUnsafe);
-
- qt_config->endGroup();
-}
-
-void Config::ReadRendererValues() {
- qt_config->beginGroup(QStringLiteral("Renderer"));
-
- ReadCategory(Settings::Category::Renderer);
- ReadCategory(Settings::Category::RendererAdvanced);
- ReadCategory(Settings::Category::RendererDebug);
-
- qt_config->endGroup();
-}
-
-void Config::ReadScreenshotValues() {
- qt_config->beginGroup(QStringLiteral("Screenshots"));
-
- ReadCategory(Settings::Category::Screenshots);
- FS::SetYuzuPath(
- FS::YuzuPath::ScreenshotsDir,
- qt_config
- ->value(QStringLiteral("screenshot_path"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)))
- .toString()
- .toStdString());
-
- qt_config->endGroup();
-}
-
-void Config::ReadShortcutValues() {
- qt_config->beginGroup(QStringLiteral("Shortcuts"));
-
- for (const auto& [name, group, shortcut] : default_hotkeys) {
- qt_config->beginGroup(group);
- qt_config->beginGroup(name);
- // No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1
- // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
- // a file dialog in windowed mode
- UISettings::values.shortcuts.push_back(
- {name,
- group,
- {ReadSetting(QStringLiteral("KeySeq"), shortcut.keyseq).toString(),
- ReadSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq)
- .toString(),
- shortcut.context, ReadSetting(QStringLiteral("Repeat"), shortcut.repeat).toBool()}});
- qt_config->endGroup();
- qt_config->endGroup();
- }
-
- qt_config->endGroup();
-}
-
-void Config::ReadSystemValues() {
- qt_config->beginGroup(QStringLiteral("System"));
-
- ReadCategory(Settings::Category::System);
- ReadCategory(Settings::Category::SystemAudio);
-
- qt_config->endGroup();
-}
-
-void Config::ReadUIValues() {
- qt_config->beginGroup(QStringLiteral("UI"));
-
- UISettings::values.theme =
- ReadSetting(
- QStringLiteral("theme"),
- QString::fromUtf8(UISettings::themes[static_cast<size_t>(default_theme)].second))
- .toString();
-
- ReadUIGamelistValues();
- ReadUILayoutValues();
- ReadPathValues();
- ReadScreenshotValues();
- ReadShortcutValues();
- ReadMultiplayerValues();
-
- ReadCategory(Settings::Category::Ui);
- ReadCategory(Settings::Category::UiGeneral);
-
- qt_config->endGroup();
-}
-
-void Config::ReadUIGamelistValues() {
- qt_config->beginGroup(QStringLiteral("UIGameList"));
-
- ReadCategory(Settings::Category::UiGameList);
-
- const int favorites_size = qt_config->beginReadArray(QStringLiteral("favorites"));
- for (int i = 0; i < favorites_size; i++) {
- qt_config->setArrayIndex(i);
- UISettings::values.favorited_ids.append(
- ReadSetting(QStringLiteral("program_id")).toULongLong());
- }
- qt_config->endArray();
-
- qt_config->endGroup();
-}
-
-void Config::ReadUILayoutValues() {
- qt_config->beginGroup(QStringLiteral("UILayout"));
-
- UISettings::values.geometry = ReadSetting(QStringLiteral("geometry")).toByteArray();
- UISettings::values.state = ReadSetting(QStringLiteral("state")).toByteArray();
- UISettings::values.renderwindow_geometry =
- ReadSetting(QStringLiteral("geometryRenderWindow")).toByteArray();
- UISettings::values.gamelist_header_state =
- ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
- UISettings::values.microprofile_geometry =
- ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
-
- ReadCategory(Settings::Category::UiLayout);
-
- qt_config->endGroup();
-}
-
-void Config::ReadWebServiceValues() {
- qt_config->beginGroup(QStringLiteral("WebService"));
-
- ReadCategory(Settings::Category::WebService);
-
- qt_config->endGroup();
-}
-
-void Config::ReadMultiplayerValues() {
- qt_config->beginGroup(QStringLiteral("Multiplayer"));
-
- ReadCategory(Settings::Category::Multiplayer);
-
- // Read ban list back
- int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
- UISettings::values.multiplayer_ban_list.first.resize(size);
- for (int i = 0; i < size; ++i) {
- qt_config->setArrayIndex(i);
- UISettings::values.multiplayer_ban_list.first[i] =
- ReadSetting(QStringLiteral("username")).toString().toStdString();
- }
- qt_config->endArray();
- size = qt_config->beginReadArray(QStringLiteral("ip_ban_list"));
- UISettings::values.multiplayer_ban_list.second.resize(size);
- for (int i = 0; i < size; ++i) {
- qt_config->setArrayIndex(i);
- UISettings::values.multiplayer_ban_list.second[i] =
- ReadSetting(QStringLiteral("ip")).toString().toStdString();
- }
- qt_config->endArray();
-
- qt_config->endGroup();
-}
-
-void Config::ReadNetworkValues() {
- qt_config->beginGroup(QString::fromStdString("Services"));
-
- ReadCategory(Settings::Category::Network);
-
- qt_config->endGroup();
-}
-
-void Config::ReadValues() {
- if (global) {
- ReadDataStorageValues();
- ReadDebuggingValues();
- ReadDisabledAddOnValues();
- ReadNetworkValues();
- ReadServiceValues();
- ReadUIValues();
- ReadWebServiceValues();
- ReadMiscellaneousValues();
- }
- ReadControlValues();
- ReadCoreValues();
- ReadCpuValues();
- ReadRendererValues();
- ReadAudioValues();
- ReadSystemValues();
-}
-
-void Config::SavePlayerValue(std::size_t player_index) {
- const QString player_prefix = [this, player_index] {
- if (type == ConfigType::InputProfile) {
- return QString{};
- } else {
- return QStringLiteral("player_%1_").arg(player_index);
- }
- }();
-
- const auto& player = Settings::values.players.GetValue()[player_index];
- if (IsCustomConfig()) {
- if (player.profile_name.empty()) {
- // No custom profile selected
- return;
- }
- WriteSetting(QStringLiteral("%1profile_name").arg(player_prefix),
- QString::fromStdString(player.profile_name), QString{});
- }
-
- WriteSetting(QStringLiteral("%1type").arg(player_prefix),
- static_cast<u8>(player.controller_type),
- static_cast<u8>(Settings::ControllerType::ProController));
-
- if (!player_prefix.isEmpty() || !Settings::IsConfiguringGlobal()) {
- WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected,
- player_index == 0);
- WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix),
- player.vibration_enabled, true);
- WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix),
- player.vibration_strength, 100);
- WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left,
- Settings::JOYCON_BODY_NEON_BLUE);
- WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix),
- player.body_color_right, Settings::JOYCON_BODY_NEON_RED);
- WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix),
- player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE);
- WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix),
- player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
- }
-
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- WriteSetting(QStringLiteral("%1").arg(player_prefix) +
- QString::fromStdString(Settings::NativeButton::mapping[i]),
- QString::fromStdString(player.buttons[i]),
- QString::fromStdString(default_param));
- }
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_stick_mod[i], 0.5f);
- WriteSetting(QStringLiteral("%1").arg(player_prefix) +
- QString::fromStdString(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(player.analogs[i]),
- QString::fromStdString(default_param));
- }
- for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
- const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
- WriteSetting(QStringLiteral("%1").arg(player_prefix) +
- QString::fromStdString(Settings::NativeMotion::mapping[i]),
- QString::fromStdString(player.motions[i]),
- QString::fromStdString(default_param));
- }
-}
-
-void Config::SaveDebugValues() {
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- WriteSetting(QStringLiteral("debug_pad_") +
- QString::fromStdString(Settings::NativeButton::mapping[i]),
- QString::fromStdString(Settings::values.debug_pad_buttons[i]),
- QString::fromStdString(default_param));
- }
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_stick_mod[i], 0.5f);
- WriteSetting(QStringLiteral("debug_pad_") +
- QString::fromStdString(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(Settings::values.debug_pad_analogs[i]),
- QString::fromStdString(default_param));
- }
-}
-
-void Config::SaveTouchscreenValues() {
- const auto& touchscreen = Settings::values.touchscreen;
-
- WriteSetting(QStringLiteral("touchscreen_enabled"), touchscreen.enabled, true);
-
- WriteSetting(QStringLiteral("touchscreen_angle"), touchscreen.rotation_angle, 0);
- WriteSetting(QStringLiteral("touchscreen_diameter_x"), touchscreen.diameter_x, 15);
- WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
-}
-
-void Config::SaveMotionTouchValues() {
- qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
- for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
- qt_config->setArrayIndex(static_cast<int>(p));
- WriteSetting(QStringLiteral("name"),
- QString::fromStdString(Settings::values.touch_from_button_maps[p].name),
- QStringLiteral("default"));
- qt_config->beginWriteArray(QStringLiteral("entries"));
- for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
- ++q) {
- qt_config->setArrayIndex(static_cast<int>(q));
- WriteSetting(
- QStringLiteral("bind"),
- QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q]));
- }
- qt_config->endArray();
- }
- qt_config->endArray();
-}
-
-void Config::SaveHidbusValues() {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
- WriteSetting(QStringLiteral("ring_controller"),
- QString::fromStdString(Settings::values.ringcon_analogs),
- QString::fromStdString(default_param));
-}
-
-void Config::SaveValues() {
- if (global) {
- SaveDataStorageValues();
- SaveDebuggingValues();
- SaveDisabledAddOnValues();
- SaveNetworkValues();
- SaveUIValues();
- SaveWebServiceValues();
- SaveMiscellaneousValues();
- }
- SaveControlValues();
- SaveCoreValues();
- SaveCpuValues();
- SaveRendererValues();
- SaveAudioValues();
- SaveSystemValues();
-
- qt_config->sync();
-}
-
-void Config::SaveAudioValues() {
- qt_config->beginGroup(QStringLiteral("Audio"));
-
- WriteCategory(Settings::Category::Audio);
-
- qt_config->endGroup();
-}
-
-void Config::SaveControlValues() {
- qt_config->beginGroup(QStringLiteral("Controls"));
-
- WriteCategory(Settings::Category::Controls);
-
- Settings::values.players.SetGlobal(!IsCustomConfig());
- for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
- SavePlayerValue(p);
- }
- if (IsCustomConfig()) {
- qt_config->endGroup();
- return;
- }
- SaveDebugValues();
- SaveTouchscreenValues();
- SaveMotionTouchValues();
- SaveHidbusValues();
-
- qt_config->endGroup();
-}
-
-void Config::SaveCoreValues() {
- qt_config->beginGroup(QStringLiteral("Core"));
-
- WriteCategory(Settings::Category::Core);
-
- qt_config->endGroup();
-}
-
-void Config::SaveDataStorageValues() {
- qt_config->beginGroup(QStringLiteral("Data Storage"));
-
- WriteSetting(QStringLiteral("nand_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
- WriteSetting(QStringLiteral("sdmc_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
- WriteSetting(QStringLiteral("load_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
- WriteSetting(QStringLiteral("dump_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
- WriteSetting(QStringLiteral("tas_directory"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
-
- WriteCategory(Settings::Category::DataStorage);
-
- qt_config->endGroup();
-}
-
-void Config::SaveDebuggingValues() {
- qt_config->beginGroup(QStringLiteral("Debugging"));
-
- // Intentionally not using the QT default setting as this is intended to be changed in the ini
- qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
-
- WriteCategory(Settings::Category::Debugging);
- WriteCategory(Settings::Category::DebuggingGraphics);
-
- qt_config->endGroup();
-}
-
-void Config::SaveNetworkValues() {
- qt_config->beginGroup(QStringLiteral("Services"));
-
- WriteCategory(Settings::Category::Network);
-
- qt_config->endGroup();
-}
-
-void Config::SaveDisabledAddOnValues() {
- qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
-
- int i = 0;
- for (const auto& elem : Settings::values.disabled_addons) {
- qt_config->setArrayIndex(i);
- WriteSetting(QStringLiteral("title_id"), QVariant::fromValue<u64>(elem.first), 0);
- qt_config->beginWriteArray(QStringLiteral("disabled"));
- for (std::size_t j = 0; j < elem.second.size(); ++j) {
- qt_config->setArrayIndex(static_cast<int>(j));
- WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), QString{});
- }
- qt_config->endArray();
- ++i;
- }
-
- qt_config->endArray();
-}
-
-void Config::SaveMiscellaneousValues() {
- qt_config->beginGroup(QStringLiteral("Miscellaneous"));
-
- WriteCategory(Settings::Category::Miscellaneous);
-
- qt_config->endGroup();
-}
-
-void Config::SavePathValues() {
- qt_config->beginGroup(QStringLiteral("Paths"));
-
- WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
- WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
- qt_config->beginWriteArray(QStringLiteral("gamedirs"));
- for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
- qt_config->setArrayIndex(i);
- const auto& game_dir = UISettings::values.game_dirs[i];
- WriteSetting(QStringLiteral("path"), game_dir.path);
- WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false);
- WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
- }
- qt_config->endArray();
- WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
- WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
-
- qt_config->endGroup();
-}
-
-void Config::SaveCpuValues() {
- qt_config->beginGroup(QStringLiteral("Cpu"));
-
- WriteCategory(Settings::Category::Cpu);
- WriteCategory(Settings::Category::CpuDebug);
- WriteCategory(Settings::Category::CpuUnsafe);
-
- qt_config->endGroup();
-}
-
-void Config::SaveRendererValues() {
- qt_config->beginGroup(QStringLiteral("Renderer"));
-
- WriteCategory(Settings::Category::Renderer);
- WriteCategory(Settings::Category::RendererAdvanced);
- WriteCategory(Settings::Category::RendererDebug);
-
- qt_config->endGroup();
-}
-
-void Config::SaveScreenshotValues() {
- qt_config->beginGroup(QStringLiteral("Screenshots"));
-
- WriteSetting(QStringLiteral("screenshot_path"),
- QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)));
- WriteCategory(Settings::Category::Screenshots);
-
- qt_config->endGroup();
-}
-
-void Config::SaveShortcutValues() {
- qt_config->beginGroup(QStringLiteral("Shortcuts"));
-
- // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
- // However, their ordering must also be the same.
- for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
- const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
- const auto& default_hotkey = default_hotkeys[i].shortcut;
-
- qt_config->beginGroup(group);
- qt_config->beginGroup(name);
- WriteSetting(QStringLiteral("KeySeq"), shortcut.keyseq, default_hotkey.keyseq);
- WriteSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq,
- default_hotkey.controller_keyseq);
- WriteSetting(QStringLiteral("Context"), shortcut.context, default_hotkey.context);
- WriteSetting(QStringLiteral("Repeat"), shortcut.repeat, default_hotkey.repeat);
- qt_config->endGroup();
- qt_config->endGroup();
- }
-
- qt_config->endGroup();
-}
-
-void Config::SaveSystemValues() {
- qt_config->beginGroup(QStringLiteral("System"));
-
- WriteCategory(Settings::Category::System);
- WriteCategory(Settings::Category::SystemAudio);
-
- qt_config->endGroup();
-}
-
-void Config::SaveUIValues() {
- qt_config->beginGroup(QStringLiteral("UI"));
-
- WriteCategory(Settings::Category::Ui);
- WriteCategory(Settings::Category::UiGeneral);
-
- WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
- QString::fromUtf8(UISettings::themes[static_cast<size_t>(default_theme)].second));
-
- SaveUIGamelistValues();
- SaveUILayoutValues();
- SavePathValues();
- SaveScreenshotValues();
- SaveShortcutValues();
- SaveMultiplayerValues();
-
- qt_config->endGroup();
-}
-
-void Config::SaveUIGamelistValues() {
- qt_config->beginGroup(QStringLiteral("UIGameList"));
-
- WriteCategory(Settings::Category::UiGameList);
-
- qt_config->beginWriteArray(QStringLiteral("favorites"));
- for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
- qt_config->setArrayIndex(i);
- WriteSetting(QStringLiteral("program_id"),
- QVariant::fromValue(UISettings::values.favorited_ids[i]));
- }
- qt_config->endArray();
-
- qt_config->endGroup();
-}
-
-void Config::SaveUILayoutValues() {
- qt_config->beginGroup(QStringLiteral("UILayout"));
-
- WriteSetting(QStringLiteral("geometry"), UISettings::values.geometry);
- WriteSetting(QStringLiteral("state"), UISettings::values.state);
- WriteSetting(QStringLiteral("geometryRenderWindow"), UISettings::values.renderwindow_geometry);
- WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
- WriteSetting(QStringLiteral("microProfileDialogGeometry"),
- UISettings::values.microprofile_geometry);
-
- WriteCategory(Settings::Category::UiLayout);
-
- qt_config->endGroup();
-}
-
-void Config::SaveWebServiceValues() {
- qt_config->beginGroup(QStringLiteral("WebService"));
-
- WriteCategory(Settings::Category::WebService);
-
- qt_config->endGroup();
-}
-
-void Config::SaveMultiplayerValues() {
- qt_config->beginGroup(QStringLiteral("Multiplayer"));
-
- WriteCategory(Settings::Category::Multiplayer);
-
- // Write ban list
- qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
- for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
- qt_config->setArrayIndex(static_cast<int>(i));
- WriteSetting(QStringLiteral("username"),
- QString::fromStdString(UISettings::values.multiplayer_ban_list.first[i]));
- }
- qt_config->endArray();
- qt_config->beginWriteArray(QStringLiteral("ip_ban_list"));
- for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
- qt_config->setArrayIndex(static_cast<int>(i));
- WriteSetting(QStringLiteral("ip"),
- QString::fromStdString(UISettings::values.multiplayer_ban_list.second[i]));
- }
- qt_config->endArray();
-
- qt_config->endGroup();
-}
-
-QVariant Config::ReadSetting(const QString& name) const {
- return qt_config->value(name);
-}
-
-QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
- QVariant result;
- if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
- result = default_value;
- } else {
- result = qt_config->value(name, default_value);
- }
- return result;
-}
-
-void Config::WriteSetting(const QString& name, const QVariant& value) {
- qt_config->setValue(name, value);
-}
-
-void Config::WriteSetting(const QString& name, const QVariant& value,
- const QVariant& default_value) {
- qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
- qt_config->setValue(name, value);
-}
-
-void Config::WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
- bool use_global) {
- if (!global) {
- qt_config->setValue(name + QStringLiteral("/use_global"), use_global);
- }
- if (global || !use_global) {
- qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
- qt_config->setValue(name, value);
- }
-}
-
-void Config::Reload() {
- ReadValues();
- // To apply default value changes
- SaveValues();
-}
-
-void Config::Save() {
- SaveValues();
-}
-
-void Config::ReadControlPlayerValue(std::size_t player_index) {
- qt_config->beginGroup(QStringLiteral("Controls"));
- ReadPlayerValue(player_index);
- qt_config->endGroup();
-}
-
-void Config::SaveControlPlayerValue(std::size_t player_index) {
- qt_config->beginGroup(QStringLiteral("Controls"));
- SavePlayerValue(player_index);
- qt_config->endGroup();
-}
-
-void Config::ClearControlPlayerValues() {
- qt_config->beginGroup(QStringLiteral("Controls"));
- // If key is an empty string, all keys in the current group() are removed.
- qt_config->remove(QString{});
- qt_config->endGroup();
-}
-
-const std::string& Config::GetConfigFilePath() const {
- return qt_config_loc;
-}
-
-static auto FindRelevantList(Settings::Category category) {
- auto& map = Settings::values.linkage.by_category;
- if (map.contains(category)) {
- return Settings::values.linkage.by_category[category];
- }
- return UISettings::values.linkage.by_category[category];
-}
-
-void Config::ReadCategory(Settings::Category category) {
- const auto& settings = FindRelevantList(category);
- std::for_each(settings.begin(), settings.end(),
- [&](const auto& setting) { ReadSettingGeneric(setting); });
-}
-
-void Config::WriteCategory(Settings::Category category) {
- const auto& settings = FindRelevantList(category);
- std::for_each(settings.begin(), settings.end(),
- [&](const auto& setting) { WriteSettingGeneric(setting); });
-}
-
-void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) {
- if (!setting->Save() || (!setting->Switchable() && !global)) {
- return;
- }
- const QString name = QString::fromStdString(setting->GetLabel());
- const auto default_value =
- QVariant::fromValue<QString>(QString::fromStdString(setting->DefaultToString()));
-
- bool use_global = true;
- if (setting->Switchable() && !global) {
- use_global = qt_config->value(name + QStringLiteral("/use_global"), true).value<bool>();
- setting->SetGlobal(use_global);
- }
-
- if (global || !use_global) {
- const bool is_default =
- qt_config->value(name + QStringLiteral("/default"), true).value<bool>();
- if (!is_default) {
- setting->LoadString(
- qt_config->value(name, default_value).value<QString>().toStdString());
- } else {
- // Empty string resets the Setting to default
- setting->LoadString("");
- }
- }
-}
-
-void Config::WriteSettingGeneric(Settings::BasicSetting* const setting) const {
- if (!setting->Save()) {
- return;
- }
- const QVariant value = QVariant::fromValue(QString::fromStdString(setting->ToString()));
- const QVariant default_value =
- QVariant::fromValue(QString::fromStdString(setting->DefaultToString()));
- const QString label = QString::fromStdString(setting->GetLabel());
- if (setting->Switchable()) {
- if (!global) {
- qt_config->setValue(label + QStringLiteral("/use_global"), setting->UsingGlobal());
- }
- if (global || !setting->UsingGlobal()) {
- qt_config->setValue(label + QStringLiteral("/default"), value == default_value);
- qt_config->setValue(label, value);
- }
- } else if (global) {
- qt_config->setValue(label + QStringLiteral("/default"), value == default_value);
- qt_config->setValue(label, value);
- }
-}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
deleted file mode 100644
index 74ec4f771..000000000
--- a/src/yuzu/configuration/config.h
+++ /dev/null
@@ -1,179 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <memory>
-#include <string>
-#include <QMetaType>
-#include <QVariant>
-#include "common/settings.h"
-#include "common/settings_enums.h"
-#include "yuzu/uisettings.h"
-
-class QSettings;
-
-namespace Core {
-class System;
-}
-
-class Config {
-public:
- enum class ConfigType {
- GlobalConfig,
- PerGameConfig,
- InputProfile,
- };
-
- explicit Config(const std::string& config_name = "qt-config",
- ConfigType config_type = ConfigType::GlobalConfig);
- ~Config();
-
- void Reload();
- void Save();
-
- void ReadControlPlayerValue(std::size_t player_index);
- void SaveControlPlayerValue(std::size_t player_index);
- void ClearControlPlayerValues();
-
- const std::string& GetConfigFilePath() const;
-
- static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
- static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
- static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
- static const std::array<int, 2> default_stick_mod;
- static const std::array<int, 2> default_ringcon_analogs;
- static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
- default_mouse_buttons;
- static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
- static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
- static const std::array<UISettings::Shortcut, 23> default_hotkeys;
-
- 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<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;
-
- static constexpr UISettings::Theme default_theme{
-#ifdef _WIN32
- UISettings::Theme::DarkColorful
-#else
- UISettings::Theme::DefaultColorful
-#endif
- };
-
-private:
- void Initialize(const std::string& config_name);
- bool IsCustomConfig();
-
- void ReadValues();
- void ReadPlayerValue(std::size_t player_index);
- void ReadDebugValues();
- void ReadKeyboardValues();
- void ReadMouseValues();
- void ReadTouchscreenValues();
- void ReadMotionTouchValues();
- void ReadHidbusValues();
- void ReadIrCameraValues();
-
- // Read functions bases off the respective config section names.
- void ReadAudioValues();
- void ReadControlValues();
- void ReadCoreValues();
- void ReadDataStorageValues();
- void ReadDebuggingValues();
- void ReadServiceValues();
- void ReadDisabledAddOnValues();
- void ReadMiscellaneousValues();
- void ReadPathValues();
- void ReadCpuValues();
- void ReadRendererValues();
- void ReadScreenshotValues();
- void ReadShortcutValues();
- void ReadSystemValues();
- void ReadUIValues();
- void ReadUIGamelistValues();
- void ReadUILayoutValues();
- void ReadWebServiceValues();
- void ReadMultiplayerValues();
- void ReadNetworkValues();
-
- void SaveValues();
- void SavePlayerValue(std::size_t player_index);
- void SaveDebugValues();
- void SaveMouseValues();
- void SaveTouchscreenValues();
- void SaveMotionTouchValues();
- void SaveHidbusValues();
- void SaveIrCameraValues();
-
- // Save functions based off the respective config section names.
- void SaveAudioValues();
- void SaveControlValues();
- void SaveCoreValues();
- void SaveDataStorageValues();
- void SaveDebuggingValues();
- void SaveNetworkValues();
- void SaveDisabledAddOnValues();
- void SaveMiscellaneousValues();
- void SavePathValues();
- void SaveCpuValues();
- void SaveRendererValues();
- void SaveScreenshotValues();
- void SaveShortcutValues();
- void SaveSystemValues();
- void SaveUIValues();
- void SaveUIGamelistValues();
- void SaveUILayoutValues();
- void SaveWebServiceValues();
- void SaveMultiplayerValues();
-
- /**
- * Reads a setting from the qt_config.
- *
- * @param name The setting's identifier
- * @param default_value The value to use when the setting is not already present in the config
- */
- QVariant ReadSetting(const QString& name) const;
- QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
-
- /**
- * Writes a setting to the qt_config.
- *
- * @param name The setting's idetentifier
- * @param value Value of the setting
- * @param default_value Default of the setting if not present in qt_config
- * @param use_global Specifies if the custom or global config should be in use, for custom
- * configs
- */
- void WriteSetting(const QString& name, const QVariant& value);
- void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
- void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
- bool use_global);
-
- void ReadCategory(Settings::Category category);
- void WriteCategory(Settings::Category category);
- void ReadSettingGeneric(Settings::BasicSetting* const setting);
- void WriteSettingGeneric(Settings::BasicSetting* const setting) const;
-
- const ConfigType type;
- std::unique_ptr<QSettings> qt_config;
- std::string qt_config_loc;
- const bool global;
-};
-
-// These metatype declarations cannot be in common/settings.h because core is devoid of QT
-Q_DECLARE_METATYPE(Settings::CpuAccuracy);
-Q_DECLARE_METATYPE(Settings::GpuAccuracy);
-Q_DECLARE_METATYPE(Settings::FullscreenMode);
-Q_DECLARE_METATYPE(Settings::NvdecEmulation);
-Q_DECLARE_METATYPE(Settings::ResolutionSetup);
-Q_DECLARE_METATYPE(Settings::ScalingFilter);
-Q_DECLARE_METATYPE(Settings::AntiAliasing);
-Q_DECLARE_METATYPE(Settings::RendererBackend);
-Q_DECLARE_METATYPE(Settings::ShaderBackend);
-Q_DECLARE_METATYPE(Settings::AstcRecompression);
-Q_DECLARE_METATYPE(Settings::AstcDecodeMode);
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 81dd51ad3..9b6ef47a7 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -38,17 +38,21 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
std::map<u32, QWidget*> hold;
- auto push = [&](Settings::Category category) {
+ auto push_settings = [&](Settings::Category category) {
for (auto* setting : Settings::values.linkage.by_category[category]) {
settings.push_back(setting);
}
+ };
+
+ auto push_ui_settings = [&](Settings::Category category) {
for (auto* setting : UISettings::values.linkage.by_category[category]) {
settings.push_back(setting);
}
};
- push(Settings::Category::Audio);
- push(Settings::Category::SystemAudio);
+ push_settings(Settings::Category::Audio);
+ push_settings(Settings::Category::SystemAudio);
+ push_ui_settings(Settings::Category::UiAudio);
for (auto* setting : settings) {
auto* widget = builder.BuildWidget(setting, apply_funcs);
diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp
index d95e96696..3368f53f3 100644
--- a/src/yuzu/configuration/configure_camera.cpp
+++ b/src/yuzu/configuration/configure_camera.cpp
@@ -10,10 +10,10 @@
#include <QStandardItemModel>
#include <QTimer>
+#include "common/settings.h"
#include "input_common/drivers/camera.h"
#include "input_common/main.h"
#include "ui_configure_camera.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_camera.h"
ConfigureCamera::ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_)
diff --git a/src/yuzu/configuration/configure_camera.h b/src/yuzu/configuration/configure_camera.h
index 9a90512b3..3d822da7b 100644
--- a/src/yuzu/configuration/configure_camera.h
+++ b/src/yuzu/configuration/configure_camera.h
@@ -1,4 +1,4 @@
-// Text : Copyright 2022 yuzu Emulator Project
+// Text : Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index a51359903..7e16cf17d 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -27,6 +27,13 @@ ConfigureCpu::ConfigureCpu(const Core::System& system_,
connect(accuracy_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureCpu::UpdateGroup);
+
+ connect(backend_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ &ConfigureCpu::UpdateGroup);
+
+#ifdef HAS_NCE
+ ui->backend_group->setVisible(true);
+#endif
}
ConfigureCpu::~ConfigureCpu() = default;
@@ -34,6 +41,7 @@ ConfigureCpu::~ConfigureCpu() = default;
void ConfigureCpu::SetConfiguration() {}
void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
auto* accuracy_layout = ui->widget_accuracy->layout();
+ auto* backend_layout = ui->widget_backend->layout();
auto* unsafe_layout = ui->unsafe_widget->layout();
std::map<u32, QWidget*> unsafe_hold{};
@@ -62,6 +70,9 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
// Keep track of cpu_accuracy combobox to display/hide the unsafe settings
accuracy_layout->addWidget(widget);
accuracy_combobox = widget->combobox;
+ } else if (setting->Id() == Settings::values.cpu_backend.Id()) {
+ backend_layout->addWidget(widget);
+ backend_combobox = widget->combobox;
} else {
// Presently, all other settings here are unsafe checkboxes
unsafe_hold.insert({setting->Id(), widget});
@@ -73,6 +84,7 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
}
UpdateGroup(accuracy_combobox->currentIndex());
+ UpdateGroup(backend_combobox->currentIndex());
}
void ConfigureCpu::UpdateGroup(int index) {
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
index 61a6de7aa..a102b4c1f 100644
--- a/src/yuzu/configuration/configure_cpu.h
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -49,4 +49,5 @@ private:
std::vector<std::function<void(bool)>> apply_funcs{};
QComboBox* accuracy_combobox;
+ QComboBox* backend_combobox;
};
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index f734e842e..13fd43605 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -60,6 +60,36 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="backend_group">
+ <property name="title">
+ <string>CPU Backend</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QWidget" name="widget_backend" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout1">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="unsafe_group">
<property name="title">
<string>Unsafe CPU Optimization Settings</string>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index b22fda746..1010038b7 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -27,16 +27,6 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
connect(ui->toggle_gdbstub, &QCheckBox::toggled,
[&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
-
- connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) {
- if (crash_dump_warning_shown) {
- return;
- }
- QMessageBox::warning(this, tr("Restart Required"),
- tr("yuzu is required to restart in order to apply this setting."),
- QMessageBox::Ok, QMessageBox::Ok);
- crash_dump_warning_shown = true;
- });
}
ConfigureDebug::~ConfigureDebug() = default;
@@ -61,6 +51,8 @@ void ConfigureDebug::SetConfiguration() {
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->disable_buffer_reorder->setEnabled(runtime_lock);
+ ui->disable_buffer_reorder->setChecked(Settings::values.disable_buffer_reorder.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);
@@ -89,13 +81,6 @@ void ConfigureDebug::SetConfiguration() {
ui->disable_web_applet->setEnabled(false);
ui->disable_web_applet->setText(tr("Web applet not compiled"));
#endif
-
-#ifdef YUZU_DBGHELP
- ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue());
-#else
- ui->create_crash_dumps->setEnabled(false);
- ui->create_crash_dumps->setText(tr("MiniDump creation not compiled"));
-#endif
}
void ConfigureDebug::ApplyConfiguration() {
@@ -107,13 +92,13 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
Settings::values.reporting_services = ui->reporting_services->isChecked();
Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
- Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked();
Settings::values.quest_flag = ui->quest_flag->isChecked();
Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
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.disable_buffer_reorder = ui->disable_buffer_reorder->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 66b8b7459..22b51f39c 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -271,19 +271,6 @@
</widget>
</item>
<item row="8" column="0">
- <widget class="QCheckBox" name="disable_macro_hle">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
- </property>
- <property name="text">
- <string>Disable Macro HLE</string>
- </property>
- </widget>
- </item>
- <item row="7" column="0">
<widget class="QCheckBox" name="dump_macros">
<property name="enabled">
<bool>true</bool>
@@ -306,17 +293,27 @@
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="enable_shader_feedback">
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="dump_shaders">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
<property name="toolTip">
- <string>When checked, yuzu will log statistics about the compiled pipeline cache</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>Enable Shader Feedback</string>
+ <string>Dump Game Shaders</string>
</property>
</widget>
</item>
- <item row="6" column="0">
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="enable_renderdoc_hotkey">
+ <property name="text">
+ <string>Enable Renderdoc Hotkey</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
<widget class="QCheckBox" name="disable_macro_jit">
<property name="enabled">
<bool>true</bool>
@@ -330,20 +327,17 @@
</widget>
</item>
<item row="9" column="0">
- <spacer name="verticalSpacer_5">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ <widget class="QCheckBox" name="disable_macro_hle">
+ <property name="enabled">
+ <bool>true</bool>
</property>
- <property name="sizeType">
- <enum>QSizePolicy::Preferred</enum>
+ <property name="toolTip">
+ <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>0</height>
- </size>
+ <property name="text">
+ <string>Disable Macro HLE</string>
</property>
- </spacer>
+ </widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="enable_graphics_debugging">
@@ -358,23 +352,39 @@
</property>
</widget>
</item>
- <item row="5" column="0">
- <widget class="QCheckBox" name="dump_shaders">
- <property name="enabled">
- <bool>true</bool>
+ <item row="10" 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="2" column="0">
+ <widget class="QCheckBox" name="enable_shader_feedback">
<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, yuzu will log statistics about the compiled pipeline cache</string>
</property>
<property name="text">
- <string>Dump Game Shaders</string>
+ <string>Enable Shader Feedback</string>
</property>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="enable_renderdoc_hotkey">
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="disable_buffer_reorder">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When checked, disables reording of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
<property name="text">
- <string>Enable Renderdoc Hotkey</string>
+ <string>Disable Buffer Reorder</string>
</property>
</widget>
</item>
@@ -471,13 +481,6 @@
</property>
</widget>
</item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="create_crash_dumps">
- <property name="text">
- <string>Create Minidump After Crash</string>
- </property>
- </widget>
- </item>
<item row="3" column="0">
<widget class="QCheckBox" name="dump_audio_commands">
<property name="toolTip">
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 0ad95cc02..aab54a1cc 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -8,7 +8,6 @@
#include "core/core.h"
#include "ui_configure.h"
#include "vk_device_info.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_audio.h"
#include "yuzu/configuration/configure_cpu.h"
#include "yuzu/configuration/configure_debug_tab.h"
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index c727fadd1..701b895e7 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -36,12 +36,29 @@ ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() {}
void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
- QLayout& layout = *ui->general_widget->layout();
+ QLayout& general_layout = *ui->general_widget->layout();
+ QLayout& linux_layout = *ui->linux_widget->layout();
- std::map<u32, QWidget*> hold{};
+ std::map<u32, QWidget*> general_hold{};
+ std::map<u32, QWidget*> linux_hold{};
- for (const auto setting :
- UISettings::values.linkage.by_category[Settings::Category::UiGeneral]) {
+ std::vector<Settings::BasicSetting*> settings;
+
+ auto push = [&settings](auto& list) {
+ for (auto setting : list) {
+ settings.push_back(setting);
+ }
+ };
+
+ push(UISettings::values.linkage.by_category[Settings::Category::UiGeneral]);
+ push(Settings::values.linkage.by_category[Settings::Category::Linux]);
+
+ // Only show Linux group on Unix
+#ifndef __unix__
+ ui->LinuxGroupBox->setVisible(false);
+#endif
+
+ for (const auto setting : settings) {
auto* widget = builder.BuildWidget(setting, apply_funcs);
if (widget == nullptr) {
@@ -52,11 +69,23 @@ void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
continue;
}
- hold.emplace(setting->Id(), widget);
+ switch (setting->GetCategory()) {
+ case Settings::Category::UiGeneral:
+ general_hold.emplace(setting->Id(), widget);
+ break;
+ case Settings::Category::Linux:
+ linux_hold.emplace(setting->Id(), widget);
+ break;
+ default:
+ widget->deleteLater();
+ }
}
- for (const auto& [id, widget] : hold) {
- layout.addWidget(widget);
+ for (const auto& [id, widget] : general_hold) {
+ general_layout.addWidget(widget);
+ }
+ for (const auto& [id, widget] : linux_hold) {
+ linux_layout.addWidget(widget);
}
}
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index a10e7d3a5..ef20891a3 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -47,6 +47,33 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="LinuxGroupBox">
+ <property name="title">
+ <string>Linux</string>
+ </property>
+ <layout class="QVBoxLayout" name="LinuxVerticalLayout_1">
+ <item>
+ <widget class="QWidget" name="linux_widget" native="true">
+ <layout class="QVBoxLayout" name="LinuxVerticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index 68e21cd84..76fc33e49 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -9,10 +9,11 @@
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
+#include "frontend_common/config.h"
#include "ui_configure_hotkeys.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_hotkeys.h"
#include "yuzu/hotkeys.h"
+#include "yuzu/uisettings.h"
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
constexpr int name_column = 0;
@@ -62,18 +63,21 @@ ConfigureHotkeys::~ConfigureHotkeys() = default;
void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
for (const auto& group : registry.hotkey_groups) {
+ QString parent_item_data = QString::fromStdString(group.first);
auto* parent_item =
- new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(group.first)));
+ new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(parent_item_data)));
parent_item->setEditable(false);
- parent_item->setData(group.first);
+ parent_item->setData(parent_item_data);
for (const auto& hotkey : group.second) {
- auto* action =
- new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(hotkey.first)));
+ QString hotkey_action_data = QString::fromStdString(hotkey.first);
+ auto* action = new QStandardItem(
+ QCoreApplication::translate("Hotkeys", qPrintable(hotkey_action_data)));
auto* keyseq =
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
- auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq);
+ auto* controller_keyseq =
+ new QStandardItem(QString::fromStdString(hotkey.second.controller_keyseq));
action->setEditable(false);
- action->setData(hotkey.first);
+ action->setData(hotkey_action_data);
keyseq->setEditable(false);
controller_keyseq->setEditable(false);
parent_item->appendRow({action, keyseq, controller_keyseq});
@@ -301,13 +305,13 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
const QStandardItem* controller_keyseq =
parent->child(key_column_id, controller_column);
for (auto& [group, sub_actions] : registry.hotkey_groups) {
- if (group != parent->data())
+ if (group != parent->data().toString().toStdString())
continue;
for (auto& [action_name, hotkey] : sub_actions) {
- if (action_name != action->data())
+ if (action_name != action->data().toString().toStdString())
continue;
hotkey.keyseq = QKeySequence(keyseq->text());
- hotkey.controller_keyseq = controller_keyseq->text();
+ hotkey.controller_keyseq = controller_keyseq->text().toStdString();
}
}
}
@@ -319,7 +323,7 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
void ConfigureHotkeys::RestoreDefaults() {
for (int r = 0; r < model->rowCount(); ++r) {
const QStandardItem* parent = model->item(r, 0);
- const int hotkey_size = static_cast<int>(Config::default_hotkeys.size());
+ const int hotkey_size = static_cast<int>(UISettings::default_hotkeys.size());
if (hotkey_size != parent->rowCount()) {
QMessageBox::warning(this, tr("Invalid hotkey settings"),
@@ -330,10 +334,11 @@ void ConfigureHotkeys::RestoreDefaults() {
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
model->item(r, 0)
->child(r2, hotkey_column)
- ->setText(Config::default_hotkeys[r2].shortcut.keyseq);
+ ->setText(QString::fromStdString(UISettings::default_hotkeys[r2].shortcut.keyseq));
model->item(r, 0)
->child(r2, controller_column)
- ->setText(Config::default_hotkeys[r2].shortcut.controller_keyseq);
+ ->setText(QString::fromStdString(
+ UISettings::default_hotkeys[r2].shortcut.controller_keyseq));
}
}
}
@@ -379,7 +384,7 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
void ConfigureHotkeys::RestoreControllerHotkey(QModelIndex index) {
const QString& default_key_sequence =
- Config::default_hotkeys[index.row()].shortcut.controller_keyseq;
+ QString::fromStdString(UISettings::default_hotkeys[index.row()].shortcut.controller_keyseq);
const auto [key_sequence_used, used_action] = IsUsedControllerKey(default_key_sequence);
if (key_sequence_used && default_key_sequence != model->data(index).toString()) {
@@ -393,7 +398,8 @@ void ConfigureHotkeys::RestoreControllerHotkey(QModelIndex index) {
void ConfigureHotkeys::RestoreHotkey(QModelIndex index) {
const QKeySequence& default_key_sequence = QKeySequence::fromString(
- Config::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText);
+ QString::fromStdString(UISettings::default_hotkeys[index.row()].shortcut.keyseq),
+ QKeySequence::NativeText);
const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) {
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 5a48e388b..02e23cce6 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -101,13 +101,13 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,
};
- player_connected = {
+ connected_controller_checkboxes = {
ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
};
- std::array<QLabel*, 8> player_connected_labels = {
+ std::array<QLabel*, 8> connected_controller_labels = {
ui->label, ui->label_3, ui->label_4, ui->label_5,
ui->label_6, ui->label_7, ui->label_8, ui->label_9,
};
@@ -115,30 +115,44 @@ 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_connected[i], &QCheckBox::clicked, [this, i](int checked) {
- // Ensures that the controllers are always connected in sequential order
- this->propagateMouseClickOnPlayers(i, checked, true);
+ connect(player_controllers[i], &ConfigureInputPlayer::Connected, [this, i](bool checked) {
+ // Ensures that connecting a controller changes the number of players
+ if (connected_controller_checkboxes[i]->isChecked() != checked) {
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked);
+ }
+ });
+ connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
+ // Reconnect current controller if it was the last one checked
+ // (player number was reduced by more than one)
+ const bool reconnect_first = !checked &&
+ i < connected_controller_checkboxes.size() - 1 &&
+ connected_controller_checkboxes[i + 1]->isChecked();
+
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked, reconnect_first);
});
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
&ConfigureInput::UpdateAllInputDevices);
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
&ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
- connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
+ connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
+ // Keep activated controllers synced with the "Connected Controllers" checkboxes
player_controllers[i]->ConnectPlayer(state == Qt::Checked);
});
// Remove/hide all the elements that exceed max_players, if applicable.
if (i >= max_players) {
ui->tabWidget->removeTab(static_cast<int>(max_players));
- player_connected[i]->hide();
- player_connected_labels[i]->hide();
+ connected_controller_checkboxes[i]->hide();
+ connected_controller_labels[i]->hide();
}
}
// Only the first player can choose handheld mode so connect the signal just to player 1
connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged,
[this](bool is_handheld) { UpdateDockedState(is_handheld); });
- advanced = new ConfigureInputAdvanced(this);
+ advanced = new ConfigureInputAdvanced(hid_core, this);
ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
ui->tabAdvanced->layout()->addWidget(advanced);
@@ -175,28 +189,25 @@ 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);
- }
+void ConfigureInput::PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current) {
+ connected_controller_checkboxes[player_index]->setChecked(checked);
if (checked) {
// Check all previous buttons when checked
if (player_index > 0) {
- propagateMouseClickOnPlayers(player_index - 1, checked, false);
+ PropagatePlayerNumberChanged(player_index - 1, checked);
}
} 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);
+ if (player_index < connected_controller_checkboxes.size() - 1) {
+ PropagatePlayerNumberChanged(player_index + 1, checked);
}
}
+
+ if (reconnect_current) {
+ connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
+ }
}
QList<QWidget*> ConfigureInput::GetSubTabs() const {
@@ -249,17 +260,17 @@ void ConfigureInput::LoadConfiguration() {
}
void ConfigureInput::LoadPlayerControllerIndices() {
- for (std::size_t i = 0; i < player_connected.size(); ++i) {
+ for (std::size_t i = 0; i < connected_controller_checkboxes.size(); ++i) {
if (i == 0) {
auto* handheld =
system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
if (handheld->IsConnected()) {
- player_connected[i]->setChecked(true);
+ connected_controller_checkboxes[i]->setChecked(true);
continue;
}
}
const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i);
- player_connected[i]->setChecked(controller->IsConnected());
+ connected_controller_checkboxes[i]->setChecked(controller->IsConnected());
}
}
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index abb7f7089..beb503dae 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -56,7 +56,9 @@ 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);
+ // Enable preceding controllers or disable following ones
+ void PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current = false);
/// Load configuration settings.
void LoadConfiguration();
@@ -71,7 +73,8 @@ private:
std::array<ConfigureInputPlayer*, 8> player_controllers;
std::array<QWidget*, 8> player_tabs;
- std::array<QCheckBox*, 8> player_connected;
+ // Checkboxes representing the "Connected Controllers".
+ std::array<QCheckBox*, 8> connected_controller_checkboxes;
ConfigureInputAdvanced* advanced;
Core::System& system;
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index 3cfd5d439..441cea3f6 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -4,11 +4,13 @@
#include <QColorDialog>
#include "common/settings.h"
#include "core/core.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
#include "ui_configure_input_advanced.h"
#include "yuzu/configuration/configure_input_advanced.h"
-ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
- : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputAdvanced>()) {
+ConfigureInputAdvanced::ConfigureInputAdvanced(Core::HID::HIDCore& hid_core_, QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputAdvanced>()), hid_core{hid_core_} {
ui->setupUi(this);
controllers_color_buttons = {{
@@ -123,6 +125,8 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
player.button_color_left = colors[1];
player.body_color_right = colors[2];
player.button_color_right = colors[3];
+
+ hid_core.GetEmulatedControllerByIndex(player_idx)->ReloadColorsFromSettings();
}
Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked();
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
index fc1230284..41f822c4a 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -14,11 +14,15 @@ namespace Ui {
class ConfigureInputAdvanced;
}
+namespace Core::HID {
+class HIDCore;
+} // namespace Core::HID
+
class ConfigureInputAdvanced : public QWidget {
Q_OBJECT
public:
- explicit ConfigureInputAdvanced(QWidget* parent = nullptr);
+ explicit ConfigureInputAdvanced(Core::HID::HIDCore& hid_core_, QWidget* parent = nullptr);
~ConfigureInputAdvanced() override;
void ApplyConfiguration();
@@ -44,4 +48,6 @@ private:
std::array<std::array<QColor, 4>, 8> controllers_colors;
std::array<std::array<QPushButton*, 4>, 8> controllers_color_buttons;
+
+ Core::HID::HIDCore& hid_core;
};
diff --git a/src/yuzu/configuration/configure_input_per_game.cpp b/src/yuzu/configuration/configure_input_per_game.cpp
index 78e65d468..8d9f65a05 100644
--- a/src/yuzu/configuration/configure_input_per_game.cpp
+++ b/src/yuzu/configuration/configure_input_per_game.cpp
@@ -5,12 +5,12 @@
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
+#include "frontend_common/config.h"
#include "ui_configure_input_per_game.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_per_game.h"
#include "yuzu/configuration/input_profiles.h"
-ConfigureInputPerGame::ConfigureInputPerGame(Core::System& system_, Config* config_,
+ConfigureInputPerGame::ConfigureInputPerGame(Core::System& system_, QtConfig* config_,
QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPerGame>()),
profiles(std::make_unique<InputProfiles>()), system{system_}, config{config_} {
@@ -110,6 +110,6 @@ void ConfigureInputPerGame::SaveConfiguration() {
// Clear all controls from the config in case the user reverted back to globals
config->ClearControlPlayerValues();
for (size_t index = 0; index < Settings::values.players.GetValue().size(); ++index) {
- config->SaveControlPlayerValue(index);
+ config->SaveQtControlPlayerValues(index);
}
}
diff --git a/src/yuzu/configuration/configure_input_per_game.h b/src/yuzu/configuration/configure_input_per_game.h
index 660faf574..4420e856c 100644
--- a/src/yuzu/configuration/configure_input_per_game.h
+++ b/src/yuzu/configuration/configure_input_per_game.h
@@ -9,6 +9,7 @@
#include "ui_configure_input_per_game.h"
#include "yuzu/configuration/input_profiles.h"
+#include "yuzu/configuration/qt_config.h"
class QComboBox;
@@ -22,7 +23,7 @@ class ConfigureInputPerGame : public QWidget {
Q_OBJECT
public:
- explicit ConfigureInputPerGame(Core::System& system_, Config* config_,
+ explicit ConfigureInputPerGame(Core::System& system_, QtConfig* config_,
QWidget* parent = nullptr);
/// Load and Save configurations to settings file.
@@ -41,5 +42,5 @@ private:
std::array<QComboBox*, 8> profile_comboboxes;
Core::System& system;
- Config* config;
+ QtConfig* config;
};
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 576f5b571..0f7b3714e 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -12,15 +12,16 @@
#include <QTimer>
#include "common/assert.h"
#include "common/param_package.h"
+#include "configuration/qt_config.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
+#include "frontend_common/config.h"
#include "input_common/drivers/keyboard.h"
#include "input_common/drivers/mouse.h"
#include "input_common/main.h"
#include "ui_configure_input_player.h"
#include "yuzu/bootmanager.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_input_player_widget.h"
#include "yuzu/configuration/configure_mouse_panning.h"
@@ -322,11 +323,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
setFocusPolicy(Qt::ClickFocus);
button_map = {
- ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
- ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
- ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
- ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
- ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
+ ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
+ ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
+ ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
+ ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
+ ui->buttonSLLeft, ui->buttonSRLeft, ui->buttonHome, ui->buttonScreenshot,
+ ui->buttonSLRight, ui->buttonSRRight,
};
analog_map_buttons = {{
@@ -1181,10 +1183,13 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
// List of all the widgets that will be hidden by any of the following layouts that need
// "unhidden" after the controller type changes
- const std::array<QWidget*, 11> layout_show = {
- ui->buttonShoulderButtonsSLSR,
+ const std::array<QWidget*, 14> layout_show = {
+ ui->buttonShoulderButtonsSLSRLeft,
+ ui->buttonShoulderButtonsSLSRRight,
ui->horizontalSpacerShoulderButtonsWidget,
ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->horizontalSpacerShoulderButtonsWidget3,
+ ui->horizontalSpacerShoulderButtonsWidget4,
ui->buttonShoulderButtonsLeft,
ui->buttonMiscButtonsMinusScreenshot,
ui->bottomLeft,
@@ -1202,16 +1207,19 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
std::vector<QWidget*> layout_hidden;
switch (layout) {
case Core::HID::NpadStyleIndex::ProController:
- case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::Handheld:
layout_hidden = {
- ui->buttonShoulderButtonsSLSR,
+ ui->buttonShoulderButtonsSLSRLeft,
+ ui->buttonShoulderButtonsSLSRRight,
ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->horizontalSpacerShoulderButtonsWidget4,
};
break;
case Core::HID::NpadStyleIndex::JoyconLeft:
layout_hidden = {
+ ui->buttonShoulderButtonsSLSRRight,
ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->horizontalSpacerShoulderButtonsWidget3,
ui->buttonShoulderButtonsRight,
ui->buttonMiscButtonsPlusHome,
ui->bottomRight,
@@ -1219,16 +1227,17 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
break;
case Core::HID::NpadStyleIndex::JoyconRight:
layout_hidden = {
- ui->horizontalSpacerShoulderButtonsWidget,
- ui->buttonShoulderButtonsLeft,
- ui->buttonMiscButtonsMinusScreenshot,
- ui->bottomLeft,
+ ui->buttonShoulderButtonsSLSRLeft, ui->horizontalSpacerShoulderButtonsWidget,
+ ui->horizontalSpacerShoulderButtonsWidget4, ui->buttonShoulderButtonsLeft,
+ ui->buttonMiscButtonsMinusScreenshot, ui->bottomLeft,
};
break;
case Core::HID::NpadStyleIndex::GameCube:
layout_hidden = {
- ui->buttonShoulderButtonsSLSR,
+ ui->buttonShoulderButtonsSLSRLeft,
+ ui->buttonShoulderButtonsSLSRRight,
ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->horizontalSpacerShoulderButtonsWidget4,
ui->buttonMiscButtonsMinusGroup,
ui->buttonMiscButtonsScreenshotGroup,
};
@@ -1389,25 +1398,25 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
emulated_controller->SetButtonParam(
button_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
- Config::default_buttons[button_id])});
+ QtConfig::default_buttons[button_id])});
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
Common::ParamPackage analog_param{};
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
- Config::default_analogs[analog_id][sub_button_id])};
+ QtConfig::default_analogs[analog_id][sub_button_id])};
SetAnalogParam(params, analog_param, analog_sub_buttons[sub_button_id]);
}
analog_param.Set("modifier", InputCommon::GenerateKeyboardParam(
- Config::default_stick_mod[analog_id]));
+ QtConfig::default_stick_mod[analog_id]));
emulated_controller->SetStickParam(analog_id, analog_param);
}
for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
emulated_controller->SetMotionParam(
motion_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
- Config::default_motions[motion_id])});
+ QtConfig::default_motions[motion_id])});
}
// If mouse is selected we want to override with mappings from the driver
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index d4df43d73..fda09e925 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -75,7 +75,7 @@ public:
void ClearAll();
signals:
- /// Emitted when this controller is connected by the user.
+ /// Emitted when this controller is (dis)connected by the user.
void Connected(bool connected);
/// Emitted when the Handheld mode is selected (undocked with dual joycons attached).
void HandheldStateChanged(bool is_handheld);
@@ -183,9 +183,6 @@ private:
/// Stores a pair of "Connected Controllers" combobox index and Controller Type enum.
std::vector<std::pair<int, Core::HID::NpadStyleIndex>> index_controller_type_pairs;
- static constexpr int PLAYER_COUNT = 8;
- std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox;
-
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index 611a79477..5518cccd1 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -1208,6 +1208,159 @@
<property name="spacing">
<number>3</number>
</property>
+ <item>
+ <widget class="QWidget" name="buttonShoulderButtonsSLSRLeft" native="true">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSLSRLeftVerticalLayout">
+ <property name="spacing">
+ <number>0</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 alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonShoulderButtonsSLLeftGroup">
+ <property name="title">
+ <string>SL</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSLLeftVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonSLLeft">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>SL</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonShoulderButtonsSRLeftGroup">
+ <property name="title">
+ <string>SR</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSRLeftVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonSRLeft">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>SR</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalSpacerShoulderButtonsWidget4" native="true">
+ <layout class="QHBoxLayout" name="horizontalSpacerShoulderButtonsWidget4Layout">
+ <property name="spacing">
+ <number>0</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>
+ <spacer name="horizontalSpacerShoulderButtons5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
<item>
<widget class="QWidget" name="buttonShoulderButtonsLeft" native="true">
<layout class="QVBoxLayout" name="buttonShoulderButtonsLeftVerticalLayout">
@@ -1830,125 +1983,125 @@
</layout>
</widget>
</item>
- <item>
- <widget class="QWidget" name="buttonShoulderButtonsSLSR" native="true">
- <layout class="QVBoxLayout" name="buttonShoulderButtonsSLSRVerticalLayout">
- <property name="spacing">
- <number>0</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 alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="buttonShoulderButtonsSLGroup">
- <property name="title">
- <string>SL</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout">
- <property name="spacing">
- <number>3</number>
- </property>
- <property name="leftMargin">
- <number>3</number>
- </property>
- <property name="topMargin">
- <number>3</number>
- </property>
- <property name="rightMargin">
- <number>3</number>
- </property>
- <property name="bottomMargin">
- <number>3</number>
- </property>
- <item>
- <widget class="QPushButton" name="buttonSL">
- <property name="minimumSize">
- <size>
- <width>68</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>68</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="styleSheet">
- <string notr="true">min-width: 68px;</string>
- </property>
- <property name="text">
- <string>SL</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="buttonShoulderButtonsSRGroup">
- <property name="title">
- <string>SR</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout">
- <property name="spacing">
- <number>3</number>
- </property>
- <property name="leftMargin">
- <number>3</number>
- </property>
- <property name="topMargin">
- <number>3</number>
- </property>
- <property name="rightMargin">
- <number>3</number>
- </property>
- <property name="bottomMargin">
- <number>3</number>
- </property>
- <item>
- <widget class="QPushButton" name="buttonSR">
- <property name="minimumSize">
- <size>
- <width>68</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>68</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="styleSheet">
- <string notr="true">min-width: 68px;</string>
- </property>
- <property name="text">
- <string>SR</string>
- </property>
- </widget>
- </item>
+ <item>
+ <widget class="QWidget" name="buttonShoulderButtonsSLSRRight" native="true">
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSLSRRightVerticalLayout">
+ <property name="spacing">
+ <number>0</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 alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonShoulderButtonsSLRightGroup">
+ <property name="title">
+ <string>SL</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSLRightVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonSLRight">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>SL</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="buttonShoulderButtonsSRRightGroup">
+ <property name="title">
+ <string>SR</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonShoulderButtonsSRRightVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonSRRight">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>SR</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
</layout>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
+ </widget>
+ </item>
</layout>
</item>
<item>
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index a188eef92..550cff9a0 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -297,8 +297,8 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center)
// Sideview SL and SR buttons
button_color = colors.slider_button;
- DrawRoundButton(p, center + QPoint(59, 52), button_values[SR], 5, 12, Direction::Left);
- DrawRoundButton(p, center + QPoint(59, -69), button_values[SL], 5, 12, Direction::Left);
+ DrawRoundButton(p, center + QPoint(59, 52), button_values[SRLeft], 5, 12, Direction::Left);
+ DrawRoundButton(p, center + QPoint(59, -69), button_values[SLLeft], 5, 12, Direction::Left);
DrawLeftBody(p, center);
@@ -353,8 +353,10 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center)
// SR and SL buttons
p.setPen(colors.outline);
button_color = colors.slider_button;
- DrawRoundButton(p, center + QPoint(155, 52), button_values[SR], 5.2f, 12, Direction::None, 4);
- DrawRoundButton(p, center + QPoint(155, -69), button_values[SL], 5.2f, 12, Direction::None, 4);
+ DrawRoundButton(p, center + QPoint(155, 52), button_values[SRLeft], 5.2f, 12, Direction::None,
+ 4);
+ DrawRoundButton(p, center + QPoint(155, -69), button_values[SLLeft], 5.2f, 12, Direction::None,
+ 4);
// SR and SL text
p.setPen(colors.transparent);
@@ -428,8 +430,10 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center
// Sideview SL and SR buttons
button_color = colors.slider_button;
- DrawRoundButton(p, center + QPoint(-59, 52), button_values[SL], 5, 11, Direction::Right);
- DrawRoundButton(p, center + QPoint(-59, -69), button_values[SR], 5, 11, Direction::Right);
+ DrawRoundButton(p, center + QPoint(-59, 52), button_values[SLRight], 5, 11,
+ Direction::Right);
+ DrawRoundButton(p, center + QPoint(-59, -69), button_values[SRRight], 5, 11,
+ Direction::Right);
DrawRightBody(p, center);
@@ -484,8 +488,10 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center
// SR and SL buttons
p.setPen(colors.outline);
button_color = colors.slider_button;
- DrawRoundButton(p, center + QPoint(-155, 52), button_values[SL], 5, 12, Direction::None, 4.0f);
- DrawRoundButton(p, center + QPoint(-155, -69), button_values[SR], 5, 12, Direction::None, 4.0f);
+ DrawRoundButton(p, center + QPoint(-155, 52), button_values[SLRight], 5, 12, Direction::None,
+ 4.0f);
+ DrawRoundButton(p, center + QPoint(-155, -69), button_values[SRRight], 5, 12, Direction::None,
+ 4.0f);
// SR and SL text
p.setPen(colors.transparent);
@@ -557,6 +563,19 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center)
DrawRoundButton(p, center + QPoint(-154, -72), button_values[Minus], 7, 4, Direction::Up,
1);
+ // Left SR and SL sideview buttons
+ button_color = colors.slider_button;
+ DrawRoundButton(p, center + QPoint(-20, -62), button_values[SLLeft], 4, 11,
+ Direction::Left);
+ DrawRoundButton(p, center + QPoint(-20, 47), button_values[SRLeft], 4, 11, Direction::Left);
+
+ // Right SR and SL sideview buttons
+ button_color = colors.slider_button;
+ DrawRoundButton(p, center + QPoint(20, 47), button_values[SLRight], 4, 11,
+ Direction::Right);
+ DrawRoundButton(p, center + QPoint(20, -62), button_values[SRRight], 4, 11,
+ Direction::Right);
+
DrawDualBody(p, center);
// Right trigger top view
@@ -1792,16 +1811,6 @@ void PlayerControlPreview::DrawDualBody(QPainter& p, const QPointF center) {
p.setBrush(colors.right);
DrawPolygon(p, qright_joycon_topview);
- // Right SR and SL sideview buttons
- p.setPen(colors.outline);
- p.setBrush(colors.slider_button);
- DrawRoundRectangle(p, center + QPoint(19, 47), 7, 22, 1);
- DrawRoundRectangle(p, center + QPoint(19, -62), 7, 22, 1);
-
- // Left SR and SL sideview buttons
- DrawRoundRectangle(p, center + QPoint(-19, 47), 7, 22, 1);
- DrawRoundRectangle(p, center + QPoint(-19, -62), 7, 22, 1);
-
// Right Sideview body
p.setBrush(colors.slider);
DrawPolygon(p, qright_joycon_slider);
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index b91d6ad4a..b274a3321 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -25,8 +25,8 @@
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
+#include "frontend_common/config.h"
#include "ui_configure_per_game.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_audio.h"
#include "yuzu/configuration/configure_cpu.h"
@@ -50,8 +50,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
- game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
-
+ game_config = std::make_unique<QtConfig>(config_file_name, Config::ConfigType::PerGameConfig);
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this);
cpu_tab = std::make_unique<ConfigureCpu>(system_, tab_group, *builder, this);
@@ -108,7 +107,7 @@ void ConfigurePerGame::ApplyConfiguration() {
system.ApplySettings();
Settings::LogSettings();
- game_config->Save();
+ game_config->SaveAllValues();
}
void ConfigurePerGame::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index 1a727f32c..c8ee46c04 100644
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -12,9 +12,10 @@
#include "configuration/shared_widget.h"
#include "core/file_sys/vfs_types.h"
+#include "frontend_common/config.h"
#include "vk_device_info.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/qt_config.h"
#include "yuzu/configuration/shared_translation.h"
namespace Core {
@@ -72,7 +73,7 @@ private:
QGraphicsScene* scene;
- std::unique_ptr<Config> game_config;
+ std::unique_ptr<QtConfig> game_config;
Core::System& system;
std::unique_ptr<ConfigurationShared::Builder> builder;
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 674a75a62..140a7fe5d 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -19,7 +19,6 @@
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
#include "ui_configure_per_game_addons.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_per_game_addons.h"
#include "yuzu/uisettings.h"
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index a47089988..6d2219bf5 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -306,10 +306,10 @@ void ConfigureProfileManager::SetUserImage() {
return;
}
- // Some games crash when the profile image is too big. Resize any image bigger than 256x256
+ // Profile image must be 256x256
QImage image(image_path);
- if (image.width() > 256 || image.height() > 256) {
- image = image.scaled(256, 256, Qt::KeepAspectRatio);
+ if (image.width() != 256 || image.height() != 256) {
+ image = image.scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
if (!image.save(image_path)) {
QMessageBox::warning(this, tr("Error resizing user image"),
tr("Unable to resize image"));
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
index f83705544..9572ff43c 100644
--- a/src/yuzu/configuration/configure_ringcon.cpp
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -8,6 +8,7 @@
#include <QTimer>
#include <fmt/format.h>
+#include "configuration/qt_config.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "input_common/drivers/keyboard.h"
@@ -15,7 +16,6 @@
#include "input_common/main.h"
#include "ui_configure_ringcon.h"
#include "yuzu/bootmanager.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_ringcon.h"
const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM>
@@ -270,7 +270,7 @@ void ConfigureRingController::LoadConfiguration() {
void ConfigureRingController::RestoreDefaults() {
const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
- 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
+ 0, 0, QtConfig::default_ringcon_analogs[0], QtConfig::default_ringcon_analogs[1], 0, 0.05f);
emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));
UpdateUI();
}
diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h
index b23c27906..6fd95e2b8 100644
--- a/src/yuzu/configuration/configure_ringcon.h
+++ b/src/yuzu/configuration/configure_ringcon.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 0c8e5c8b4..7cbf43775 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -16,7 +16,6 @@
#include "core/core.h"
#include "core/hle/service/time/time_manager.h"
#include "ui_configure_system.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_system.h"
#include "yuzu/configuration/shared_widget.h"
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index 2a735836e..04b771129 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -57,7 +57,7 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox">
+ <widget class="QGroupBox" name="coreGroup">
<property name="title">
<string>Core</string>
</property>
diff --git a/src/yuzu/configuration/configure_tas.h b/src/yuzu/configuration/configure_tas.h
index 4a6b0ba4e..a91891906 100644
--- a/src/yuzu/configuration/configure_tas.h
+++ b/src/yuzu/configuration/configure_tas.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
index 5a03e48df..94df6d9d3 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
+#include "common/settings.h"
#include "ui_configure_touchscreen_advanced.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h
index 034dc0d46..b6fdffdc8 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.h
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 82f3b6e78..dd43f0a0e 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -164,7 +164,7 @@ ConfigureUi::~ConfigureUi() = default;
void ConfigureUi::ApplyConfiguration() {
UISettings::values.theme =
- ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
+ ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString().toStdString();
UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
UISettings::values.show_compat = ui->show_compat->isChecked();
UISettings::values.show_size = ui->show_size->isChecked();
@@ -191,9 +191,10 @@ void ConfigureUi::RequestGameListUpdate() {
}
void ConfigureUi::SetConfiguration() {
- ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
+ ui->theme_combobox->setCurrentIndex(
+ ui->theme_combobox->findData(QString::fromStdString(UISettings::values.theme)));
ui->language_combobox->setCurrentIndex(
- ui->language_combobox->findData(UISettings::values.language));
+ ui->language_combobox->findData(QString::fromStdString(UISettings::values.language)));
ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue());
ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
ui->show_size->setChecked(UISettings::values.show_size.GetValue());
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
index 41ef4250a..716efbccd 100644
--- a/src/yuzu/configuration/input_profiles.cpp
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -5,7 +5,7 @@
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
-#include "yuzu/configuration/config.h"
+#include "frontend_common/config.h"
#include "yuzu/configuration/input_profiles.h"
namespace FS = Common::FS;
@@ -44,7 +44,7 @@ InputProfiles::InputProfiles() {
if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
map_profiles.insert_or_assign(
name_without_ext,
- std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile));
+ std::make_unique<QtConfig>(name_without_ext, Config::ConfigType::InputProfile));
}
return true;
@@ -85,7 +85,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p
}
map_profiles.insert_or_assign(
- profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
+ profile_name, std::make_unique<QtConfig>(profile_name, Config::ConfigType::InputProfile));
return SaveProfile(profile_name, player_index);
}
@@ -113,7 +113,7 @@ bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t pla
return false;
}
- map_profiles[profile_name]->ReadControlPlayerValue(player_index);
+ map_profiles[profile_name]->ReadQtControlPlayerValues(player_index);
return true;
}
@@ -122,7 +122,7 @@ bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t pla
return false;
}
- map_profiles[profile_name]->SaveControlPlayerValue(player_index);
+ map_profiles[profile_name]->SaveQtControlPlayerValues(player_index);
return true;
}
diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h
index 2bf3e4250..023ec74a6 100644
--- a/src/yuzu/configuration/input_profiles.h
+++ b/src/yuzu/configuration/input_profiles.h
@@ -6,6 +6,8 @@
#include <string>
#include <unordered_map>
+#include "configuration/qt_config.h"
+
namespace Core {
class System;
}
@@ -30,5 +32,5 @@ public:
private:
bool ProfileExistsInMap(const std::string& profile_name) const;
- std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
+ std::unordered_map<std::string, std::unique_ptr<QtConfig>> map_profiles;
};
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp
new file mode 100644
index 000000000..5a8e69aa9
--- /dev/null
+++ b/src/yuzu/configuration/qt_config.cpp
@@ -0,0 +1,549 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "input_common/main.h"
+#include "qt_config.h"
+#include "uisettings.h"
+
+const std::array<int, Settings::NativeButton::NumButtons> QtConfig::default_buttons = {
+ Qt::Key_C, Qt::Key_X, Qt::Key_V, Qt::Key_Z, Qt::Key_F,
+ Qt::Key_G, Qt::Key_Q, Qt::Key_E, Qt::Key_R, Qt::Key_T,
+ Qt::Key_M, Qt::Key_N, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right,
+ Qt::Key_Down, Qt::Key_Q, Qt::Key_E, 0, 0,
+ Qt::Key_Q, Qt::Key_E,
+};
+
+const std::array<int, Settings::NativeMotion::NumMotions> QtConfig::default_motions = {
+ Qt::Key_7,
+ Qt::Key_8,
+};
+
+const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> QtConfig::default_analogs{{
+ {
+ Qt::Key_W,
+ Qt::Key_S,
+ Qt::Key_A,
+ Qt::Key_D,
+ },
+ {
+ Qt::Key_I,
+ Qt::Key_K,
+ Qt::Key_J,
+ Qt::Key_L,
+ },
+}};
+
+const std::array<int, 2> QtConfig::default_stick_mod = {
+ Qt::Key_Shift,
+ 0,
+};
+
+const std::array<int, 2> QtConfig::default_ringcon_analogs{{
+ Qt::Key_A,
+ Qt::Key_D,
+}};
+
+QtConfig::QtConfig(const std::string& config_name, const ConfigType config_type)
+ : Config(config_type) {
+ Initialize(config_name);
+ if (config_type != ConfigType::InputProfile) {
+ ReadQtValues();
+ SaveQtValues();
+ }
+}
+
+QtConfig::~QtConfig() {
+ if (global) {
+ QtConfig::SaveAllValues();
+ }
+}
+
+void QtConfig::ReloadAllValues() {
+ Reload();
+ ReadQtValues();
+ SaveQtValues();
+}
+
+void QtConfig::SaveAllValues() {
+ Save();
+ SaveQtValues();
+}
+
+void QtConfig::ReadQtValues() {
+ if (global) {
+ ReadUIValues();
+ }
+ ReadQtControlValues();
+}
+
+void QtConfig::ReadQtPlayerValues(const std::size_t player_index) {
+ std::string player_prefix;
+ if (type != ConfigType::InputProfile) {
+ player_prefix.append("player_").append(ToString(player_index)).append("_");
+ }
+
+ auto& player = Settings::values.players.GetValue()[player_index];
+ if (IsCustomConfig()) {
+ const auto profile_name =
+ ReadStringSetting(std::string(player_prefix).append("profile_name"));
+ if (profile_name.empty()) {
+ // Use the global input config
+ player = Settings::values.players.GetValue(true)[player_index];
+ return;
+ }
+ }
+
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ auto& player_buttons = player.buttons[i];
+
+ player_buttons = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
+ if (player_buttons.empty()) {
+ player_buttons = default_param;
+ }
+ }
+
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ auto& player_analogs = player.analogs[i];
+
+ player_analogs = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
+ if (player_analogs.empty()) {
+ player_analogs = default_param;
+ }
+ }
+
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+ auto& player_motions = player.motions[i];
+
+ player_motions = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
+ if (player_motions.empty()) {
+ player_motions = default_param;
+ }
+ }
+}
+
+void QtConfig::ReadHidbusValues() {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+ auto& ringcon_analogs = Settings::values.ringcon_analogs;
+
+ ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param);
+ if (ringcon_analogs.empty()) {
+ ringcon_analogs = default_param;
+ }
+}
+
+void QtConfig::ReadDebugControlValues() {
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
+
+ debug_pad_buttons = ReadStringSetting(
+ std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param);
+ if (debug_pad_buttons.empty()) {
+ debug_pad_buttons = default_param;
+ }
+ }
+
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
+
+ debug_pad_analogs = ReadStringSetting(
+ std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param);
+ if (debug_pad_analogs.empty()) {
+ debug_pad_analogs = default_param;
+ }
+ }
+}
+
+void QtConfig::ReadQtControlValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ Settings::values.players.SetGlobal(!IsCustomConfig());
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ ReadQtPlayerValues(p);
+ }
+ if (IsCustomConfig()) {
+ EndGroup();
+ return;
+ }
+ ReadDebugControlValues();
+ ReadHidbusValues();
+
+ EndGroup();
+}
+
+void QtConfig::ReadPathValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
+
+ UISettings::values.roms_path = ReadStringSetting(std::string("romsPath"));
+ UISettings::values.symbols_path = ReadStringSetting(std::string("symbolsPath"));
+ UISettings::values.game_dir_deprecated =
+ ReadStringSetting(std::string("gameListRootDir"), std::string("."));
+ UISettings::values.game_dir_deprecated_deepscan =
+ ReadBooleanSetting(std::string("gameListDeepScan"), std::make_optional(false));
+
+ const int gamedirs_size = BeginArray(std::string("gamedirs"));
+ for (int i = 0; i < gamedirs_size; ++i) {
+ SetArrayIndex(i);
+ UISettings::GameDir game_dir;
+ game_dir.path = ReadStringSetting(std::string("path"));
+ game_dir.deep_scan =
+ ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false));
+ game_dir.expanded = ReadBooleanSetting(std::string("expanded"), std::make_optional(true));
+ UISettings::values.game_dirs.append(game_dir);
+ }
+ EndArray();
+
+ // Create NAND and SD card directories if empty, these are not removable through the UI,
+ // also carries over old game list settings if present
+ if (UISettings::values.game_dirs.empty()) {
+ UISettings::GameDir game_dir;
+ game_dir.path = std::string("SDMC");
+ game_dir.expanded = true;
+ UISettings::values.game_dirs.append(game_dir);
+ game_dir.path = std::string("UserNAND");
+ UISettings::values.game_dirs.append(game_dir);
+ game_dir.path = std::string("SysNAND");
+ UISettings::values.game_dirs.append(game_dir);
+ if (UISettings::values.game_dir_deprecated != std::string(".")) {
+ game_dir.path = UISettings::values.game_dir_deprecated;
+ game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
+ UISettings::values.game_dirs.append(game_dir);
+ }
+ }
+ UISettings::values.recent_files =
+ QString::fromStdString(ReadStringSetting(std::string("recentFiles")))
+ .split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive);
+ UISettings::values.language = ReadStringSetting(std::string("language"), std::string(""));
+
+ EndGroup();
+}
+
+void QtConfig::ReadShortcutValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
+
+ for (const auto& [name, group, shortcut] : UISettings::default_hotkeys) {
+ BeginGroup(group);
+ BeginGroup(name);
+
+ // No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1
+ // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
+ // a file dialog in windowed mode
+ UISettings::values.shortcuts.push_back(
+ {name,
+ group,
+ {ReadStringSetting(std::string("KeySeq"), shortcut.keyseq),
+ ReadStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq),
+ shortcut.context,
+ ReadBooleanSetting(std::string("Repeat"), std::optional(shortcut.repeat))}});
+
+ EndGroup(); // name
+ EndGroup(); // group
+ }
+
+ EndGroup();
+}
+
+void QtConfig::ReadUIValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
+
+ UISettings::values.theme = ReadStringSetting(
+ std::string("theme"),
+ std::string(UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second));
+
+ ReadUIGamelistValues();
+ ReadUILayoutValues();
+ ReadPathValues();
+ ReadScreenshotValues();
+ ReadShortcutValues();
+ ReadMultiplayerValues();
+
+ ReadCategory(Settings::Category::Ui);
+ ReadCategory(Settings::Category::UiGeneral);
+
+ EndGroup();
+}
+
+void QtConfig::ReadUIGamelistValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
+
+ ReadCategory(Settings::Category::UiGameList);
+
+ const int favorites_size = BeginArray("favorites");
+ for (int i = 0; i < favorites_size; i++) {
+ SetArrayIndex(i);
+ UISettings::values.favorited_ids.append(
+ ReadUnsignedIntegerSetting(std::string("program_id")));
+ }
+ EndArray();
+
+ EndGroup();
+}
+
+void QtConfig::ReadUILayoutValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
+
+ ReadCategory(Settings::Category::UiLayout);
+
+ EndGroup();
+}
+
+void QtConfig::ReadMultiplayerValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Multiplayer));
+
+ ReadCategory(Settings::Category::Multiplayer);
+
+ // Read ban list back
+ int size = BeginArray(std::string("username_ban_list"));
+ UISettings::values.multiplayer_ban_list.first.resize(size);
+ for (int i = 0; i < size; ++i) {
+ SetArrayIndex(i);
+ UISettings::values.multiplayer_ban_list.first[i] =
+ ReadStringSetting(std::string("username"), std::string(""));
+ }
+ EndArray();
+
+ size = BeginArray(std::string("ip_ban_list"));
+ UISettings::values.multiplayer_ban_list.second.resize(size);
+ for (int i = 0; i < size; ++i) {
+ UISettings::values.multiplayer_ban_list.second[i] =
+ ReadStringSetting("username", std::string(""));
+ }
+ EndArray();
+
+ EndGroup();
+}
+
+void QtConfig::SaveQtValues() {
+ if (global) {
+ SaveUIValues();
+ }
+ SaveQtControlValues();
+
+ WriteToIni();
+}
+
+void QtConfig::SaveQtPlayerValues(const std::size_t player_index) {
+ std::string player_prefix;
+ if (type != ConfigType::InputProfile) {
+ player_prefix = std::string("player_").append(ToString(player_index)).append("_");
+ }
+
+ const auto& player = Settings::values.players.GetValue()[player_index];
+ if (IsCustomConfig() && player.profile_name.empty()) {
+ // No custom profile selected
+ return;
+ }
+
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
+ player.buttons[i], std::make_optional(default_param));
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
+ player.analogs[i], std::make_optional(default_param));
+ }
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+ WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
+ player.motions[i], std::make_optional(default_param));
+ }
+}
+
+void QtConfig::SaveDebugControlValues() {
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
+ Settings::values.debug_pad_buttons[i], std::make_optional(default_param));
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
+ Settings::values.debug_pad_analogs[i], std::make_optional(default_param));
+ }
+}
+
+void QtConfig::SaveHidbusValues() {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+ WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
+ std::make_optional(default_param));
+}
+
+void QtConfig::SaveQtControlValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ Settings::values.players.SetGlobal(!IsCustomConfig());
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ SaveQtPlayerValues(p);
+ }
+ if (IsCustomConfig()) {
+ EndGroup();
+ return;
+ }
+ SaveDebugControlValues();
+ SaveHidbusValues();
+
+ EndGroup();
+}
+
+void QtConfig::SavePathValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
+
+ WriteSetting(std::string("romsPath"), UISettings::values.roms_path);
+ WriteSetting(std::string("symbolsPath"), UISettings::values.symbols_path);
+ BeginArray(std::string("gamedirs"));
+ for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
+ SetArrayIndex(i);
+ const auto& game_dir = UISettings::values.game_dirs[i];
+ WriteSetting(std::string("path"), game_dir.path);
+ WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false));
+ WriteSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true));
+ }
+ EndArray();
+
+ WriteSetting(std::string("recentFiles"),
+ UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
+ WriteSetting(std::string("language"), UISettings::values.language);
+
+ EndGroup();
+}
+
+void QtConfig::SaveShortcutValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
+
+ // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
+ // However, their ordering must also be the same.
+ for (std::size_t i = 0; i < UISettings::default_hotkeys.size(); i++) {
+ const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
+ const auto& default_hotkey = UISettings::default_hotkeys[i].shortcut;
+
+ BeginGroup(group);
+ BeginGroup(name);
+
+ WriteSetting(std::string("KeySeq"), shortcut.keyseq,
+ std::make_optional(default_hotkey.keyseq));
+ WriteSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq,
+ std::make_optional(default_hotkey.controller_keyseq));
+ WriteSetting(std::string("Context"), shortcut.context,
+ std::make_optional(default_hotkey.context));
+ WriteSetting(std::string("Repeat"), shortcut.repeat,
+ std::make_optional(default_hotkey.repeat));
+
+ EndGroup(); // name
+ EndGroup(); // group
+ }
+
+ EndGroup();
+}
+
+void QtConfig::SaveUIValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
+
+ WriteCategory(Settings::Category::Ui);
+ WriteCategory(Settings::Category::UiGeneral);
+
+ WriteSetting(std::string("theme"), UISettings::values.theme,
+ std::make_optional(std::string(
+ UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second)));
+
+ SaveUIGamelistValues();
+ SaveUILayoutValues();
+ SavePathValues();
+ SaveScreenshotValues();
+ SaveShortcutValues();
+ SaveMultiplayerValues();
+
+ EndGroup();
+}
+
+void QtConfig::SaveUIGamelistValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
+
+ WriteCategory(Settings::Category::UiGameList);
+
+ BeginArray(std::string("favorites"));
+ for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
+ SetArrayIndex(i);
+ WriteSetting(std::string("program_id"), UISettings::values.favorited_ids[i]);
+ }
+ EndArray(); // favorites
+
+ EndGroup();
+}
+
+void QtConfig::SaveUILayoutValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::UiLayout));
+
+ WriteCategory(Settings::Category::UiLayout);
+
+ EndGroup();
+}
+
+void QtConfig::SaveMultiplayerValues() {
+ BeginGroup(std::string("Multiplayer"));
+
+ WriteCategory(Settings::Category::Multiplayer);
+
+ // Write ban list
+ BeginArray(std::string("username_ban_list"));
+ for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
+ SetArrayIndex(static_cast<int>(i));
+ WriteSetting(std::string("username"), UISettings::values.multiplayer_ban_list.first[i]);
+ }
+ EndArray(); // username_ban_list
+
+ BeginArray(std::string("ip_ban_list"));
+ for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
+ SetArrayIndex(static_cast<int>(i));
+ WriteSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]);
+ }
+ EndArray(); // ip_ban_list
+
+ EndGroup();
+}
+
+std::vector<Settings::BasicSetting*>& QtConfig::FindRelevantList(Settings::Category category) {
+ auto& map = Settings::values.linkage.by_category;
+ if (map.contains(category)) {
+ return Settings::values.linkage.by_category[category];
+ }
+ return UISettings::values.linkage.by_category[category];
+}
+
+void QtConfig::ReadQtControlPlayerValues(std::size_t player_index) {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ ReadPlayerValues(player_index);
+ ReadQtPlayerValues(player_index);
+
+ EndGroup();
+}
+
+void QtConfig::SaveQtControlPlayerValues(std::size_t player_index) {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ SavePlayerValues(player_index);
+ SaveQtPlayerValues(player_index);
+
+ EndGroup();
+
+ WriteToIni();
+}
diff --git a/src/yuzu/configuration/qt_config.h b/src/yuzu/configuration/qt_config.h
new file mode 100644
index 000000000..dc2dceb4d
--- /dev/null
+++ b/src/yuzu/configuration/qt_config.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QMetaType>
+
+#include "frontend_common/config.h"
+
+class QtConfig final : public Config {
+public:
+ explicit QtConfig(const std::string& config_name = "qt-config",
+ ConfigType config_type = ConfigType::GlobalConfig);
+ ~QtConfig() override;
+
+ void ReloadAllValues() override;
+ void SaveAllValues() override;
+
+ void ReadQtControlPlayerValues(std::size_t player_index);
+ void SaveQtControlPlayerValues(std::size_t player_index);
+
+protected:
+ void ReadQtValues();
+ void ReadQtPlayerValues(std::size_t player_index);
+ void ReadQtControlValues();
+ void ReadHidbusValues() override;
+ void ReadDebugControlValues() override;
+ void ReadPathValues() override;
+ void ReadShortcutValues() override;
+ void ReadUIValues() override;
+ void ReadUIGamelistValues() override;
+ void ReadUILayoutValues() override;
+ void ReadMultiplayerValues() override;
+
+ void SaveQtValues();
+ void SaveQtPlayerValues(std::size_t player_index);
+ void SaveQtControlValues();
+ void SaveHidbusValues() override;
+ void SaveDebugControlValues() override;
+ void SavePathValues() override;
+ void SaveShortcutValues() override;
+ void SaveUIValues() override;
+ void SaveUIGamelistValues() override;
+ void SaveUILayoutValues() override;
+ void SaveMultiplayerValues() override;
+
+ std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
+
+public:
+ static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
+ static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
+ static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
+ static const std::array<int, 2> default_stick_mod;
+ static const std::array<int, 2> default_ringcon_analogs;
+};
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 3fe448f27..7e908924c 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -1,17 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "common/time_zone.h"
#include "yuzu/configuration/shared_translation.h"
#include <map>
#include <memory>
#include <tuple>
#include <utility>
+#include <QCoreApplication>
#include <QWidget>
#include "common/settings.h"
#include "common/settings_enums.h"
#include "common/settings_setting.h"
+#include "common/time_zone.h"
#include "yuzu/uisettings.h"
namespace ConfigurationShared {
@@ -21,123 +22,136 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); };
#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \
- translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{tr((NAME)), tr((TOOLTIP))}})
+ translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}})
// A setting can be ignored by giving it a blank name
// Audio
- INSERT(Settings, sink_id, "Output Engine:", "");
- INSERT(Settings, audio_output_device_id, "Output Device:", "");
- INSERT(Settings, audio_input_device_id, "Input Device:", "");
- INSERT(Settings, audio_muted, "Mute audio", "");
- INSERT(Settings, volume, "Volume:", "");
- INSERT(Settings, dump_audio_commands, "", "");
- INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
+ INSERT(Settings, sink_id, tr("Output Engine:"), QStringLiteral());
+ INSERT(Settings, audio_output_device_id, tr("Output Device:"), QStringLiteral());
+ INSERT(Settings, audio_input_device_id, tr("Input Device:"), QStringLiteral());
+ INSERT(Settings, audio_muted, tr("Mute audio"), QStringLiteral());
+ INSERT(Settings, volume, tr("Volume:"), QStringLiteral());
+ INSERT(Settings, dump_audio_commands, QStringLiteral(), QStringLiteral());
+ INSERT(UISettings, mute_when_in_background, tr("Mute audio when in background"),
+ QStringLiteral());
// Core
- INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
- INSERT(Settings, memory_layout_mode, "Memory Layout", "");
- INSERT(Settings, use_speed_limit, "", "");
- INSERT(Settings, speed_limit, "Limit Speed Percent", "");
+ INSERT(Settings, use_multi_core, tr("Multicore CPU Emulation"), QStringLiteral());
+ INSERT(Settings, memory_layout_mode, tr("Memory Layout"), QStringLiteral());
+ INSERT(Settings, use_speed_limit, QStringLiteral(), QStringLiteral());
+ INSERT(Settings, speed_limit, tr("Limit Speed Percent"), QStringLiteral());
// Cpu
- INSERT(Settings, cpu_accuracy, "Accuracy:", "");
+ INSERT(Settings, cpu_accuracy, tr("Accuracy:"), QStringLiteral());
+ INSERT(Settings, cpu_backend, tr("Backend:"), QStringLiteral());
// Cpu Debug
// Cpu Unsafe
- INSERT(Settings, cpuopt_unsafe_unfuse_fma,
- "Unfuse FMA (improve performance on CPUs without FMA)",
- "This option improves speed by reducing accuracy of fused-multiply-add instructions on "
- "CPUs without native FMA support.");
- INSERT(Settings, cpuopt_unsafe_reduce_fp_error, "Faster FRSQRTE and FRECPE",
- "This option improves the speed of some approximate floating-point functions by using "
- "less accurate native approximations.");
- INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr, "Faster ASIMD instructions (32 bits only)",
- "This option improves the speed of 32 bits ASIMD floating-point functions by running "
- "with incorrect rounding modes.");
- INSERT(Settings, cpuopt_unsafe_inaccurate_nan, "Inaccurate NaN handling",
- "This option improves speed by removing NaN checking. Please note this also reduces "
- "accuracy of certain floating-point instructions.");
INSERT(
- Settings, cpuopt_unsafe_fastmem_check, "Disable address space checks",
- "This option improves speed by eliminating a safety check before every memory read/write "
- "in guest. Disabling it may allow a game to read/write the emulator's memory.");
- INSERT(Settings, cpuopt_unsafe_ignore_global_monitor, "Ignore global monitor",
- "This option improves speed by relying only on the semantics of cmpxchg to ensure "
+ Settings, cpuopt_unsafe_unfuse_fma,
+ tr("Unfuse FMA (improve performance on CPUs without FMA)"),
+ tr("This option improves speed by reducing accuracy of fused-multiply-add instructions on "
+ "CPUs without native FMA support."));
+ INSERT(
+ Settings, cpuopt_unsafe_reduce_fp_error, tr("Faster FRSQRTE and FRECPE"),
+ tr("This option improves the speed of some approximate floating-point functions by using "
+ "less accurate native approximations."));
+ INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr,
+ tr("Faster ASIMD instructions (32 bits only)"),
+ tr("This option improves the speed of 32 bits ASIMD floating-point functions by running "
+ "with incorrect rounding modes."));
+ INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"),
+ tr("This option improves speed by removing NaN checking. Please note this also reduces "
+ "accuracy of certain floating-point instructions."));
+ INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"),
+ tr("This option improves speed by eliminating a safety check before every memory "
+ "read/write "
+ "in guest. Disabling it may allow a game to read/write the emulator's memory."));
+ INSERT(
+ Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"),
+ tr("This option improves speed by relying only on the semantics of cmpxchg to ensure "
"safety of exclusive access instructions. Please note this may result in deadlocks and "
- "other race conditions.");
+ "other race conditions."));
// Renderer
- INSERT(Settings, renderer_backend, "API:", "");
- INSERT(Settings, vulkan_device, "Device:", "");
- INSERT(Settings, shader_backend, "Shader Backend:", "");
- INSERT(Settings, resolution_setup, "Resolution:", "");
- INSERT(Settings, scaling_filter, "Window Adapting Filter:", "");
- INSERT(Settings, fsr_sharpening_slider, "FSR Sharpness:", "");
- INSERT(Settings, anti_aliasing, "Anti-Aliasing Method:", "");
- INSERT(Settings, fullscreen_mode, "Fullscreen Mode:", "");
- INSERT(Settings, aspect_ratio, "Aspect Ratio:", "");
- INSERT(Settings, use_disk_shader_cache, "Use disk pipeline cache", "");
- INSERT(Settings, use_asynchronous_gpu_emulation, "Use asynchronous GPU emulation", "");
- INSERT(Settings, nvdec_emulation, "NVDEC emulation:", "");
- INSERT(Settings, accelerate_astc, "ASTC Decoding Method:", "");
- INSERT(Settings, astc_recompression, "ASTC Recompression Method:", "");
- INSERT(Settings, vsync_mode, "VSync Mode:",
- "FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
+ INSERT(Settings, renderer_backend, tr("API:"), QStringLiteral());
+ INSERT(Settings, vulkan_device, tr("Device:"), QStringLiteral());
+ INSERT(Settings, shader_backend, tr("Shader Backend:"), QStringLiteral());
+ INSERT(Settings, resolution_setup, tr("Resolution:"), QStringLiteral());
+ INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QStringLiteral());
+ INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), QStringLiteral());
+ INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), QStringLiteral());
+ INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), QStringLiteral());
+ INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"), QStringLiteral());
+ INSERT(Settings, use_disk_shader_cache, tr("Use disk pipeline cache"), QStringLiteral());
+ INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
+ QStringLiteral());
+ INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), QStringLiteral());
+ INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"), QStringLiteral());
+ INSERT(Settings, astc_recompression, tr("ASTC Recompression Method:"), QStringLiteral());
+ INSERT(
+ Settings, vsync_mode, tr("VSync Mode:"),
+ tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
"refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from "
"a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop "
"frames.\nImmediate (no synchronization) just presents whatever is available and can "
- "exhibit tearing.");
- INSERT(Settings, bg_red, "", "");
- INSERT(Settings, bg_green, "", "");
- INSERT(Settings, bg_blue, "", "");
+ "exhibit tearing."));
+ INSERT(Settings, bg_red, QStringLiteral(), QStringLiteral());
+ INSERT(Settings, bg_green, QStringLiteral(), QStringLiteral());
+ INSERT(Settings, bg_blue, QStringLiteral(), QStringLiteral());
// Renderer (Advanced Graphics)
- INSERT(Settings, async_presentation, "Enable asynchronous presentation (Vulkan only)", "");
- INSERT(Settings, renderer_force_max_clock, "Force maximum clocks (Vulkan only)",
- "Runs work in the background while waiting for graphics commands to keep the GPU from "
- "lowering its clock speed.");
- INSERT(Settings, max_anisotropy, "Anisotropic Filtering:", "");
- INSERT(Settings, gpu_accuracy, "Accuracy Level:", "");
- INSERT(Settings, use_asynchronous_shaders, "Use asynchronous shader building (Hack)",
- "Enables asynchronous shader compilation, which may reduce shader stutter. This feature "
- "is experimental.");
- INSERT(Settings, use_fast_gpu_time, "Use Fast GPU Time (Hack)",
- "Enables Fast GPU Time. This option will force most games to run at their highest "
- "native resolution.");
- INSERT(Settings, use_vulkan_driver_pipeline_cache, "Use Vulkan pipeline cache",
- "Enables GPU vendor-specific pipeline cache. This option can improve shader loading "
- "time significantly in cases where the Vulkan driver does not store pipeline cache "
- "files internally.");
- INSERT(Settings, enable_compute_pipelines, "Enable Compute Pipelines (Intel Vulkan Only)",
- "Enable compute pipelines, required by some games.\nThis setting only exists for Intel "
+ INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"),
+ QStringLiteral());
+ INSERT(
+ Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"),
+ tr("Runs work in the background while waiting for graphics commands to keep the GPU from "
+ "lowering its clock speed."));
+ INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"), QStringLiteral());
+ INSERT(Settings, gpu_accuracy, tr("Accuracy Level:"), QStringLiteral());
+ INSERT(
+ Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"),
+ tr("Enables asynchronous shader compilation, which may reduce shader stutter. This feature "
+ "is experimental."));
+ INSERT(Settings, use_fast_gpu_time, tr("Use Fast GPU Time (Hack)"),
+ tr("Enables Fast GPU Time. This option will force most games to run at their highest "
+ "native resolution."));
+ INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"),
+ tr("Enables GPU vendor-specific pipeline cache. This option can improve shader loading "
+ "time significantly in cases where the Vulkan driver does not store pipeline cache "
+ "files internally."));
+ INSERT(
+ Settings, enable_compute_pipelines, tr("Enable Compute Pipelines (Intel Vulkan Only)"),
+ tr("Enable compute pipelines, required by some games.\nThis setting only exists for Intel "
"proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled "
- "on all other drivers.");
- INSERT(Settings, use_reactive_flushing, "Enable Reactive Flushing",
- "Uses reactive flushing instead of predictive flushing, allowing more accurate memory "
- "syncing.");
- INSERT(Settings, use_video_framerate, "Sync to framerate of video playback",
- "Run the game at normal speed during video playback, even when the framerate is "
- "unlocked.");
- INSERT(Settings, barrier_feedback_loops, "Barrier feedback loops",
- "Improves rendering of transparency effects in specific games.");
+ "on all other drivers."));
+ INSERT(
+ Settings, use_reactive_flushing, tr("Enable Reactive Flushing"),
+ tr("Uses reactive flushing instead of predictive flushing, allowing more accurate memory "
+ "syncing."));
+ INSERT(Settings, use_video_framerate, tr("Sync to framerate of video playback"),
+ tr("Run the game at normal speed during video playback, even when the framerate is "
+ "unlocked."));
+ INSERT(Settings, barrier_feedback_loops, tr("Barrier feedback loops"),
+ tr("Improves rendering of transparency effects in specific games."));
// Renderer (Debug)
// System
- INSERT(Settings, rng_seed, "RNG Seed", "");
- INSERT(Settings, rng_seed_enabled, "", "");
- INSERT(Settings, device_name, "Device Name", "");
- INSERT(Settings, custom_rtc, "Custom RTC", "");
- INSERT(Settings, custom_rtc_enabled, "", "");
- INSERT(Settings, language_index,
- "Language:", "Note: this can be overridden when region setting is auto-select");
- INSERT(Settings, region_index, "Region:", "");
- INSERT(Settings, time_zone_index, "Time Zone:", "");
- INSERT(Settings, sound_index, "Sound Output Mode:", "");
- INSERT(Settings, use_docked_mode, "Console Mode:", "");
- INSERT(Settings, current_user, "", "");
+ INSERT(Settings, rng_seed, tr("RNG Seed"), QStringLiteral());
+ INSERT(Settings, rng_seed_enabled, QStringLiteral(), QStringLiteral());
+ INSERT(Settings, device_name, tr("Device Name"), QStringLiteral());
+ INSERT(Settings, custom_rtc, tr("Custom RTC"), QStringLiteral());
+ INSERT(Settings, custom_rtc_enabled, QStringLiteral(), QStringLiteral());
+ INSERT(Settings, language_index, tr("Language:"),
+ tr("Note: this can be overridden when region setting is auto-select"));
+ INSERT(Settings, region_index, tr("Region:"), QStringLiteral());
+ INSERT(Settings, time_zone_index, tr("Time Zone:"), QStringLiteral());
+ INSERT(Settings, sound_index, tr("Sound Output Mode:"), QStringLiteral());
+ INSERT(Settings, use_docked_mode, tr("Console Mode:"), QStringLiteral());
+ INSERT(Settings, current_user, QStringLiteral(), QStringLiteral());
// Controls
@@ -154,12 +168,17 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
// Ui
// Ui General
- INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
- INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
- INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
- INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", "");
- INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
- INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
+ INSERT(UISettings, select_user_on_boot, tr("Prompt for user on game boot"), QStringLiteral());
+ INSERT(UISettings, pause_when_in_background, tr("Pause emulation when in background"),
+ QStringLiteral());
+ INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"),
+ QStringLiteral());
+ INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), QStringLiteral());
+ INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
+ QStringLiteral());
+
+ // Linux
+ INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral());
// Ui Debugging
@@ -179,140 +198,146 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
return parent->tr(text, context);
};
-#define PAIR(ENUM, VALUE, TRANSLATION) \
- { static_cast<u32>(Settings::ENUM::VALUE), tr(TRANSLATION) }
-#define CTX_PAIR(ENUM, VALUE, TRANSLATION, CONTEXT) \
- { static_cast<u32>(Settings::ENUM::VALUE), tr(TRANSLATION, CONTEXT) }
+#define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)}
// Intentionally skipping VSyncMode to let the UI fill that one out
translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(),
{
- PAIR(AstcDecodeMode, Cpu, "CPU"),
- PAIR(AstcDecodeMode, Gpu, "GPU"),
- PAIR(AstcDecodeMode, CpuAsynchronous, "CPU Asynchronous"),
- }});
- translations->insert({Settings::EnumMetadata<Settings::AstcRecompression>::Index(),
- {
- PAIR(AstcRecompression, Uncompressed, "Uncompressed (Best quality)"),
- PAIR(AstcRecompression, Bc1, "BC1 (Low quality)"),
- PAIR(AstcRecompression, Bc3, "BC3 (Medium quality)"),
+ PAIR(AstcDecodeMode, Cpu, tr("CPU")),
+ PAIR(AstcDecodeMode, Gpu, tr("GPU")),
+ PAIR(AstcDecodeMode, CpuAsynchronous, tr("CPU Asynchronous")),
}});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::AstcRecompression>::Index(),
+ {
+ PAIR(AstcRecompression, Uncompressed, tr("Uncompressed (Best quality)")),
+ PAIR(AstcRecompression, Bc1, tr("BC1 (Low quality)")),
+ PAIR(AstcRecompression, Bc3, tr("BC3 (Medium quality)")),
+ }});
translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(),
{
#ifdef HAS_OPENGL
- PAIR(RendererBackend, OpenGL, "OpenGL"),
+ PAIR(RendererBackend, OpenGL, tr("OpenGL")),
#endif
- PAIR(RendererBackend, Vulkan, "Vulkan"),
- PAIR(RendererBackend, Null, "Null"),
- }});
- translations->insert({Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
- {
- PAIR(ShaderBackend, Glsl, "GLSL"),
- PAIR(ShaderBackend, Glasm, "GLASM (Assembly Shaders, NVIDIA Only)"),
- PAIR(ShaderBackend, SpirV, "SPIR-V (Experimental, Mesa Only)"),
+ PAIR(RendererBackend, Vulkan, tr("Vulkan")),
+ PAIR(RendererBackend, Null, tr("Null")),
}});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
+ {
+ PAIR(ShaderBackend, Glsl, tr("GLSL")),
+ PAIR(ShaderBackend, Glasm, tr("GLASM (Assembly Shaders, NVIDIA Only)")),
+ PAIR(ShaderBackend, SpirV, tr("SPIR-V (Experimental, Mesa Only)")),
+ }});
translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(),
{
- PAIR(GpuAccuracy, Normal, "Normal"),
- PAIR(GpuAccuracy, High, "High"),
- PAIR(GpuAccuracy, Extreme, "Extreme"),
+ PAIR(GpuAccuracy, Normal, tr("Normal")),
+ PAIR(GpuAccuracy, High, tr("High")),
+ PAIR(GpuAccuracy, Extreme, tr("Extreme")),
}});
- translations->insert({Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
+ translations->insert(
+ {Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
+ {
+ PAIR(CpuAccuracy, Auto, tr("Auto")),
+ PAIR(CpuAccuracy, Accurate, tr("Accurate")),
+ PAIR(CpuAccuracy, Unsafe, tr("Unsafe")),
+ PAIR(CpuAccuracy, Paranoid, tr("Paranoid (disables most optimizations)")),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::CpuBackend>::Index(),
{
- PAIR(CpuAccuracy, Auto, "Auto"),
- PAIR(CpuAccuracy, Accurate, "Accurate"),
- PAIR(CpuAccuracy, Unsafe, "Unsafe"),
- PAIR(CpuAccuracy, Paranoid, "Paranoid (disables most optimizations)"),
+ PAIR(CpuBackend, Dynarmic, tr("Dynarmic")),
+ PAIR(CpuBackend, Nce, tr("NCE")),
}});
translations->insert({Settings::EnumMetadata<Settings::FullscreenMode>::Index(),
{
- PAIR(FullscreenMode, Borderless, "Borderless Windowed"),
- PAIR(FullscreenMode, Exclusive, "Exclusive Fullscreen"),
+ PAIR(FullscreenMode, Borderless, tr("Borderless Windowed")),
+ PAIR(FullscreenMode, Exclusive, tr("Exclusive Fullscreen")),
}});
translations->insert({Settings::EnumMetadata<Settings::NvdecEmulation>::Index(),
{
- PAIR(NvdecEmulation, Off, "No Video Output"),
- PAIR(NvdecEmulation, Cpu, "CPU Video Decoding"),
- PAIR(NvdecEmulation, Gpu, "GPU Video Decoding (Default)"),
- }});
- translations->insert({Settings::EnumMetadata<Settings::ResolutionSetup>::Index(),
- {
- PAIR(ResolutionSetup, Res1_2X, "0.5X (360p/540p) [EXPERIMENTAL]"),
- PAIR(ResolutionSetup, Res3_4X, "0.75X (540p/810p) [EXPERIMENTAL]"),
- PAIR(ResolutionSetup, Res1X, "1X (720p/1080p)"),
- PAIR(ResolutionSetup, Res3_2X, "1.5X (1080p/1620p) [EXPERIMENTAL]"),
- PAIR(ResolutionSetup, Res2X, "2X (1440p/2160p)"),
- PAIR(ResolutionSetup, Res3X, "3X (2160p/3240p)"),
- PAIR(ResolutionSetup, Res4X, "4X (2880p/4320p)"),
- PAIR(ResolutionSetup, Res5X, "5X (3600p/5400p)"),
- PAIR(ResolutionSetup, Res6X, "6X (4320p/6480p)"),
- PAIR(ResolutionSetup, Res7X, "7X (5040p/7560p)"),
- PAIR(ResolutionSetup, Res8X, "8X (5760p/8640p)"),
+ PAIR(NvdecEmulation, Off, tr("No Video Output")),
+ PAIR(NvdecEmulation, Cpu, tr("CPU Video Decoding")),
+ PAIR(NvdecEmulation, Gpu, tr("GPU Video Decoding (Default)")),
}});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::ResolutionSetup>::Index(),
+ {
+ PAIR(ResolutionSetup, Res1_2X, tr("0.5X (360p/540p) [EXPERIMENTAL]")),
+ PAIR(ResolutionSetup, Res3_4X, tr("0.75X (540p/810p) [EXPERIMENTAL]")),
+ PAIR(ResolutionSetup, Res1X, tr("1X (720p/1080p)")),
+ PAIR(ResolutionSetup, Res3_2X, tr("1.5X (1080p/1620p) [EXPERIMENTAL]")),
+ PAIR(ResolutionSetup, Res2X, tr("2X (1440p/2160p)")),
+ PAIR(ResolutionSetup, Res3X, tr("3X (2160p/3240p)")),
+ PAIR(ResolutionSetup, Res4X, tr("4X (2880p/4320p)")),
+ PAIR(ResolutionSetup, Res5X, tr("5X (3600p/5400p)")),
+ PAIR(ResolutionSetup, Res6X, tr("6X (4320p/6480p)")),
+ PAIR(ResolutionSetup, Res7X, tr("7X (5040p/7560p)")),
+ PAIR(ResolutionSetup, Res8X, tr("8X (5760p/8640p)")),
+ }});
translations->insert({Settings::EnumMetadata<Settings::ScalingFilter>::Index(),
{
- PAIR(ScalingFilter, NearestNeighbor, "Nearest Neighbor"),
- PAIR(ScalingFilter, Bilinear, "Bilinear"),
- PAIR(ScalingFilter, Bicubic, "Bicubic"),
- PAIR(ScalingFilter, Gaussian, "Gaussian"),
- PAIR(ScalingFilter, ScaleForce, "ScaleForce"),
- PAIR(ScalingFilter, Fsr, "AMD FidelityFX™️ Super Resolution"),
+ PAIR(ScalingFilter, NearestNeighbor, tr("Nearest Neighbor")),
+ PAIR(ScalingFilter, Bilinear, tr("Bilinear")),
+ PAIR(ScalingFilter, Bicubic, tr("Bicubic")),
+ PAIR(ScalingFilter, Gaussian, tr("Gaussian")),
+ PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")),
+ PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")),
}});
translations->insert({Settings::EnumMetadata<Settings::AntiAliasing>::Index(),
{
- PAIR(AntiAliasing, None, "None"),
- PAIR(AntiAliasing, Fxaa, "FXAA"),
- PAIR(AntiAliasing, Smaa, "SMAA"),
+ PAIR(AntiAliasing, None, tr("None")),
+ PAIR(AntiAliasing, Fxaa, tr("FXAA")),
+ PAIR(AntiAliasing, Smaa, tr("SMAA")),
}});
translations->insert({Settings::EnumMetadata<Settings::AspectRatio>::Index(),
{
- PAIR(AspectRatio, R16_9, "Default (16:9)"),
- PAIR(AspectRatio, R4_3, "Force 4:3"),
- PAIR(AspectRatio, R21_9, "Force 21:9"),
- PAIR(AspectRatio, R16_10, "Force 16:10"),
- PAIR(AspectRatio, Stretch, "Stretch to Window"),
+ PAIR(AspectRatio, R16_9, tr("Default (16:9)")),
+ PAIR(AspectRatio, R4_3, tr("Force 4:3")),
+ PAIR(AspectRatio, R21_9, tr("Force 21:9")),
+ PAIR(AspectRatio, R16_10, tr("Force 16:10")),
+ PAIR(AspectRatio, Stretch, tr("Stretch to Window")),
}});
translations->insert({Settings::EnumMetadata<Settings::AnisotropyMode>::Index(),
{
- PAIR(AnisotropyMode, Automatic, "Automatic"),
- PAIR(AnisotropyMode, Default, "Default"),
- PAIR(AnisotropyMode, X2, "2x"),
- PAIR(AnisotropyMode, X4, "4x"),
- PAIR(AnisotropyMode, X8, "8x"),
- PAIR(AnisotropyMode, X16, "16x"),
+ PAIR(AnisotropyMode, Automatic, tr("Automatic")),
+ PAIR(AnisotropyMode, Default, tr("Default")),
+ PAIR(AnisotropyMode, X2, tr("2x")),
+ PAIR(AnisotropyMode, X4, tr("4x")),
+ PAIR(AnisotropyMode, X8, tr("8x")),
+ PAIR(AnisotropyMode, X16, tr("16x")),
}});
translations->insert(
{Settings::EnumMetadata<Settings::Language>::Index(),
{
- PAIR(Language, Japanese, "Japanese (日本語)"),
- PAIR(Language, EnglishAmerican, "American English"),
- PAIR(Language, French, "French (français)"),
- PAIR(Language, German, "German (Deutsch)"),
- PAIR(Language, Italian, "Italian (italiano)"),
- PAIR(Language, Spanish, "Spanish (español)"),
- PAIR(Language, Chinese, "Chinese"),
- PAIR(Language, Korean, "Korean (한국어)"),
- PAIR(Language, Dutch, "Dutch (Nederlands)"),
- PAIR(Language, Portuguese, "Portuguese (português)"),
- PAIR(Language, Russian, "Russian (Русский)"),
- PAIR(Language, Taiwanese, "Taiwanese"),
- PAIR(Language, EnglishBritish, "British English"),
- PAIR(Language, FrenchCanadian, "Canadian French"),
- PAIR(Language, SpanishLatin, "Latin American Spanish"),
- PAIR(Language, ChineseSimplified, "Simplified Chinese"),
- PAIR(Language, ChineseTraditional, "Traditional Chinese (正體中文)"),
- PAIR(Language, PortugueseBrazilian, "Brazilian Portuguese (português do Brasil)"),
+ PAIR(Language, Japanese, tr("Japanese (日本語)")),
+ PAIR(Language, EnglishAmerican, tr("American English")),
+ PAIR(Language, French, tr("French (français)")),
+ PAIR(Language, German, tr("German (Deutsch)")),
+ PAIR(Language, Italian, tr("Italian (italiano)")),
+ PAIR(Language, Spanish, tr("Spanish (español)")),
+ PAIR(Language, Chinese, tr("Chinese")),
+ PAIR(Language, Korean, tr("Korean (한국어)")),
+ PAIR(Language, Dutch, tr("Dutch (Nederlands)")),
+ PAIR(Language, Portuguese, tr("Portuguese (português)")),
+ PAIR(Language, Russian, tr("Russian (Русский)")),
+ PAIR(Language, Taiwanese, tr("Taiwanese")),
+ PAIR(Language, EnglishBritish, tr("British English")),
+ PAIR(Language, FrenchCanadian, tr("Canadian French")),
+ PAIR(Language, SpanishLatin, tr("Latin American Spanish")),
+ PAIR(Language, ChineseSimplified, tr("Simplified Chinese")),
+ PAIR(Language, ChineseTraditional, tr("Traditional Chinese (正體中文)")),
+ PAIR(Language, PortugueseBrazilian, tr("Brazilian Portuguese (português do Brasil)")),
}});
translations->insert({Settings::EnumMetadata<Settings::Region>::Index(),
{
- PAIR(Region, Japan, "Japan"),
- PAIR(Region, Usa, "USA"),
- PAIR(Region, Europe, "Europe"),
- PAIR(Region, Australia, "Australia"),
- PAIR(Region, China, "China"),
- PAIR(Region, Korea, "Korea"),
- PAIR(Region, Taiwan, "Taiwan"),
+ PAIR(Region, Japan, tr("Japan")),
+ PAIR(Region, Usa, tr("USA")),
+ PAIR(Region, Europe, tr("Europe")),
+ PAIR(Region, Australia, tr("Australia")),
+ PAIR(Region, China, tr("China")),
+ PAIR(Region, Korea, tr("Korea")),
+ PAIR(Region, Taiwan, tr("Taiwan")),
}});
translations->insert(
{Settings::EnumMetadata<Settings::TimeZone>::Index(),
@@ -324,72 +349,74 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
{static_cast<u32>(Settings::TimeZone::Default),
tr("Default (%1)", "Default time zone")
.arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))},
- PAIR(TimeZone, Cet, "CET"),
- PAIR(TimeZone, Cst6Cdt, "CST6CDT"),
- PAIR(TimeZone, Cuba, "Cuba"),
- PAIR(TimeZone, Eet, "EET"),
- PAIR(TimeZone, Egypt, "Egypt"),
- PAIR(TimeZone, Eire, "Eire"),
- PAIR(TimeZone, Est, "EST"),
- PAIR(TimeZone, Est5Edt, "EST5EDT"),
- PAIR(TimeZone, Gb, "GB"),
- PAIR(TimeZone, GbEire, "GB-Eire"),
- PAIR(TimeZone, Gmt, "GMT"),
- PAIR(TimeZone, GmtPlusZero, "GMT+0"),
- PAIR(TimeZone, GmtMinusZero, "GMT-0"),
- PAIR(TimeZone, GmtZero, "GMT0"),
- PAIR(TimeZone, Greenwich, "Greenwich"),
- PAIR(TimeZone, Hongkong, "Hongkong"),
- PAIR(TimeZone, Hst, "HST"),
- PAIR(TimeZone, Iceland, "Iceland"),
- PAIR(TimeZone, Iran, "Iran"),
- PAIR(TimeZone, Israel, "Israel"),
- PAIR(TimeZone, Jamaica, "Jamaica"),
- PAIR(TimeZone, Japan, "Japan"),
- PAIR(TimeZone, Kwajalein, "Kwajalein"),
- PAIR(TimeZone, Libya, "Libya"),
- PAIR(TimeZone, Met, "MET"),
- PAIR(TimeZone, Mst, "MST"),
- PAIR(TimeZone, Mst7Mdt, "MST7MDT"),
- PAIR(TimeZone, Navajo, "Navajo"),
- PAIR(TimeZone, Nz, "NZ"),
- PAIR(TimeZone, NzChat, "NZ-CHAT"),
- PAIR(TimeZone, Poland, "Poland"),
- PAIR(TimeZone, Portugal, "Portugal"),
- PAIR(TimeZone, Prc, "PRC"),
- PAIR(TimeZone, Pst8Pdt, "PST8PDT"),
- PAIR(TimeZone, Roc, "ROC"),
- PAIR(TimeZone, Rok, "ROK"),
- PAIR(TimeZone, Singapore, "Singapore"),
- PAIR(TimeZone, Turkey, "Turkey"),
- PAIR(TimeZone, Uct, "UCT"),
- PAIR(TimeZone, Universal, "Universal"),
- PAIR(TimeZone, Utc, "UTC"),
- PAIR(TimeZone, WSu, "W-SU"),
- PAIR(TimeZone, Wet, "WET"),
- PAIR(TimeZone, Zulu, "Zulu"),
+ PAIR(TimeZone, Cet, tr("CET")),
+ PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")),
+ PAIR(TimeZone, Cuba, tr("Cuba")),
+ PAIR(TimeZone, Eet, tr("EET")),
+ PAIR(TimeZone, Egypt, tr("Egypt")),
+ PAIR(TimeZone, Eire, tr("Eire")),
+ PAIR(TimeZone, Est, tr("EST")),
+ PAIR(TimeZone, Est5Edt, tr("EST5EDT")),
+ PAIR(TimeZone, Gb, tr("GB")),
+ PAIR(TimeZone, GbEire, tr("GB-Eire")),
+ PAIR(TimeZone, Gmt, tr("GMT")),
+ PAIR(TimeZone, GmtPlusZero, tr("GMT+0")),
+ PAIR(TimeZone, GmtMinusZero, tr("GMT-0")),
+ PAIR(TimeZone, GmtZero, tr("GMT0")),
+ PAIR(TimeZone, Greenwich, tr("Greenwich")),
+ PAIR(TimeZone, Hongkong, tr("Hongkong")),
+ PAIR(TimeZone, Hst, tr("HST")),
+ PAIR(TimeZone, Iceland, tr("Iceland")),
+ PAIR(TimeZone, Iran, tr("Iran")),
+ PAIR(TimeZone, Israel, tr("Israel")),
+ PAIR(TimeZone, Jamaica, tr("Jamaica")),
+ PAIR(TimeZone, Japan, tr("Japan")),
+ PAIR(TimeZone, Kwajalein, tr("Kwajalein")),
+ PAIR(TimeZone, Libya, tr("Libya")),
+ PAIR(TimeZone, Met, tr("MET")),
+ PAIR(TimeZone, Mst, tr("MST")),
+ PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")),
+ PAIR(TimeZone, Navajo, tr("Navajo")),
+ PAIR(TimeZone, Nz, tr("NZ")),
+ PAIR(TimeZone, NzChat, tr("NZ-CHAT")),
+ PAIR(TimeZone, Poland, tr("Poland")),
+ PAIR(TimeZone, Portugal, tr("Portugal")),
+ PAIR(TimeZone, Prc, tr("PRC")),
+ PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")),
+ PAIR(TimeZone, Roc, tr("ROC")),
+ PAIR(TimeZone, Rok, tr("ROK")),
+ PAIR(TimeZone, Singapore, tr("Singapore")),
+ PAIR(TimeZone, Turkey, tr("Turkey")),
+ PAIR(TimeZone, Uct, tr("UCT")),
+ PAIR(TimeZone, Universal, tr("Universal")),
+ PAIR(TimeZone, Utc, tr("UTC")),
+ PAIR(TimeZone, WSu, tr("W-SU")),
+ PAIR(TimeZone, Wet, tr("WET")),
+ PAIR(TimeZone, Zulu, tr("Zulu")),
}});
translations->insert({Settings::EnumMetadata<Settings::AudioMode>::Index(),
{
- PAIR(AudioMode, Mono, "Mono"),
- PAIR(AudioMode, Stereo, "Stereo"),
- PAIR(AudioMode, Surround, "Surround"),
+ PAIR(AudioMode, Mono, tr("Mono")),
+ PAIR(AudioMode, Stereo, tr("Stereo")),
+ PAIR(AudioMode, Surround, tr("Surround")),
}});
translations->insert({Settings::EnumMetadata<Settings::MemoryLayout>::Index(),
{
- PAIR(MemoryLayout, Memory_4Gb, "4GB DRAM (Default)"),
- PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"),
- PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"),
+ PAIR(MemoryLayout, Memory_4Gb, tr("4GB DRAM (Default)")),
+ PAIR(MemoryLayout, Memory_6Gb, tr("6GB DRAM (Unsafe)")),
+ PAIR(MemoryLayout, Memory_8Gb, tr("8GB DRAM (Unsafe)")),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
+ {
+ PAIR(ConsoleMode, Docked, tr("Docked")),
+ PAIR(ConsoleMode, Handheld, tr("Handheld")),
}});
- 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"),
+ PAIR(ConfirmStop, Ask_Always, tr("Always ask (Default)")),
+ PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")),
+ PAIR(ConfirmStop, Ask_Never, tr("Never ask")),
}});
#undef PAIR
diff --git a/src/yuzu/configuration/shared_translation.h b/src/yuzu/configuration/shared_translation.h
index 99a0e808c..d5fc3b8de 100644
--- a/src/yuzu/configuration/shared_translation.h
+++ b/src/yuzu/configuration/shared_translation.h
@@ -10,6 +10,7 @@
#include <vector>
#include <QString>
#include "common/common_types.h"
+#include "common/settings.h"
class QWidget;
@@ -22,4 +23,46 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent);
std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent);
+static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map = {
+ {Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))},
+ {Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))},
+ {Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))},
+};
+
+static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map = {
+ {Settings::ScalingFilter::NearestNeighbor,
+ QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Nearest"))},
+ {Settings::ScalingFilter::Bilinear,
+ QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))},
+ {Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))},
+ {Settings::ScalingFilter::Gaussian,
+ QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))},
+ {Settings::ScalingFilter::ScaleForce,
+ QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))},
+ {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
+};
+
+static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map = {
+ {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
+ {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
+};
+
+static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map = {
+ {Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
+ {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
+ {Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
+};
+
+static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map = {
+ {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))},
+ {Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))},
+ {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
+};
+
+static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map = {
+ {Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
+ {Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
+ {Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
+};
+
} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index ea8d7add4..941683a43 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -194,7 +194,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
return group;
}
- const auto get_selected = [=]() -> int {
+ const auto get_selected = [this]() -> int {
for (const auto& [id, button] : radio_buttons) {
if (button->isChecked()) {
return id;
@@ -203,7 +203,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
return -1;
};
- const auto set_index = [=](u32 value) {
+ const auto set_index = [this](u32 value) {
for (const auto& [id, button] : radio_buttons) {
button->setChecked(id == value);
}
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 0783a2430..6d227ef8d 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -36,10 +36,8 @@ constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{
bool IsDarkTheme() {
const auto& theme = UISettings::values.theme;
- return theme == QStringLiteral("qdarkstyle") ||
- theme == QStringLiteral("qdarkstyle_midnight_blue") ||
- theme == QStringLiteral("colorful_dark") ||
- theme == QStringLiteral("colorful_midnight_blue");
+ return theme == std::string("qdarkstyle") || theme == std::string("qdarkstyle_midnight_blue") ||
+ theme == std::string("colorful_dark") || theme == std::string("colorful_midnight_blue");
}
} // namespace
@@ -127,7 +125,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
return list;
}
- if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64BitProcess()) {
+ if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64Bit()) {
return list;
}
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 2bb1a0239..59b317135 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -278,7 +278,7 @@ void GameList::OnUpdateThemedIcons() {
case GameListItemType::CustomDir: {
const UISettings::GameDir& game_dir =
UISettings::values.game_dirs[child->data(GameListDir::GameDirRole).toInt()];
- const QString icon_name = QFileInfo::exists(game_dir.path)
+ const QString icon_name = QFileInfo::exists(QString::fromStdString(game_dir.path))
? QStringLiteral("folder")
: QStringLiteral("bad_folder");
child->setData(
@@ -380,7 +380,6 @@ void GameList::UnloadController() {
GameList::~GameList() {
UnloadController();
- emit ShouldCancelWorker();
}
void GameList::SetFilterFocus() {
@@ -397,6 +396,10 @@ void GameList::ClearFilter() {
search_field->clear();
}
+void GameList::WorkerEvent() {
+ current_worker->ProcessEvents(this);
+}
+
void GameList::AddDirEntry(GameListDir* entry_items) {
item_model->invisibleRootItem()->appendRow(entry_items);
tree_view->setExpanded(
@@ -564,9 +567,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
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"));
+// TODO: Implement shortcut creation for macOS
+#if !defined(__APPLE__)
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
@@ -644,10 +648,11 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
});
+// TODO: Implement shortcut creation for macOS
+#if !defined(__APPLE__)
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);
});
@@ -722,7 +727,8 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
});
connect(open_directory_location, &QAction::triggered, [this, game_dir_index] {
- emit OpenDirectory(UISettings::values.game_dirs[game_dir_index].path);
+ emit OpenDirectory(
+ QString::fromStdString(UISettings::values.game_dirs[game_dir_index].path));
});
}
@@ -826,28 +832,21 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
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();
+ // Cancel any existing worker.
+ current_worker.reset();
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
search_field->clear();
- GameListWorker* worker =
- new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
+ current_worker = std::make_unique<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,
- Qt::QueuedConnection);
- connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
+ // Get events from the worker as data becomes available
+ connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent,
Qt::QueuedConnection);
- // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to
- // cancel without delay.
- connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel,
- Qt::DirectConnection);
- QThreadPool::globalInstance()->start(worker);
- current_worker = std::move(worker);
+ QThreadPool::globalInstance()->start(current_worker.get());
}
void GameList::SaveInterfaceLayout() {
@@ -871,7 +870,7 @@ const QStringList GameList::supported_file_extensions = {
QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
void GameList::RefreshGameDirectory() {
- if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
+ if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
PopulateAsync(UISettings::values.game_dirs);
}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 712570cea..563a3a35b 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -109,7 +109,6 @@ signals:
void BootGame(const QString& game_path, u64 program_id, std::size_t program_index,
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,
const std::string& game_path);
void OpenTransferableShaderCacheRequested(u64 program_id);
@@ -138,11 +137,16 @@ private slots:
void OnUpdateThemedIcons();
private:
+ friend class GameListWorker;
+ void WorkerEvent();
+
void AddDirEntry(GameListDir* entry_items);
void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
- void ValidateEntry(const QModelIndex& item);
void DonePopulating(const QStringList& watch_list);
+private:
+ void ValidateEntry(const QModelIndex& item);
+
void RefreshGameDirectory();
void ToggleFavorite(u64 program_id);
@@ -165,7 +169,7 @@ private:
QVBoxLayout* layout = nullptr;
QTreeView* tree_view = nullptr;
QStandardItemModel* item_model = nullptr;
- GameListWorker* current_worker = nullptr;
+ std::unique_ptr<GameListWorker> current_worker;
QFileSystemWatcher* watcher = nullptr;
ControllerNavigation* controller_navigation = nullptr;
CompatibilityList compatibility_list;
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 86a0c41d9..c330b574f 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -286,13 +286,13 @@ public:
setData(QObject::tr("System Titles"), Qt::DisplayRole);
break;
case GameListItemType::CustomDir: {
- const QString icon_name = QFileInfo::exists(game_dir->path)
- ? QStringLiteral("folder")
- : QStringLiteral("bad_folder");
+ const QString path = QString::fromStdString(game_dir->path);
+ const QString icon_name =
+ QFileInfo::exists(path) ? QStringLiteral("folder") : QStringLiteral("bad_folder");
setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
Qt::DecorationRole);
- setData(game_dir->path, Qt::DisplayRole);
+ setData(path, Qt::DisplayRole);
break;
}
default:
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 077ced12b..dc006832e 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -233,10 +233,53 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
const PlayTime::PlayTimeManager& play_time_manager_,
Core::System& system_)
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
- compatibility_list{compatibility_list_},
- play_time_manager{play_time_manager_}, system{system_} {}
+ compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{
+ system_} {
+ // We want the game list to manage our lifetime.
+ setAutoDelete(false);
+}
+
+GameListWorker::~GameListWorker() {
+ this->disconnect();
+ stop_requested.store(true);
+ processing_completed.Wait();
+}
+
+void GameListWorker::ProcessEvents(GameList* game_list) {
+ while (true) {
+ std::function<void(GameList*)> func;
+ {
+ // Lock queue to protect concurrent modification.
+ std::scoped_lock lk(lock);
+
+ // If we can't pop a function, return.
+ if (queued_events.empty()) {
+ return;
+ }
+
+ // Pop a function.
+ func = std::move(queued_events.back());
+ queued_events.pop_back();
+ }
+
+ // Run the function.
+ func(game_list);
+ }
+}
+
+template <typename F>
+void GameListWorker::RecordEvent(F&& func) {
+ {
+ // Lock queue to protect concurrent modification.
+ std::scoped_lock lk(lock);
-GameListWorker::~GameListWorker() = default;
+ // Add the function into the front of the queue.
+ queued_events.emplace_front(std::move(func));
+ }
+
+ // Data now available.
+ emit DataAvailable();
+}
void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
using namespace FileSys;
@@ -284,9 +327,9 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
GetMetadataFromControlNCA(patch, *control, icon, name);
}
- emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
- program_id, compatibility_list, play_time_manager, patch),
- parent_dir);
+ auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
+ program_id, compatibility_list, play_time_manager, patch);
+ RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
}
@@ -360,11 +403,12 @@ 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,
- Common::FS::GetSize(physical_name), icon,
- *loader, id, compatibility_list,
- play_time_manager, patch),
- parent_dir);
+ auto entry = MakeGameListEntry(
+ physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
+ id, compatibility_list, play_time_manager, patch);
+
+ RecordEvent(
+ [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
} else {
std::vector<u8> icon;
@@ -376,11 +420,12 @@ 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,
- Common::FS::GetSize(physical_name), icon,
- *loader, program_id, compatibility_list,
- play_time_manager, patch),
- parent_dir);
+ auto entry = MakeGameListEntry(
+ physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
+ program_id, compatibility_list, play_time_manager, patch);
+
+ RecordEvent(
+ [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
}
} else if (is_dir) {
@@ -399,38 +444,41 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
}
void GameListWorker::run() {
+ watch_list.clear();
provider->ClearAllEntries();
+ const auto DirEntryReady = [&](GameListDir* game_list_dir) {
+ RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); });
+ };
+
for (UISettings::GameDir& game_dir : game_dirs) {
- if (game_dir.path == QStringLiteral("SDMC")) {
+ if (stop_requested) {
+ break;
+ }
+
+ if (game_dir.path == std::string("SDMC")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
- emit DirEntryReady(game_list_dir);
+ DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
- } else if (game_dir.path == QStringLiteral("UserNAND")) {
+ } else if (game_dir.path == std::string("UserNAND")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
- emit DirEntryReady(game_list_dir);
+ DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
- } else if (game_dir.path == QStringLiteral("SysNAND")) {
+ } else if (game_dir.path == std::string("SysNAND")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
- emit DirEntryReady(game_list_dir);
+ DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
} else {
- watch_list.append(game_dir.path);
+ watch_list.append(QString::fromStdString(game_dir.path));
auto* const game_list_dir = new GameListDir(game_dir);
- emit DirEntryReady(game_list_dir);
- ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(),
- game_dir.deep_scan, game_list_dir);
- ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
- game_dir.deep_scan, game_list_dir);
+ DirEntryReady(game_list_dir);
+ ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan,
+ game_list_dir);
+ ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan,
+ game_list_dir);
}
}
- emit Finished(watch_list);
+ RecordEvent([this](GameList* game_list) { game_list->DonePopulating(watch_list); });
processing_completed.Set();
}
-
-void GameListWorker::Cancel() {
- this->disconnect();
- stop_requested.store(true);
- processing_completed.Wait();
-}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 54dc05e30..d5990fcde 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -4,6 +4,7 @@
#pragma once
#include <atomic>
+#include <deque>
#include <memory>
#include <string>
@@ -20,6 +21,7 @@ namespace Core {
class System;
}
+class GameList;
class QStandardItem;
namespace FileSys {
@@ -46,24 +48,22 @@ public:
/// Starts the processing of directory tree information.
void run() override;
- /// Tells the worker that it should no longer continue processing. Thread-safe.
- void Cancel();
-
-signals:
+public:
/**
- * The `EntryReady` signal is emitted once an entry has been prepared and is ready
- * to be added to the game list.
- * @param entry_items a list with `QStandardItem`s that make up the columns of the new
- * entry.
+ * Synchronously processes any events queued by the worker.
+ *
+ * AddDirEntry is called on the game list for every discovered directory.
+ * AddEntry is called on the game list for every discovered program.
+ * DonePopulating is called on the game list when processing completes.
*/
- void DirEntryReady(GameListDir* entry_items);
- void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir);
+ void ProcessEvents(GameList* game_list);
- /**
- * After the worker has traversed the game directory looking for entries, this signal is
- * emitted with a list of folders that should be watched for changes as well.
- */
- void Finished(QStringList watch_list);
+signals:
+ void DataAvailable();
+
+private:
+ template <typename F>
+ void RecordEvent(F&& func);
private:
void AddTitlesToGameList(GameListDir* parent_dir);
@@ -84,8 +84,11 @@ private:
QStringList watch_list;
- Common::Event processing_completed;
+ std::mutex lock;
+ std::condition_variable cv;
+ std::deque<std::function<void(GameList*)>> queued_events;
std::atomic_bool stop_requested = false;
+ Common::Event processing_completed;
Core::System& system;
};
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index 6530186c1..eebfbf155 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -19,7 +19,7 @@ void HotkeyRegistry::SaveHotkeys() {
for (const auto& hotkey : group.second) {
UISettings::values.shortcuts.push_back(
{hotkey.first, group.first,
- UISettings::ContextualShortcut({hotkey.second.keyseq.toString(),
+ UISettings::ContextualShortcut({hotkey.second.keyseq.toString().toStdString(),
hotkey.second.controller_keyseq,
hotkey.second.context, hotkey.second.repeat})});
}
@@ -31,12 +31,12 @@ void HotkeyRegistry::LoadHotkeys() {
// beginGroup()
for (auto shortcut : UISettings::values.shortcuts) {
Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
- if (!shortcut.shortcut.keyseq.isEmpty()) {
- hk.keyseq =
- QKeySequence::fromString(shortcut.shortcut.keyseq, QKeySequence::NativeText);
+ if (!shortcut.shortcut.keyseq.empty()) {
+ hk.keyseq = QKeySequence::fromString(QString::fromStdString(shortcut.shortcut.keyseq),
+ QKeySequence::NativeText);
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context);
}
- if (!shortcut.shortcut.controller_keyseq.isEmpty()) {
+ if (!shortcut.shortcut.controller_keyseq.empty()) {
hk.controller_keyseq = shortcut.shortcut.controller_keyseq;
}
if (hk.shortcut) {
@@ -51,7 +51,8 @@ void HotkeyRegistry::LoadHotkeys() {
}
}
-QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
+QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string& action,
+ QWidget* widget) {
Hotkey& hk = hotkey_groups[group][action];
if (!hk.shortcut) {
@@ -62,7 +63,8 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
return hk.shortcut;
}
-ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const QString& group, const QString& action,
+ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group,
+ const std::string& action,
Core::HID::EmulatedController* controller) {
Hotkey& hk = hotkey_groups[group][action];
@@ -74,12 +76,12 @@ ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const QString& group, co
return hk.controller_shortcut;
}
-QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
+QKeySequence HotkeyRegistry::GetKeySequence(const std::string& group, const std::string& action) {
return hotkey_groups[group][action].keyseq;
}
-Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
- const QString& action) {
+Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const std::string& group,
+ const std::string& action) {
return hotkey_groups[group][action].context;
}
@@ -101,10 +103,10 @@ void ControllerShortcut::SetKey(const ControllerButtonSequence& buttons) {
button_sequence = buttons;
}
-void ControllerShortcut::SetKey(const QString& buttons_shortcut) {
+void ControllerShortcut::SetKey(const std::string& buttons_shortcut) {
ControllerButtonSequence sequence{};
- name = buttons_shortcut.toStdString();
- std::istringstream command_line(buttons_shortcut.toStdString());
+ name = buttons_shortcut;
+ std::istringstream command_line(buttons_shortcut);
std::string line;
while (std::getline(command_line, line, '+')) {
if (line.empty()) {
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 56eee8d82..e11332d2e 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -33,7 +33,7 @@ public:
~ControllerShortcut();
void SetKey(const ControllerButtonSequence& buttons);
- void SetKey(const QString& buttons_shortcut);
+ void SetKey(const std::string& buttons_shortcut);
ControllerButtonSequence ButtonSequence() const;
@@ -88,8 +88,8 @@ public:
* will be the same. Thus, you shouldn't rely on the caller really being the
* QShortcut's parent.
*/
- QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
- ControllerShortcut* GetControllerHotkey(const QString& group, const QString& action,
+ QShortcut* GetHotkey(const std::string& group, const std::string& action, QWidget* widget);
+ ControllerShortcut* GetControllerHotkey(const std::string& group, const std::string& action,
Core::HID::EmulatedController* controller);
/**
@@ -98,7 +98,7 @@ public:
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
*/
- QKeySequence GetKeySequence(const QString& group, const QString& action);
+ QKeySequence GetKeySequence(const std::string& group, const std::string& action);
/**
* Returns a Qt::ShortcutContext object who can be connected to other
@@ -108,20 +108,20 @@ public:
* "Debugger").
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
*/
- Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
+ Qt::ShortcutContext GetShortcutContext(const std::string& group, const std::string& action);
private:
struct Hotkey {
QKeySequence keyseq;
- QString controller_keyseq;
+ std::string controller_keyseq;
QShortcut* shortcut = nullptr;
ControllerShortcut* controller_shortcut = nullptr;
Qt::ShortcutContext context = Qt::WindowShortcut;
bool repeat;
};
- using HotkeyMap = std::map<QString, Hotkey>;
- using HotkeyGroupMap = std::map<QString, HotkeyMap>;
+ using HotkeyMap = std::map<std::string, Hotkey>;
+ using HotkeyGroupMap = std::map<std::string, HotkeyMap>;
HotkeyGroupMap hotkey_groups;
};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 1431cf2fe..10c788290 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -10,12 +10,14 @@
#include <thread>
#include "core/loader/nca.h"
#include "core/tools/renderdoc.h"
+
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
#ifdef __unix__
#include <csignal>
#include <sys/socket.h>
+#include "common/linux/gamemode.h"
#endif
#include <boost/container/flat_set.hpp>
@@ -46,6 +48,7 @@
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/set/set_sys.h"
#include "yuzu/multiplayer/state.h"
#include "yuzu/util/controller_navigation.h"
@@ -127,6 +130,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/telemetry_session.h"
+#include "frontend_common/config.h"
#include "input_common/drivers/tas_input.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/main.h"
@@ -139,9 +143,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/bootmanager.h"
#include "yuzu/compatdb.h"
#include "yuzu/compatibility_list.h"
-#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
#include "yuzu/configuration/configure_input_per_game.h"
+#include "yuzu/configuration/qt_config.h"
#include "yuzu/debugger/console.h"
#include "yuzu/debugger/controller.h"
#include "yuzu/debugger/profiler.h"
@@ -159,8 +163,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/util/clickable_label.h"
#include "yuzu/vk_device_info.h"
-#ifdef YUZU_DBGHELP
-#include "yuzu/mini_dump.h"
+#ifdef YUZU_CRASH_DUMPS
+#include "yuzu/breakpad.h"
#endif
using namespace Common::Literals;
@@ -184,7 +188,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
#endif
constexpr int default_mouse_hide_timeout = 2500;
-constexpr int default_mouse_center_timeout = 10;
constexpr int default_input_update_timeout = 1;
constexpr size_t CopyBufferSize = 1_MiB;
@@ -310,13 +313,14 @@ bool GMainWindow::CheckDarkMode() {
#endif // __unix__
}
-GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
+GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulkan)
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
provider{std::make_unique<FileSys::ManualContentProvider>()} {
#ifdef __unix__
SetupSigInterrupts();
+ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
#endif
system->Initialize();
@@ -434,9 +438,6 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
- mouse_center_timer.setInterval(default_mouse_center_timeout);
- connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
-
update_input_timer.setInterval(default_input_update_timeout);
connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers);
update_input_timer.start();
@@ -675,7 +676,7 @@ void GMainWindow::ControllerSelectorReconfigureControllers(
// Don't forget to apply settings.
system->HIDCore().DisableAllControllerConfiguration();
system->ApplySettings();
- config->Save();
+ config->SaveAllValues();
UpdateStatusButtons();
@@ -1046,7 +1047,12 @@ void GMainWindow::InitializeWidgets() {
statusBar()->addPermanentWidget(label);
}
- // TODO (flTobi): Add the widget when multiplayer is fully implemented
+ firmware_label = new QLabel();
+ firmware_label->setObjectName(QStringLiteral("FirmwareLabel"));
+ firmware_label->setVisible(false);
+ firmware_label->setFocusPolicy(Qt::NoFocus);
+ statusBar()->addPermanentWidget(firmware_label);
+
statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
@@ -1064,19 +1070,19 @@ void GMainWindow::InitializeWidgets() {
volume_slider->setObjectName(QStringLiteral("volume_slider"));
volume_slider->setMaximum(200);
volume_slider->setPageStep(5);
- connect(volume_slider, &QSlider::valueChanged, this, [this](int percentage) {
- Settings::values.audio_muted = false;
- const auto volume = static_cast<u8>(percentage);
- Settings::values.volume.SetValue(volume);
- UpdateVolumeUI();
- });
volume_popup->layout()->addWidget(volume_slider);
- volume_button = new QPushButton();
+ volume_button = new VolumeButton();
volume_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
volume_button->setFocusPolicy(Qt::NoFocus);
volume_button->setCheckable(true);
UpdateVolumeUI();
+ connect(volume_slider, &QSlider::valueChanged, this, [this](int percentage) {
+ Settings::values.audio_muted = false;
+ const auto volume = static_cast<u8>(percentage);
+ Settings::values.volume.SetValue(volume);
+ UpdateVolumeUI();
+ });
connect(volume_button, &QPushButton::clicked, this, [&] {
UpdateVolumeUI();
volume_popup->setVisible(!volume_popup->isVisible());
@@ -1103,6 +1109,8 @@ void GMainWindow::InitializeWidgets() {
context_menu.exec(volume_button->mapToGlobal(menu_location));
volume_button->repaint();
});
+ connect(volume_button, &VolumeButton::VolumeChanged, this, &GMainWindow::UpdateVolumeUI);
+
statusBar()->insertPermanentWidget(0, volume_button);
// setup AA button
@@ -1126,7 +1134,7 @@ void GMainWindow::InitializeWidgets() {
connect(aa_status_button, &QPushButton::customContextMenuRequested,
[this](const QPoint& menu_location) {
QMenu context_menu;
- for (auto const& aa_text_pair : Config::anti_aliasing_texts_map) {
+ for (auto const& aa_text_pair : ConfigurationShared::anti_aliasing_texts_map) {
context_menu.addAction(aa_text_pair.second, [this, aa_text_pair] {
Settings::values.anti_aliasing.SetValue(aa_text_pair.first);
UpdateAAText();
@@ -1150,7 +1158,7 @@ void GMainWindow::InitializeWidgets() {
connect(filter_status_button, &QPushButton::customContextMenuRequested,
[this](const QPoint& menu_location) {
QMenu context_menu;
- for (auto const& filter_text_pair : Config::scaling_filter_texts_map) {
+ for (auto const& filter_text_pair : ConfigurationShared::scaling_filter_texts_map) {
context_menu.addAction(filter_text_pair.second, [this, filter_text_pair] {
Settings::values.scaling_filter.SetValue(filter_text_pair.first);
UpdateFilterText();
@@ -1173,7 +1181,7 @@ void GMainWindow::InitializeWidgets() {
[this](const QPoint& menu_location) {
QMenu context_menu;
- for (auto const& pair : Config::use_docked_mode_texts_map) {
+ for (auto const& pair : ConfigurationShared::use_docked_mode_texts_map) {
context_menu.addAction(pair.second, [this, &pair] {
if (pair.first != Settings::values.use_docked_mode.GetValue()) {
OnToggleDockedMode();
@@ -1197,7 +1205,7 @@ void GMainWindow::InitializeWidgets() {
[this](const QPoint& menu_location) {
QMenu context_menu;
- for (auto const& gpu_accuracy_pair : Config::gpu_accuracy_texts_map) {
+ for (auto const& gpu_accuracy_pair : ConfigurationShared::gpu_accuracy_texts_map) {
if (gpu_accuracy_pair.first == Settings::GpuAccuracy::Extreme) {
continue;
}
@@ -1226,7 +1234,8 @@ void GMainWindow::InitializeWidgets() {
[this](const QPoint& menu_location) {
QMenu context_menu;
- for (auto const& renderer_backend_pair : Config::renderer_backend_texts_map) {
+ for (auto const& renderer_backend_pair :
+ ConfigurationShared::renderer_backend_texts_map) {
if (renderer_backend_pair.first == Settings::RendererBackend::Null) {
continue;
}
@@ -1291,16 +1300,17 @@ void GMainWindow::InitializeRecentFileMenuActions() {
void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name,
const bool tas_allowed) {
- static const QString main_window = QStringLiteral("Main Window");
- action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name));
- action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name));
+ static const auto main_window = std::string("Main Window");
+ action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name.toStdString()));
+ action->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, action_name.toStdString()));
action->setAutoRepeat(false);
this->addAction(action);
auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
const auto* controller_hotkey =
- hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
+ hotkey_registry.GetControllerHotkey(main_window, action_name.toStdString(), controller);
connect(
controller_hotkey, &ControllerShortcut::Activated, this,
[action, tas_allowed, this] {
@@ -1332,10 +1342,11 @@ void GMainWindow::InitializeHotkeys() {
static const QString main_window = QStringLiteral("Main Window");
const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) {
- const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this);
+ const auto* hotkey =
+ hotkey_registry.GetHotkey(main_window.toStdString(), action_name.toStdString(), this);
auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
- const auto* controller_hotkey =
- hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
+ const auto* controller_hotkey = hotkey_registry.GetControllerHotkey(
+ main_window.toStdString(), action_name.toStdString(), controller);
connect(hotkey, &QShortcut::activated, this, function);
connect(controller_hotkey, &ControllerShortcut::Activated, this, function,
Qt::QueuedConnection);
@@ -1363,14 +1374,6 @@ void GMainWindow::InitializeHotkeys() {
}
});
connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
- if (Settings::values.mouse_enabled) {
- Settings::values.mouse_panning = false;
- QMessageBox::warning(
- this, tr("Emulated mouse is enabled"),
- tr("Real mouse input and mouse panning are incompatible. Please disable the "
- "emulated mouse in input advanced settings to allow mouse panning."));
- return;
- }
Settings::values.mouse_panning = !Settings::values.mouse_panning;
if (Settings::values.mouse_panning) {
render_window->installEventFilter(render_window);
@@ -1572,6 +1575,7 @@ void GMainWindow::ConnectMenuEvents() {
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_Open_Controller_Menu, &GMainWindow::OnOpenControllerMenu);
connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
// TAS
@@ -1599,14 +1603,13 @@ 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,
- };
+ 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,
+ ui->action_Open_Controller_Menu};
for (QAction* action : running_actions) {
action->setEnabled(emulation_running);
@@ -1906,12 +1909,16 @@ void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index,
StartGameType type, AmLaunchType launch_type) {
LOG_INFO(Frontend, "yuzu starting...");
- StoreRecentFile(filename); // Put the filename on top of the list
+
+ if (program_id == 0 ||
+ program_id > static_cast<u64>(Service::AM::Applets::AppletProgramId::MaxProgramId)) {
+ StoreRecentFile(filename); // Put the filename on top of the list
+ }
// Save configurations
UpdateUISettings();
game_list->SaveInterfaceLayout();
- config->Save();
+ config->SaveAllValues();
u64 title_id{0};
@@ -1929,7 +1936,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
const auto config_file_name = title_id == 0
? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
- Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
+ QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
system->HIDCore().ReloadInputDevices();
system->ApplySettings();
}
@@ -2019,7 +2026,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}
.filename());
}
- const bool is_64bit = system->Kernel().ApplicationProcess()->Is64BitProcess();
+ const bool is_64bit = system->Kernel().ApplicationProcess()->Is64Bit();
const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)");
title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit")
.arg(QString::fromStdString(title_name), instruction_set_suffix)
@@ -2115,6 +2122,10 @@ void GMainWindow::OnEmulationStopped() {
discord_rpc->Update();
+#ifdef __unix__
+ Common::Linux::StopGamemode();
+#endif
+
// The emulation is stopped, so closing the window or not does not matter anymore
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
@@ -2154,6 +2165,10 @@ void GMainWindow::OnEmulationStopped() {
emu_frametime_label->setVisible(false);
renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
+ if (!firmware_label->text().isEmpty()) {
+ firmware_label->setVisible(true);
+ }
+
current_game_path.clear();
// When closing the game, destroy the GLWindow to clear the context after the game is closed
@@ -2172,6 +2187,7 @@ void GMainWindow::ShutdownGame() {
return;
}
+ play_time_manager->Stop();
OnShutdownBegin();
OnEmulationStopTimeExpired();
OnEmulationStopped();
@@ -2735,7 +2751,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
+ const auto extracted = FileSys::ExtractRomFS(romfs);
if (extracted == nullptr) {
failed();
return;
@@ -2840,170 +2856,259 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
}
-void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
- GameListShortcutTarget target) {
- // Get path to yuzu executable
- const QStringList args = QApplication::arguments();
- std::filesystem::path yuzu_command = args[0].toStdString();
-
- // If relative path, make it an absolute path
- if (yuzu_command.c_str()[0] == '.') {
- yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
+bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
+ const std::string& comment,
+ const std::filesystem::path& icon_path,
+ const std::filesystem::path& command,
+ const std::string& arguments, const std::string& categories,
+ const std::string& keywords, const std::string& name) try {
+#if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD
+ std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
+ std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
+ if (!shortcut_stream.is_open()) {
+ LOG_ERROR(Frontend, "Failed to create shortcut");
+ return false;
}
-
-#if defined(__linux__)
- // Warn once if we are making a shortcut to a volatile AppImage
- const std::string appimage_ending =
- std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
- if (yuzu_command.string().ends_with(appimage_ending) &&
- !UISettings::values.shortcut_already_warned) {
- if (QMessageBox::warning(this, tr("Create Shortcut"),
- tr("This will create a shortcut to the current AppImage. This may "
- "not work well if you update. Continue?"),
- QMessageBox::StandardButton::Ok |
- QMessageBox::StandardButton::Cancel) ==
- QMessageBox::StandardButton::Cancel) {
- return;
+ // TODO: Migrate fmt::print to std::print in futures STD C++ 23.
+ fmt::print(shortcut_stream, "[Desktop Entry]\n");
+ fmt::print(shortcut_stream, "Type=Application\n");
+ fmt::print(shortcut_stream, "Version=1.0\n");
+ fmt::print(shortcut_stream, "Name={}\n", name);
+ if (!comment.empty()) {
+ fmt::print(shortcut_stream, "Comment={}\n", comment);
+ }
+ if (std::filesystem::is_regular_file(icon_path)) {
+ fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
+ }
+ fmt::print(shortcut_stream, "TryExec={}\n", command.string());
+ fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
+ if (!categories.empty()) {
+ fmt::print(shortcut_stream, "Categories={}\n", categories);
+ }
+ if (!keywords.empty()) {
+ fmt::print(shortcut_stream, "Keywords={}\n", keywords);
+ }
+ return true;
+#elif defined(_WIN32) // Windows
+ HRESULT hr = CoInitialize(nullptr);
+ if (FAILED(hr)) {
+ LOG_ERROR(Frontend, "CoInitialize failed");
+ return false;
+ }
+ SCOPE_EXIT({ CoUninitialize(); });
+ IShellLinkW* ps1 = nullptr;
+ IPersistFile* persist_file = nullptr;
+ SCOPE_EXIT({
+ if (persist_file != nullptr) {
+ persist_file->Release();
}
- UISettings::values.shortcut_already_warned = true;
+ if (ps1 != nullptr) {
+ ps1->Release();
+ }
+ });
+ HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
+ reinterpret_cast<void**>(&ps1));
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to create IShellLinkW instance");
+ return false;
}
-#endif // __linux__
-
- std::filesystem::path target_directory{};
-
- switch (target) {
- case GameListShortcutTarget::Desktop: {
- const QString desktop_path =
- QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
- target_directory = desktop_path.toUtf8().toStdString();
- break;
+ hres = ps1->SetPath(command.c_str());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set path");
+ return false;
}
- 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();
+ if (!arguments.empty()) {
+ hres = ps1->SetArguments(Common::UTF8ToUTF16W(arguments).data());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set arguments");
+ return false;
}
- break;
}
- default:
- return;
+ if (!comment.empty()) {
+ hres = ps1->SetDescription(Common::UTF8ToUTF16W(comment).data());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set description");
+ return false;
+ }
}
-
- 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;
+ if (std::filesystem::is_regular_file(icon_path)) {
+ hres = ps1->SetIconLocation(icon_path.c_str(), 0);
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set icon location");
+ return false;
+ }
}
+ hres = ps1->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&persist_file));
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to get IPersistFile interface");
+ return false;
+ }
+ hres = persist_file->Save(std::filesystem::path{shortcut_path / (name + ".lnk")}.c_str(), TRUE);
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to save shortcut");
+ return false;
+ }
+ return true;
+#else // Unsupported platform
+ return false;
+#endif
+} catch (const std::exception& e) {
+ LOG_ERROR(Frontend, "Failed to create shortcut: {}", e.what());
+ return false;
+}
+// Messages in pre-defined message boxes for less code spaghetti
+bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title) {
+ int result = 0;
+ QMessageBox::StandardButtons buttons;
+ switch (imsg) {
+ case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES:
+ buttons = QMessageBox::Yes | QMessageBox::No;
+ result =
+ QMessageBox::information(parent, tr("Create Shortcut"),
+ tr("Do you want to launch the game in fullscreen?"), buttons);
+ return result == QMessageBox::Yes;
+ case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS:
+ QMessageBox::information(parent, tr("Create Shortcut"),
+ tr("Successfully created a shortcut to %1").arg(game_title));
+ return false;
+ case GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING:
+ buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel;
+ result =
+ QMessageBox::warning(this, tr("Create Shortcut"),
+ tr("This will create a shortcut to the current AppImage. This may "
+ "not work well if you update. Continue?"),
+ buttons);
+ return result == QMessageBox::Ok;
+ default:
+ buttons = QMessageBox::Ok;
+ QMessageBox::critical(parent, tr("Create Shortcut"),
+ tr("Failed to create a shortcut to %1").arg(game_title), buttons);
+ return false;
+ }
+}
- 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/"
- : std::filesystem::path(xdg_data_home)) /
- "icons/hicolor/256x256";
- if (!Common::FS::CreateDirs(system_icons_path)) {
+bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
+ std::filesystem::path& out_icon_path) {
+ // Get path to Yuzu icons directory & icon extension
+ std::string ico_extension = "png";
+#if defined(_WIN32)
+ out_icon_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::IconsDir);
+ ico_extension = "ico";
+#elif defined(__linux__) || defined(__FreeBSD__)
+ out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256";
+#endif
+ // Create icons directory if it doesn't exist
+ if (!Common::FS::CreateDirs(out_icon_path)) {
QMessageBox::critical(
this, tr("Create Icon"),
tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
- .arg(QString::fromStdString(system_icons_path)),
+ .arg(QString::fromStdString(out_icon_path.string())),
QMessageBox::StandardButton::Ok);
- return;
+ out_icon_path.clear();
+ return false;
}
- std::filesystem::path icon_path =
- system_icons_path / (program_id == 0 ? fmt::format("yuzu-{}.png", game_file_name)
- : fmt::format("yuzu-{:016X}.png", program_id));
- 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
- std::string icon_extension;
-#endif
- // Get title from game file
- const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
- system->GetContentProvider()};
- const auto control = pm.GetControlMetadata();
- const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
-
- std::string title{fmt::format("{:016X}", program_id)};
-
- if (control.first != nullptr) {
- title = control.first->GetApplicationName();
- } else {
- loader->ReadTitle(title);
- }
+ // Create icon file path
+ out_icon_path /= (program_id == 0 ? fmt::format("yuzu-{}.{}", game_file_name, ico_extension)
+ : fmt::format("yuzu-{:016X}.{}", program_id, ico_extension));
+ return true;
+}
- // Get icon from game file
- std::vector<u8> icon_image_file{};
- if (control.second != nullptr) {
- icon_image_file = control.second->ReadAllBytes();
- } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
- LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
+void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
+ GameListShortcutTarget target) {
+ std::string game_title;
+ QString qt_game_title;
+ std::filesystem::path out_icon_path;
+ // Get path to yuzu executable
+ const QStringList args = QApplication::arguments();
+ std::filesystem::path yuzu_command = args[0].toStdString();
+ // If relative path, make it an absolute path
+ if (yuzu_command.c_str()[0] == '.') {
+ yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
}
-
- 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_data.save(QString::fromStdString(icon_path.string()))) {
- LOG_ERROR(Frontend, "Could not write icon as PNG to file");
+ // Shortcut path
+ std::filesystem::path shortcut_path{};
+ if (target == GameListShortcutTarget::Desktop) {
+ shortcut_path =
+ QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
+ } else if (target == GameListShortcutTarget::Applications) {
+ shortcut_path =
+ QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString();
+ }
+ // Icon path and title
+ if (std::filesystem::exists(shortcut_path)) {
+ // Get title from game file
+ const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
+ system->GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+ const auto loader =
+ Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
+ game_title = fmt::format("{:016X}", program_id);
+ if (control.first != nullptr) {
+ game_title = control.first->GetApplicationName();
+ } else {
+ loader->ReadTitle(game_title);
+ }
+ // Delete illegal characters from title
+ const std::string illegal_chars = "<>:\"/\\|?*.";
+ for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) {
+ if (illegal_chars.find(*it) != std::string::npos) {
+ game_title.erase(it.base() - 1);
+ }
+ }
+ qt_game_title = QString::fromStdString(game_title);
+ // Get icon from game file
+ std::vector<u8> icon_image_file{};
+ if (control.second != nullptr) {
+ icon_image_file = control.second->ReadAllBytes();
+ } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
+ LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
+ }
+ QImage icon_data =
+ QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
+ if (GMainWindow::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
+ if (!SaveIconToFile(out_icon_path, icon_data)) {
+ LOG_ERROR(Frontend, "Could not write icon 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");
+ GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
+ qt_game_title);
+ LOG_ERROR(Frontend, "Invalid shortcut target");
return;
}
+#if defined(__linux__)
+ // Special case for AppImages
+ // Warn once if we are making a shortcut to a volatile AppImage
+ const std::string appimage_ending =
+ std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
+ if (yuzu_command.string().ends_with(appimage_ending) &&
+ !UISettings::values.shortcut_already_warned) {
+ if (GMainWindow::CreateShortcutMessagesGUI(
+ this, GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, qt_game_title)) {
+ return;
+ }
+ UISettings::values.shortcut_already_warned = true;
+ }
#endif // __linux__
-
-#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, '_');
+ // Create shortcut
+ std::string arguments = fmt::format("-g \"{:s}\"", game_path);
+ if (GMainWindow::CreateShortcutMessagesGUI(
+ this, GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, qt_game_title)) {
+ arguments = "-f " + arguments;
}
- 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 comment = fmt::format("Start {:s} with the yuzu Emulator", game_title);
const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "Switch;Nintendo;";
- if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
- yuzu_command.string(), arguments, categories, keywords)) {
- QMessageBox::critical(this, tr("Create Shortcut"),
- tr("Failed to create a shortcut at %1")
- .arg(QString::fromStdString(shortcut_path.string())));
+ if (GMainWindow::CreateShortcutLink(shortcut_path, comment, out_icon_path, yuzu_command,
+ arguments, categories, keywords, game_title)) {
+ GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS,
+ qt_game_title);
return;
}
-
- LOG_INFO(Frontend, "Wrote a shortcut to {}", shortcut_path.string());
- QMessageBox::information(
- this, tr("Create Shortcut"),
- tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title)));
+ GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR,
+ qt_game_title);
}
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
@@ -3038,7 +3143,7 @@ void GMainWindow::OnGameListAddDirectory() {
return;
}
- UISettings::GameDir game_dir{dir_path, false, true};
+ UISettings::GameDir game_dir{dir_path.toStdString(), false, true};
if (!UISettings::values.game_dirs.contains(game_dir)) {
UISettings::values.game_dirs.append(game_dir);
game_list->PopulateAsync(UISettings::values.game_dirs);
@@ -3084,14 +3189,14 @@ void GMainWindow::OnMenuLoadFile() {
"%1 is an identifier for the Switch executable file extensions.")
.arg(extensions);
const QString filename = QFileDialog::getOpenFileName(
- this, tr("Load File"), UISettings::values.roms_path, file_filter);
+ this, tr("Load File"), QString::fromStdString(UISettings::values.roms_path), file_filter);
is_load_file_select_active = false;
if (filename.isEmpty()) {
return;
}
- UISettings::values.roms_path = QFileInfo(filename).path();
+ UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
BootGame(filename);
}
@@ -3124,7 +3229,8 @@ void GMainWindow::OnMenuInstallToNAND() {
"Image (*.xci)");
QStringList filenames = QFileDialog::getOpenFileNames(
- this, tr("Install Files"), UISettings::values.roms_path, file_filter);
+ this, tr("Install Files"), QString::fromStdString(UISettings::values.roms_path),
+ file_filter);
if (filenames.isEmpty()) {
return;
@@ -3142,7 +3248,7 @@ void GMainWindow::OnMenuInstallToNAND() {
}
// Save folder location of the first selected file
- UISettings::values.roms_path = QFileInfo(filenames[0]).path();
+ UISettings::values.roms_path = QFileInfo(filenames[0]).path().toStdString();
int remaining = filenames.size();
@@ -3402,6 +3508,10 @@ void GMainWindow::OnStartGame() {
play_time_manager->Start();
discord_rpc->Update();
+
+#ifdef __unix__
+ Common::Linux::StartGamemode();
+#endif
}
void GMainWindow::OnRestartGame() {
@@ -3422,6 +3532,10 @@ void GMainWindow::OnPauseGame() {
play_time_manager->Stop();
UpdateMenuState();
AllowOSSleep();
+
+#ifdef __unix__
+ Common::Linux::StopGamemode();
+#endif
}
void GMainWindow::OnPauseContinueGame() {
@@ -3482,12 +3596,12 @@ void GMainWindow::OnExecuteProgram(std::size_t program_index) {
}
void GMainWindow::OnExit() {
- OnStopGame();
+ ShutdownGame();
}
void GMainWindow::OnSaveConfig() {
system->ApplySettings();
- config->Save();
+ config->SaveAllValues();
}
void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) {
@@ -3703,6 +3817,9 @@ void GMainWindow::OnConfigure() {
const auto old_theme = UISettings::values.theme;
const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
const auto old_language_index = Settings::values.language_index.GetValue();
+#ifdef __unix__
+ const bool old_gamemode = Settings::values.enable_gamemode.GetValue();
+#endif
Settings::SetConfiguringGlobal(true);
ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(),
@@ -3743,7 +3860,7 @@ void GMainWindow::OnConfigure() {
Settings::values.disabled_addons.clear();
- config = std::make_unique<Config>();
+ config = std::make_unique<QtConfig>();
UISettings::values.reset_to_defaults = false;
UISettings::values.game_dirs = std::move(old_game_dirs);
@@ -3764,6 +3881,11 @@ void GMainWindow::OnConfigure() {
if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
}
+#ifdef __unix__
+ if (Settings::values.enable_gamemode.GetValue() != old_gamemode) {
+ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
+ }
+#endif
if (!multiplayer_state->IsHostingPublicRoom()) {
multiplayer_state->UpdateCredentials();
@@ -3778,7 +3900,7 @@ void GMainWindow::OnConfigure() {
UISettings::values.configuration_applied = false;
- config->Save();
+ config->SaveAllValues();
if ((UISettings::values.hide_mouse || Settings::values.mouse_panning) && emulation_running) {
render_window->installEventFilter(render_window);
@@ -3994,68 +4116,8 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
UISettings::values.configuration_applied = false;
if (!is_powered_on) {
- config->Save();
- }
-}
-
-bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title,
- const std::string& comment, const std::string& icon_path,
- const std::string& command, const std::string& arguments,
- const std::string& categories, const std::string& keywords) {
-#if defined(__linux__) || defined(__FreeBSD__)
- // This desktop file template was writing referencing
- // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
- std::string shortcut_contents{};
- shortcut_contents.append("[Desktop Entry]\n");
- shortcut_contents.append("Type=Application\n");
- shortcut_contents.append("Version=1.0\n");
- shortcut_contents.append(fmt::format("Name={:s}\n", title));
- shortcut_contents.append(fmt::format("Comment={:s}\n", comment));
- shortcut_contents.append(fmt::format("Icon={:s}\n", icon_path));
- shortcut_contents.append(fmt::format("TryExec={:s}\n", command));
- shortcut_contents.append(fmt::format("Exec={:s} {:s}\n", command, arguments));
- shortcut_contents.append(fmt::format("Categories={:s}\n", categories));
- shortcut_contents.append(fmt::format("Keywords={:s}\n", keywords));
-
- std::ofstream shortcut_stream(shortcut_path);
- if (!shortcut_stream.is_open()) {
- LOG_WARNING(Common, "Failed to create file {:s}", shortcut_path);
- return false;
- }
- shortcut_stream << shortcut_contents;
- 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;
+ config->SaveAllValues();
}
-
- persist_file->Release();
- shell_link->Release();
-
- return true;
-#endif
- return false;
}
void GMainWindow::OnLoadAmiibo() {
@@ -4096,7 +4158,6 @@ void GMainWindow::OnLoadAmiibo() {
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);
@@ -4270,7 +4331,7 @@ void GMainWindow::OnToggleStatusBar() {
}
void GMainWindow::OnAlbum() {
- constexpr u64 AlbumId = 0x010000000000100Dull;
+ constexpr u64 AlbumId = static_cast<u64>(Service::AM::Applets::AppletProgramId::PhotoViewer);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
QMessageBox::warning(this, tr("No firmware available"),
@@ -4288,12 +4349,12 @@ void GMainWindow::OnAlbum() {
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);
+ UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
+ BootGame(filename, AlbumId);
}
void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
- constexpr u64 CabinetId = 0x0100000000001002ull;
+ constexpr u64 CabinetId = static_cast<u64>(Service::AM::Applets::AppletProgramId::Cabinet);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
QMessageBox::warning(this, tr("No firmware available"),
@@ -4312,12 +4373,12 @@ void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
system->GetAppletManager().SetCabinetMode(mode);
const auto filename = QString::fromStdString(cabinet_nca->GetFullPath());
- UISettings::values.roms_path = QFileInfo(filename).path();
- BootGame(filename);
+ UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
+ BootGame(filename, CabinetId);
}
void GMainWindow::OnMiiEdit() {
- constexpr u64 MiiEditId = 0x0100000000001009ull;
+ constexpr u64 MiiEditId = static_cast<u64>(Service::AM::Applets::AppletProgramId::MiiEdit);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
QMessageBox::warning(this, tr("No firmware available"),
@@ -4335,8 +4396,33 @@ void GMainWindow::OnMiiEdit() {
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);
+ UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
+ BootGame(filename, MiiEditId);
+}
+
+void GMainWindow::OnOpenControllerMenu() {
+ constexpr u64 ControllerAppletId =
+ static_cast<u64>(Service::AM::Applets::AppletProgramId::Controller);
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Controller Menu."));
+ return;
+ }
+
+ auto controller_applet_nca =
+ bis_system->GetEntry(ControllerAppletId, FileSys::ContentRecordType::Program);
+ if (!controller_applet_nca) {
+ QMessageBox::warning(this, tr("Controller Applet"),
+ tr("Controller Menu is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Controller);
+
+ const auto filename = QString::fromStdString((controller_applet_nca->GetFullPath()));
+ UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
+ BootGame(filename, ControllerAppletId);
}
void GMainWindow::OnCaptureScreenshot() {
@@ -4525,11 +4611,13 @@ void GMainWindow::UpdateStatusBar() {
emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
game_fps_label->setVisible(true);
emu_frametime_label->setVisible(true);
+ firmware_label->setVisible(false);
}
void GMainWindow::UpdateGPUAccuracyButton() {
const auto gpu_accuracy = Settings::values.gpu_accuracy.GetValue();
- const auto gpu_accuracy_text = Config::gpu_accuracy_texts_map.find(gpu_accuracy)->second;
+ const auto gpu_accuracy_text =
+ ConfigurationShared::gpu_accuracy_texts_map.find(gpu_accuracy)->second;
gpu_accuracy_button->setText(gpu_accuracy_text.toUpper());
gpu_accuracy_button->setChecked(gpu_accuracy != Settings::GpuAccuracy::Normal);
}
@@ -4538,31 +4626,32 @@ void GMainWindow::UpdateDockedButton() {
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(console_mode)->second.toUpper());
+ ConfigurationShared::use_docked_mode_texts_map.find(console_mode)->second.toUpper());
}
void GMainWindow::UpdateAPIText() {
const auto api = Settings::values.renderer_backend.GetValue();
- const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second;
+ const auto renderer_status_text =
+ ConfigurationShared::renderer_backend_texts_map.find(api)->second;
renderer_status_button->setText(
api == Settings::RendererBackend::OpenGL
- ? tr("%1 %2").arg(
- renderer_status_text.toUpper(),
- Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue())
- ->second)
+ ? tr("%1 %2").arg(renderer_status_text.toUpper(),
+ ConfigurationShared::shader_backend_texts_map
+ .find(Settings::values.shader_backend.GetValue())
+ ->second)
: renderer_status_text.toUpper());
}
void GMainWindow::UpdateFilterText() {
const auto filter = Settings::values.scaling_filter.GetValue();
- const auto filter_text = Config::scaling_filter_texts_map.find(filter)->second;
+ const auto filter_text = ConfigurationShared::scaling_filter_texts_map.find(filter)->second;
filter_status_button->setText(filter == Settings::ScalingFilter::Fsr ? tr("FSR")
: filter_text.toUpper());
}
void GMainWindow::UpdateAAText() {
const auto aa_mode = Settings::values.anti_aliasing.GetValue();
- const auto aa_text = Config::anti_aliasing_texts_map.find(aa_mode)->second;
+ const auto aa_text = ConfigurationShared::anti_aliasing_texts_map.find(aa_mode)->second;
aa_status_button->setText(aa_mode == Settings::AntiAliasing::None
? QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "NO AA"))
: aa_text.toUpper());
@@ -4632,26 +4721,10 @@ void GMainWindow::ShowMouseCursor() {
}
}
-void GMainWindow::CenterMouseCursor() {
- if (emu_thread == nullptr || !Settings::values.mouse_panning) {
- mouse_center_timer.stop();
- return;
- }
- if (!this->isActiveWindow()) {
- mouse_center_timer.stop();
- return;
- }
- const int center_x = render_window->width() / 2;
- const int center_y = render_window->height() / 2;
-
- QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
-}
-
void GMainWindow::OnMouseActivity() {
if (!Settings::values.mouse_panning) {
ShowMouseCursor();
}
- mouse_center_timer.stop();
}
void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
@@ -4742,6 +4815,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
"games."));
}
+ SetFirmwareVersion();
+
if (behavior == ReinitializeKeyBehavior::Warning) {
game_list->PopulateAsync(UISettings::values.game_dirs);
}
@@ -4769,7 +4844,7 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
}
bool GMainWindow::CheckFirmwarePresence() {
- constexpr u64 MiiEditId = 0x0100000000001009ull;
+ constexpr u64 MiiEditId = static_cast<u64>(Service::AM::Applets::AppletProgramId::MiiEdit);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
@@ -4784,6 +4859,28 @@ bool GMainWindow::CheckFirmwarePresence() {
return true;
}
+void GMainWindow::SetFirmwareVersion() {
+ Service::Set::FirmwareVersionFormat firmware_data{};
+ const auto result = Service::Set::GetFirmwareVersionImpl(
+ firmware_data, *system, Service::Set::GetFirmwareVersionType::Version2);
+
+ if (result.IsError() || !CheckFirmwarePresence()) {
+ LOG_INFO(Frontend, "Installed firmware: No firmware available");
+ firmware_label->setVisible(false);
+ return;
+ }
+
+ firmware_label->setVisible(true);
+
+ const std::string display_version(firmware_data.display_version.data());
+ const std::string display_title(firmware_data.display_title.data());
+
+ LOG_INFO(Frontend, "Installed firmware: {}", display_title);
+
+ firmware_label->setText(QString::fromStdString(display_version));
+ firmware_label->setToolTip(QString::fromStdString(display_title));
+}
+
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>;
@@ -4845,7 +4942,12 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
}
bool GMainWindow::ConfirmClose() {
- if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) {
+ if (emu_thread == nullptr ||
+ UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) {
+ return true;
+ }
+ if (!system->GetExitLocked() &&
+ UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) {
return true;
}
const auto text = tr("Are you sure you want to close yuzu?");
@@ -4860,6 +4962,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
UpdateUISettings();
game_list->SaveInterfaceLayout();
+ UISettings::SaveWindowState();
hotkey_registry.SaveHotkeys();
// Unload controllers early
@@ -4922,22 +5025,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
AcceptDropEvent(event);
}
-void GMainWindow::leaveEvent(QEvent* event) {
- if (Settings::values.mouse_panning) {
- const QRect& rect = geometry();
- QPoint position = QCursor::pos();
-
- qint32 x = qBound(rect.left(), position.x(), rect.right());
- qint32 y = qBound(rect.top(), position.y(), rect.bottom());
- // Only start the timer if the mouse has left the window bound.
- // The leave event is also triggered when the window looses focus.
- if (x != position.x() || y != position.y()) {
- mouse_center_timer.start();
- }
- event->accept();
- }
-}
-
bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
@@ -4950,7 +5037,7 @@ bool GMainWindow::ConfirmChangeGame() {
}
bool GMainWindow::ConfirmForceLockedExit() {
- if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) {
+ if (emu_thread == nullptr) {
return true;
}
const auto text = tr("The currently running application has requested yuzu to not exit.\n\n"
@@ -5014,9 +5101,9 @@ static void AdjustLinkColor() {
}
void GMainWindow::UpdateUITheme() {
- const QString default_theme =
- QString::fromUtf8(UISettings::themes[static_cast<size_t>(Config::default_theme)].second);
- QString current_theme = UISettings::values.theme;
+ const QString default_theme = QString::fromUtf8(
+ UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second);
+ QString current_theme = QString::fromStdString(UISettings::values.theme);
if (current_theme.isEmpty()) {
current_theme = default_theme;
@@ -5044,7 +5131,7 @@ void GMainWindow::UpdateUITheme() {
QFile f(theme_uri);
if (!f.open(QFile::ReadOnly | QFile::Text)) {
LOG_ERROR(Frontend, "Unable to open style \"{}\", fallback to the default theme",
- UISettings::values.theme.toStdString());
+ UISettings::values.theme);
current_theme = default_theme;
}
}
@@ -5057,7 +5144,7 @@ void GMainWindow::UpdateUITheme() {
setStyleSheet(ts.readAll());
} else {
LOG_ERROR(Frontend, "Unable to set style \"{}\", stylesheet file not found",
- UISettings::values.theme.toStdString());
+ UISettings::values.theme);
qApp->setStyleSheet({});
setStyleSheet({});
}
@@ -5066,27 +5153,28 @@ void GMainWindow::UpdateUITheme() {
void GMainWindow::LoadTranslation() {
bool loaded;
- if (UISettings::values.language.isEmpty()) {
+ if (UISettings::values.language.empty()) {
// If the selected language is empty, use system locale
loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/"));
} else {
// Otherwise load from the specified file
- loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/"));
+ loaded = translator.load(QString::fromStdString(UISettings::values.language),
+ QStringLiteral(":/languages/"));
}
if (loaded) {
qApp->installTranslator(&translator);
} else {
- UISettings::values.language = QStringLiteral("en");
+ UISettings::values.language = std::string("en");
}
}
void GMainWindow::OnLanguageChanged(const QString& locale) {
- if (UISettings::values.language != QStringLiteral("en")) {
+ if (UISettings::values.language != std::string("en")) {
qApp->removeTranslator(&translator);
}
- UISettings::values.language = locale;
+ UISettings::values.language = locale.toStdString();
LoadTranslation();
ui->retranslateUi(this);
multiplayer_state->retranslateUi();
@@ -5106,13 +5194,21 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
discord_rpc->Update();
}
+#ifdef __unix__
+void GMainWindow::SetGamemodeEnabled(bool state) {
+ if (emulation_running) {
+ Common::Linux::SetGamemodeState(state);
+ }
+}
+#endif
+
void GMainWindow::changeEvent(QEvent* event) {
#ifdef __unix__
// PaletteChange event appears to only reach so far into the GUI, explicitly asking to
// UpdateUITheme is a decent work around
if (event->type() == QEvent::PaletteChange) {
const QPalette test_palette(qApp->palette());
- const QString current_theme = UISettings::values.theme;
+ const QString current_theme = QString::fromStdString(UISettings::values.theme);
// Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
static QColor last_window_color;
const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
@@ -5126,6 +5222,32 @@ void GMainWindow::changeEvent(QEvent* event) {
QWidget::changeEvent(event);
}
+void VolumeButton::wheelEvent(QWheelEvent* event) {
+
+ int num_degrees = event->angleDelta().y() / 8;
+ int num_steps = (num_degrees / 15) * scroll_multiplier;
+ // Stated in QT docs: Most mouse types work in steps of 15 degrees, in which case the delta
+ // value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees.
+
+ if (num_steps > 0) {
+ Settings::values.volume.SetValue(
+ std::min(200, Settings::values.volume.GetValue() + num_steps));
+ } else {
+ Settings::values.volume.SetValue(
+ std::max(0, Settings::values.volume.GetValue() + num_steps));
+ }
+
+ scroll_multiplier = std::min(MaxMultiplier, scroll_multiplier * 2);
+ scroll_timer.start(100); // reset the multiplier if no scroll event occurs within 100 ms
+
+ emit VolumeChanged();
+ event->accept();
+}
+
+void VolumeButton::ResetMultiplier() {
+ scroll_multiplier = 1;
+}
+
#ifdef main
#undef main
#endif
@@ -5180,29 +5302,23 @@ static void SetHighDPIAttributes() {
}
int main(int argc, char* argv[]) {
- std::unique_ptr<Config> config = std::make_unique<Config>();
+ std::unique_ptr<QtConfig> config = std::make_unique<QtConfig>();
+ UISettings::RestoreWindowState(config);
bool has_broken_vulkan = false;
bool is_child = false;
if (CheckEnvVars(&is_child)) {
return 0;
}
-#ifdef YUZU_DBGHELP
- PROCESS_INFORMATION pi;
- if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
- MiniDump::SpawnDebuggee(argv[0], pi)) {
- // Delete the config object so that it doesn't save when the program exits
- config.reset(nullptr);
- MiniDump::DebugDebuggee(pi);
- return 0;
- }
-#endif
-
if (StartupChecks(argv[0], &has_broken_vulkan,
Settings::values.perform_vulkan_check.GetValue())) {
return 0;
}
+#ifdef YUZU_CRASH_DUMPS
+ Breakpad::InstallCrashHandler();
+#endif
+
Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 270a40c5f..530e445f9 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -6,13 +6,16 @@
#include <memory>
#include <optional>
+#include <filesystem>
#include <QMainWindow>
#include <QMessageBox>
+#include <QPushButton>
#include <QTimer>
#include <QTranslator>
#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
+#include "configuration/qt_config.h"
#include "input_common/drivers/tas_input.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
@@ -24,7 +27,7 @@
#include <QtDBus/QtDBus>
#endif
-class Config;
+class QtConfig;
class ClickableLabel;
class EmuThread;
class GameList;
@@ -137,6 +140,28 @@ namespace VkDeviceInfo {
class Record;
}
+class VolumeButton : public QPushButton {
+ Q_OBJECT
+public:
+ explicit VolumeButton(QWidget* parent = nullptr) : QPushButton(parent), scroll_multiplier(1) {
+ connect(&scroll_timer, &QTimer::timeout, this, &VolumeButton::ResetMultiplier);
+ }
+
+signals:
+ void VolumeChanged();
+
+protected:
+ void wheelEvent(QWheelEvent* event) override;
+
+private slots:
+ void ResetMultiplier();
+
+private:
+ int scroll_multiplier;
+ QTimer scroll_timer;
+ constexpr static int MaxMultiplier = 8;
+};
+
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -151,10 +176,17 @@ class GMainWindow : public QMainWindow {
UI_EMU_STOPPING,
};
+ enum {
+ CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES,
+ CREATE_SHORTCUT_MSGBOX_SUCCESS,
+ CREATE_SHORTCUT_MSGBOX_ERROR,
+ CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING,
+ };
+
public:
void filterBarSetChecked(bool state);
void UpdateUITheme();
- explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan);
+ explicit GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulkan);
~GMainWindow() override;
bool DropAction(QDropEvent* event);
@@ -308,6 +340,7 @@ private:
void SetupSigInterrupts();
static void HandleSigInterrupt(int);
void OnSigInterruptNotifierActivated();
+ void SetGamemodeEnabled(bool state);
#endif
private slots:
@@ -379,6 +412,7 @@ private slots:
void OnAlbum();
void OnCabinet(Service::NFP::CabinetMode mode);
void OnMiiEdit();
+ void OnOpenControllerMenu();
void OnCaptureScreenshot();
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
void OnLanguageChanged(const QString& locale);
@@ -418,13 +452,13 @@ private:
void UpdateInputDrivers();
void HideMouseCursor();
void ShowMouseCursor();
- void CenterMouseCursor();
void OpenURL(const QUrl& url);
void LoadTranslation();
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
bool CheckDarkMode();
bool CheckSystemArchiveDecryption();
bool CheckFirmwarePresence();
+ void SetFirmwareVersion();
void ConfigureFilesystemProvider(const std::string& filepath);
/**
* Open (or not) the right confirm dialog based on current setting and game exit lock
@@ -433,11 +467,14 @@ private:
bool ConfirmShutdownGame();
QString GetTasStateDescription() const;
- bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
- const std::string& comment, const std::string& icon_path,
- const std::string& command, const std::string& arguments,
- const std::string& categories, const std::string& keywords);
-
+ bool CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title);
+ bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
+ std::filesystem::path& out_icon_path);
+ bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment,
+ const std::filesystem::path& icon_path,
+ const std::filesystem::path& command, const std::string& arguments,
+ const std::string& categories, const std::string& keywords,
+ const std::string& name);
/**
* Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
* The only difference is that it returns a boolean.
@@ -476,17 +513,18 @@ private:
QLabel* game_fps_label = nullptr;
QLabel* emu_frametime_label = nullptr;
QLabel* tas_label = nullptr;
+ QLabel* firmware_label = nullptr;
QPushButton* gpu_accuracy_button = nullptr;
QPushButton* renderer_status_button = nullptr;
QPushButton* dock_status_button = nullptr;
QPushButton* filter_status_button = nullptr;
QPushButton* aa_status_button = nullptr;
- QPushButton* volume_button = nullptr;
+ VolumeButton* volume_button = nullptr;
QWidget* volume_popup = nullptr;
QSlider* volume_slider = nullptr;
QTimer status_bar_update_timer;
- std::unique_ptr<Config> config;
+ std::unique_ptr<QtConfig> config;
// Whether emulation is currently running in yuzu.
bool emulation_running = false;
@@ -497,7 +535,6 @@ private:
bool auto_paused = false;
bool auto_muted = false;
QTimer mouse_hide_timer;
- QTimer mouse_center_timer;
QTimer update_input_timer;
QString startup_icon_theme;
@@ -554,5 +591,4 @@ protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
- void leaveEvent(QEvent* event) override;
};
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 88684ffb5..e53f9951e 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -25,7 +25,7 @@
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
- <property name="margin">
+ <property name="margin" stdset="0">
<number>0</number>
</property>
</layout>
@@ -36,7 +36,7 @@
<x>0</x>
<y>0</y>
<width>1280</width>
- <height>26</height>
+ <height>21</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
@@ -162,6 +162,7 @@
<addaction name="menu_cabinet_applet"/>
<addaction name="action_Load_Album"/>
<addaction name="action_Load_Mii_Edit"/>
+ <addaction name="action_Open_Controller_Menu"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
<addaction name="menuTAS"/>
@@ -382,9 +383,9 @@
</property>
</action>
<action name="action_Load_Album">
- <property name="text">
- <string>Open &amp;Album</string>
- </property>
+ <property name="text">
+ <string>Open &amp;Album</string>
+ </property>
</action>
<action name="action_Load_Cabinet_Nickname_Owner">
<property name="text">
@@ -407,9 +408,9 @@
</property>
</action>
<action name="action_Load_Mii_Edit">
- <property name="text">
- <string>Open &amp;Mii Editor</string>
- </property>
+ <property name="text">
+ <string>Open &amp;Mii Editor</string>
+ </property>
</action>
<action name="action_Configure_Tas">
<property name="text">
@@ -454,6 +455,11 @@
<string>R&amp;ecord</string>
</property>
</action>
+ <action name="action_Open_Controller_Menu">
+ <property name="text">
+ <string>Open &amp;Controller Menu</string>
+ </property>
+ </action>
</widget>
<resources>
<include location="yuzu.qrc"/>
diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp
deleted file mode 100644
index a34dc6a9c..000000000
--- a/src/yuzu/mini_dump.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <cstdio>
-#include <cstring>
-#include <ctime>
-#include <filesystem>
-#include <fmt/format.h>
-#include <windows.h>
-#include "yuzu/mini_dump.h"
-#include "yuzu/startup_checks.h"
-
-// dbghelp.h must be included after windows.h
-#include <dbghelp.h>
-
-namespace MiniDump {
-
-void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
- EXCEPTION_POINTERS* pep) {
- char file_name[255];
- const std::time_t the_time = std::time(nullptr);
- std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
-
- // Open the file
- HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
- CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
-
- if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
- fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
- return;
- }
-
- // Create the minidump
- const MINIDUMP_TYPE dump_type = MiniDumpNormal;
-
- const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
- dump_type, (pep != 0) ? info : 0, 0, 0);
-
- if (write_dump_status) {
- fmt::print(stderr, "MiniDump created: {}", file_name);
- } else {
- fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
- }
-
- // Close the file
- CloseHandle(file_handle);
-}
-
-void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
- EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
-
- HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
- if (thread_handle == nullptr) {
- fmt::print(stderr, "OpenThread failed ({})", GetLastError());
- return;
- }
-
- // Get child process context
- CONTEXT context = {};
- context.ContextFlags = CONTEXT_ALL;
- if (!GetThreadContext(thread_handle, &context)) {
- fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
- return;
- }
-
- // Create exception pointers for minidump
- EXCEPTION_POINTERS ep;
- ep.ExceptionRecord = &record;
- ep.ContextRecord = &context;
-
- MINIDUMP_EXCEPTION_INFORMATION info;
- info.ThreadId = deb_ev.dwThreadId;
- info.ExceptionPointers = &ep;
- info.ClientPointers = false;
-
- CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
-
- if (CloseHandle(thread_handle) == 0) {
- fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
- }
-}
-
-bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
- std::memset(&pi, 0, sizeof(pi));
-
- // Don't debug if we are already being debugged
- if (IsDebuggerPresent()) {
- return false;
- }
-
- if (!SpawnChild(arg0, &pi, 0)) {
- fmt::print(stderr, "warning: continuing without crash dumps");
- return false;
- }
-
- const bool can_debug = DebugActiveProcess(pi.dwProcessId);
- if (!can_debug) {
- fmt::print(stderr,
- "warning: DebugActiveProcess failed ({}), continuing without crash dumps",
- GetLastError());
- return false;
- }
-
- return true;
-}
-
-static const char* ExceptionName(DWORD exception) {
- switch (exception) {
- case EXCEPTION_ACCESS_VIOLATION:
- return "EXCEPTION_ACCESS_VIOLATION";
- case EXCEPTION_DATATYPE_MISALIGNMENT:
- return "EXCEPTION_DATATYPE_MISALIGNMENT";
- case EXCEPTION_BREAKPOINT:
- return "EXCEPTION_BREAKPOINT";
- case EXCEPTION_SINGLE_STEP:
- return "EXCEPTION_SINGLE_STEP";
- case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
- return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
- case EXCEPTION_FLT_DENORMAL_OPERAND:
- return "EXCEPTION_FLT_DENORMAL_OPERAND";
- case EXCEPTION_FLT_DIVIDE_BY_ZERO:
- return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
- case EXCEPTION_FLT_INEXACT_RESULT:
- return "EXCEPTION_FLT_INEXACT_RESULT";
- case EXCEPTION_FLT_INVALID_OPERATION:
- return "EXCEPTION_FLT_INVALID_OPERATION";
- case EXCEPTION_FLT_OVERFLOW:
- return "EXCEPTION_FLT_OVERFLOW";
- case EXCEPTION_FLT_STACK_CHECK:
- return "EXCEPTION_FLT_STACK_CHECK";
- case EXCEPTION_FLT_UNDERFLOW:
- return "EXCEPTION_FLT_UNDERFLOW";
- case EXCEPTION_INT_DIVIDE_BY_ZERO:
- return "EXCEPTION_INT_DIVIDE_BY_ZERO";
- case EXCEPTION_INT_OVERFLOW:
- return "EXCEPTION_INT_OVERFLOW";
- case EXCEPTION_PRIV_INSTRUCTION:
- return "EXCEPTION_PRIV_INSTRUCTION";
- case EXCEPTION_IN_PAGE_ERROR:
- return "EXCEPTION_IN_PAGE_ERROR";
- case EXCEPTION_ILLEGAL_INSTRUCTION:
- return "EXCEPTION_ILLEGAL_INSTRUCTION";
- case EXCEPTION_NONCONTINUABLE_EXCEPTION:
- return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
- case EXCEPTION_STACK_OVERFLOW:
- return "EXCEPTION_STACK_OVERFLOW";
- case EXCEPTION_INVALID_DISPOSITION:
- return "EXCEPTION_INVALID_DISPOSITION";
- case EXCEPTION_GUARD_PAGE:
- return "EXCEPTION_GUARD_PAGE";
- case EXCEPTION_INVALID_HANDLE:
- return "EXCEPTION_INVALID_HANDLE";
- default:
- return "unknown exception type";
- }
-}
-
-void DebugDebuggee(PROCESS_INFORMATION& pi) {
- DEBUG_EVENT deb_ev = {};
-
- while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
- const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
- if (!wait_success) {
- fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
- return;
- }
-
- switch (deb_ev.dwDebugEventCode) {
- case OUTPUT_DEBUG_STRING_EVENT:
- case CREATE_PROCESS_DEBUG_EVENT:
- case CREATE_THREAD_DEBUG_EVENT:
- case EXIT_PROCESS_DEBUG_EVENT:
- case EXIT_THREAD_DEBUG_EVENT:
- case LOAD_DLL_DEBUG_EVENT:
- case RIP_EVENT:
- case UNLOAD_DLL_DEBUG_EVENT:
- // Continue on all other debug events
- ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
- break;
- case EXCEPTION_DEBUG_EVENT:
- EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
-
- // We want to generate a crash dump if we are seeing the same exception again.
- if (!deb_ev.u.Exception.dwFirstChance) {
- fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
- record.ExceptionCode, ExceptionName(record.ExceptionCode));
- DumpFromDebugEvent(deb_ev, pi);
- }
-
- // Continue without handling the exception.
- // Lets the debuggee use its own exception handler.
- // - If one does not exist, we will see the exception once more where we make a minidump
- // for. Then when it reaches here again, yuzu will probably crash.
- // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
- // infinite loop of exceptions.
- ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
- break;
- }
- }
-}
-
-} // namespace MiniDump
diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h
deleted file mode 100644
index d6b6cca84..000000000
--- a/src/yuzu/mini_dump.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <windows.h>
-
-#include <dbghelp.h>
-
-namespace MiniDump {
-
-void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
- EXCEPTION_POINTERS* pep);
-
-void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi);
-bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi);
-void DebugDebuggee(PROCESS_INFORMATION& pi);
-
-} // namespace MiniDump
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 1c833767b..7bb7e95af 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <QSettings>
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
#include "yuzu/uisettings.h"
#ifndef CANNOT_EXPLICITLY_INSTANTIATE
@@ -15,6 +18,8 @@ template class Setting<unsigned long long>;
} // namespace Settings
#endif
+namespace FS = Common::FS;
+
namespace UISettings {
const Themes themes{{
@@ -28,10 +33,8 @@ const Themes themes{{
bool IsDarkTheme() {
const auto& theme = UISettings::values.theme;
- return theme == QStringLiteral("qdarkstyle") ||
- theme == QStringLiteral("qdarkstyle_midnight_blue") ||
- theme == QStringLiteral("colorful_dark") ||
- theme == QStringLiteral("colorful_midnight_blue");
+ return theme == std::string("qdarkstyle") || theme == std::string("qdarkstyle_midnight_blue") ||
+ theme == std::string("colorful_dark") || theme == std::string("colorful_midnight_blue");
}
Values values = {};
@@ -52,4 +55,58 @@ u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) {
return height * 16 / 9;
}
+void SaveWindowState() {
+ const auto window_state_config_loc =
+ FS::PathToUTF8String(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "window_state.ini");
+
+ void(FS::CreateParentDir(window_state_config_loc));
+ QSettings config(QString::fromStdString(window_state_config_loc), QSettings::IniFormat);
+
+ config.setValue(QStringLiteral("geometry"), values.geometry);
+ config.setValue(QStringLiteral("state"), values.state);
+ config.setValue(QStringLiteral("geometryRenderWindow"), values.renderwindow_geometry);
+ config.setValue(QStringLiteral("gameListHeaderState"), values.gamelist_header_state);
+ config.setValue(QStringLiteral("microProfileDialogGeometry"), values.microprofile_geometry);
+
+ config.sync();
+}
+
+void RestoreWindowState(std::unique_ptr<QtConfig>& qtConfig) {
+ const auto window_state_config_loc =
+ FS::PathToUTF8String(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "window_state.ini");
+
+ // Migrate window state from old location
+ if (!FS::Exists(window_state_config_loc) && qtConfig->Exists("UI", "UILayout\\geometry")) {
+ const auto config_loc =
+ FS::PathToUTF8String(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "qt-config.ini");
+ QSettings config(QString::fromStdString(config_loc), QSettings::IniFormat);
+
+ config.beginGroup(QStringLiteral("UI"));
+ config.beginGroup(QStringLiteral("UILayout"));
+ values.geometry = config.value(QStringLiteral("geometry")).toByteArray();
+ values.state = config.value(QStringLiteral("state")).toByteArray();
+ values.renderwindow_geometry =
+ config.value(QStringLiteral("geometryRenderWindow")).toByteArray();
+ values.gamelist_header_state =
+ config.value(QStringLiteral("gameListHeaderState")).toByteArray();
+ values.microprofile_geometry =
+ config.value(QStringLiteral("microProfileDialogGeometry")).toByteArray();
+ config.endGroup();
+ config.endGroup();
+ return;
+ }
+
+ void(FS::CreateParentDir(window_state_config_loc));
+ const QSettings config(QString::fromStdString(window_state_config_loc), QSettings::IniFormat);
+
+ values.geometry = config.value(QStringLiteral("geometry")).toByteArray();
+ values.state = config.value(QStringLiteral("state")).toByteArray();
+ values.renderwindow_geometry =
+ config.value(QStringLiteral("geometryRenderWindow")).toByteArray();
+ values.gamelist_header_state =
+ config.value(QStringLiteral("gameListHeaderState")).toByteArray();
+ values.microprofile_geometry =
+ config.value(QStringLiteral("microProfileDialogGeometry")).toByteArray();
+}
+
} // namespace UISettings
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index b62ff620c..549a39e1b 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -14,6 +14,7 @@
#include "common/common_types.h"
#include "common/settings.h"
#include "common/settings_enums.h"
+#include "configuration/qt_config.h"
using Settings::Category;
using Settings::ConfirmStop;
@@ -37,15 +38,15 @@ namespace UISettings {
bool IsDarkTheme();
struct ContextualShortcut {
- QString keyseq;
- QString controller_keyseq;
+ std::string keyseq;
+ std::string controller_keyseq;
int context;
bool repeat;
};
struct Shortcut {
- QString name;
- QString group;
+ std::string name;
+ std::string group;
ContextualShortcut shortcut;
};
@@ -58,11 +59,19 @@ enum class Theme {
MidnightBlueColorful,
};
+static constexpr Theme default_theme{
+#ifdef _WIN32
+ Theme::DarkColorful
+#else
+ Theme::DefaultColorful
+#endif
+};
+
using Themes = std::array<std::pair<const char*, const char*>, 6>;
extern const Themes themes;
struct GameDir {
- QString path;
+ std::string path;
bool deep_scan = false;
bool expanded = false;
bool operator==(const GameDir& rhs) const {
@@ -93,10 +102,6 @@ struct Values {
Setting<bool> show_filter_bar{linkage, true, "showFilterBar", Category::Ui};
Setting<bool> show_status_bar{linkage, true, "showStatusBar", Category::Ui};
- Setting<bool> confirm_before_closing{
- linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
- true, true};
-
SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
ConfirmStop::Ask_Always,
"confirmStop",
@@ -113,9 +118,13 @@ struct Values {
Settings::Specialization::Default,
true,
true};
- Setting<bool> mute_when_in_background{
- linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
- true, true};
+ Setting<bool> mute_when_in_background{linkage,
+ false,
+ "muteWhenInBackground",
+ Category::UiAudio,
+ Settings::Specialization::Default,
+ true,
+ true};
Setting<bool> hide_mouse{
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
true, true};
@@ -144,15 +153,15 @@ struct Values {
Category::Screenshots};
Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots};
- QString roms_path;
- QString symbols_path;
- QString game_dir_deprecated;
+ std::string roms_path;
+ std::string symbols_path;
+ std::string game_dir_deprecated;
bool game_dir_deprecated_deepscan;
- QVector<UISettings::GameDir> game_dirs;
+ QVector<GameDir> game_dirs;
QStringList recent_files;
- QString language;
+ std::string language;
- QString theme;
+ std::string theme;
// Shortcut name <Shortcut, context>
std::vector<Shortcut> shortcuts;
@@ -206,6 +215,54 @@ extern Values values;
u32 CalculateWidth(u32 height, Settings::AspectRatio ratio);
+void SaveWindowState();
+void RestoreWindowState(std::unique_ptr<QtConfig>& qtConfig);
+
+// This shouldn't have anything except static initializers (no functions). So
+// QKeySequence(...).toString() is NOT ALLOWED HERE.
+// This must be in alphabetical order according to action name as it must have the same order as
+// UISetting::values.shortcuts, which is alphabetically ordered.
+// clang-format off
+const std::array<Shortcut, 23> default_hotkeys{{
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+P"), std::string("Screenshot"), Qt::WidgetWithChildrenShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F8"), std::string("Home+L"), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F10"), std::string("Home+X"), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F9"), std::string("Home+R"), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F4"), std::string("Home+Plus"), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Esc"), std::string(""), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+Q"), std::string("Home+Minus"), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F11"), std::string("Home+B"), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+O"), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F2"), std::string("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F6"), std::string("R+Plus+Minus"), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F5"), std::string("L+Plus+Minus"), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F6"), std::string(""), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F5"), std::string(""), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F"), std::string(""), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+U"), std::string("Home+Y"), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F9"), std::string(""), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string(""), std::string(""), Qt::ApplicationShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+S"), std::string(""), Qt::WindowShortcut, false}},
+}};
+// clang-format on
+
} // namespace UISettings
Q_DECLARE_METATYPE(UISettings::GameDir*);
+
+// These metatype declarations cannot be in common/settings.h because core is devoid of QT
+Q_DECLARE_METATYPE(Settings::CpuAccuracy);
+Q_DECLARE_METATYPE(Settings::GpuAccuracy);
+Q_DECLARE_METATYPE(Settings::FullscreenMode);
+Q_DECLARE_METATYPE(Settings::NvdecEmulation);
+Q_DECLARE_METATYPE(Settings::ResolutionSetup);
+Q_DECLARE_METATYPE(Settings::ScalingFilter);
+Q_DECLARE_METATYPE(Settings::AntiAliasing);
+Q_DECLARE_METATYPE(Settings::RendererBackend);
+Q_DECLARE_METATYPE(Settings::ShaderBackend);
+Q_DECLARE_METATYPE(Settings::AstcRecompression);
+Q_DECLARE_METATYPE(Settings::AstcDecodeMode);
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index f2854c8ec..e22cf84bf 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -4,7 +4,10 @@
#include <array>
#include <cmath>
#include <QPainter>
+
+#include "common/logging/log.h"
#include "yuzu/util/util.h"
+
#ifdef _WIN32
#include <windows.h>
#include "common/fs/file.h"
@@ -42,7 +45,7 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
return circle_pixmap;
}
-bool SaveIconToFile(const std::string_view path, const QImage& image) {
+bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
#if defined(WIN32)
#pragma pack(push, 2)
struct IconDir {
@@ -73,7 +76,7 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
.id_count = static_cast<WORD>(scale_sizes.size()),
};
- Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
+ Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile);
if (!icon_file.IsOpen()) {
return false;
@@ -135,6 +138,14 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
icon_file.Close();
return true;
+#elif defined(__linux__) || defined(__FreeBSD__)
+ // Convert and write the icon as a PNG
+ if (!image.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());
+ }
+ return true;
#else
return false;
#endif
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 09c14ce3f..4094cf6c2 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -3,6 +3,7 @@
#pragma once
+#include <filesystem>
#include <QFont>
#include <QString>
@@ -25,4 +26,4 @@
* @param image The image to save
* @return bool If the operation succeeded
*/
-[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);
+[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
diff --git a/src/yuzu/vk_device_info.cpp b/src/yuzu/vk_device_info.cpp
index 92f10d315..ab0d39c25 100644
--- a/src/yuzu/vk_device_info.cpp
+++ b/src/yuzu/vk_device_info.cpp
@@ -31,6 +31,7 @@ void PopulateRecords(std::vector<Record>& records, QWindow* window) try {
// Create a test window with a Vulkan surface type for checking present modes.
QWindow test_window(window);
test_window.setSurfaceType(QWindow::VulkanSurface);
+ test_window.create();
auto wsi = QtCommon::GetWindowSystemInfo(&test_window);
vk::InstanceDispatch dld;
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 46eddf423..fbeba8813 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -13,9 +13,6 @@ function(create_resource file output filename)
endfunction()
add_executable(yuzu-cmd
- config.cpp
- config.h
- default_ini.h
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
emu_window/emu_window_sdl2_gl.cpp
@@ -25,14 +22,16 @@ add_executable(yuzu-cmd
emu_window/emu_window_sdl2_vk.cpp
emu_window/emu_window_sdl2_vk.h
precompiled_headers.h
+ sdl_config.cpp
+ sdl_config.h
yuzu.cpp
yuzu.rc
)
create_target_directory_groups(yuzu-cmd)
-target_link_libraries(yuzu-cmd PRIVATE common core input_common)
-target_link_libraries(yuzu-cmd PRIVATE inih::INIReader glad)
+target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common)
+target_link_libraries(yuzu-cmd PRIVATE glad)
if (MSVC)
target_link_libraries(yuzu-cmd PRIVATE getopt)
endif()
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
deleted file mode 100644
index 0d25ff400..000000000
--- a/src/yuzu_cmd/config.cpp
+++ /dev/null
@@ -1,279 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <memory>
-#include <optional>
-#include <sstream>
-#include <INIReader.h>
-#include <SDL.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 "core/hle/service/acc/profile_manager.h"
-#include "input_common/main.h"
-#include "yuzu_cmd/config.h"
-#include "yuzu_cmd/default_ini.h"
-
-namespace FS = Common::FS;
-
-const std::filesystem::path default_config_path =
- FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini";
-
-Config::Config(std::optional<std::filesystem::path> config_path)
- : sdl2_config_loc{config_path.value_or(default_config_path)},
- sdl2_config{std::make_unique<INIReader>(FS::PathToUTF8String(sdl2_config_loc))} {
- Reload();
-}
-
-Config::~Config() = default;
-
-bool Config::LoadINI(const std::string& default_contents, bool retry) {
- const auto config_loc_str = FS::PathToUTF8String(sdl2_config_loc);
- if (sdl2_config->ParseError() < 0) {
- if (retry) {
- LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
- config_loc_str);
-
- void(FS::CreateParentDir(sdl2_config_loc));
- void(FS::WriteStringToFile(sdl2_config_loc, FS::FileType::TextFile, default_contents));
-
- sdl2_config = std::make_unique<INIReader>(config_loc_str);
-
- return LoadINI(default_contents, false);
- }
- LOG_ERROR(Config, "Failed.");
- return false;
- }
- LOG_INFO(Config, "Successfully loaded {}", config_loc_str);
- return true;
-}
-
-static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = {
- SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
- SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W,
- SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
-};
-
-static const std::array<int, Settings::NativeMotion::NumMotions> default_motions = {
- SDL_SCANCODE_7,
- SDL_SCANCODE_8,
-};
-
-static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{
- {
- SDL_SCANCODE_UP,
- SDL_SCANCODE_DOWN,
- SDL_SCANCODE_LEFT,
- SDL_SCANCODE_RIGHT,
- SDL_SCANCODE_D,
- },
- {
- SDL_SCANCODE_I,
- SDL_SCANCODE_K,
- SDL_SCANCODE_J,
- SDL_SCANCODE_L,
- SDL_SCANCODE_D,
- },
-}};
-
-template <>
-void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
- std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault());
- if (setting_value.empty()) {
- setting_value = setting.GetDefault();
- }
- setting = std::move(setting_value);
-}
-
-template <>
-void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
- setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
-}
-
-template <typename Type, bool ranged>
-void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
- setting = static_cast<Type>(sdl2_config->GetInteger(group, setting.GetLabel(),
- static_cast<long>(setting.GetDefault())));
-}
-
-void Config::ReadCategory(Settings::Category category) {
- for (const auto setting : Settings::values.linkage.by_category[category]) {
- const char* category_name = [&]() {
- if (category == Settings::Category::Controls) {
- // For compatibility with older configs
- return "ControlsGeneral";
- } else {
- return Settings::TranslateCategory(category);
- }
- }();
- std::string setting_value =
- sdl2_config->Get(category_name, setting->GetLabel(), setting->DefaultToString());
- setting->LoadString(setting_value);
- }
-}
-
-void Config::ReadValues() {
- // Controls
- ReadCategory(Settings::Category::Controls);
-
- for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
- auto& player = Settings::values.players.GetValue()[p];
-
- const auto group = fmt::format("ControlsP{}", p);
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- player.buttons[i] =
- sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param);
- if (player.buttons[i].empty()) {
- player.buttons[i] = default_param;
- }
- }
-
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_analogs[i][4], 0.5f);
- player.analogs[i] =
- sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
- if (player.analogs[i].empty()) {
- player.analogs[i] = default_param;
- }
- }
-
- for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
- const std::string default_param =
- InputCommon::GenerateKeyboardParam(default_motions[i]);
- auto& player_motions = player.motions[i];
-
- player_motions =
- sdl2_config->Get(group, Settings::NativeMotion::mapping[i], default_param);
- if (player_motions.empty()) {
- player_motions = default_param;
- }
- }
-
- player.connected = sdl2_config->GetBoolean(group, "connected", false);
- }
-
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- Settings::values.debug_pad_buttons[i] = sdl2_config->Get(
- "ControlsGeneral", std::string("debug_pad_") + Settings::NativeButton::mapping[i],
- default_param);
- if (Settings::values.debug_pad_buttons[i].empty())
- Settings::values.debug_pad_buttons[i] = default_param;
- }
-
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_analogs[i][4], 0.5f);
- Settings::values.debug_pad_analogs[i] = sdl2_config->Get(
- "ControlsGeneral", std::string("debug_pad_") + Settings::NativeAnalog::mapping[i],
- default_param);
- if (Settings::values.debug_pad_analogs[i].empty())
- Settings::values.debug_pad_analogs[i] = default_param;
- }
-
- Settings::values.touchscreen.enabled =
- sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
- Settings::values.touchscreen.rotation_angle =
- sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0);
- Settings::values.touchscreen.diameter_x =
- sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
- Settings::values.touchscreen.diameter_y =
- sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
-
- int num_touch_from_button_maps =
- sdl2_config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
- if (num_touch_from_button_maps > 0) {
- for (int i = 0; i < num_touch_from_button_maps; ++i) {
- Settings::TouchFromButtonMap map;
- map.name = sdl2_config->Get("ControlsGeneral",
- std::string("touch_from_button_maps_") + std::to_string(i) +
- std::string("_name"),
- "default");
- const int num_touch_maps = sdl2_config->GetInteger(
- "ControlsGeneral",
- std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"),
- 0);
- map.buttons.reserve(num_touch_maps);
-
- for (int j = 0; j < num_touch_maps; ++j) {
- std::string touch_mapping =
- sdl2_config->Get("ControlsGeneral",
- std::string("touch_from_button_maps_") + std::to_string(i) +
- std::string("_bind_") + std::to_string(j),
- "");
- map.buttons.emplace_back(std::move(touch_mapping));
- }
-
- Settings::values.touch_from_button_maps.emplace_back(std::move(map));
- }
- } else {
- Settings::values.touch_from_button_maps.emplace_back(
- Settings::TouchFromButtonMap{"default", {}});
- num_touch_from_button_maps = 1;
- }
- Settings::values.touch_from_button_map_index = std::clamp(
- Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
-
- ReadCategory(Settings::Category::Audio);
- ReadCategory(Settings::Category::Core);
- ReadCategory(Settings::Category::Cpu);
- ReadCategory(Settings::Category::CpuDebug);
- ReadCategory(Settings::Category::CpuUnsafe);
- ReadCategory(Settings::Category::Renderer);
- ReadCategory(Settings::Category::RendererAdvanced);
- ReadCategory(Settings::Category::RendererDebug);
- ReadCategory(Settings::Category::System);
- ReadCategory(Settings::Category::SystemAudio);
- ReadCategory(Settings::Category::DataStorage);
- ReadCategory(Settings::Category::Debugging);
- ReadCategory(Settings::Category::DebuggingGraphics);
- ReadCategory(Settings::Category::Miscellaneous);
- ReadCategory(Settings::Category::Network);
- ReadCategory(Settings::Category::WebService);
-
- // Data Storage
- FS::SetYuzuPath(FS::YuzuPath::NANDDir,
- sdl2_config->Get("Data Storage", "nand_directory",
- FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
- FS::SetYuzuPath(FS::YuzuPath::SDMCDir,
- sdl2_config->Get("Data Storage", "sdmc_directory",
- FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
- FS::SetYuzuPath(FS::YuzuPath::LoadDir,
- sdl2_config->Get("Data Storage", "load_directory",
- FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
- FS::SetYuzuPath(FS::YuzuPath::DumpDir,
- sdl2_config->Get("Data Storage", "dump_directory",
- FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
-
- // Debugging
- Settings::values.record_frame_times =
- sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
-
- const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
- std::stringstream ss(title_list);
- std::string line;
- while (std::getline(ss, line, '|')) {
- const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
- const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
-
- std::stringstream inner_ss(disabled_list);
- std::string inner_line;
- std::vector<std::string> out;
- while (std::getline(inner_ss, inner_line, '|')) {
- out.push_back(inner_line);
- }
-
- Settings::values.disabled_addons.insert_or_assign(title_id, out);
- }
-}
-
-void Config::Reload() {
- LoadINI(DefaultINI::sdl2_config_file);
- ReadValues();
-}
diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h
deleted file mode 100644
index 512591a39..000000000
--- a/src/yuzu_cmd/config.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <filesystem>
-#include <memory>
-#include <optional>
-#include <string>
-
-#include "common/settings.h"
-
-class INIReader;
-
-class Config {
- std::filesystem::path sdl2_config_loc;
- std::unique_ptr<INIReader> sdl2_config;
-
- bool LoadINI(const std::string& default_contents = "", bool retry = true);
- void ReadValues();
-
-public:
- explicit Config(std::optional<std::filesystem::path> config_path);
- ~Config();
-
- void Reload();
-
-private:
- /**
- * Applies a value read from the sdl2_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 ReadCategory(Settings::Category category);
-};
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
deleted file mode 100644
index 119e22183..000000000
--- a/src/yuzu_cmd/default_ini.h
+++ /dev/null
@@ -1,553 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-namespace DefaultINI {
-
-const char* sdl2_config_file =
- R"(
-[ControlsP0]
-# The input devices and parameters for each Switch native input
-# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ...
-# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
-# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
-
-# Indicates if this player should be connected at boot
-# 0 (default): Disabled, 1: Enabled
-connected=
-
-# for button input, the following devices are available:
-# - "keyboard" (default) for keyboard input. Required parameters:
-# - "code": the code of the key to bind
-# - "sdl" for joystick input using SDL. Required parameters:
-# - "guid": SDL identification GUID of the joystick
-# - "port": the index of the joystick to bind
-# - "button"(optional): the index of the button to bind
-# - "hat"(optional): the index of the hat to bind as direction buttons
-# - "axis"(optional): the index of the axis to bind
-# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
-# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
-# triggered if the axis value crosses
-# - "direction"(only used for axis): "+" means the button is triggered when the axis value
-# is greater than the threshold; "-" means the button is triggered when the axis value
-# is smaller than the threshold
-button_a=
-button_b=
-button_x=
-button_y=
-button_lstick=
-button_rstick=
-button_l=
-button_r=
-button_zl=
-button_zr=
-button_plus=
-button_minus=
-button_dleft=
-button_dup=
-button_dright=
-button_ddown=
-button_lstick_left=
-button_lstick_up=
-button_lstick_right=
-button_lstick_down=
-button_sl=
-button_sr=
-button_home=
-button_screenshot=
-
-# for analog input, the following devices are available:
-# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
-# - "up", "down", "left", "right": sub-devices for each direction.
-# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
-# - "modifier": sub-devices as a modifier.
-# - "modifier_scale": a float number representing the applied modifier scale to the analog input.
-# Must be in range of 0.0-1.0. Defaults to 0.5
-# - "sdl" for joystick input using SDL. Required parameters:
-# - "guid": SDL identification GUID of the joystick
-# - "port": the index of the joystick to bind
-# - "axis_x": the index of the axis to bind as x-axis (default to 0)
-# - "axis_y": the index of the axis to bind as y-axis (default to 1)
-lstick=
-rstick=
-
-# for motion input, the following devices are available:
-# - "keyboard" (default) for emulating random motion input from buttons. Required parameters:
-# - "code": the code of the key to bind
-# - "sdl" for motion input using SDL. Required parameters:
-# - "guid": SDL identification GUID of the joystick
-# - "port": the index of the joystick to bind
-# - "motion": the index of the motion sensor to bind
-# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters:
-# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001"
-# - "port": the port of the cemu hook server
-# - "pad": the index of the joystick
-# - "motion": the index of the motion sensor of the joystick to bind
-motionleft=
-motionright=
-
-[ControlsGeneral]
-# To use the debug_pad, prepend `debug_pad_` before each button setting above.
-# i.e. debug_pad_button_a=
-
-# Enable debug pad inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-debug_pad_enabled =
-
-# Enable sdl raw input. Allows to configure up to 8 xinput controllers.
-# 0 (default): Disabled, 1: Enabled
-enable_raw_input =
-
-# Enable yuzu joycon driver instead of SDL drive.
-# 0: Disabled, 1 (default): Enabled
-enable_joycon_driver =
-
-# Emulates an analog input from buttons. Allowing to dial any angle.
-# 0 (default): Disabled, 1: Enabled
-emulate_analog_keyboard =
-
-# Whether to enable or disable vibration
-# 0: Disabled, 1 (default): Enabled
-vibration_enabled=
-
-# Whether to enable or disable accurate vibrations
-# 0 (default): Disabled, 1: Enabled
-enable_accurate_vibrations=
-
-# Enables controller motion inputs
-# 0: Disabled, 1 (default): Enabled
-motion_enabled =
-
-# Defines the udp device's touch screen coordinate system for cemuhookudp devices
-# - "min_x", "min_y", "max_x", "max_y"
-touch_device=
-
-# for mapping buttons to touch inputs.
-#touch_from_button_map=1
-#touch_from_button_maps_0_name=default
-#touch_from_button_maps_0_count=2
-#touch_from_button_maps_0_bind_0=foo
-#touch_from_button_maps_0_bind_1=bar
-# etc.
-
-# List of Cemuhook UDP servers, delimited by ','.
-# Default: 127.0.0.1:26760
-# Example: 127.0.0.1:26760,123.4.5.67:26761
-udp_input_servers =
-
-# Enable controlling an axis via a mouse input.
-# 0 (default): Off, 1: On
-mouse_panning =
-
-# Set mouse panning horizontal sensitivity.
-# Default: 50.0
-mouse_panning_x_sensitivity =
-
-# Set mouse panning vertical sensitivity.
-# Default: 50.0
-mouse_panning_y_sensitivity =
-
-# Set mouse panning deadzone horizontal counterweight.
-# Default: 0.0
-mouse_panning_deadzone_x_counterweight =
-
-# Set mouse panning deadzone vertical counterweight.
-# Default: 0.0
-mouse_panning_deadzone_y_counterweight =
-
-# Set mouse panning stick decay strength.
-# Default: 22.0
-mouse_panning_decay_strength =
-
-# Set mouse panning stick minimum decay.
-# Default: 5.0
-mouse_panning_minimum_decay =
-
-# Emulate an analog control stick from keyboard inputs.
-# 0 (default): Disabled, 1: Enabled
-emulate_analog_keyboard =
-
-# Enable mouse inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-mouse_enabled =
-
-# Enable keyboard inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-keyboard_enabled =
-
-)"
- R"(
-[Core]
-# Whether to use multi-core for CPU emulation
-# 0: Disabled, 1 (default): Enabled
-use_multi_core =
-
-# Enable unsafe extended guest system memory layout (8GB DRAM)
-# 0 (default): Disabled, 1: Enabled
-use_unsafe_extended_memory_layout =
-
-[Cpu]
-# Adjusts various optimizations.
-# Auto-select mode enables choice unsafe optimizations.
-# Accurate enables only safe optimizations.
-# Unsafe allows any unsafe optimizations.
-# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations
-cpu_accuracy =
-
-# Allow disabling safe optimizations.
-# 0 (default): Disabled, 1: Enabled
-cpu_debug_mode =
-
-# Enable inline page tables optimization (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_page_tables =
-
-# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_block_linking =
-
-# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_return_stack_buffer =
-
-# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fast_dispatcher =
-
-# Enable context elimination CPU Optimization (reduce host memory use for guest context)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_context_elimination =
-
-# Enable constant propagation CPU optimization (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_const_prop =
-
-# Enable miscellaneous CPU optimizations (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_misc_ir =
-
-# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_reduce_misalign_checks =
-
-# Enable Host MMU Emulation (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fastmem =
-
-# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fastmem_exclusives =
-
-# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_recompile_exclusives =
-
-# Enable optimization to ignore invalid memory accesses (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_ignore_memory_aborts =
-
-# Enable unfuse FMA (improve performance on CPUs without FMA)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_unfuse_fma =
-
-# Enable faster FRSQRTE and FRECPE
-# Only enabled if cpu_accuracy is set to Unsafe.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_reduce_fp_error =
-
-# Enable faster ASIMD instructions (32 bits only)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_ignore_standard_fpcr =
-
-# Enable inaccurate NaN handling
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_inaccurate_nan =
-
-# Disable address space checks (64 bits only)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_fastmem_check =
-
-# Enable faster exclusive instructions
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_ignore_global_monitor =
-
-)"
- R"(
-[Renderer]
-# Which backend API to use.
-# 0: OpenGL, 1 (default): Vulkan
-backend =
-
-# Whether to enable asynchronous presentation (Vulkan only)
-# 0 (default): Off, 1: On
-async_presentation =
-
-# Enable graphics API debugging mode.
-# 0 (default): Disabled, 1: Enabled
-debug =
-
-# Enable shader feedback.
-# 0 (default): Disabled, 1: Enabled
-renderer_shader_feedback =
-
-# Enable Nsight Aftermath crash dumps
-# 0 (default): Disabled, 1: Enabled
-nsight_aftermath =
-
-# Disable shader loop safety checks, executing the shader without loop logic changes
-# 0 (default): Disabled, 1: Enabled
-disable_shader_loop_safety_checks =
-
-# Which Vulkan physical device to use (defaults to 0)
-vulkan_device =
-
-# 0: 0.5x (360p/540p) [EXPERIMENTAL]
-# 1: 0.75x (540p/810p) [EXPERIMENTAL]
-# 2 (default): 1x (720p/1080p)
-# 3: 1.5x (1080p/1620p) [EXPERIMENTAL]
-# 4: 2x (1440p/2160p)
-# 5: 3x (2160p/3240p)
-# 6: 4x (2880p/4320p)
-# 7: 5x (3600p/5400p)
-# 8: 6x (4320p/6480p)
-# 9: 7x (5040p/7560p)
-# 10: 8x (5760/8640p)
-resolution_setup =
-
-# Pixel filter to use when up- or down-sampling rendered frames.
-# 0: Nearest Neighbor
-# 1 (default): Bilinear
-# 2: Bicubic
-# 3: Gaussian
-# 4: ScaleForce
-# 5: AMD FidelityFX™️ Super Resolution
-scaling_filter =
-
-# Anti-Aliasing (AA)
-# 0 (default): None, 1: FXAA, 2: SMAA
-anti_aliasing =
-
-# Whether to use fullscreen or borderless window mode
-# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen
-fullscreen_mode =
-
-# Aspect ratio
-# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window
-aspect_ratio =
-
-# Anisotropic filtering
-# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
-max_anisotropy =
-
-# Whether to enable VSync or not.
-# OpenGL: Values other than 0 enable VSync
-# Vulkan: FIFO is selected if the requested mode is not supported by the driver.
-# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
-# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
-# Mailbox can have lower latency than FIFO and does not tear but may drop frames.
-# Immediate (no synchronization) just presents whatever is available and can exhibit tearing.
-# 0: Immediate (Off), 1: Mailbox, 2 (Default): FIFO (On), 3: FIFO Relaxed
-use_vsync =
-
-# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is
-# not available and GLASM is selected, GLSL will be used.
-# 0: GLSL, 1 (default): GLASM, 2: SPIR-V
-shader_backend =
-
-# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
-# 0: Off, 1 (default): On
-use_reactive_flushing =
-
-# Whether to allow asynchronous shader building.
-# 0 (default): Off, 1: On
-use_asynchronous_shaders =
-
-# NVDEC emulation.
-# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
-nvdec_emulation =
-
-# Accelerate ASTC texture decoding.
-# 0: Off, 1 (default): On
-accelerate_astc =
-
-# Decode ASTC textures asynchronously.
-# 0 (default): Off, 1: On
-async_astc =
-
-# Recompress ASTC textures to a different format.
-# 0 (default): Uncompressed, 1: BC1 (Low quality), 2: BC3: (Medium quality)
-async_astc =
-
-# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value
-# 0: Off, 1: On (default)
-use_speed_limit =
-
-# Limits the speed of the game to run no faster than this value as a percentage of target speed
-# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
-speed_limit =
-
-# Whether to use disk based shader cache
-# 0: Off, 1 (default): On
-use_disk_shader_cache =
-
-# Which gpu accuracy level to use
-# 0: Normal, 1 (default): High, 2: Extreme (Very slow)
-gpu_accuracy =
-
-# Whether to use asynchronous GPU emulation
-# 0 : Off (slow), 1 (default): On (fast)
-use_asynchronous_gpu_emulation =
-
-# Inform the guest that GPU operations completed more quickly than they did.
-# 0: Off, 1 (default): On
-use_fast_gpu_time =
-
-# Whether to use garbage collection or not for GPU caches.
-# 0 (default): Off, 1: On
-use_caches_gc =
-
-# The clear color for the renderer. What shows up on the sides of the bottom screen.
-# Must be in range of 0-255. Defaults to 0 for all.
-bg_red =
-bg_blue =
-bg_green =
-
-)"
- R"(
-[Audio]
-# Which audio output engine to use.
-# auto (default): Auto-select
-# cubeb: Cubeb audio engine (if available)
-# sdl2: SDL2 audio engine (if available)
-# null: No audio output
-output_engine =
-
-# Which audio device to use.
-# auto (default): Auto-select
-output_device =
-
-# Output volume.
-# 100 (default): 100%, 0; mute
-volume =
-
-[Data Storage]
-# Whether to create a virtual SD card.
-# 1 (default): Yes, 0: No
-use_virtual_sd =
-
-# Whether or not to enable gamecard emulation
-# 1: Yes, 0 (default): No
-gamecard_inserted =
-
-# Whether or not the gamecard should be emulated as the current game
-# If 'gamecard_inserted' is 0 this setting is irrelevant
-# 1: Yes, 0 (default): No
-gamecard_current_game =
-
-# Path to an XCI file to use as the gamecard
-# If 'gamecard_inserted' is 0 this setting is irrelevant
-# If 'gamecard_current_game' is 1 this setting is irrelevant
-gamecard_path =
-
-[System]
-# Whether the system is docked
-# 1 (default): Yes, 0: No
-use_docked_mode =
-
-# Sets the seed for the RNG generator built into the switch
-# rng_seed will be ignored and randomly generated if rng_seed_enabled is false
-rng_seed_enabled =
-rng_seed =
-
-# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
-# This will auto-increment, with the time set being the time the game is started
-# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
-custom_rtc_enabled =
-custom_rtc =
-
-# Sets the systems language index
-# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese,
-# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French,
-# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese
-language_index =
-
-# The system region that yuzu will use during emulation
-# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
-region_index =
-
-# The system time zone that yuzu will use during emulation
-# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone
-time_zone_index =
-
-# Sets the sound output mode.
-# 0: Mono, 1 (default): Stereo, 2: Surround
-sound_index =
-
-[Miscellaneous]
-# A filter which removes logs below a certain logging level.
-# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
-log_filter = *:Trace
-
-# Use developer keys
-# 0 (default): Disabled, 1: Enabled
-use_dev_keys =
-
-[Debugging]
-# Record frame time data, can be found in the log directory. Boolean value
-record_frame_times =
-# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
-dump_exefs=false
-# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
-dump_nso=false
-# Determines whether or not yuzu will save the filesystem access log.
-enable_fs_access_log=false
-# Enables verbose reporting services
-reporting_services =
-# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode
-# false: Retail/Normal Mode (default), true: Kiosk Mode
-quest_flag =
-# Determines whether debug asserts should be enabled, which will throw an exception on asserts.
-# false: Disabled (default), true: Enabled
-use_debug_asserts =
-# Determines whether unimplemented HLE service calls should be automatically stubbed.
-# false: Disabled (default), true: Enabled
-use_auto_stub =
-# Enables/Disables the macro JIT compiler
-disable_macro_jit=false
-# Determines whether to enable the GDB stub and wait for the debugger to attach before running.
-# false: Disabled (default), true: Enabled
-use_gdbstub=false
-# The port to use for the GDB server, if it is enabled.
-gdbstub_port=6543
-
-[WebService]
-# Whether or not to enable telemetry
-# 0: No, 1 (default): Yes
-enable_telemetry =
-# URL for Web API
-web_api_url = https://api.yuzu-emu.org
-# Username and token for yuzu Web Service
-# See https://profile.yuzu-emu.org/ for more info
-yuzu_username =
-yuzu_token =
-
-[Network]
-# Name of the network interface device to use with yuzu LAN play.
-# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo'
-# e.g. On Windows: 'Ethernet', 'Wi-Fi'
-network_interface =
-
-[AddOns]
-# Used to disable add-ons
-# List of title IDs of games that will have add-ons disabled (separated by '|'):
-title_ids =
-# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
-# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
-)";
-} // namespace DefaultINI
diff --git a/src/yuzu_cmd/sdl_config.cpp b/src/yuzu_cmd/sdl_config.cpp
new file mode 100644
index 000000000..39fd8050c
--- /dev/null
+++ b/src/yuzu_cmd/sdl_config.cpp
@@ -0,0 +1,257 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// SDL will break our main function in yuzu-cmd if we don't define this before adding SDL.h
+#define SDL_MAIN_HANDLED
+#include <SDL.h>
+
+#include "input_common/main.h"
+#include "sdl_config.h"
+
+const std::array<int, Settings::NativeButton::NumButtons> SdlConfig::default_buttons = {
+ SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
+ SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W,
+ SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
+};
+
+const std::array<int, Settings::NativeMotion::NumMotions> SdlConfig::default_motions = {
+ SDL_SCANCODE_7,
+ SDL_SCANCODE_8,
+};
+
+const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> SdlConfig::default_analogs{
+ {
+ {
+ SDL_SCANCODE_UP,
+ SDL_SCANCODE_DOWN,
+ SDL_SCANCODE_LEFT,
+ SDL_SCANCODE_RIGHT,
+ },
+ {
+ SDL_SCANCODE_I,
+ SDL_SCANCODE_K,
+ SDL_SCANCODE_J,
+ SDL_SCANCODE_L,
+ },
+ }};
+
+const std::array<int, 2> SdlConfig::default_stick_mod = {
+ SDL_SCANCODE_D,
+ 0,
+};
+
+const std::array<int, 2> SdlConfig::default_ringcon_analogs{{
+ 0,
+ 0,
+}};
+
+SdlConfig::SdlConfig(const std::optional<std::string> config_path) {
+ Initialize(config_path);
+ ReadSdlValues();
+ SaveSdlValues();
+}
+
+SdlConfig::~SdlConfig() {
+ if (global) {
+ SdlConfig::SaveAllValues();
+ }
+}
+
+void SdlConfig::ReloadAllValues() {
+ Reload();
+ ReadSdlValues();
+ SaveSdlValues();
+}
+
+void SdlConfig::SaveAllValues() {
+ Save();
+ SaveSdlValues();
+}
+
+void SdlConfig::ReadSdlValues() {
+ ReadSdlControlValues();
+}
+
+void SdlConfig::ReadSdlControlValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ Settings::values.players.SetGlobal(!IsCustomConfig());
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ ReadSdlPlayerValues(p);
+ }
+ if (IsCustomConfig()) {
+ EndGroup();
+ return;
+ }
+ ReadDebugControlValues();
+ ReadHidbusValues();
+
+ EndGroup();
+}
+
+void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) {
+ std::string player_prefix;
+ if (type != ConfigType::InputProfile) {
+ player_prefix.append("player_").append(ToString(player_index)).append("_");
+ }
+
+ auto& player = Settings::values.players.GetValue()[player_index];
+ if (IsCustomConfig()) {
+ const auto profile_name =
+ ReadStringSetting(std::string(player_prefix).append("profile_name"));
+ if (profile_name.empty()) {
+ // Use the global input config
+ player = Settings::values.players.GetValue(true)[player_index];
+ return;
+ }
+ }
+
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ auto& player_buttons = player.buttons[i];
+
+ player_buttons = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
+ if (player_buttons.empty()) {
+ player_buttons = default_param;
+ }
+ }
+
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ auto& player_analogs = player.analogs[i];
+
+ player_analogs = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
+ if (player_analogs.empty()) {
+ player_analogs = default_param;
+ }
+ }
+
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+ auto& player_motions = player.motions[i];
+
+ player_motions = ReadStringSetting(
+ std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
+ if (player_motions.empty()) {
+ player_motions = default_param;
+ }
+ }
+}
+
+void SdlConfig::ReadDebugControlValues() {
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
+ debug_pad_buttons = ReadStringSetting(
+ std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param);
+ if (debug_pad_buttons.empty()) {
+ debug_pad_buttons = default_param;
+ }
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
+ debug_pad_analogs = ReadStringSetting(
+ std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param);
+ if (debug_pad_analogs.empty()) {
+ debug_pad_analogs = default_param;
+ }
+ }
+}
+
+void SdlConfig::ReadHidbusValues() {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+ auto& ringcon_analogs = Settings::values.ringcon_analogs;
+
+ ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param);
+ if (ringcon_analogs.empty()) {
+ ringcon_analogs = default_param;
+ }
+}
+
+void SdlConfig::SaveSdlValues() {
+ SaveSdlControlValues();
+
+ WriteToIni();
+}
+
+void SdlConfig::SaveSdlControlValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+ Settings::values.players.SetGlobal(!IsCustomConfig());
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ SaveSdlPlayerValues(p);
+ }
+ if (IsCustomConfig()) {
+ EndGroup();
+ return;
+ }
+ SaveDebugControlValues();
+ SaveHidbusValues();
+
+ EndGroup();
+}
+
+void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) {
+ std::string player_prefix;
+ if (type != ConfigType::InputProfile) {
+ player_prefix = std::string("player_").append(ToString(player_index)).append("_");
+ }
+
+ const auto& player = Settings::values.players.GetValue()[player_index];
+ if (IsCustomConfig() && player.profile_name.empty()) {
+ // No custom profile selected
+ return;
+ }
+
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
+ player.buttons[i], std::make_optional(default_param));
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
+ player.analogs[i], std::make_optional(default_param));
+ }
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+ WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
+ player.motions[i], std::make_optional(default_param));
+ }
+}
+
+void SdlConfig::SaveDebugControlValues() {
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
+ Settings::values.debug_pad_buttons[i], std::make_optional(default_param));
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
+ Settings::values.debug_pad_analogs[i], std::make_optional(default_param));
+ }
+}
+
+void SdlConfig::SaveHidbusValues() {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+ WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
+ std::make_optional(default_param));
+}
+
+std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) {
+ return Settings::values.linkage.by_category[category];
+}
diff --git a/src/yuzu_cmd/sdl_config.h b/src/yuzu_cmd/sdl_config.h
new file mode 100644
index 000000000..1fd1c692d
--- /dev/null
+++ b/src/yuzu_cmd/sdl_config.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "frontend_common/config.h"
+
+class SdlConfig final : public Config {
+public:
+ explicit SdlConfig(std::optional<std::string> config_path);
+ ~SdlConfig() override;
+
+ void ReloadAllValues() override;
+ void SaveAllValues() override;
+
+protected:
+ void ReadSdlValues();
+ void ReadSdlPlayerValues(std::size_t player_index);
+ void ReadSdlControlValues();
+ void ReadHidbusValues() override;
+ void ReadDebugControlValues() override;
+ void ReadPathValues() override {}
+ void ReadShortcutValues() override {}
+ void ReadUIValues() override {}
+ void ReadUIGamelistValues() override {}
+ void ReadUILayoutValues() override {}
+ void ReadMultiplayerValues() override {}
+
+ void SaveSdlValues();
+ void SaveSdlPlayerValues(std::size_t player_index);
+ void SaveSdlControlValues();
+ void SaveHidbusValues() override;
+ void SaveDebugControlValues() override;
+ void SavePathValues() override {}
+ void SaveShortcutValues() override {}
+ void SaveUIValues() override {}
+ void SaveUIGamelistValues() override {}
+ void SaveUILayoutValues() override {}
+ void SaveMultiplayerValues() override {}
+
+ std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
+
+public:
+ static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
+ static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
+ static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
+ static const std::array<int, 2> default_stick_mod;
+ static const std::array<int, 2> default_ringcon_analogs;
+};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 087cfaa26..a81635fa4 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -29,10 +29,11 @@
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/telemetry_session.h"
+#include "frontend_common/config.h"
#include "input_common/main.h"
#include "network/network.h"
+#include "sdl_config.h"
#include "video_core/renderer_base.h"
-#include "yuzu_cmd/config.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h"
@@ -62,6 +63,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
+#ifdef __unix__
+#include "common/linux/gamemode.h"
+#endif
+
static void PrintHelp(const char* argv0) {
std::cout << "Usage: " << argv0
<< " [options] <filename>\n"
@@ -300,7 +305,7 @@ int main(int argc, char** argv) {
}
}
- Config config{config_path};
+ SdlConfig config{config_path};
// apply the log_filter setting
// the logger was initialized before and doesn't pick up the filter on its own
@@ -424,6 +429,10 @@ int main(int argc, char** argv) {
exit(0);
});
+#ifdef __unix__
+ Common::Linux::StartGamemode();
+#endif
+
void(system.Run());
if (system.DebuggerEnabled()) {
system.InitializeDebugger();
@@ -435,6 +444,10 @@ int main(int argc, char** argv) {
void(system.Pause());
system.ShutdownMainProcess();
+#ifdef __unix__
+ Common::Linux::StopGamemode();
+#endif
+
detached_tasks.WaitForAllTasks();
return 0;
}